actnel_lib/
lib.rs

1use std::fmt::Formatter;
2
3use rand::prelude::*;
4use serde::{Deserialize, Serialize};
5use sha2::Digest;
6
7#[derive(Serialize, Deserialize, Debug, Clone)]
8#[serde(transparent)]
9pub struct SecretKey(pub String);
10impl SecretKey {
11    pub fn generate() -> Self {
12        let mut rng = rand::thread_rng();
13        Self(
14            std::iter::repeat(())
15                .map(|_| rng.sample(rand::distributions::Alphanumeric))
16                .take(22)
17                .collect::<String>(),
18        )
19    }
20
21    pub fn client_id(&self) -> ClientId {
22        ClientId(base64::encode(
23            &sha2::Sha256::digest(self.0.as_bytes()).to_vec(),
24        ))
25    }
26}
27
28#[derive(Serialize, Deserialize, Debug, Clone)]
29#[serde(transparent)]
30pub struct ReconnectToken(pub String);
31
32#[derive(Serialize, Deserialize, Clone)]
33pub struct ClientQuotas {
34    #[serde(rename = "requestCount")]
35    pub request_count: i32,
36    #[serde(rename = "requestLimit")]
37    pub request_limit: i32,
38}
39
40impl std::fmt::Debug for ClientQuotas {
41    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("ClientQuotas")
43            .field("count", &self.request_count)
44            .field("limit", &self.request_limit)
45            .finish()
46    }
47}
48
49#[derive(Serialize, Deserialize, Debug, Clone)]
50#[serde(rename_all = "snake_case")]
51pub enum ServerHello {
52    Success {
53        sub_domain: String,
54        hostname: String,
55        client_id: ClientId,
56        quotas: ClientQuotas,
57    },
58    SubDomainInUse,
59    InvalidSubDomain,
60    AuthFailed,
61    Error(String),
62}
63
64impl ServerHello {
65    #[allow(unused)]
66    pub fn random_domain() -> String {
67        let mut rng = rand::thread_rng();
68        std::iter::repeat(())
69            .map(|_| rng.sample(rand::distributions::Alphanumeric))
70            .take(8)
71            .collect::<String>()
72            .to_lowercase()
73    }
74
75    #[allow(unused)]
76    pub fn prefixed_random_domain(prefix: &str) -> String {
77        format!("{}-{}", prefix, Self::random_domain())
78    }
79}
80
81#[derive(Serialize, Deserialize, Debug, Clone)]
82pub struct ClientHello {
83    pub device_id: DeviceId,
84    pub sub_domain: Option<String>,
85    pub client_type: ClientType,
86    pub reconnect_token: Option<ReconnectToken>,
87}
88
89impl ClientHello {
90    pub fn generate(sub_domain: Option<String>, typ: ClientType) -> Self {
91        ClientHello {
92            device_id: DeviceId::generate(),
93            client_type: typ,
94            sub_domain,
95            reconnect_token: None,
96        }
97    }
98
99    pub fn reconnect(reconnect_token: ReconnectToken) -> Self {
100        ClientHello {
101            device_id: DeviceId::generate(),
102            sub_domain: None,
103            client_type: ClientType::Anonymous,
104            reconnect_token: Some(reconnect_token),
105        }
106    }
107}
108
109#[derive(Serialize, Deserialize, Debug, Clone)]
110pub enum ClientType {
111    Auth { key: SecretKey },
112    Anonymous,
113}
114
115#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
116#[serde(transparent)]
117pub struct ClientId(String);
118
119impl std::fmt::Display for ClientId {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        self.0.fmt(f)
122    }
123}
124impl ClientId {
125    pub fn generate() -> Self {
126        let mut id = [0u8; 32];
127        rand::thread_rng().fill_bytes(&mut id);
128        ClientId(base64::encode_config(&id, base64::URL_SAFE_NO_PAD))
129    }
130
131    pub fn safe_id(self) -> ClientId {
132        ClientId(base64::encode(
133            &sha2::Sha256::digest(self.0.as_bytes()).to_vec(),
134        ))
135    }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Hash)]
139pub struct StreamId([u8; 8]);
140
141impl StreamId {
142    pub fn generate() -> StreamId {
143        let mut id = [0u8; 8];
144        rand::thread_rng().fill_bytes(&mut id);
145        StreamId(id)
146    }
147
148    pub fn to_string(&self) -> String {
149        format!(
150            "stream_{}",
151            base64::encode_config(&self.0, base64::URL_SAFE_NO_PAD)
152        )
153    }
154}
155
156#[derive(Debug, Clone)]
157pub enum ControlPacket {
158    Init(StreamId),
159    Data(StreamId, Vec<u8>),
160    Refused(StreamId),
161    End(StreamId),
162    Ping(Option<ReconnectToken>),
163}
164
165pub const PING_INTERVAL: u64 = 30;
166
167const EMPTY_STREAM: StreamId = StreamId([0xF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
168const TOKEN_STREAM: StreamId = StreamId([0xF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]);
169
170impl ControlPacket {
171    pub fn serialize(self) -> Vec<u8> {
172        match self {
173            ControlPacket::Init(sid) => [vec![0x01], sid.0.to_vec()].concat(),
174            ControlPacket::Data(sid, data) => [vec![0x02], sid.0.to_vec(), data].concat(),
175            ControlPacket::Refused(sid) => [vec![0x03], sid.0.to_vec()].concat(),
176            ControlPacket::End(sid) => [vec![0x04], sid.0.to_vec()].concat(),
177            ControlPacket::Ping(tok) => {
178                let data = tok.map_or(EMPTY_STREAM.0.to_vec(), |t| {
179                    vec![TOKEN_STREAM.0.to_vec(), t.0.into_bytes()].concat()
180                });
181                [vec![0x05], data].concat()
182            }
183        }
184    }
185
186    pub fn packet_type(&self) -> &str {
187        match &self {
188            ControlPacket::Ping(_) => "PING",
189            ControlPacket::Init(_) => "INIT STREAM",
190            ControlPacket::Data(_, _) => "STREAM DATA",
191            ControlPacket::Refused(_) => "REFUSED",
192            ControlPacket::End(_) => "END STREAM",
193        }
194    }
195
196    pub fn deserialize(data: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
197        if data.len() < 9 {
198            return Err("invalid DataPacket, missing stream id".into());
199        }
200
201        let mut stream_id = [0u8; 8];
202        stream_id.clone_from_slice(&data[1..9]);
203        let stream_id = StreamId(stream_id);
204
205        let packet = match data[0] {
206            0x01 => ControlPacket::Init(stream_id),
207            0x02 => ControlPacket::Data(stream_id, data[9..].to_vec()),
208            0x03 => ControlPacket::Refused(stream_id),
209            0x04 => ControlPacket::End(stream_id),
210            0x05 => {
211                if stream_id == EMPTY_STREAM {
212                    ControlPacket::Ping(None)
213                } else {
214                    ControlPacket::Ping(Some(ReconnectToken(
215                        String::from_utf8_lossy(&data[9..]).to_string(),
216                    )))
217                }
218            }
219            _ => return Err("invalid control byte in DataPacket".into()),
220        };
221
222        Ok(packet)
223    }
224}
225
226#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
227#[serde(transparent)]
228pub struct DeviceId(String);
229
230impl std::fmt::Display for DeviceId {
231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232        self.0.fmt(f)
233    }
234}
235impl DeviceId {
236    pub fn generate() -> Self {
237        let mut builder = machineid_rs::IdBuilder::new(machineid_rs::Encryption::SHA256);
238        builder.add_component(machineid_rs::HWIDComponent::SystemID);
239        builder.add_component(machineid_rs::HWIDComponent::Username);
240        let key = std::env::var("ACTNEL_DEVICE_KEY").unwrap();
241        let hwid = builder.build(&key).unwrap();
242        DeviceId(hwid)
243    }
244}