Skip to main content

ryg_rans_rs_cli/container/
block.rs

1//! # Block record — header, model, payload
2
3use crate::container::codec;
4use crate::container::{
5    BLOCK_HEADER_SIZE, BLOCK_KIND_RANS, BLOCK_KIND_RAW, BLOCK_KIND_RLE, BLOCK_TAG,
6};
7use crate::error::{AppError, FormatError, IntegrityError};
8use sha2::{Digest, Sha256};
9
10/// A decoded block record.
11#[derive(Debug, Clone)]
12pub struct Block {
13    pub block_index: u64,
14    pub block_kind: u8,
15    pub codec_id: u16,
16    pub scale_bits: u8,
17    pub state_count: u8,
18    pub uncompressed_length: u32,
19    pub payload: Vec<u8>,
20    pub model_data: Vec<u8>,
21    pub payload_sha256: [u8; 32],
22    pub decoded_sha256: [u8; 32],
23}
24
25impl Block {
26    /// Create a new RANS block.
27    pub fn new_rans(
28        block_index: u64,
29        codec_id: u16,
30        scale_bits: u8,
31        state_count: u8,
32        uncompressed_length: u32,
33        payload: Vec<u8>,
34        model_data: Vec<u8>,
35    ) -> Self {
36        let payload_sha256 = sha256(&payload);
37        let decoded_sha256 = [0u8; 32]; // computed by caller after decode
38        Self {
39            block_index,
40            block_kind: BLOCK_KIND_RANS,
41            codec_id,
42            scale_bits,
43            state_count,
44            uncompressed_length,
45            payload,
46            model_data,
47            payload_sha256,
48            decoded_sha256,
49        }
50    }
51
52    /// Create a new RAW block.
53    pub fn new_raw(block_index: u64, data: Vec<u8>) -> Self {
54        let payload_sha256 = sha256(&data);
55        let decoded_sha256 = payload_sha256; // raw: payload == decoded
56        Self {
57            block_index,
58            block_kind: BLOCK_KIND_RAW,
59            codec_id: 0,
60            scale_bits: 0,
61            state_count: 0,
62            uncompressed_length: data.len() as u32,
63            payload: data,
64            model_data: Vec::new(),
65            payload_sha256,
66            decoded_sha256,
67        }
68    }
69
70    /// Create a new RLE block.
71    pub fn new_rle(block_index: u64, symbol: u8, count: u32) -> Self {
72        let payload = vec![symbol];
73        let payload_sha256 = sha256(&payload);
74        let decoded_data = vec![symbol; count as usize];
75        let decoded_sha256 = sha256(&decoded_data);
76        Self {
77            block_index,
78            block_kind: BLOCK_KIND_RLE,
79            codec_id: 0,
80            scale_bits: 0,
81            state_count: 0,
82            uncompressed_length: count,
83            payload,
84            model_data: Vec::new(),
85            payload_sha256,
86            decoded_sha256,
87        }
88    }
89
90    /// Serialize block header to bytes (104 bytes).
91    pub fn header_to_bytes(&self) -> Vec<u8> {
92        let mut buf = Vec::with_capacity(BLOCK_HEADER_SIZE);
93        buf.extend_from_slice(BLOCK_TAG);
94        buf.extend_from_slice(&(BLOCK_HEADER_SIZE as u16).to_le_bytes());
95        buf.push(1); // block_version
96        buf.push(self.block_kind);
97        buf.extend_from_slice(&self.block_index.to_le_bytes());
98        buf.extend_from_slice(&self.codec_id.to_le_bytes());
99        buf.push(self.scale_bits);
100        buf.push(self.state_count);
101        buf.push(if self.model_data.is_empty() { 0 } else { 0 }); // model_encoding
102        buf.push(0); // integrity_id
103        buf.extend_from_slice(&[0u8; 2]); // reserved
104        buf.extend_from_slice(&self.uncompressed_length.to_le_bytes());
105        buf.extend_from_slice(&(self.payload.len() as u32).to_le_bytes());
106        buf.extend_from_slice(&(self.model_data.len() as u32).to_le_bytes());
107        buf.extend_from_slice(&[0u8; 4]); // reserved2
108        buf.extend_from_slice(&self.payload_sha256);
109        buf.extend_from_slice(&self.decoded_sha256);
110        buf
111    }
112
113    /// Parse a block header from bytes.  Returns the header + number of bytes consumed.
114    pub fn parse_header(
115        bytes: &[u8],
116        expected_index: u64,
117    ) -> Result<(BlockHeaderInfo, usize), AppError> {
118        if bytes.len() < BLOCK_HEADER_SIZE {
119            return Err(AppError::Format(FormatError {
120                detail: "block header too short".into(),
121                block_index: Some(expected_index),
122                offset: Some(0),
123            }));
124        }
125
126        let tag = &bytes[0..4];
127        if tag != BLOCK_TAG {
128            return Err(AppError::Format(FormatError {
129                detail: format!("bad block tag: {:02x?}", tag),
130                block_index: Some(expected_index),
131                offset: Some(0),
132            }));
133        }
134
135        let _block_header_length = u16::from_le_bytes([bytes[4], bytes[5]]);
136        let block_version = bytes[6];
137        if block_version != 1 {
138            return Err(AppError::Format(FormatError {
139                detail: format!("unsupported block version: {}", block_version),
140                block_index: Some(expected_index),
141                offset: Some(6),
142            }));
143        }
144
145        let block_kind = bytes[7];
146        if block_kind > 2 {
147            return Err(AppError::Format(FormatError {
148                detail: format!("unknown block kind: {}", block_kind),
149                block_index: Some(expected_index),
150                offset: Some(7),
151            }));
152        }
153
154        let block_index = u64::from_le_bytes([
155            bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
156        ]);
157        if block_index != expected_index {
158            return Err(AppError::Format(FormatError {
159                detail: format!("expected block {}, got {}", expected_index, block_index),
160                block_index: Some(expected_index),
161                offset: Some(8),
162            }));
163        }
164
165        let codec_id = u16::from_le_bytes([bytes[16], bytes[17]]);
166        if block_kind == BLOCK_KIND_RANS && !codec::is_supported(codec_id) {
167            return Err(AppError::Format(FormatError {
168                detail: format!("unsupported codec id: {}", codec_id),
169                block_index: Some(block_index),
170                offset: Some(16),
171            }));
172        }
173
174        let scale_bits = bytes[18];
175        let state_count = bytes[19];
176
177        // reserved bytes 22..23 must be zero
178        if bytes[22] != 0 || bytes[23] != 0 {
179            return Err(AppError::Format(FormatError {
180                detail: "non-zero reserved bytes in block header".into(),
181                block_index: Some(block_index),
182                offset: Some(22),
183            }));
184        }
185
186        let uncompressed_length = u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]);
187        let payload_length = u32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]);
188        let model_length = u32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]);
189
190        // reserved2 bytes 36..39 must be zero
191        if bytes[36..40].iter().any(|&b| b != 0) {
192            return Err(AppError::Format(FormatError {
193                detail: "non-zero reserved2 bytes in block header".into(),
194                block_index: Some(block_index),
195                offset: Some(36),
196            }));
197        }
198
199        let mut payload_sha256 = [0u8; 32];
200        payload_sha256.copy_from_slice(&bytes[40..72]);
201        let mut decoded_sha256 = [0u8; 32];
202        decoded_sha256.copy_from_slice(&bytes[72..104]);
203
204        Ok((
205            BlockHeaderInfo {
206                block_index,
207                block_kind,
208                codec_id,
209                scale_bits,
210                state_count,
211                uncompressed_length,
212                payload_length,
213                model_length,
214                payload_sha256,
215                decoded_sha256,
216            },
217            BLOCK_HEADER_SIZE,
218        ))
219    }
220
221    /// Serialize full block record (header + model + payload) to bytes.
222    pub fn to_bytes(&self) -> Vec<u8> {
223        let mut buf = self.header_to_bytes();
224        buf.extend_from_slice(&self.model_data);
225        buf.extend_from_slice(&self.payload);
226        buf
227    }
228}
229
230/// Parsed block header info (before reading model/payload).
231#[derive(Debug, Clone)]
232pub struct BlockHeaderInfo {
233    pub block_index: u64,
234    pub block_kind: u8,
235    pub codec_id: u16,
236    pub scale_bits: u8,
237    pub state_count: u8,
238    pub uncompressed_length: u32,
239    pub payload_length: u32,
240    pub model_length: u32,
241    pub payload_sha256: [u8; 32],
242    pub decoded_sha256: [u8; 32],
243}
244
245/// Compute SHA-256 of a byte slice.
246fn sha256(data: &[u8]) -> [u8; 32] {
247    let mut hasher = Sha256::new();
248    hasher.update(data);
249    let result = hasher.finalize();
250    let mut hash = [0u8; 32];
251    hash.copy_from_slice(&result);
252    hash
253}