Skip to content

Commit

Permalink
Merge pull request #1262 from derwehr/master
Browse files Browse the repository at this point in the history
[octetsream-codec] fix serialization and deserialization of nested objects
  • Loading branch information
relu91 authored Apr 5, 2024
2 parents 8d2fae9 + 1d86750 commit 5a96838
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 15 deletions.
91 changes: 76 additions & 15 deletions packages/core/src/codecs/octetstream-codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,41 @@ export default class OctetstreamCodec implements ContentCodec {
debug("OctetstreamCodec parsing", bytes);
debug("Parameters", parameters);

const bigEndian = !(parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN) === true); // default to big endian
let signed = parameters.signed !== "false"; // default to signed
const length =
parameters.length != null
? parseInt(parameters.length)
: (warn("Missing 'length' parameter necessary for write. I'll do my best"), undefined);

if (length !== undefined) {
if (isNaN(length) || length < 0) {
throw new Error("'length' parameter must be a non-negative number");
}
if (length !== bytes.length) {
throw new Error(`Lengths do not match, required: ${length} provided: ${bytes.length}`);
}
}

let signed = true; // default to signed
if (parameters.signed !== undefined) {
if (parameters.signed !== "true" && parameters.signed !== "false") {
throw new Error("'signed' parameter must be 'true' or 'false'");
}
signed = parameters.signed === "true";
}

let bitLength = schema?.["ex:bitLength"] !== undefined ? parseInt(schema["ex:bitLength"]) : bytes.length * 8;

if (isNaN(bitLength) || bitLength < 0) {
throw new Error("'ex:bitLength' must be a non-negative number");
}

const offset = schema?.["ex:bitOffset"] !== undefined ? parseInt(schema["ex:bitOffset"]) : 0;
if (parameters.length != null && parseInt(parameters.length) !== bytes.length) {
throw new Error("Lengths do not match, required: " + parameters.length + " provided: " + bytes.length);

if (isNaN(offset) || offset < 0) {
throw new Error("'ex:bitOffset' must be a non-negative number");
}
let bitLength: number =
schema?.["ex:bitLength"] !== undefined ? parseInt(schema["ex:bitLength"]) : bytes.length * 8;

const bigEndian = !(parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN) === true); // default to big endian
let dataType: string = schema?.type;

if (!dataType) {
Expand Down Expand Up @@ -214,24 +241,47 @@ export default class OctetstreamCodec implements ContentCodec {
const sortedProperties = Object.getOwnPropertyNames(schema.properties);
for (const propertyName of sortedProperties) {
const propertySchema = schema.properties[propertyName];
result[propertyName] = this.bytesToValue(bytes, propertySchema, parameters);
const length = bytes.length.toString();
result[propertyName] = this.bytesToValue(bytes, propertySchema, { ...parameters, length });
}
return result;
}

valueToBytes(value: unknown, schema?: DataSchema, parameters: { [key: string]: string | undefined } = {}): Buffer {
debug(`OctetstreamCodec serializing '${value}'`);

if (parameters.length == null) {
warn("Missing 'length' parameter necessary for write. I'll do my best");
const bigEndian = !(parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN) === true); // default to big endian

let signed = true; // default to true

if (parameters.signed !== undefined) {
if (parameters.signed !== "true" && parameters.signed !== "false") {
throw new Error("'signed' parameter must be 'true' or 'false'");
}
signed = parameters.signed === "true";
}

let length =
parameters.length != null
? parseInt(parameters.length)
: (warn("Missing 'length' parameter necessary for write. I'll do my best"), undefined);

if (length !== undefined && (isNaN(length) || length < 0)) {
throw new Error("'length' parameter must be a non-negative number");
}

const bigEndian = !(parameters.byteSeq?.includes(Endianness.LITTLE_ENDIAN) === true); // default to big endian
let signed = parameters.signed !== "false"; // default to signed
// byte length of the buffer to be returned
let length = parameters.length != null ? parseInt(parameters.length) : undefined;
let bitLength = schema?.["ex:bitLength"] !== undefined ? parseInt(schema["ex:bitLength"]) : undefined;

if (bitLength !== undefined && (isNaN(bitLength) || bitLength < 0)) {
throw new Error("'ex:bitLength' must be a non-negative number");
}

const offset = schema?.["ex:bitOffset"] !== undefined ? parseInt(schema["ex:bitOffset"]) : 0;

if (isNaN(offset) || offset < 0) {
throw new Error("'ex:bitOffset' must be a non-negative number");
}

let dataType: string = schema?.type ?? undefined;

if (value === undefined) {
Expand Down Expand Up @@ -542,7 +592,18 @@ export default class OctetstreamCodec implements ContentCodec {
throw new Error("Missing 'length' parameter necessary for write");
}

result = result ?? Buffer.alloc(parseInt(parameters.length));
const length = parseInt(parameters.length);
const offset = schema["ex:bitOffset"] !== undefined ? parseInt(schema["ex:bitOffset"]) : 0;

if (isNaN(offset) || offset < 0) {
throw new Error("'ex:bitOffset' must be a non-negative number");
}

if (offset > length * 8) {
throw new Error(`'ex:bitOffset' ${offset} exceeds 'length' ${length}`);
}

result = result ?? Buffer.alloc(length);
for (const propertyName in schema.properties) {
if (Object.hasOwnProperty.call(value, propertyName) === false) {
throw new Error(`Missing property '${propertyName}'`);
Expand All @@ -557,7 +618,7 @@ export default class OctetstreamCodec implements ContentCodec {
} else {
buf = this.valueToBytes(propertyValue, propertySchema, parameters);
}
this.copyBits(buf, propertyOffset, result, propertyOffset, propertyLength);
this.copyBits(buf, propertyOffset, result, offset + propertyOffset, propertyLength);
}
return result;
}
Expand Down
158 changes: 158 additions & 0 deletions packages/core/test/ContentSerdesTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,59 @@ class SerdesOctetTests {
},
}
);

checkStreamToValue(
[0x0e, 0x10, 0x10, 0x10, 0x0e],
{
flags1: { flag1: false, flag2: true },
flags2: { flag1: true, flag2: false },
},
"object",
{
type: "object",
properties: {
flags1: {
type: "object",
"ex:bitOffset": 0,
"ex:bitLength": 8,
properties: {
flag1: {
type: "boolean",
title: "Bit 1",
"ex:bitOffset": 3,
"ex:bitLength": 1,
},
flag2: {
type: "boolean",
title: "Bit 2",
"ex:bitOffset": 4,
"ex:bitLength": 1,
},
},
},
flags2: {
type: "object",
"ex:bitOffset": 8,
"ex:bitLength": 8,
properties: {
flag1: {
type: "boolean",
title: "Bit 1",
"ex:bitOffset": 3,
"ex:bitLength": 1,
},
flag2: {
type: "boolean",
title: "Bit 2",
"ex:bitOffset": 4,
"ex:bitLength": 1,
},
},
},
},
},
{ length: "5" }
);
}

