code wiki / (root) / nx_cdc_lib.nx

nx_cdc_lib.nx source

↩ module page · 172 lines · 7440 B

1// nx_cdc_lib.nx -- CONTENT-DEFINED CHUNKING (FastCDC gear, normalized) as a LIBRARY (/compare/dataio DI5, 2026-09-05). 2// 3// WHY: fixed-size chunking re-keys every chunk after an insertion, so a one-byte edit near the head of a 400 KB 4// artifact makes every following chunk "new" to a receiver that already holds the previous version -- the whole file 5// ships again. Content-defined boundaries (a rolling gear hash, cut where its low bits are zero) re-align within one 6// chunk of the edit, so only the touched region is new. This is the FastCDC shape (Xia et al., USENIX ATC 2016, the 7// dataio board's pinned reference): a 256-entry gear table, a one-shift-one-add rolling fingerprint, a minimum length 8// skipped outright, NORMALIZED chunking (a stricter mask before the average, a looser one after, so lengths cluster 9// around the average) and a hard maximum. 10// 11// EVERYTHING IS DERIVED, NOTHING TUNED. The gear table is SHA-256 of a NAMED seed and the byte value -- reproducible on 12// any host, so no table ships that could drift. The mask bit counts come from log2(avg). min, avg and max belong to the 13// CALLER: nx_content_put derives them from the ONE wire chunk constant it already owns (max = the wire chunk, so every 14// content-defined chunk still fits one call). 15// 16// HONESTY LIMITS, stated: the masks take the LOW bits of the fingerprint (the paper spreads the bits; the low-bit 17// variant is simpler and its statistics are PROVEN by nx_cdc_gate on a pseudo-random corpus rather than assumed). The 18// final chunk may be shorter than min. This lib never reads a file or the clock; it computes over a buffer. 19// license_tier: ORIGINAL No hw writes (Rule 26). 20import "nx_syscalls.nx" 21import "nx_sha256.nx" 22 23const CDC_GEAR_N: i64 = 256 24const CDC_GEAR_W: i64 = 8 // one i64 per gear entry 25const CDC_GEAR_BYTES: i64 = 2048 // CDC_GEAR_N x CDC_GEAR_W 26const CDC_SEED: *u8 = "nx-cdc-gear-v1" as *u8 27const CDC_DIGEST_B: i64 = 32 28const CDC_MIN_BITS: i64 = 4 // an average below 16 bytes has no meaningful mask: refused, never guessed 29const CDC_ERR: i64 = 0 - 1 30 31func cdc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 32 33// gear[i] = the first 8 bytes (big-endian) of sha256(seed || byte i), for i in 0..255. `out` holds CDC_GEAR_BYTES. 34func cdc_gear_table(out: *u8) -> i64 { 35 let sl: i64 = cdc_slen(CDC_SEED) 36 let msg: *u8 = sys_mmap(sl + 2) 37 var k: i64 = 0 38 while k < sl { msg[k] = CDC_SEED[k]; k = k + 1 } 39 let dg: *u8 = sys_mmap(CDC_DIGEST_B) 40 var i: i64 = 0 41 while i < CDC_GEAR_N { 42 msg[sl] = i as u8 43 sha256_digest(msg, sl + 1, dg) 44 var j: i64 = 0 45 while j < CDC_GEAR_W { out[i * CDC_GEAR_W + j] = dg[j]; j = j + 1 } 46 i = i + 1 47 } 48 return CDC_GEAR_N 49} 50// the gear value for byte b, assembled big-endian from the table 51func cdc_gear_at(tbl: *u8, b: i64) -> i64 { 52 var v: i64 = 0 53 var j: i64 = 0 54 while j < CDC_GEAR_W { v = v * 256 + (tbl[b * CDC_GEAR_W + j] as i64); j = j + 1 } 55 return v 56} 57// floor(log2(avg)): the number of fingerprint bits the AVERAGE mask tests 58func cdc_mask_bits(avg: i64) -> i64 { var b: i64 = 0; var v: i64 = avg; while v > 1 { v = v / 2; b = b + 1 } return b } 59// a mask of `bits` low ones (0 for bits <= 0); built by doubling so no shift operator semantics are assumed 60func cdc_mask(bits: i64) -> i64 { 61 if bits <= 0 { return 0 } 62 var m: i64 = 1 63 var i: i64 = 0 64 while i < bits { m = m * 2; i = i + 1 } 65 return m - 1 66} 67// THE CUT. Returns the END (exclusive) of the chunk that starts at `start`, in (start+min .. start+max] -- or n for a 68// tail shorter than min. Normalized: while shorter than avg the stricter mask (bits+1) must hit; after avg the looser 69// mask (bits-1) may hit; at max the cut is forced. 70func cdc_next_boundary(tbl: *u8, buf: *u8, n: i64, start: i64, min: i64, avg: i64, max: i64) -> i64 { 71 let remain: i64 = n - start 72 if remain <= min { return n } 73 var lim: i64 = max 74 if remain < max { lim = remain } 75 var normal: i64 = avg 76 if remain < avg { normal = remain } 77 let bits: i64 = cdc_mask_bits(avg) 78 let mask_s: i64 = cdc_mask(bits + 1) 79 let mask_l: i64 = cdc_mask(bits - 1) 80 var fp: i64 = 0 81 var i: i64 = min 82 var cut: i64 = 0 - 1 83 var go: i64 = 1 84 while go == 1 { 85 if i >= normal { go = 0 } else { 86 fp = fp * 2 + cdc_gear_at(tbl, buf[start + i] as i64) 87 if (fp & mask_s) == 0 { cut = start + i; go = 0 } else { i = i + 1 } 88 } 89 } 90 if cut >= 0 { return cut } 91 go = 1 92 while go == 1 { 93 if i >= lim { go = 0 } else { 94 fp = fp * 2 + cdc_gear_at(tbl, buf[start + i] as i64) 95 if (fp & mask_l) == 0 { cut = start + i; go = 0 } else { i = i + 1 } 96 } 97 } 98 if cut >= 0 { return cut } 99 return start + lim 100} 101// THE PLAN: offs[k] = start of chunk k for k in 0..count-1, offs[count] = n. Returns count, or CDC_ERR when the 102// parameters are not a chunking (min <= 0, avg <= min, max < avg, avg below the mask floor) or the table is too small. 103func cdc_plan(tbl: *u8, buf: *u8, n: i64, min: i64, avg: i64, max: i64, offs: *i64, cap: i64) -> i64 { 104 if min <= 0 { return CDC_ERR } 105 if avg <= min { return CDC_ERR } 106 if max < avg { return CDC_ERR } 107 if cdc_mask_bits(avg) < CDC_MIN_BITS { return CDC_ERR } 108 if cap < 2 { return CDC_ERR } 109 var k: i64 = 0 110 var pos: i64 = 0 111 var go: i64 = 1 112 var err: i64 = 0 113 while go == 1 { 114 if pos >= n { go = 0 } else { 115 if k + 1 >= cap { err = 1; go = 0 } else { 116 offs[k] = pos 117 pos = cdc_next_boundary(tbl, buf, n, pos, min, avg, max) 118 k = k + 1 119 } 120 } 121 } 122 if err == 1 { return CDC_ERR } 123 offs[k] = n 124 return k 125} 126// the CONTROL every content-defined claim is measured against: fixed-size chunks of `size` 127func cdc_plan_fixed(n: i64, size: i64, offs: *i64, cap: i64) -> i64 { 128 if size <= 0 { return CDC_ERR } 129 var k: i64 = 0 130 var pos: i64 = 0 131 var go: i64 = 1 132 var err: i64 = 0 133 while go == 1 { 134 if pos >= n { go = 0 } else { 135 if k + 1 >= cap { err = 1; go = 0 } else { 136 offs[k] = pos 137 pos = pos + size 138 if pos > n { pos = n } 139 k = k + 1 140 } 141 } 142 } 143 if err == 1 { return CDC_ERR } 144 offs[k] = n 145 return k 146} 147// the 32-byte digest of chunk k of a plan, into out (32 bytes) 148func cdc_chunk_digest(buf: *u8, offs: *i64, k: i64, out: *u8) -> i64 { 149 let a: i64 = offs[k] 150 let b: i64 = offs[k + 1] 151 sha256_digest(((buf as i64) + a) as *u8, b - a, out) 152 return b - a 153} 154// how many chunk digests of plan A (count na, digests da) also appear in plan B (count nb, digests db): the dedup 155// measure -- the bytes a receiver holding B would NOT need again for A 156func cdc_shared_digests(da: *u8, na: i64, db: *u8, nb: i64) -> i64 { 157 var shared: i64 = 0 158 var i: i64 = 0 159 while i < na { 160 var j: i64 = 0 161 var hit: i64 = 0 162 while j < nb { 163 var same: i64 = 1 164 var t: i64 = 0 165 while t < CDC_DIGEST_B { if da[i * CDC_DIGEST_B + t] != db[j * CDC_DIGEST_B + t] { same = 0; t = CDC_DIGEST_B } else { t = t + 1 } } 166 if same == 1 { hit = 1; j = nb } else { j = j + 1 } 167 } 168 shared = shared + hit 169 i = i + 1 170 } 171 return shared 172}