Files
acn-js/packages/acn/src/pdu/sdt.ts
T

276 lines
7.7 KiB
TypeScript

import {
Protocols,
SDTAckData,
SDTConnectAcceptData,
SDTConnectData,
SDTConnectRefuseData,
SDTDisconnectData,
SDTDisconnectingData,
SDTGetSessionsData,
SDTJoinAcceptData,
SDTJoinData,
SDTJoinRefuseData,
SDTLeavingData,
SDTNakData,
SDTWrapperData,
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 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 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 = decodeData(vector, bytes.subarray(dataOffset, dataOffset + dataLength));
return {
vector,
data,
};
}
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
| SDTLeavingData
| SDTGetSessionsData
| SDTNakData
| SDTConnectData
| SDTConnectAcceptData
| SDTConnectRefuseData
| SDTDisconnectData
| SDTDisconnectingData
| 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),
};
}
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),
};
}
case SessionDataTransportVectors.GET_SESSIONS: {
return {
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),
};
}
case SessionDataTransportVectors.DISCONNECT:
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),
};
}
case SessionDataTransportVectors.DISCONNECTING: {
return {
protocolID: view.getUint32(0),
reasonCode: view.getUint8(4),
};
}
default:
console.error(`unhandled SDT vector: ${vector}`);
return bytes;
}
}
export default {
decode,
};