Skip to main content

n3_lib/
table.rs

1//! Encrypted TBL tables, retaining schema, raw strings and floating-point bits.
2
3use crate::binary::{Error, Reader, Result, Writer};
4use crate::records::{hex, unhex};
5use serde_json::{Value, json};
6
7/// Editable TBL document.
8#[derive(Debug, Clone, PartialEq)]
9pub struct Table {
10    /// Column codes 1..9; the first column must be u32 (6).
11    pub types: Vec<u8>,
12    /// Integer cells or original hex bytes; f32 cells store unsigned bits.
13    pub rows: Vec<Vec<Value>>,
14    /// Unexplained bytes after the final declared row.
15    pub trailing: Vec<u8>,
16}
17fn error(message: &str) -> Error {
18    Error {
19        offset: 0,
20        message: message.into(),
21    }
22}
23/// Encrypt or decrypt the KO stream cipher without changing the input.
24pub fn crypt_table(input: &[u8], encrypt: bool) -> Vec<u8> {
25    let mut key = 0x816_u32;
26    input
27        .iter()
28        .map(|&byte| {
29            let value = byte ^ (key >> 8) as u8;
30            let ciphertext = if encrypt { value } else { byte };
31            key = ((u32::from(ciphertext) + key) * 0x6081 + 0x1608) & 0xffff;
32            value
33        })
34        .collect()
35}
36fn validate(types: &[u8]) -> Result<()> {
37    if types.is_empty()
38        || types.len() > 1024
39        || types[0] != 6
40        || types.iter().any(|kind| !(1..=9).contains(kind))
41    {
42        return Err(error("Invalid TBL column schema"));
43    }
44    Ok(())
45}
46/// Decrypt a table, checking storage bounds before allocating rows.
47pub fn parse_table(input: &[u8]) -> Result<Table> {
48    if input.len() > 256 * 1024 * 1024 {
49        return Err(error("TBL byte budget exceeded"));
50    }
51    let bytes = crypt_table(input, false);
52    let mut reader = Reader::new(&bytes);
53    let columns = reader.count(1024, 4)?;
54    let mut types = Vec::with_capacity(columns);
55    for _ in 0..columns {
56        types.push(u8::try_from(reader.i32()?).map_err(|_| error("Invalid column type"))?);
57    }
58    validate(&types)?;
59    let minimum: usize = types
60        .iter()
61        .map(|&kind| [0, 1, 1, 2, 2, 4, 4, 4, 4, 8][kind as usize])
62        .sum();
63    let count = reader.count(8_000_000 / columns, minimum)?;
64    let mut rows = Vec::with_capacity(count);
65    for _ in 0..count {
66        let mut row = Vec::with_capacity(columns);
67        for kind in &types {
68            row.push(match kind {
69                1 => json!(reader.i8()?),
70                2 => json!(reader.u8()?),
71                3 => json!(reader.i16()?),
72                4 => json!(reader.u16()?),
73                5 => json!(reader.i32()?),
74                6 | 8 => json!(reader.u32()?),
75                7 => json!(hex(&reader.string(1_048_576)?)),
76                9 => json!(hex(reader.raw(8)?)),
77                _ => return Err(error("Unsupported TBL column")),
78            });
79        }
80        rows.push(row);
81    }
82    Ok(Table {
83        types,
84        rows,
85        trailing: reader.raw(reader.remaining())?.to_vec(),
86    })
87}
88/// Write a new or edited table and apply the original encryption algorithm.
89pub fn write_table(table: &Table) -> Result<Vec<u8>> {
90    validate(&table.types)?;
91    if table.rows.len() > 8_000_000 / table.types.len() {
92        return Err(error("TBL row budget exceeded"));
93    }
94    let mut writer = Writer::default();
95    writer.i32(table.types.len() as i32)?;
96    for &kind in &table.types {
97        writer.i32(i32::from(kind))?;
98    }
99    writer.i32(table.rows.len() as i32)?;
100    for row in &table.rows {
101        if row.len() != table.types.len() {
102            return Err(error("TBL row width mismatch"));
103        }
104        for (&kind, cell) in table.types.iter().zip(row) {
105            if kind == 7 || kind == 9 {
106                let bytes = unhex(
107                    cell.as_str()
108                        .ok_or_else(|| error("Expected cell hex bytes"))?,
109                )?;
110                if kind == 7 {
111                    writer.string(&bytes)?;
112                } else {
113                    if bytes.len() != 8 {
114                        return Err(error("f64 requires eight bytes"));
115                    }
116                    writer.raw(&bytes)?;
117                }
118            } else {
119                let value = cell
120                    .as_i64()
121                    .ok_or_else(|| error("Expected numeric cell"))?;
122                let invalid = |_| error("Cell outside column range");
123                match kind {
124                    1 => writer.i8(i8::try_from(value).map_err(invalid)?)?,
125                    2 => writer.u8(u8::try_from(value).map_err(invalid)?)?,
126                    3 => writer.i16(i16::try_from(value).map_err(invalid)?)?,
127                    4 => writer.u16(u16::try_from(value).map_err(invalid)?)?,
128                    5 => writer.i32(i32::try_from(value).map_err(invalid)?)?,
129                    6 | 8 => writer.u32(u32::try_from(value).map_err(invalid)?)?,
130                    _ => return Err(error("Unsupported column")),
131                }
132            }
133        }
134    }
135    writer.raw(&table.trailing)?;
136    Ok(crypt_table(&writer.finish(), true))
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    #[test]
143    fn every_column_roundtrips() -> Result<()> {
144        let table = Table {
145            types: vec![6, 1, 2, 3, 4, 5, 7, 8, 9],
146            rows: vec![vec![
147                json!(4294967295_u32),
148                json!(-128),
149                json!(255),
150                json!(-32768),
151                json!(65535),
152                json!(-2147483648_i32),
153                json!("00ff0000"),
154                json!(0x7fa12345_u32),
155                json!("010000000000f07f"),
156            ]],
157            trailing: vec![255],
158        };
159        let bytes = write_table(&table)?;
160        assert_eq!(parse_table(&bytes)?, table);
161        assert_eq!(write_table(&parse_table(&bytes)?)?, bytes);
162        Ok(())
163    }
164}