Skip to main content

spvirit_server/
decode.rs

1//! PUT body decoding utilities.
2//!
3//! The PVA PUT command carries a variable-format payload that encodes field
4//! updates.  Different clients encode it in slightly different ways, so the
5//! decoder tries several strategies in order.
6
7use spvirit_codec::spvd_decode::{DecodedValue, PvdDecoder, StructureDesc};
8use spvirit_codec::spvd_encode::encode_size_pvd;
9
10/// Decode a PUT body payload using the known structure descriptor.
11///
12/// Tries multiple strategies to handle the various PUT encodings produced by
13/// different PVA clients (standard bitset, status-prefixed, shifted bitset,
14/// value-only).
15pub fn decode_put_body(
16    body: &[u8],
17    desc: &StructureDesc,
18    is_be: bool,
19) -> Option<DecodedValue> {
20    let decoder = PvdDecoder::new(is_be);
21    if let Some((value, _)) = decoder.decode_structure_with_bitset(body, desc) {
22        if !decoded_is_empty(&value) {
23            return Some(value);
24        }
25    }
26    if !body.is_empty() && body[0] == 0xFF {
27        if let Some((value, _)) = decoder.decode_structure_with_bitset(&body[1..], desc) {
28            if !decoded_is_empty(&value) {
29                return Some(value);
30            }
31        }
32    }
33    if let Some(value) = decode_put_body_shifted_bitset(body, desc, is_be) {
34        return Some(value);
35    }
36    if let Some(value) = decode_put_body_value_only(body, desc, is_be) {
37        return Some(value);
38    }
39    None
40}
41
42fn decoded_is_empty(value: &DecodedValue) -> bool {
43    matches!(value, DecodedValue::Structure(fields) if fields.is_empty())
44}
45
46fn decode_put_body_shifted_bitset(
47    body: &[u8],
48    desc: &StructureDesc,
49    is_be: bool,
50) -> Option<DecodedValue> {
51    let decoder = PvdDecoder::new(is_be);
52    let (size, consumed) = decoder.decode_size(body)?;
53    if size == 0 || body.len() < consumed + size {
54        return None;
55    }
56    let bitset = &body[consumed..consumed + size];
57    let data = &body[consumed + size..];
58    let shifted = shift_bitset_left(bitset, 1);
59    let mut shifted_body = Vec::new();
60    shifted_body.extend_from_slice(&encode_size_pvd(shifted.len(), is_be));
61    shifted_body.extend_from_slice(&shifted);
62    shifted_body.extend_from_slice(data);
63    decoder
64        .decode_structure_with_bitset(&shifted_body, desc)
65        .map(|(value, _)| value)
66        .filter(|value| !decoded_is_empty(value))
67}
68
69fn decode_put_body_value_only(
70    body: &[u8],
71    desc: &StructureDesc,
72    is_be: bool,
73) -> Option<DecodedValue> {
74    let decoder = PvdDecoder::new(is_be);
75    if let Some((size, consumed)) = decoder.decode_size(body) {
76        if consumed + size <= body.len() {
77            let data = &body[consumed + size..];
78            if let Some(value) = decode_value_only_from_data(data, desc, &decoder) {
79                return Some(value);
80            }
81        }
82    }
83    decode_value_only_from_data(body, desc, &decoder)
84}
85
86fn decode_value_only_from_data(
87    data: &[u8],
88    desc: &StructureDesc,
89    decoder: &PvdDecoder,
90) -> Option<DecodedValue> {
91    let value_field = desc.fields.iter().find(|f| f.name == "value")?;
92    decoder
93        .decode_value(data, &value_field.field_type)
94        .map(|(value, _)| DecodedValue::Structure(vec![("value".to_string(), value)]))
95}
96
97/// Shift a bitset left by `shift` bit positions.
98pub fn shift_bitset_left(bitset: &[u8], shift: usize) -> Vec<u8> {
99    if shift == 0 {
100        return bitset.to_vec();
101    }
102    let total_bits = bitset.len() * 8;
103    let new_bits = total_bits + shift;
104    let mut out = vec![0u8; (new_bits + 7) / 8];
105    for bit in 0..total_bits {
106        if (bitset[bit / 8] & (1 << (bit % 8))) != 0 {
107            let new_bit = bit + shift;
108            out[new_bit / 8] |= 1 << (new_bit % 8);
109        }
110    }
111    out
112}
113
114/// Reassemble a segmented PVA message from the first header and accumulated
115/// payload fragments.
116pub fn assemble_segmented_message(first_header: [u8; 8], payloads: Vec<Vec<u8>>) -> Vec<u8> {
117    let mut header = first_header;
118    let is_be = (header[2] & 0x80) != 0;
119    header[2] &= !0x30;
120    let total_len: usize = payloads.iter().map(|p| p.len()).sum();
121    let len_bytes = if is_be {
122        (total_len as u32).to_be_bytes()
123    } else {
124        (total_len as u32).to_le_bytes()
125    };
126    header[4..8].copy_from_slice(&len_bytes);
127    let mut out = header.to_vec();
128    for payload in payloads {
129        out.extend_from_slice(&payload);
130    }
131    out
132}