crowdstrike_cloudproto/services/lfo/
file_header.rs

1use byteorder::{ReadBytesExt, BE};
2use std::io::{Cursor, Read};
3use tracing::trace;
4
5/// The size of the header in an LFO ReplyOk, which is *not* the size of an LFO file header on disk
6pub(crate) const LFO_RESP_HDR_LEN: usize = 0x2A;
7pub(crate) const CRC_LEN: usize = 4;
8
9#[repr(u16)]
10#[derive(Debug, Copy, Clone, Eq, PartialEq)]
11pub enum CompressionFormats {
12    /// Transmit files uncompressed
13    None = 0,
14    /// Transmit XZ compressed files (LZMA algorithm)
15    Xz = 1,
16}
17
18/// Reproduces the internal format of the LFO file headers, as used by the official client
19/// If you just care about downloading a file, you probably don't need to look at this struct.
20#[derive(Debug, Copy, Clone, Eq, PartialEq)]
21pub struct LfoFileHeader {
22    /// Constant value ("RHDL")
23    pub magic: u32,
24    /// Unclear, I have only seen the constant value 1 passed around
25    pub unk_cst1: u16,
26    /// See [`CompressionFormats`](CompressionFormats) for known values
27    pub comp_format: u16,
28    /// The size of the requested file data, after any decompression
29    pub payload_size: u32,
30    /// Sha256 hash of the final data, without LFO header and after any decompression
31    pub data_hash: [u8; 32],
32    // 0x2C: Other fields again
33    /// In the official client, this field gets updated as it receives more data.
34    /// You should ignore this field.
35    pub cur_payload_size: u32,
36    /// In the official client, this starts at 1, goes up to 5 as we continue downloading.
37    /// Ignore this field.
38    pub cur_state: u16,
39    /// This field is physically present in LFO headers, but its purpose has not been documented.
40    pub unk: u16,
41}
42
43impl TryFrom<&[u8]> for LfoFileHeader {
44    type Error = String;
45
46    fn try_from(lfo_payload: &[u8]) -> Result<Self, Self::Error> {
47        // NOTE: These function assumes no chunked/range downloads (i.e. a single chunk)
48        // Otherwise it would need to take the previous LfoFileHeader and update it
49        // In practice even the 700+MiB kernel module packages fit in a single blob
50        // of only a few MiBs, since they're always sent and stored as XZ compressed archives
51
52        if lfo_payload.len() < LFO_RESP_HDR_LEN + CRC_LEN {
53            return Err("LFO OK header too small".into());
54        }
55        let header = &lfo_payload[..LFO_RESP_HDR_LEN];
56        let payload_data = &lfo_payload[LFO_RESP_HDR_LEN..]; // Includes trailing CRC!
57        let mut header_reader = Cursor::new(&header);
58        let chunk_start_off = header_reader.read_u32::<BE>().unwrap();
59        let chunk_end_off = header_reader.read_u32::<BE>().unwrap();
60        let mut pkt_unk_buf = [0; 32];
61        header_reader.read_exact(&mut pkt_unk_buf).unwrap();
62        let comp_format = header_reader.read_u16::<BE>().unwrap();
63        trace!("Received LFO header data: {}", hex::encode(header));
64
65        if chunk_start_off > chunk_end_off {
66            return Err(format!(
67                "LFO response start offset {:#x} is past end offset {:#x}",
68                chunk_start_off, chunk_end_off
69            ));
70        }
71
72        let len_without_crc = payload_data.len() - CRC_LEN;
73        if chunk_start_off != 0 {
74            return Err("Unexpected non-0 offset in LFO response".into());
75        }
76        let chunk_size = chunk_end_off - chunk_start_off;
77        if comp_format == 0 && chunk_size != len_without_crc as u32 {
78            return Err(format!(
79                "Expected {:#x} bytes LFO file data, but uncompressed payload is {:#x} bytes",
80                chunk_size, len_without_crc
81            ));
82        }
83
84        let expected_crc = u32::from_be_bytes(payload_data[len_without_crc..].try_into().unwrap());
85        let crc = crc32fast::hash(&payload_data[..len_without_crc]);
86        if crc != expected_crc {
87            return Err(format!(
88                "Expected CRC 0x{:X}, but computed 0x{:X}",
89                expected_crc, crc
90            ));
91        }
92
93        Ok(Self {
94            magic: 0x4C444852, // "RHDL"
95            unk_cst1: 1,
96            comp_format,
97            payload_size: chunk_end_off,
98            data_hash: pkt_unk_buf,
99            cur_payload_size: len_without_crc as u32,
100            cur_state: 5,
101            unk: 0,
102        })
103    }
104}