amazon_cloudfront_client_routing_lib/
lib.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This crate is a Rust version of CloudFront Client Routing Library. Functions
5//! are provided to encode a label and prepend it to a domain and to decode a
6//! label for verification purposes.
7
8mod bitwise;
9pub mod client_routing_label;
10pub mod encode_decode;
11pub mod errors;
12pub mod hash;
13pub mod ip;
14
15use client_routing_label::{ClientRoutingLabel, DecodedClientRoutingLabel};
16use errors::DecodeLengthError;
17use hash::hash_cgid;
18use ip::parse_client_ip;
19
20/// Returns domain with client routing key prepended as a subdomain.
21///
22/// The encode function takes in 3 parameters: `client_ip`, `content_group_id`,
23/// and `fqdn`. `client_ip` is parsed into
24/// [`ClientSubnetEncodingData`](crate::ip::ClientSubnetEncodingData). `cgid` is
25/// hashed into a 64 bit number via xxHash. That data is then encoded into a
26/// client routing label and then returned prepended as a subdomain to the
27/// `fqdn`.
28///
29/// # Examples:
30/// ```
31/// use amazon_cloudfront_client_routing_lib::encode_request_data;
32/// 
33/// // ipv4
34/// let mut encoded_label = encode_request_data("1.2.3.4", "mv-456", "example.com");
35/// assert_eq!("abacaqdaaaaaaaamnjg3oubcyvrgm.example.com", encoded_label);
36///
37/// // ipv6
38/// encoded_label = encode_request_data("0102:0304:0506:0708:090a:0b0c:0d0e:0f10", "mv-456", "example.com");
39/// assert_eq!("abqcaqdaqcqmaaaynjg3oubcyvrgm.example.com", encoded_label);
40///
41/// // invalid client_ip
42/// encoded_label = encode_request_data("1.2.a", "mv-456", "example.com");
43/// assert_eq!("abaaaaaaaaaaaaaanjg3oubcyvrgm.example.com", encoded_label);
44///
45/// // empty cgid
46/// encoded_label = encode_request_data("1.2.3.4", "", "example.com");
47/// assert_eq!("abacaqdaaaaaaaamaaaaaaaaaaaaa.example.com", encoded_label);
48/// ```
49pub fn encode_request_data(client_ip: &str, content_group_id: &str, fqdn: &str) -> String {
50    let client_subnet_encoding_data = parse_client_ip(client_ip);
51
52    let mut label = ClientRoutingLabel::default();
53
54    label.set_data(client_subnet_encoding_data, hash_cgid(content_group_id));
55
56    let client_routing_label = label.encode();
57    format!("{}.{}", client_routing_label, fqdn)
58}
59
60/// Returns a result containing either a [`DecodedClientRoutingLabel`] or a
61/// [`DecodeLengthError`].
62///
63/// The decode function takes in a &str param: `domain`. This domain can be a FQDN
64/// or just the dns label generated by the [`encode_request_data`] function. It
65/// decodes the string and formats it into a [`DecodedClientRoutingLabel`]. If the
66/// client routing label is not the first DNS label or is not included in `domain`
67/// a [`DecodeLengthError`] will be returned.
68///
69/// # Examples:
70/// ```
71/// use amazon_cloudfront_client_routing_lib::decode_request_data;
72///
73/// // valid client routing label
74/// let decoded_label = decode_request_data("abacaqdaaaaaaaamnjg3oubcyvrgm");
75/// match decoded_label {
76///     Ok(data) => {
77///         assert_eq!([1, 2, 3, 0, 0, 0, 0, 0], data.client_subnet);
78///         assert_eq!(24, data.subnet_mask);
79///         assert_eq!(false, data.is_ipv6);
80///         assert_eq!(15319960192071419084, data.cgid);
81///     },
82///     Err(e) => panic!("Decoding error when there shouldn't be: {}", e)
83/// };
84/// 
85/// // fqdn with valid client routing label
86/// let decoded_label = decode_request_data("abacaqdaaaaaaaamnjg3oubcyvrgm.example.com");
87/// match decoded_label {
88///     Ok(data) => {
89///         assert_eq!([1, 2, 3, 0, 0, 0, 0, 0], data.client_subnet);
90///         assert_eq!(24, data.subnet_mask);
91///         assert_eq!(false, data.is_ipv6);
92///         assert_eq!(15319960192071419084, data.cgid);
93///     },
94///     Err(e) => panic!("Decoding error when there shouldn't be: {}", e)
95/// };
96/// 
97/// // fqdn with subdomain and valid client routing label
98/// let decoded_label = decode_request_data("abacaqdaaaaaaaamnjg3oubcyvrgm.vod1.example.com");
99/// match decoded_label {
100///     Ok(data) => {
101///         assert_eq!([1, 2, 3, 0, 0, 0, 0, 0], data.client_subnet);
102///         assert_eq!(24, data.subnet_mask);
103///         assert_eq!(false, data.is_ipv6);
104///         assert_eq!(15319960192071419084, data.cgid);
105///     },
106///     Err(e) => panic!("Decoding error when there shouldn't be: {}", e)
107/// };
108/// 
109/// // fqdn without valid client routing label
110/// let decoded_label = decode_request_data("example.com");
111/// match decoded_label {
112///     Ok(data) => panic!("Should have thrown a DecodeLengthError"),
113///     Err(e) => {
114///         assert_eq!(format!("{}", e), "Passed 7 - expected 29 characters");
115///     }
116/// };
117/// 
118/// // client routing label needs to be the first DNS label
119/// let decoded_label = decode_request_data("vod1.abacaqdaaaaaaaamnjg3oubcyvrgm.example.com");
120/// match decoded_label {
121///     Ok(data) => panic!("Should have thrown a DecodeLengthError"),
122///     Err(e) => {
123///         assert_eq!(format!("{}", e), "Passed 4 - expected 29 characters");
124///     }
125/// };
126///
127/// // invalid
128/// let decoded_label = decode_request_data("abacaqdaaaaaaaamnjg3oubcy"); // invalid length
129/// match decoded_label {
130///     Ok(data) => panic!("Should have thrown a DecodeLengthError"),
131///     Err(e) => {
132///         assert_eq!(format!("{}", e), "Passed 25 - expected 29 characters");
133///     }
134/// };
135/// ```
136pub fn decode_request_data(
137    domain: &str,
138) -> Result<DecodedClientRoutingLabel, DecodeLengthError> {
139    let client_routing_label = domain.split(".").next().unwrap_or_default();
140    let client_routing_label: &mut [u8] = &mut Box::from(client_routing_label.as_bytes());
141    client_routing_label.make_ascii_lowercase();
142
143    let mut label = ClientRoutingLabel::default();
144
145    label.decode(client_routing_label)
146}