nx_av1_obu.nx source
↩ module page · 211 lines · 6931 B
1// nx_av1_obu.nx -- AV1/AV2 OBU layer: reader and writer.
2//
3// The layer directly above nx_av1_ec.nx. Every AV1 and AV2 bitstream is a
4// sequence of Open Bitstream Units, each a 1-2 byte header plus an optional
5// LEB128 size. AVIF's still-picture path uses the same units. Written both
6// directions so it proves by round-trip.
7//
8// LEB128 IS UNSIGNED AND LITTLE-ENDIAN BY GROUP. Seven payload bits per byte,
9// low group first, high bit as the continuation flag. AV1 caps it at 8 bytes.
10// The natural mistake is to stop at the first byte with the high bit clear
11// WITHOUT bounding the count -- a hostile stream then walks the parser off
12// the buffer one byte at a time.
13//
14// THE TWO RESERVED BITS ARE NOT DECORATION. obu_forbidden_bit must be 0 and
15// obu_reserved_1bit must be 0. A parser that ignores them will happily
16// consume arbitrary bytes as OBU headers and resynchronise onto garbage,
17// which is exactly how a fuzzer turns a malformed file into a long walk
18// through memory. Both are REFUSED here.
19//
20// UVLC IS NOT LEB128. Frame headers use an Exp-Golomb-style variable code
21// read MSB-first off the bitstream: leading zeros, then that many value bits,
22// biased by (1 << zeros) - 1. Confusing the two silently misreads every frame
23// header field after the first.
24//
25// genealogy_id: av1_spec_5_3_obu
26// lineage_id: nx_av1_obu_v1
27// license_tier: ORIGINAL
28
29import "nx_syscalls.nx"
30import "nx_bitstream.nx"
31
32const NX_OBU_MAX_LEB: i64 = 8
33const NX_OBU_SEQ_HEADER: i64 = 1
34const NX_OBU_TEMPORAL_DELIM: i64 = 2
35const NX_OBU_FRAME_HEADER: i64 = 3
36const NX_OBU_TILE_GROUP: i64 = 4
37const NX_OBU_METADATA: i64 = 5
38const NX_OBU_FRAME: i64 = 6
39const NX_OBU_PADDING: i64 = 15
40
41// parsed field slots
42const NX_OBU_FLD_TYPE: i64 = 0
43const NX_OBU_FLD_EXTFLAG: i64 = 1
44const NX_OBU_FLD_HASSIZE: i64 = 2
45const NX_OBU_FLD_TEMPORAL: i64 = 3
46const NX_OBU_FLD_SPATIAL: i64 = 4
47const NX_OBU_FLD_PAYOFF: i64 = 5
48const NX_OBU_FLD_PAYLEN: i64 = 6
49const NX_OBU_FLD_NEXT: i64 = 7
50
51// ===== LEB128 =====================================================
52//
53// Bounded at 8 bytes by the spec. out_len receives the bytes consumed.
54// Returns -1 on a run that never terminates inside the cap or the buffer.
55
56func nx_obu_leb128_read(d: *u8, n: i64, off: i64, out_len: *i64) -> i64 {
57 var v: i64 = 0
58 var i: i64 = 0
59 while i < NX_OBU_MAX_LEB {
60 if off + i >= n { return 0 - 1 }
61 let b: i64 = (d[off + i] as i64) & 255
62 v = v | ((b & 0x7f) << (i * 7))
63 i = i + 1
64 if (b & 0x80) == 0 {
65 out_len[0] = i
66 return v
67 }
68 }
69 return 0 - 1
70}
71
72func nx_obu_leb128_write(out: *u8, cap: i64, off: i64, v: i64) -> i64 {
73 if v < 0 { return 0 }
74 var x: i64 = v
75 var i: i64 = 0
76 var go: i64 = 1
77 while go == 1 {
78 if i >= NX_OBU_MAX_LEB { return 0 }
79 if off + i >= cap { return 0 }
80 var b: i64 = x & 0x7f
81 x = x >> 7
82 if x != 0 { b = b | 0x80 } else { go = 0 }
83 out[off + i] = b as u8
84 i = i + 1
85 }
86 return i
87}
88
89// ===== UVLC =======================================================
90//
91// Leading zeros, a terminating one, then that many value bits, biased.
92// A run of 32 or more zeros is the spec's saturation case.
93
94func nx_obu_uvlc(bs: *NxBitStream) -> i64 {
95 var zeros: i64 = 0
96 var go: i64 = 1
97 while go == 1 {
98 let b: i64 = nx_bitstream_read_msb(bs, 1)
99 if bs.overflow == 1 { return 0 - 1 }
100 if b == 1 { go = 0 } else {
101 zeros = zeros + 1
102 if zeros >= 32 { return 0xffffffff }
103 }
104 }
105 if zeros == 0 { return 0 }
106 let v: i64 = nx_bitstream_read_msb(bs, zeros)
107 if bs.overflow == 1 { return 0 - 1 }
108 return v + (1 << zeros) - 1
109}
110
111// ===== OBU header =================================================
112//
113// Returns 1 and fills fld, or 0. Both reserved bits are enforced: a parser
114// that skips them resynchronises onto arbitrary bytes.
115
116func nx_obu_parse(d: *u8, n: i64, off: i64, fld: *i64) -> i64 {
117 if off >= n { return 0 }
118 let h: i64 = (d[off] as i64) & 255
119 if (h & 0x80) != 0 { return 0 } // obu_forbidden_bit
120 let otype: i64 = (h >> 3) & 15
121 let extf: i64 = (h >> 2) & 1
122 let hassize: i64 = (h >> 1) & 1
123 if (h & 1) != 0 { return 0 } // obu_reserved_1bit
124
125 var p: i64 = off + 1
126 var tid: i64 = 0
127 var sid: i64 = 0
128 if extf == 1 {
129 if p >= n { return 0 }
130 let e: i64 = (d[p] as i64) & 255
131 tid = (e >> 5) & 7
132 sid = (e >> 3) & 3
133 p = p + 1
134 }
135
136 var paylen: i64 = 0
137 if hassize == 1 {
138 let ll: *i64 = sys_mmap(64) as *i64
139 let sz: i64 = nx_obu_leb128_read(d, n, p, ll)
140 if sz < 0 { return 0 }
141 p = p + ll[0]
142 paylen = sz
143 } else {
144 paylen = n - p
145 }
146 if p + paylen > n { return 0 }
147
148 fld[NX_OBU_FLD_TYPE] = otype
149 fld[NX_OBU_FLD_EXTFLAG] = extf
150 fld[NX_OBU_FLD_HASSIZE] = hassize
151 fld[NX_OBU_FLD_TEMPORAL] = tid
152 fld[NX_OBU_FLD_SPATIAL] = sid
153 fld[NX_OBU_FLD_PAYOFF] = p
154 fld[NX_OBU_FLD_PAYLEN] = paylen
155 fld[NX_OBU_FLD_NEXT] = p + paylen
156 return 1
157}
158
159// ===== write one OBU ==============================================
160//
161// Always emits the size field -- a sized OBU is self-delimiting, which is
162// what makes a stream walkable without decoding it. Returns the new offset.
163
164func nx_obu_write(out: *u8, cap: i64, off: i64, otype: i64, tid: i64, sid: i64,
165 use_ext: i64, payload: *u8, plen: i64) -> i64 {
166 if otype < 0 { return 0 }
167 if otype > 15 { return 0 }
168 if plen < 0 { return 0 }
169 if tid < 0 { return 0 }
170 if tid > 7 { return 0 }
171 if sid < 0 { return 0 }
172 if sid > 3 { return 0 }
173 if off + 2 > cap { return 0 }
174
175 var h: i64 = (otype << 3) | (1 << 1)
176 if use_ext == 1 { h = h | (1 << 2) }
177 out[off] = h as u8
178 var p: i64 = off + 1
179 if use_ext == 1 {
180 if p >= cap { return 0 }
181 out[p] = (((tid & 7) << 5) | ((sid & 3) << 3)) as u8
182 p = p + 1
183 }
184 let ln: i64 = nx_obu_leb128_write(out, cap, p, plen)
185 if ln == 0 { return 0 }
186 p = p + ln
187 if p + plen > cap { return 0 }
188 var i: i64 = 0
189 while i < plen { out[p + i] = payload[i]; i = i + 1 }
190 return p + plen
191}
192
193// ===== walk a temporal unit =======================================
194//
195// Returns the number of OBUs found, or -1 if any header is malformed.
196// Refusing the whole unit on one bad header is deliberate: resynchronising
197// mid-stream is how a decoder ends up interpreting payload as headers.
198
199func nx_obu_count(d: *u8, n: i64) -> i64 {
200 let fld: *i64 = sys_mmap(128) as *i64
201 var p: i64 = 0
202 var c: i64 = 0
203 while p < n {
204 if nx_obu_parse(d, n, p, fld) != 1 { return 0 - 1 }
205 c = c + 1
206 let nxt: i64 = fld[NX_OBU_FLD_NEXT]
207 if nxt <= p { return 0 - 1 }
208 p = nxt
209 }
210 return c
211}