mirror of https://github.com/waku-org/js-waku.git
feat!: enable encoding of `meta` field
This commit is contained in:
parent
497588bc36
commit
bd983ea48e
|
@ -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(
|
||||
|
@ -35,6 +36,7 @@ export class TopicOnlyDecoder implements IDecoder<TopicOnlyMessage> {
|
|||
payload: new Uint8Array(),
|
||||
rateLimitProof: undefined,
|
||||
timestamp: undefined,
|
||||
meta: undefined,
|
||||
version: undefined,
|
||||
ephemeral: undefined,
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { IProtoMessage } from "@waku/interfaces";
|
||||
import { expect } from "chai";
|
||||
import fc from "fast-check";
|
||||
|
||||
|
@ -57,4 +58,51 @@ describe("Waku Message version 0", function () {
|
|||
)
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { IMetaSetter } from "@waku/interfaces";
|
||||
import type {
|
||||
EncoderOptions,
|
||||
IDecodedMessage,
|
||||
|
@ -50,6 +51,10 @@ export class DecodedMessage implements IDecodedMessage {
|
|||
}
|
||||
}
|
||||
|
||||
get meta(): Uint8Array | undefined {
|
||||
return this.proto.meta;
|
||||
}
|
||||
|
||||
get version(): number {
|
||||
// https://rfc.vac.dev/spec/14/
|
||||
// > If omitted, the value SHOULD be interpreted as version 0.
|
||||
|
@ -62,7 +67,11 @@ export class DecodedMessage implements IDecodedMessage {
|
|||
}
|
||||
|
||||
export class Encoder implements IEncoder {
|
||||
constructor(public contentTopic: string, public ephemeral: boolean = false) {}
|
||||
constructor(
|
||||
public contentTopic: string,
|
||||
public ephemeral: boolean = false,
|
||||
public metaSetter?: IMetaSetter
|
||||
) {}
|
||||
|
||||
async toWire(message: IMessage): Promise<Uint8Array> {
|
||||
return proto.WakuMessage.encode(await this.toProtoObj(message));
|
||||
|
@ -71,14 +80,22 @@ export class Encoder implements IEncoder {
|
|||
async toProtoObj(message: IMessage): Promise<IProtoMessage> {
|
||||
const timestamp = message.timestamp ?? new Date();
|
||||
|
||||
return {
|
||||
const protoMessage = {
|
||||
payload: message.payload,
|
||||
version: Version,
|
||||
contentTopic: this.contentTopic,
|
||||
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
|
||||
meta: undefined,
|
||||
rateLimitProof: message.rateLimitProof,
|
||||
ephemeral: this.ephemeral,
|
||||
};
|
||||
|
||||
if (this.metaSetter) {
|
||||
const meta = this.metaSetter(protoMessage);
|
||||
return { ...protoMessage, meta };
|
||||
}
|
||||
|
||||
return protoMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,8 +111,9 @@ export class Encoder implements IEncoder {
|
|||
export function createEncoder({
|
||||
contentTopic,
|
||||
ephemeral,
|
||||
metaSetter,
|
||||
}: EncoderOptions): Encoder {
|
||||
return new Encoder(contentTopic, ephemeral);
|
||||
return new Encoder(contentTopic, ephemeral, metaSetter);
|
||||
}
|
||||
|
||||
export class Decoder implements IDecoder<DecodedMessage> {
|
||||
|
@ -109,6 +127,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|||
contentTopic: protoMessage.contentTopic,
|
||||
version: protoMessage.version ?? undefined,
|
||||
timestamp: protoMessage.timestamp ?? undefined,
|
||||
meta: protoMessage.meta ?? undefined,
|
||||
rateLimitProof: protoMessage.rateLimitProof ?? undefined,
|
||||
ephemeral: protoMessage.ephemeral ?? false,
|
||||
});
|
||||
|
@ -135,7 +154,7 @@ export class Decoder implements IDecoder<DecodedMessage> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an decoder that decode messages without Waku level encryption.
|
||||
* Creates a decoder that decode messages without Waku level encryption.
|
||||
*
|
||||
* A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/)
|
||||
* format when received from the Waku network. The resulting decoder can then be
|
||||
|
|
|
@ -6,6 +6,7 @@ const EmptyMessage: IProtoMessage = {
|
|||
contentTopic: "",
|
||||
version: undefined,
|
||||
timestamp: undefined,
|
||||
meta: undefined,
|
||||
rateLimitProof: undefined,
|
||||
ephemeral: undefined,
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ export interface IProtoMessage {
|
|||
contentTopic: string;
|
||||
version: number | undefined;
|
||||
timestamp: bigint | undefined;
|
||||
meta: Uint8Array | undefined;
|
||||
rateLimitProof: IRateLimitProof | undefined;
|
||||
ephemeral: boolean | undefined;
|
||||
}
|
||||
|
@ -30,6 +31,10 @@ export interface IMessage {
|
|||
rateLimitProof?: IRateLimitProof;
|
||||
}
|
||||
|
||||
export interface IMetaSetter {
|
||||
(message: IProtoMessage & { meta: undefined }): Uint8Array;
|
||||
}
|
||||
|
||||
export interface EncoderOptions {
|
||||
/** The content topic to set on outgoing messages. */
|
||||
contentTopic: string;
|
||||
|
@ -38,6 +43,12 @@ export interface EncoderOptions {
|
|||
* @defaultValue `false`
|
||||
*/
|
||||
ephemeral?: boolean;
|
||||
/**
|
||||
* A function called when encoding messages to set the meta field.
|
||||
* @param IProtoMessage The message encoded for wire, without the meta field.
|
||||
* If encryption is used, `metaSetter` only accesses _encrypted_ payload.
|
||||
*/
|
||||
metaSetter?: IMetaSetter;
|
||||
}
|
||||
|
||||
export interface IEncoder {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { IProtoMessage } from "@waku/interfaces";
|
||||
import { expect } from "chai";
|
||||
import fc from "fast-check";
|
||||
|
||||
|
@ -81,4 +82,51 @@ describe("Ecies Encryption", function () {
|
|||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("Check meta is set [ecies]", async function () {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
fc.string(),
|
||||
fc.string(),
|
||||
fc.uint8Array({ minLength: 1 }),
|
||||
fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
|
||||
async (pubSubTopic, contentTopic, payload, privateKey) => {
|
||||
const publicKey = getPublicKey(privateKey);
|
||||
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,
|
||||
publicKey,
|
||||
metaSetter,
|
||||
});
|
||||
const bytes = await encoder.toWire({ payload });
|
||||
|
||||
const decoder = createDecoder(contentTopic, privateKey);
|
||||
const protoResult = await decoder.fromWireToProtoObj(bytes!);
|
||||
if (!protoResult) throw "Failed to proto decode";
|
||||
const result = await decoder.fromProtoObj(pubSubTopic, protoResult);
|
||||
if (!result) throw "Failed to decode";
|
||||
|
||||
const expectedMeta = metaSetter({
|
||||
payload: protoResult.payload,
|
||||
timestamp: undefined,
|
||||
contentTopic: "",
|
||||
ephemeral: undefined,
|
||||
meta: undefined,
|
||||
rateLimitProof: undefined,
|
||||
version: undefined,
|
||||
});
|
||||
|
||||
expect(result.meta).to.deep.equal(expectedMeta);
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Decoder as DecoderV0 } from "@waku/core/lib/message/version_0";
|
||||
import { IMetaSetter } from "@waku/interfaces";
|
||||
import type {
|
||||
EncoderOptions as BaseEncoderOptions,
|
||||
IDecoder,
|
||||
|
@ -34,7 +35,8 @@ class Encoder implements IEncoder {
|
|||
public contentTopic: string,
|
||||
private publicKey: Uint8Array,
|
||||
private sigPrivKey?: Uint8Array,
|
||||
public ephemeral: boolean = false
|
||||
public ephemeral: boolean = false,
|
||||
public metaSetter?: IMetaSetter
|
||||
) {}
|
||||
|
||||
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
|
||||
|
@ -50,14 +52,22 @@ class Encoder implements IEncoder {
|
|||
|
||||
const payload = await encryptAsymmetric(preparedPayload, this.publicKey);
|
||||
|
||||
return {
|
||||
const protoMessage = {
|
||||
payload,
|
||||
version: Version,
|
||||
contentTopic: this.contentTopic,
|
||||
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
|
||||
meta: undefined,
|
||||
rateLimitProof: message.rateLimitProof,
|
||||
ephemeral: this.ephemeral,
|
||||
};
|
||||
|
||||
if (this.metaSetter) {
|
||||
const meta = this.metaSetter(protoMessage);
|
||||
return { ...protoMessage, meta };
|
||||
}
|
||||
|
||||
return protoMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,8 +95,15 @@ export function createEncoder({
|
|||
publicKey,
|
||||
sigPrivKey,
|
||||
ephemeral = false,
|
||||
metaSetter,
|
||||
}: EncoderOptions): Encoder {
|
||||
return new Encoder(contentTopic, publicKey, sigPrivKey, ephemeral);
|
||||
return new Encoder(
|
||||
contentTopic,
|
||||
publicKey,
|
||||
sigPrivKey,
|
||||
ephemeral,
|
||||
metaSetter
|
||||
);
|
||||
}
|
||||
|
||||
class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { IProtoMessage } from "@waku/interfaces";
|
||||
import { expect } from "chai";
|
||||
import fc from "fast-check";
|
||||
|
||||
|
@ -70,4 +71,50 @@ describe("Symmetric Encryption", function () {
|
|||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("Check meta is set [symmetric]", async function () {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
fc.string(),
|
||||
fc.string(),
|
||||
fc.uint8Array({ minLength: 1 }),
|
||||
fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
|
||||
async (pubSubTopic, contentTopic, payload, symKey) => {
|
||||
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,
|
||||
symKey,
|
||||
metaSetter,
|
||||
});
|
||||
const bytes = await encoder.toWire({ payload });
|
||||
|
||||
const decoder = createDecoder(contentTopic, symKey);
|
||||
const protoResult = await decoder.fromWireToProtoObj(bytes!);
|
||||
if (!protoResult) throw "Failed to proto decode";
|
||||
const result = await decoder.fromProtoObj(pubSubTopic, protoResult);
|
||||
if (!result) throw "Failed to decode";
|
||||
|
||||
const expectedMeta = metaSetter({
|
||||
payload: protoResult.payload,
|
||||
timestamp: undefined,
|
||||
contentTopic: "",
|
||||
ephemeral: undefined,
|
||||
meta: undefined,
|
||||
rateLimitProof: undefined,
|
||||
version: undefined,
|
||||
});
|
||||
|
||||
expect(result.meta).to.deep.equal(expectedMeta);
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import type {
|
|||
IDecoder,
|
||||
IEncoder,
|
||||
IMessage,
|
||||
IMetaSetter,
|
||||
IProtoMessage,
|
||||
} from "@waku/interfaces";
|
||||
import { WakuMessage } from "@waku/proto";
|
||||
|
@ -29,7 +30,8 @@ class Encoder implements IEncoder {
|
|||
public contentTopic: string,
|
||||
private symKey: Uint8Array,
|
||||
private sigPrivKey?: Uint8Array,
|
||||
public ephemeral: boolean = false
|
||||
public ephemeral: boolean = false,
|
||||
public metaSetter?: IMetaSetter
|
||||
) {}
|
||||
|
||||
async toWire(message: IMessage): Promise<Uint8Array | undefined> {
|
||||
|
@ -44,14 +46,23 @@ class Encoder implements IEncoder {
|
|||
const preparedPayload = await preCipher(message.payload, this.sigPrivKey);
|
||||
|
||||
const payload = await encryptSymmetric(preparedPayload, this.symKey);
|
||||
return {
|
||||
|
||||
const protoMessage = {
|
||||
payload,
|
||||
version: Version,
|
||||
contentTopic: this.contentTopic,
|
||||
timestamp: BigInt(timestamp.valueOf()) * OneMillion,
|
||||
meta: undefined,
|
||||
rateLimitProof: message.rateLimitProof,
|
||||
ephemeral: this.ephemeral,
|
||||
};
|
||||
|
||||
if (this.metaSetter) {
|
||||
const meta = this.metaSetter(protoMessage);
|
||||
return { ...protoMessage, meta };
|
||||
}
|
||||
|
||||
return protoMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,8 +91,9 @@ export function createEncoder({
|
|||
symKey,
|
||||
sigPrivKey,
|
||||
ephemeral = false,
|
||||
metaSetter,
|
||||
}: EncoderOptions): Encoder {
|
||||
return new Encoder(contentTopic, symKey, sigPrivKey, ephemeral);
|
||||
return new Encoder(contentTopic, symKey, sigPrivKey, ephemeral, metaSetter);
|
||||
}
|
||||
|
||||
class Decoder extends DecoderV0 implements IDecoder<DecodedMessage> {
|
||||
|
|
|
@ -430,6 +430,7 @@ export interface WakuMessage {
|
|||
contentTopic: string;
|
||||
version?: number;
|
||||
timestamp?: bigint;
|
||||
meta?: Uint8Array;
|
||||
rateLimitProof?: RateLimitProof;
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
|
@ -465,6 +466,11 @@ export namespace WakuMessage {
|
|||
w.sint64(obj.timestamp);
|
||||
}
|
||||
|
||||
if (obj.meta != null) {
|
||||
w.uint32(90);
|
||||
w.bytes(obj.meta);
|
||||
}
|
||||
|
||||
if (obj.rateLimitProof != null) {
|
||||
w.uint32(170);
|
||||
RateLimitProof.codec().encode(obj.rateLimitProof, w);
|
||||
|
@ -503,6 +509,9 @@ export namespace WakuMessage {
|
|||
case 10:
|
||||
obj.timestamp = reader.sint64();
|
||||
break;
|
||||
case 11:
|
||||
obj.meta = reader.bytes();
|
||||
break;
|
||||
case 21:
|
||||
obj.rateLimitProof = RateLimitProof.codec().decode(
|
||||
reader,
|
||||
|
|
|
@ -362,6 +362,7 @@ export interface WakuMessage {
|
|||
contentTopic: string;
|
||||
version?: number;
|
||||
timestamp?: bigint;
|
||||
meta?: Uint8Array;
|
||||
rateLimitProof?: RateLimitProof;
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
|
@ -397,6 +398,11 @@ export namespace WakuMessage {
|
|||
w.sint64(obj.timestamp);
|
||||
}
|
||||
|
||||
if (obj.meta != null) {
|
||||
w.uint32(90);
|
||||
w.bytes(obj.meta);
|
||||
}
|
||||
|
||||
if (obj.rateLimitProof != null) {
|
||||
w.uint32(170);
|
||||
RateLimitProof.codec().encode(obj.rateLimitProof, w);
|
||||
|
@ -435,6 +441,9 @@ export namespace WakuMessage {
|
|||
case 10:
|
||||
obj.timestamp = reader.sint64();
|
||||
break;
|
||||
case 11:
|
||||
obj.meta = reader.bytes();
|
||||
break;
|
||||
case 21:
|
||||
obj.rateLimitProof = RateLimitProof.codec().decode(
|
||||
reader,
|
||||
|
|
|
@ -17,6 +17,7 @@ message WakuMessage {
|
|||
string content_topic = 2;
|
||||
optional uint32 version = 3;
|
||||
optional sint64 timestamp = 10;
|
||||
optional bytes meta = 11;
|
||||
optional RateLimitProof rate_limit_proof = 21;
|
||||
optional bool ephemeral = 31;
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@ export interface WakuMessage {
|
|||
contentTopic: string;
|
||||
version?: number;
|
||||
timestamp?: bigint;
|
||||
meta?: Uint8Array;
|
||||
rateLimitProof?: RateLimitProof;
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
|
@ -169,6 +170,11 @@ export namespace WakuMessage {
|
|||
w.sint64(obj.timestamp);
|
||||
}
|
||||
|
||||
if (obj.meta != null) {
|
||||
w.uint32(90);
|
||||
w.bytes(obj.meta);
|
||||
}
|
||||
|
||||
if (obj.rateLimitProof != null) {
|
||||
w.uint32(170);
|
||||
RateLimitProof.codec().encode(obj.rateLimitProof, w);
|
||||
|
@ -207,6 +213,9 @@ export namespace WakuMessage {
|
|||
case 10:
|
||||
obj.timestamp = reader.sint64();
|
||||
break;
|
||||
case 11:
|
||||
obj.meta = reader.bytes();
|
||||
break;
|
||||
case 21:
|
||||
obj.rateLimitProof = RateLimitProof.codec().decode(
|
||||
reader,
|
||||
|
|
|
@ -676,6 +676,7 @@ export interface WakuMessage {
|
|||
contentTopic: string;
|
||||
version?: number;
|
||||
timestamp?: bigint;
|
||||
meta?: Uint8Array;
|
||||
rateLimitProof?: RateLimitProof;
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
|
@ -711,6 +712,11 @@ export namespace WakuMessage {
|
|||
w.sint64(obj.timestamp);
|
||||
}
|
||||
|
||||
if (obj.meta != null) {
|
||||
w.uint32(90);
|
||||
w.bytes(obj.meta);
|
||||
}
|
||||
|
||||
if (obj.rateLimitProof != null) {
|
||||
w.uint32(170);
|
||||
RateLimitProof.codec().encode(obj.rateLimitProof, w);
|
||||
|
@ -749,6 +755,9 @@ export namespace WakuMessage {
|
|||
case 10:
|
||||
obj.timestamp = reader.sint64();
|
||||
break;
|
||||
case 11:
|
||||
obj.meta = reader.bytes();
|
||||
break;
|
||||
case 21:
|
||||
obj.rateLimitProof = RateLimitProof.codec().decode(
|
||||
reader,
|
||||
|
|
Loading…
Reference in New Issue