From 1fe051c1b4669ae13b1392b3e252571fdd3aed6a Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Thu, 5 Dec 2024 23:01:45 -0600 Subject: [PATCH 01/20] start work on sdt packets, starting with JOIN --- packages/acn/src/models.ts | 58 +++++++++++++++++++++ packages/acn/src/pdu/index.ts | 2 + packages/acn/src/pdu/sdt.ts | 96 +++++++++++++++++++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 packages/acn/src/pdu/sdt.ts diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index a339a25..8136fbc 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -19,3 +19,61 @@ export type RootLayerPDU = { header: string; data: Uint8Array; }; + +export enum SessionDataTransportVectors { + REL_WRAP = 1, + UNREL_WRAP, + CHANNEL_PARAMS, + JOIN, + JOIN_REFUSE, + JOIN_ACCEPT, + LEAVE, + LEAVING, + CONNECT, + CONNECT_ACCEPT, + CONNECT_REFUSE, + DISCONNECT, + DISCONNECTING, + ACK, + NAK, + GET_SESSIONS, + SESSIONS, +} + +export enum TransportLayerAddressTypes { + SDT_ADDR_NULL = 0, + SDT_ADDR_IPV4, + SDT_ADDR_IPV6, +} + +export type SDTChannelParams = { + expiry: number; + nakOutboundFlag: number; + nakHoldoff: number; + nakModulus: number; + nakMaxWait: number; +}; + +export type TransportLayerAddress = { + type: TransportLayerAddressTypes; + port?: number; + address?: string; +}; + +export type SDTJOINData = { + componentID: string; + memberID: number; + channelNumber: number; + reciprocalChannel: number; + totalSequenceNumber: number; + reliableSequenceNumber: number; + destinationAddress: TransportLayerAddress; + channelParameters: SDTChannelParams; + adhocExpiry: number; +}; + +export type SessionDataTransportPDU = { + vector: SessionDataTransportVectors; + // TODO(jwetzell): cleanup these types + data: SDTJOINData | Uint8Array; +}; diff --git a/packages/acn/src/pdu/index.ts b/packages/acn/src/pdu/index.ts index 58670a5..07a880b 100644 --- a/packages/acn/src/pdu/index.ts +++ b/packages/acn/src/pdu/index.ts @@ -1,4 +1,6 @@ import rlp from './rlp'; +import sdt from './sdt'; export default { rlp, + sdt, }; diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts new file mode 100644 index 0000000..d9b2252 --- /dev/null +++ b/packages/acn/src/pdu/sdt.ts @@ -0,0 +1,96 @@ +import { SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; +import { toHex } from '../utils'; + +// TODO(jwetzell): work out flag inheritance, will need previous PDU + +export function decode(bytes: Uint8Array): SessionDataTransportPDU { + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + const flags = view.getUint8(0) >> 4; + const lengthFlag = ((flags >> 3) & 0x1) === 1; + const vectorFlag = ((flags >> 2) & 0x1) === 1; + + if (!vectorFlag) { + throw new Error('SDT PDU must have a vector'); + } + + const dataFlag = (flags & 0x1) === 1; + + if (!dataFlag) { + // TODO(jwetzell): idk know if this is true + throw new Error('SDT PDU must have data'); + } + + const lengthH = view.getUint8(0) & 0x0f; + let lengthOffset = 1; + const lengthL = view.getUint8(lengthOffset); + + let length = (lengthH << 8) + lengthL; + console.log('length', length); + if (lengthFlag) { + lengthOffset += 1; + const lengthX = view.getUint8(lengthOffset); + length = (lengthH << 16) + (lengthL << 8) + lengthX; + } + let vectorOffset = lengthOffset + 1; + + if (lengthFlag) { + vectorOffset += 1; + } + + const vector = view.getUint8(vectorOffset); + + const dataOffset = vectorOffset + 1; + // NOTE(jwetzell): flags/lengthH + lengthL + lengthX + vector + const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 1); + const data = bytes.subarray(dataOffset, dataOffset + dataLength); + + switch (vector) { + case SessionDataTransportVectors.JOIN: + const destinationAddress: TransportLayerAddress = { + type: view.getUint8(dataOffset + 30), + }; + + // TODO(jwetzell): handle IPv6, type === 2 + if (destinationAddress.type === 1) { + // IPv4 + destinationAddress.port = view.getUint16(dataOffset + 31); + destinationAddress.address = bytes.subarray(dataOffset + 33, dataOffset + 37).join('.'); + } + + const adhocExpiry = view.getUint8(dataOffset + 45); + const joinData = { + componentID: toHex(bytes.subarray(dataOffset, dataOffset + 16)), + memberID: view.getUint16(dataOffset + 16), + channelNumber: view.getUint16(dataOffset + 18), + reciprocalChannel: view.getUint16(dataOffset + 20), + totalSequenceNumber: view.getUint32(dataOffset + 22), + reliableSequenceNumber: view.getUint32(dataOffset + 26), + destinationAddress, + channelParameters: { + expiry: view.getUint8(dataOffset + 37), + nakOutboundFlag: view.getUint8(dataOffset + 38), + nakHoldoff: view.getUint16(dataOffset + 39), + nakModulus: view.getUint16(dataOffset + 41), + nakMaxWait: view.getUint16(dataOffset + 43), + }, + adhocExpiry, + }; + return { + vector, + data: joinData, + }; + + default: + console.error(`unhandled SDT vector: ${vector}`); + break; + } + + return { + vector, + data, + }; +} + +export default { + decode, +}; From fbb1f2e52ee71ef79b5447b9c0bbb363f312b4f4 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Thu, 5 Dec 2024 23:08:00 -0600 Subject: [PATCH 02/20] cleanup comment --- packages/acn/src/pdu/sdt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index d9b2252..721292c 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -16,7 +16,7 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { const dataFlag = (flags & 0x1) === 1; if (!dataFlag) { - // TODO(jwetzell): idk know if this is true + // TODO(jwetzell): idk if this is true throw new Error('SDT PDU must have data'); } From d3c92f6ee27e92ea970c4d3ca534eff584707e59 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Thu, 5 Dec 2024 23:19:03 -0600 Subject: [PATCH 03/20] use blocks on switch case --- packages/acn/src/pdu/sdt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 721292c..ca1e4bd 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -45,7 +45,7 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { const data = bytes.subarray(dataOffset, dataOffset + dataLength); switch (vector) { - case SessionDataTransportVectors.JOIN: + case SessionDataTransportVectors.JOIN: { const destinationAddress: TransportLayerAddress = { type: view.getUint8(dataOffset + 30), }; @@ -79,7 +79,7 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { vector, data: joinData, }; - + } default: console.error(`unhandled SDT vector: ${vector}`); break; From b300978e05d6cbe6d6a58d8607fcc354517ce291 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Thu, 5 Dec 2024 23:20:13 -0600 Subject: [PATCH 04/20] fix casing on type --- packages/acn/src/models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 8136fbc..816e7f2 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -60,7 +60,7 @@ export type TransportLayerAddress = { address?: string; }; -export type SDTJOINData = { +export type SDTJoinData = { componentID: string; memberID: number; channelNumber: number; @@ -75,5 +75,5 @@ export type SDTJOINData = { export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJOINData | Uint8Array; + data: SDTJoinData | Uint8Array; }; From e56ecdbc0e7d861a2e57abe3c3cef241133fbad9 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Thu, 5 Dec 2024 23:21:47 -0600 Subject: [PATCH 05/20] add support for decoding join accept data --- packages/acn/src/models.ts | 10 +++++++++- packages/acn/src/pdu/sdt.ts | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 816e7f2..56e80e8 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -72,8 +72,16 @@ export type SDTJoinData = { adhocExpiry: number; }; +export type SDTJoinAcceptData = { + leaderComponentID: string; + channelNumber: number; + memberID: number; + reliableSequenceNumber: number; + reciprocalChannel: number; +}; + export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | Uint8Array; + data: SDTJoinData | SDTJoinAcceptData | Uint8Array; }; diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index ca1e4bd..d231de2 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -80,6 +80,19 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { data: joinData, }; } + case SessionDataTransportVectors.JOIN_ACCEPT: { + const joinData = { + leaderComponentID: toHex(bytes.subarray(dataOffset, dataOffset + 16)), + channelNumber: view.getUint16(dataOffset + 16), + memberID: view.getUint16(dataOffset + 18), + reliableSequenceNumber: view.getUint32(dataOffset + 20), + reciprocalChannel: view.getUint16(dataOffset + 24), + }; + return { + vector, + data: joinData, + }; + } default: console.error(`unhandled SDT vector: ${vector}`); break; From 76892f6e19fe5a80cfce202fe53bf8ca18c69827 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Thu, 5 Dec 2024 23:21:58 -0600 Subject: [PATCH 06/20] add support for decoding join refuse data --- packages/acn/src/models.ts | 10 +++++++++- packages/acn/src/pdu/sdt.ts | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 56e80e8..5b1e4bf 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -80,8 +80,16 @@ export type SDTJoinAcceptData = { reciprocalChannel: number; }; +export type SDTJoinRefuseData = { + leaderComponentID: string; + channelNumber: number; + memberID: number; + reliableSequenceNumber: number; + refuseCode: number; +}; + export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | SDTJoinAcceptData | Uint8Array; + data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData| Uint8Array; }; diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index d231de2..27108a8 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -93,6 +93,20 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { data: joinData, }; } + case SessionDataTransportVectors.JOIN_REFUSE: { + const joinData = { + leaderComponentID: toHex(bytes.subarray(dataOffset, dataOffset + 16)), + channelNumber: view.getUint16(dataOffset + 16), + memberID: view.getUint16(dataOffset + 18), + reliableSequenceNumber: view.getUint32(dataOffset + 20), + refuseCode: view.getUint8(dataOffset + 24), + }; + + return { + vector, + data: joinData, + }; + } default: console.error(`unhandled SDT vector: ${vector}`); break; From f62b294fe84d124577eb604ebff60d81147a3750 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Fri, 6 Dec 2024 21:52:27 -0600 Subject: [PATCH 07/20] add basic wrapper and ack data decoding --- packages/acn/src/models.ts | 17 ++++++++++++++++- packages/acn/src/pdu/sdt.ts | 29 +++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 5b1e4bf..e1c045a 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -88,8 +88,23 @@ export type SDTJoinRefuseData = { refuseCode: number; }; +export type SDTWrapperData = { + channelNumber: number; + totalSequenceNumber: number; + reliableSequenceNumber: number; + oldestAvailableWrapper: number; + firstMemberToAck: number; + lastMemberToAck: number; + makThreshold: number; + sdtClientBlock: Uint8Array; +}; + +export type SDTAckData = { + reliableSequenceNumber: number; +}; + export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData| Uint8Array; + data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | Uint8Array; }; diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 27108a8..b2224b9 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,4 +1,4 @@ -import { SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; +import { SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; import { toHex } from '../utils'; // TODO(jwetzell): work out flag inheritance, will need previous PDU @@ -25,7 +25,6 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { const lengthL = view.getUint8(lengthOffset); let length = (lengthH << 8) + lengthL; - console.log('length', length); if (lengthFlag) { lengthOffset += 1; const lengthX = view.getUint8(lengthOffset); @@ -107,6 +106,32 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { data: joinData, }; } + case SessionDataTransportVectors.REL_WRAP: + case SessionDataTransportVectors.UNREL_WRAP: { + const wrapperData: SDTWrapperData = { + channelNumber: view.getUint16(dataOffset), + totalSequenceNumber: view.getUint32(dataOffset + 2), + reliableSequenceNumber: view.getUint32(dataOffset + 6), + oldestAvailableWrapper: view.getUint32(dataOffset + 10), + firstMemberToAck: view.getUint16(dataOffset + 14), + lastMemberToAck: view.getUint16(dataOffset + 16), + makThreshold: view.getUint16(dataOffset + 18), + // TODO(jwetzell): actually decode client block + sdtClientBlock: bytes.subarray(dataOffset + 20), + }; + return { + vector, + data: wrapperData, + }; + } + case SessionDataTransportVectors.ACK: { + return { + vector, + data: { + reliableSequenceNumber: view.getUint32(dataOffset), + }, + }; + } default: console.error(`unhandled SDT vector: ${vector}`); break; From 148f3276d61573676103f195a26a80ffaffd099b Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 10:30:55 -0600 Subject: [PATCH 08/20] add SDT Client block decoding --- packages/acn/src/models.ts | 15 ++- packages/acn/src/pdu/sdt.ts | 245 ++++++++++++++++++++++-------------- 2 files changed, 160 insertions(+), 100 deletions(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index e1c045a..0a4df2f 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -1,5 +1,5 @@ -export type Protocols = { - SDT: 1; +export enum Protocols { + SDT = 1 }; export type UDPPreamble = { @@ -17,7 +17,7 @@ export type ACNPacket = { export type RootLayerPDU = { vector: number; header: string; - data: Uint8Array; + data: SessionDataTransportPDU | Uint8Array; }; export enum SessionDataTransportVectors { @@ -96,7 +96,7 @@ export type SDTWrapperData = { firstMemberToAck: number; lastMemberToAck: number; makThreshold: number; - sdtClientBlock: Uint8Array; + sdtClientBlock?: SDTClientBlock; }; export type SDTAckData = { @@ -108,3 +108,10 @@ export type SessionDataTransportPDU = { // TODO(jwetzell): cleanup these types data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | Uint8Array; }; + +export type SDTClientBlock = { + memberID: number + clientProtocol: number + association: number + data: SessionDataTransportPDU | Uint8Array +} diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index b2224b9..de68d41 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,4 +1,4 @@ -import { SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; +import { Protocols, SDTAckData, SDTJoinAcceptData, SDTJoinData, SDTJoinRefuseData, SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; import { toHex } from '../utils'; // TODO(jwetzell): work out flag inheritance, will need previous PDU @@ -41,101 +41,7 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { const dataOffset = vectorOffset + 1; // NOTE(jwetzell): flags/lengthH + lengthL + lengthX + vector const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 1); - const data = bytes.subarray(dataOffset, dataOffset + dataLength); - - switch (vector) { - case SessionDataTransportVectors.JOIN: { - const destinationAddress: TransportLayerAddress = { - type: view.getUint8(dataOffset + 30), - }; - - // TODO(jwetzell): handle IPv6, type === 2 - if (destinationAddress.type === 1) { - // IPv4 - destinationAddress.port = view.getUint16(dataOffset + 31); - destinationAddress.address = bytes.subarray(dataOffset + 33, dataOffset + 37).join('.'); - } - - const adhocExpiry = view.getUint8(dataOffset + 45); - const joinData = { - componentID: toHex(bytes.subarray(dataOffset, dataOffset + 16)), - memberID: view.getUint16(dataOffset + 16), - channelNumber: view.getUint16(dataOffset + 18), - reciprocalChannel: view.getUint16(dataOffset + 20), - totalSequenceNumber: view.getUint32(dataOffset + 22), - reliableSequenceNumber: view.getUint32(dataOffset + 26), - destinationAddress, - channelParameters: { - expiry: view.getUint8(dataOffset + 37), - nakOutboundFlag: view.getUint8(dataOffset + 38), - nakHoldoff: view.getUint16(dataOffset + 39), - nakModulus: view.getUint16(dataOffset + 41), - nakMaxWait: view.getUint16(dataOffset + 43), - }, - adhocExpiry, - }; - return { - vector, - data: joinData, - }; - } - case SessionDataTransportVectors.JOIN_ACCEPT: { - const joinData = { - leaderComponentID: toHex(bytes.subarray(dataOffset, dataOffset + 16)), - channelNumber: view.getUint16(dataOffset + 16), - memberID: view.getUint16(dataOffset + 18), - reliableSequenceNumber: view.getUint32(dataOffset + 20), - reciprocalChannel: view.getUint16(dataOffset + 24), - }; - return { - vector, - data: joinData, - }; - } - case SessionDataTransportVectors.JOIN_REFUSE: { - const joinData = { - leaderComponentID: toHex(bytes.subarray(dataOffset, dataOffset + 16)), - channelNumber: view.getUint16(dataOffset + 16), - memberID: view.getUint16(dataOffset + 18), - reliableSequenceNumber: view.getUint32(dataOffset + 20), - refuseCode: view.getUint8(dataOffset + 24), - }; - - return { - vector, - data: joinData, - }; - } - case SessionDataTransportVectors.REL_WRAP: - case SessionDataTransportVectors.UNREL_WRAP: { - const wrapperData: SDTWrapperData = { - channelNumber: view.getUint16(dataOffset), - totalSequenceNumber: view.getUint32(dataOffset + 2), - reliableSequenceNumber: view.getUint32(dataOffset + 6), - oldestAvailableWrapper: view.getUint32(dataOffset + 10), - firstMemberToAck: view.getUint16(dataOffset + 14), - lastMemberToAck: view.getUint16(dataOffset + 16), - makThreshold: view.getUint16(dataOffset + 18), - // TODO(jwetzell): actually decode client block - sdtClientBlock: bytes.subarray(dataOffset + 20), - }; - return { - vector, - data: wrapperData, - }; - } - case SessionDataTransportVectors.ACK: { - return { - vector, - data: { - reliableSequenceNumber: view.getUint32(dataOffset), - }, - }; - } - default: - console.error(`unhandled SDT vector: ${vector}`); - break; - } + const data = decodeData(vector, bytes.subarray(dataOffset, dataOffset + dataLength)); return { vector, @@ -143,6 +49,153 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { }; } +function decodeClientBlock(bytes: Uint8Array) { + if(bytes.byteLength === 0){ + return undefined + } + + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + const flags = view.getUint8(0) >> 4; + const lengthFlag = ((flags >> 3) & 0x1) === 1; + const vectorFlag = ((flags >> 2) & 0x1) === 1; + + if (!vectorFlag) { + throw new Error('SDT PDU must have a vector'); + } + + const dataFlag = (flags & 0x1) === 1; + + if (!dataFlag) { + // TODO(jwetzell): idk if this is true + throw new Error('SDT PDU must have data'); + } + + const lengthH = view.getUint8(0) & 0x0f; + let lengthOffset = 1; + const lengthL = view.getUint8(lengthOffset); + + let length = (lengthH << 8) + lengthL; + if (lengthFlag) { + lengthOffset += 1; + const lengthX = view.getUint8(lengthOffset); + length = (lengthH << 16) + (lengthL << 8) + lengthX; + } + let vectorOffset = lengthOffset + 1; + + if (lengthFlag) { + vectorOffset += 1; + } + + const memberID = view.getUint16(vectorOffset); + + + const headerOffset = vectorOffset + 2 + const clientProtocol = view.getUint32(headerOffset) + const association = view.getUint16(headerOffset+4) + + + const dataOffset = headerOffset + 6; + // NOTE(jwetzell): flags/lengthH + lengthL + lengthX + vector + header + const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 2 + 6); + let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength) + + + if(clientProtocol === Protocols.SDT){ + data = decode(data) + } else { + console.error(`SDT client block contains unknown protocol: ${clientProtocol}`) + } + return { + memberID, + clientProtocol, + association, + data + } +} + + +function decodeData(vector: SessionDataTransportVectors, bytes: Uint8Array): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | Uint8Array { + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) + switch (vector) { + case SessionDataTransportVectors.JOIN: { + const destinationAddress: TransportLayerAddress = { + type: view.getUint8(30), + }; + + // TODO(jwetzell): handle IPv6, type === 2 + if (destinationAddress.type === 1) { + // IPv4 + destinationAddress.port = view.getUint16(31); + destinationAddress.address = bytes.subarray(33, 37).join('.'); + } + + const adhocExpiry = view.getUint8(45); + const joinData = { + componentID: toHex(bytes.subarray(0, 16)), + memberID: view.getUint16(16), + channelNumber: view.getUint16(18), + reciprocalChannel: view.getUint16(20), + totalSequenceNumber: view.getUint32(22), + reliableSequenceNumber: view.getUint32(26), + destinationAddress, + channelParameters: { + expiry: view.getUint8(37), + nakOutboundFlag: view.getUint8(38), + nakHoldoff: view.getUint16(39), + nakModulus: view.getUint16(41), + nakMaxWait: view.getUint16(43), + }, + adhocExpiry, + }; + return joinData; + } + case SessionDataTransportVectors.JOIN_ACCEPT: { + const joinData = { + leaderComponentID: toHex(bytes.subarray(0, 16)), + channelNumber: view.getUint16(16), + memberID: view.getUint16(18), + reliableSequenceNumber: view.getUint32(20), + reciprocalChannel: view.getUint16(24), + }; + return joinData; + } + case SessionDataTransportVectors.JOIN_REFUSE: { + const joinData = { + leaderComponentID: toHex(bytes.subarray(0, 16)), + channelNumber: view.getUint16(16), + memberID: view.getUint16(18), + reliableSequenceNumber: view.getUint32(20), + refuseCode: view.getUint8(24), + }; + + return joinData; + } + case SessionDataTransportVectors.REL_WRAP: + case SessionDataTransportVectors.UNREL_WRAP: { + const wrapperData: SDTWrapperData = { + channelNumber: view.getUint16(0), + totalSequenceNumber: view.getUint32(2), + reliableSequenceNumber: view.getUint32(6), + oldestAvailableWrapper: view.getUint32(10), + firstMemberToAck: view.getUint16(14), + lastMemberToAck: view.getUint16(16), + makThreshold: view.getUint16(18), + sdtClientBlock: decodeClientBlock(bytes.subarray(20)), + }; + + return wrapperData; + } + case SessionDataTransportVectors.ACK: { + return { + reliableSequenceNumber: view.getUint32(0), + }; + } + default: + console.error(`unhandled SDT vector: ${vector}`); + return bytes; + } +} + export default { decode, }; From d238f259a96a52e269320125971b61457bc07a72 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 10:31:08 -0600 Subject: [PATCH 09/20] decode SDT data in RLP if found --- packages/acn/src/pdu/rlp.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/acn/src/pdu/rlp.ts b/packages/acn/src/pdu/rlp.ts index bf8e9dc..3190f4d 100644 --- a/packages/acn/src/pdu/rlp.ts +++ b/packages/acn/src/pdu/rlp.ts @@ -1,4 +1,5 @@ -import { RootLayerPDU } from '../models'; +import pdu from '.'; +import { Protocols, RootLayerPDU, SessionDataTransportPDU } from '../models'; import { toHex } from '../utils'; function decode(bytes: Uint8Array): RootLayerPDU { @@ -49,7 +50,11 @@ function decode(bytes: Uint8Array): RootLayerPDU { const dataOffset = headerOffset + 16; // NOTE(jwetzell): flags/lengthH + lengthL + lengthX + vector + header const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 4 + 16); - const data = bytes.subarray(dataOffset, dataOffset + dataLength); + let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength); + + if (vector === Protocols.SDT) { + data = pdu.sdt.decode(data); + } return { vector, From c1a5fb08a9acd9ed27c0410fe76466a795f3e2d4 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 11:17:27 -0600 Subject: [PATCH 10/20] Update README.md --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index ee782d2..2b05d92 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,23 @@ not sure how far I will take this but messing around with E1.17 ACN protocol ## areas - rlp - sdt + - vectors + - [x] REL_WRAP + - [x] UNREL_WRAP + - [ ] CHANNEL_PARAMS + - [x] JOIN + - [x] JOIN_REFUSE + - [x] JOIN_ACCEPT + - [ ] LEAVE + - [x] LEAVING + - [ ] CONNECT + - [ ] CONNECT_ACCEPT + - [ ] CONNECT_REFUSE + - [ ] DISCONNECT + - [ ] DISCONNECTING + - [x] ACK + - [ ] NAK + - [ ] GET_SESSIONS + - [ ] SESSIONS - dmp - ddl From 03df8fec565d2d8955933db2bcbc0f20e1c87fa7 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 10:34:36 -0600 Subject: [PATCH 11/20] add sdt leaving data decoding --- packages/acn/src/models.ts | 10 +++++++++- packages/acn/src/pdu/sdt.ts | 13 +++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 0a4df2f..0eae7e9 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -88,6 +88,14 @@ export type SDTJoinRefuseData = { refuseCode: number; }; +export type SDTLeavingData = { + leaderComponentID: string; + channelNumber: number; + memberID: number; + reliableSequenceNumber: number; + reasonCode: number; +}; + export type SDTWrapperData = { channelNumber: number; totalSequenceNumber: number; @@ -106,7 +114,7 @@ export type SDTAckData = { export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | Uint8Array; + data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData| Uint8Array; }; export type SDTClientBlock = { diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index de68d41..72eefec 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,4 +1,4 @@ -import { Protocols, SDTAckData, SDTJoinAcceptData, SDTJoinData, SDTJoinRefuseData, SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; +import { Protocols, SDTAckData, SDTJoinAcceptData, SDTJoinData, SDTJoinRefuseData, SDTLeavingData, SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; import { toHex } from '../utils'; // TODO(jwetzell): work out flag inheritance, will need previous PDU @@ -114,7 +114,7 @@ function decodeClientBlock(bytes: Uint8Array) { } -function decodeData(vector: SessionDataTransportVectors, bytes: Uint8Array): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | Uint8Array { +function decodeData(vector: SessionDataTransportVectors, bytes: Uint8Array): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | Uint8Array { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) switch (vector) { case SessionDataTransportVectors.JOIN: { @@ -190,6 +190,15 @@ function decodeData(vector: SessionDataTransportVectors, bytes: Uint8Array): SDT reliableSequenceNumber: view.getUint32(0), }; } + case SessionDataTransportVectors.LEAVING: { + return { + leaderComponentID: toHex(bytes.subarray(0, 16)), + channelNumber: view.getUint16(16), + memberID: view.getUint16(18), + reliableSequenceNumber: view.getUint32(20), + reasonCode: view.getUint8(24), + }; + } default: console.error(`unhandled SDT vector: ${vector}`); return bytes; From 10403a49cafb64392919eb53cb807a846d44db6c Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 11:19:20 -0600 Subject: [PATCH 12/20] npm run format:write --- packages/acn/src/models.ts | 16 ++++++------- packages/acn/src/pdu/rlp.ts | 2 +- packages/acn/src/pdu/sdt.ts | 46 ++++++++++++++++++++++--------------- packages/acn/src/utils.ts | 14 +++++------ 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 0eae7e9..80fd115 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -1,6 +1,6 @@ export enum Protocols { - SDT = 1 -}; + SDT = 1, +} export type UDPPreamble = { preambleSize: number; @@ -114,12 +114,12 @@ export type SDTAckData = { export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData| Uint8Array; + data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | Uint8Array; }; export type SDTClientBlock = { - memberID: number - clientProtocol: number - association: number - data: SessionDataTransportPDU | Uint8Array -} + memberID: number; + clientProtocol: number; + association: number; + data: SessionDataTransportPDU | Uint8Array; +}; diff --git a/packages/acn/src/pdu/rlp.ts b/packages/acn/src/pdu/rlp.ts index 3190f4d..4e0b569 100644 --- a/packages/acn/src/pdu/rlp.ts +++ b/packages/acn/src/pdu/rlp.ts @@ -54,7 +54,7 @@ function decode(bytes: Uint8Array): RootLayerPDU { if (vector === Protocols.SDT) { data = pdu.sdt.decode(data); - } + } return { vector, diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 72eefec..32c6a9b 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,4 +1,15 @@ -import { Protocols, SDTAckData, SDTJoinAcceptData, SDTJoinData, SDTJoinRefuseData, SDTLeavingData, SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, TransportLayerAddress } from '../models'; +import { + Protocols, + SDTAckData, + SDTJoinAcceptData, + SDTJoinData, + SDTJoinRefuseData, + SDTLeavingData, + SDTWrapperData, + SessionDataTransportPDU, + SessionDataTransportVectors, + TransportLayerAddress, +} from '../models'; import { toHex } from '../utils'; // TODO(jwetzell): work out flag inheritance, will need previous PDU @@ -50,8 +61,8 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { } function decodeClientBlock(bytes: Uint8Array) { - if(bytes.byteLength === 0){ - return undefined + if (bytes.byteLength === 0) { + return undefined; } const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); @@ -88,34 +99,33 @@ function decodeClientBlock(bytes: Uint8Array) { const memberID = view.getUint16(vectorOffset); - - const headerOffset = vectorOffset + 2 - const clientProtocol = view.getUint32(headerOffset) - const association = view.getUint16(headerOffset+4) - + const headerOffset = vectorOffset + 2; + const clientProtocol = view.getUint32(headerOffset); + const association = view.getUint16(headerOffset + 4); const dataOffset = headerOffset + 6; // NOTE(jwetzell): flags/lengthH + lengthL + lengthX + vector + header const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 2 + 6); - let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength) + let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength); - - if(clientProtocol === Protocols.SDT){ - data = decode(data) + if (clientProtocol === Protocols.SDT) { + data = decode(data); } else { - console.error(`SDT client block contains unknown protocol: ${clientProtocol}`) + console.error(`SDT client block contains unknown protocol: ${clientProtocol}`); } return { memberID, clientProtocol, association, - data - } + data, + }; } - -function decodeData(vector: SessionDataTransportVectors, bytes: Uint8Array): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | Uint8Array { - const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) +function decodeData( + vector: SessionDataTransportVectors, + bytes: Uint8Array +): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | Uint8Array { + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); switch (vector) { case SessionDataTransportVectors.JOIN: { const destinationAddress: TransportLayerAddress = { diff --git a/packages/acn/src/utils.ts b/packages/acn/src/utils.ts index cd0b88d..5747042 100644 --- a/packages/acn/src/utils.ts +++ b/packages/acn/src/utils.ts @@ -1,7 +1,7 @@ -export function toHex(bytes: Uint8Array): string{ - let hex = ''; - bytes.forEach((byte)=>{ - hex += byte.toString(16).padStart(2,'0') - }) - return hex; -} \ No newline at end of file +export function toHex(bytes: Uint8Array): string { + let hex = ''; + bytes.forEach((byte) => { + hex += byte.toString(16).padStart(2, '0'); + }); + return hex; +} From af67fe3cfec5fc4aa2dba8bb23e7c61ce24a827e Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 11:36:11 -0600 Subject: [PATCH 13/20] add sdt get sessions data decoding --- README.md | 16 ++++++++-------- packages/acn/src/models.ts | 6 +++++- packages/acn/src/pdu/sdt.ts | 8 +++++++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2b05d92..fa46500 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,22 @@ not sure how far I will take this but messing around with E1.17 ACN protocol - rlp - sdt - vectors - - [x] REL_WRAP - - [x] UNREL_WRAP - - [ ] CHANNEL_PARAMS - [x] JOIN - [x] JOIN_REFUSE - [x] JOIN_ACCEPT - - [ ] LEAVE - [x] LEAVING + - [ ] NAK + - [x] REL_WRAP + - [x] UNREL_WRAP + - [x] GET_SESSIONS + - [ ] SESSIONS + - [x] ACK + - [ ] CHANNEL_PARAMS + - [ ] LEAVE - [ ] CONNECT - [ ] CONNECT_ACCEPT - [ ] CONNECT_REFUSE - [ ] DISCONNECT - [ ] DISCONNECTING - - [x] ACK - - [ ] NAK - - [ ] GET_SESSIONS - - [ ] SESSIONS - dmp - ddl diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 80fd115..addb466 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -111,10 +111,14 @@ export type SDTAckData = { reliableSequenceNumber: number; }; +export type SDTGetSessionsData = { + componentID: string; +}; + export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | Uint8Array; + data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | SDTGetSessionsData | Uint8Array; }; export type SDTClientBlock = { diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 32c6a9b..02e2a34 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,6 +1,7 @@ import { Protocols, SDTAckData, + SDTGetSessionsData, SDTJoinAcceptData, SDTJoinData, SDTJoinRefuseData, @@ -124,7 +125,7 @@ function decodeClientBlock(bytes: Uint8Array) { function decodeData( vector: SessionDataTransportVectors, bytes: Uint8Array -): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | Uint8Array { +): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | SDTGetSessionsData | Uint8Array { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); switch (vector) { case SessionDataTransportVectors.JOIN: { @@ -209,6 +210,11 @@ function decodeData( reasonCode: view.getUint8(24), }; } + case SessionDataTransportVectors.GET_SESSIONS: { + return { + componentID: toHex(bytes.subarray(0, 16)) + } + } default: console.error(`unhandled SDT vector: ${vector}`); return bytes; From 759ae56bdc1d992a87070f6476d2d4c5de1f2ced Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 11:41:03 -0600 Subject: [PATCH 14/20] add sdt NAK data decoding --- README.md | 2 +- packages/acn/src/models.ts | 20 +++++++++++++++++++- packages/acn/src/pdu/sdt.ts | 26 +++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fa46500..1293b2d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ not sure how far I will take this but messing around with E1.17 ACN protocol - [x] JOIN_REFUSE - [x] JOIN_ACCEPT - [x] LEAVING - - [ ] NAK + - [x] NAK - [x] REL_WRAP - [x] UNREL_WRAP - [x] GET_SESSIONS diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index addb466..e9a49e3 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -111,6 +111,15 @@ export type SDTAckData = { reliableSequenceNumber: number; }; +export type SDTNakData = { + leaderComponenetID: string; + channelNumber: number; + memberID: number; + reliableSequenceNumber: number; + firstMissedSequence: number; + lastMissedSequence: number; +}; + export type SDTGetSessionsData = { componentID: string; }; @@ -118,7 +127,16 @@ export type SDTGetSessionsData = { export type SessionDataTransportPDU = { vector: SessionDataTransportVectors; // TODO(jwetzell): cleanup these types - data: SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | SDTGetSessionsData | Uint8Array; + data: + | SDTJoinData + | SDTJoinAcceptData + | SDTJoinRefuseData + | SDTWrapperData + | SDTAckData + | SDTLeavingData + | SDTGetSessionsData + | SDTNakData + | Uint8Array; }; export type SDTClientBlock = { diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 02e2a34..a5e593d 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -6,6 +6,7 @@ import { SDTJoinData, SDTJoinRefuseData, SDTLeavingData, + SDTNakData, SDTWrapperData, SessionDataTransportPDU, SessionDataTransportVectors, @@ -125,7 +126,16 @@ function decodeClientBlock(bytes: Uint8Array) { function decodeData( vector: SessionDataTransportVectors, bytes: Uint8Array -): SDTJoinData | SDTJoinAcceptData | SDTJoinRefuseData | SDTWrapperData | SDTAckData | SDTLeavingData | SDTGetSessionsData | Uint8Array { +): + | SDTJoinData + | SDTJoinAcceptData + | SDTJoinRefuseData + | SDTWrapperData + | SDTAckData + | SDTLeavingData + | SDTGetSessionsData + | SDTNakData + | Uint8Array { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); switch (vector) { case SessionDataTransportVectors.JOIN: { @@ -212,8 +222,18 @@ function decodeData( } case SessionDataTransportVectors.GET_SESSIONS: { return { - componentID: toHex(bytes.subarray(0, 16)) - } + componentID: toHex(bytes.subarray(0, 16)), + }; + } + case SessionDataTransportVectors.NAK: { + return { + leaderComponentID: toHex(bytes.subarray(0, 16)), + channelNumber: view.getUint16(16), + memberID: view.getUint16(18), + reliableSequenceNumber: view.getUint32(20), + firstMissedSequence: view.getUint32(24), + lastMissedSequence: view.getUint32(28), + }; } default: console.error(`unhandled SDT vector: ${vector}`); From a47205ed9cc0585aa30805c78d3877c17398bfab Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 12:06:03 -0600 Subject: [PATCH 15/20] add sdt connect, connect accept, connect refuse data decoding --- README.md | 6 +++--- packages/acn/src/models.ts | 13 +++++++++++++ packages/acn/src/pdu/sdt.ts | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1293b2d..4c10d6c 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ not sure how far I will take this but messing around with E1.17 ACN protocol - [x] ACK - [ ] CHANNEL_PARAMS - [ ] LEAVE - - [ ] CONNECT - - [ ] CONNECT_ACCEPT - - [ ] CONNECT_REFUSE + - [x] CONNECT + - [x] CONNECT_ACCEPT + - [x] CONNECT_REFUSE - [ ] DISCONNECT - [ ] DISCONNECTING - dmp diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index e9a49e3..64432e8 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -120,6 +120,16 @@ export type SDTNakData = { lastMissedSequence: number; }; +export type SDTConnectData = { + protocolID: number; +}; + +export type SDTConnectAcceptData = SDTConnectData; +export type SDTConnectRefuseData = { + protocolID: number; + refuseCode: number; +}; + export type SDTGetSessionsData = { componentID: string; }; @@ -136,6 +146,9 @@ export type SessionDataTransportPDU = { | SDTLeavingData | SDTGetSessionsData | SDTNakData + | SDTConnectData + | SDTConnectAcceptData + | SDTConnectRefuseData | Uint8Array; }; diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index a5e593d..661c507 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,6 +1,9 @@ import { Protocols, SDTAckData, + SDTConnectAcceptData, + SDTConnectData, + SDTConnectRefuseData, SDTGetSessionsData, SDTJoinAcceptData, SDTJoinData, @@ -135,6 +138,9 @@ function decodeData( | SDTLeavingData | SDTGetSessionsData | SDTNakData + | SDTConnectData + | SDTConnectAcceptData + | SDTConnectRefuseData | Uint8Array { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); switch (vector) { @@ -235,6 +241,18 @@ function decodeData( lastMissedSequence: view.getUint32(28), }; } + case SessionDataTransportVectors.CONNECT: + case SessionDataTransportVectors.CONNECT_ACCEPT: { + return { + protocolID: view.getUint32(0), + }; + } + case SessionDataTransportVectors.CONNECT_REFUSE: { + return { + protocolID: view.getUint32(0), + refuseCode: view.getUint8(4), + }; + } default: console.error(`unhandled SDT vector: ${vector}`); return bytes; From 98b316c22d96f22cac01d743994775a271b36303 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 13:56:56 -0600 Subject: [PATCH 16/20] add sdt disconnect and disconnecting data decoding --- packages/acn/src/models.ts | 8 ++++++++ packages/acn/src/pdu/sdt.ts | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/acn/src/models.ts b/packages/acn/src/models.ts index 64432e8..3c2e858 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/models.ts @@ -130,6 +130,12 @@ export type SDTConnectRefuseData = { refuseCode: number; }; +export type SDTDisconnectData = SDTConnectData; +export type SDTDisconnectingData = { + protocolID: number; + reasonCode: number; +}; + export type SDTGetSessionsData = { componentID: string; }; @@ -149,6 +155,8 @@ export type SessionDataTransportPDU = { | SDTConnectData | SDTConnectAcceptData | SDTConnectRefuseData + | SDTDisconnectData + | SDTDisconnectingData | Uint8Array; }; diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 661c507..6b0c61d 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -4,6 +4,8 @@ import { SDTConnectAcceptData, SDTConnectData, SDTConnectRefuseData, + SDTDisconnectData, + SDTDisconnectingData, SDTGetSessionsData, SDTJoinAcceptData, SDTJoinData, @@ -141,6 +143,8 @@ function decodeData( | SDTConnectData | SDTConnectAcceptData | SDTConnectRefuseData + | SDTDisconnectData + | SDTDisconnectingData | Uint8Array { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); switch (vector) { @@ -241,6 +245,7 @@ function decodeData( lastMissedSequence: view.getUint32(28), }; } + case SessionDataTransportVectors.DISCONNECT: case SessionDataTransportVectors.CONNECT: case SessionDataTransportVectors.CONNECT_ACCEPT: { return { @@ -253,6 +258,12 @@ function decodeData( refuseCode: view.getUint8(4), }; } + case SessionDataTransportVectors.DISCONNECTING: { + return { + protocolID: view.getUint32(0), + reasonCode: view.getUint8(4), + }; + } default: console.error(`unhandled SDT vector: ${vector}`); return bytes; From b4f406de2183e3ceab2d092ff92d006075440bae Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 7 Dec 2024 14:06:14 -0600 Subject: [PATCH 17/20] type/enum cleanup --- packages/acn/src/enums.ts | 45 ++++++++++++++++++++++++ packages/acn/src/index.ts | 2 +- packages/acn/src/pdu/rlp.ts | 7 ++-- packages/acn/src/pdu/sdt.ts | 37 ++++++++++--------- packages/acn/src/{models.ts => types.ts} | 42 ++++------------------ packages/acn/src/udp.ts | 2 +- 6 files changed, 77 insertions(+), 58 deletions(-) create mode 100644 packages/acn/src/enums.ts rename packages/acn/src/{models.ts => types.ts} (83%) diff --git a/packages/acn/src/enums.ts b/packages/acn/src/enums.ts new file mode 100644 index 0000000..584f966 --- /dev/null +++ b/packages/acn/src/enums.ts @@ -0,0 +1,45 @@ +export enum Protocol { + SDT = 1, +} + +export enum SDTVector { + REL_WRAP = 1, + UNREL_WRAP, + CHANNEL_PARAMS, + JOIN, + JOIN_REFUSE, + JOIN_ACCEPT, + LEAVE, + LEAVING, + CONNECT, + CONNECT_ACCEPT, + CONNECT_REFUSE, + DISCONNECT, + DISCONNECTING, + ACK, + NAK, + GET_SESSIONS, + SESSIONS, +} + +export enum TransportLayerAddressType { + SDT_ADDR_NULL = 0, + SDT_ADDR_IPV4, + SDT_ADDR_IPV6, +} + +export enum SDTReasonCode { + NONSPECIFIC = 1, + ILLEGAL_PARAMETERS, + LOW_RESOURCES, + ALREADY_MEMBER, + BAD_ADDRESS_TYPE, + NO_RECIPROCAL_CHANNEL, + CHANNEL_EXPIRED, + LOST_SEQUENCE, + SATURATED, + TRANSPORT_ADDRESS_CHANGING, + ASKED_TO_LEAVE, + NO_RECIPIENT, + ONLY_UNICAST_SUPPORTED, +} diff --git a/packages/acn/src/index.ts b/packages/acn/src/index.ts index 6d508cd..a64b562 100644 --- a/packages/acn/src/index.ts +++ b/packages/acn/src/index.ts @@ -1,2 +1,2 @@ -export * from './models'; +export * from './types'; export * from './udp'; diff --git a/packages/acn/src/pdu/rlp.ts b/packages/acn/src/pdu/rlp.ts index 4e0b569..bf5c45e 100644 --- a/packages/acn/src/pdu/rlp.ts +++ b/packages/acn/src/pdu/rlp.ts @@ -1,5 +1,6 @@ import pdu from '.'; -import { Protocols, RootLayerPDU, SessionDataTransportPDU } from '../models'; +import { Protocol } from '../enums'; +import { RootLayerPDU, SessionDataTransportPDU } from '../types'; import { toHex } from '../utils'; function decode(bytes: Uint8Array): RootLayerPDU { @@ -52,8 +53,10 @@ function decode(bytes: Uint8Array): RootLayerPDU { const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 4 + 16); let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength); - if (vector === Protocols.SDT) { + if (vector === Protocol.SDT) { data = pdu.sdt.decode(data); + } else { + console.error(`unhandled protocol in RLP: ${vector}`) } return { diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 6b0c61d..3e6e8c4 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -1,5 +1,5 @@ +import { Protocol, SDTVector } from '../enums'; import { - Protocols, SDTAckData, SDTConnectAcceptData, SDTConnectData, @@ -14,9 +14,8 @@ import { SDTNakData, SDTWrapperData, SessionDataTransportPDU, - SessionDataTransportVectors, TransportLayerAddress, -} from '../models'; +} from '../types'; import { toHex } from '../utils'; // TODO(jwetzell): work out flag inheritance, will need previous PDU @@ -115,7 +114,7 @@ function decodeClientBlock(bytes: Uint8Array) { const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 2 + 6); let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength); - if (clientProtocol === Protocols.SDT) { + if (clientProtocol === Protocol.SDT) { data = decode(data); } else { console.error(`SDT client block contains unknown protocol: ${clientProtocol}`); @@ -129,7 +128,7 @@ function decodeClientBlock(bytes: Uint8Array) { } function decodeData( - vector: SessionDataTransportVectors, + vector: SDTVector, bytes: Uint8Array ): | SDTJoinData @@ -148,7 +147,7 @@ function decodeData( | Uint8Array { const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); switch (vector) { - case SessionDataTransportVectors.JOIN: { + case SDTVector.JOIN: { const destinationAddress: TransportLayerAddress = { type: view.getUint8(30), }; @@ -180,7 +179,7 @@ function decodeData( }; return joinData; } - case SessionDataTransportVectors.JOIN_ACCEPT: { + case SDTVector.JOIN_ACCEPT: { const joinData = { leaderComponentID: toHex(bytes.subarray(0, 16)), channelNumber: view.getUint16(16), @@ -190,7 +189,7 @@ function decodeData( }; return joinData; } - case SessionDataTransportVectors.JOIN_REFUSE: { + case SDTVector.JOIN_REFUSE: { const joinData = { leaderComponentID: toHex(bytes.subarray(0, 16)), channelNumber: view.getUint16(16), @@ -201,8 +200,8 @@ function decodeData( return joinData; } - case SessionDataTransportVectors.REL_WRAP: - case SessionDataTransportVectors.UNREL_WRAP: { + case SDTVector.REL_WRAP: + case SDTVector.UNREL_WRAP: { const wrapperData: SDTWrapperData = { channelNumber: view.getUint16(0), totalSequenceNumber: view.getUint32(2), @@ -216,12 +215,12 @@ function decodeData( return wrapperData; } - case SessionDataTransportVectors.ACK: { + case SDTVector.ACK: { return { reliableSequenceNumber: view.getUint32(0), }; } - case SessionDataTransportVectors.LEAVING: { + case SDTVector.LEAVING: { return { leaderComponentID: toHex(bytes.subarray(0, 16)), channelNumber: view.getUint16(16), @@ -230,12 +229,12 @@ function decodeData( reasonCode: view.getUint8(24), }; } - case SessionDataTransportVectors.GET_SESSIONS: { + case SDTVector.GET_SESSIONS: { return { componentID: toHex(bytes.subarray(0, 16)), }; } - case SessionDataTransportVectors.NAK: { + case SDTVector.NAK: { return { leaderComponentID: toHex(bytes.subarray(0, 16)), channelNumber: view.getUint16(16), @@ -245,20 +244,20 @@ function decodeData( lastMissedSequence: view.getUint32(28), }; } - case SessionDataTransportVectors.DISCONNECT: - case SessionDataTransportVectors.CONNECT: - case SessionDataTransportVectors.CONNECT_ACCEPT: { + case SDTVector.DISCONNECT: + case SDTVector.CONNECT: + case SDTVector.CONNECT_ACCEPT: { return { protocolID: view.getUint32(0), }; } - case SessionDataTransportVectors.CONNECT_REFUSE: { + case SDTVector.CONNECT_REFUSE: { return { protocolID: view.getUint32(0), refuseCode: view.getUint8(4), }; } - case SessionDataTransportVectors.DISCONNECTING: { + case SDTVector.DISCONNECTING: { return { protocolID: view.getUint32(0), reasonCode: view.getUint8(4), diff --git a/packages/acn/src/models.ts b/packages/acn/src/types.ts similarity index 83% rename from packages/acn/src/models.ts rename to packages/acn/src/types.ts index 3c2e858..fe4eb3f 100644 --- a/packages/acn/src/models.ts +++ b/packages/acn/src/types.ts @@ -1,6 +1,4 @@ -export enum Protocols { - SDT = 1, -} +import { TransportLayerAddressType, SDTVector, SDTReasonCode } from './enums'; export type UDPPreamble = { preambleSize: number; @@ -20,32 +18,6 @@ export type RootLayerPDU = { data: SessionDataTransportPDU | Uint8Array; }; -export enum SessionDataTransportVectors { - REL_WRAP = 1, - UNREL_WRAP, - CHANNEL_PARAMS, - JOIN, - JOIN_REFUSE, - JOIN_ACCEPT, - LEAVE, - LEAVING, - CONNECT, - CONNECT_ACCEPT, - CONNECT_REFUSE, - DISCONNECT, - DISCONNECTING, - ACK, - NAK, - GET_SESSIONS, - SESSIONS, -} - -export enum TransportLayerAddressTypes { - SDT_ADDR_NULL = 0, - SDT_ADDR_IPV4, - SDT_ADDR_IPV6, -} - export type SDTChannelParams = { expiry: number; nakOutboundFlag: number; @@ -55,7 +27,7 @@ export type SDTChannelParams = { }; export type TransportLayerAddress = { - type: TransportLayerAddressTypes; + type: TransportLayerAddressType; port?: number; address?: string; }; @@ -85,7 +57,7 @@ export type SDTJoinRefuseData = { channelNumber: number; memberID: number; reliableSequenceNumber: number; - refuseCode: number; + refuseCode: SDTReasonCode; }; export type SDTLeavingData = { @@ -93,7 +65,7 @@ export type SDTLeavingData = { channelNumber: number; memberID: number; reliableSequenceNumber: number; - reasonCode: number; + reasonCode: SDTReasonCode; }; export type SDTWrapperData = { @@ -127,13 +99,13 @@ export type SDTConnectData = { export type SDTConnectAcceptData = SDTConnectData; export type SDTConnectRefuseData = { protocolID: number; - refuseCode: number; + refuseCode: SDTReasonCode; }; export type SDTDisconnectData = SDTConnectData; export type SDTDisconnectingData = { protocolID: number; - reasonCode: number; + reasonCode: SDTReasonCode; }; export type SDTGetSessionsData = { @@ -141,7 +113,7 @@ export type SDTGetSessionsData = { }; export type SessionDataTransportPDU = { - vector: SessionDataTransportVectors; + vector: SDTVector; // TODO(jwetzell): cleanup these types data: | SDTJoinData diff --git a/packages/acn/src/udp.ts b/packages/acn/src/udp.ts index 8e1a8b5..0877fae 100644 --- a/packages/acn/src/udp.ts +++ b/packages/acn/src/udp.ts @@ -1,4 +1,4 @@ -import { ACNPacket, RootLayerPDU, UDPPreamble } from './models'; +import { ACNPacket, RootLayerPDU, UDPPreamble } from './types'; import pdu from './pdu'; // ANSI E1.17 - 2015 (R2020) EPI 17 From 1e3da4571d5f2ba3d6573960e4d44a800b049646 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 8 Dec 2024 11:03:40 -0600 Subject: [PATCH 18/20] add support for decoding all PDUs in an SDT PDU --- packages/acn/src/pdu/rlp.ts | 15 ++++++++++++--- packages/acn/src/pdu/sdt.ts | 1 + packages/acn/src/types.ts | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/acn/src/pdu/rlp.ts b/packages/acn/src/pdu/rlp.ts index bf5c45e..4c3d132 100644 --- a/packages/acn/src/pdu/rlp.ts +++ b/packages/acn/src/pdu/rlp.ts @@ -51,12 +51,21 @@ function decode(bytes: Uint8Array): RootLayerPDU { const dataOffset = headerOffset + 16; // NOTE(jwetzell): flags/lengthH + lengthL + lengthX + vector + header const dataLength = length - (1 + 1 + (lengthFlag ? 1 : 0) + 4 + 16); - let data: SessionDataTransportPDU | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength); + let data: SessionDataTransportPDU[] | Uint8Array = bytes.subarray(dataOffset, dataOffset + dataLength); if (vector === Protocol.SDT) { - data = pdu.sdt.decode(data); + const blocks: SessionDataTransportPDU[] = []; + + let blockDataOffset = 0; + // TODO(jwetzell): this could blow up so might need some attention later? + while (blockDataOffset < dataLength) { + const blockData = pdu.sdt.decode(bytes.subarray(dataOffset + blockDataOffset)); + blocks.push(blockData); + blockDataOffset += blockData.length; + } + data = blocks; } else { - console.error(`unhandled protocol in RLP: ${vector}`) + console.error(`unhandled protocol in RLP: ${vector}`); } return { diff --git a/packages/acn/src/pdu/sdt.ts b/packages/acn/src/pdu/sdt.ts index 3e6e8c4..e87cf7e 100644 --- a/packages/acn/src/pdu/sdt.ts +++ b/packages/acn/src/pdu/sdt.ts @@ -62,6 +62,7 @@ export function decode(bytes: Uint8Array): SessionDataTransportPDU { return { vector, + length, data, }; } diff --git a/packages/acn/src/types.ts b/packages/acn/src/types.ts index fe4eb3f..5398bac 100644 --- a/packages/acn/src/types.ts +++ b/packages/acn/src/types.ts @@ -15,7 +15,7 @@ export type ACNPacket = { export type RootLayerPDU = { vector: number; header: string; - data: SessionDataTransportPDU | Uint8Array; + data: SessionDataTransportPDU[] | Uint8Array; }; export type SDTChannelParams = { @@ -114,6 +114,7 @@ export type SDTGetSessionsData = { export type SessionDataTransportPDU = { vector: SDTVector; + length: number; // TODO(jwetzell): cleanup these types data: | SDTJoinData From 3909266b227d80b88a38266a6829a91104ac3015 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 8 Dec 2024 11:22:10 -0600 Subject: [PATCH 19/20] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c10d6c..4ad7a8b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ not sure how far I will take this but messing around with E1.17 ACN protocol - [x] CONNECT - [x] CONNECT_ACCEPT - [x] CONNECT_REFUSE - - [ ] DISCONNECT - - [ ] DISCONNECTING + - [x] DISCONNECT + - [x] DISCONNECTING - dmp - ddl From d12a51bb0afaff65b21ec69d29d85a7720d376f9 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sun, 8 Dec 2024 11:22:32 -0600 Subject: [PATCH 20/20] fix test with new block structure --- packages/acn/tests/udp-decode.test.js | 33 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/acn/tests/udp-decode.test.js b/packages/acn/tests/udp-decode.test.js index fe1602b..026cec7 100644 --- a/packages/acn/tests/udp-decode.test.js +++ b/packages/acn/tests/udp-decode.test.js @@ -20,10 +20,33 @@ const goodTests = [ pduBlock: { vector: 1, header: 'b0abd82a20d24240a7cd3e49d999a4e5', - data: new Uint8Array([ - 112, 50, 4, 73, 136, 131, 52, 255, 255, 74, 69, 176, 66, 102, 183, 33, 235, 216, 155, 0, 1, 244, 136, 0, 0, 0, - 0, 244, 136, 0, 0, 244, 136, 1, 67, 216, 239, 192, 220, 207, 5, 0, 0, 2, 0, 10, 0, 20, 1, 44, - ]), + data: [ + { + vector: 4, + length: 50, + data: { + componentID: '49888334ffff4a45b04266b721ebd89b', + memberID: 1, + channelNumber: 62600, + reciprocalChannel: 0, + totalSequenceNumber: 62600, + reliableSequenceNumber: 62600, + destinationAddress: { + type: 1, + port: 17368, + address: '239.192.220.207' + }, + channelParameters: { + expiry: 5, + nakOutboundFlag: 0, + nakHoldoff: 2, + nakModulus: 10, + nakMaxWait: 20 + }, + adhocExpiry: 1, + } + } + ], }, postamble: undefined, }, @@ -71,7 +94,9 @@ const badTests = [ describe('ACN Message Decoding Pass', () => { goodTests.forEach((messageTest) => { it(messageTest.description, () => { + console.log(JSON.stringify(messageTest.expected)) const decoded = acn.decode(messageTest.bytes); + console.log(JSON.stringify(decoded)) deepEqual(decoded, messageTest.expected); }); });