From ae28ce7a11de542c8d9598670c365bd7f37447b5 Mon Sep 17 00:00:00 2001 From: Osama Khalid Date: Fri, 23 Jan 2026 21:27:54 -0500 Subject: [PATCH] fix: add bindings to manage heuristic dissectors --- lib/wiregasm/bindings.cpp | 32 +++++++++ lib/wiregasm/wiregasm.cpp | 135 ++++++++++++++++++++++++++++++++++++++ lib/wiregasm/wiregasm.h | 56 +++++++++++++++- samples/nr.pcapng | Bin 0 -> 12300 bytes src/index.test.ts | 111 +++++++++++++++++++++++++++++++ src/index.ts | 59 +++++++++++++++++ src/types.ts | 64 ++++++++++++++++++ 7 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 samples/nr.pcapng diff --git a/lib/wiregasm/bindings.cpp b/lib/wiregasm/bindings.cpp index 1d5923e..aa9bbbc 100644 --- a/lib/wiregasm/bindings.cpp +++ b/lib/wiregasm/bindings.cpp @@ -20,6 +20,13 @@ EMSCRIPTEN_BINDINGS(Wiregasm) { emscripten::function("listPreferences", &wg_list_preferences); emscripten::function("applyPreferences", &wg_prefs_apply_all); emscripten::function("wiresharkVersion", &wg_ws_version); + // Protocol enable/disable functions + emscripten::function("listProtocols", &wg_list_protocols); + emscripten::function("setProtocolEnabled", &wg_set_protocol_enabled); + emscripten::function("setProtocolEnabledByName", &wg_set_protocol_enabled_by_name); + // Heuristic dissector enable/disable functions + emscripten::function("listHeuristicDissectors", &wg_list_heuristic_dissectors); + emscripten::function("setHeuristicEnabled", &wg_set_heuristic_enabled); } EMSCRIPTEN_BINDINGS(DissectSession) { @@ -306,3 +313,28 @@ EMSCRIPTEN_BINDINGS(ExportObject) { EMSCRIPTEN_BINDINGS(MapInput) { register_map("MapInput"); } + +EMSCRIPTEN_BINDINGS(ProtocolInfo) { + value_object("ProtocolInfo") + .field("id", &ProtocolInfo::id) + .field("name", &ProtocolInfo::name) + .field("long_name", &ProtocolInfo::long_name) + .field("enabled", &ProtocolInfo::enabled) + .field("enabled_by_default", &ProtocolInfo::enabled_by_default) + .field("can_toggle", &ProtocolInfo::can_toggle); + + register_vector("VectorProtocolInfo"); +} + +EMSCRIPTEN_BINDINGS(HeuristicInfo) { + value_object("HeuristicInfo") + .field("short_name", &HeuristicInfo::short_name) + .field("display_name", &HeuristicInfo::display_name) + .field("list_name", &HeuristicInfo::list_name) + .field("protocol_name", &HeuristicInfo::protocol_name) + .field("protocol_id", &HeuristicInfo::protocol_id) + .field("enabled", &HeuristicInfo::enabled) + .field("enabled_by_default", &HeuristicInfo::enabled_by_default); + + register_vector("VectorHeuristicInfo"); +} diff --git a/lib/wiregasm/wiregasm.cpp b/lib/wiregasm/wiregasm.cpp index 7afa022..c08a33f 100644 --- a/lib/wiregasm/wiregasm.cpp +++ b/lib/wiregasm/wiregasm.cpp @@ -1,7 +1,9 @@ #include "wiregasm.h" #include "lib.h" +#include #include #include +#include #include #include #include @@ -549,3 +551,136 @@ IoGraphResult DissectSession::iograph(MapInput args) { } return wg_session_process_iograph(&this->capture_file, args); } + +// ============================================================================ +// Protocol enable/disable functions +// ============================================================================ + +vector wg_list_protocols() { + vector result; + void *cookie = NULL; + int proto_id; + + for (proto_id = proto_get_first_protocol(&cookie); proto_id != -1; + proto_id = proto_get_next_protocol(&cookie)) { + + protocol_t *protocol = find_protocol_by_id(proto_id); + if (protocol == NULL) + continue; + + ProtocolInfo info; + info.id = proto_id; + + const char *filter_name = proto_get_protocol_filter_name(proto_id); + if (filter_name) { + info.name = string(filter_name); + } + + const char *long_name = proto_get_protocol_long_name(protocol); + if (long_name) { + info.long_name = string(long_name); + } + + info.enabled = proto_is_protocol_enabled(protocol); + info.enabled_by_default = proto_is_protocol_enabled_by_default(protocol); + info.can_toggle = proto_can_toggle_protocol(proto_id); + + result.push_back(info); + } + + return result; +} + +bool wg_set_protocol_enabled(int proto_id, bool enabled) { + protocol_t *protocol = find_protocol_by_id(proto_id); + if (protocol == NULL) { + return false; + } + + if (!proto_can_toggle_protocol(proto_id)) { + return false; + } + + proto_set_decoding(proto_id, enabled); + return true; +} + +bool wg_set_protocol_enabled_by_name(string proto_name, bool enabled) { + int proto_id = proto_get_id_by_filter_name(proto_name.c_str()); + if (proto_id == -1) { + return false; + } + + return wg_set_protocol_enabled(proto_id, enabled); +} + +// ============================================================================ +// Heuristic dissector enable/disable functions +// ============================================================================ + +// Callback data for collecting heuristic dissectors +struct HeurCollectorData { + vector *results; +}; + +// Callback for each heuristic entry within a table +static void heur_entry_collector_cb(const char *table_name, struct heur_dtbl_entry *entry, void *user_data) { + HeurCollectorData *data = (HeurCollectorData *)user_data; + + HeuristicInfo info; + + if (entry->short_name) { + info.short_name = string(entry->short_name); + } + + if (entry->display_name) { + info.display_name = string(entry->display_name); + } + + if (entry->list_name) { + info.list_name = string(entry->list_name); + } + + if (entry->protocol) { + const char *proto_name = proto_get_protocol_short_name(entry->protocol); + if (proto_name) { + info.protocol_name = string(proto_name); + } + // Get the protocol ID so we can link heuristics to their parent protocol + info.protocol_id = proto_get_id(entry->protocol); + } else { + info.protocol_id = -1; + } + + info.enabled = entry->enabled; + info.enabled_by_default = entry->enabled_by_default; + + data->results->push_back(info); +} + +// Callback for each heuristic table +static void heur_table_collector_cb(const char *table_name, struct heur_dissector_list *table, void *user_data) { + // Iterate all entries in this table + heur_dissector_table_foreach(table_name, heur_entry_collector_cb, user_data); +} + +vector wg_list_heuristic_dissectors() { + vector result; + HeurCollectorData data; + data.results = &result; + + // Iterate all heuristic tables, then entries within each + dissector_all_heur_tables_foreach_table(heur_table_collector_cb, &data, NULL); + + return result; +} + +bool wg_set_heuristic_enabled(string short_name, bool enabled) { + heur_dtbl_entry_t *entry = find_heur_dissector_by_unique_short_name(short_name.c_str()); + if (entry == NULL) { + return false; + } + + entry->enabled = enabled; + return true; +} diff --git a/lib/wiregasm/wiregasm.h b/lib/wiregasm/wiregasm.h index f2b1df9..3faedcb 100644 --- a/lib/wiregasm/wiregasm.h +++ b/lib/wiregasm/wiregasm.h @@ -241,6 +241,51 @@ struct DownloadResponse { Download download; }; +// Protocol info struct for listing all protocols +struct ProtocolInfo { + int id; // protocol ID + string name; // protocol filter name (e.g., "tcp") + string long_name; // protocol long name (e.g., "Transmission Control Protocol") + bool enabled; // whether protocol is currently enabled + bool enabled_by_default; // whether protocol is enabled by default + bool can_toggle; // whether protocol can be toggled (some can't) +}; + +// Heuristic dissector info struct for listing all heuristic dissectors +struct HeuristicInfo { + string short_name; // unique short name (e.g., "mac_nr_udp") + string display_name; // display name for UI + string list_name; // parent dissector table name (e.g., "udp") + string protocol_name; // associated protocol name + int protocol_id; // protocol ID this heuristic belongs to + bool enabled; // whether heuristic is currently enabled + bool enabled_by_default; // whether heuristic is enabled by default +}; + +// Unified enabled item for building a hierarchical UI like Wireshark's Enabled Protocols dialog +// Can represent either a protocol or a heuristic dissector +struct EnabledProtocolItem { + // Common fields + string name; // display name (short_name for protocols, display_name for heuristics) + string description; // long description + bool enabled; // whether currently enabled + bool enabled_by_default; // whether enabled by default + bool can_toggle; // whether can be toggled + + // Type identification + bool is_heuristic; // true if this is a heuristic, false if protocol + + // Protocol-specific (when is_heuristic = false) + int protocol_id; // protocol ID (only valid for protocols) + + // Heuristic-specific (when is_heuristic = true) + string heuristic_short_name; // unique short name for enabling (only valid for heuristics) + string list_name; // dissector table this heuristic listens on (e.g., "udp") + + // Child heuristics (only valid for protocols) + vector heuristics; // heuristic dissectors belonging to this protocol +}; + // globals bool wg_init(); @@ -259,6 +304,15 @@ vector wg_list_preferences(string module_name); string wg_get_upload_dir(); string wg_get_plugins_dir(); +// Protocol enable/disable functions +vector wg_list_protocols(); +bool wg_set_protocol_enabled(int proto_id, bool enabled); +bool wg_set_protocol_enabled_by_name(string proto_name, bool enabled); + +// Heuristic dissector enable/disable functions +vector wg_list_heuristic_dissectors(); +bool wg_set_heuristic_enabled(string short_name, bool enabled); + class DissectSession { private: string path; @@ -277,4 +331,4 @@ class DissectSession { ~DissectSession(); }; -#endif +#endif \ No newline at end of file diff --git a/samples/nr.pcapng b/samples/nr.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..b7c5bfb6731fd47086e8a1a168151906c1c2581d GIT binary patch literal 12300 zcmd^_30xD`_Q%gmR!9h8Z!Jq$L{LMQCHa%+F1t6(J}7oO<{AZMw?l`v}sHb z=8XV`KK?W?(J)*dRfRnS;75SJFQ>DqG{5$%b3YV(?OYf?m^EhYv#8GG=0kb!7hG#^ z?Q96s%L+)@oO6}gl{nf9=BEGetS`^kv^$xxU$7?Juc*kR3Vch?ls~Kuntjo|X>sG7 z>zi!aB}HSf4y@-p)Kz+ocU&@t}nO`&IpMm_6a>migrYyl<_01Pyyzxoh=+IOs{ zr!LQZ5*_H$N4nf;G`vgN2edv7Vqp5+{QHvIi-Oa}*b;l(qN=KzNt1V*-Paezf!Xt2 z-fPMZEQ>_f>%tGC}%Ctl$=+0XF$-{N;KRBxx zR~f^D^=Cq@iJ;n2)t{*-_{?&ses|S1JM^)u+lbbIH zwVJl%TEp6d7q3md9x&GW$9sh<&aKYAbJw(OakOBWrZ@>dlZ9Z9b=MzIGUdPeiR3=ocB)rTpwDd$~d!+r&##Ucm|I+Nx=j&c(hQ-x( zO+MtdX~W$?TokF{G(Dwdwx0c}`IWO;0qP5?si5cFDD&Zsod!RkrVjJ8Oy) z_lMm&h!f5yYb>b0>|=UqP1#E@q4)rSi3y-qm7#j zZtNVFSHqB>+x&yUiMbbt1s`$0))hFz!8IuD!Im5AJIp(G46*lGek^}ZK*u(}w(y}J zgCWcQ#s0w3#`ytzhE2BTN<{(Z3G-=={a2;?*?%EEEAMy0>k0qU5p-i*>vXW7 zIlyWKORAAE3t&82JilYIU*_s-h*kosIH~Iyzc*9|dQ1o+Ue1D;RWhcP$Y@r1bg6XF zZO?XoCz&*}(6u(#MZhs+(clAdU)-~`D3#^|*cXhb61Wv)F(SLvBY{;Q5xGIONw zp<~?D8-6Y){WPt2$^Ng`yUINS5CMk>APtb9hJP18Bq0G2SO6>F@jJ@--zJPvEMzzq zZ{4XI@&k{Na+EHl0XVJ5z9pKwjW9tmcWaSRU*s7l9#mfa z8%`;J5ENij6kt3s`)O|nZk^fd*%AmBbeTip_x?;AiCcPdMdOs+q_k+6shnaR! zXr_^hnS!w9SUoyi?K2MejufDpViF)esm~mkmE;++ecqy5Ux0*Hlxev28ydl2|j^~@lt8W4*)!(F}o9@cCTYs9%m7I_^F!bKyqku@fWfuux0 zP(px4^msrq(enVhzzJPo&T1+J<4O`=JJVhJ9$j`26|}P5HgEyVLeYuB>{n?C6AC7#e$keEBq{pBqstzbOe-F5OT1FL z$L?UAK=~KVh0Ca%6dc!$HysN2R6E?5w zwQ_$FY{3xeYNye?lysg1oLs_7#Nr+`b_G{cM+(><+{AmY+4T)Vlg}x0up9hg7RmPm zLrT1clL89TfpC7lcHSi2UT#OyYZv zebDUB0LV>3v^1M+DUzM4ky(Ogt^f$br6_+XV2DOBS->Z#NVlTMw=!i#&`5&)NSHJN z-;=vr5SIioy4VyY4p{;)Xa!k(e>X|2fDr_-1bA)aPgBqp$DlH05=Vp%klV~ig0LkC zm_aymc+0Q24MDOloYVhNt~WXS;-iX7qomvTgX-hae*?601&I%)aMJZ<^thxstd<|-DmOt{f7 zKrZwx4fiF~-m~DCsbb|B$(gUcO~i*Dx<49-UP{JO?<@diwtOX5w}r4 zk}WwV{ZYanIVf&rtVjg;Lswq?MUnd>5<|-|Re6QiBr3)$3HU^gj&WCSin`mocZuMk zTq|djKbDCrkgmv_+6XP3_cW9flYm{|D554?FuXTRAPL$@=>A~1wU3p84?f~!>C%iM z%m?-K#ljubgk#B?_@D*UK)q&(YVQ|l$t3C6&7|NBVlw0EoGaS$@wt)@0SJDb5A|44yDP3}8>`jb zu`+yvV&yrkQU|e8Eql zSiiY`!8T!bst#-_bRwJL({{<3Sm(2{uX2j5I&G#pS!xxQ6@dvxr6svP~{=X7Ne{l}f{YJN* f(t+RbeisjKUJH#W$z`i8{~va-P)+M)!~XgwOBe^S literal 0 HcmV?d00001 diff --git a/src/index.test.ts b/src/index.test.ts index 618793d..baa006f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -946,6 +946,117 @@ describe("Wiregasm Library - Reloading Lua Plugins", () => { }); }); +describe("Wiregasm Library - Heuristic Dissectors", () => { + const wg = new Wiregasm(); + + beforeAll(async () => { + return wg.init(loadWiregasm, await buildCompressedOverrides()); + }); + + afterAll(() => { + wg.destroy(); + }); + + test("list heuristic dissectors works", async () => { + const heuristics = wg.list_heuristic_dissectors(); + expect(heuristics.length).toBeGreaterThan(0); + + // Find mac_nr_udp heuristic + const macNrUdp = heuristics.find((h) => h.short_name === "mac_nr_udp"); + expect(macNrUdp).toBeDefined(); + expect(macNrUdp?.display_name).toBe("MAC-NR over UDP"); + expect(macNrUdp?.list_name).toBe("udp"); + expect(macNrUdp?.enabled_by_default).toBe(false); + }); + + test("list protocols works", async () => { + const protocols = wg.list_protocols(); + expect(protocols.length).toBeGreaterThan(0); + + // Find MAC-NR protocol + const macNr = protocols.find((p) => p.name === "mac-nr"); + expect(macNr).toBeDefined(); + expect(macNr?.long_name).toContain("MAC"); + }); + + test("enable/disable heuristic dissector works", async () => { + // Check initial state + let heuristics = wg.list_heuristic_dissectors(); + let macNrUdp = heuristics.find((h) => h.short_name === "mac_nr_udp"); + expect(macNrUdp?.enabled).toBe(false); + + // Enable the heuristic + const result = wg.set_heuristic_enabled("mac_nr_udp", true); + expect(result).toBe(true); + + // Verify it's enabled + heuristics = wg.list_heuristic_dissectors(); + macNrUdp = heuristics.find((h) => h.short_name === "mac_nr_udp"); + expect(macNrUdp?.enabled).toBe(true); + + // Disable it again + wg.set_heuristic_enabled("mac_nr_udp", false); + heuristics = wg.list_heuristic_dissectors(); + macNrUdp = heuristics.find((h) => h.short_name === "mac_nr_udp"); + expect(macNrUdp?.enabled).toBe(false); + }); + + test("mac_nr_udp heuristic dissector decodes NR pcap correctly", async () => { + // Enable the mac_nr_udp heuristic before loading + wg.set_heuristic_enabled("mac_nr_udp", true); + + // Load the NR capture file + const data = await fs.readFile("samples/nr.pcapng"); + const ret = wg.load("nr.pcapng", data); + expect(ret.code).toEqual(0); + + // Get the first frame and check if MAC-NR is detected + const frame = wg.frame(1); + expect(frame.number).toEqual(1); + + // Find the MAC-NR protocol in the tree + let foundMacNr = false; + for (let i = 0; i < frame.tree.size(); i++) { + const tree = frame.tree.get(i); + if (tree.label.includes("MAC-NR")) { + foundMacNr = true; + break; + } + } + + expect(foundMacNr).toBe(true); + + // Disable the heuristic for cleanup + wg.set_heuristic_enabled("mac_nr_udp", false); + }); + + test("without heuristic enabled, MAC-NR is not detected", async () => { + // Make sure heuristic is disabled + wg.set_heuristic_enabled("mac_nr_udp", false); + + // Load the NR capture file + const data = await fs.readFile("samples/nr.pcapng"); + const ret = wg.load("nr2.pcapng", data); + expect(ret.code).toEqual(0); + + // Get the first frame + const frame = wg.frame(1); + expect(frame.number).toEqual(1); + + // MAC-NR should NOT be in the tree (only UDP) + let foundMacNr = false; + for (let i = 0; i < frame.tree.size(); i++) { + const tree = frame.tree.get(i); + if (tree.label.includes("MAC-NR")) { + foundMacNr = true; + break; + } + } + + expect(foundMacNr).toBe(false); + }); +}); + describe("Wiregasm Library - IoGraph", () => { const wg = new Wiregasm(); diff --git a/src/index.ts b/src/index.ts index 2cd94eb..4c31495 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,11 +7,13 @@ import { Follow, Frame, FramesResponse, + HeuristicInfo, LoadResponse, MapInput, Pref, PrefModule, PrefSetResult, + ProtocolInfo, TapConvResponse, TapExportObjectResponse, TapResponse, @@ -287,6 +289,63 @@ export class Wiregasm { is_conv_tap(tap: any): tap is TapConvResponse { return tap instanceof this.lib.TapConvResponse; } + + // Protocol enable/disable methods + + /** + * List all registered protocols + * + * @returns Array of all protocols with their enabled state + */ + list_protocols(): ProtocolInfo[] { + const vec = this.lib.listProtocols(); + return vectorToArray(vec); + } + + /** + * Enable or disable a protocol by its ID + * + * @param protoId Protocol ID + * @param enabled Whether to enable or disable the protocol + * @returns true if successful, false otherwise + */ + set_protocol_enabled(protoId: number, enabled: boolean): boolean { + return this.lib.setProtocolEnabled(protoId, enabled); + } + + /** + * Enable or disable a protocol by its filter name + * + * @param protoName Protocol filter name (e.g., "tcp", "udp") + * @param enabled Whether to enable or disable the protocol + * @returns true if successful, false otherwise + */ + set_protocol_enabled_by_name(protoName: string, enabled: boolean): boolean { + return this.lib.setProtocolEnabledByName(protoName, enabled); + } + + // Heuristic dissector enable/disable methods + + /** + * List all registered heuristic dissectors + * + * @returns Array of all heuristic dissectors with their enabled state + */ + list_heuristic_dissectors(): HeuristicInfo[] { + const vec = this.lib.listHeuristicDissectors(); + return vectorToArray(vec); + } + + /** + * Enable or disable a heuristic dissector by its unique short name + * + * @param shortName Unique short name of the heuristic dissector (e.g., "mac_nr_udp") + * @param enabled Whether to enable or disable the heuristic + * @returns true if successful, false otherwise + */ + set_heuristic_enabled(shortName: string, enabled: boolean): boolean { + return this.lib.setHeuristicEnabled(shortName, enabled); + } } export * from "./types"; diff --git a/src/types.ts b/src/types.ts index 3bc3ec6..442460d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -244,6 +244,25 @@ export interface IoGraph { items: Vector; } +export interface ProtocolInfo { + id: number; + name: string; + long_name: string; + enabled: boolean; + enabled_by_default: boolean; + can_toggle: boolean; +} + +export interface HeuristicInfo { + short_name: string; + display_name: string; + list_name: string; + protocol_name: string; + protocol_id: number; // protocol ID this heuristic belongs to (-1 if none) + enabled: boolean; + enabled_by_default: boolean; +} + export interface DissectSession { /** * Free up any memory used by the session @@ -450,6 +469,51 @@ export interface WiregasmLib extends EmscriptenModule { * @param length Length of the data */ upload(file_name: string, data_ptr: number, length: number): string; + + // Protocol enable/disable functions + + /** + * List all registered protocols + * + * @returns List of all protocols with their enabled state + */ + listProtocols(): Vector; + + /** + * Enable or disable a protocol by its ID + * + * @param protoId Protocol ID + * @param enabled Whether to enable or disable the protocol + * @returns true if successful, false otherwise + */ + setProtocolEnabled(protoId: number, enabled: boolean): boolean; + + /** + * Enable or disable a protocol by its filter name + * + * @param protoName Protocol filter name (e.g., "tcp", "udp") + * @param enabled Whether to enable or disable the protocol + * @returns true if successful, false otherwise + */ + setProtocolEnabledByName(protoName: string, enabled: boolean): boolean; + + // Heuristic dissector enable/disable functions + + /** + * List all registered heuristic dissectors + * + * @returns List of all heuristic dissectors with their enabled state + */ + listHeuristicDissectors(): Vector; + + /** + * Enable or disable a heuristic dissector by its unique short name + * + * @param shortName Unique short name of the heuristic dissector (e.g., "mac_nr_udp") + * @param enabled Whether to enable or disable the heuristic + * @returns true if successful, false otherwise + */ + setHeuristicEnabled(shortName: string, enabled: boolean): boolean; } export type WiregasmLoader = (