---
date: '2026-07-05'
description: UTF-8, base64, hex, RLE, delta+zigzag+varint, bitpacking, checksums
id: notes
modified: 2026-07-06 13:05:30 GMT-04:00
tags:
  - cs
title: codecs
created: '2026-07-05'
published: '2026-07-05'
pageLayout: default
slug: hinterland/prep/bt/06-codecs/notes
permalink: https://aarnphm.xyz/hinterland/prep/bt/06-codecs/notes.md
generator:
  quartz: v4.6.0
  hostedProvider: Cloudflare
  baseUrl: aarnphm.xyz
full: https://aarnphm.xyz/llms-full.txt
---
## route

This module is breadth after varints and streams.

1. Read `picture`, `utf-8`, `base64`, and `rle`.
2. Solve `utf8_encode`, `utf8_decode`, `b64_encode`, `b64_decode`, `rle_encode`, and `rle_decode`.
3. Read `delta stack`.
4. Solve `delta_varint_encode` and `delta_varint_decode`.
5. Read `bitpacking` and `checksums`.
6. Solve `bitpack`, `bitunpack`, and `fletcher16`.
7. Review [[hinterland/prep/06-codecs/notes.fc]].

## picture

Every codec is a contract about units, boundaries, and invalid inputs.

```mermaid
flowchart LR
  Input["input units"] --> Acc["accumulator"]
  Acc --> Emit["output units"]
  Acc --> Validate["validate boundary"]
  Validate --> Error["error policy"]
```

Say these before coding:

- input unit
- output unit
- bit order
- length rule
- canonical form
- malformed input behavior

## utf-8

UTF-8 maps a Unicode codepoint to 1-4 bytes.

| range               | bytes                                 | payload |
| ------------------- | ------------------------------------- | ------- |
| `0x0000..0x007f`    | `0xxxxxxx`                            | 7 bits  |
| `0x0080..0x07ff`    | `110xxxxx 10xxxxxx`                   | 11 bits |
| `0x0800..0xffff`    | `1110xxxx 10xxxxxx 10xxxxxx`          | 16 bits |
| `0x10000..0x10ffff` | `11110xxx 10xxxxxx 10xxxxxx 10xxxxxx` | 21 bits |

Worked euro sign:

```text
U+20AC = 0010 0000 1010 1100
split 4|6|6: 0010 000010 101100
emit: e2 82 ac
```

Boundary vectors:

| codepoint  | UTF-8         |
| ---------- | ------------- |
| `0x7f`     | `7f`          |
| `0x80`     | `c2 80`       |
| `0x7ff`    | `df bf`       |
| `0x800`    | `e0 a0 80`    |
| `0x10000`  | `f0 90 80 80` |
| `0x10ffff` | `f4 8f bf bf` |

Decoder validation order:

1. classify lead byte.
2. check enough continuation bytes exist.
3. check each continuation has top bits `10`.
4. assemble codepoint.
5. reject overlongs, surrogates, and values above `0x10ffff`.

Overlongs are security bugs, not tidiness. `c0 af` encodes `/` in two bytes under a sloppy decoder.

## base64

Base64 maps 3 bytes to 4 sextets.

```text
Man = 4d 61 6e
bits = 010011 010110 000101 101110
chars = T W F u
```

Rules:

- alphabet is `A-Z`, `a-z`, `0-9`, `+`, `/`.
- urlsafe swaps `+ /` for `- _`.
- output length with padding is `4 * ceil(n / 3)`.
- 1 leftover byte <span>&rarr;</span> 2 chars plus `==`.
- 2 leftover bytes <span>&rarr;</span> 3 chars plus `=`.
- encoded length mod 4 equal to 1 is impossible.

Validation:

1. padding appears only at the end.
2. no more than two `=`.
3. every non-padding char is in the reverse table.
4. if canonicality matters, reject nonzero dangling bits.

## rle

Three levels:

| format             | shape                         | when it works                      |
| ------------------ | ----------------------------- | ---------------------------------- |
| textual count-char | `"3A1B"`                      | toy only                           |
| byte pair          | `[count][value]`              | long byte runs                     |
| PackBits style     | literal runs plus repeat runs | avoids 2x expansion on random data |

Pair RLE recipe:

```text
scan maximal run
while run length > 255:
  emit ff value
  run length -= 255
emit run length, value
```

Example: 300 `A` bytes <span>&rarr;</span> `ff 41 2d 41`.

Worst case for pair RLE is 2x expansion when every run has length 1. Say that without being asked.

## delta stack

The timestamp / posting-list compression stack:

```mermaid
flowchart LR
  Values["values"] --> Delta["delta from previous"]
  Delta --> Zigzag["zigzag signed deltas"]
  Zigzag --> Uvarint["uvarint"]
  Uvarint --> Bytes["bytes"]
```

Worked:

| value | delta | zigzag | uvarint |
| ----- | ----- | ------ | ------- |
| 1000  | 1000  | 2000   | `d0 0f` |
| 1005  | 5     | 10     | `0a`    |
| 1004  | -1    | 1      | `01`    |
| 1010  | 6     | 12     | `0c`    |

Stream: `d0 0f 0a 01 0c`.

Delta overflow matters. For int64 inputs, a delta can need 64 unsigned bits. Do the delta in unsigned mod-$2^{64}$ arithmetic; Python must mask by hand.

## bitpacking

Pack `n` values of width `k` into `ceil(n*k/8)` bytes.

State bit order before coding. This kit uses LSB-first:

- value `i` owns stream bits `[i*k, (i+1)*k)`.
- stream bit `j` lives at bit `j % 8` of byte `j // 8`.
- unused high bits of the last byte are zero.

Accumulator recipe:

<div class="notebook-runtime" data-notebook-runtime="notebook-runtime-gzdkr9"></div>

<div class="notebook-code-cell" data-notebook-cell-frame="code-cell-1" id="code-cell-1" data-notebook-language="python">

<div class="notebook-runtime-cell" data-notebook-cell="code-cell-1" data-notebook-execution-count=""><span class="notebook-execution-prompt" data-notebook-execution-label="code-cell-1" aria-live="polite">In [ ]:</span></div>

<div class="notebook-cell-actions" data-notebook-cell-actions="code-cell-1">
<span class="notebook-language-badge notebook-language-badge-python" data-notebook-language="python" title="Python cell"><span class="notebook-language-icon" aria-hidden="true"><svg class="notebook-language-svg notebook-python-icon" viewBox="0 0 111 112" aria-hidden="true" focusable="false"><path fill="#3776ab" d="M54.918785.00091927421C50.335132.02221727 45.957846.41313697 42.106285 1.0946693 30.760069 3.0991731 28.700036 7.2947714 28.700035 15.032169v10.21875h26.8125v3.40625h-36.875c-7.792459 0-14.6157588 4.683717-16.7499998 13.59375-2.46181998 10.212966-2.57101508 16.586023 0 27.25 1.9059283 7.937852 6.4575432 13.593748 14.2499998 13.59375h9.21875v-12.25c0-8.849902 7.657144-16.656248 16.75-16.65625h26.78125c7.454951 0 13.406253-6.138164 13.40625-13.625v-25.53125c0-7.2663386-6.12998-12.7247771-13.40625-13.9374997C64.281548.32794397 59.502438-.02037903 54.918785.00091927421zM40.418785 8.2196694c2.769547 0 5.03125 2.2986456 5.03125 5.1249996-.000002 2.816336-2.261703 5.09375-5.03125 5.09375-2.779476-.000001-5.03125-2.277415-5.03125-5.09375-.000001-2.826353 2.251774-5.1249996 5.03125-5.1249996z"/><path fill="#ffd43b" d="M85.637535 28.657169v11.90625c0 9.230755-7.825895 16.999999-16.75 17h-26.78125c-7.335833 0-13.406249 6.278483-13.40625 13.625v25.531247c0 7.266344 6.318588 11.540324 13.40625 13.625004 8.487331 2.49561 16.626237 2.94663 26.78125 0 6.750155-1.95439 13.406253-5.88761 13.40625-13.625004V86.500919h-26.78125v-3.40625h40.187504c7.792461 0 10.696251-5.435408 13.406241-13.59375 2.79933-8.398886 2.68022-16.475776 0-27.25-1.92578-7.757441-5.60387-13.59375-13.406241-13.59375zm-15.0625 64.65625c2.779478.000003 5.03125 2.277417 5.03125 5.093747-.000002 2.826354-2.251775 5.125004-5.03125 5.125004-2.76955 0-5.03125-2.29865-5.03125-5.125004.000002-2.81633 2.261697-5.093747 5.03125-5.093747z"/></svg></span><span class="notebook-language-label">Python cell</span></span>
<button type="button" class="notebook-icon-button" data-notebook-run-cell="code-cell-1" aria-label="Run code-cell-1" title="Run code-cell-1"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5v14l11-7z"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-edit-cell="code-cell-1" aria-label="Edit code-cell-1" title="Edit code-cell-1"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m4 16.5-.5 4 4-.5L19 8.5 15.5 5z"/><path d="m14 6.5 3.5 3.5"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-save-cell="code-cell-1" aria-label="Save code-cell-1 locally" title="Save code-cell-1 locally" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 4h11l3 3v13H5z"/><path d="M8 4v6h8V4"/><path d="M8 20v-6h8v6"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-revert-cell="code-cell-1" aria-label="Revert code-cell-1 local edit" title="Revert code-cell-1 local edit" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-vim-cell="code-cell-1" aria-label="Enable Vim mode" title="Enable Vim mode" hidden><svg class="notebook-vim-icon" viewBox="0 0 602 734" aria-hidden="true" focusable="false"><g transform="translate(2 3)"><path class="notebook-vim-icon-left" d="M0 155.5704 155-1l-.000003 728L0 572.237919z"/><path class="notebook-vim-icon-right" d="M443.060403 156.982405 600-1l-3.181208 728L442 572.219941z" transform="translate(521 363.5) scale(-1 1) translate(-521 -363.5)"/><path class="notebook-vim-icon-cross" d="M154.986294 0 558 615.189696 445.224605 728 42 114.172017z"/></g></svg></button>
<span class="notebook-local-source-status" data-notebook-local-source-status="code-cell-1" hidden></span>
</div>

<div class="notebook-source-editor" data-notebook-source-editor="code-cell-1" hidden></div>

```python shell
acc = 0
nbits = 0
for value in values:
  acc |= value << nbits
  nbits += k
  while nbits >= 8:
    out.append(acc & 0xFF)
    acc >>= 8
    nbits -= 8
```

<div class="notebook-runtime-output" data-notebook-output="code-cell-1" hidden></div>

</div>

Sanity checks:

- `k = 8` returns the input bytes.
- `k = 1`, values `[1,0,1,1,0,0,1,0]`, byte `0x4d`.

## checksums

| check       | catches                          | misses                           |
| ----------- | -------------------------------- | -------------------------------- |
| byte sum    | single-byte changes              | permutations, balanced changes   |
| XOR         | some flips                       | duplicated changes, permutations |
| Fletcher-16 | position changes better than sum | `0x00` vs `0xff` blind spot      |
| CRC-32      | burst errors                     | adversarial edits                |
| MAC         | adversarial edits                | needs key management             |

Fletcher-16:

<div class="notebook-code-cell" data-notebook-cell-frame="code-cell-2" id="code-cell-2" data-notebook-language="python">

<div class="notebook-runtime-cell" data-notebook-cell="code-cell-2" data-notebook-execution-count=""><span class="notebook-execution-prompt" data-notebook-execution-label="code-cell-2" aria-live="polite">In [ ]:</span></div>

