• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LukaJCB / ts-mls / 20546506344

28 Dec 2025 12:42AM UTC coverage: 96.121% (-0.1%) from 96.237%
20546506344

push

github

LukaJCB
Deprecate json codec for group state

1239 of 1388 branches covered (89.27%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

16 existing lines in 5 files now uncovered.

7162 of 7352 relevant lines covered (97.42%)

44152.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.51
/src/codec/variableLength.ts
1
import { CodecError } from "../mlsError.js"
1✔
2
import { base64ToBytes, bytesToBase64 } from "../util/byteArray.js"
1✔
3
import { decodeUint64, uint64Encoder } from "./number.js"
1✔
4
import { Decoder, mapDecoder, mapDecoders } from "./tlsDecoder.js"
1✔
5
import { BufferEncoder, contramapBufferEncoder, contramapBufferEncoders } from "./tlsEncoder.js"
1✔
6

7
export const varLenDataEncoder: BufferEncoder<Uint8Array> = (data) => {
1✔
8
  const [len, write] = lengthEncoder(data.length)
181,957✔
9

10
  return [
181,957✔
11
    len + data.length,
181,957✔
12
    (offset, buffer) => {
181,957✔
13
      write(offset, buffer)
1,696,099✔
14
      const view = new Uint8Array(buffer)
1,696,099✔
15
      view.set(data, offset + len)
1,696,099✔
16
    },
1,696,099✔
17
  ]
181,957✔
18
}
181,957✔
19

20
export function lengthEncoder(len: number): [number, (offset: number, buffer: ArrayBuffer) => void] {
1✔
21
  if (len < 64) {
3,354,300✔
22
    return [
2,661,533✔
23
      1,
2,661,533✔
24
      (offset, buffer) => {
2,661,533✔
25
        // 1-byte length: 00xxxxxx
26
        const view = new DataView(buffer)
2,661,533✔
27
        view.setUint8(offset, len & 0b00111111)
2,661,533✔
28
      },
2,661,533✔
29
    ]
2,661,533✔
30
  } else if (len < 16384) {
3,354,300✔
31
    return [
691,759✔
32
      2,
691,759✔
33
      (offset, buffer) => {
691,759✔
34
        // 2-byte length: 01xxxxxx xxxxxxxx
35
        const view = new DataView(buffer)
691,759✔
36
        view.setUint8(offset, ((len >> 8) & 0b00111111) | 0b01000000)
691,759✔
37
        view.setUint8(offset + 1, len & 0xff)
691,759✔
38
      },
691,759✔
39
    ]
691,759✔
40
  } else if (len < 0x40000000) {
692,767✔
41
    return [
1,007✔
42
      4,
1,007✔
43
      (offset, buffer) => {
1,007✔
44
        // 4-byte length: 10xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
45
        const view = new DataView(buffer)
1,007✔
46
        view.setUint8(offset, ((len >> 24) & 0b00111111) | 0b10000000)
1,007✔
47
        view.setUint8(offset + 1, (len >> 16) & 0xff)
1,007✔
48
        view.setUint8(offset + 2, (len >> 8) & 0xff)
1,007✔
49
        view.setUint8(offset + 3, len & 0xff)
1,007✔
50
      },
1,007✔
51
    ]
1,007✔
52
  } else {
1,007✔
53
    throw new CodecError("Length too large to encode (max is 2^30 - 1)")
1✔
54
  }
1✔
55
}
3,354,300✔
56

57
export function determineLength(data: Uint8Array, offset: number = 0): { length: number; lengthFieldSize: number } {
1✔
58
  if (offset >= data.length) {
225,601✔
59
    throw new CodecError("Offset beyond buffer")
1✔
60
  }
1✔
61

62
  const firstByte = data[offset] as number
225,600✔
63
  const prefix = firstByte >> 6
225,600✔
64

65
  if (prefix === 0) {
225,601✔
66
    return { length: firstByte & 0b00111111, lengthFieldSize: 1 }
150,054✔
67
  } else if (prefix === 1) {
225,601✔
68
    if (offset + 2 > data.length) throw new CodecError("Incomplete 2-byte length")
75,322!
69
    return { length: ((firstByte & 0b00111111) << 8) | (data[offset + 1] as number), lengthFieldSize: 2 }
75,322✔
70
  } else if (prefix === 2) {
75,546✔
71
    if (offset + 4 > data.length) throw new CodecError("Incomplete 4-byte length")
223!
72
    return {
223✔
73
      length:
223✔
74
        ((firstByte & 0b00111111) << 24) |
223✔
75
        ((data[offset + 1] as number) << 16) |
223✔
76
        ((data[offset + 2] as number) << 8) |
223✔
77
        (data[offset + 3] as number),
223✔
78
      lengthFieldSize: 4,
223✔
79
    }
223✔
80
  } else {
224✔
81
    throw new CodecError("8-byte length not supported in this implementation")
1✔
82
  }
1✔
83
}
225,601✔
84

85
export const decodeVarLenData: Decoder<Uint8Array> = (buf, offset) => {
1✔
86
  if (offset >= buf.length) {
225,586✔
87
    throw new CodecError("Offset beyond buffer")
1✔
88
  }
1✔
89

90
  const { length, lengthFieldSize } = determineLength(buf, offset)
225,585✔
91

92
  const totalBytes = lengthFieldSize + length
225,585✔
93
  if (offset + totalBytes > buf.length) {
225,586✔
94
    throw new CodecError("Data length exceeds buffer")
1✔
95
  }
1✔
96

97
  const data = buf.subarray(offset + lengthFieldSize, offset + totalBytes)
225,584✔
98
  return [data, totalBytes]
225,584✔
99
}
225,584✔
100

101
export function varLenTypeEncoder<T>(enc: BufferEncoder<T>): BufferEncoder<T[]> {
1✔
102
  return (data) => {
91✔
103
    let totalLength = 0
1,018,849✔
104
    let writeTotal = (_offset: number, _buffer: ArrayBuffer) => {}
1,018,849✔
105
    for (let i = 0; i < data.length; i++) {
1,018,849✔
106
      const [len, write] = enc(data[i]!)
2,902,288✔
107
      const oldFunc = writeTotal
2,902,288✔
108
      const currentLen = totalLength
2,902,288✔
109
      writeTotal = (offset: number, buffer: ArrayBuffer) => {
2,902,288✔
110
        oldFunc(offset, buffer)
2,902,288✔
111
        write(offset + currentLen, buffer)
2,902,288✔
112
      }
2,902,288✔
113
      totalLength += len
2,902,288✔
114
    }
2,902,288✔
115
    const [headerLength, writeLength] = lengthEncoder(totalLength)
2,902,288✔
116
    return [
2,902,288✔
117
      headerLength + totalLength,
2,902,288✔
118
      (offset, buffer) => {
2,902,288✔
119
        writeLength(offset, buffer)
1,018,849✔
120
        writeTotal(offset + headerLength, buffer)
1,018,849✔
121
      },
1,018,849✔
122
    ]
2,902,288✔
123
  }
1,018,849✔
124
}
91✔
125

126
export function decodeVarLenType<T>(dec: Decoder<T>): Decoder<T[]> {
1✔
127
  return (b, offset) => {
92✔
128
    const d = decodeVarLenData(b, offset)
92,268✔
129
    if (d === undefined) return
92,268!
130

131
    const [totalBytes, totalLength] = d
92,268✔
132

133
    let cursor = 0
92,268✔
134
    const result: T[] = []
92,268✔
135

136
    while (cursor < totalBytes.length) {
92,268✔
137
      const item = dec(totalBytes, cursor)
192,711✔
138
      if (item === undefined) return undefined
192,711✔
139

140
      const [value, len] = item
192,709✔
141
      result.push(value)
192,709✔
142
      cursor += len
192,709✔
143
    }
192,709✔
144

145
    return [result, totalLength]
92,266✔
146
  }
92,268✔
147
}
92✔
148

149
export function base64RecordEncoder<V>(valueEncoder: BufferEncoder<V>): BufferEncoder<Record<string, V>> {
1✔
150
  const entryEncoder = contramapBufferEncoders(
3✔
151
    [contramapBufferEncoder(varLenDataEncoder, base64ToBytes), valueEncoder],
3✔
152
    ([key, value]: [string, V]) => [key, value] as const,
3✔
153
  )
3✔
154

155
  return contramapBufferEncoders([varLenTypeEncoder(entryEncoder)], (record) => [Object.entries(record)] as const)
3✔
156
}
3✔
157

158
export function decodeBase64Record<V>(decodeValue: Decoder<V>): Decoder<Record<string, V>> {
1✔
159
  return mapDecoder(
3✔
160
    decodeVarLenType(
3✔
161
      mapDecoders([mapDecoder(decodeVarLenData, bytesToBase64), decodeValue], (key, value) => [key, value] as const),
3✔
162
    ),
3✔
163
    (entries) => {
3✔
164
      const record: Record<string, V> = {}
1✔
165
      for (const [key, value] of entries) {
1!
UNCOV
166
        record[key] = value
×
UNCOV
167
      }
×
168
      return record
1✔
169
    },
1✔
170
  )
3✔
171
}
3✔
172

173
export function numberRecordEncoder<V>(
1✔
174
  numberEncoder: BufferEncoder<number>,
6✔
175
  valueEncoder: BufferEncoder<V>,
6✔
176
): BufferEncoder<Record<number, V>> {
6✔
177
  const entryEncoder = contramapBufferEncoders(
6✔
178
    [numberEncoder, valueEncoder],
6✔
179
    ([key, value]: [number, V]) => [key, value] as const,
6✔
180
  )
6✔
181

182
  return contramapBufferEncoder(varLenTypeEncoder(entryEncoder), (record) =>
6✔
183
    Object.entries(record).map(([key, value]) => [Number(key), value] as [number, V]),
3✔
184
  )
6✔
185
}
6✔
186

187
export function decodeNumberRecord<V>(
1✔
188
  decodeNumber: Decoder<number>,
6✔
189
  decodeValue: Decoder<V>,
6✔
190
): Decoder<Record<number, V>> {
6✔
191
  return mapDecoder(
6✔
192
    decodeVarLenType(mapDecoders([decodeNumber, decodeValue], (key, value) => [key, value] as const)),
6✔
193
    (entries) => {
6✔
194
      const record: Record<number, V> = {}
3✔
195
      for (const [key, value] of entries) {
3✔
196
        record[key] = value
1✔
197
      }
1✔
198
      return record
3✔
199
    },
3✔
200
  )
6✔
201
}
6✔
202
export function bigintMapEncoder<V>(valueEncoder: BufferEncoder<V>): BufferEncoder<Map<bigint, V>> {
1✔
203
  const entryEncoder = contramapBufferEncoders(
3✔
204
    [uint64Encoder, valueEncoder],
3✔
205
    ([key, value]: [bigint, V]) => [key, value] as const,
3✔
206
  )
3✔
207

208
  return contramapBufferEncoder(varLenTypeEncoder(entryEncoder), (map) => Array.from(map.entries()))
3✔
209
}
3✔
210

211
export function decodeBigintMap<V>(decodeValue: Decoder<V>): Decoder<Map<bigint, V>> {
1✔
212
  return mapDecoder(
3✔
213
    decodeVarLenType(mapDecoders([decodeUint64, decodeValue], (key, value) => [key, value] as const)),
3✔
214
    (entries) => new Map(entries),
3✔
215
  )
3✔
216
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc