code wiki / (root) / nx_av1_obu.nx

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}