<div class="notebook-cell-actions" data-notebook-cell-actions="code-cell-2">
<span class="notebook-language-badge notebook-language-badge-python" data-notebook-language="python" title="Python cell"><span class="notebook-language-icon" aria-hidden="true"><svg class="notebook-language-svg notebook-python-icon" viewBox="0 0 111 112" aria-hidden="true" focusable="false"><path fill="#3776ab" d="M54.918785.00091927421C50.335132.02221727 45.957846.41313697 42.106285 1.0946693 30.760069 3.0991731 28.700036 7.2947714 28.700035 15.032169v10.21875h26.8125v3.40625h-36.875c-7.792459 0-14.6157588 4.683717-16.7499998 13.59375-2.46181998 10.212966-2.57101508 16.586023 0 27.25 1.9059283 7.937852 6.4575432 13.593748 14.2499998 13.59375h9.21875v-12.25c0-8.849902 7.657144-16.656248 16.75-16.65625h26.78125c7.454951 0 13.406253-6.138164 13.40625-13.625v-25.53125c0-7.2663386-6.12998-12.7247771-13.40625-13.9374997C64.281548.32794397 59.502438-.02037903 54.918785.00091927421zM40.418785 8.2196694c2.769547 0 5.03125 2.2986456 5.03125 5.1249996-.000002 2.816336-2.261703 5.09375-5.03125 5.09375-2.779476-.000001-5.03125-2.277415-5.03125-5.09375-.000001-2.826353 2.251774-5.1249996 5.03125-5.1249996z"/><path fill="#ffd43b" d="M85.637535 28.657169v11.90625c0 9.230755-7.825895 16.999999-16.75 17h-26.78125c-7.335833 0-13.406249 6.278483-13.40625 13.625v25.531247c0 7.266344 6.318588 11.540324 13.40625 13.625004 8.487331 2.49561 16.626237 2.94663 26.78125 0 6.750155-1.95439 13.406253-5.88761 13.40625-13.625004V86.500919h-26.78125v-3.40625h40.187504c7.792461 0 10.696251-5.435408 13.406241-13.59375 2.79933-8.398886 2.68022-16.475776 0-27.25-1.92578-7.757441-5.60387-13.59375-13.406241-13.59375zm-15.0625 64.65625c2.779478.000003 5.03125 2.277417 5.03125 5.093747-.000002 2.826354-2.251775 5.125004-5.03125 5.125004-2.76955 0-5.03125-2.29865-5.03125-5.125004.000002-2.81633 2.261697-5.093747 5.03125-5.093747z"/></svg></span><span class="notebook-language-label">Python cell</span></span>
<button type="button" class="notebook-icon-button" data-notebook-run-cell="code-cell-2" aria-label="Run code-cell-2" title="Run code-cell-2"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5v14l11-7z"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-edit-cell="code-cell-2" aria-label="Edit code-cell-2" title="Edit code-cell-2"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m4 16.5-.5 4 4-.5L19 8.5 15.5 5z"/><path d="m14 6.5 3.5 3.5"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-save-cell="code-cell-2" aria-label="Save code-cell-2 locally" title="Save code-cell-2 locally" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 4h11l3 3v13H5z"/><path d="M8 4v6h8V4"/><path d="M8 20v-6h8v6"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-revert-cell="code-cell-2" aria-label="Revert code-cell-2 local edit" title="Revert code-cell-2 local edit" hidden><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg></button>
<button type="button" class="notebook-icon-button" data-notebook-vim-cell="code-cell-2" aria-label="Enable Vim mode" title="Enable Vim mode" hidden><svg class="notebook-vim-icon" viewBox="0 0 602 734" aria-hidden="true" focusable="false"><g transform="translate(2 3)"><path class="notebook-vim-icon-left" d="M0 155.5704 155-1l-.000003 728L0 572.237919z"/><path class="notebook-vim-icon-right" d="M443.060403 156.982405 600-1l-3.181208 728L442 572.219941z" transform="translate(521 363.5) scale(-1 1) translate(-521 -363.5)"/><path class="notebook-vim-icon-cross" d="M154.986294 0 558 615.189696 445.224605 728 42 114.172017z"/></g></svg></button>
<span class="notebook-local-source-status" data-notebook-local-source-status="code-cell-2" hidden></span>
</div>

<div class="notebook-source-editor" data-notebook-source-editor="code-cell-2" hidden></div>

```python shell
s1 = 0
s2 = 0
for b in buf:
  s1 = (s1 + b) % 255
  s2 = (s2 + s1) % 255
return (s2 << 8) | s1
```

<div class="notebook-runtime-output" data-notebook-output="code-cell-2" hidden></div>

</div>

Know when to step up: CRC for accidental corruption, keyed MAC for adversaries.

## guards

- UTF-8 rejects surrogates `0xd800..0xdfff`.
- UTF-8 rejects lead bytes `c0`, `c1`, and `f5..ff`.
- Base64 length mod 4 equal to 1 is invalid.
- Pair RLE must reject count 0.
- Uvarint decode must cap at 10 bytes for u64.
- Bitpacking must reject values that do not fit in `k` bits.
- Checksum code in C should keep accumulators unsigned.

## drills

1. Encode U+20AC.
2. Explain why `c0 af` is invalid.
3. Base64 encode `"Man"`.
4. How many `=` for 5 bytes?
5. Pair-RLE encode 300 `A` bytes.
6. Encode `[1000, 1005, 1004, 1010]` through delta-zigzag-uvarint.
7. Compute bytes needed for 100 five-bit values.
8. Explain why XOR is a weak checksum.

<script type="application/json" data-notebook-runtime-data>{"id":"notebook-runtime-gzdkr9","sourcePath":"hinterland/prep/bt/06-codecs/notes.md","language":"python","indexUrl":"https://cdn.jsdelivr.net/pyodide/v0.29.4/full/","cells":[{"id":"code-cell-1","source":"acc = 0\nnbits = 0\nfor value in values:\n  acc |= value \u003c\u003c nbits\n  nbits += k\n  while nbits \u003e= 8:\n    out.append(acc \u0026 0xFF)\n    acc \u003e\u003e= 8\n    nbits -= 8","language":"python","executionIndex":null},{"id":"code-cell-2","source":"s1 = 0\ns2 = 0\nfor b in buf:\n  s1 = (s1 + b) % 255\n  s2 = (s2 + s1) % 255\nreturn (s2 \u003c\u003c 8) | s1","language":"python","executionIndex":null}],"toolbar":false,"debug":true,"vimMode":true}</script>

