1563 Interactive Packet Analyzer
Visualize and build IoT protocol packets with field-level analysis
1563.1 Interactive Packet Analyzer
This tool provides a comprehensive environment for understanding IoT protocol packet structures. Explore MQTT, CoAP, HTTP, Modbus, BLE, and Zigbee packets with interactive field visualization, packet building, and protocol comparison features.
This interactive packet analyzer helps you understand the structure of common IoT protocols. Examine header fields, build custom packets, compare protocol overhead, and visualize request-response timing diagrams.
- Select a Protocol from the dropdown (MQTT, CoAP, HTTP, Modbus, BLE, Zigbee)
- Choose a Sample Packet or build your own
- Hover over Header Fields to see detailed explanations
- Use the Packet Builder to create custom packets
- View Hex Dump and Binary representations
- Compare Protocol Overhead with the overhead calculator
- Explore Timing Diagrams for request-response patterns
Show code
{
// ===========================================================================
// INTERACTIVE PACKET ANALYZER
// ===========================================================================
// Features:
// - Protocol selector: MQTT, CoAP, HTTP, Modbus, BLE, Zigbee
// - Packet structure visualizer with hover tooltips
// - Packet builder for custom packets
// - Field editor with hex/binary display
// - Overhead calculator
// - Protocol comparison
// - Timing diagrams
// - Sample packet library
// - Export hex dump
//
// IEEE Color Palette:
// Navy: #2C3E50 (primary)
// Teal: #16A085 (secondary)
// Orange: #E67E22 (highlights)
// Gray: #7F8C8D (neutral)
// ===========================================================================
// ---------------------------------------------------------------------------
// CONFIGURATION
// ---------------------------------------------------------------------------
const config = {
width: 920,
height: 1400,
colors: {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
green: "#27AE60",
red: "#E74C3C",
yellow: "#F1C40F",
purple: "#9B59B6",
blue: "#3498DB",
pink: "#E91E63"
},
// Protocol definitions
protocols: {
mqtt: {
name: "MQTT",
description: "Message Queuing Telemetry Transport - lightweight pub/sub messaging",
color: "#9B59B6",
minHeader: 2,
samples: {
connect: {
name: "CONNECT",
description: "Client connection request",
fields: [
{ name: "Fixed Header", bytes: 1, value: 0x10, bits: "0001 0000", description: "Packet type (1) + flags (0)" },
{ name: "Remaining Length", bytes: 1, value: 0x12, bits: "0001 0010", description: "Length of variable header + payload (18 bytes)" },
{ name: "Protocol Name Length", bytes: 2, value: 0x0004, bits: "0000 0000 0000 0100", description: "Length of protocol name (4)" },
{ name: "Protocol Name", bytes: 4, value: "MQTT", bits: "4D515454", description: "Protocol identifier 'MQTT'" },
{ name: "Protocol Level", bytes: 1, value: 0x04, bits: "0000 0100", description: "MQTT version 3.1.1" },
{ name: "Connect Flags", bytes: 1, value: 0x02, bits: "0000 0010", description: "Clean session flag set" },
{ name: "Keep Alive", bytes: 2, value: 0x003C, bits: "0000 0000 0011 1100", description: "Keep-alive timeout (60 seconds)" },
{ name: "Client ID Length", bytes: 2, value: 0x0006, bits: "0000 0000 0000 0110", description: "Length of client ID (6)" },
{ name: "Client ID", bytes: 6, value: "sensor", bits: "73656E736F72", description: "Client identifier 'sensor'" }
],
payload: []
},
publish: {
name: "PUBLISH",
description: "Publish message to topic",
fields: [
{ name: "Fixed Header", bytes: 1, value: 0x30, bits: "0011 0000", description: "Packet type (3) + DUP=0, QoS=0, RETAIN=0" },
{ name: "Remaining Length", bytes: 1, value: 0x13, bits: "0001 0011", description: "Length of variable header + payload (19 bytes)" },
{ name: "Topic Length", bytes: 2, value: 0x000B, bits: "0000 0000 0000 1011", description: "Length of topic name (11)" },
{ name: "Topic Name", bytes: 11, value: "sensors/temp", bits: "73656E736F72732F74656D70", description: "Topic 'sensors/temp'" },
],
payload: [
{ name: "Payload", bytes: 4, value: "25.5", bits: "32352E35", description: "Message payload '25.5'" }
]
}
}
},
coap: {
name: "CoAP",
description: "Constrained Application Protocol - REST for constrained devices",
color: "#16A085",
minHeader: 4,
samples: {
get: {
name: "GET Request",
description: "Request resource from server",
fields: [
{ name: "Version + Type + TKL", bytes: 1, value: 0x40, bits: "0100 0000", description: "Ver=1, Type=CON(0), Token Length=0" },
{ name: "Code", bytes: 1, value: 0x01, bits: "0000 0001", description: "Method: GET (0.01)" },
{ name: "Message ID", bytes: 2, value: 0x1234, bits: "0001 0010 0011 0100", description: "Message identifier for matching" },
{ name: "Option Delta + Length", bytes: 1, value: 0xB4, bits: "1011 0100", description: "Option 11 (Uri-Path), length 4" },
{ name: "Option Value", bytes: 4, value: "temp", bits: "74656D70", description: "Uri-Path: 'temp'" }
],
payload: []
},
post: {
name: "POST Request",
description: "Create/update resource on server",
fields: [
{ name: "Version + Type + TKL", bytes: 1, value: 0x42, bits: "0100 0010", description: "Ver=1, Type=CON(0), Token Length=2" },
{ name: "Code", bytes: 1, value: 0x02, bits: "0000 0010", description: "Method: POST (0.02)" },
{ name: "Message ID", bytes: 2, value: 0xABCD, bits: "1010 1011 1100 1101", description: "Message identifier" },
{ name: "Token", bytes: 2, value: 0x1234, bits: "0001 0010 0011 0100", description: "Request token for response matching" },
{ name: "Option (Content-Format)", bytes: 2, value: 0xC100, bits: "1100 0001 0000 0000", description: "Content-Format: text/plain (0)" },
{ name: "Payload Marker", bytes: 1, value: 0xFF, bits: "1111 1111", description: "Start of payload (0xFF)" }
],
payload: [
{ name: "Payload", bytes: 4, value: "25.5", bits: "32352E35", description: "Sensor reading '25.5'" }
]
}
}
},
http: {
name: "HTTP",
description: "Hypertext Transfer Protocol - standard web protocol",
color: "#3498DB",
minHeader: 50,
samples: {
get: {
name: "GET Request",
description: "Request resource via HTTP",
fields: [
{ name: "Request Line", bytes: 21, value: "GET /temp HTTP/1.1\\r\\n", bits: "474554202F74656D7020485454502F312E310D0A", description: "HTTP method, path, and version" },
{ name: "Host Header", bytes: 20, value: "Host: 192.168.1.10\\r\\n", bits: "486F73743A203139322E3136382E312E31300D0A", description: "Target server address" },
{ name: "Accept Header", bytes: 26, value: "Accept: application/json\\r\\n", bits: "4163636570743A206170706C69636174696F6E2F6A736F6E0D0A", description: "Acceptable response types" },
{ name: "Blank Line", bytes: 2, value: "\\r\\n", bits: "0D0A", description: "End of headers marker" }
],
payload: []
},
post: {
name: "POST Request",
description: "Send data via HTTP POST",
fields: [
{ name: "Request Line", bytes: 22, value: "POST /temp HTTP/1.1\\r\\n", bits: "504F5354202F74656D7020485454502F312E310D0A", description: "HTTP POST method" },
{ name: "Host Header", bytes: 20, value: "Host: 192.168.1.10\\r\\n", bits: "486F73743A203139322E3136382E312E31300D0A", description: "Target server address" },
{ name: "Content-Type", bytes: 32, value: "Content-Type: application/json\\r\\n", bits: "436F6E74656E742D547970653A206170706C69636174696F6E2F6A736F6E0D0A", description: "Payload content type" },
{ name: "Content-Length", bytes: 18, value: "Content-Length: 15\\r\\n", bits: "436F6E74656E742D4C656E6774683A2031350D0A", description: "Size of payload" },
{ name: "Blank Line", bytes: 2, value: "\\r\\n", bits: "0D0A", description: "End of headers" }
],
payload: [
{ name: "JSON Body", bytes: 15, value: "{\"temp\":25.5}", bits: "7B2274656D70223A32352E357D", description: "JSON payload" }
]
}
}
},
modbus: {
name: "Modbus TCP",
description: "Industrial protocol for SCADA systems",
color: "#E67E22",
minHeader: 7,
samples: {
read: {
name: "Read Holding Registers",
description: "Read multiple registers (FC 03)",
fields: [
{ name: "Transaction ID", bytes: 2, value: 0x0001, bits: "0000 0000 0000 0001", description: "Transaction identifier" },
{ name: "Protocol ID", bytes: 2, value: 0x0000, bits: "0000 0000 0000 0000", description: "Modbus protocol (always 0)" },
{ name: "Length", bytes: 2, value: 0x0006, bits: "0000 0000 0000 0110", description: "Bytes following (6)" },
{ name: "Unit ID", bytes: 1, value: 0x01, bits: "0000 0001", description: "Slave address (1)" },
{ name: "Function Code", bytes: 1, value: 0x03, bits: "0000 0011", description: "Read holding registers (FC 03)" },
{ name: "Start Address", bytes: 2, value: 0x0000, bits: "0000 0000 0000 0000", description: "Starting register (0)" },
{ name: "Quantity", bytes: 2, value: 0x0002, bits: "0000 0000 0000 0010", description: "Number of registers (2)" }
],
payload: []
},
write: {
name: "Write Single Register",
description: "Write to single register (FC 06)",
fields: [
{ name: "Transaction ID", bytes: 2, value: 0x0002, bits: "0000 0000 0000 0010", description: "Transaction identifier" },
{ name: "Protocol ID", bytes: 2, value: 0x0000, bits: "0000 0000 0000 0000", description: "Modbus protocol (always 0)" },
{ name: "Length", bytes: 2, value: 0x0006, bits: "0000 0000 0000 0110", description: "Bytes following (6)" },
{ name: "Unit ID", bytes: 1, value: 0x01, bits: "0000 0001", description: "Slave address (1)" },
{ name: "Function Code", bytes: 1, value: 0x06, bits: "0000 0110", description: "Write single register (FC 06)" },
{ name: "Register Address", bytes: 2, value: 0x0001, bits: "0000 0000 0000 0001", description: "Register address (1)" },
{ name: "Register Value", bytes: 2, value: 0x00FF, bits: "0000 0000 1111 1111", description: "Value to write (255)" }
],
payload: []
}
}
},
ble: {
name: "BLE",
description: "Bluetooth Low Energy - short-range wireless",
color: "#2980B9",
minHeader: 2,
samples: {
advertising: {
name: "Advertising PDU",
description: "BLE advertisement packet",
fields: [
{ name: "PDU Header", bytes: 2, value: 0x2506, bits: "0010 0101 0000 0110", description: "PDU type (ADV_IND), TxAdd, length" },
{ name: "Advertiser Address", bytes: 6, value: "AA:BB:CC:DD:EE:FF", bits: "AABBCCDDEEFF", description: "Device MAC address" },
{ name: "AD Length", bytes: 1, value: 0x02, bits: "0000 0010", description: "AD structure length (2)" },
{ name: "AD Type (Flags)", bytes: 1, value: 0x01, bits: "0000 0001", description: "AD type: Flags" },
{ name: "AD Data (Flags)", bytes: 1, value: 0x06, bits: "0000 0110", description: "LE General + BR/EDR not supported" },
{ name: "AD Length", bytes: 1, value: 0x09, bits: "0000 1001", description: "AD structure length (9)" },
{ name: "AD Type (Name)", bytes: 1, value: 0x09, bits: "0000 1001", description: "AD type: Complete Local Name" },
{ name: "AD Data (Name)", bytes: 8, value: "IoTSense", bits: "496F5453656E7365", description: "Device name 'IoTSense'" }
],
payload: []
},
gatt: {
name: "GATT Read Response",
description: "GATT attribute read response",
fields: [
{ name: "L2CAP Length", bytes: 2, value: 0x0007, bits: "0000 0000 0000 0111", description: "L2CAP payload length (7)" },
{ name: "L2CAP Channel", bytes: 2, value: 0x0004, bits: "0000 0000 0000 0100", description: "ATT channel (0x0004)" },
{ name: "ATT Opcode", bytes: 1, value: 0x0B, bits: "0000 1011", description: "Read Response (0x0B)" },
{ name: "Attribute Value", bytes: 4, value: "25.5", bits: "32352E35", description: "Sensor value '25.5'" }
],
payload: []
}
}
},
zigbee: {
name: "Zigbee",
description: "IEEE 802.15.4 mesh networking protocol",
color: "#27AE60",
minHeader: 9,
samples: {
data: {
name: "Data Frame",
description: "Zigbee NWK data frame",
fields: [
{ name: "Frame Control", bytes: 2, value: 0x0801, bits: "0000 1000 0000 0001", description: "Data frame, NWK security disabled" },
{ name: "Dest Address", bytes: 2, value: 0x1234, bits: "0001 0010 0011 0100", description: "Destination short address" },
{ name: "Source Address", bytes: 2, value: 0x5678, bits: "0101 0110 0111 1000", description: "Source short address" },
{ name: "Radius", bytes: 1, value: 0x1E, bits: "0001 1110", description: "Max hop count (30)" },
{ name: "Sequence Number", bytes: 1, value: 0x42, bits: "0100 0010", description: "Frame sequence (66)" },
{ name: "APS Frame Control", bytes: 1, value: 0x00, bits: "0000 0000", description: "APS data frame" },
{ name: "APS Dest Endpoint", bytes: 1, value: 0x01, bits: "0000 0001", description: "Destination endpoint (1)" },
{ name: "APS Cluster ID", bytes: 2, value: 0x0402, bits: "0000 0100 0000 0010", description: "Temperature measurement cluster" },
{ name: "APS Profile ID", bytes: 2, value: 0x0104, bits: "0000 0001 0000 0100", description: "Home Automation profile" },
{ name: "APS Source Endpoint", bytes: 1, value: 0x01, bits: "0000 0001", description: "Source endpoint (1)" },
{ name: "APS Counter", bytes: 1, value: 0x05, bits: "0000 0101", description: "APS frame counter (5)" }
],
payload: [
{ name: "ZCL Header", bytes: 3, value: 0x180100, bits: "0001 1000 0000 0001 0000 0000", description: "ZCL frame control, seq, cmd" },
{ name: "Temperature", bytes: 2, value: 0x0A1E, bits: "0000 1010 0001 1110", description: "Temperature: 25.9C (0x0A1E/100)" }
]
},
cmd: {
name: "Network Command",
description: "Zigbee route request",
fields: [
{ name: "Frame Control", bytes: 2, value: 0x0901, bits: "0000 1001 0000 0001", description: "NWK command frame" },
{ name: "Dest Address", bytes: 2, value: 0xFFFC, bits: "1111 1111 1111 1100", description: "Broadcast to routers" },
{ name: "Source Address", bytes: 2, value: 0x0001, bits: "0000 0000 0000 0001", description: "Coordinator address" },
{ name: "Radius", bytes: 1, value: 0x1E, bits: "0001 1110", description: "Max hop count (30)" },
{ name: "Sequence Number", bytes: 1, value: 0x15, bits: "0001 0101", description: "Frame sequence (21)" },
{ name: "Command ID", bytes: 1, value: 0x01, bits: "0000 0001", description: "Route Request command" },
{ name: "Route Options", bytes: 1, value: 0x00, bits: "0000 0000", description: "Route discovery options" },
{ name: "Route Request ID", bytes: 1, value: 0x0A, bits: "0000 1010", description: "Request identifier (10)" },
{ name: "Dest Short Addr", bytes: 2, value: 0x1234, bits: "0001 0010 0011 0100", description: "Destination to find route to" },
{ name: "Path Cost", bytes: 1, value: 0x00, bits: "0000 0000", description: "Accumulated path cost (0)" }
],
payload: []
}
}
}
}
};
// ---------------------------------------------------------------------------
// STATE MANAGEMENT
// ---------------------------------------------------------------------------
let state = {
selectedProtocol: "mqtt",
selectedSample: "connect",
customPacket: null,
compareProtocol: "coap",
showBinary: false,
editingField: null
};
// ---------------------------------------------------------------------------
// HELPER FUNCTIONS
// ---------------------------------------------------------------------------
function getProtocol() {
return config.protocols[state.selectedProtocol];
}
function getSample() {
const protocol = getProtocol();
return protocol.samples[state.selectedSample];
}
function getAllFields() {
const sample = getSample();
return [...sample.fields, ...sample.payload];
}
function bytesToHex(value) {
if (typeof value === "number") {
return value.toString(16).toUpperCase().padStart(2, "0");
}
if (typeof value === "string") {
return Array.from(value).map(c => c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")).join("");
}
return "00";
}
function calculateTotalBytes(fields) {
return fields.reduce((sum, f) => sum + f.bytes, 0);
}
// ---------------------------------------------------------------------------
// CREATE MAIN CONTAINER
// ---------------------------------------------------------------------------
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("max-width", `${config.width}px`)
.style("margin", "0 auto")
.style("padding", "10px");
// ---------------------------------------------------------------------------
// PROTOCOL SELECTOR
// ---------------------------------------------------------------------------
const selectorPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "8px")
.style("padding", "16px")
.style("margin-bottom", "16px");
selectorPanel.append("div")
.style("font-size", "18px")
.style("font-weight", "700")
.style("color", config.colors.navy)
.style("margin-bottom", "12px")
.text("Protocol & Packet Selection");
// Protocol selector row
const protocolRow = selectorPanel.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "12px")
.style("margin-bottom", "12px");
protocolRow.append("label")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("min-width", "80px")
.text("Protocol:");
const protocolSelect = protocolRow.append("select")
.style("padding", "8px 12px")
.style("border-radius", "4px")
.style("border", `1px solid ${config.colors.gray}`)
.style("background", config.colors.white)
.style("font-size", "14px")
.style("cursor", "pointer")
.style("min-width", "150px");
protocolSelect.selectAll("option")
.data(Object.entries(config.protocols))
.join("option")
.attr("value", d => d[0])
.text(d => d[1].name);
const protocolDesc = protocolRow.append("span")
.style("color", config.colors.gray)
.style("font-size", "13px")
.style("font-style", "italic")
.text(getProtocol().description);
// Sample packet selector row
const sampleRow = selectorPanel.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("align-items", "center")
.style("gap", "12px");
sampleRow.append("label")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("min-width", "80px")
.text("Packet:");
const sampleSelect = sampleRow.append("select")
.style("padding", "8px 12px")
.style("border-radius", "4px")
.style("border", `1px solid ${config.colors.gray}`)
.style("background", config.colors.white)
.style("font-size", "14px")
.style("cursor", "pointer")
.style("min-width", "180px");
const sampleDesc = sampleRow.append("span")
.style("color", config.colors.teal)
.style("font-size", "13px")
.style("font-weight", "500");
// ---------------------------------------------------------------------------
// PACKET STRUCTURE VISUALIZER
// ---------------------------------------------------------------------------
const structurePanel = container.append("div")
.style("background", config.colors.white)
.style("border", `1px solid ${config.colors.gray}`)
.style("border-radius", "8px")
.style("padding", "16px")
.style("margin-bottom", "16px");
structurePanel.append("div")
.style("font-size", "16px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("margin-bottom", "12px")
.text("Packet Structure");
// Field visualizer container
const fieldVisualizerContainer = structurePanel.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "4px")
.style("margin-bottom", "16px");
// Tooltip div
const tooltip = container.append("div")
.style("position", "absolute")
.style("background", config.colors.navy)
.style("color", config.colors.white)
.style("padding", "10px 14px")
.style("border-radius", "6px")
.style("font-size", "12px")
.style("max-width", "300px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("z-index", "1000")
.style("box-shadow", "0 4px 12px rgba(0,0,0,0.15)");
// Legend
const legendRow = structurePanel.append("div")
.style("display", "flex")
.style("gap", "20px")
.style("margin-top", "10px")
.style("padding-top", "10px")
.style("border-top", `1px solid ${config.colors.lightGray}`);
const legendItems = [
{ color: config.colors.teal, label: "Header Fields" },
{ color: config.colors.orange, label: "Payload" },
{ color: config.colors.purple, label: "Checksum/CRC" }
];
legendItems.forEach(item => {
const legendItem = legendRow.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "6px");
legendItem.append("div")
.style("width", "16px")
.style("height", "16px")
.style("background", item.color)
.style("border-radius", "3px");
legendItem.append("span")
.style("color", config.colors.navy)
.style("font-size", "12px")
.text(item.label);
});
// ---------------------------------------------------------------------------
// HEX/BINARY DUMP
// ---------------------------------------------------------------------------
const dumpPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "8px")
.style("padding", "16px")
.style("margin-bottom", "16px");
const dumpHeader = dumpPanel.append("div")
.style("display", "flex")
.style("justify-content", "space-between")
.style("align-items", "center")
.style("margin-bottom", "12px");
dumpHeader.append("div")
.style("font-size", "16px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.text("Packet Data");
const toggleBtnContainer = dumpHeader.append("div")
.style("display", "flex")
.style("gap", "8px");
const hexBtn = toggleBtnContainer.append("button")
.style("padding", "6px 12px")
.style("background", config.colors.teal)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("cursor", "pointer")
.style("font-size", "12px")
.text("Hex");
const binaryBtn = toggleBtnContainer.append("button")
.style("padding", "6px 12px")
.style("background", config.colors.white)
.style("color", config.colors.navy)
.style("border", `1px solid ${config.colors.gray}`)
.style("border-radius", "4px")
.style("cursor", "pointer")
.style("font-size", "12px")
.text("Binary");
const copyBtn = toggleBtnContainer.append("button")
.style("padding", "6px 12px")
.style("background", config.colors.orange)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "4px")
.style("cursor", "pointer")
.style("font-size", "12px")
.text("Copy");
const dumpContent = dumpPanel.append("pre")
.style("background", "#1a1a2e")
.style("color", "#00ff00")
.style("padding", "12px")
.style("border-radius", "4px")
.style("font-family", "'Consolas', 'Monaco', monospace")
.style("font-size", "13px")
.style("overflow-x", "auto")
.style("white-space", "pre-wrap")
.style("word-break", "break-all")
.style("margin", "0");
// ---------------------------------------------------------------------------
// OVERHEAD CALCULATOR
// ---------------------------------------------------------------------------
const overheadPanel = container.append("div")
.style("background", config.colors.white)
.style("border", `1px solid ${config.colors.gray}`)
.style("border-radius", "8px")
.style("padding", "16px")
.style("margin-bottom", "16px");
overheadPanel.append("div")
.style("font-size", "16px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("margin-bottom", "12px")
.text("Protocol Overhead Analysis");
const overheadSvg = overheadPanel.append("svg")
.attr("width", 880)
.attr("height", 120)
.style("display", "block");
// Overhead bar chart
const overheadBarGroup = overheadSvg.append("g").attr("transform", "translate(0, 20)");
// Header bar
overheadBarGroup.append("rect")
.attr("class", "header-bar")
.attr("x", 0)
.attr("y", 0)
.attr("height", 30)
.attr("rx", 4)
.attr("fill", config.colors.teal);
overheadBarGroup.append("text")
.attr("class", "header-label")
.attr("y", 20)
.attr("fill", config.colors.white)
.attr("font-size", "12px")
.attr("font-weight", "600");
// Payload bar
overheadBarGroup.append("rect")
.attr("class", "payload-bar")
.attr("y", 0)
.attr("height", 30)
.attr("rx", 4)
.attr("fill", config.colors.orange);
overheadBarGroup.append("text")
.attr("class", "payload-label")
.attr("y", 20)
.attr("fill", config.colors.white)
.attr("font-size", "12px")
.attr("font-weight", "600");
// Stats row
const overheadStats = overheadSvg.append("g").attr("transform", "translate(0, 70)");
const statItems = [
{ id: "total", label: "Total Size:", x: 0 },
{ id: "header", label: "Header:", x: 150 },
{ id: "payload", label: "Payload:", x: 280 },
{ id: "overhead", label: "Overhead %:", x: 410 },
{ id: "efficiency", label: "Efficiency:", x: 560 }
];
statItems.forEach(item => {
overheadStats.append("text")
.attr("x", item.x)
.attr("y", 0)
.attr("fill", config.colors.navy)
.attr("font-size", "12px")
.text(item.label);
overheadStats.append("text")
.attr("x", item.x)
.attr("y", 20)
.attr("fill", config.colors.teal)
.attr("font-size", "14px")
.attr("font-weight", "600")
.attr("class", `stat-${item.id}`);
});
// ---------------------------------------------------------------------------
// PROTOCOL COMPARISON
// ---------------------------------------------------------------------------
const comparePanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "8px")
.style("padding", "16px")
.style("margin-bottom", "16px");
const compareHeader = comparePanel.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "12px")
.style("margin-bottom", "12px");
compareHeader.append("div")
.style("font-size", "16px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.text("Protocol Comparison");
compareHeader.append("span")
.style("color", config.colors.gray)
.style("font-size", "13px")
.text("Compare with:");
const compareSelect = compareHeader.append("select")
.style("padding", "6px 10px")
.style("border-radius", "4px")
.style("border", `1px solid ${config.colors.gray}`)
.style("background", config.colors.white)
.style("font-size", "13px")
.style("cursor", "pointer");
// Comparison table
const compareTable = comparePanel.append("table")
.style("width", "100%")
.style("border-collapse", "collapse")
.style("background", config.colors.white)
.style("border-radius", "6px")
.style("overflow", "hidden");
const tableHeader = compareTable.append("thead").append("tr")
.style("background", config.colors.navy);
["Feature", "Current Protocol", "Comparison Protocol"].forEach(h => {
tableHeader.append("th")
.style("padding", "10px 12px")
.style("text-align", "left")
.style("color", config.colors.white)
.style("font-size", "13px")
.text(h);
});
const tableBody = compareTable.append("tbody");
// ---------------------------------------------------------------------------
// TIMING DIAGRAM
// ---------------------------------------------------------------------------
const timingPanel = container.append("div")
.style("background", config.colors.white)
.style("border", `1px solid ${config.colors.gray}`)
.style("border-radius", "8px")
.style("padding", "16px")
.style("margin-bottom", "16px");
timingPanel.append("div")
.style("font-size", "16px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("margin-bottom", "12px")
.text("Request-Response Timing Diagram");
const timingSvg = timingPanel.append("svg")
.attr("width", 880)
.attr("height", 180)
.style("display", "block");
// ---------------------------------------------------------------------------
// SAMPLE PACKET LIBRARY
// ---------------------------------------------------------------------------
const libraryPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "8px")
.style("padding", "16px");
libraryPanel.append("div")
.style("font-size", "16px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.style("margin-bottom", "12px")
.text("Sample Packet Library");
const libraryGrid = libraryPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fill, minmax(250px, 1fr))")
.style("gap", "12px");
// ---------------------------------------------------------------------------
// UPDATE FUNCTIONS
// ---------------------------------------------------------------------------
function updateSampleOptions() {
const protocol = getProtocol();
const samples = Object.entries(protocol.samples);
sampleSelect.selectAll("option").remove();
sampleSelect.selectAll("option")
.data(samples)
.join("option")
.attr("value", d => d[0])
.text(d => d[1].name);
state.selectedSample = samples[0][0];
sampleSelect.property("value", state.selectedSample);
}
function updateCompareOptions() {
const otherProtocols = Object.entries(config.protocols)
.filter(([key]) => key !== state.selectedProtocol);
compareSelect.selectAll("option").remove();
compareSelect.selectAll("option")
.data(otherProtocols)
.join("option")
.attr("value", d => d[0])
.text(d => d[1].name);
state.compareProtocol = otherProtocols[0][0];
compareSelect.property("value", state.compareProtocol);
}
function updateFieldVisualizer() {
const sample = getSample();
const protocol = getProtocol();
const allFields = getAllFields();
const totalBytes = calculateTotalBytes(allFields);
fieldVisualizerContainer.selectAll("*").remove();
allFields.forEach((field, idx) => {
const isPayload = sample.payload.includes(field);
const isChecksum = field.name.toLowerCase().includes("crc") || field.name.toLowerCase().includes("checksum");
const fieldColor = isChecksum ? config.colors.purple :
isPayload ? config.colors.orange : config.colors.teal;
const fieldDiv = fieldVisualizerContainer.append("div")
.style("background", fieldColor)
.style("color", config.colors.white)
.style("padding", "8px 12px")
.style("border-radius", "4px")
.style("font-size", "11px")
.style("cursor", "pointer")
.style("min-width", `${Math.max(60, field.bytes * 30)}px`)
.style("text-align", "center")
.style("transition", "transform 0.2s, box-shadow 0.2s")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)");
fieldDiv.append("div")
.style("font-weight", "600")
.style("white-space", "nowrap")
.style("overflow", "hidden")
.style("text-overflow", "ellipsis")
.text(field.name);
fieldDiv.append("div")
.style("font-size", "10px")
.style("opacity", "0.9")
.text(`${field.bytes} byte${field.bytes > 1 ? "s" : ""}`);
// Hover effects
fieldDiv.on("mouseenter", function(event) {
d3.select(this)
.style("transform", "translateY(-2px)")
.style("box-shadow", "0 4px 12px rgba(0,0,0,0.2)");
tooltip
.style("opacity", 1)
.html(`
<strong>${field.name}</strong><br>
<span style="color: ${config.colors.lightGray}">Size:</span> ${field.bytes} byte(s)<br>
<span style="color: ${config.colors.lightGray}">Value:</span> ${field.value}<br>
<span style="color: ${config.colors.lightGray}">Hex:</span> ${typeof field.value === "number" ? "0x" + field.value.toString(16).toUpperCase() : bytesToHex(field.value)}<br>
<span style="color: ${config.colors.orange}">${field.description}</span>
`)
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY + 10}px`);
});
fieldDiv.on("mousemove", function(event) {
tooltip
.style("left", `${event.pageX + 10}px`)
.style("top", `${event.pageY + 10}px`);
});
fieldDiv.on("mouseleave", function() {
d3.select(this)
.style("transform", "translateY(0)")
.style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)");
tooltip.style("opacity", 0);
});
});
}
function updateDump() {
const allFields = getAllFields();
let output = "";
if (state.showBinary) {
// Binary dump
output = "Binary Representation:\n";
output += "─".repeat(70) + "\n";
allFields.forEach(field => {
const bits = field.bits.replace(/\s/g, "");
const formatted = bits.match(/.{1,8}/g)?.join(" ") || bits;
output += `${field.name.padEnd(25)} │ ${formatted}\n`;
});
} else {
// Hex dump
output = "Hex Dump:\n";
output += "─".repeat(70) + "\n";
output += "Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ASCII\n";
output += "─".repeat(70) + "\n";
let hexBytes = [];
allFields.forEach(field => {
if (typeof field.value === "number") {
let hex = field.value.toString(16).toUpperCase().padStart(field.bytes * 2, "0");
for (let i = 0; i < hex.length; i += 2) {
hexBytes.push(hex.substr(i, 2));
}
} else if (typeof field.value === "string") {
for (let i = 0; i < field.value.length; i++) {
hexBytes.push(field.value.charCodeAt(i).toString(16).toUpperCase().padStart(2, "0"));
}
}
});
for (let i = 0; i < hexBytes.length; i += 16) {
const offset = i.toString(16).toUpperCase().padStart(6, "0");
const chunk = hexBytes.slice(i, i + 16);
const hexPart = chunk.join(" ").padEnd(47);
const asciiPart = chunk.map(h => {
const code = parseInt(h, 16);
return (code >= 32 && code <= 126) ? String.fromCharCode(code) : ".";
}).join("");
output += `${offset} ${hexPart} ${asciiPart}\n`;
}
output += "\n─".repeat(70) + "\n";
output += `Total: ${hexBytes.length} bytes`;
}
dumpContent.text(output);
}
function updateOverhead() {
const sample = getSample();
const headerBytes = calculateTotalBytes(sample.fields);
const payloadBytes = calculateTotalBytes(sample.payload);
const totalBytes = headerBytes + payloadBytes;
const barWidth = 800;
const headerWidth = (headerBytes / Math.max(totalBytes, 1)) * barWidth;
const payloadWidth = (payloadBytes / Math.max(totalBytes, 1)) * barWidth;
overheadBarGroup.select(".header-bar")
.attr("width", Math.max(headerWidth, 20));
overheadBarGroup.select(".header-label")
.attr("x", Math.max(headerWidth / 2, 10))
.attr("text-anchor", "middle")
.text(`Header: ${headerBytes}B`);
overheadBarGroup.select(".payload-bar")
.attr("x", headerWidth + 2)
.attr("width", Math.max(payloadWidth - 2, payloadBytes > 0 ? 20 : 0));
overheadBarGroup.select(".payload-label")
.attr("x", headerWidth + Math.max(payloadWidth / 2, 10))
.attr("text-anchor", "middle")
.text(payloadBytes > 0 ? `Payload: ${payloadBytes}B` : "");
// Update stats
const overhead = totalBytes > 0 ? ((headerBytes / totalBytes) * 100).toFixed(1) : 0;
const efficiency = totalBytes > 0 ? ((payloadBytes / totalBytes) * 100).toFixed(1) : 0;
overheadSvg.select(".stat-total").text(`${totalBytes} bytes`);
overheadSvg.select(".stat-header").text(`${headerBytes} bytes`);
overheadSvg.select(".stat-payload").text(`${payloadBytes} bytes`);
overheadSvg.select(".stat-overhead").text(`${overhead}%`);
overheadSvg.select(".stat-efficiency").text(`${efficiency}%`);
}
function updateComparison() {
const current = config.protocols[state.selectedProtocol];
const compare = config.protocols[state.compareProtocol];
const currentSample = Object.values(current.samples)[0];
const compareSample = Object.values(compare.samples)[0];
const currentHeader = calculateTotalBytes(currentSample.fields);
const compareHeader = calculateTotalBytes(compareSample.fields);
const features = [
{ name: "Protocol Name", current: current.name, compare: compare.name },
{ name: "Min Header Size", current: `${current.minHeader} bytes`, compare: `${compare.minHeader} bytes` },
{ name: "Sample Header", current: `${currentHeader} bytes`, compare: `${compareHeader} bytes` },
{ name: "Transport", current: getTransport(state.selectedProtocol), compare: getTransport(state.compareProtocol) },
{ name: "Best For", current: getBestFor(state.selectedProtocol), compare: getBestFor(state.compareProtocol) },
{ name: "Typical Latency", current: getLatency(state.selectedProtocol), compare: getLatency(state.compareProtocol) }
];
tableBody.selectAll("tr").remove();
features.forEach((f, i) => {
const row = tableBody.append("tr")
.style("background", i % 2 === 0 ? config.colors.lightGray : config.colors.white);
row.append("td")
.style("padding", "8px 12px")
.style("font-size", "13px")
.style("font-weight", "600")
.style("color", config.colors.navy)
.text(f.name);
row.append("td")
.style("padding", "8px 12px")
.style("font-size", "13px")
.style("color", config.colors.teal)
.text(f.current);
row.append("td")
.style("padding", "8px 12px")
.style("font-size", "13px")
.style("color", config.colors.orange)
.text(f.compare);
});
}
function getTransport(protocol) {
const transports = {
mqtt: "TCP (default), WebSocket",
coap: "UDP",
http: "TCP",
modbus: "TCP/RTU (serial)",
ble: "2.4 GHz radio",
zigbee: "IEEE 802.15.4"
};
return transports[protocol] || "Various";
}
function getBestFor(protocol) {
const uses = {
mqtt: "Cloud messaging, pub/sub",
coap: "Constrained devices",
http: "Web APIs, high bandwidth",
modbus: "Industrial automation",
ble: "Wearables, short range",
zigbee: "Mesh networks, home auto"
};
return uses[protocol] || "General purpose";
}
function getLatency(protocol) {
const latencies = {
mqtt: "~10-100ms",
coap: "~10-50ms",
http: "~50-200ms",
modbus: "~5-50ms",
ble: "~3-30ms",
zigbee: "~15-100ms"
};
return latencies[protocol] || "Varies";
}
function updateTimingDiagram() {
timingSvg.selectAll("*").remove();
const protocol = getProtocol();
const sample = getSample();
// Draw timing diagram
const margin = { left: 100, right: 30 };
const width = 880 - margin.left - margin.right;
// Client and Server labels
timingSvg.append("text")
.attr("x", margin.left - 10)
.attr("y", 30)
.attr("text-anchor", "end")
.attr("fill", config.colors.navy)
.attr("font-size", "13px")
.attr("font-weight", "600")
.text("Client");
timingSvg.append("text")
.attr("x", margin.left + width + 10)
.attr("y", 30)
.attr("text-anchor", "start")
.attr("fill", config.colors.navy)
.attr("font-size", "13px")
.attr("font-weight", "600")
.text("Server");
// Vertical lines
timingSvg.append("line")
.attr("x1", margin.left)
.attr("y1", 40)
.attr("x2", margin.left)
.attr("y2", 160)
.attr("stroke", config.colors.navy)
.attr("stroke-width", 2);
timingSvg.append("line")
.attr("x1", margin.left + width)
.attr("y1", 40)
.attr("x2", margin.left + width)
.attr("y2", 160)
.attr("stroke", config.colors.navy)
.attr("stroke-width", 2);
// Request arrow
const reqY = 70;
timingSvg.append("line")
.attr("x1", margin.left)
.attr("y1", reqY)
.attr("x2", margin.left + width)
.attr("y2", reqY + 20)
.attr("stroke", config.colors.teal)
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowhead)");
timingSvg.append("text")
.attr("x", margin.left + width / 2)
.attr("y", reqY - 5)
.attr("text-anchor", "middle")
.attr("fill", config.colors.teal)
.attr("font-size", "12px")
.attr("font-weight", "600")
.text(`${sample.name} Request`);
// Response arrow
const respY = 120;
timingSvg.append("line")
.attr("x1", margin.left + width)
.attr("y1", respY)
.attr("x2", margin.left)
.attr("y2", respY + 20)
.attr("stroke", config.colors.orange)
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowhead-orange)");
timingSvg.append("text")
.attr("x", margin.left + width / 2)
.attr("y", respY - 5)
.attr("text-anchor", "middle")
.attr("fill", config.colors.orange)
.attr("font-size", "12px")
.attr("font-weight", "600")
.text("Response/ACK");
// Arrowhead markers
const defs = timingSvg.append("defs");
defs.append("marker")
.attr("id", "arrowhead")
.attr("markerWidth", 10)
.attr("markerHeight", 7)
.attr("refX", 9)
.attr("refY", 3.5)
.attr("orient", "auto")
.append("polygon")
.attr("points", "0 0, 10 3.5, 0 7")
.attr("fill", config.colors.teal);
defs.append("marker")
.attr("id", "arrowhead-orange")
.attr("markerWidth", 10)
.attr("markerHeight", 7)
.attr("refX", 9)
.attr("refY", 3.5)
.attr("orient", "auto")
.append("polygon")
.attr("points", "0 0, 10 3.5, 0 7")
.attr("fill", config.colors.orange);
// RTT label
timingSvg.append("text")
.attr("x", margin.left - 50)
.attr("y", (reqY + respY + 20) / 2)
.attr("text-anchor", "middle")
.attr("fill", config.colors.gray)
.attr("font-size", "11px")
.text("RTT");
// Brace for RTT
timingSvg.append("path")
.attr("d", `M ${margin.left - 40} ${reqY} Q ${margin.left - 55} ${(reqY + respY + 20) / 2} ${margin.left - 40} ${respY + 20}`)
.attr("fill", "none")
.attr("stroke", config.colors.gray)
.attr("stroke-width", 1);
}
function updateLibrary() {
libraryGrid.selectAll("*").remove();
Object.entries(config.protocols).forEach(([protKey, protocol]) => {
Object.entries(protocol.samples).forEach(([sampleKey, sample]) => {
const card = libraryGrid.append("div")
.style("background", config.colors.white)
.style("border", `2px solid ${protocol.color}`)
.style("border-radius", "6px")
.style("padding", "12px")
.style("cursor", "pointer")
.style("transition", "transform 0.2s, box-shadow 0.2s");
card.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.style("margin-bottom", "6px")
.html(`
<span style="background: ${protocol.color}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600">${protocol.name}</span>
<span style="font-weight: 600; color: ${config.colors.navy}">${sample.name}</span>
`);
card.append("div")
.style("font-size", "12px")
.style("color", config.colors.gray)
.text(sample.description);
const bytes = calculateTotalBytes(sample.fields) + calculateTotalBytes(sample.payload);
card.append("div")
.style("font-size", "11px")
.style("color", config.colors.teal)
.style("margin-top", "6px")
.text(`${bytes} bytes`);
card.on("mouseenter", function() {
d3.select(this)
.style("transform", "translateY(-2px)")
.style("box-shadow", "0 4px 12px rgba(0,0,0,0.1)");
});
card.on("mouseleave", function() {
d3.select(this)
.style("transform", "translateY(0)")
.style("box-shadow", "none");
});
card.on("click", function() {
state.selectedProtocol = protKey;
state.selectedSample = sampleKey;
protocolSelect.property("value", protKey);
updateSampleOptions();
sampleSelect.property("value", sampleKey);
updateAll();
});
});
});
}
function updateAll() {
const protocol = getProtocol();
const sample = getSample();
protocolDesc.text(protocol.description);
sampleDesc.text(sample.description);
updateFieldVisualizer();
updateDump();
updateOverhead();
updateComparison();
updateTimingDiagram();
}
// ---------------------------------------------------------------------------
// EVENT HANDLERS
// ---------------------------------------------------------------------------
protocolSelect.on("change", function() {
state.selectedProtocol = this.value;
updateSampleOptions();
updateCompareOptions();
updateAll();
updateLibrary();
});
sampleSelect.on("change", function() {
state.selectedSample = this.value;
updateAll();
});
compareSelect.on("change", function() {
state.compareProtocol = this.value;
updateComparison();
});
hexBtn.on("click", function() {
state.showBinary = false;
hexBtn.style("background", config.colors.teal).style("color", config.colors.white).style("border", "none");
binaryBtn.style("background", config.colors.white).style("color", config.colors.navy).style("border", `1px solid ${config.colors.gray}`);
updateDump();
});
binaryBtn.on("click", function() {
state.showBinary = true;
binaryBtn.style("background", config.colors.teal).style("color", config.colors.white).style("border", "none");
hexBtn.style("background", config.colors.white).style("color", config.colors.navy).style("border", `1px solid ${config.colors.gray}`);
updateDump();
});
copyBtn.on("click", function() {
const text = dumpContent.text();
navigator.clipboard.writeText(text).then(() => {
copyBtn.text("Copied!");
setTimeout(() => copyBtn.text("Copy"), 2000);
});
});
// ---------------------------------------------------------------------------
// INITIALIZATION
// ---------------------------------------------------------------------------
updateSampleOptions();
updateCompareOptions();
updateAll();
updateLibrary();
return container.node();
}1563.2 Understanding Protocol Packets
1563.2.1 Protocol Comparison
| Protocol | Header Size | Transport | Best Use Case | Latency |
|---|---|---|---|---|
| MQTT | 2-5 bytes | TCP | Cloud messaging, pub/sub | 10-100ms |
| CoAP | 4 bytes | UDP | Constrained devices | 10-50ms |
| HTTP | 50-200+ bytes | TCP | Web APIs, REST | 50-200ms |
| Modbus | 7-8 bytes | TCP/Serial | Industrial automation | 5-50ms |
| BLE | 2-27 bytes | 2.4 GHz | Wearables, beacons | 3-30ms |
| Zigbee | 9+ bytes | 802.15.4 | Mesh networks | 15-100ms |
1563.2.2 Key Packet Components
Header Fields: Control information including:
- Protocol version and type
- Message identifiers
- Flags and options
- Length indicators
Payload: The actual data being transmitted
Checksum/CRC: Error detection codes ensuring data integrity
1563.2.3 Overhead Considerations
Protocol overhead directly impacts:
- Battery Life: More bytes = more radio transmission time
- Bandwidth: Critical for constrained networks
- Latency: Larger packets take longer to transmit
Rule of Thumb: For small sensor readings (< 10 bytes), CoAP or MQTT QoS 0 minimize overhead. HTTP’s verbose headers make it unsuitable for highly constrained devices.
1563.2.4 Common Packet Structures
MQTT PUBLISH (minimal):
Fixed Header (1 byte) + Remaining Length (1 byte) + Topic (variable) + Payload
CoAP GET (minimal):
Header (4 bytes) + Token (0-8 bytes) + Options (variable)
Modbus Read Request (fixed):
MBAP Header (7 bytes) + Function Code (1 byte) + Address (2 bytes) + Quantity (2 bytes)
- Use binary payloads instead of JSON when possible
- Choose protocols that match your QoS requirements
- Consider compression for repetitive data
- Use message batching for high-frequency sensors