@test async "OctetStream to value should throw"() {
Expand Down Expand Up @@ -531,6 +584,55 @@ class SerdesOctetTests {
{ type: "uint8" }
)
).to.throw(Error, "Type is unsigned but 'signed' is true");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream;length=test`, body: Buffer.from([0x36]) },
{ type: "integer" }
)
).to.throw(Error, "'length' parameter must be a non-negative number");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream;length=-1`, body: Buffer.from([0x36]) },
{ type: "integer" }
)
).to.throw(Error, "'length' parameter must be a non-negative number");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream;signed=invalid`, body: Buffer.from([0x36]) },
{ type: "integer" }
)
).to.throw(Error, "'signed' parameter must be 'true' or 'false'");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream`, body: Buffer.from([0x36]) },
{ type: "integer", "ex:bitOffset": "invalid" }
)
).to.throw(Error, "'ex:bitOffset' must be a non-negative number");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream`, body: Buffer.from([0x36]) },
{ type: "integer", "ex:bitOffset": -1 }
)
).to.throw(Error, "'ex:bitOffset' must be a non-negative number");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream`, body: Buffer.from([0x36]) },
{ type: "integer", "ex:bitLength": "invalid" }
)
).to.throw(Error, "'ex:bitLength' must be a non-negative number");

expect(() =>
ContentSerdes.contentToValue(
{ type: `application/octet-stream`, body: Buffer.from([0x36]) },
{ type: "integer", "ex:bitLength": -1 }
)
).to.throw(Error, "'ex:bitLength' must be a non-negative number");
}

@test async "value to OctetStream"() {
Expand Down Expand Up @@ -763,6 +865,39 @@ class SerdesOctetTests {
);
body = await content.toBuffer();
expect(body).to.deep.equal(Buffer.from([0xc0]));

content = ContentSerdes.valueToContent(
{
flags1: { flag1: false, flag2: true },
flags2: { flag1: true, flag2: false },
},
{
type: "object",
properties: {
flags1: {
type: "object",
properties: {
flag1: { type: "boolean", "ex:bitOffset": 3, "ex:bitLength": 1 },
flag2: { type: "boolean", "ex:bitOffset": 4, "ex:bitLength": 1 },
},
"ex:bitLength": 8,
},
flags2: {
type: "object",
properties: {
flag1: { type: "boolean", "ex:bitOffset": 3, "ex:bitLength": 1 },
flag2: { type: "boolean", "ex:bitOffset": 4, "ex:bitLength": 1 },
},
"ex:bitOffset": 8,
"ex:bitLength": 8,
},
},
"ex:bitLength": 16,
},
"application/octet-stream;length=2;"
);
body = await content.toBuffer();
expect(body).to.deep.equal(Buffer.from([0x08, 0x10]));
}

@test "value to OctetStream should throw"() {
Expand Down Expand Up @@ -851,6 +986,29 @@ class SerdesOctetTests {
Error,
"Missing 'type' property in schema"
);
expect(() => ContentSerdes.valueToContent(10, { type: "int8" }, "application/octet-stream;signed=8")).to.throw(
Error,
"'signed' parameter must be 'true' or 'false'"
);
expect(() =>
ContentSerdes.valueToContent(10, { type: "int8" }, "application/octet-stream;length=-1;")
).to.throw(Error, "'length' parameter must be a non-negative number");
expect(() => ContentSerdes.valueToContent(10, { type: "int8" }, "application/octet-stream;length=x;")).to.throw(
Error,
"'length' parameter must be a non-negative number"
);
expect(() =>
ContentSerdes.valueToContent(10, { type: "integer", "ex:bitOffset": -16 }, "application/octet-stream")
).to.throw(Error, "'ex:bitOffset' must be a non-negative number");
expect(() =>
ContentSerdes.valueToContent(10, { type: "integer", "ex:bitOffset": "foo" }, "application/octet-stream")
).to.throw(Error, "'ex:bitOffset' must be a non-negative number");
expect(() =>
ContentSerdes.valueToContent(10, { type: "integer", "ex:bitLength": -8 }, "application/octet-stream")
).to.throw(Error, "'ex:bitLength' must be a non-negative number");
expect(() =>
ContentSerdes.valueToContent(10, { type: "integer", "ex:bitLength": "foo" }, "application/octet-stream")
).to.throw(Error, "'ex:bitLength' must be a non-negative number");
}
}

Expand Down

0 comments on commit 5a96838

Please sign in to comment.