amazon_cloudfront_client_routing_lib/
client_routing_label.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::bitwise::get_mask;
5use crate::encode_decode::Base32;
6use crate::errors::DecodeLengthError;
7use crate::ip::ClientSubnetEncodingData;
8
9const CLIENT_ROUTING_LABEL_VERSION: u16 = 1;
10
11/// Struct containing decoded client routing label values.
12///
13/// Consist of 5 properties: `client_sdk_version`, `is_ipv6`, `client_subnet`,
14/// `subnet_mask`, and `cgid`. Each property maps directly to a value in
15/// [`ClientRoutingLabel`], with the minimal type needed to represent that data.
16/// Only the least significant bits will be kept in the case `value` can't fit in
17/// `num_bits`.
18///
19/// # Examples:
20/// ```
21/// use amazon_cloudfront_client_routing_lib::client_routing_label::DecodedClientRoutingLabel;
22///
23/// let client_sdk_version: u16 = 1;
24/// let is_ipv6: bool = false;
25/// let client_subnet: [u8; 8] = [1, 2, 3, 0, 0, 0, 0, 0];
26/// let subnet_mask: u8 = 24;
27/// let cgid: u64 = 15151312625956013430;
28///
29/// let decoded_client_routing_label = DecodedClientRoutingLabel {
30///     client_sdk_version,
31///     is_ipv6,
32///     client_subnet,
33///     subnet_mask,
34///     cgid
35/// };
36/// ```
37#[derive(Copy, Clone, Debug)]
38pub struct DecodedClientRoutingLabel {
39    pub client_sdk_version: u16,
40    pub is_ipv6: bool,
41    pub client_subnet: [u8; 8],
42    pub subnet_mask: u8,
43    pub cgid: u64,
44}
45
46/// Struct containing data to encode in a [`ClientRoutingLabel`].
47///
48/// Consist of 2 properties: `value`, and `num_bits`. `value` is a u64 and
49/// should be set to the actual data to encode. `num_bits` is a u8 and should be
50/// set to how many bits should be encoded. This ensures a particular value will
51/// always be encoded to the same bit position in a label, regardless of the
52/// actual size of value.
53///
54/// # Examples:
55/// ```
56/// use amazon_cloudfront_client_routing_lib::client_routing_label::EncodableData;
57/// use amazon_cloudfront_client_routing_lib::encode_decode::Base32;
58///
59/// let mut data: EncodableData;
60/// let encoding_system = Base32 {};
61///
62/// // value is 1 bit and needs to encode as 10 bits: 0b0000000001
63/// data = EncodableData {
64///     value: 1,
65///     num_bits: 10,
66/// };
67///
68/// assert_eq!("ab", encoding_system.encode(&mut [data]));
69///
70/// // value is 4 bits and needs to encode as 5 bits: 0b10000
71/// data = EncodableData {
72///     value: 16,
73///     num_bits: 5
74/// };
75///
76/// assert_eq!("q", encoding_system.encode(&mut [data]));
77///
78/// // value is 6 bits and needs to encode as 5 bits: 0b00000
79/// // only the least significant bits are retained
80/// data = EncodableData {
81///     value: 32,
82///     num_bits: 5
83/// };
84///
85/// assert_eq!("a", encoding_system.encode(&mut [data]));
86#[derive(Copy, Clone, Debug)]
87pub struct EncodableData {
88    pub value: u64,
89    pub num_bits: u8,
90}
91
92impl EncodableData {
93    /// Returns `num_bits_needed` from the front of [`EncodableData`].
94    /// 
95    /// Masks and shifts `value` so the bits in the proper location are returned.
96    /// If [`EncodableData`] has a larger `num_bits` than bits in the actual `value`,
97    /// 0 will be returned. Decreases `num_bits` by `num_bits_needed` to keep track
98    /// of how many bits are left to encode.
99    /// 
100    /// `num_bits_needed` needs to be an integer 1-8 because the max bit size for a
101    /// character for any encoding system up to base 256 is 8 bits. This function will
102    /// also throw an error if `num_bits_needed` is bigger than `num_bits`.
103    /// 
104    /// # Examples:
105    /// ```
106    /// use amazon_cloudfront_client_routing_lib::client_routing_label::EncodableData;
107    /// 
108    /// let mut encodable_data = EncodableData {
109    ///     value: 10, // value can be represented by 4 bits: 0b1010
110    ///     num_bits: 6 // specifying 6 bits means it should be encoded as: 0b001010
111    /// };
112    /// 
113    /// assert_eq!(2, encodable_data.get_next_bits_to_encode(4)); // 0b0010
114    /// assert_eq!(2, encodable_data.get_next_bits_to_encode(2)); // 0b10
115    /// ```
116    pub fn get_next_bits_to_encode(&mut self, num_bits_needed: u8) -> u8 {
117        self.num_bits -= num_bits_needed;
118        let mask: u128 = (get_mask(num_bits_needed) as u128) << self.num_bits;
119        let bits_to_encode = (self.value as u128 & mask) >> self.num_bits;
120
121        self.value &= get_mask(self.num_bits);
122        
123        bits_to_encode as u8
124    }
125
126    /// Determines if there are enough bits in `num_bits` to make a char.
127    /// 
128    /// Takes one parameter: `num_bits_in_char`. `num_bits_in_char` should
129    /// be determined by the encoding system e.g. 5 bits for a char in base32 encoding.
130    /// 
131    /// # Examples:
132    /// ```
133    /// use amazon_cloudfront_client_routing_lib::client_routing_label::EncodableData;
134    /// 
135    /// let encodable_data = EncodableData {
136    ///     value: 10,
137    ///     num_bits: 6
138    /// };
139    /// 
140    /// assert_eq!(true, encodable_data.has_bits_for_char(5));
141    /// ```
142    pub fn has_bits_for_char(self, num_bits_in_char: u8) -> bool {
143        self.num_bits >= num_bits_in_char
144    }
145
146    /// Adds `value_to_add` to `value` and decrements `num_bits` by `num_bits_to_add`.
147    /// 
148    /// Intended to be used when decoding a value. `value` will be left shifted by
149    /// `num_bits_to_add` and then `value_to_add` gets shifted in. This ensures
150    /// bits can be added in their proper places. `num_bits` gets decremented to
151    /// keep track of how many bits are still needed to fill [`EncodableData`].
152    /// 
153    /// # Examples:
154    /// ```
155    /// use amazon_cloudfront_client_routing_lib::client_routing_label::EncodableData;
156    /// 
157    /// let mut encodable_data = EncodableData {
158    ///     value: 0,
159    ///     num_bits: 10
160    /// };
161    /// 
162    /// encodable_data.add_bits(6, 21);
163    /// assert_eq!(21, encodable_data.value);
164    /// 
165    /// encodable_data.add_bits(3, 6);
166    /// assert_eq!(174, encodable_data.value);
167    /// ```
168    pub fn add_bits(&mut self, num_bits_to_add: u8, value_to_add: u8) {
169        self.num_bits -= num_bits_to_add;
170        self.value <<= num_bits_to_add;
171        self.value |= value_to_add as u64;
172    }
173}
174
175/// Struct containing data to encode and what encoding system to use.
176///
177/// Consist of 2 properties: `encodable_data` and `encoding_system`.
178/// `encodable_data` should be an array of 5 EncodableData items. The Default
179/// implementation should be used for creating this struct to ensure each item
180/// in the `encodable_data` contains the proper `num_bits` value.
181///
182/// # Examples
183/// ```
184/// use amazon_cloudfront_client_routing_lib::client_routing_label::ClientRoutingLabel;
185///
186/// let mut client_routing_label = ClientRoutingLabel::default();
187/// client_routing_label.encodable_data[0].value = 1; // sdk version
188/// client_routing_label.encodable_data[1].value = 1; // is ipv6
189/// client_routing_label.encodable_data[2].value = 9340004030419828736; // client subnet
190/// client_routing_label.encodable_data[3].value = 48; // subnet mask
191/// client_routing_label.encodable_data[4].value = 8517775255794402596; // cgid
192/// ```
193#[derive(Copy, Clone, Debug)]
194pub struct ClientRoutingLabel {
195    pub encodable_data: [EncodableData; 5],
196    pub encoding_system: Base32,
197}
198
199impl Default for ClientRoutingLabel {
200    fn default() -> Self {
201        let sdk_version = EncodableData {
202            value: CLIENT_ROUTING_LABEL_VERSION as u64,
203            num_bits: 10,
204        };
205        let is_ipv6: EncodableData = EncodableData {
206            value: 0,
207            num_bits: 1,
208        };
209        let client_subnet = EncodableData {
210            value: 0,
211            num_bits: 64,
212        };
213        let subnet_mask = EncodableData {
214            value: 0,
215            num_bits: 6,
216        };
217        let cgid = EncodableData {
218            value: 0,
219            num_bits: 64,
220        };
221        Self {
222            encodable_data: [sdk_version, is_ipv6, client_subnet, subnet_mask, cgid],
223            encoding_system: Base32 {},
224        }
225    }
226}
227
228impl ClientRoutingLabel {
229    /// Sets client subnet and cgid data in [`ClientRoutingLabel`].
230    ///
231    /// Takes in 2 parameters: `client_subnet_encoding_data` and `cgid`.
232    /// `client_subnet_encoding_data` should be a [`ClientSubnetEncodingData`]
233    /// struct and has the formatted values for `is_ipv6`, `client_subnet`, and
234    /// `subnet_mask`.
235    ///
236    /// # Examples:
237    /// ```
238    /// use amazon_cloudfront_client_routing_lib::client_routing_label::ClientRoutingLabel;
239    /// use amazon_cloudfront_client_routing_lib::ip::ClientSubnetEncodingData;
240    ///
241    /// let cgid = 8517775255794402596;
242    /// let client_subnet_encoding_data = ClientSubnetEncodingData {
243    ///     is_ipv6: 0,
244    ///     client_subnet: 6148494311290830848,
245    ///     subnet_mask: 24,
246    /// };
247    ///
248    /// let mut client_routing_label = ClientRoutingLabel::default();
249    /// client_routing_label.set_data(client_subnet_encoding_data, cgid);
250    /// ```
251    pub fn set_data(&mut self, client_subnet_encoding_data: ClientSubnetEncodingData, cgid: u64) {
252        self.encodable_data[1].value = client_subnet_encoding_data.is_ipv6;
253        self.encodable_data[2].value = client_subnet_encoding_data.client_subnet;
254        self.encodable_data[3].value = client_subnet_encoding_data.subnet_mask;
255        self.encodable_data[4].value = cgid;
256    }
257
258    /// Encodes `encodable_data` and returns encoded client routing label
259    ///
260    /// Calls the encode function of `encoding_system`. Each [`EncodableData`]
261    /// item in `encodable_data` is formatted to the proper number of bits and
262    /// encoded into a string.
263    ///
264    /// # Examples:
265    /// ```
266    /// use amazon_cloudfront_client_routing_lib::client_routing_label::ClientRoutingLabel;
267    /// use amazon_cloudfront_client_routing_lib::ip::ClientSubnetEncodingData;
268    ///
269    /// let cgid = 8517775255794402596;
270    /// let client_subnet_encoding_data = ClientSubnetEncodingData {
271    ///     is_ipv6: 0,
272    ///     client_subnet: 6148494311290830848,
273    ///     subnet_mask: 24,
274    /// };
275    ///
276    /// let mut client_routing_label = ClientRoutingLabel::default();
277    /// client_routing_label.set_data(client_subnet_encoding_data, cgid);
278    ///
279    /// assert_eq!("abfku6xaaaaaaaamhmnjxo5hdzrje", client_routing_label.encode());
280    /// ```
281    pub fn encode(&mut self) -> String {
282        self.encoding_system.encode(&mut self.encodable_data)
283    }
284
285    /// Decodes `client_routing_label` and returns a result containing either a
286    /// [`DecodedClientRoutingLabel`] or a [`DecodeLengthError`] if the
287    /// `client_routing_label` is invalid.
288    ///
289    /// # Examples:
290    /// ```
291    /// use amazon_cloudfront_client_routing_lib::client_routing_label::ClientRoutingLabel;
292    ///
293    /// let mut client_routing_label = ClientRoutingLabel::default();
294    ///
295    /// let decode_result = client_routing_label.decode(b"abfku6xaaaaaaaamhmnjxo5hdzrje");
296    ///
297    /// match decode_result {
298    ///     Ok(decoded_client_routing_label) => {
299    ///         assert_eq!(1, decoded_client_routing_label.client_sdk_version);
300    ///         assert_eq!(false, decoded_client_routing_label.is_ipv6);
301    ///         assert_eq!([85, 83, 215, 0, 0, 0, 0, 0], decoded_client_routing_label.client_subnet);
302    ///         assert_eq!(24, decoded_client_routing_label.subnet_mask);
303    ///         assert_eq!(8517775255794402596, decoded_client_routing_label.cgid);
304    ///     },
305    ///     Err(_e) => panic!("Decoding experienced an error when it shouldn't have")
306    /// };
307    /// ```
308    pub fn decode(
309        &mut self,
310        client_routing_label: &[u8],
311    ) -> Result<DecodedClientRoutingLabel, DecodeLengthError> {
312        let total_num_bits = self.get_total_num_bits();
313        let decoded_label = self.encoding_system.decode(
314            &mut self.encodable_data,
315            client_routing_label,
316            total_num_bits,
317        );
318
319        match decoded_label {
320            Ok(_value) => Ok(self.get_decoded_client_routing_label()),
321            Err(e) => Err(e),
322        }
323    }
324
325    /// Returns total num bits a label contains.
326    ///
327    /// Iterates over each item in `encodable_data` and sums the `num_bits` for
328    /// each item, then returns that sum.
329    ///
330    /// # Examples:
331    /// ```
332    /// use amazon_cloudfront_client_routing_lib::client_routing_label::ClientRoutingLabel;
333    ///
334    /// let mut client_routing_label = ClientRoutingLabel::default();
335    /// assert_eq!(145, client_routing_label.get_total_num_bits());
336    /// ```
337    pub fn get_total_num_bits(&mut self) -> u8 {
338        self.encodable_data.iter().fold(0, |a, b| a + b.num_bits)
339    }
340
341    /// Creates and returns [`DecodedClientRoutingLabel`] based on
342    /// `encodable_data`.
343    fn get_decoded_client_routing_label(&mut self) -> DecodedClientRoutingLabel {
344        DecodedClientRoutingLabel {
345            client_sdk_version: self.encodable_data[0].value as u16,
346            is_ipv6: self.encodable_data[1].value != 0,
347            client_subnet: self.encodable_data[2].value.to_be_bytes(),
348            subnet_mask: self.encodable_data[3].value as u8,
349            cgid: self.encodable_data[4].value,
350        }
351    }
352}