Files
usenet-indexer/edge/src/codec.rs
T
Daan MeijerandClaude Opus 5.5 5109481d67
Test and deploy / test (push) Successful in 46s
Test and deploy / edge (push) Successful in 1m54s
Test and deploy / deploy (push) Successful in 2m56s
The Rust worker is the product: edge/, built and checked in CI
User: Rust is the product (option a). tools/edge-bench-rs moves to edge/
(crate edge; binaries edge-node, the worker, and edge-bench, the replay).
CI's new job edge runs cargo test and edge/check-fixtures.sh: both
RangeIndexers on three committed captures (edge/fixtures, 25k overview
lines each of a.b.models, a.b.ath and a.b.boneless in 5 chunks, 3.5 MB),
compared field by field with check.ts; deploy waits for it. The
TypeScript RangeIndexer stays as check.ts's reference. README: what it
is now; the benchmark findings as history.

Random names: looksRandom missed long tokens of capitals and digits
("51294P1CUKR2R35X", release 74539068, readable and a Newznab title).
12+ capitals and digits switching 6+ times between them are random
(COD4MW2REMASTERED switches 4 times, BEATLES1964REMASTER twice); both
implementations, identical on the fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:01:18 +02:00

162 lines
5.2 KiB
Rust

//! lib/chunks/codec.ts encodeChunk, format v2: a version byte, the length of
//! the compressed part, zstd(JSON head + text ids), then the nyuu and hex ids
//! as raw bytes.
use crate::js::json_str;
pub struct ChunkFile<'a> {
pub name: &'a str,
pub poster: &'a str,
pub subject: &'a str,
pub posted_at: Option<i64>,
pub groups: &'a [String],
/// [part, message-id, bytes]
pub segments: Vec<(u32, &'a str, i64)>,
}
fn is_nyuu(id: &str, suffix: &str) -> bool {
let b = id.as_bytes();
if b.len() != 38 + suffix.len() || b[24] != b'-' || !id.ends_with(suffix) {
return false;
}
(0..24).step_by(2).all(|i| b[i].is_ascii_uppercase() && b[i + 1].is_ascii_lowercase()) && b[25..38].iter().all(u8::is_ascii_digit)
}
fn is_hex(id: &str, suffix: &str) -> bool {
let b = id.as_bytes();
b.len() == 32 + suffix.len() && id.ends_with(suffix) && b[..32].iter().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(c))
}
fn pack_letters(id: &[u8], out: &mut Vec<u8>) {
let mut acc: u32 = 0;
let mut bits = 0;
for i in (0..24).step_by(2) {
acc = (acc << 10) | ((id[i] - b'A') as u32 * 26 + (id[i + 1] - b'a') as u32);
bits += 10;
while bits >= 8 {
bits -= 8;
out.push(((acc >> bits) & 0xff) as u8);
}
acc &= (1 << bits) - 1;
}
}
fn hex_val(c: u8) -> u8 {
if c.is_ascii_digit() {
c - b'0'
} else {
c - b'a' + 10
}
}
fn push_num(out: &mut String, n: i64) {
use std::fmt::Write;
write!(out, "{n}").unwrap();
}
pub fn encode_chunk(files: &[ChunkFile]) -> Vec<u8> {
let mut head = String::with_capacity(256 * files.len());
let mut text: Vec<&str> = Vec::new();
let mut raw: Vec<u8> = Vec::new();
head.push('[');
for (fi, f) in files.iter().enumerate() {
let ids = &f.segments;
let code = match ids.first() {
None => 't',
Some(&(_, first, _)) => {
let nyuu = first.get(38..).filter(|s| s.starts_with('@'));
let hex = first.get(32..).filter(|s| s.starts_with('@'));
if nyuu.is_some_and(|suffix| ids.iter().all(|&(_, id, _)| is_nyuu(id, suffix))) {
'n'
} else if hex.is_some_and(|suffix| ids.iter().all(|&(_, id, _)| is_hex(id, suffix))) {
'h'
} else {
't'
}
}
};
if fi > 0 {
head.push(',');
}
head.push_str("{\"n\":");
json_str(f.name, &mut head);
head.push_str(",\"p\":");
json_str(f.poster, &mut head);
head.push_str(",\"j\":");
json_str(f.subject, &mut head);
head.push_str(",\"d\":");
match f.posted_at {
Some(t) => push_num(&mut head, t),
None => head.push_str("null"),
}
head.push_str(",\"g\":[");
for (i, g) in f.groups.iter().enumerate() {
if i > 0 {
head.push(',');
}
json_str(g, &mut head);
}
head.push_str("],\"s\":[");
for (i, &(_, _, b)) in ids.iter().enumerate() {
if i > 0 {
head.push(',');
}
push_num(&mut head, b);
}
head.push(']');
if !ids.iter().enumerate().all(|(k, &(n, _, _))| n as usize == k + 1) {
head.push_str(",\"k\":[");
for (i, &(n, _, _)) in ids.iter().enumerate() {
if i > 0 {
head.push(',');
}
push_num(&mut head, n as i64);
}
head.push(']');
}
match code {
'n' => {
head.push_str(",\"c\":\"n\",\"x\":");
json_str(&ids[0].1[38..], &mut head);
head.push_str(",\"t\":[");
let mut previous = 0i64;
for (i, &(_, id, _)) in ids.iter().enumerate() {
pack_letters(id.as_bytes(), &mut raw);
let t: i64 = id[25..38].parse().unwrap();
if i > 0 {
head.push(',');
}
push_num(&mut head, t - previous);
previous = t;
}
head.push(']');
}
'h' => {
head.push_str(",\"c\":\"h\",\"x\":");
json_str(&ids[0].1[32..], &mut head);
for &(_, id, _) in ids {
let b = id.as_bytes();
for i in (0..32).step_by(2) {
raw.push(hex_val(b[i]) << 4 | hex_val(b[i + 1]));
}
}
}
_ => text.extend(ids.iter().map(|&(_, id, _)| id)),
}
head.push('}');
}
head.push(']');
let mut plain = head.into_bytes();
for id in text {
plain.push(b'\n');
plain.extend_from_slice(id.as_bytes());
}
let packed = zstd::bulk::compress(&plain, 3).unwrap();
let mut out = Vec::with_capacity(5 + packed.len() + raw.len());
out.push(2);
out.extend_from_slice(&(packed.len() as u32).to_be_bytes());
out.extend_from_slice(&packed);
out.extend_from_slice(&raw);
out
}