mirror of
https://github.com/jwetzell/acn-js.git
synced 2026-08-11 17:53:40 +00:00
Merge pull request #5 from jwetzell/feat/sdt
Add support for decoding SDT protocol blocks inside RLP
This commit is contained in:
@@ -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] JOIN
|
||||
- [x] JOIN_REFUSE
|
||||
- [x] JOIN_ACCEPT
|
||||
- [x] LEAVING
|
||||
- [x] NAK
|
||||
- [x] REL_WRAP
|
||||
- [x] UNREL_WRAP
|
||||
- [x] GET_SESSIONS
|
||||
- [ ] SESSIONS
|
||||
- [x] ACK
|
||||
- [ ] CHANNEL_PARAMS
|
||||
- [ ] LEAVE
|
||||
- [x] CONNECT
|
||||
- [x] CONNECT_ACCEPT
|
||||
- [x] CONNECT_REFUSE
|
||||
- [x] DISCONNECT
|
||||
- [x] DISCONNECTING
|
||||
- dmp
|
||||
- ddl
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './models';
|
||||
export * from './types';
|
||||
export * from './udp';
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export type Protocols = {
|
||||
SDT: 1;
|
||||
};
|
||||
|
||||
export type UDPPreamble = {
|
||||
preambleSize: number;
|
||||
postambleSize: number;
|
||||
packetIdentifier: string;
|
||||
};
|
||||
|
||||
export type ACNPacket<PreambleType, PDUBlockType, PostambleType> = {
|
||||
preamble: PreambleType;
|
||||
pduBlock: PDUBlockType;
|
||||
postamble: PostambleType;
|
||||
};
|
||||
|
||||
export type RootLayerPDU = {
|
||||
vector: number;
|
||||
header: string;
|
||||
data: Uint8Array;
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
import rlp from './rlp';
|
||||
import sdt from './sdt';
|
||||
export default {
|
||||
rlp,
|
||||
sdt,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { RootLayerPDU } from '../models';
|
||||
import pdu from '.';
|
||||
import { Protocol } from '../enums';
|
||||
import { RootLayerPDU, SessionDataTransportPDU } from '../types';
|
||||
import { toHex } from '../utils';
|
||||
|
||||
function decode(bytes: Uint8Array): RootLayerPDU {
|
||||
@@ -49,7 +51,22 @@ 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 === Protocol.SDT) {
|
||||
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}`);
|
||||
}
|
||||
|
||||
return {
|
||||
vector,
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
import { Protocol, SDTVector } from '../enums';
|
||||
import {
|
||||
SDTAckData,
|
||||
SDTConnectAcceptData,
|
||||
SDTConnectData,
|
||||
SDTConnectRefuseData,
|
||||
SDTDisconnectData,
|
||||
SDTDisconnectingData,
|
||||
SDTGetSessionsData,
|
||||
SDTJoinAcceptData,
|
||||
SDTJoinData,
|
||||
SDTJoinRefuseData,
|
||||
SDTLeavingData,
|
||||
SDTNakData,
|
||||
SDTWrapperData,
|
||||
SessionDataTransportPDU,
|
||||
TransportLayerAddress,
|
||||
} from '../types';
|
||||
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,
|
||||
length,
|
||||
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 === Protocol.SDT) {
|
||||
data = decode(data);
|
||||
} else {
|
||||
console.error(`SDT client block contains unknown protocol: ${clientProtocol}`);
|
||||
}
|
||||
return {
|
||||
memberID,
|
||||
clientProtocol,
|
||||
association,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
function decodeData(
|
||||
vector: SDTVector,
|
||||
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 SDTVector.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 SDTVector.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 SDTVector.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 SDTVector.REL_WRAP:
|
||||
case SDTVector.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 SDTVector.ACK: {
|
||||
return {
|
||||
reliableSequenceNumber: view.getUint32(0),
|
||||
};
|
||||
}
|
||||
case SDTVector.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 SDTVector.GET_SESSIONS: {
|
||||
return {
|
||||
componentID: toHex(bytes.subarray(0, 16)),
|
||||
};
|
||||
}
|
||||
case SDTVector.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 SDTVector.DISCONNECT:
|
||||
case SDTVector.CONNECT:
|
||||
case SDTVector.CONNECT_ACCEPT: {
|
||||
return {
|
||||
protocolID: view.getUint32(0),
|
||||
};
|
||||
}
|
||||
case SDTVector.CONNECT_REFUSE: {
|
||||
return {
|
||||
protocolID: view.getUint32(0),
|
||||
refuseCode: view.getUint8(4),
|
||||
};
|
||||
}
|
||||
case SDTVector.DISCONNECTING: {
|
||||
return {
|
||||
protocolID: view.getUint32(0),
|
||||
reasonCode: view.getUint8(4),
|
||||
};
|
||||
}
|
||||
default:
|
||||
console.error(`unhandled SDT vector: ${vector}`);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
decode,
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
import { TransportLayerAddressType, SDTVector, SDTReasonCode } from './enums';
|
||||
|
||||
export type UDPPreamble = {
|
||||
preambleSize: number;
|
||||
postambleSize: number;
|
||||
packetIdentifier: string;
|
||||
};
|
||||
|
||||
export type ACNPacket<PreambleType, PDUBlockType, PostambleType> = {
|
||||
preamble: PreambleType;
|
||||
pduBlock: PDUBlockType;
|
||||
postamble: PostambleType;
|
||||
};
|
||||
|
||||
export type RootLayerPDU = {
|
||||
vector: number;
|
||||
header: string;
|
||||
data: SessionDataTransportPDU[] | Uint8Array;
|
||||
};
|
||||
|
||||
export type SDTChannelParams = {
|
||||
expiry: number;
|
||||
nakOutboundFlag: number;
|
||||
nakHoldoff: number;
|
||||
nakModulus: number;
|
||||
nakMaxWait: number;
|
||||
};
|
||||
|
||||
export type TransportLayerAddress = {
|
||||
type: TransportLayerAddressType;
|
||||
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 SDTJoinAcceptData = {
|
||||
leaderComponentID: string;
|
||||
channelNumber: number;
|
||||
memberID: number;
|
||||
reliableSequenceNumber: number;
|
||||
reciprocalChannel: number;
|
||||
};
|
||||
|
||||
export type SDTJoinRefuseData = {
|
||||
leaderComponentID: string;
|
||||
channelNumber: number;
|
||||
memberID: number;
|
||||
reliableSequenceNumber: number;
|
||||
refuseCode: SDTReasonCode;
|
||||
};
|
||||
|
||||
export type SDTLeavingData = {
|
||||
leaderComponentID: string;
|
||||
channelNumber: number;
|
||||
memberID: number;
|
||||
reliableSequenceNumber: number;
|
||||
reasonCode: SDTReasonCode;
|
||||
};
|
||||
|
||||
export type SDTWrapperData = {
|
||||
channelNumber: number;
|
||||
totalSequenceNumber: number;
|
||||
reliableSequenceNumber: number;
|
||||
oldestAvailableWrapper: number;
|
||||
firstMemberToAck: number;
|
||||
lastMemberToAck: number;
|
||||
makThreshold: number;
|
||||
sdtClientBlock?: SDTClientBlock;
|
||||
};
|
||||
|
||||
export type SDTAckData = {
|
||||
reliableSequenceNumber: number;
|
||||
};
|
||||
|
||||
export type SDTNakData = {
|
||||
leaderComponenetID: string;
|
||||
channelNumber: number;
|
||||
memberID: number;
|
||||
reliableSequenceNumber: number;
|
||||
firstMissedSequence: number;
|
||||
lastMissedSequence: number;
|
||||
};
|
||||
|
||||
export type SDTConnectData = {
|
||||
protocolID: number;
|
||||
};
|
||||
|
||||
export type SDTConnectAcceptData = SDTConnectData;
|
||||
export type SDTConnectRefuseData = {
|
||||
protocolID: number;
|
||||
refuseCode: SDTReasonCode;
|
||||
};
|
||||
|
||||
export type SDTDisconnectData = SDTConnectData;
|
||||
export type SDTDisconnectingData = {
|
||||
protocolID: number;
|
||||
reasonCode: SDTReasonCode;
|
||||
};
|
||||
|
||||
export type SDTGetSessionsData = {
|
||||
componentID: string;
|
||||
};
|
||||
|
||||
export type SessionDataTransportPDU = {
|
||||
vector: SDTVector;
|
||||
length: number;
|
||||
// TODO(jwetzell): cleanup these types
|
||||
data:
|
||||
| SDTJoinData
|
||||
| SDTJoinAcceptData
|
||||
| SDTJoinRefuseData
|
||||
| SDTWrapperData
|
||||
| SDTAckData
|
||||
| SDTLeavingData
|
||||
| SDTGetSessionsData
|
||||
| SDTNakData
|
||||
| SDTConnectData
|
||||
| SDTConnectAcceptData
|
||||
| SDTConnectRefuseData
|
||||
| SDTDisconnectData
|
||||
| SDTDisconnectingData
|
||||
| Uint8Array;
|
||||
};
|
||||
|
||||
export type SDTClientBlock = {
|
||||
memberID: number;
|
||||
clientProtocol: number;
|
||||
association: number;
|
||||
data: SessionDataTransportPDU | Uint8Array;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export function toHex(bytes: Uint8Array): string{
|
||||
let hex = '';
|
||||
bytes.forEach((byte)=>{
|
||||
hex += byte.toString(16).padStart(2,'0')
|
||||
})
|
||||
return hex;
|
||||
}
|
||||
export function toHex(bytes: Uint8Array): string {
|
||||
let hex = '';
|
||||
bytes.forEach((byte) => {
|
||||
hex += byte.toString(16).padStart(2, '0');
|
||||
});
|
||||
return hex;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user