diff --git a/src/CdrReader.test.ts b/src/CdrReader.test.ts index 0713777..d59dd69 100644 --- a/src/CdrReader.test.ts +++ b/src/CdrReader.test.ts @@ -277,12 +277,42 @@ Object { [true, 63, 9], [false, 127, 0xffffffff], ])( - "round trips EMHEADER values with mustUnderstand: %d, id: %d, and size: %d", + "round trips EMHEADER values with mustUnderstand: %d, id: %d, and size: %d without lengthCode", (mustUnderstand: boolean, id: number, objectSize: number) => { const writer = new CdrWriter({ kind: EncapsulationKind.PL_CDR2_LE }); writer.emHeader(mustUnderstand, id, objectSize); + const reader = new CdrReader(writer.data); + // don't want to test default assignment of length code here + const { lengthCode, ...headerNoLengthCode } = reader.emHeader(); + + expect(headerNoLengthCode).toEqual({ + objectSize, + id, + mustUnderstand, + }); + // should be defined because of CDR2 + expect(lengthCode).toBeDefined(); + }, + ); + it.each([ + [true, 100, 1, 0], // LC 0, 1 byte + [false, 200, 2, 1], // LC 1, 2 bytes + [false, 1028, 4, 2], // LC 2, 4 bytes + [false, 65, 8, 3], // LC 3, 8 bytes + [true, 63, 9, 4], // LC 4, any size + [false, 127, 0xffffffff, 5], // LC + [false, 65, 12, 6], // LC 6, multiple of 4 bytes + [false, 65, 32, 7], // LC 7, multiple of 8 bytes + [false, 127, 0xffffffff, 5], + ])( + "round trips EMHEADER values with mustUnderstand: %d, id: %d, size: %d, and lengthCode: %d", + (mustUnderstand: boolean, id: number, objectSize: number, lengthCode: number) => { + const writer = new CdrWriter({ kind: EncapsulationKind.PL_CDR2_LE }); + + writer.emHeader(mustUnderstand, id, objectSize, lengthCode); + const reader = new CdrReader(writer.data); const header = reader.emHeader(); @@ -290,6 +320,7 @@ Object { objectSize, id, mustUnderstand, + lengthCode, }); }, ); diff --git a/src/CdrReader.ts b/src/CdrReader.ts index 0c8ce48..ead00a2 100644 --- a/src/CdrReader.ts +++ b/src/CdrReader.ts @@ -1,6 +1,7 @@ import { EncapsulationKind } from "./EncapsulationKind"; import { getEncapsulationKindInfo } from "./getEncapsulationKindInfo"; import { isBigEndian } from "./isBigEndian"; +import { lengthCodeToObjectSizes } from "./lengthCodes"; import { EXTENDED_PID, SENTINEL_PID } from "./reservedPIDs"; interface Indexable { @@ -185,9 +186,10 @@ export class CdrReader { } /** - * Reads the member header (EMHEADER) and returns the member ID, mustUnderstand flag, and object size + * Reads the member header (EMHEADER) and returns the member ID, mustUnderstand flag, and object size with optional length code + * The length code is only present in CDR2 and should prompt objectSize to be used in place of sequence length if applicable. */ - emHeader(): { mustUnderstand: boolean; id: number; objectSize: number } { + emHeader(): { mustUnderstand: boolean; id: number; objectSize: number; lengthCode?: number } { if (this.isCDR2) { return this.memberHeaderV2(); } else { @@ -196,7 +198,11 @@ export class CdrReader { } /** XCDR1 PL_CDR encapsulation parameter header*/ - private memberHeaderV1(): { id: number; objectSize: number; mustUnderstand: boolean } { + private memberHeaderV1(): { + id: number; + objectSize: number; + mustUnderstand: boolean; + } { // 4-byte header with two 16-bit fields this.align(4); const idHeader = this.uint16(); @@ -260,7 +266,12 @@ export class CdrReader { } } - private memberHeaderV2(): { id: number; objectSize: number; mustUnderstand: boolean } { + private memberHeaderV2(): { + id: number; + objectSize: number; + mustUnderstand: boolean; + lengthCode: number; + } { const header = this.uint32(); // EMHEADER = (M_FLAG<<31) + (LC<<28) + M.id // M is the member of a structure @@ -272,7 +283,7 @@ export class CdrReader { const objectSize = this.emHeaderObjectSize(lengthCode); - return { mustUnderstand, id, objectSize }; + return { mustUnderstand, id, objectSize, lengthCode }; } /** Uses the length code to derive the member object size in @@ -282,13 +293,10 @@ export class CdrReader { // 7.4.3.4.2 Member Header (EMHEADER), Length Code (LC) and NEXTINT switch (lengthCode) { case 0: - return 1; case 1: - return 2; case 2: - return 4; case 3: - return 8; + return lengthCodeToObjectSizes[lengthCode]; // LC > 3 -> NEXTINT exists after header case 4: case 5: diff --git a/src/CdrWriter.ts b/src/CdrWriter.ts index f8c2303..0b1ba5d 100644 --- a/src/CdrWriter.ts +++ b/src/CdrWriter.ts @@ -1,6 +1,7 @@ import { EncapsulationKind } from "./EncapsulationKind"; import { getEncapsulationKindInfo } from "./getEncapsulationKindInfo"; import { isBigEndian } from "./isBigEndian"; +import { getLengthCodeForObjectSize, lengthCodeToObjectSizes } from "./lengthCodes"; import { EXTENDED_PID, SENTINEL_PID } from "./reservedPIDs"; export type CdrWriterOpts = { @@ -184,12 +185,17 @@ export class CdrWriter { } /** - * Writes the member header (EMHEADER): mustUnderstand flag, the member ID, and object size + * Writes the member header (EMHEADER): mustUnderstand flag, the member ID, object size, and optional length code for CDR2 emHeaders * Accomodates for PL_CDR and PL_CDR2 based on the CdrWriter constructor options */ - emHeader(mustUnderstand: boolean, id: number, objectSize: number): CdrWriter { + emHeader( + mustUnderstand: boolean, + id: number, + objectSize: number, + lengthCode?: number, + ): CdrWriter { return this.isCDR2 - ? this.memberHeaderV2(mustUnderstand, id, objectSize) + ? this.memberHeaderV2(mustUnderstand, id, objectSize, lengthCode) : this.memberHeaderV1(mustUnderstand, id, objectSize); } @@ -231,7 +237,12 @@ export class CdrWriter { return this; } - private memberHeaderV2(mustUnderstand: boolean, id: number, objectSize: number): CdrWriter { + private memberHeaderV2( + mustUnderstand: boolean, + id: number, + objectSize: number, + lengthCode?: number, + ): CdrWriter { if (id > 0x0fffffff) { // first byte is used for M_FLAG and LC throw Error(`Member ID ${id} is too large. Max value is ${0x0fffffff}`); @@ -241,37 +252,46 @@ export class CdrWriter { // M_FLAG is the value of the Must Understand option for the member const mustUnderstandFlag = mustUnderstand ? 1 << 31 : 0; // LC is the value of the Length Code for the member. - let lengthCode: number | undefined; - switch (objectSize) { + const finalLengthCode = lengthCode ?? getLengthCodeForObjectSize(objectSize); + + const header = mustUnderstandFlag | (finalLengthCode << 28) | id; + + this.uint32(header); + + switch (finalLengthCode) { + case 0: case 1: - lengthCode = 0; - break; case 2: - lengthCode = 1; + case 3: { + const shouldBeSize = lengthCodeToObjectSizes[finalLengthCode]; + if (objectSize !== shouldBeSize) { + throw new Error( + `Cannot write a length code ${finalLengthCode} header with an object size not equal to ${shouldBeSize}`, + ); + } break; + } + // When the length code is > 3 the header is 8 bytes because of the NEXTINT value storing the object size case 4: - lengthCode = 2; + case 5: + this.uint32(objectSize); break; - case 8: - lengthCode = 3; + case 6: + if (objectSize % 4 !== 0) { + throw new Error( + "Cannot write a length code 6 header with an object size that is not a multiple of 4", + ); + } + this.uint32(Math.floor(objectSize / 4)); + break; + case 7: + if (objectSize % 8 !== 0) { + throw new Error( + "Cannot write a length code 7 header with an object size that is not a multiple of 8", + ); + } + this.uint32(Math.floor(objectSize / 8)); break; - } - - if (lengthCode == undefined) { - // Not currently supporting writing of lengthCodes > 4 - if (objectSize > 0xffffffff) { - throw Error(`Object size ${objectSize} for EMHEADER too large. Max size is ${0xfffffffff}`); - } - lengthCode = 4; - } - - const header = mustUnderstandFlag | (lengthCode << 28) | id; - - this.uint32(header); - - // When the length code is > 3 the header is 8 bytes because of the NEXTINT value storing the object size - if (lengthCode >= 4) { - this.uint32(objectSize); } return this; diff --git a/src/lengthCodes.ts b/src/lengthCodes.ts new file mode 100644 index 0000000..5902d72 --- /dev/null +++ b/src/lengthCodes.ts @@ -0,0 +1,36 @@ +export function getLengthCodeForObjectSize(objectSize: number): number { + let defaultLengthCode; + + switch (objectSize) { + case 1: + defaultLengthCode = 0; + break; + case 2: + defaultLengthCode = 1; + break; + case 4: + defaultLengthCode = 2; + break; + case 8: + defaultLengthCode = 3; + break; + } + + if (defaultLengthCode == undefined) { + // Not currently supporting writing of lengthCodes > 4 + if (objectSize > 0xffffffff) { + throw Error( + `Object size ${objectSize} for EMHEADER too large without specifying length code. Max size is ${0xffffffff}`, + ); + } + defaultLengthCode = 4; + } + return defaultLengthCode; +} + +export const lengthCodeToObjectSizes = { + 0: 1, + 1: 2, + 2: 4, + 3: 8, +};