import { create } from "@bufbuild/protobuf"; import * as Protobuf from "@meshtastic/protobufs"; import { describe, expect, it } from "vitest"; import { MeshClient } from "../../core/client/MeshClient.ts"; import { createFakeTransport } from "../../core/testing/createFakeTransport.ts"; import { ChannelNumber } from "../../core/types.ts"; const KEY_A = new Uint8Array([1, 2, 3, 4]); const KEY_B = new Uint8Array([9, 8, 7, 6]); function nodeInfo(num: number, key?: Uint8Array, longName = `Node ${num}`) { return create(Protobuf.Mesh.NodeInfoSchema, { num, user: create(Protobuf.Mesh.UserSchema, { id: `!${num.toString(16)}`, longName, shortName: longName.slice(0, 4), publicKey: key ?? new Uint8Array(), }), }); } describe("NodesClient PKI error tracking", () => { it("MISMATCH_PKI when an existing node re-presents with a different key", () => { const { transport } = createFakeTransport(); const client = new MeshClient({ transport }); client.events.onNodeInfoPacket.dispatch(nodeInfo(1, KEY_A, "First")); expect(client.nodes.errorFor(1)).toBeUndefined(); client.events.onNodeInfoPacket.dispatch(nodeInfo(1, KEY_B, "Impostor")); expect(client.nodes.errorFor(1)?.error).toBe("MISMATCH_PKI"); // Original node is preserved. expect(client.nodes.byNum(1)?.user?.longName).toBe("First"); }); it("DUPLICATE_PKI when a new node claims another node's public key", () => { const { transport } = createFakeTransport(); const client = new MeshClient({ transport }); client.events.onNodeInfoPacket.dispatch(nodeInfo(1, KEY_A)); client.events.onNodeInfoPacket.dispatch(nodeInfo(2, KEY_A)); expect(client.nodes.errorFor(2)?.error).toBe("DUPLICATE_PKI"); // The duplicate is rejected — only the first node remains. expect(client.nodes.list.value.map((n) => n.num)).toEqual([1]); }); it("clears the error on a subsequent successful update", () => { const { transport } = createFakeTransport(); const client = new MeshClient({ transport }); // Seed with empty key so any incoming key is allowed. client.events.onNodeInfoPacket.dispatch(nodeInfo(1)); client.events.onNodeInfoPacket.dispatch(nodeInfo(1, KEY_A)); // Trigger a routing-error error. client.nodes.setError(1, Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY); expect(client.nodes.hasError(1)).toBe(true); // A clean refresh with the same key should clear the error. client.events.onNodeInfoPacket.dispatch(nodeInfo(1, KEY_A)); expect(client.nodes.hasError(1)).toBe(false); }); it("records routing PKI errors against the originating node", () => { const { transport } = createFakeTransport(); const client = new MeshClient({ transport }); client.events.onRoutingPacket.dispatch({ id: 1, from: 99, to: 0xffffffff, channel: ChannelNumber.Primary, type: "broadcast", rxTime: new Date(), data: create(Protobuf.Mesh.RoutingSchema, { variant: { case: "errorReason", value: Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY, }, }), }); expect(client.nodes.errorFor(99)?.error).toBe(Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY); }); it("clearError / clearAllErrors", () => { const { transport } = createFakeTransport(); const client = new MeshClient({ transport }); client.nodes.setError(1, "MISMATCH_PKI"); client.nodes.setError(2, "DUPLICATE_PKI"); expect(client.nodes.errors.value.length).toBe(2); client.nodes.clearError(1); expect(client.nodes.errors.value.length).toBe(1); client.nodes.clearAllErrors(); expect(client.nodes.errors.value.length).toBe(0); }); });