crowdstrike_cloudproto/services/lfo/request.rs
1use crate::services::lfo::CompressionFormats;
2use crate::services::{DEFAULT_AID_HEX, DEFAULT_CID_HEX};
3
4/// Ask for a single file on a remote LFO server by path.
5///
6/// By default requests indicate support for XZ compression, but this is configurable.
7/// Even if a request accepts compression, the server may decide to reply with an uncompressed
8/// response if the requested file is itself an archive on disk.
9///
10/// Requests contain the CID (Customer ID) and AID (Agent ID) of the client, but the LFO server
11/// will accept any value for these, so in practice no authentication is required.
12#[derive(Eq, PartialEq, Debug, Clone)]
13pub struct LfoRequest {
14 // The CID assigned to a Crowdstrike customer (same as the CCID without the last -N number)
15 // The LFO server doesn't really check if it belongs to anyone. Just try to pass a valid CID.
16 pub(crate) cid: [u8; 16],
17 // Agent ID. LFO isn't uptight like TS if the AID is not an active customer.
18 // In fact, you can give it all zeroes. LFO is friendly like that.
19 pub(crate) aid: [u8; 16],
20 // The real client supports values 0 or 1. We only support 0.
21 pub(crate) compression: u16,
22 // The file to download
23 pub(crate) remote_path: String,
24 // This field is probably the offset for chunked downloads. Not supported or tested yet.
25 // Large files can't be downloaded in one packet, so the client may get partial responses
26 // The offset allows downloading the rest of those large files in multiple queries
27 pub(crate) offset: u32,
28}
29
30impl LfoRequest {
31 /// Create a request for `remote_path` with default values
32 pub fn new_simple(remote_path: String) -> Self {
33 Self {
34 // LFO doesn't mind all zeroes
35 cid: hex::decode(DEFAULT_CID_HEX).unwrap().try_into().unwrap(),
36 aid: hex::decode(DEFAULT_AID_HEX).unwrap().try_into().unwrap(),
37 compression: 0,
38 remote_path,
39 offset: 0,
40 }
41 }
42
43 pub fn new_custom(
44 cid: [u8; 16],
45 aid: [u8; 16],
46 compression: CompressionFormats,
47 remote_path: String,
48 ) -> Self {
49 Self {
50 cid,
51 aid,
52 compression: compression as u16,
53 remote_path,
54 // Only 0 if supported for now
55 // The receive side WILL break right now if it sees a reply with non-zero offset
56 offset: 0,
57 }
58 }
59
60 pub(crate) fn to_payload(&self) -> Vec<u8> {
61 let mut payload = vec![];
62 payload.extend_from_slice(&self.cid); // CU "simple store" value
63 payload.extend_from_slice(&self.aid); // AG "simple store" value
64 payload.extend_from_slice(8u32.to_be_bytes().as_slice());
65 payload.extend_from_slice(&self.offset.to_be_bytes());
66 payload.extend_from_slice(&self.compression.to_be_bytes());
67 payload.extend_from_slice(self.remote_path.as_bytes());
68 payload
69 }
70
71 #[cfg(test)]
72 pub(crate) fn try_from_payload(payload: &[u8]) -> Result<Self, super::LfoError> {
73 use super::LfoError;
74 use byteorder::{ReadBytesExt, BE};
75 use std::io::Read;
76
77 let mut cursor = std::io::Cursor::new(payload);
78 let mut cid = [0u8; 16];
79 cursor.read_exact(&mut cid)?;
80 let mut aid = [0u8; 16];
81 cursor.read_exact(&mut aid)?;
82 _ = cursor.read_u32::<BE>()?;
83 let offset = cursor.read_u32::<BE>()?;
84 let compression = cursor.read_u16::<BE>()?;
85 let remote_path = String::from_utf8(payload[cursor.position() as usize..].into())
86 .map_err(|_| LfoError::InvalidRequest)?;
87 Ok(Self {
88 cid,
89 aid,
90 compression,
91 remote_path,
92 offset,
93 })
94 }
95}