Skip to content
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

feat!: add meta field on Waku Message and provide setter and validation interfaces #1230

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/core/src/lib/message/topic_only_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class TopicOnlyMessage implements IDecodedMessage {
public payload: Uint8Array = new Uint8Array();
public rateLimitProof: undefined;
public timestamp: undefined;
public meta: undefined;
public ephemeral: undefined;

constructor(
Expand All @@ -22,6 +23,10 @@ export class TopicOnlyMessage implements IDecodedMessage {
get contentTopic(): string {
return this.proto.contentTopic;
}

isMetaValid(): boolean {
return true;
}
}

export class TopicOnlyDecoder implements IDecoder<TopicOnlyMessage> {
Expand All @@ -35,6 +40,7 @@ export class TopicOnlyDecoder implements IDecoder<TopicOnlyMessage> {
payload: new Uint8Array(),
rateLimitProof: undefined,
timestamp: undefined,
meta: undefined,
version: undefined,
ephemeral: undefined,
});
Expand Down
229 changes: 192 additions & 37 deletions packages/core/src/lib/message/version_0.spec.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,208 @@
import type { IProtoMessage } from "@waku/interfaces";
import { expect } from "chai";
import fc from "fast-check";

import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js";

const TestContentTopic = "/test/1/waku-message/utf8";
const TestPubSubTopic = "/test/pubsub/topic";

describe("Waku Message version 0", function () {
it("Round trip binary serialization", async function () {
await fc.assert(
fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => {
const encoder = createEncoder({
contentTopic: TestContentTopic,
});
const bytes = await encoder.toWire({ payload });
const decoder = createDecoder(TestContentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
TestPubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.contentTopic).to.eq(TestContentTopic);
expect(result.pubSubTopic).to.eq(TestPubSubTopic);
expect(result.version).to.eq(0);
expect(result.ephemeral).to.be.false;
expect(result.payload).to.deep.eq(payload);
expect(result.timestamp).to.not.be.undefined;
})
fc.asyncProperty(
fc.string(),
fc.string(),
fc.uint8Array({ minLength: 1 }),
async (contentTopic, pubSubTopic, payload) => {
const encoder = createEncoder({
contentTopic,
});
const bytes = await encoder.toWire({ payload });
const decoder = createDecoder(contentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
pubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.contentTopic).to.eq(contentTopic);
expect(result.pubSubTopic).to.eq(pubSubTopic);
expect(result.version).to.eq(0);
expect(result.ephemeral).to.be.false;
expect(result.payload).to.deep.eq(payload);
expect(result.timestamp).to.not.be.undefined;
}
)
);
});

it("Ephemeral field set to true", async function () {
await fc.assert(
fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => {
const encoder = createEncoder({
contentTopic: TestContentTopic,
ephemeral: true,
});
const bytes = await encoder.toWire({ payload });
const decoder = createDecoder(TestContentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
TestPubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.ephemeral).to.be.true;
})
fc.asyncProperty(
fc.string(),
fc.string(),
fc.uint8Array({ minLength: 1 }),
async (contentTopic, pubSubTopic, payload) => {
const encoder = createEncoder({
contentTopic,
ephemeral: true,
});
const bytes = await encoder.toWire({ payload });
const decoder = createDecoder(contentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
pubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.ephemeral).to.be.true;
}
)
);
});

it("Meta field set when metaSetter is specified", async function () {
await fc.assert(
fc.asyncProperty(
fc.string(),
fc.string(),
fc.uint8Array({ minLength: 1 }),
async (contentTopic, pubSubTopic, payload) => {
// Encode the length of the payload
// Not a relevant real life example
const metaSetter = (
msg: IProtoMessage & { meta: undefined }
): Uint8Array => {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, msg.payload.length, false);
return new Uint8Array(buffer);
};

const encoder = createEncoder({
contentTopic,
ephemeral: true,
metaSetter,
});
const bytes = await encoder.toWire({ payload });
const decoder = createDecoder(contentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
pubSubTopic,
protoResult!
)) as DecodedMessage;

const expectedMeta = metaSetter({
payload,
timestamp: undefined,
contentTopic: "",
ephemeral: undefined,
meta: undefined,
rateLimitProof: undefined,
version: undefined,
});

expect(result.meta).to.deep.eq(expectedMeta);
}
)
);
});

it("isMetaValid returns true when no validator specified", async function () {
await fc.assert(
fc.asyncProperty(
fc.string(),
fc.string(),
fc.uint8Array({ minLength: 1 }),
async (pubSubTopic, contentTopic, payload) => {
const encoder = createEncoder({
contentTopic,
});
const bytes = await encoder.toWire({ payload });
const decoder = createDecoder(contentTopic);
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
pubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.isMetaValid()).to.be.true;
}
)
);
});

it("isMetaValid returns false when validator specified returns false", async function () {
await fc.assert(
fc.asyncProperty(
fc.string(),
fc.string(),
fc.uint8Array({ minLength: 1 }),
async (pubSubTopic, contentTopic, payload) => {
const encoder = createEncoder({
contentTopic,
});
const decoder = createDecoder(contentTopic, () => false);

const bytes = await encoder.toWire({ payload });
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
pubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.isMetaValid()).to.be.false;
}
)
);
});

it("isMetaValid returns true when matching meta setter", async function () {
await fc.assert(
fc.asyncProperty(
fc.string(),
fc.string(),
fc.uint8Array({ minLength: 1 }),
async (pubSubTopic, contentTopic, payload) => {
const metaSetter = (
msg: IProtoMessage & { meta: undefined }
): Uint8Array => {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setUint32(0, msg.payload.length);
return new Uint8Array(buffer);
};

const encoder = createEncoder({
contentTopic,
metaSetter,
});

const metaValidator = (
_pubSubTopic: string,
message: IProtoMessage
): boolean => {
if (!message.meta) return false;

const view = new DataView(
message.meta.buffer,
message.meta.byteOffset,
4
);
const metaInt = view.getUint32(0);

return metaInt === message.payload.length;
};
const decoder = createDecoder(contentTopic, metaValidator);

const bytes = await encoder.toWire({ payload });
const protoResult = await decoder.fromWireToProtoObj(bytes);
const result = (await decoder.fromProtoObj(
pubSubTopic,
protoResult!
)) as DecodedMessage;

expect(result.isMetaValid()).to.be.true;
}
)
);
});
});
Loading