Skip to content

Commit 676c01d

Browse files
authored
fix: encode enum values (#30)
Previously we were encoding enum keys which is wrong, we should be encoding values.
1 parent d85a9f4 commit 676c01d

File tree

8 files changed

+174
-36
lines changed

8 files changed

+174
-36
lines changed

packages/protons-benchmark/package.json

+2-4
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,17 @@
6262
"clean": "aegir clean",
6363
"lint": "aegir lint",
6464
"dep-check": "aegir dep-check",
65-
"build": "aegir build && cp -R src/protobufjs dist/src/protobufjs",
65+
"build": "aegir build --no-bundle && cp -R src/protobufjs dist/src/protobufjs",
6666
"prestart": "npm run build",
6767
"start": "node dist/src/index.js"
6868
},
6969
"dependencies": {
70+
"aegir": "^37.0.5",
7071
"benny": "^3.7.1",
7172
"pbjs": "^0.0.14",
7273
"protobufjs": "^6.11.2",
7374
"protons": "^2.0.0",
7475
"protons-runtime": "^0.0.0"
7576
},
76-
"devDependencies": {
77-
"aegir": "^37.0.5"
78-
},
7977
"private": true
8078
}

packages/protons-runtime/src/codecs/enum.ts

+26-16
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,43 @@ import { unsigned } from '../utils/varint.js'
33
import { createCodec, CODEC_TYPES } from '../codec.js'
44
import type { DecodeFunction, EncodeFunction, EncodingLengthFunction, Codec } from '../codec.js'
55

