-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose read/write lengthcode for CDR2 emheaders #20
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { EncapsulationKind } from "./EncapsulationKind"; | ||
import { getEncapsulationKindInfo } from "./getEncapsulationKindInfo"; | ||
import { isBigEndian } from "./isBigEndian"; | ||
import { LengthCode, getLengthCodeForObjectSize, lengthCodeToObjectSizes } from "./lengthCodes"; | ||
import { EXTENDED_PID, SENTINEL_PID } from "./reservedPIDs"; | ||
|
||
export type CdrWriterOpts = { | ||
|
@@ -184,12 +185,32 @@ export class CdrWriter { | |
} | ||
|
||
/** | ||
* Writes the member header (EMHEADER): mustUnderstand flag, the member ID, and object size | ||
* Writes the member header (EMHEADER) | ||
* Accomodates for PL_CDR and PL_CDR2 based on the CdrWriter constructor options | ||
* | ||
* @param mustUnderstand - Whether the member is required to be understood by the receiver | ||
* @param id - The member ID | ||
* @param objectSize - The size of the member in bytes | ||
* @param lengthCode - Optional length code for CDR2 emHeaders. | ||
* lengthCode values [5-7] allow the emHeader object size to take the place of the normally encoded member length. | ||
* | ||
* NOTE: Dynamically determines default value if not provided that does not affect serialization ie will use lengthCode values [0-4]. | ||
* | ||
* From Extensible and Dynamic Topic Types in DDS-XTypes v1.3 @ `7.4.3.4.2`: | ||
* "EMHEADER1 with LC values 5 to 7 also affect the serialization/deserialization virtual machine in that they cause NEXTINT to be | ||
* reused also as part of the serialized member. This is useful because the serialization of certain members also starts with an | ||
* integer length, which would take exactly the same value as NEXTINT. Therefore the use of length codes 5 to 7 saves 4 bytes in | ||
* the serialization." | ||
* @returns - CdrWriter instance | ||
*/ | ||
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 as LengthCode) | ||
: this.memberHeaderV1(mustUnderstand, id, objectSize); | ||
} | ||
|
||
|
@@ -231,7 +252,12 @@ export class CdrWriter { | |
return this; | ||
} | ||
|
||
private memberHeaderV2(mustUnderstand: boolean, id: number, objectSize: number): CdrWriter { | ||
private memberHeaderV2( | ||
mustUnderstand: boolean, | ||
id: number, | ||
objectSize: number, | ||
lengthCode?: LengthCode, | ||
): 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 +267,49 @@ 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 = 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(objectSize >> 2); | ||
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}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we ever hit this error in the past? Or if not does that mean we are just adding support for this in the writer so that we can use it in our unit tests (we won't actually be using lengthCodes > 4 in production anywhere)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We really only use this for unit tests, so we haven't ever hit it before. I don't know about downstream users of this writer though. I'm adding this capability to the writer only for unit tests. |
||
} | ||
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); | ||
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(objectSize >> 3); | ||
break; | ||
default: | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
throw new Error(`Unexpected length code ${finalLengthCode}`); | ||
} | ||
|
||
return this; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
export type LengthCode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; | ||
|
||
export function getLengthCodeForObjectSize(objectSize: number): LengthCode { | ||
let defaultLengthCode: LengthCode | undefined; | ||
|
||
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 = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could :just: do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, but I like having the const type here to reflect the pairing in the spec |
||
0: 1, | ||
1: 2, | ||
2: 4, | ||
3: 8, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😵💫