Skip to content

Commit 9790ea6

Browse files
committed
Add documentation
1 parent 4f13fad commit 9790ea6

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# buffer-codec
2+
3+
This package provides a set of functions for encoding and decoding typed arrays
4+
to/from various base encodings (c.f. [RFC
5+
4648](https://datatracker.ietf.org/doc/html/rfc4648)), and for slicing and
6+
combining the bits of these arrays into values of different sizes and
7+
endiannesses. It principally exists to support the
8+
[@escodb/buffer](https://github.com/escodb/buffer) package but also supports
9+
encodings that are not part of the `Buffer` interface.
10+
11+
All the functions for converting binary data are constant-time. The same set of
12+
bitwise and arithmetic operations are applied to every unit of input, and the
13+
values in the input are not used to drive any branching decisions or table
14+
lookups. The only thing that should affect how long a decoding function takes to
15+
run is the length of the input string.
16+
17+
18+
## Installation
19+
20+
npm install @escodb/buffer-codec
21+
22+
23+
## Usage
24+
25+
The package's functionality breaks down into three main areas:
26+
27+
- Converting strings to/from their UTF-8 representation via the `utf8` module
28+
- Encoding and decoding binary data to/from base 16, 32, or 64 via the `hex`,
29+
`base32` and `base64` modules
30+
- Slicing/combining the bits of array values into arbitrary-sized chunks via the
31+
`transcodeBE()` and `transcodeLE()` functions
32+
33+
34+
### Encoding and decoding text
35+
36+
The `utf8` codec converts a `String` into a `Uint8Array` containing the bytes of
37+
the string's UTF-8 representation and back again. Just as for the binary data
38+
modules, the `encode()` function converts a `Uint8Array` into a `String` and
39+
`decode()` does the opposite.
40+
41+
```js
42+
const { utf8 } = require('@escodb/buffer-codec')
43+
44+
let bytes = utf8.decode('¿Qué vas a hacer mañana?')
45+
// -> Uint8Array(27) [0xc2, 0xbf, 0x51, 0x75, 0xc3, 0xa9, 0x20, 0x76, ...]
46+
47+
utf8.encode(bytes)
48+
// -> '¿Qué vas a hacer mañana?'
49+
```
50+
51+
52+
### Encoding binary data
53+
54+
The `hex` (also aliased as `base16`), `base32` and `base64` modules provide an
55+
`encode()` function that takes a `Uint8Array` of arbitrary length and content
56+
and returns a `String` representing the input.
57+
58+
```js
59+
const { hex, base32, base64 } = require('@escodb/buffer-codec')
60+
61+
let bytes = new Uint8Array([
62+
0xa6, 0x2d, 0xd5, 0xb1, 0x5e, 0x34, 0x49, 0xac, 0x26, 0xfa, 0x3d, 0x7c, 0x1c
63+
])
64+
65+
hex.encode(bytes)
66+
// -> 'a62dd5b15e3449ac26fa3d7c1c'
67+
68+
base32.encode(bytes)
69+
// -> 'UYW5LMK6GRE2YJX2HV6BY==='
70+
71+
base64.encode(bytes)
72+
// -> 'pi3VsV40Sawm+j18HA=='
73+
```
74+
75+
The `base32` and `base64` modules add `=` padding characters where necessary to
76+
make the output length an integer multiple of the block size. The `hex` module
77+
returns lowercase output for compatibility with the Node.js `Buffer.toString()`
78+
method, but the `base32` module returns uppercase. The `base64` module uses the
79+
`+` and `/` characters, not the URL/filename-safe `-` and `_` characters for the
80+
last two values in the output alphabet.
81+
82+
83+
### Decoding binary data
84+
85+
The `hex`, `base32` and `base64` modules provide a `decode()` function that
86+
takes a `String` and returns a `Uint8Array` containing the result of decoding
87+
the string.
88+
89+
```js
90+
const { hex, base32, base64 } = require('@escodb/buffer-codec')
91+
92+
hex.decode('a62dd5b15e3449ac26fa3d7c1c')
93+
// -> Uint8Array(13) [166, 45, 213, 177, 94, 52, 73, 172, 38, 250, 61, 124, 28]
94+
95+
base32.decode('UYW5LMK6GRE2YJX2HV6BY===')
96+
// -> Uint8Array(13) [166, 45, 213, 177, 94, 52, 73, 172, 38, 250, 61, 124, 28]
97+
98+
base64.decode('pi3VsV40Sawm+j18HA==')
99+
// -> Uint8Array(13) [166, 45, 213, 177, 94, 52, 73, 172, 38, 250, 61, 124, 28]
100+
```
101+
102+
The `hex` and `base32` modules accept alphabetic characters in either upper or
103+
lower case. The `base64` module _only_ accepts `+` and `/` as the final
104+
characters in the input space; it does not support the URL/filename-safe
105+
character set.
106+
107+
All modules will accept inputs without `=` padding characters at the end, and
108+
only consider the input before the padding when determining the length of the
109+
output.
110+
111+
For reasons of performance and constant-time behaviour, these functions do not
112+
check that the input only contains valid characters. Characters outside the
113+
accepted alphabet are interpreted as a block of zero bits of the appropriate
114+
size (4 bits for `hex`, 5 bits for `base32`, 6 bits for `base64`). If your
115+
application needs to reject invalid characters, you must implement this check
116+
yourself.
117+
118+
119+
### Converting between arbitrary bit sizes
120+
121+
The `transcodeBE()` and `transcodeLE()` functions treat an array as a sequence
122+
of bits and let you slice and combine those bits into differently sized chunks.
123+
They both perform the same task, except that `transcodeBE()` uses big-endian
124+
bit/byte ordering and `transcodeLE()` is little-endian. Their general form is:
125+
126+
```js
127+
transcode{BE,LE}(inBits, outBits, source, target, targetStart, sourceStart, sourceEnd)
128+
```
129+
130+
- `inBits`: the number of bits in each input value in the `source` array
131+
- `outBits`: the number of bits in each output value in the `target` array
132+
- `source`: an `Array` or typed array that can fit values of at least `inBits`
133+
in size; values will be read from this array
134+
- `target`: an `Array` or typed array that can fit values of at least `outBits`
135+
in size; values will be written to this array
136+
- `targetStart` (default `0`): the offset at which to start writing in `target`
137+
- `sourceStart` (default `0`): the offset at which to start reading in `source`
138+
- `sourceEnd` (default: `source.length`): the offset at which to stop reading in
139+
`source`
140+
141+
For example, say we have a `Uint8Array` containing the single byte `0xb6`.
142+
Calling `transcodeBE(8, 1, source, target)` will read each value in `source` as
143+
an 8-bit value, and write this stream of bits as a sequence of 1-bit values into
144+
`target`. In other words, it places the individual bits of `0xb6` into `target`.
145+
146+
```js
147+
const { transcodeBE } = require('@escodb/buffer-codec')
148+
149+
let source = new Uint8Array([0xb6])
150+
let target = new Uint8Array(8)
151+
152+
transcodeBE(8, 1, source, target)
153+
154+
// -> target = Uint8Array(8) [1, 0, 1, 1, 0, 1, 1, 0]
155+
```
156+
157+
Calling `transcodeBE(8, 2, source, target)` means we write 2-bit values into
158+
target. Looking at the bits listed above, this means writing `10` (2), `11` (3),
159+
`01` (1), `10` (2).
160+
161+
```js
162+
target = new Uint8Array(4)
163+
transcodeBE(8, 2, source, target)
164+
165+
// -> target = Uint8Array(4) [2, 3, 1, 2]
166+
```
167+
168+
Similarly, using an output size of 4 bits writes `1011` (11), `0110` (6).
169+
170+
```js
171+
target = new Uint8Array(2)
172+
transcodeBE(8, 4, bytes)
173+
174+
// -> target = Uint8Array(2) [11, 6]
175+
```
176+
177+
If the total number of input bits available is not an integer multiple of the
178+
output bit size, `transcodeBE()` behaves as though the final chunk of bits is
179+
padded on the right with `0` bits. For example, with an output bit size of 3,
180+
the bits of `0xb6` split into `101`, `101`, `10`. Since `10` is less than 3 bits
181+
long, it is padded with `0` at the end to make it fill 3 bits, so it becomes
182+
`100` (4).
183+
184+
```js
185+
target = new Uint8Array(3)
186+
transcodeBE(8, 3, bytes)
187+
188+
// -> target = Uint8Array(2) [5, 5, 4]
189+
```
190+
191+
In general, the input and output bit sizes can take any value from `1` to `32`,
192+
as long as they don't exceed the bit size of the relevant array. For example, if
193+
`target` is a `Uint8Array` then it cannot hold values larger than 8 bits, so any
194+
values larger than that will be subject to truncation or wrapping.
195+
196+
As well as slicing numbers into smaller chunks, we can also combine them into
197+
larger values. For example, we can group 8-bit bytes into 16-bit or 32-bit
198+
values, as long as the `target` array is the appropriate type.
199+
200+
```js
201+
const { transcodeBE } = require('@escodb/buffer-codec')
202+
203+
let bytes = new Uint8Array([
204+
0xa6, 0x2d, 0xd5, 0xb1, 0x5e, 0x34, 0x49, 0xac, 0x26, 0xfa, 0x3d, 0x7c
205+
])
206+
207+
let out16 = new Uint16Array(6)
208+
transcodeBE(8, 16, bytes, out16)
209+
// out16 = Uint16Array([0xa62d, 0xd5b1, 0x5e34, 0x49ac, 0x26fa, 0x3d7c])
210+
211+
let out32 = new Uint32(4)
212+
transcodeBE(8, 32, bytes, out32)
213+
// out32 = Uint32Array([0xa62dd5b1, 0x5e3449ac, 0x26fa3d7c])
214+
```
215+
216+
Using `transcodeLE()` does the same thing as `transcodeBE()`, except that it
217+
uses little-endian bit/byte order.
218+
219+
```js
220+
const { transcodeLE } = require('@escodb/buffer-codec')
221+
222+
let bytes = new Uint8Array([
223+
0xa6, 0x2d, 0xd5, 0xb1, 0x5e, 0x34, 0x49, 0xac, 0x26, 0xfa, 0x3d, 0x7c
224+
])
225+
226+
let out16 = new Uint16Array(6)
227+
transcodeLE(8, 16, bytes, out16)
228+
// out16 = Uint16Array([0x2da6, 0xb1d5, 0x345e, 0xac49, 0xfa26, 0x7c3d])
229+
230+
let out32 = new Uint32(4)
231+
transcodeLE(8, 32, bytes, out32)
232+
// out32 = Uint32Array([0xb1d52da6, 0xac49345e, 0x7c3dfa26])
233+
```
234+
235+
Finally, there are some optional arguments that control where to begin writing
236+
in the `target` array, and which region in the `source` array to read from. For
237+
example, setting `targetStart` to `3` produces:
238+
239+
```js
240+
let out16 = new Uint16Array(6)
241+
transcodeBE(8, 16, bytes, out16, 3)
242+
// out16 = Uint16Array([0, 0, 0, 0xa62d, 0xd5b1, 0x5e34])
243+
```
244+
245+
Setting `sourceStart` to `7` and `sourceEnd` to `9` means we read just the
246+
values `0xac`, `0x26` from the `source` array:
247+
248+
```js
249+
let out16 = new Uint16Array(6)
250+
transcodeBE(8, 16, bytes, out16, 3, 7, 9)
251+
// out16 = Uint16Array([0, 0, 0, 0xac26, 0, 0])
252+
```

0 commit comments

Comments
 (0)