frameassembly-wasm 1.5.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -26
- package/frameassembly_wasm.d.ts +16 -3
- package/frameassembly_wasm.js +23 -4
- package/frameassembly_wasm_bg.wasm +0 -0
- package/package.json +18 -3
package/README.md
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
# FrameAssembly WebAssembly Compiler (`frameassembly-wasm`
|
|
1
|
+
# FrameAssembly WebAssembly Compiler (`frameassembly-wasm` v2.0.0)
|
|
2
2
|
|
|
3
3
|
[](https://frameassembly.ai.studio/)
|
|
4
4
|
[](https://www.npmjs.com/package/frameassembly-wasm)
|
|
5
5
|
[](https://github.com/SteveWinschel/FrameAssembly/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
[**Try in Browser (`frameassembly.ai.studio`)**](https://frameassembly.ai.studio/) • [**Installation**](#installation) • [**Vite, React & Next.js**](#initialization-vite-react-nextjs--google-ai-studio) • [**
|
|
7
|
+
[**Try in Browser (`frameassembly.ai.studio`)**](https://frameassembly.ai.studio/) • [**Installation**](#installation) • [**Vite, React & Next.js**](#initialization-vite-react-nextjs--google-ai-studio) • [**API Reference**](#api-reference)
|
|
8
8
|
|
|
9
|
-
WebAssembly compiler, `.pcap`/`.pcapng`-to-`.fasm` decompiler,
|
|
9
|
+
WebAssembly compiler, `.pcap`/`.pcapng`-to-`.fasm` decompiler, packet ladder inspector, and UTF-16 IDE diagnostics for **FrameAssembly (`.fasm` v2.0.0)** — compiling `.fasm` code into `.pcapng` packet captures and decompiling existing captures back into `.fasm` code across 81 L2–L7 protocols in browser applications, VS Code Desktop & Web extensions, web cyber ranges, and Node.js test suites. Powers the official FrameAssembly interactive browser playground at [**frameassembly.ai.studio**](https://frameassembly.ai.studio/) and the official VS Code extension (`SteveWinschel.frameassembly-vscode`). *(Note: FrameAssembly is a network packet specification compiler, not x86 Flat Assembler).*
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Client-Side `.fasm` to `.pcapng` Compilation & PCAP Decompilation in the Browser & Node.js
|
|
14
14
|
|
|
15
|
-
`frameassembly-wasm` compiles the FrameAssembly Rust engine to WebAssembly, turning `.fasm` code into `.pcapng` byte streams, decompiling binary `.pcap` and `.pcapng` captures into `.fasm` code via `reversePcap(pcapBytes)`, and emitting real-time Monaco
|
|
15
|
+
`frameassembly-wasm` compiles the FrameAssembly Rust engine to WebAssembly, turning `.fasm` code into `.pcapng` byte streams, decompiling binary `.pcap` and `.pcapng` captures into strict-lowercase `.fasm` code via `reverse_pcap(pcapBytes)` / `reversePcap(pcapBytes)`, and emitting real-time Monaco, CodeMirror, and VS Code diagnostics directly in the browser or Node.js without a backend server:
|
|
16
16
|
|
|
17
17
|
```frameassembly
|
|
18
18
|
host client { ip 192.168.1.10 mac 00:11:22:33:44:55 }
|
|
19
19
|
host server { ip 8.8.8.8 mac aa:bb:cc:dd:ee:ff }
|
|
20
20
|
|
|
21
21
|
flow dns_query(c, s) {
|
|
22
|
-
c -> s dns query txid 0x1234 qname "example.com" qtype "
|
|
23
|
-
c <- s dns response txid 0x1234 qname "example.com" qtype "
|
|
22
|
+
c -> s dns query txid 0x1234 qname "example.com" qtype "a" wait 1ms
|
|
23
|
+
c <- s dns response txid 0x1234 qname "example.com" qtype "a" rdata 93.184.216.34 wait 1ms
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
compile {
|
|
@@ -43,27 +43,27 @@ const source = `host client { ip 192.168.1.10 mac 00:11:22:33:44:55 }
|
|
|
43
43
|
host server { ip 8.8.8.8 mac aa:bb:cc:dd:ee:ff }
|
|
44
44
|
|
|
45
45
|
flow dns_query(c, s) {
|
|
46
|
-
c -> s dns query txid 0x1234 qname "example.com" qtype "
|
|
47
|
-
c <- s dns response txid 0x1234 qname "example.com" qtype "
|
|
46
|
+
c -> s dns query txid 0x1234 qname "example.com" qtype "a" wait 1ms
|
|
47
|
+
c <- s dns response txid 0x1234 qname "example.com" qtype "a" rdata 93.184.216.34 wait 1ms
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
compile {
|
|
51
51
|
dns_query(client, server)
|
|
52
52
|
}`;
|
|
53
53
|
|
|
54
|
-
// 1. Validate syntax & semantics for Monaco / CodeMirror editor markers
|
|
54
|
+
// 1. Validate syntax & semantics for Monaco / CodeMirror / VS Code editor markers
|
|
55
55
|
const diagnostics = validate_fasm(source);
|
|
56
56
|
if (diagnostics.length === 0) {
|
|
57
|
-
// 2. Inspect individual packet
|
|
57
|
+
// 2. Inspect individual packet ladder rows, protocol labels, summaries, and hex payloads
|
|
58
58
|
const previews = inspect_fasm(source);
|
|
59
|
-
console.log(`Synthesized ${previews.length} frames.`);
|
|
59
|
+
console.log(`Synthesized ${previews.length} frames (${previews[0].protocol}: ${previews[0].summary}).`);
|
|
60
60
|
|
|
61
61
|
// 3. Compile to binary PCAPNG Uint8Array
|
|
62
62
|
const pcapngBytes = compile_fasm_to_pcapng(source);
|
|
63
63
|
const blob = new Blob([pcapngBytes], { type: "application/vnd.tcpdump.pcap" });
|
|
64
64
|
console.log(`Generated PCAPNG: ${blob.size} bytes`);
|
|
65
65
|
|
|
66
|
-
// 4. Decompile binary PCAP/PCAPNG bytes back into
|
|
66
|
+
// 4. Decompile binary PCAP/PCAPNG bytes back into strict-lowercase .fasm code
|
|
67
67
|
const reversed = reversePcap(pcapngBytes);
|
|
68
68
|
console.log(`Reversed ${reversed.packetCount} frames across ${reversed.hostCount} hosts:\n${reversed.fasm}`);
|
|
69
69
|
}
|
|
@@ -90,10 +90,10 @@ npm install frameassembly-wasm
|
|
|
90
90
|
inspect_fasm,
|
|
91
91
|
compile_fasm_to_pcapng,
|
|
92
92
|
reversePcap,
|
|
93
|
-
} from "https://cdn.jsdelivr.net/npm/frameassembly-wasm@
|
|
93
|
+
} from "https://cdn.jsdelivr.net/npm/frameassembly-wasm@2.0.0/frameassembly_wasm.js";
|
|
94
94
|
|
|
95
95
|
await init();
|
|
96
|
-
console.log("FrameAssembly WebAssembly engine
|
|
96
|
+
console.log("FrameAssembly WebAssembly engine v2.0.0 loaded successfully.");
|
|
97
97
|
</script>
|
|
98
98
|
```
|
|
99
99
|
|
|
@@ -124,7 +124,7 @@ await init({ module_or_path: wasmUrl });
|
|
|
124
124
|
## API Reference
|
|
125
125
|
|
|
126
126
|
### `validate_fasm(source: string): IdeDiagnostic[]` / `validateFasm(source: string): IdeDiagnostic[]`
|
|
127
|
-
Parses and
|
|
127
|
+
Parses, validates, and test-compiles `.fasm` source code, returning zero-based UTF-16 line and column spans ready for Monaco Editor, CodeMirror 6, or VS Code diagnostic markers (including v2.0.0 strict-lowercase quick-fix remediation hints in `help`). Returns an empty array `[]` when the source compiles cleanly.
|
|
128
128
|
|
|
129
129
|
```ts
|
|
130
130
|
interface IdeDiagnostic {
|
|
@@ -133,12 +133,12 @@ interface IdeDiagnostic {
|
|
|
133
133
|
end_line: number; // 0-based UTF-16 line index
|
|
134
134
|
end_col: number; // 0-based UTF-16 character column
|
|
135
135
|
message: string; // Human-readable diagnostic message
|
|
136
|
-
help?: string; // Actionable
|
|
136
|
+
help?: string; // Actionable remediation or 1-click quick-fix hint
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
### `inspect_fasm(source: string): PacketPreview[]`
|
|
141
|
-
Compiles `.fasm` source code into an array of packet inspection
|
|
141
|
+
Compiles `.fasm` source code into an array of packet ladder inspection entries, including relative millisecond timestamps, endpoint pairs (`source_dest`), protocol labels (`protocol`), decoded Ethernet/VLAN/QinQ/MPLS/L3/L4/L7 summaries (`summary`), frame lengths, and lowercase space-delimited hex byte dumps. Throws a JavaScript `Error` if compilation fails.
|
|
142
142
|
|
|
143
143
|
```ts
|
|
144
144
|
interface PacketPreview {
|
|
@@ -146,21 +146,24 @@ interface PacketPreview {
|
|
|
146
146
|
timestamp_ms: number; // Relative timestamp in milliseconds
|
|
147
147
|
length: number; // Total packet length in bytes
|
|
148
148
|
hex: string; // Space-separated lowercase hex byte dump
|
|
149
|
+
source_dest: string; // Formatted "src -> dst" IPv4, IPv6, or MAC pair
|
|
150
|
+
protocol: string; // Highest-layer protocol label (e.g. "DNS", "SNMP", "MODBUS")
|
|
151
|
+
summary: string; // Decoded L2/L3/L4/L7 frame summary string
|
|
149
152
|
}
|
|
150
153
|
```
|
|
151
154
|
|
|
152
155
|
### `compile_fasm_to_pcapng(source: string): Uint8Array` / `compileFasm(source: string): Uint8Array`
|
|
153
|
-
Compiles `.fasm` source code into a binary `Uint8Array` containing a valid, standards-compliant PCAPNG byte stream with nanosecond timestamp precision (`IfTsResol = 9`). Throws a JavaScript `Error` with
|
|
156
|
+
Compiles `.fasm` source code into a binary `Uint8Array` containing a valid, standards-compliant PCAPNG byte stream with nanosecond timestamp precision (`IfTsResol = 9`). Throws a JavaScript `Error` with diagnostic context if compilation fails.
|
|
154
157
|
|
|
155
|
-
### `reversePcap(pcap_bytes: Uint8Array): ReverseJsResult`
|
|
156
|
-
Decompiles an in-memory `.pcap` or `.pcapng` byte array (`Uint8Array`) across all 81 native Layer 2 through Layer 7 protocols into
|
|
158
|
+
### `reverse_pcap(pcap_bytes: Uint8Array): ReverseJsResult` / `reversePcap(pcap_bytes: Uint8Array): ReverseJsResult`
|
|
159
|
+
Decompiles an in-memory `.pcap` or `.pcapng` byte array (`Uint8Array`) across all 81 native Layer 2 through Layer 7 protocols into strict-lowercase `.fasm` code (`host1`, `host2`, ... declarations and an inline `compile` block), returning summary statistics (`packetCount`, `hostCount`). Throws a structured error object (`{ success: false, error, message }`) if `pcap_bytes` is not a valid packet capture.
|
|
157
160
|
|
|
158
161
|
```ts
|
|
159
162
|
interface ReversePcapResult {
|
|
160
163
|
success: boolean; // true when decompilation succeeds
|
|
161
|
-
fasm: string; //
|
|
164
|
+
fasm: string; // Strict-lowercase .fasm source code
|
|
162
165
|
packetCount: number; // Total number of reversed packets
|
|
163
|
-
hostCount: number; // Total number of
|
|
166
|
+
hostCount: number; // Total number of host1, host2, ... declarations emitted
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
type ReverseJsResult = ReversePcapResult;
|
|
@@ -206,15 +209,15 @@ export async function importCaptureFile(file: File): Promise<string> {
|
|
|
206
209
|
|
|
207
210
|
## Features & Engine Capabilities
|
|
208
211
|
|
|
209
|
-
- **Zero Server Dependencies**: Executes 100% locally in WebAssembly inside Chrome, Firefox, Safari, Edge, Web Workers, and Node.js.
|
|
210
|
-
- **81 Native Protocols (Forward & Reverse)**: Complete compilation and `.pcap`/`.pcapng` decompilation coverage across L2–L7 (Ethernet, 802.1Q VLAN, QinQ, MPLS, ARP, IPv4/IPv6, ICMP, TCP, UDP, SCTP, SNMPv1/v2c/v3 USM, DNS, DHCP, HTTP, TLS, Modbus, DNP3, and more), including pure Layer-2 MAC-only switch topologies and inline `compile` statements.
|
|
212
|
+
- **Zero Server Dependencies**: Executes 100% locally in WebAssembly inside Chrome, Firefox, Safari, Edge, Web Workers, VS Code Desktop/Web, and Node.js.
|
|
213
|
+
- **81 Native Protocols (Forward & Reverse)**: Complete compilation and `.pcap`/`.pcapng` decompilation coverage across L2–L7 (Ethernet, 802.1Q VLAN, QinQ, MPLS, ARP, IPv4/IPv6, ICMP, TCP, UDP, SCTP, SNMPv1/v2c/v3 USM, DNS, DHCP, HTTP, TLS, QUIC, Modbus, DNP3, and more), including pure Layer-2 MAC-only switch topologies and inline `compile` statements.
|
|
211
214
|
- **Nanosecond Fidelity**: Compiles and reverses deterministic PCAP/PCAPNG captures with nanosecond-resolution Section and Interface Description Blocks (`IfTsResol(9)`).
|
|
212
|
-
- **Sub-Millisecond Compilation**: Small to medium captures compile and decompile in `< 5ms`, making real-time live typing diagnostics and drag-and-drop PCAP import instantaneous in web editors.
|
|
215
|
+
- **Sub-Millisecond Compilation**: Small to medium captures compile and decompile in `< 5ms`, making real-time live typing diagnostics, packet ladder previews, and drag-and-drop PCAP import instantaneous in web and IDE editors.
|
|
213
216
|
|
|
214
217
|
---
|
|
215
218
|
|
|
216
219
|
## Disambiguation: FrameAssembly vs. Flat Assembler
|
|
217
220
|
|
|
218
|
-
**FrameAssembly** (`.fasm`, media type `text/vnd.frameassembly`) is a
|
|
221
|
+
**FrameAssembly** (`.fasm`, media type `text/vnd.frameassembly`) is a network packet specification compiler for generating and decompiling `.pcap` and `.pcapng` packet captures. It is completely unrelated to x86 Flat Assembler (`fasm.net`) and SymbiFlow / F4PGA FPGA bitstream FASM tools.
|
|
219
222
|
|
|
220
223
|
Full documentation, interactive browser playground ([https://frameassembly.ai.studio/](https://frameassembly.ai.studio/)), grammar specifications, and 2,611 verified examples: https://github.com/SteveWinschel/FrameAssembly
|
package/frameassembly_wasm.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface PacketPreview {
|
|
|
15
15
|
timestamp_ms: number;
|
|
16
16
|
length: number;
|
|
17
17
|
hex: string;
|
|
18
|
+
source_dest: string;
|
|
19
|
+
protocol: string;
|
|
20
|
+
summary: string;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export interface ReversePcapResult {
|
|
@@ -45,10 +48,19 @@ export function compileFasm(source: string): Uint8Array;
|
|
|
45
48
|
export function compile_fasm_to_pcapng(source: string): Uint8Array;
|
|
46
49
|
|
|
47
50
|
/**
|
|
48
|
-
* Returns decoded frame metadata and hex strings for
|
|
51
|
+
* Returns decoded frame metadata, endpoint/protocol summaries, and hex strings for packet ladder inspectors.
|
|
49
52
|
*/
|
|
50
53
|
export function inspect_fasm(source: string): PacketPreview[];
|
|
51
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Decompiles an in-memory `.pcap` or `.pcapng` capture (`reversePcap` camelCase alias).
|
|
57
|
+
*
|
|
58
|
+
* # Errors
|
|
59
|
+
* Returns a structured `JsValue` error object (`{ success: false, error, message }`) if
|
|
60
|
+
* `pcap_bytes` is not a valid `.pcap` or `.pcapng` capture.
|
|
61
|
+
*/
|
|
62
|
+
export function reversePcap(pcap_bytes: Uint8Array | number[]): ReversePcapResult;
|
|
63
|
+
|
|
52
64
|
/**
|
|
53
65
|
* Decompiles an in-memory `.pcap` or `.pcapng` capture (`Uint8Array`) into canonical `.fasm`
|
|
54
66
|
* source code and summary counts (`{ success: true, fasm, packetCount, hostCount }`).
|
|
@@ -57,7 +69,7 @@ export function inspect_fasm(source: string): PacketPreview[];
|
|
|
57
69
|
* Returns a structured `JsValue` error object (`{ success: false, error, message }`) if
|
|
58
70
|
* `pcap_bytes` is not a valid `.pcap` or `.pcapng` capture.
|
|
59
71
|
*/
|
|
60
|
-
export function
|
|
72
|
+
export function reverse_pcap(pcap_bytes: Uint8Array | number[]): ReversePcapResult;
|
|
61
73
|
|
|
62
74
|
/**
|
|
63
75
|
* Validates `.fasm` source and returns UTF-16 line/column diagnostics (`validateFasm` alias).
|
|
@@ -65,7 +77,7 @@ export function reversePcap(pcap_bytes: Uint8Array | number[]): ReversePcapResul
|
|
|
65
77
|
export function validateFasm(source: string): IdeDiagnostic[];
|
|
66
78
|
|
|
67
79
|
/**
|
|
68
|
-
* Validates `.fasm` source and returns UTF-16 line/column diagnostics for Monaco
|
|
80
|
+
* Validates `.fasm` source and returns UTF-16 line/column diagnostics for Monaco, CodeMirror, and VS Code.
|
|
69
81
|
*/
|
|
70
82
|
export function validate_fasm(source: string): IdeDiagnostic[];
|
|
71
83
|
|
|
@@ -77,6 +89,7 @@ export interface InitOutput {
|
|
|
77
89
|
readonly compile_fasm_to_pcapng: (a: number, b: number) => [number, number, number, number];
|
|
78
90
|
readonly inspect_fasm: (a: number, b: number) => [number, number, number];
|
|
79
91
|
readonly reversePcap: (a: number, b: number) => [number, number, number];
|
|
92
|
+
readonly reverse_pcap: (a: number, b: number) => [number, number, number];
|
|
80
93
|
readonly validateFasm: (a: number, b: number) => [number, number, number];
|
|
81
94
|
readonly validate_fasm: (a: number, b: number) => [number, number, number];
|
|
82
95
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
package/frameassembly_wasm.js
CHANGED
|
@@ -35,7 +35,7 @@ export function compile_fasm_to_pcapng(source) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Returns decoded frame metadata and hex strings for
|
|
38
|
+
* Returns decoded frame metadata, endpoint/protocol summaries, and hex strings for packet ladder inspectors.
|
|
39
39
|
* @param {string} source
|
|
40
40
|
* @returns {PacketPreview[]}
|
|
41
41
|
*/
|
|
@@ -50,8 +50,7 @@ export function inspect_fasm(source) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Decompiles an in-memory `.pcap` or `.pcapng` capture (`
|
|
54
|
-
* source code and summary counts (`{ success: true, fasm, packetCount, hostCount }`).
|
|
53
|
+
* Decompiles an in-memory `.pcap` or `.pcapng` capture (`reversePcap` camelCase alias).
|
|
55
54
|
*
|
|
56
55
|
* # Errors
|
|
57
56
|
* Returns a structured `JsValue` error object (`{ success: false, error, message }`) if
|
|
@@ -69,6 +68,26 @@ export function reversePcap(pcap_bytes) {
|
|
|
69
68
|
return takeFromExternrefTable0(ret[0]);
|
|
70
69
|
}
|
|
71
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Decompiles an in-memory `.pcap` or `.pcapng` capture (`Uint8Array`) into canonical `.fasm`
|
|
73
|
+
* source code and summary counts (`{ success: true, fasm, packetCount, hostCount }`).
|
|
74
|
+
*
|
|
75
|
+
* # Errors
|
|
76
|
+
* Returns a structured `JsValue` error object (`{ success: false, error, message }`) if
|
|
77
|
+
* `pcap_bytes` is not a valid `.pcap` or `.pcapng` capture.
|
|
78
|
+
* @param {Uint8Array | number[]} pcap_bytes
|
|
79
|
+
* @returns {ReversePcapResult}
|
|
80
|
+
*/
|
|
81
|
+
export function reverse_pcap(pcap_bytes) {
|
|
82
|
+
const ptr0 = passArray8ToWasm0(pcap_bytes, wasm.__wbindgen_malloc);
|
|
83
|
+
const len0 = WASM_VECTOR_LEN;
|
|
84
|
+
const ret = wasm.reverse_pcap(ptr0, len0);
|
|
85
|
+
if (ret[2]) {
|
|
86
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
87
|
+
}
|
|
88
|
+
return takeFromExternrefTable0(ret[0]);
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
/**
|
|
73
92
|
* Validates `.fasm` source and returns UTF-16 line/column diagnostics (`validateFasm` alias).
|
|
74
93
|
* @param {string} source
|
|
@@ -85,7 +104,7 @@ export function validateFasm(source) {
|
|
|
85
104
|
}
|
|
86
105
|
|
|
87
106
|
/**
|
|
88
|
-
* Validates `.fasm` source and returns UTF-16 line/column diagnostics for Monaco
|
|
107
|
+
* Validates `.fasm` source and returns UTF-16 line/column diagnostics for Monaco, CodeMirror, and VS Code.
|
|
89
108
|
* @param {string} source
|
|
90
109
|
* @returns {IdeDiagnostic[]}
|
|
91
110
|
*/
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"collaborators": [
|
|
5
5
|
"Steve Winschel"
|
|
6
6
|
],
|
|
7
|
-
"description": "
|
|
8
|
-
"version": "
|
|
7
|
+
"description": "Compile .fasm network traffic code to .pcapng and decompile .pcap/.pcapng packet captures in WebAssembly across 81 L2-L7 protocols with live IDE diagnostics and frame inspection.",
|
|
8
|
+
"version": "2.0.0",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -24,9 +24,24 @@
|
|
|
24
24
|
],
|
|
25
25
|
"keywords": [
|
|
26
26
|
"frameassembly",
|
|
27
|
+
"fasm",
|
|
27
28
|
"pcap",
|
|
28
29
|
"pcapng",
|
|
30
|
+
"wasm",
|
|
31
|
+
"webassembly",
|
|
29
32
|
"wireshark",
|
|
30
|
-
"
|
|
33
|
+
"packet-generator",
|
|
34
|
+
"pcap-decompiler",
|
|
35
|
+
"pcap-parser",
|
|
36
|
+
"tcpreplay",
|
|
37
|
+
"suricata",
|
|
38
|
+
"zeek",
|
|
39
|
+
"snmpv3",
|
|
40
|
+
"firewall-testing",
|
|
41
|
+
"network-testing",
|
|
42
|
+
"cybersecurity",
|
|
43
|
+
"pentesting",
|
|
44
|
+
"packets-as-code",
|
|
45
|
+
"modbus"
|
|
31
46
|
]
|
|
32
47
|
}
|