6-
export function enumeration <T> (e: T): Codec<T> {
7-
const encodingLength: EncodingLengthFunction<string> = function enumEncodingLength (val: string) {
8-
const keys = Object.keys(e)
9-
const index = keys.indexOf(val)
6+
export function enumeration <T> (v: any): Codec<T> {
7+
function findValue (val: string | number): number {
8+
if (v[val.toString()] == null) {
9+
throw new Error('Invalid enum value')
10+
}
11+
12+
if (typeof val === 'number') {
13+
return val
14+
}
15+
16+
return v[val]
17+
}
1018

11-
return unsigned.encodingLength(index)
19+
const encodingLength: EncodingLengthFunction<number | string> = function enumEncodingLength (val) {
20+
return unsigned.encodingLength(findValue(val))
1221
}
1322

14-
const encode: EncodeFunction<string> = function enumEncode (val) {
15-
const keys = Object.keys(e)
16-
const index = keys.indexOf(val)
17-
const buf = new Uint8Array(unsigned.encodingLength(index))
23+
const encode: EncodeFunction<number | string> = function enumEncode (val) {
24+
const enumValue = findValue(val)
1825

19-
unsigned.encode(index, buf)
26+
const buf = new Uint8Array(unsigned.encodingLength(enumValue))
27+
unsigned.encode(enumValue, buf)
2028

2129
return buf
2230
}
2331

24-
const decode: DecodeFunction<string> = function enumDecode (buf, offset) {
25-
const index = unsigned.decode(buf, offset)
26-
const keys = Object.keys(e)
32+
const decode: DecodeFunction<number | string> = function enumDecode (buf, offset) {
33+
const value = unsigned.decode(buf, offset)
34+
const strValue = value.toString()
2735

28-
if (keys[index] == null) {
29-
throw new Error('Could not find enum key for value')
36+
// Use the reverse mapping to look up the enum key for the stored value
37+
// https://www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings
38+
if (v[strValue] == null) {
39+
throw new Error('Invalid enum value')
3040
}
3141

32-
return keys[index]
42+
return v[strValue]
3343
}
3444

3545
// @ts-expect-error yeah yeah

packages/protons/src/index.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,23 @@ function compileMessage (messageDef: MessageDef, moduleDef: ModuleDef): string {
131131
return `
132132
export enum ${messageDef.name} {
133133
${
134-
Object.keys(messageDef.values).map(enumValueName => {
135-
return `${enumValueName} = '${enumValueName}'`
134+
Object.keys(messageDef.values).map(name => {
135+
return `${name} = '${name}'`
136+
}).join(',\n ').trim()
137+
}
138+
}
139+
140+
enum __${messageDef.name}Values {
141+
${
142+
Object.entries(messageDef.values).map(([name, value]) => {
143+
return `${name} = ${value}`
136144
}).join(',\n ').trim()
137145
}
138146
}
139147
140148
export namespace ${messageDef.name} {
141149
export const codec = () => {
142-
return enumeration<typeof ${messageDef.name}>(${messageDef.name})
150+
return enumeration<typeof ${messageDef.name}>(__${messageDef.name}Values)
143151
}
144152
}`.trim()
145153
}

packages/protons/test/fixtures/circuit.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,28 @@ export namespace CircuitRelay {
3131
MALFORMED_MESSAGE = 'MALFORMED_MESSAGE'
3232
}
3333

34+
enum __StatusValues {
35+
SUCCESS = 100,
36+
HOP_SRC_ADDR_TOO_LONG = 220,
37+
HOP_DST_ADDR_TOO_LONG = 221,
38+
HOP_SRC_MULTIADDR_INVALID = 250,
39+
HOP_DST_MULTIADDR_INVALID = 251,
40+
HOP_NO_CONN_TO_DST = 260,
41+
HOP_CANT_DIAL_DST = 261,
42+
HOP_CANT_OPEN_DST_STREAM = 262,
43+
HOP_CANT_SPEAK_RELAY = 270,
44+
HOP_CANT_RELAY_TO_SELF = 280,
45+
STOP_SRC_ADDR_TOO_LONG = 320,
46+
STOP_DST_ADDR_TOO_LONG = 321,
47+
STOP_SRC_MULTIADDR_INVALID = 350,
48+
STOP_DST_MULTIADDR_INVALID = 351,
49+
STOP_RELAY_REFUSED = 390,
50+
MALFORMED_MESSAGE = 400
51+
}
52+
3453
export namespace Status {
3554
export const codec = () => {
36-
return enumeration<typeof Status>(Status)
55+
return enumeration<typeof Status>(__StatusValues)
3756
}
3857
}
3958

@@ -44,9 +63,16 @@ export namespace CircuitRelay {
4463
CAN_HOP = 'CAN_HOP'
4564
}
4665

66+
enum __TypeValues {
67+
HOP = 1,
68+
STOP = 2,
69+
STATUS = 3,
70+
CAN_HOP = 4
71+
}
72+
4773
export namespace Type {
4874
export const codec = () => {
49-
return enumeration<typeof Type>(Type)
75+
return enumeration<typeof Type>(__TypeValues)
5076
}
5177
}
5278

packages/protons/test/fixtures/daemon.ts

+61-7
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,22 @@ export namespace Request {
3030
PEERSTORE = 'PEERSTORE'
3131
}
3232

33+
enum __TypeValues {
34+
IDENTIFY = 0,
35+
CONNECT = 1,
36+
STREAM_OPEN = 2,
37+
STREAM_HANDLER = 3,
38+
DHT = 4,
39+
LIST_PEERS = 5,
40+
CONNMANAGER = 6,
41+
DISCONNECT = 7,
42+
PUBSUB = 8,
43+
PEERSTORE = 9
44+
}
45+
3346
export namespace Type {
3447
export const codec = () => {
35-
return enumeration<typeof Type>(Type)
48+
return enumeration<typeof Type>(__TypeValues)
3649
}
3750
}
3851

@@ -76,9 +89,14 @@ export namespace Response {
7689
ERROR = 'ERROR'
7790
}
7891

92+
enum __TypeValues {
93+
OK = 0,
94+
ERROR = 1
95+
}
96+
7997
export namespace Type {
8098
export const codec = () => {
81-
return enumeration<typeof Type>(Type)
99+
return enumeration<typeof Type>(__TypeValues)
82100
}
83101
}
84102

@@ -263,9 +281,21 @@ export namespace DHTRequest {
263281
PROVIDE = 'PROVIDE'
264282
}
265283

284+
enum __TypeValues {
285+
FIND_PEER = 0,
286+
FIND_PEERS_CONNECTED_TO_PEER = 1,
287+
FIND_PROVIDERS = 2,
288+
GET_CLOSEST_PEERS = 3,
289+
GET_PUBLIC_KEY = 4,
290+
GET_VALUE = 5,
291+
SEARCH_VALUE = 6,
292+
PUT_VALUE = 7,
293+
PROVIDE = 8
294+
}
295+
266296
export namespace Type {
267297
export const codec = () => {
268-
return enumeration<typeof Type>(Type)
298+
return enumeration<typeof Type>(__TypeValues)
269299
}
270300
}
271301

@@ -303,9 +333,15 @@ export namespace DHTResponse {
303333
END = 'END'
304334
}
305335

336+
enum __TypeValues {
337+
BEGIN = 0,
338+
VALUE = 1,
339+
END = 2
340+
}
341+
306342
export namespace Type {
307343
export const codec = () => {
308-
return enumeration<typeof Type>(Type)
344+
return enumeration<typeof Type>(__TypeValues)
309345
}
310346
}
311347

@@ -362,9 +398,15 @@ export namespace ConnManagerRequest {
362398
TRIM = 'TRIM'
363399
}
364400

401+
enum __TypeValues {
402+
TAG_PEER = 0,
403+
UNTAG_PEER = 1,
404+
TRIM = 2
405+
}
406+
365407
export namespace Type {
366408
export const codec = () => {
367-
return enumeration<typeof Type>(Type)
409+
return enumeration<typeof Type>(__TypeValues)
368410
}
369411
}
370412

@@ -420,9 +462,16 @@ export namespace PSRequest {
420462
SUBSCRIBE = 'SUBSCRIBE'
421463
}
422464

465+
enum __TypeValues {
466+
GET_TOPICS = 0,
467+
LIST_PEERS = 1,
468+
PUBLISH = 2,
469+
SUBSCRIBE = 3
470+
}
471+
423472
export namespace Type {
424473
export const codec = () => {
425-
return enumeration<typeof Type>(Type)
474+
return enumeration<typeof Type>(__TypeValues)
426475
}
427476
}
428477

@@ -507,9 +556,14 @@ export namespace PeerstoreRequest {
507556
GET_PEER_INFO = 'GET_PEER_INFO'
508557
}
509558

559+
enum __TypeValues {
560+
GET_PROTOCOLS = 1,
561+
GET_PEER_INFO = 2
562+
}
563+
510564
export namespace Type {
511565
export const codec = () => {
512-
return enumeration<typeof Type>(Type)
566+
return enumeration<typeof Type>(__TypeValues)
513567
}
514568
}
515569

packages/protons/test/fixtures/dht.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,18 @@ export namespace Message {
5151
PING = 'PING'
5252
}
5353

54+
enum __MessageTypeValues {
55+
PUT_VALUE = 0,
56+
GET_VALUE = 1,
57+
ADD_PROVIDER = 2,
58+
GET_PROVIDERS = 3,
59+
FIND_NODE = 4,
60+
PING = 5
61+
}
62+
5463
export namespace MessageType {
5564
export const codec = () => {
56-
return enumeration<typeof MessageType>(MessageType)
65+
return enumeration<typeof MessageType>(__MessageTypeValues)
5766
}
5867
}
5968

@@ -64,9 +73,16 @@ export namespace Message {
6473
CANNOT_CONNECT = 'CANNOT_CONNECT'
6574
}
6675

76+
enum __ConnectionTypeValues {
77+
NOT_CONNECTED = 0,
78+
CONNECTED = 1,
79+
CAN_CONNECT = 2,
80+
CANNOT_CONNECT = 3
81+
}
82+
6783
export namespace ConnectionType {
6884
export const codec = () => {
69-
return enumeration<typeof ConnectionType>(ConnectionType)
85+
return enumeration<typeof ConnectionType>(__ConnectionTypeValues)
7086
}
7187
}
7288

packages/protons/test/fixtures/test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ export enum AnEnum {
99
DERP = 'DERP'
1010
}
1111

12+
enum __AnEnumValues {
13+
HERP = 0,
14+
DERP = 1
15+
}
16+
1217
export namespace AnEnum {
1318
export const codec = () => {
14-
return enumeration<typeof AnEnum>(AnEnum)
19+
return enumeration<typeof AnEnum>(__AnEnumValues)
1520
}
1621
}
1722
export interface SubMessage {

packages/protons/test/index.spec.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import pbjs from 'pbjs'
66
import { Basic } from './fixtures/basic.js'
77
import { AllTheTypes, AnEnum } from './fixtures/test.js'
88
import fs from 'fs'
9-
import protobufjs from 'protobufjs'
9+
import protobufjs, { Type as PBType } from 'protobufjs'
1010
import { Peer } from './fixtures/peer.js'
11+
import { CircuitRelay } from './fixtures/circuit.js'
1112

1213
const Long = protobufjs.util.Long
1314

@@ -160,4 +161,24 @@ describe('encode', () => {
160161
expect(Peer.decode(encoded)).to.deep.equal(peer)
161162
expect(Peer.decode(pbjsBuf)).to.deep.equal(peer)
162163
})
164+
165+
it('decodes enums with values that are not 0-n', () => {
166+
const message: CircuitRelay = {
167+
type: CircuitRelay.Type.STOP,
168+
code: CircuitRelay.Status.HOP_NO_CONN_TO_DST
169+
}
170+
171+
const root = protobufjs.loadSync('./test/fixtures/circuit.proto')
172+
// @ts-expect-error
173+
const PbCircuitRelay = root.nested.CircuitRelay as PBType
174+
175+
const pbufJsBuf = PbCircuitRelay.encode(PbCircuitRelay.fromObject(message)).finish()
176+
177+
const encoded = CircuitRelay.encode(message)
178+
179+
expect(encoded).to.equalBytes(pbufJsBuf)
180+
181+
expect(CircuitRelay.decode(encoded)).to.deep.equal(message)
182+
expect(CircuitRelay.decode(pbufJsBuf)).to.deep.equal(message)
183+
})
163184
})

0 commit comments

Comments
 (0)