|
4 | 4 | This module provides the low-level encoding and decoding primitives used by |
5 | 5 | both the compressor and decompressor: |
6 | 6 |
|
7 | | -1. **Varint encoding**: Variable-length integer encoding for the uncompressed |
8 | | - length prefix. Small values use fewer bytes, saving space. |
9 | | -
|
10 | | -2. **Tag byte encoding**: Compact representation of literal and copy operations. |
| 7 | +1. **Tag byte encoding**: Compact representation of literal and copy operations. |
11 | 8 | The tag byte format packs operation type and length into minimal space. |
12 | 9 |
|
| 10 | +The uncompressed length prefix uses the shared LEB128 varint codec from the |
| 11 | +networking layer with a five-byte cap, matching the 32-bit range used by the |
| 12 | +Snappy format specification. |
| 13 | +
|
13 | 14 | Reference: https://github.com/google/snappy/blob/main/format_description.txt |
14 | 15 | """ |
15 | 16 |
|
|
31 | 32 | MAX_COPY_2_OFFSET, |
32 | 33 | MAX_INLINE_LITERAL_LENGTH, |
33 | 34 | MIN_COPY_1_LENGTH, |
34 | | - VARINT_CONTINUATION_BIT, |
35 | | - VARINT_DATA_MASK, |
36 | 35 | ) |
37 | 36 |
|
38 | 37 | type TagType = Literal["literal", "copy"] |
39 | 38 | """Snappy tag type: either a literal (raw bytes) or a copy (back-reference).""" |
40 | 39 |
|
41 | | -# Varint Encoding |
42 | | -# |
43 | | -# Varints encode integers using as few bytes as possible. |
44 | | -# - Small values use fewer bytes. |
45 | | -# - Large values use more. |
46 | | -# |
47 | | -# Each byte has 8 bits: |
48 | | -# - Bit 7 (high): continuation flag. |
49 | | -# - 1 = more bytes follow, |
50 | | -# - 0 = this is the last byte. |
51 | | -# - Bits 0-6 (low): 7 bits of the integer value. |
52 | | -# |
53 | | -# Bytes are emitted least-significant chunk first. |
54 | | -# |
55 | | -# Byte count by value: |
56 | | -# 0 .. 127 -> 1 byte |
57 | | -# 128 .. 16,383 -> 2 bytes |
58 | | -# 16,384 .. 2,097,151 -> 3 bytes |
59 | | -# 2,097,152 .. 268,435,455 -> 4 bytes |
60 | | -# 268,435,456 .. 2^32 - 1 -> 5 bytes |
61 | | -# |
62 | | -# Example: encoding 300 |
63 | | -# |
64 | | -# 300 in binary: 100101100 (9 bits, needs 2 chunks of 7 bits) |
65 | | -# |
66 | | -# Chunk 1 (bits 0-6): 0101100 = 44. More bits remain, so continuation = 1. |
67 | | -# Byte 1 = 0x80 | 44 = 0xAC |
68 | | -# |
69 | | -# Chunk 2 (bits 7+): 0000010 = 2. No more bits, so continuation = 0. |
70 | | -# Byte 2 = 0x00 | 2 = 0x02 |
71 | | -# |
72 | | -# Encoded: [0xAC, 0x02] |
73 | | -# |
74 | | -# Example: decoding [0xAC, 0x02] |
75 | | -# |
76 | | -# For each byte: check bit 7 for continuation, mask with 0x7F to get data. |
77 | | -# |
78 | | -# Byte 1 = 0xAC = 10101100: |
79 | | -# bit 7 = 1 -> more bytes coming |
80 | | -# data = 0xAC & 0x7F = 0101100 = 44 |
81 | | -# result = 44 |
82 | | -# |
83 | | -# Byte 2 = 0x02 = 00000010: |
84 | | -# bit 7 = 0 -> done |
85 | | -# data = 0x02 & 0x7F = 0000010 = 2 (mask has no effect here) |
86 | | -# result = 44 | (2 << 7) = 44 + 256 = 300 |
87 | | - |
88 | | - |
89 | | -def encode_varint32(value: int) -> bytes: |
90 | | - """Encode a 32-bit integer as a variable-length byte sequence. |
91 | | -
|
92 | | - The varint format uses 7 bits per byte for data, with the high bit |
93 | | - indicating whether more bytes follow. This efficiently encodes small |
94 | | - values in fewer bytes. |
95 | | -
|
96 | | - Algorithm: |
97 | | - 1. Take the lowest 7 bits of the value. |
98 | | - 2. If more bits remain, set the continuation bit (0x80). |
99 | | - 3. Repeat until all bits are encoded. |
100 | | -
|
101 | | - Args: |
102 | | - value: Non-negative integer to encode (must fit in 32 bits). |
103 | | -
|
104 | | - Returns: |
105 | | - Variable-length bytes encoding the integer (1-5 bytes). |
106 | | -
|
107 | | - Raises: |
108 | | - ValueError: If value is negative or exceeds 32 bits. |
109 | | - """ |
110 | | - # Validate input range. |
111 | | - # Varints in Snappy are unsigned 32-bit integers. |
112 | | - if value < 0: |
113 | | - raise ValueError(f"Varint value must be non-negative, got {value}") |
114 | | - if value > 0xFFFFFFFF: |
115 | | - raise ValueError(f"Varint value exceeds 32 bits: {value}") |
116 | | - |
117 | | - # Build the encoding byte by byte. |
118 | | - # We accumulate bytes in a list for efficiency. |
119 | | - result: list[int] = [] |
120 | | - |
121 | | - while True: |
122 | | - # Extract the lowest 7 bits. |
123 | | - byte = value & VARINT_DATA_MASK |
124 | | - |
125 | | - # Shift out the bits we just encoded. |
126 | | - value >>= 7 |
127 | | - |
128 | | - if value != 0: |
129 | | - # More bits remain: set continuation bit. |
130 | | - byte |= VARINT_CONTINUATION_BIT |
131 | | - |
132 | | - result.append(byte) |
133 | | - |
134 | | - if value == 0: |
135 | | - # All bits encoded. |
136 | | - break |
137 | | - |
138 | | - return bytes(result) |
139 | | - |
140 | | - |
141 | | -def decode_varint32(data: bytes, offset: int = 0) -> tuple[int, int]: |
142 | | - """Decode a varint from a byte sequence at the given offset. |
143 | | -
|
144 | | - Reads bytes starting at offset, accumulating 7 bits per byte into |
145 | | - the result. Stops when a byte without the continuation bit is found. |
146 | | -
|
147 | | - Args: |
148 | | - data: Byte sequence containing the varint. |
149 | | - offset: Position in data where the varint starts. |
150 | | -
|
151 | | - Returns: |
152 | | - Tuple of (decoded_value, bytes_consumed). |
153 | | -
|
154 | | - Raises: |
155 | | - ValueError: If the varint is malformed (too long or truncated). |
156 | | - """ |
157 | | - result = 0 |
158 | | - shift = 0 |
159 | | - bytes_read = 0 |
160 | | - |
161 | | - while True: |
162 | | - # Check bounds. |
163 | | - if offset + bytes_read >= len(data): |
164 | | - raise ValueError("Truncated varint: unexpected end of data") |
165 | | - |
166 | | - # Read next byte. |
167 | | - byte = data[offset + bytes_read] |
168 | | - bytes_read += 1 |
169 | | - |
170 | | - # Accumulate the 7 data bits at the current shift position. |
171 | | - result |= (byte & VARINT_DATA_MASK) << shift |
172 | | - shift += 7 |
173 | | - |
174 | | - # Check if this is the last byte (no continuation bit). |
175 | | - if (byte & VARINT_CONTINUATION_BIT) == 0: |
176 | | - break |
177 | | - |
178 | | - # Safety check: varints should not exceed 5 bytes for 32-bit values. |
179 | | - # (5 bytes * 7 bits = 35 bits, which covers 32-bit range) |
180 | | - if bytes_read >= 5: |
181 | | - raise ValueError("Varint too long: exceeds 5 bytes") |
182 | | - |
183 | | - # Verify the result fits in 32 bits. |
184 | | - if result > 0xFFFFFFFF: |
185 | | - raise ValueError(f"Varint overflow: {result} exceeds 32 bits") |
186 | | - |
187 | | - return result, bytes_read |
188 | | - |
189 | 40 |
|
190 | 41 | # Tag Byte Encoding - Literals |
191 | 42 | # |
|
0 commit comments