Skip to content

Commit

Permalink
fix/custom metadata header alloc (#231)
Browse files Browse the repository at this point in the history
* fix: clear buffer when allocating for custom metadata header

* test: add composite metadata tests

- encodeAndAddCustomMetadata
- encodeCustomMetadataHeader

Signed-off-by: Kevin Viglucci <[email protected]>
  • Loading branch information
viglucci committed Apr 27, 2023
1 parent 380237c commit 9a81261
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { encodeAndAddCustomMetadata } from "rsocket-composite-metadata";
import { hex } from "./test-utils/hex";

describe("encodeAndAddCustomMetadata", () => {
it("throws if custom mimtype length is less than 1", () => {
expect(() =>
encodeAndAddCustomMetadata(Buffer.from([]), "", Buffer.from("1234"))
).toThrow(
"Custom mime type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"
);
});

it("throws if custom mimtype length is greater than 127", () => {
let mime = "";
while (mime.length < 130) {
mime += "a";
}
expect(() =>
encodeAndAddCustomMetadata(Buffer.from([]), mime, Buffer.from("1234"))
).toThrow(
"Custom mime type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"
);
});

it("encodes the header and payload as per spec", () => {
const { c, u, s, t, o, m } = hex;
const metadata = encodeAndAddCustomMetadata(
Buffer.from([]),
"custom",
Buffer.from("1234")
);
const expectedHeaderLength8 = "05";
const expectedPayloadLength24 = "000004";
const expectedHeader = `${expectedHeaderLength8}${c}${u}${s}${t}${o}${m}${expectedPayloadLength24}`;
const expectedPayload = `${hex["1"]}${hex["2"]}${hex["3"]}${hex["4"]}`;
expect(metadata.toString("hex")).toBe(
`${expectedHeader}${expectedPayload}`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { encodeCustomMetadataHeader } from "rsocket-composite-metadata";
import { hex } from "./test-utils/hex";

describe("encodeCustomMetadataHeader", () => {
it("throws if length is less than 1", () => {
expect(() => encodeCustomMetadataHeader("", 0)).toThrow(
"Custom mime type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"
);
});

it("throws if length is greater than 127", () => {
let mime = "";
while (mime.length < 130) {
mime += "a";
}
expect(() => encodeCustomMetadataHeader(mime, mime.length)).toThrow(
"Custom mime type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"
);
});

it("encodes the header as per spec", () => {
const { t, e, s } = hex;
const mime = "test";
// length minus 1 (uint8)
const expectedLength8 = "03";
// full length (uint24)
const expectedLength24 = "000004";
const header = encodeCustomMetadataHeader(mime, mime.length);
expect(header.toString("hex")).toBe(
`${expectedLength8}${t}${e}${s}${t}${expectedLength24}`
);
});
});
24 changes: 24 additions & 0 deletions packages/rsocket-composite-metadata/__tests__/test-utils/hex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function numHex(s) {
let a = s.toString(16);
if (a.length % 2 > 0) {
a = "0" + a;
}
return a;
}

function strHex(s) {
let a = "";
for (let i = 0; i < s.length; i++) {
a = a + numHex(s.charCodeAt(i));
}

return a;
}

const alphabetNumeric = "abcdefghijklmnopqrstuvqxyz0123456789";

export const hex: any = {};

alphabetNumeric.split("").forEach((c) => {
hex[c] = strHex(c);
});
17 changes: 17 additions & 0 deletions packages/rsocket-composite-metadata/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Config } from "@jest/types";
import { pathsToModuleNameMapper } from "ts-jest/utils";
import { compilerOptions } from "../../tsconfig.json";

const config: Config.InitialOptions = {
preset: "ts-jest",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
// This has to match the baseUrl defined in tsconfig.json.
prefix: "<rootDir>/../../",
}),
modulePathIgnorePatterns: ["<rootDir>/__tests__/test-utils"],
collectCoverage: true,
collectCoverageFrom: ["<rootDir>/src/**/*.ts", "!**/node_modules/**"],
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};

export default config;
1 change: 1 addition & 0 deletions packages/rsocket-composite-metadata/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
expect.extend({});
2 changes: 1 addition & 1 deletion packages/rsocket-composite-metadata/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"clean": "rimraf -rf ./dist",
"compile": "tsc -p tsconfig.build.json",
"prepublishOnly": "yarn run build",
"test": "echo \"Error: no test specified\" && exit 0"
"test": "yarn jest"
},
"dependencies": {
"rsocket-core": "^1.0.0-alpha.1"
Expand Down
10 changes: 5 additions & 5 deletions packages/rsocket-composite-metadata/src/CompositeMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,14 @@ export function encodeCustomMetadataHeader(
customMime: string,
metadataLength: number
): Buffer {
// allocate one byte + the length of the mimetype
const metadataHeader: Buffer = Buffer.allocUnsafe(4 + customMime.length);
// reserve 1 byte for the customMime length
// /!\ careful not to read that first byte, which is random at this point
// int writerIndexInitial = metadataHeader.writerIndex();
// metadataHeader.writerIndex(writerIndexInitial + 1);

// fill the buffer to clear previous memory
metadataHeader.fill(0);

// write the custom mime in UTF8 but validate it is all ASCII-compatible
// (which produces the right result since ASCII chars are still encoded on 1 byte in UTF8)
// (which produces the correct result since ASCII chars are still encoded on 1 byte in UTF8)
const customMimeLength: number = metadataHeader.write(customMime, 1);
if (!isAscii(metadataHeader, 1)) {
throw new Error("Custom mime type must be US_ASCII characters only");
Expand Down

0 comments on commit 9a81261

Please sign in to comment.