insertion, auto-close, adjacent
+ * text merged — so child indices from the mount root are not trustworthy.
+ * A missing key is a receiver error.
+ */
+export interface BindMarker {
+ key: number;
+ id: number;
+ _unknownFields?: { [key: number]: Uint8Array[] } | undefined;
+}
+
+function createBaseFrame(): Frame {
+ return { commit: false, op: undefined, _unknownFields: {} };
+}
+
+export const Frame: MessageFns = {
+ encode(message: Frame, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.commit !== false) {
+ writer.uint32(8).bool(message.commit);
+ }
+ switch (message.op?.$case) {
+ case "insertBefore":
+ InsertBefore.encode(message.op.value, writer.uint32(18).fork()).join();
+ break;
+ case "setText":
+ SetText.encode(message.op.value, writer.uint32(26).fork()).join();
+ break;
+ case "setAttribute":
+ SetAttribute.encode(message.op.value, writer.uint32(34).fork()).join();
+ break;
+ case "setProperty":
+ SetProperty.encode(message.op.value, writer.uint32(42).fork()).join();
+ break;
+ case "createElement":
+ CreateElement.encode(message.op.value, writer.uint32(50).fork()).join();
+ break;
+ case "createText":
+ CreateText.encode(message.op.value, writer.uint32(58).fork()).join();
+ break;
+ case "remove":
+ Remove.encode(message.op.value, writer.uint32(66).fork()).join();
+ break;
+ case "cloneTemplate":
+ CloneTemplate.encode(message.op.value, writer.uint32(74).fork()).join();
+ break;
+ case "bindPath":
+ BindPath.encode(message.op.value, writer.uint32(82).fork()).join();
+ break;
+ case "createPlaceholder":
+ CreatePlaceholder.encode(message.op.value, writer.uint32(90).fork()).join();
+ break;
+ case "addListener":
+ AddListener.encode(message.op.value, writer.uint32(98).fork()).join();
+ break;
+ case "removeListener":
+ RemoveListener.encode(message.op.value, writer.uint32(106).fork()).join();
+ break;
+ case "intern":
+ Intern.encode(message.op.value, writer.uint32(114).fork()).join();
+ break;
+ case "registerTemplate":
+ RegisterTemplate.encode(message.op.value, writer.uint32(122).fork()).join();
+ break;
+ case "insertAfter":
+ InsertAfter.encode(message.op.value, writer.uint32(130).fork()).join();
+ break;
+ case "bindMarker":
+ BindMarker.encode(message.op.value, writer.uint32(138).fork()).join();
+ break;
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): Frame {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseFrame();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.commit = reader.bool();
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.op = { $case: "insertBefore", value: InsertBefore.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 3: {
+ if (tag !== 26) {
+ break;
+ }
+
+ message.op = { $case: "setText", value: SetText.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 4: {
+ if (tag !== 34) {
+ break;
+ }
+
+ message.op = { $case: "setAttribute", value: SetAttribute.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 5: {
+ if (tag !== 42) {
+ break;
+ }
+
+ message.op = { $case: "setProperty", value: SetProperty.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 6: {
+ if (tag !== 50) {
+ break;
+ }
+
+ message.op = { $case: "createElement", value: CreateElement.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 7: {
+ if (tag !== 58) {
+ break;
+ }
+
+ message.op = { $case: "createText", value: CreateText.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 8: {
+ if (tag !== 66) {
+ break;
+ }
+
+ message.op = { $case: "remove", value: Remove.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 9: {
+ if (tag !== 74) {
+ break;
+ }
+
+ message.op = { $case: "cloneTemplate", value: CloneTemplate.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 10: {
+ if (tag !== 82) {
+ break;
+ }
+
+ message.op = { $case: "bindPath", value: BindPath.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 11: {
+ if (tag !== 90) {
+ break;
+ }
+
+ message.op = { $case: "createPlaceholder", value: CreatePlaceholder.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 12: {
+ if (tag !== 98) {
+ break;
+ }
+
+ message.op = { $case: "addListener", value: AddListener.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 13: {
+ if (tag !== 106) {
+ break;
+ }
+
+ message.op = { $case: "removeListener", value: RemoveListener.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 14: {
+ if (tag !== 114) {
+ break;
+ }
+
+ message.op = { $case: "intern", value: Intern.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 15: {
+ if (tag !== 122) {
+ break;
+ }
+
+ message.op = { $case: "registerTemplate", value: RegisterTemplate.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 16: {
+ if (tag !== 130) {
+ break;
+ }
+
+ message.op = { $case: "insertAfter", value: InsertAfter.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 17: {
+ if (tag !== 138) {
+ break;
+ }
+
+ message.op = { $case: "bindMarker", value: BindMarker.decode(reader, reader.uint32()) };
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseIntern(): Intern {
+ return { id: 0, s: "", _unknownFields: {} };
+}
+
+export const Intern: MessageFns = {
+ encode(message: Intern, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message.s !== "") {
+ writer.uint32(18).string(message.s);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): Intern {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseIntern();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.s = reader.string();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseCreateElement(): CreateElement {
+ return { id: 0, tag: 0, ns: undefined, _unknownFields: {} };
+}
+
+export const CreateElement: MessageFns = {
+ encode(message: CreateElement, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message.tag !== 0) {
+ writer.uint32(16).uint32(message.tag);
+ }
+ if (message.ns !== undefined) {
+ writer.uint32(24).uint32(message.ns);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): CreateElement {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseCreateElement();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.tag = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.ns = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseCreateText(): CreateText {
+ return { id: 0, text: "", _unknownFields: {} };
+}
+
+export const CreateText: MessageFns = {
+ encode(message: CreateText, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message.text !== "") {
+ writer.uint32(18).string(message.text);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): CreateText {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseCreateText();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.text = reader.string();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseCreatePlaceholder(): CreatePlaceholder {
+ return { id: 0, _unknownFields: {} };
+}
+
+export const CreatePlaceholder: MessageFns = {
+ encode(message: CreatePlaceholder, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): CreatePlaceholder {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseCreatePlaceholder();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseInsertBefore(): InsertBefore {
+ return { parent: undefined, id: 0, anchor: undefined, _unknownFields: {} };
+}
+
+export const InsertBefore: MessageFns = {
+ encode(message: InsertBefore, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.parent !== undefined) {
+ writer.uint32(8).uint32(message.parent);
+ }
+ if (message.id !== 0) {
+ writer.uint32(16).uint32(message.id);
+ }
+ if (message.anchor !== undefined) {
+ writer.uint32(24).uint32(message.anchor);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): InsertBefore {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseInsertBefore();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.parent = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.anchor = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseInsertAfter(): InsertAfter {
+ return { parent: undefined, id: 0, anchor: 0, _unknownFields: {} };
+}
+
+export const InsertAfter: MessageFns = {
+ encode(message: InsertAfter, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.parent !== undefined) {
+ writer.uint32(8).uint32(message.parent);
+ }
+ if (message.id !== 0) {
+ writer.uint32(16).uint32(message.id);
+ }
+ if (message.anchor !== 0) {
+ writer.uint32(24).uint32(message.anchor);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): InsertAfter {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseInsertAfter();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.parent = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.anchor = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseRemove(): Remove {
+ return { id: 0, _unknownFields: {} };
+}
+
+export const Remove: MessageFns = {
+ encode(message: Remove, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): Remove {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseRemove();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseSetText(): SetText {
+ return { id: 0, text: "", _unknownFields: {} };
+}
+
+export const SetText: MessageFns = {
+ encode(message: SetText, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message.text !== "") {
+ writer.uint32(18).string(message.text);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SetText {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSetText();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.text = reader.string();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseSetAttribute(): SetAttribute {
+ return { id: 0, name: 0, ns: undefined, value: undefined, _unknownFields: {} };
+}
+
+export const SetAttribute: MessageFns = {
+ encode(message: SetAttribute, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message.name !== 0) {
+ writer.uint32(16).uint32(message.name);
+ }
+ if (message.ns !== undefined) {
+ writer.uint32(24).uint32(message.ns);
+ }
+ switch (message.value?.$case) {
+ case "text":
+ writer.uint32(34).string(message.value.value);
+ break;
+ case "asset":
+ writer.uint32(42).bytes(message.value.value);
+ break;
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SetAttribute {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSetAttribute();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.name = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.ns = reader.uint32();
+ continue;
+ }
+ case 4: {
+ if (tag !== 34) {
+ break;
+ }
+
+ message.value = { $case: "text", value: reader.string() };
+ continue;
+ }
+ case 5: {
+ if (tag !== 42) {
+ break;
+ }
+
+ message.value = { $case: "asset", value: reader.bytes() };
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseSetProperty(): SetProperty {
+ return { id: 0, name: 0, value: undefined, _unknownFields: {} };
+}
+
+export const SetProperty: MessageFns = {
+ encode(message: SetProperty, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ if (message.name !== 0) {
+ writer.uint32(16).uint32(message.name);
+ }
+ switch (message.value?.$case) {
+ case "text":
+ writer.uint32(26).string(message.value.value);
+ break;
+ case "int":
+ writer.uint32(32).sint64(message.value.value);
+ break;
+ case "float":
+ writer.uint32(41).double(message.value.value);
+ break;
+ case "boolean":
+ writer.uint32(48).bool(message.value.value);
+ break;
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): SetProperty {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSetProperty();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.name = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 26) {
+ break;
+ }
+
+ message.value = { $case: "text", value: reader.string() };
+ continue;
+ }
+ case 4: {
+ if (tag !== 32) {
+ break;
+ }
+
+ message.value = { $case: "int", value: longToNumber(reader.sint64()) };
+ continue;
+ }
+ case 5: {
+ if (tag !== 41) {
+ break;
+ }
+
+ message.value = { $case: "float", value: reader.double() };
+ continue;
+ }
+ case 6: {
+ if (tag !== 48) {
+ break;
+ }
+
+ message.value = { $case: "boolean", value: reader.bool() };
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseListener(): Listener {
+ return {
+ target: undefined,
+ name: 0,
+ bubbles: false,
+ capture: false,
+ passive: false,
+ preventDefault: false,
+ stopPropagation: false,
+ _unknownFields: {},
+ };
+}
+
+export const Listener: MessageFns = {
+ encode(message: Listener, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ switch (message.target?.$case) {
+ case "id":
+ writer.uint32(8).uint32(message.target.value);
+ break;
+ case "global":
+ writer.uint32(64).int32(message.target.value);
+ break;
+ }
+ if (message.name !== 0) {
+ writer.uint32(16).uint32(message.name);
+ }
+ if (message.bubbles !== false) {
+ writer.uint32(24).bool(message.bubbles);
+ }
+ if (message.capture !== false) {
+ writer.uint32(32).bool(message.capture);
+ }
+ if (message.passive !== false) {
+ writer.uint32(40).bool(message.passive);
+ }
+ if (message.preventDefault !== false) {
+ writer.uint32(48).bool(message.preventDefault);
+ }
+ if (message.stopPropagation !== false) {
+ writer.uint32(56).bool(message.stopPropagation);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): Listener {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseListener();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.target = { $case: "id", value: reader.uint32() };
+ continue;
+ }
+ case 8: {
+ if (tag !== 64) {
+ break;
+ }
+
+ message.target = { $case: "global", value: reader.int32() as any };
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.name = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.bubbles = reader.bool();
+ continue;
+ }
+ case 4: {
+ if (tag !== 32) {
+ break;
+ }
+
+ message.capture = reader.bool();
+ continue;
+ }
+ case 5: {
+ if (tag !== 40) {
+ break;
+ }
+
+ message.passive = reader.bool();
+ continue;
+ }
+ case 6: {
+ if (tag !== 48) {
+ break;
+ }
+
+ message.preventDefault = reader.bool();
+ continue;
+ }
+ case 7: {
+ if (tag !== 56) {
+ break;
+ }
+
+ message.stopPropagation = reader.bool();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseAddListener(): AddListener {
+ return { listener: undefined, _unknownFields: {} };
+}
+
+export const AddListener: MessageFns = {
+ encode(message: AddListener, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.listener !== undefined) {
+ Listener.encode(message.listener, writer.uint32(10).fork()).join();
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): AddListener {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseAddListener();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 10) {
+ break;
+ }
+
+ message.listener = Listener.decode(reader, reader.uint32());
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseRemoveListener(): RemoveListener {
+ return { listener: undefined, _unknownFields: {} };
+}
+
+export const RemoveListener: MessageFns = {
+ encode(message: RemoveListener, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.listener !== undefined) {
+ Listener.encode(message.listener, writer.uint32(10).fork()).join();
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): RemoveListener {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseRemoveListener();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 10) {
+ break;
+ }
+
+ message.listener = Listener.decode(reader, reader.uint32());
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseTemplateAttr(): TemplateAttr {
+ return { name: 0, ns: undefined, value: undefined, _unknownFields: {} };
+}
+
+export const TemplateAttr: MessageFns = {
+ encode(message: TemplateAttr, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.name !== 0) {
+ writer.uint32(8).uint32(message.name);
+ }
+ if (message.ns !== undefined) {
+ writer.uint32(16).uint32(message.ns);
+ }
+ switch (message.value?.$case) {
+ case "text":
+ writer.uint32(26).string(message.value.value);
+ break;
+ case "asset":
+ writer.uint32(34).bytes(message.value.value);
+ break;
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): TemplateAttr {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseTemplateAttr();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.name = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.ns = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 26) {
+ break;
+ }
+
+ message.value = { $case: "text", value: reader.string() };
+ continue;
+ }
+ case 4: {
+ if (tag !== 34) {
+ break;
+ }
+
+ message.value = { $case: "asset", value: reader.bytes() };
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseTemplateElement(): TemplateElement {
+ return { tag: 0, ns: undefined, attrs: [], children: [], _unknownFields: {} };
+}
+
+export const TemplateElement: MessageFns = {
+ encode(message: TemplateElement, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.tag !== 0) {
+ writer.uint32(8).uint32(message.tag);
+ }
+ if (message.ns !== undefined) {
+ writer.uint32(16).uint32(message.ns);
+ }
+ for (const v of message.attrs) {
+ TemplateAttr.encode(v!, writer.uint32(26).fork()).join();
+ }
+ writer.uint32(34).fork();
+ for (const v of message.children) {
+ writer.uint32(v);
+ }
+ writer.join();
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): TemplateElement {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseTemplateElement();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.tag = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.ns = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 26) {
+ break;
+ }
+
+ message.attrs.push(TemplateAttr.decode(reader, reader.uint32()));
+ continue;
+ }
+ case 4: {
+ if (tag === 32) {
+ message.children.push(reader.uint32());
+
+ continue;
+ }
+
+ if (tag === 34) {
+ const end2 = reader.uint32() + reader.pos;
+ while (reader.pos < end2) {
+ message.children.push(reader.uint32());
+ }
+
+ continue;
+ }
+
+ break;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseDynamic(): Dynamic {
+ return { _unknownFields: {} };
+}
+
+export const Dynamic: MessageFns = {
+ encode(message: Dynamic, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): Dynamic {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseDynamic();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseTemplateNode(): TemplateNode {
+ return { kind: undefined, _unknownFields: {} };
+}
+
+export const TemplateNode: MessageFns = {
+ encode(message: TemplateNode, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ switch (message.kind?.$case) {
+ case "element":
+ TemplateElement.encode(message.kind.value, writer.uint32(10).fork()).join();
+ break;
+ case "text":
+ writer.uint32(18).string(message.kind.value);
+ break;
+ case "dynamic":
+ Dynamic.encode(message.kind.value, writer.uint32(26).fork()).join();
+ break;
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): TemplateNode {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseTemplateNode();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 10) {
+ break;
+ }
+
+ message.kind = { $case: "element", value: TemplateElement.decode(reader, reader.uint32()) };
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.kind = { $case: "text", value: reader.string() };
+ continue;
+ }
+ case 3: {
+ if (tag !== 26) {
+ break;
+ }
+
+ message.kind = { $case: "dynamic", value: Dynamic.decode(reader, reader.uint32()) };
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseRegisterTemplate(): RegisterTemplate {
+ return { id: 0, nodes: [], roots: [], _unknownFields: {} };
+}
+
+export const RegisterTemplate: MessageFns = {
+ encode(message: RegisterTemplate, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.id !== 0) {
+ writer.uint32(8).uint32(message.id);
+ }
+ for (const v of message.nodes) {
+ TemplateNode.encode(v!, writer.uint32(18).fork()).join();
+ }
+ writer.uint32(26).fork();
+ for (const v of message.roots) {
+ writer.uint32(v);
+ }
+ writer.join();
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): RegisterTemplate {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseRegisterTemplate();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.nodes.push(TemplateNode.decode(reader, reader.uint32()));
+ continue;
+ }
+ case 3: {
+ if (tag === 24) {
+ message.roots.push(reader.uint32());
+
+ continue;
+ }
+
+ if (tag === 26) {
+ const end2 = reader.uint32() + reader.pos;
+ while (reader.pos < end2) {
+ message.roots.push(reader.uint32());
+ }
+
+ continue;
+ }
+
+ break;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseCloneTemplate(): CloneTemplate {
+ return { tmpl: 0, root: 0, id: 0, _unknownFields: {} };
+}
+
+export const CloneTemplate: MessageFns = {
+ encode(message: CloneTemplate, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.tmpl !== 0) {
+ writer.uint32(8).uint32(message.tmpl);
+ }
+ if (message.root !== 0) {
+ writer.uint32(16).uint32(message.root);
+ }
+ if (message.id !== 0) {
+ writer.uint32(24).uint32(message.id);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): CloneTemplate {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseCloneTemplate();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.tmpl = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.root = reader.uint32();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseBindPath(): BindPath {
+ return { root: 0, path: new Uint8Array(0), id: 0, _unknownFields: {} };
+}
+
+export const BindPath: MessageFns = {
+ encode(message: BindPath, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.root !== 0) {
+ writer.uint32(8).uint32(message.root);
+ }
+ if (message.path.length !== 0) {
+ writer.uint32(18).bytes(message.path);
+ }
+ if (message.id !== 0) {
+ writer.uint32(24).uint32(message.id);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): BindPath {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseBindPath();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.root = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 18) {
+ break;
+ }
+
+ message.path = reader.bytes();
+ continue;
+ }
+ case 3: {
+ if (tag !== 24) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function createBaseBindMarker(): BindMarker {
+ return { key: 0, id: 0, _unknownFields: {} };
+}
+
+export const BindMarker: MessageFns = {
+ encode(message: BindMarker, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
+ if (message.key !== 0) {
+ writer.uint32(8).uint32(message.key);
+ }
+ if (message.id !== 0) {
+ writer.uint32(16).uint32(message.id);
+ }
+ if (message._unknownFields !== undefined) {
+ for (const [key, values] of globalThis.Object.entries(message._unknownFields)) {
+ const tag = parseInt(key, 10);
+ for (const value of values) {
+ writer.uint32(tag).raw(value);
+ }
+ }
+ }
+ return writer;
+ },
+
+ decode(input: BinaryReader | Uint8Array, length?: number): BindMarker {
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
+ const previousRecursionDepth = (reader as any).__tsProtoDecodeDepth ?? 0;
+ if (previousRecursionDepth >= 100) {
+ throw new globalThis.Error("protobuf decode recursion limit exceeded");
+ }
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth + 1;
+ try {
+ const end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseBindMarker();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1: {
+ if (tag !== 8) {
+ break;
+ }
+
+ message.key = reader.uint32();
+ continue;
+ }
+ case 2: {
+ if (tag !== 16) {
+ break;
+ }
+
+ message.id = reader.uint32();
+ continue;
+ }
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ const buf = reader.skip(tag & 7);
+
+ const list = message._unknownFields![tag];
+
+ if (list === undefined) {
+ message._unknownFields![tag] = [buf];
+ } else {
+ list.push(buf);
+ }
+ }
+ return message;
+ } finally {
+ (reader as any).__tsProtoDecodeDepth = previousRecursionDepth;
+ }
+ },
+};
+
+function longToNumber(int64: { toString(): string }): number {
+ const num = globalThis.Number(int64.toString());
+ if (num > globalThis.Number.MAX_SAFE_INTEGER) {
+ throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
+ }
+ if (num < globalThis.Number.MIN_SAFE_INTEGER) {
+ throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER");
+ }
+ return num;
+}
+
+export interface MessageFns {
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
+}
diff --git a/receiver/src/mod.ts b/receiver/src/mod.ts
index 041e400..fb29182 100644
--- a/receiver/src/mod.ts
+++ b/receiver/src/mod.ts
@@ -1,6 +1,5 @@
// Re-exports for `@polymorph/stream-dom-receiver`.
-export { Reader, WireType, Writer } from "./proto.ts";
export { FrameDecoder, PROTOCOL_VERSION } from "./frames.ts";
export type {
AttrValue,
diff --git a/receiver/src/proto.ts b/receiver/src/proto.ts
deleted file mode 100644
index 42e7ca6..0000000
--- a/receiver/src/proto.ts
+++ /dev/null
@@ -1,313 +0,0 @@
-// Minimal protobuf (proto3) wire codec — no dependencies, no generated
-// code. proto/stream-dom.proto and proto/stream-dom-events.proto are
-// normative for every field number and type; this module only knows the
-// wire format (docs/design.md "Encoding: protobuf, and why not a
-// hand-rolled layout").
-//
-// Deliberately thin: `frames.ts` and `events.ts` decode field-by-field with
-// a switch on tag (the "pbf hybrid decode" the design record names), so
-// there is no generated-message layer here at all — just varint/fixed/
-// length-delimited primitives plus wire-type skip.
-
-/** Protobuf wire types (varint tag = (fieldNumber << 3) | wireType). */
-export const enum WireType {
- Varint = 0,
- Fixed64 = 1,
- LengthDelimited = 2,
- Fixed32 = 5,
-}
-
-/** Thrown only when a `Reader` ran out of bytes before it could finish
- * decoding a value — as opposed to a value that IS fully present but
- * malformed (e.g. a varint whose continuation bit stays set past the
- * widest legal encoding). `FrameDecoder`'s straddling-frame buffering
- * depends on telling these apart: a truncation means "wait for more
- * bytes"; a malformed value is a real protocol error and must not be
- * swallowed as if more bytes would fix it. */
-export class TruncatedError extends RangeError {
- constructor(message: string) {
- super(message);
- this.name = "TruncatedError";
- }
-}
-
-const textDecoder = new TextDecoder();
-const textEncoder = new TextEncoder();
-
-/** Reads one message (or a length-delimited slice of one) from a
- * `Uint8Array`. Proto3 semantics: a field absent from the wire has its
- * type's default value; the caller (frames.ts) is responsible for
- * presence-tracking `optional` scalars by noting which field numbers it
- * saw. */
-export class Reader {
- #buf: Uint8Array;
- #pos: number;
- #end: number;
-
- constructor(buf: Uint8Array, pos = 0, end = buf.length) {
- this.#buf = buf;
- this.#pos = pos;
- this.#end = end;
- }
-
- get pos(): number {
- return this.#pos;
- }
-
- /** Bytes remaining before `end`. */
- get remaining(): number {
- return this.#end - this.#pos;
- }
-
- finished(): boolean {
- return this.#pos >= this.#end;
- }
-
- #need(n: number): void {
- if (this.#pos + n > this.#end) {
- throw new TruncatedError(
- `proto.Reader: truncated (need ${n} bytes, have ${this.remaining})`,
- );
- }
- }
-
- /** Unsigned varint, up to 32 significant bits (field tags, node ids,
- * str-refs, `uint32` fields — everything in stream-dom.proto except the
- * `sint64` case). Throws a plain `RangeError` (NOT `TruncatedError`) if
- * the encoded value does not fit a u32 — that is a malformed varint,
- * fully present on the wire, not a buffering situation. */
- readVarint32(): number {
- let result = 0;
- let shift = 0;
- for (;;) {
- this.#need(1);
- const b = this.#buf[this.#pos++];
- result |= (b & 0x7f) << shift;
- if ((b & 0x80) === 0) break;
- shift += 7;
- if (shift > 35) {
- throw new RangeError("proto.Reader: varint too long for u32");
- }
- }
- return result >>> 0;
- }
-
- /** Unsigned varint as a bigint, for fields wider than 32 bits (`sint64`).
- */
- readVarint64(): bigint {
- let result = 0n;
- let shift = 0n;
- for (;;) {
- this.#need(1);
- const b = this.#buf[this.#pos++];
- result |= BigInt(b & 0x7f) << shift;
- if ((b & 0x80) === 0) break;
- shift += 7n;
- }
- return result;
- }
-
- /** Zigzag-decoded `sint32`. */
- readSInt32(): number {
- const u = this.readVarint32();
- return (u >>> 1) ^ -(u & 1);
- }
-
- /** Zigzag-decoded `sint64`, narrowed to `number` (`Number(bigint)`):
- * every `sint64` on this wire (`SetProperty.int`) carries ordinary DOM
- * property integers, never a value outside `Number.isSafeInteger`
- * range, so bigint precision is not worth the API friction of a mixed
- * number/bigint value type in `frames.ts`'s `FrameSink`. */
- readSInt64(): number {
- const u = this.readVarint64();
- const doubled = u & 1n ? -(u + 1n) / 2n : u / 2n;
- return Number(doubled);
- }
-
- readBool(): boolean {
- return this.readVarint32() !== 0;
- }
-
- readFixed32(): number {
- this.#need(4);
- const v = new DataView(
- this.#buf.buffer,
- this.#buf.byteOffset + this.#pos,
- 4,
- )
- .getUint32(0, true);
- this.#pos += 4;
- return v;
- }
-
- readDouble(): number {
- this.#need(8);
- const v = new DataView(
- this.#buf.buffer,
- this.#buf.byteOffset + this.#pos,
- 8,
- )
- .getFloat64(0, true);
- this.#pos += 8;
- return v;
- }
-
- readFloat(): number {
- this.#need(4);
- const v = new DataView(
- this.#buf.buffer,
- this.#buf.byteOffset + this.#pos,
- 4,
- )
- .getFloat32(0, true);
- this.#pos += 4;
- return v;
- }
-
- /** Length-delimited bytes, copied out (a view would alias a chunk buffer
- * a caller may not retain — see FrameDecoder's straddling-frame
- * handling). */
- readBytes(): Uint8Array {
- const len = this.readVarint32();
- this.#need(len);
- const out = this.#buf.slice(this.#pos, this.#pos + len);
- this.#pos += len;
- return out;
- }
-
- readString(): string {
- const len = this.readVarint32();
- this.#need(len);
- const s = textDecoder.decode(
- this.#buf.subarray(this.#pos, this.#pos + len),
- );
- this.#pos += len;
- return s;
- }
-
- /** A length-delimited sub-message, as a `Reader` scoped to its bytes. */
- readMessage(): Reader {
- const len = this.readVarint32();
- this.#need(len);
- const r = new Reader(this.#buf, this.#pos, this.#pos + len);
- this.#pos += len;
- return r;
- }
-
- /** Read a field tag, returning `[fieldNumber, wireType]`. */
- readTag(): [number, WireType] {
- const tag = this.readVarint32();
- return [tag >>> 3, (tag & 0x7) as WireType];
- }
-
- /** Skip a field's value given its wire type (unknown-field / unknown-op
- * tolerance — docs/design.md: "receivers skip what they do not know"). */
- skip(wireType: WireType): void {
- switch (wireType) {
- case WireType.Varint:
- this.readVarint64();
- break;
- case WireType.Fixed64:
- this.#need(8);
- this.#pos += 8;
- break;
- case WireType.LengthDelimited: {
- const len = this.readVarint32();
- this.#need(len);
- this.#pos += len;
- break;
- }
- case WireType.Fixed32:
- this.#need(4);
- this.#pos += 4;
- break;
- default:
- throw new RangeError(`proto.Reader: unknown wire type ${wireType}`);
- }
- }
-}
-
-/** Writes one message into a growable byte buffer. Used by remote.ts's
- * template validation error messages? No — by the receiver-side event
- * re-encoder is not needed; this is here for `events.ts`'s `encodePayload`
- * (an `EventPayload` the receiver sends back is decoded on the guest side,
- * so this Writer needs only the cases stream-dom-events.proto's messages
- * use: varint, bool, double, string, embedded messages). */
-export class Writer {
- #chunks: number[] = [];
-
- #byte(b: number): void {
- this.#chunks.push(b & 0xff);
- }
-
- writeVarint32(value: number): void {
- let v = value >>> 0;
- for (;;) {
- if ((v & ~0x7f) === 0) {
- this.#byte(v);
- return;
- }
- this.#byte((v & 0x7f) | 0x80);
- v >>>= 7;
- }
- }
-
- writeSInt32(value: number): void {
- this.writeVarint32(((value << 1) ^ (value >> 31)) >>> 0);
- }
-
- writeTag(fieldNumber: number, wireType: WireType): void {
- this.writeVarint32((fieldNumber << 3) | wireType);
- }
-
- writeBool(fieldNumber: number, value: boolean): void {
- this.writeTag(fieldNumber, WireType.Varint);
- this.writeVarint32(value ? 1 : 0);
- }
-
- writeUint32(fieldNumber: number, value: number): void {
- this.writeTag(fieldNumber, WireType.Varint);
- this.writeVarint32(value);
- }
-
- writeSInt32Field(fieldNumber: number, value: number): void {
- this.writeTag(fieldNumber, WireType.Varint);
- this.writeSInt32(value);
- }
-
- writeDouble(fieldNumber: number, value: number): void {
- this.writeTag(fieldNumber, WireType.Fixed64);
- const buf = new Uint8Array(8);
- new DataView(buf.buffer).setFloat64(0, value, true);
- for (const b of buf) this.#byte(b);
- }
-
- writeFloat(fieldNumber: number, value: number): void {
- this.writeTag(fieldNumber, WireType.Fixed32);
- const buf = new Uint8Array(4);
- new DataView(buf.buffer).setFloat32(0, value, true);
- for (const b of buf) this.#byte(b);
- }
-
- writeString(fieldNumber: number, value: string): void {
- const bytes = textEncoder.encode(value);
- this.writeTag(fieldNumber, WireType.LengthDelimited);
- this.writeVarint32(bytes.length);
- for (const b of bytes) this.#byte(b);
- }
-
- /** Embedded message with a length prefix: builds `build` into a fresh
- * `Writer`, then writes its bytes behind a length-delimited tag. */
- writeMessage(fieldNumber: number, build: (w: Writer) => void): void {
- const sub = new Writer();
- build(sub);
- const bytes = sub.finish();
- this.writeTag(fieldNumber, WireType.LengthDelimited);
- this.writeVarint32(bytes.length);
- for (const b of bytes) this.#byte(b);
- }
-
- finish(): Uint8Array {
- return Uint8Array.from(this.#chunks);
- }
-}
diff --git a/receiver/tests/driver_test.ts b/receiver/tests/driver_test.ts
index f56b8ee..8ccd066 100644
--- a/receiver/tests/driver_test.ts
+++ b/receiver/tests/driver_test.ts
@@ -11,7 +11,8 @@ import type { ProducerEventTarget } from "../src/driver.ts";
import { PROTOCOL_VERSION } from "../src/frames.ts";
import { PolicyError } from "../src/policy.ts";
import type { Policy } from "../src/policy.ts";
-import { Writer } from "../src/proto.ts";
+import { Frame } from "../src/gen/stream-dom.ts";
+import { frame as framed, stream } from "./wire.ts";
/** A policy that allows every op — the baseline these tests vary from. */
function allowAll(extra?: Partial): Policy {
@@ -71,58 +72,10 @@ async function pushAndAwaitCommit(
await p;
}
-// Frame field numbers, transcribed from proto/stream-dom.proto (see
-// policy_test.ts's header comment for why these are independent literals
-// rather than imports from src/).
-const FRAME_COMMIT = 1;
-const FRAME_INSERT_BEFORE = 2;
-const FRAME_SET_TEXT = 3;
-const FRAME_CREATE_ELEMENT = 6;
-const FRAME_CREATE_TEXT = 7;
-const FRAME_ADD_LISTENER = 12;
-const FRAME_INTERN = 14;
-
-const INTERN_ID = 1;
-const INTERN_S = 2;
-const CREATE_ELEMENT_ID = 1;
-const CREATE_ELEMENT_TAG = 2;
-const CREATE_TEXT_ID = 1;
-const CREATE_TEXT_TEXT = 2;
-const INSERT_BEFORE_PARENT = 1;
-const INSERT_BEFORE_ID = 2;
-const SET_TEXT_ID = 1;
-const SET_TEXT_TEXT = 2;
-const LISTENER_ID = 1;
-const LISTENER_NAME = 2;
-const LISTENER_BUBBLES = 3;
-const LISTENER_PREVENT_DEFAULT = 6;
-const ADD_LISTENER_LISTENER = 1;
-
-/** One length-delimited `Frame` message. `build` writes the op field(s);
- * `commit` sets `Frame.commit`. */
-function frame(commit: boolean, build?: (w: Writer) => void): Uint8Array {
- const inner = new Writer();
- if (commit) inner.writeBool(FRAME_COMMIT, true);
- build?.(inner);
- const body = inner.finish();
- const framed = new Writer();
- framed.writeVarint32(body.length);
- const lenBytes = framed.finish();
- const out = new Uint8Array(lenBytes.length + body.length);
- out.set(lenBytes, 0);
- out.set(body, lenBytes.length);
- return out;
-}
-
-function concat(chunks: Uint8Array[]): Uint8Array {
- const total = chunks.reduce((n, c) => n + c.length, 0);
- const out = new Uint8Array(total);
- let off = 0;
- for (const c of chunks) {
- out.set(c, off);
- off += c.length;
- }
- return out;
+/** One length-delimited `Frame` message, built with the generated
+ * writer; `commit` sets `Frame.commit`. */
+function frame(commit: boolean, op?: Frame["op"]): Uint8Array {
+ return framed(Frame.encode({ commit, op }).finish());
}
const DIV = 1, CLICK = 2;
@@ -131,48 +84,22 @@ const DIV = 1, CLICK = 2;
* "hi"), insert(10, 11), set-text(11, "hi there"), commit. */
function basicFrames(): Uint8Array[] {
return [
- frame(false, (w) => {
- w.writeMessage(FRAME_INTERN, (m) => {
- m.writeUint32(INTERN_ID, DIV);
- m.writeString(INTERN_S, "div");
- });
- }),
- frame(false, (w) => {
- w.writeMessage(FRAME_INTERN, (m) => {
- m.writeUint32(INTERN_ID, CLICK);
- m.writeString(INTERN_S, "click");
- });
- }),
- frame(false, (w) => {
- w.writeMessage(FRAME_CREATE_ELEMENT, (m) => {
- m.writeUint32(CREATE_ELEMENT_ID, 10);
- m.writeUint32(CREATE_ELEMENT_TAG, DIV);
- });
- }),
- frame(false, (w) => {
- w.writeMessage(FRAME_INSERT_BEFORE, (m) => {
- m.writeUint32(INSERT_BEFORE_PARENT, 0);
- m.writeUint32(INSERT_BEFORE_ID, 10);
- });
- }),
- frame(false, (w) => {
- w.writeMessage(FRAME_CREATE_TEXT, (m) => {
- m.writeUint32(CREATE_TEXT_ID, 11);
- m.writeString(CREATE_TEXT_TEXT, "hi");
- });
+ frame(false, { $case: "intern", value: { id: DIV, s: "div" } }),
+ frame(false, { $case: "intern", value: { id: CLICK, s: "click" } }),
+ frame(false, {
+ $case: "createElement",
+ value: { id: 10, tag: DIV, ns: undefined },
}),
- frame(false, (w) => {
- w.writeMessage(FRAME_INSERT_BEFORE, (m) => {
- m.writeUint32(INSERT_BEFORE_PARENT, 10);
- m.writeUint32(INSERT_BEFORE_ID, 11);
- });
+ frame(false, {
+ $case: "insertBefore",
+ value: { parent: 0, id: 10, anchor: undefined },
}),
- frame(true, (w) => {
- w.writeMessage(FRAME_SET_TEXT, (m) => {
- m.writeUint32(SET_TEXT_ID, 11);
- m.writeString(SET_TEXT_TEXT, "hi there");
- });
+ frame(false, { $case: "createText", value: { id: 11, text: "hi" } }),
+ frame(false, {
+ $case: "insertBefore",
+ value: { parent: 10, id: 11, anchor: undefined },
}),
+ frame(true, { $case: "setText", value: { id: 11, text: "hi there" } }),
];
}
@@ -180,15 +107,19 @@ function basicFrames(): Uint8Array[] {
function addClickListenerFrame(
opts?: { preventDefault?: boolean },
): Uint8Array {
- return frame(true, (w) => {
- w.writeMessage(FRAME_ADD_LISTENER, (m) => {
- m.writeMessage(ADD_LISTENER_LISTENER, (l) => {
- l.writeUint32(LISTENER_ID, 10);
- l.writeUint32(LISTENER_NAME, CLICK);
- l.writeBool(LISTENER_BUBBLES, true);
- if (opts?.preventDefault) l.writeBool(LISTENER_PREVENT_DEFAULT, true);
- });
- });
+ return frame(true, {
+ $case: "addListener",
+ value: {
+ listener: {
+ target: { $case: "id", value: 10 },
+ name: CLICK,
+ bubbles: true,
+ capture: false,
+ passive: false,
+ preventDefault: opts?.preventDefault === true,
+ stopPropagation: false,
+ },
+ },
});
}
@@ -204,7 +135,7 @@ Deno.test("Driver: bytes in, DOM out, split mid-frame, no component", async () =
},
});
- const all = concat(basicFrames());
+ const all = stream(...basicFrames());
// Split mid-frame: partway through the fixed-point of the sequence,
// well inside a frame's body rather than on a frame boundary.
const mid = Math.floor(all.length / 2);
@@ -235,7 +166,7 @@ Deno.test("Driver: a bubbling click listener fires handleEvent with the right ta
calls.push([target, nameRef, payload]);
},
});
- await pushAndAwaitCommit(driver, concat(basicFrames()));
+ await pushAndAwaitCommit(driver, stream(...basicFrames()));
await pushAndAwaitCommit(driver, addClickListenerFrame());
const div = root.querySelector("div")!;
@@ -263,7 +194,7 @@ Deno.test("Driver: prevent_default is honored even though handleEvent does nothi
root,
handleEvent: () => {}, // does nothing — flag must still land
});
- await pushAndAwaitCommit(driver, concat(basicFrames()));
+ await pushAndAwaitCommit(driver, stream(...basicFrames()));
await pushAndAwaitCommit(
driver,
addClickListenerFrame({ preventDefault: true }),
@@ -292,7 +223,7 @@ Deno.test("Driver: policy.query gates individual queries; absent means allow", a
policy: allowAll({ query: (name) => name !== "set-focus" }),
handleEvent: () => {},
});
- await pushAndAwaitCommit(gated, concat(basicFrames()));
+ await pushAndAwaitCommit(gated, stream(...basicFrames()));
assertEquals(gated.queries.setFocus(10, true), false);
// ...while a query the same policy does not refuse still answers.
assertEquals(gated.queries.getClientRect(10), {
@@ -307,7 +238,7 @@ Deno.test("Driver: policy.query gates individual queries; absent means allow", a
policy: allowAll(),
handleEvent: () => {},
});
- await pushAndAwaitCommit(open, concat(basicFrames()));
+ await pushAndAwaitCommit(open, stream(...basicFrames()));
assertEquals(open.queries.setFocus(10, true), true);
assertEquals(open.queries.getClientRect(10), {
origin: { x: 0, y: 0 },
@@ -328,7 +259,7 @@ Deno.test("Driver: a policy rejection throws PolicyError out of push", () => {
});
const driver = createDriver({ root, policy, handleEvent: () => {} });
assertThrows(
- () => driver.push(concat(basicFrames())),
+ () => driver.push(stream(...basicFrames())),
PolicyError,
"div is not in the host vocabulary",
);
@@ -352,11 +283,9 @@ Deno.test("Driver: a policy pinning another protocol version is refused at const
Deno.test("Driver: an unknown node id throws", () => {
const { win, root } = fixture();
const driver = createDriver({ root, handleEvent: () => {} });
- const bad = frame(true, (w) => {
- w.writeMessage(FRAME_SET_TEXT, (m) => {
- m.writeUint32(SET_TEXT_ID, 999);
- m.writeString(SET_TEXT_TEXT, "x");
- });
+ const bad = frame(true, {
+ $case: "setText",
+ value: { id: 999, text: "x" },
});
assertThrows(() => driver.push(bad), Error, "unknown node id 999");
withGlobalWindow(win, () => driver.dispose());
@@ -376,7 +305,7 @@ Deno.test("Driver: defaultPreventDefault prevents a submit with NO producer list
defaultPreventDefault: on,
handleEvent: () => {},
});
- await pushAndAwaitCommit(driver, concat(basicFrames()));
+ await pushAndAwaitCommit(driver, stream(...basicFrames()));
const div = root.querySelector("div")!;
const ev = new (win as unknown as { Event: typeof Event }).Event(
@@ -420,26 +349,17 @@ async function fixtureWithLeaf(
});
await pushAndAwaitCommit(
driver,
- concat([
- frame(false, (w) => {
- w.writeMessage(FRAME_INTERN, (m) => {
- m.writeUint32(INTERN_ID, DIV);
- m.writeString(INTERN_S, tag);
- });
- }),
- frame(false, (w) => {
- w.writeMessage(FRAME_CREATE_ELEMENT, (m) => {
- m.writeUint32(CREATE_ELEMENT_ID, 10);
- m.writeUint32(CREATE_ELEMENT_TAG, DIV);
- });
+ stream(
+ frame(false, { $case: "intern", value: { id: DIV, s: tag } }),
+ frame(false, {
+ $case: "createElement",
+ value: { id: 10, tag: DIV, ns: undefined },
}),
- frame(true, (w) => {
- w.writeMessage(FRAME_INSERT_BEFORE, (m) => {
- m.writeUint32(INSERT_BEFORE_PARENT, 0);
- m.writeUint32(INSERT_BEFORE_ID, 10);
- });
+ frame(true, {
+ $case: "insertBefore",
+ value: { parent: 0, id: 10, anchor: undefined },
}),
- ]),
+ ),
);
if (href !== undefined) {
root.querySelector(tag)!.setAttribute("href", href);
@@ -489,26 +409,17 @@ Deno.test("Driver: defaultPreventDefault off means nothing is prevented, even a
});
await pushAndAwaitCommit(
driver,
- concat([
- frame(false, (w) => {
- w.writeMessage(FRAME_INTERN, (m) => {
- m.writeUint32(INTERN_ID, DIV);
- m.writeString(INTERN_S, "a");
- });
- }),
- frame(false, (w) => {
- w.writeMessage(FRAME_CREATE_ELEMENT, (m) => {
- m.writeUint32(CREATE_ELEMENT_ID, 10);
- m.writeUint32(CREATE_ELEMENT_TAG, DIV);
- });
+ stream(
+ frame(false, { $case: "intern", value: { id: DIV, s: "a" } }),
+ frame(false, {
+ $case: "createElement",
+ value: { id: 10, tag: DIV, ns: undefined },
}),
- frame(true, (w) => {
- w.writeMessage(FRAME_INSERT_BEFORE, (m) => {
- m.writeUint32(INSERT_BEFORE_PARENT, 0);
- m.writeUint32(INSERT_BEFORE_ID, 10);
- });
+ frame(true, {
+ $case: "insertBefore",
+ value: { parent: 0, id: 10, anchor: undefined },
}),
- ]),
+ ),
);
root.querySelector("a")!.setAttribute("href", "http://x");
const ev = new (win as unknown as { Event: typeof Event }).Event("click", {
@@ -529,7 +440,7 @@ Deno.test("Driver: after dispose, root listeners are gone (dispatch no longer ca
calls.push(a);
},
});
- await pushAndAwaitCommit(driver, concat(basicFrames()));
+ await pushAndAwaitCommit(driver, stream(...basicFrames()));
await pushAndAwaitCommit(driver, addClickListenerFrame());
withGlobalWindow(win, () => driver.dispose());
@@ -544,5 +455,5 @@ Deno.test("Driver: after dispose, root listeners are gone (dispatch no longer ca
assertEquals(calls.length, 0);
// push is a no-op after dispose (Driver.push's documented contract).
- driver.push(concat(basicFrames()));
+ driver.push(stream(...basicFrames()));
});
diff --git a/receiver/tests/events_test.ts b/receiver/tests/events_test.ts
index 12278f0..1699d80 100644
--- a/receiver/tests/events_test.ts
+++ b/receiver/tests/events_test.ts
@@ -1,35 +1,27 @@
import { assertEquals } from "@std/assert";
import { encodePayload } from "../src/events.ts";
-import { Reader } from "../src/proto.ts";
-
-// proto/stream-dom-events.proto: EventPayload.navigation = 14,
-// NavigationData.href = 1.
-const EVENT_PAYLOAD_NAVIGATION = 14;
-const NAVIGATION_HREF = 1;
+import { EventPayload } from "../src/gen/stream-dom-events.ts";
Deno.test("encodePayload('hashchange') yields a navigation payload with the current location.href", () => {
- const bytes = encodePayload("hashchange", new Event("hashchange"));
- const r = new Reader(bytes);
- const [field] = r.readTag();
- assertEquals(field, EVENT_PAYLOAD_NAVIGATION);
- const sub = r.readMessage();
- const [hrefField] = sub.readTag();
- assertEquals(hrefField, NAVIGATION_HREF);
- const href = sub.readString();
+ const payload = EventPayload.decode(
+ encodePayload("hashchange", new Event("hashchange")),
+ );
+ assertEquals(payload.family?.$case, "navigation");
+ const href = payload.family?.$case === "navigation"
+ ? payload.family.value.href
+ : undefined;
// `deno test` runs with no `--location`, so `globalThis.location` is
// typically `undefined` here — assert against whatever the runtime
// actually reports rather than a hardcoded string, so this test stays
// meaningful under `--location` too.
assertEquals(href, globalThis.location?.href ?? "");
- assertEquals(sub.finished(), true);
- assertEquals(r.finished(), true);
});
Deno.test("encodePayload('popstate') also yields the navigation family", () => {
- const bytes = encodePayload("popstate", new Event("popstate"));
- const r = new Reader(bytes);
- const [field] = r.readTag();
- assertEquals(field, EVENT_PAYLOAD_NAVIGATION);
+ const payload = EventPayload.decode(
+ encodePayload("popstate", new Event("popstate")),
+ );
+ assertEquals(payload.family?.$case, "navigation");
});
Deno.test("encodePayload for an unrecognized name yields the empty payload (zero bytes)", () => {
diff --git a/receiver/tests/frames_test.ts b/receiver/tests/frames_test.ts
index 09ad3b5..951e3f9 100644
--- a/receiver/tests/frames_test.ts
+++ b/receiver/tests/frames_test.ts
@@ -1,6 +1,7 @@
import { assertEquals, assertThrows } from "@std/assert";
import { FrameDecoder } from "../src/frames.ts";
-import { Writer } from "../src/proto.ts";
+import { Frame, Global } from "../src/gen/stream-dom.ts";
+import { frame } from "./wire.ts";
import type {
AttrValue,
FrameSink,
@@ -255,27 +256,41 @@ Deno.test("FrameDecoder throws (does not silently wait forever) on a malformed l
assertThrows(() => decoder.push(malformed));
});
+Deno.test("FrameDecoder throws on a continuation-only length prefix past the legal width", () => {
+ const sink = new RecordingSink();
+ const decoder = new FrameDecoder(sink);
+ // Six continuation bytes and no terminator: a u32 varint is at most
+ // five, so this can never become a valid length however many bytes
+ // follow. `BinaryReader.uint32()` reports it the same way it reports a
+ // genuine truncation, so the decoder's five-byte probe window — not the
+ // reader — is what tells the two apart.
+ assertThrows(() =>
+ decoder.push(Uint8Array.of(0x80, 0x80, 0x80, 0x80, 0x80, 0x80))
+ );
+});
+
// Global.DOCUMENT (value 1) isn't exercised by basic.pb (which only uses
-// WINDOW), so hand-encode a frame for it: Frame.add_listener (field 12) ->
-// AddListener.listener (field 1) -> Listener.global (field 8, enum varint).
+// WINDOW), so build a frame for it with the generated writer.
Deno.test("FrameDecoder decodes Listener.target = Global(DOCUMENT)", () => {
- const inner = new Writer();
- inner.writeBool(1, false); // Frame.commit
- inner.writeMessage(12, (addListenerW) => {
- addListenerW.writeMessage(1, (listenerW) => {
- listenerW.writeUint32(8, 1); // Global.DOCUMENT = 1
- listenerW.writeUint32(2, 42); // Listener.name
- });
- });
- const frameBytes = inner.finish();
-
- const lengthPrefix = new Writer();
- lengthPrefix.writeVarint32(frameBytes.length);
- const framed = new Uint8Array(
- lengthPrefix.finish().length + frameBytes.length,
+ const framed = frame(
+ Frame.encode({
+ commit: false,
+ op: {
+ $case: "addListener",
+ value: {
+ listener: {
+ target: { $case: "global", value: Global.DOCUMENT },
+ name: 42,
+ bubbles: false,
+ capture: false,
+ passive: false,
+ preventDefault: false,
+ stopPropagation: false,
+ },
+ },
+ },
+ }).finish(),
);
- framed.set(lengthPrefix.finish(), 0);
- framed.set(frameBytes, lengthPrefix.finish().length);
const sink = new RecordingSink();
const decoder = new FrameDecoder(sink);
diff --git a/receiver/tests/hostile_test.ts b/receiver/tests/hostile_test.ts
index bac2ce0..49c3d08 100644
--- a/receiver/tests/hostile_test.ts
+++ b/receiver/tests/hostile_test.ts
@@ -13,7 +13,8 @@ import { assert } from "@std/assert";
import { parseHTML } from "linkedom";
import { FrameDecoder, MAX_FRAME_BYTES } from "../src/frames.ts";
import { NativeDomReceiver } from "../src/native.ts";
-import { Writer } from "../src/proto.ts";
+import { BinaryWriter, WireType } from "@bufbuild/protobuf/wire";
+import { frame as framed, stream } from "./wire.ts";
// -- deterministic PRNG ---------------------------------------------------
@@ -118,32 +119,37 @@ function assertMountIntact(h: Harness, where: string): void {
// -- frame encoding -------------------------------------------------------
-/** One length-delimited `Frame`, as the stream layout specifies (varint
- * byte length, then the message). */
-function frame(build: (w: Writer) => void): Uint8Array {
- const body = (() => {
- const w = new Writer();
- build(w);
- return w.finish();
- })();
- const lp = new Writer();
- lp.writeVarint32(body.length);
- const prefix = lp.finish();
- const out = new Uint8Array(prefix.length + body.length);
- out.set(prefix, 0);
- out.set(body, prefix.length);
- return out;
+/** Hostile fixtures are written field by field with `BinaryWriter`
+ * directly rather than through the generated `Frame` writer: the point of
+ * the generator below is to emit field combinations no valid message
+ * object can express (absent required-ish fields, unknown enum values,
+ * repeated oneof arms). These four helpers are the whole vocabulary. */
+type Build = (w: BinaryWriter) => void;
+
+function msg(w: BinaryWriter, field: number, build: Build): void {
+ w.tag(field, WireType.LengthDelimited).fork();
+ build(w);
+ w.join();
}
-function concat(parts: Uint8Array[]): Uint8Array {
- const total = parts.reduce((n, p) => n + p.length, 0);
- const out = new Uint8Array(total);
- let at = 0;
- for (const p of parts) {
- out.set(p, at);
- at += p.length;
- }
- return out;
+function u32(w: BinaryWriter, field: number, v: number): void {
+ w.tag(field, WireType.Varint).uint32(v);
+}
+
+function str(w: BinaryWriter, field: number, v: string): void {
+ w.tag(field, WireType.LengthDelimited).string(v);
+}
+
+function bool(w: BinaryWriter, field: number, v: boolean): void {
+ w.tag(field, WireType.Varint).bool(v);
+}
+
+/** One length-delimited `Frame`, as the stream layout specifies (varint
+ * byte length, then the message). */
+function frame(build: Build): Uint8Array {
+ const w = new BinaryWriter();
+ build(w);
+ return framed(w.finish());
}
// Frame.op field numbers (proto/stream-dom.proto).
@@ -223,27 +229,27 @@ Deno.test("hostile: random op sequences never damage the embedder's DOM", () =>
const ref = () => rand() < HOSTILE_RATE ? pick(badRefs) : pick(goodRefs);
const frames: Uint8Array[] = [
frame((w) =>
- w.writeMessage(F_INTERN, (m) => {
- m.writeUint32(1, 1);
- m.writeString(2, "div");
+ msg(w, F_INTERN, (m) => {
+ u32(m, 1, 1);
+ str(m, 2, "div");
})
),
frame((w) =>
- w.writeMessage(F_INTERN, (m) => {
- m.writeUint32(1, 2);
- m.writeString(2, "span");
+ msg(w, F_INTERN, (m) => {
+ u32(m, 1, 2);
+ str(m, 2, "span");
})
),
frame((w) =>
- w.writeMessage(F_INTERN, (m) => {
- m.writeUint32(1, 3);
- m.writeString(2, "class");
+ msg(w, F_INTERN, (m) => {
+ u32(m, 1, 3);
+ str(m, 2, "class");
})
),
frame((w) =>
- w.writeMessage(F_INTERN, (m) => {
- m.writeUint32(1, 4);
- m.writeString(2, "click");
+ msg(w, F_INTERN, (m) => {
+ u32(m, 1, 4);
+ str(m, 2, "click");
})
),
];
@@ -253,15 +259,15 @@ Deno.test("hostile: random op sequences never damage the embedder's DOM", () =>
for (let k = 0; k < 3; k++) {
const id = newId(true);
frames.push(frame((w) =>
- w.writeMessage(F_CREATE_ELEMENT, (m) => {
- m.writeUint32(1, id);
- m.writeUint32(2, 1);
+ msg(w, F_CREATE_ELEMENT, (m) => {
+ u32(m, 1, id);
+ u32(m, 2, 1);
})
));
frames.push(frame((w) =>
- w.writeMessage(F_INSERT_BEFORE, (m) => {
- m.writeUint32(1, k === 0 ? 0 : k);
- m.writeUint32(2, id);
+ msg(w, F_INSERT_BEFORE, (m) => {
+ u32(m, 1, k === 0 ? 0 : k);
+ u32(m, 2, id);
})
));
}
@@ -270,46 +276,43 @@ Deno.test("hostile: random op sequences never damage the embedder's DOM", () =>
switch (int(15)) {
case 0:
frames.push(frame((w) =>
- w.writeMessage(F_CREATE_ELEMENT, (m) => {
- m.writeUint32(1, newId(true));
- m.writeUint32(2, ref());
- if (rand() < 0.3) m.writeUint32(3, ref());
+ msg(w, F_CREATE_ELEMENT, (m) => {
+ u32(m, 1, newId(true));
+ u32(m, 2, ref());
+ if (rand() < 0.3) u32(m, 3, ref());
})
));
break;
case 1:
frames.push(frame((w) =>
- w.writeMessage(F_CREATE_TEXT, (m) => {
- m.writeUint32(1, newId(false));
- m.writeString(2, "t" + i);
+ msg(w, F_CREATE_TEXT, (m) => {
+ u32(m, 1, newId(false));
+ str(m, 2, "t" + i);
})
));
break;
case 2:
frames.push(
frame((w) =>
- w.writeMessage(
- F_CREATE_PLACEHOLDER,
- (m) => m.writeUint32(1, newId(false)),
- )
+ msg(w, F_CREATE_PLACEHOLDER, (m) => u32(m, 1, newId(false)))
),
);
break;
case 3:
frames.push(frame((w) =>
- w.writeMessage(F_INSERT_BEFORE, (m) => {
- if (rand() < 0.75) m.writeUint32(1, parent());
- m.writeUint32(2, target());
- if (rand() < 0.25) m.writeUint32(3, target());
+ msg(w, F_INSERT_BEFORE, (m) => {
+ if (rand() < 0.75) u32(m, 1, parent());
+ u32(m, 2, target());
+ if (rand() < 0.25) u32(m, 3, target());
})
));
break;
case 4:
frames.push(frame((w) =>
- w.writeMessage(F_INSERT_AFTER, (m) => {
- if (rand() < 0.75) m.writeUint32(1, parent());
- m.writeUint32(2, target());
- m.writeUint32(3, target());
+ msg(w, F_INSERT_AFTER, (m) => {
+ if (rand() < 0.75) u32(m, 1, parent());
+ u32(m, 2, target());
+ u32(m, 3, target());
})
));
break;
@@ -318,123 +321,125 @@ Deno.test("hostile: random op sequences never damage the embedder's DOM", () =>
const at = live.indexOf(gone);
if (at >= 0) live.splice(at, 1);
frames.push(
- frame((w) =>
- w.writeMessage(F_REMOVE, (m) => m.writeUint32(1, gone))
- ),
+ frame((w) => msg(w, F_REMOVE, (m) => u32(m, 1, gone))),
);
break;
}
case 6:
frames.push(frame((w) =>
- w.writeMessage(F_SET_TEXT, (m) => {
- m.writeUint32(1, target());
- m.writeString(2, "x" + i);
+ msg(w, F_SET_TEXT, (m) => {
+ u32(m, 1, target());
+ str(m, 2, "x" + i);
})
));
break;
case 7:
frames.push(frame((w) =>
- w.writeMessage(F_SET_ATTRIBUTE, (m) => {
- m.writeUint32(1, target());
- m.writeUint32(2, ref());
- if (rand() < 0.3) m.writeUint32(3, ref());
- if (rand() < 0.7) m.writeString(4, "v" + i);
+ msg(w, F_SET_ATTRIBUTE, (m) => {
+ u32(m, 1, target());
+ u32(m, 2, ref());
+ if (rand() < 0.3) u32(m, 3, ref());
+ if (rand() < 0.7) str(m, 4, "v" + i);
})
));
break;
case 8:
frames.push(frame((w) =>
- w.writeMessage(F_SET_PROPERTY, (m) => {
- m.writeUint32(1, target());
- m.writeUint32(2, ref());
- if (rand() < 0.5) m.writeString(3, "v" + i);
- else m.writeBool(6, rand() < 0.5);
+ msg(w, F_SET_PROPERTY, (m) => {
+ u32(m, 1, target());
+ u32(m, 2, ref());
+ if (rand() < 0.5) str(m, 3, "v" + i);
+ else bool(m, 6, rand() < 0.5);
})
));
break;
case 9:
- frames.push(frame((w) =>
- w.writeMessage(
- rand() < 0.5 ? F_ADD_LISTENER : F_REMOVE_LISTENER,
- (m) =>
- m.writeMessage(1, (l) => {
- if (rand() < 0.8) l.writeUint32(1, target());
- else l.writeUint32(8, int(3)); // Global, sometimes unknown
- l.writeUint32(2, ref());
- l.writeBool(3, rand() < 0.5);
- }),
- )
- ));
+ frames.push(
+ frame((w) =>
+ msg(
+ w,
+ rand() < 0.5 ? F_ADD_LISTENER : F_REMOVE_LISTENER,
+ (m) =>
+ msg(m, 1, (l) => {
+ if (rand() < 0.8) {
+ u32(l, 1, target());
+ } else u32(l, 8, int(3)); // Global, sometimes unknown
+ u32(l, 2, ref());
+ bool(l, 3, rand() < 0.5);
+ }),
+ )
+ ),
+ );
break;
case 10:
// A template arena with random (often out-of-range or cyclic)
// child indices.
frames.push(frame((w) =>
- w.writeMessage(F_REGISTER_TEMPLATE, (m) => {
- m.writeUint32(1, int(3));
+ msg(w, F_REGISTER_TEMPLATE, (m) => {
+ u32(m, 1, int(3));
const n = 1 + int(4);
for (let k = 0; k < n; k++) {
- m.writeMessage(2, (tn) => {
+ msg(m, 2, (tn) => {
if (rand() < 0.6) {
- tn.writeMessage(1, (el) => {
- el.writeUint32(1, ref());
+ msg(tn, 1, (el) => {
+ u32(el, 1, ref());
if (rand() < 0.5) {
- el.writeMessage(3, (at) => {
- at.writeUint32(1, ref());
- at.writeString(3, "a");
+ msg(el, 3, (at) => {
+ u32(at, 1, ref());
+ str(at, 3, "a");
});
}
for (let c = 0; c < int(3); c++) {
- el.writeUint32(4, int(6));
+ u32(el, 4, int(6));
}
});
- } else if (rand() < 0.5) tn.writeString(2, "t");
- else tn.writeMessage(3, () => {});
+ } else if (rand() < 0.5) str(tn, 2, "t");
+ else msg(tn, 3, () => {});
});
}
- m.writeUint32(3, int(4));
+ u32(m, 3, int(4));
})
));
break;
case 11:
frames.push(frame((w) =>
- w.writeMessage(F_CLONE_TEMPLATE, (m) => {
- m.writeUint32(1, int(4));
- m.writeUint32(2, int(4));
- m.writeUint32(3, newId(true));
+ msg(w, F_CLONE_TEMPLATE, (m) => {
+ u32(m, 1, int(4));
+ u32(m, 2, int(4));
+ u32(m, 3, newId(true));
})
));
break;
case 12:
frames.push(frame((w) =>
- w.writeMessage(F_BIND_PATH, (m) => {
- m.writeUint32(1, target());
+ msg(w, F_BIND_PATH, (m) => {
+ u32(m, 1, target());
// BindPath.path is `bytes`; each step is written as a
// one-byte varint, which is byte-identical to the raw byte
// for values < 128 — the only range used here.
- m.writeMessage(2, (p) => {
- for (let s = 0; s < int(4); s++) p.writeVarint32(int(4));
+ msg(m, 2, (p) => {
+ for (let s = 0; s < int(4); s++) p.uint32(int(4));
});
- m.writeUint32(3, newId(true));
+ u32(m, 3, newId(true));
})
));
break;
case 13:
frames.push(frame((w) =>
- w.writeMessage(F_BIND_MARKER, (m) => {
- m.writeUint32(1, int(4));
- m.writeUint32(2, newId(false));
+ msg(w, F_BIND_MARKER, (m) => {
+ u32(m, 1, int(4));
+ u32(m, 2, newId(false));
})
));
break;
default:
- frames.push(frame((w) => w.writeBool(F_COMMIT, true)));
+ frames.push(frame((w) => bool(w, F_COMMIT, true)));
}
}
// Feed the whole stream in chunks split at random byte boundaries: a
// frame straddling a chunk must behave exactly as one that does not.
- const bytes = concat(frames);
+ const bytes = stream(...frames);
let at = 0;
let threw = false;
while (at < bytes.length && !threw) {
@@ -496,7 +501,7 @@ Deno.test("hostile: byte mutations of basic.pb neither hang nor damage the mount
const at = int(bytes.length);
const junk = new Uint8Array(1 + int(8));
for (let k = 0; k < junk.length; k++) junk[k] = int(256);
- bytes = concat([bytes.subarray(0, at), junk, bytes.subarray(at)]);
+ bytes = stream(bytes.subarray(0, at), junk, bytes.subarray(at));
break;
}
default: { // replace a byte outright (hits length prefixes hardest)
@@ -537,11 +542,9 @@ Deno.test("hostile: byte mutations of basic.pb neither hang nor damage the mount
Deno.test("FrameDecoder: a length prefix over MAX_FRAME_BYTES throws instead of buffering forever", () => {
const h = harness();
- const lp = new Writer();
- lp.writeVarint32(MAX_FRAME_BYTES + 1);
let caught: unknown;
try {
- h.decoder.push(lp.finish());
+ h.decoder.push(new BinaryWriter().uint32(MAX_FRAME_BYTES + 1).finish());
} catch (err) {
caught = err;
}
@@ -552,26 +555,98 @@ Deno.test("FrameDecoder: a length prefix over MAX_FRAME_BYTES throws instead of
);
// A length just under the bound is still "wait for more bytes", not an
// error — the bound is a ceiling, not a size limit on real frames.
- const ok = new Writer();
- ok.writeVarint32(MAX_FRAME_BYTES);
- harness().decoder.push(ok.finish());
+ harness().decoder.push(
+ new BinaryWriter().uint32(MAX_FRAME_BYTES).finish(),
+ );
});
Deno.test("FrameDecoder: a truncated sub-message inside a COMPLETE frame is an error, not a wait", () => {
const h = harness();
// Frame { create_text: }, wrapped
- // in a frame length prefix that is itself correct. Only the length probe
- // in `#drain` may treat a truncation as "wait for more"; inside a frame
- // whose bytes are all present, it is a malformed frame.
+ // in a frame length prefix that is itself correct, and followed by a
+ // perfectly good frame. Only the length probe in `#drain` may treat a
+ // truncation as "wait for more"; inside a frame whose bytes are all
+ // present it is a malformed frame — and the frame AFTER it must not be
+ // applied either, since the throw aborts the stream.
const body = Uint8Array.of((F_CREATE_TEXT << 3) | 2, 20, 0x08, 0x01);
- const lp = new Writer();
- lp.writeVarint32(body.length);
+ const good = frame((w) =>
+ msg(w, F_CREATE_TEXT, (m) => {
+ u32(m, 1, 9);
+ str(m, 2, "ok");
+ })
+ );
let caught: unknown;
try {
- h.decoder.push(concat([lp.finish(), body]));
+ h.decoder.push(stream(framed(body), good));
} catch (err) {
caught = err;
}
assert(caught instanceof Error, "expected an Error, got " + String(caught));
+ assert(
+ h.recv.resolveNode(9) === undefined,
+ "the frame after the malformed one was applied",
+ );
assertMountIntact(h, "truncated sub-message");
});
+
+Deno.test("FrameDecoder: a sub-message length that overruns the frame does not read into the next frame", () => {
+ const h = harness();
+ // `Frame { create_text: }`: the frame's own length prefix is honest about those 6
+ // bytes, so the over-long inner length can only be satisfied by reading
+ // the FOLLOWING frames' bytes. Without a per-frame bound the decoder
+ // does exactly that and then resumes at the right place, applying both
+ // frames from bytes that were never one frame.
+ const overrun = frame((w) => {
+ w.tag(F_CREATE_TEXT, WireType.LengthDelimited).uint32(16).raw(
+ Uint8Array.of(0x08, 0x05, 0x12, 0x02, 0x68, 0x69), // id 5, text "hi"
+ );
+ });
+ const good = frame((w) => {
+ msg(w, F_CREATE_TEXT, (m) => {
+ u32(m, 1, 9);
+ str(m, 2, "abcdefghijklmnop");
+ });
+ bool(w, F_COMMIT, true);
+ });
+ let caught: unknown;
+ try {
+ h.decoder.push(stream(overrun, good));
+ } catch (err) {
+ caught = err;
+ }
+ assert(caught instanceof Error, "expected an Error, got " + String(caught));
+ assert(
+ h.recv.resolveNode(5) === undefined,
+ "the malformed frame reached the sink",
+ );
+ assert(
+ h.recv.resolveNode(9) === undefined,
+ "the frame after the malformed one was applied",
+ );
+ assertMountIntact(h, "over-long sub-message length");
+});
+
+Deno.test("FrameDecoder: a frame body that stops short of its own length is an error in both modes", () => {
+ // `Frame { commit: true }` followed by a zero tag and junk: the
+ // generated decoder stops at the zero tag WITHOUT throwing, leaving the
+ // reader inside the frame. Field number 0 does not exist in protobuf,
+ // so this is malformed wire, not a skippable unknown — rejected in
+ // strict and open mode alike.
+ const body = Uint8Array.of(0x08, 0x01, 0x00, 0xff, 0xff, 0xff);
+ for (const strict of [false, true]) {
+ const h = harness();
+ const decoder = new FrameDecoder(h.recv.sink, { strict });
+ let caught: unknown;
+ try {
+ decoder.push(framed(body));
+ } catch (err) {
+ caught = err;
+ }
+ assert(
+ caught instanceof Error,
+ `strict=${strict}: expected an Error, got ${String(caught)}`,
+ );
+ assertMountIntact(h, `short frame body (strict=${strict})`);
+ }
+});
diff --git a/receiver/tests/policy_desktop_test.ts b/receiver/tests/policy_desktop_test.ts
index 83ad7f6..8675663 100644
--- a/receiver/tests/policy_desktop_test.ts
+++ b/receiver/tests/policy_desktop_test.ts
@@ -10,7 +10,8 @@ import { parseHTML } from "linkedom";
import { createDriver } from "../src/driver.ts";
import { PolicyError } from "../src/policy.ts";
import { desktopPolicy } from "../src/policy-desktop.ts";
-import { Writer } from "../src/proto.ts";
+import { Frame } from "../src/gen/stream-dom.ts";
+import { frame as framed, stream } from "./wire.ts";
// -- unit harness ----------------------------------------------------------
@@ -749,80 +750,28 @@ Deno.test("desktopPolicy: maxListeners exhausts after that many addListener chec
// -- whole-stream, through createDriver + linkedom --------------------------
//
-// Reuses the frame-encoder pattern from policy_test.ts / driver_test.ts:
-// a `Writer`-backed length-delimited `Frame` builder, rather than a new
-// encoder.
-
-const FRAME_SET_ATTRIBUTE = 4;
-const FRAME_CREATE_ELEMENT = 6;
-const FRAME_INSERT_BEFORE = 2;
-const FRAME_ADD_LISTENER = 12;
-const FRAME_INTERN = 14;
-const FRAME_COMMIT = 1;
-
-const INTERN_ID = 1;
-const INTERN_S = 2;
-const CREATE_ELEMENT_ID = 1;
-const CREATE_ELEMENT_TAG = 2;
-const INSERT_BEFORE_PARENT = 1;
-const INSERT_BEFORE_ID = 2;
-const SET_ATTRIBUTE_ID = 1;
-const SET_ATTRIBUTE_NAME = 2;
-const SET_ATTRIBUTE_TEXT = 4;
-const LISTENER_ID = 1;
-const LISTENER_NAME = 2;
-const LISTENER_BUBBLES = 3;
-const ADD_LISTENER_LISTENER = 1;
-
-function frame(commit: boolean, build?: (w: Writer) => void): Uint8Array {
- const inner = new Writer();
- if (commit) inner.writeBool(FRAME_COMMIT, true);
- build?.(inner);
- const body = inner.finish();
- const lenW = new Writer();
- lenW.writeVarint32(body.length);
- const lenBytes = lenW.finish();
- const out = new Uint8Array(lenBytes.length + body.length);
- out.set(lenBytes, 0);
- out.set(body, lenBytes.length);
- return out;
-}
+// Reuses the frame-builder pattern from policy_test.ts / driver_test.ts:
+// the generated `Frame` writer plus the shared length prefix in wire.ts.
-function concat(chunks: Uint8Array[]): Uint8Array {
- const total = chunks.reduce((n, c) => n + c.length, 0);
- const out = new Uint8Array(total);
- let off = 0;
- for (const c of chunks) {
- out.set(c, off);
- off += c.length;
- }
- return out;
+function frame(commit: boolean, op?: Frame["op"]): Uint8Array {
+ return framed(Frame.encode({ commit, op }).finish());
}
function internFrame(id: number, s: string): Uint8Array {
- return frame(false, (w) => {
- w.writeMessage(FRAME_INTERN, (m) => {
- m.writeUint32(INTERN_ID, id);
- m.writeString(INTERN_S, s);
- });
- });
+ return frame(false, { $case: "intern", value: { id, s } });
}
function createElementFrame(id: number, tagRef: number): Uint8Array {
- return frame(false, (w) => {
- w.writeMessage(FRAME_CREATE_ELEMENT, (m) => {
- m.writeUint32(CREATE_ELEMENT_ID, id);
- m.writeUint32(CREATE_ELEMENT_TAG, tagRef);
- });
+ return frame(false, {
+ $case: "createElement",
+ value: { id, tag: tagRef, ns: undefined },
});
}
function insertBeforeFrame(parent: number, id: number): Uint8Array {
- return frame(false, (w) => {
- w.writeMessage(FRAME_INSERT_BEFORE, (m) => {
- m.writeUint32(INSERT_BEFORE_PARENT, parent);
- m.writeUint32(INSERT_BEFORE_ID, id);
- });
+ return frame(false, {
+ $case: "insertBefore",
+ value: { parent, id, anchor: undefined },
});
}
@@ -831,24 +780,31 @@ function setAttributeTextFrame(
nameRef: number,
text: string,
): Uint8Array {
- return frame(false, (w) => {
- w.writeMessage(FRAME_SET_ATTRIBUTE, (m) => {
- m.writeUint32(SET_ATTRIBUTE_ID, id);
- m.writeUint32(SET_ATTRIBUTE_NAME, nameRef);
- m.writeString(SET_ATTRIBUTE_TEXT, text);
- });
+ return frame(false, {
+ $case: "setAttribute",
+ value: {
+ id,
+ name: nameRef,
+ ns: undefined,
+ value: { $case: "text", value: text },
+ },
});
}
function addListenerFrame(id: number, nameRef: number): Uint8Array {
- return frame(true, (w) => {
- w.writeMessage(FRAME_ADD_LISTENER, (m) => {
- m.writeMessage(ADD_LISTENER_LISTENER, (l) => {
- l.writeUint32(LISTENER_ID, id);
- l.writeUint32(LISTENER_NAME, nameRef);
- l.writeBool(LISTENER_BUBBLES, true);
- });
- });
+ return frame(true, {
+ $case: "addListener",
+ value: {
+ listener: {
+ target: { $case: "id", value: id },
+ name: nameRef,
+ bubbles: true,
+ capture: false,
+ passive: false,
+ preventDefault: false,
+ stopPropagation: false,
+ },
+ },
});
}
@@ -871,7 +827,7 @@ Deno.test("desktopPolicy: a div > form > input + button stream with a submit lis
handleEvent: () => {},
});
- driver.push(concat([
+ driver.push(stream(
internFrame(DIV, "div"),
internFrame(FORM, "form"),
internFrame(INPUT, "input"),
@@ -886,7 +842,7 @@ Deno.test("desktopPolicy: a div > form > input + button stream with a submit lis
createElementFrame(13, BUTTON),
insertBeforeFrame(11, 13),
addListenerFrame(11, SUBMIT),
- ]));
+ ));
assertEquals(
root.innerHTML,
@@ -904,13 +860,13 @@ Deno.test("desktopPolicy: an with a javascript: text value is rejected
assertThrows(
() =>
- driver.push(concat([
+ driver.push(stream(
internFrame(A, "a"),
internFrame(HREF, "href"),
createElementFrame(20, A),
insertBeforeFrame(0, 20),
setAttributeTextFrame(20, HREF, "javascript:alert(1)"),
- ])),
+ )),
PolicyError,
"asset handle",
);
diff --git a/receiver/tests/policy_test.ts b/receiver/tests/policy_test.ts
index f17d8a6..a175af9 100644
--- a/receiver/tests/policy_test.ts
+++ b/receiver/tests/policy_test.ts
@@ -14,7 +14,9 @@ import type {
} from "../src/frames.ts";
import { assertPolicyVersion, PolicyError, PolicySink } from "../src/policy.ts";
import type { Policy, PolicyOp } from "../src/policy.ts";
-import { Writer } from "../src/proto.ts";
+import { BinaryWriter } from "@bufbuild/protobuf/wire";
+import { Frame } from "../src/gen/stream-dom.ts";
+import { frame } from "./wire.ts";
import { RemoteDomTranscoder } from "../src/remote.ts";
import type { RemoteConnection, RemoteMutationRecord } from "@remote-dom/core";
@@ -97,18 +99,10 @@ class RecordingSink implements FrameSink {
}
}
-/** One length-delimited `Frame` on the wire. */
-function frame(build: (w: Writer) => void): Uint8Array {
- const body = new Writer();
- build(body);
- const bytes = body.finish();
- const out = new Writer();
- out.writeVarint32(bytes.length);
- const prefix = out.finish();
- const framed = new Uint8Array(prefix.length + bytes.length);
- framed.set(prefix, 0);
- framed.set(bytes, prefix.length);
- return framed;
+/** One length-delimited `Frame` on the wire, from a generated-writer
+ * body. */
+function wellFormed(op: Frame["op"], commit = false): Uint8Array {
+ return frame(Frame.encode({ commit, op }).finish());
}
/** A policy that records what it saw and rejects whatever `reject` says. */
@@ -153,10 +147,10 @@ Deno.test("PROTOCOL_VERSION matches proto/stream-dom.proto's header line", async
Deno.test("strict decoder rejects an unknown Frame op field; open decoder skips it", () => {
// Field 30 is not an op this receiver knows (Frame's ops stop at 17).
- const bytes = frame((w) => {
- w.writeBool(1, true); // Frame.commit
- w.writeMessage(30, (unknown) => unknown.writeUint32(1, 5));
- });
+ const body = new BinaryWriter();
+ body.uint32((1 << 3) | 0).bool(true); // Frame.commit
+ body.uint32((30 << 3) | 2).fork().uint32((1 << 3) | 0).uint32(5).join();
+ const bytes = frame(body.finish());
const open = new RecordingSink();
new FrameDecoder(open).push(bytes);
@@ -172,13 +166,13 @@ Deno.test("strict decoder rejects an unknown Frame op field; open decoder skips
Deno.test("strict decoder rejects an unknown sub-message field; open decoder skips it", () => {
// CreateElement { id: 7, tag: 1, }.
- const bytes = frame((w) => {
- w.writeMessage(6, (ce) => {
- ce.writeUint32(1, 7);
- ce.writeUint32(2, 1);
- ce.writeUint32(9, 123);
- });
- });
+ const body = new BinaryWriter();
+ body.uint32((6 << 3) | 2).fork()
+ .uint32((1 << 3) | 0).uint32(7)
+ .uint32((2 << 3) | 0).uint32(1)
+ .uint32((9 << 3) | 0).uint32(123)
+ .join();
+ const bytes = frame(body.finish());
const open = new RecordingSink();
new FrameDecoder(open).push(bytes);
@@ -195,11 +189,15 @@ Deno.test("strict decoder rejects an unknown sub-message field; open decoder ski
});
Deno.test("an unknown Global enum value is rejected in both modes", () => {
- const bytes = frame((w) => {
- w.writeMessage(12, (add) => {
- add.writeMessage(1, (l) => l.writeUint32(8, 99)); // Listener.global = 99
- });
- });
+ // Listener.global = 99, a value outside the `Global` enum: built by hand
+ // because the generated writer's `Global` type admits only the two.
+ const body = new BinaryWriter();
+ body.uint32((12 << 3) | 2).fork()
+ .uint32((1 << 3) | 2).fork()
+ .uint32((8 << 3) | 0).int32(99)
+ .join()
+ .join();
+ const bytes = frame(body.finish());
for (const strict of [false, true]) {
assertThrows(
() => new FrameDecoder(new RecordingSink(), { strict }).push(bytes),
@@ -213,14 +211,14 @@ Deno.test("an unknown Global enum value is rejected in both modes", () => {
Deno.test("SetAttribute's asset arm (field 5) decodes to an asset handle", () => {
const handle = Uint8Array.of(0, 1, 2, 3);
- const bytes = frame((w) => {
- w.writeMessage(4, (sa) => {
- sa.writeUint32(1, 10); // id
- sa.writeUint32(2, 3); // name
- sa.writeTag(5, 2); // SetAttribute.asset
- sa.writeVarint32(handle.length);
- for (const b of handle) sa.writeVarint32(b);
- });
+ const bytes = wellFormed({
+ $case: "setAttribute",
+ value: {
+ id: 10,
+ name: 3,
+ ns: undefined,
+ value: { $case: "asset", value: handle },
+ },
});
const sink = new RecordingSink();
new FrameDecoder(sink).push(bytes);
@@ -235,22 +233,27 @@ Deno.test("SetAttribute's asset arm (field 5) decodes to an asset handle", () =>
Deno.test("TemplateAttr's asset arm (field 4) decodes to an asset handle", () => {
const handle = Uint8Array.of(9, 8);
- const bytes = frame((w) => {
- w.writeMessage(15, (rt) => {
- rt.writeUint32(1, 1); // RegisterTemplate.id
- rt.writeMessage(2, (node) => {
- node.writeMessage(1, (el) => {
- el.writeUint32(1, 1); // TemplateElement.tag
- el.writeMessage(3, (attr) => {
- attr.writeUint32(1, 3); // TemplateAttr.name
- attr.writeTag(4, 2); // TemplateAttr.asset
- attr.writeVarint32(handle.length);
- for (const b of handle) attr.writeVarint32(b);
- });
- });
- });
- rt.writeUint32(3, 0); // roots = [0]
- });
+ const bytes = wellFormed({
+ $case: "registerTemplate",
+ value: {
+ id: 1,
+ nodes: [{
+ kind: {
+ $case: "element",
+ value: {
+ tag: 1,
+ ns: undefined,
+ attrs: [{
+ name: 3,
+ ns: undefined,
+ value: { $case: "asset", value: handle },
+ }],
+ children: [],
+ },
+ },
+ }],
+ roots: [0],
+ },
});
const sink = new RecordingSink();
new FrameDecoder(sink).push(bytes);
diff --git a/receiver/tests/proto_test.ts b/receiver/tests/proto_test.ts
deleted file mode 100644
index 4a92816..0000000
--- a/receiver/tests/proto_test.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { assertEquals, assertThrows } from "@std/assert";
-import { Reader, TruncatedError, WireType, Writer } from "../src/proto.ts";
-
-Deno.test("varint round trip", () => {
- for (const v of [0, 1, 127, 128, 300, 16384, 0xffffffff >>> 0]) {
- const w = new Writer();
- w.writeVarint32(v);
- const r = new Reader(w.finish());
- assertEquals(r.readVarint32(), v);
- }
-});
-
-Deno.test("sint32 zigzag round trip", () => {
- for (const v of [0, 1, -1, 2, -2, 2147483647, -2147483648]) {
- const w = new Writer();
- w.writeSInt32(v);
- const r = new Reader(w.finish());
- assertEquals(r.readSInt32(), v);
- }
-});
-
-Deno.test("bool, double, string round trip", () => {
- const w = new Writer();
- w.writeBool(1, true);
- w.writeDouble(2, 3.5);
- w.writeString(3, "hello, world");
- const r = new Reader(w.finish());
- const [f1, wt1] = r.readTag();
- assertEquals(f1, 1);
- assertEquals(wt1, WireType.Varint);
- assertEquals(r.readBool(), true);
- const [f2, wt2] = r.readTag();
- assertEquals(f2, 2);
- assertEquals(wt2, WireType.Fixed64);
- assertEquals(r.readDouble(), 3.5);
- const [f3, wt3] = r.readTag();
- assertEquals(f3, 3);
- assertEquals(wt3, WireType.LengthDelimited);
- assertEquals(r.readString(), "hello, world");
- assertEquals(r.finished(), true);
-});
-
-Deno.test("embedded message with length prefix", () => {
- const w = new Writer();
- w.writeMessage(1, (m) => {
- m.writeUint32(1, 42);
- m.writeString(2, "inner");
- });
- const r = new Reader(w.finish());
- const [field, wireType] = r.readTag();
- assertEquals(field, 1);
- assertEquals(wireType, WireType.LengthDelimited);
- const sub = r.readMessage();
- const [f1] = sub.readTag();
- assertEquals(f1, 1);
- assertEquals(sub.readVarint32(), 42);
- const [f2] = sub.readTag();
- assertEquals(f2, 2);
- assertEquals(sub.readString(), "inner");
- assertEquals(sub.finished(), true);
- assertEquals(r.finished(), true);
-});
-
-Deno.test("skip by wire type: varint, fixed64, length-delimited, fixed32", () => {
- const w = new Writer();
- w.writeUint32(1, 999); // varint
- w.writeDouble(2, 1.25); // fixed64
- w.writeString(3, "skip me"); // length-delimited
- w.writeFloat(4, 2.5); // fixed32
- w.writeUint32(5, 7); // a field we actually read, to prove skip advanced correctly
- const r = new Reader(w.finish());
- for (let i = 0; i < 4; i++) {
- const [, wt] = r.readTag();
- r.skip(wt);
- }
- const [field] = r.readTag();
- assertEquals(field, 5);
- assertEquals(r.readVarint32(), 7);
- assertEquals(r.finished(), true);
-});
-
-Deno.test("readVarint32 rejects truncated input", () => {
- const buf = Uint8Array.of(0x80); // continuation bit set, no follow-up byte
- const r = new Reader(buf);
- assertThrows(() => r.readVarint32());
-});
-
-Deno.test("readVarint32 throws TruncatedError specifically when bytes run out", () => {
- const buf = Uint8Array.of(0x80, 0x80); // still continuing, buffer just ends
- const r = new Reader(buf);
- assertThrows(() => r.readVarint32(), TruncatedError);
-});
-
-Deno.test("readVarint32 throws a plain (non-Truncated) error for a malformed varint that is fully present", () => {
- // Every byte present, continuation bit set throughout — never
- // terminates within the legal width for a u32. This is NOT a buffering
- // situation (the bytes are all there); FrameDecoder must not treat it
- // as "wait for more".
- const buf = Uint8Array.of(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
- const r = new Reader(buf);
- let threw: unknown;
- try {
- r.readVarint32();
- } catch (e) {
- threw = e;
- }
- if (!(threw instanceof RangeError) || threw instanceof TruncatedError) {
- throw new Error(`expected a plain RangeError, got ${String(threw)}`);
- }
-});
-
-Deno.test("sint64 zigzag round trip via SetProperty-shaped field", () => {
- for (const v of [0, 1, -1, 1000000, -1000000]) {
- const w = new Writer();
- w.writeTag(4, WireType.Varint);
- // sint64 has no dedicated Writer method (not needed by encodePayload),
- // so round-trip it through the sint32 zigzag encoding for values that
- // fit — readSInt64 must decode the same zigzag scheme regardless of
- // width.
- w.writeSInt32(v);
- const r = new Reader(w.finish());
- r.readTag();
- assertEquals(r.readSInt64(), v);
- }
-});
diff --git a/receiver/tests/wire.ts b/receiver/tests/wire.ts
new file mode 100644
index 0000000..e2fae32
--- /dev/null
+++ b/receiver/tests/wire.ts
@@ -0,0 +1,25 @@
+// Length-prefixed framing for test fixtures: the stream layout
+// proto/stream-dom.proto's header describes (varint byte length, then one
+// Frame, repeated). Frame bodies come from the generated writers
+// (`Frame.encode(...).finish()`) for well-formed fixtures and from
+// `BinaryWriter` directly for the malformed ones.
+
+import { BinaryWriter } from "@bufbuild/protobuf/wire";
+
+/** Prepend `bytes`'s length as a varint32 — one frame on the wire. */
+export function frame(bytes: Uint8Array): Uint8Array {
+ return new BinaryWriter().uint32(bytes.length).raw(bytes).finish();
+}
+
+/** Concatenate already-framed byte runs. */
+export function stream(...frames: Uint8Array[]): Uint8Array {
+ let total = 0;
+ for (const f of frames) total += f.length;
+ const out = new Uint8Array(total);
+ let at = 0;
+ for (const f of frames) {
+ out.set(f, at);
+ at += f.length;
+ }
+ return out;
+}