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}