├── .github ├── ISSUE_TEMPLATE │ └── amazon-cloudfront-client-routing-library-issue.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── src ├── bitwise.rs ├── client_routing_label.rs ├── encode_decode.rs ├── errors.rs ├── hash.rs ├── ip.rs └── lib.rs └── tests ├── decode.rs └── encode.rs /.github/ISSUE_TEMPLATE/amazon-cloudfront-client-routing-library-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: amazon-cloudfront-client-routing-library issue 3 | about: Template 4 | title: 'Describe the issue' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Security issue notifications 11 | 12 | If you discover a potential security issue in amazon-cloudfront-client-routing-library we ask that you notify 13 | AWS Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 14 | 15 | ### Problem: 16 | 17 | 18 | 19 | ### Solution: 20 | 21 | 24 | 25 | ### Requirements / Acceptance Criteria: 26 | 27 | 34 | 35 | ### Out of scope: 36 | 37 | 38 | 39 | [//]: # (NOTE: If you believe this might be a security issue, please email aws-security@amazon.com instead of creating a GitHub issue. For more details, see the AWS Vulnerability Reporting Guide: https://aws.amazon.com/security/vulnerability-reporting/ ) 40 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Resolved issues: 2 | 3 | resolves #ISSUE-NUMBER1, resolves #ISSUE-NUMBER2, etc. 4 | 5 | ### Description of changes: 6 | 7 | 8 | 9 | ### Call-outs: 10 | 11 | 12 | 13 | ### Testing: 14 | 15 | 18 | 19 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) guidance for commit messages and pull request titles. 36 | 5. Send us a pull request and answer the default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in amazon-cloudfront-client-routing-library we ask that you notify 55 | AWS Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 56 | 57 | ## Licensing 58 | 59 | `amazon-cloudfront-client-routing-library` source code and documentation is released under the [Apache 2.0 License](https://aws.amazon.com/apache-2-0). We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "amazon-cloudfront-client-routing-lib" 3 | description = "a library to encode and decode dns labels" 4 | version = "1.0.0" 5 | edition = "2021" 6 | rust-version = "1.63" 7 | repository = "https://github.com/aws/amazon-cloudfront-client-routing-library" 8 | license = "Apache-2.0" 9 | readme = "README.md" 10 | 11 | [lib] 12 | name ="amazon_cloudfront_client_routing_lib" 13 | crate-type = ["lib", "cdylib"] 14 | 15 | [dependencies] 16 | twox-hash = "1.6.3" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | amazon-cloudfront-client-routing-lib 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Amazon CloudFront Client Routing Library 2 | 3 | The Amazon CloudFront Client Routing Library is an open-source library designed for CloudFront's Client Routing feature, which is used direct client devices to CloudFront Points of Presence (POPs) with greater precision. Client Routing feature utilizes information present within a specially formatted DNS label, and this library provides functions to encode and decode such DNS labels. 4 | 5 | 6 | ### What is Client Routing? 7 | 8 | Client Routing is a new feature from CloudFront which utilizes client subnet information encoded in a DNS label to route traffic to CloudFront POPs. In addition to utilizing this library, this feature has associated prerequisites such as using Route53 and requiring certificate updates. Documentation for Client Routing will be released in the near future, but if you are interested in using this feature sooner, please reach out to AWS Support to know more. 9 | 10 | 11 | ### How to Use the Amazon CloudFront Client Routing Library? 12 | 13 | There are two main functions in the library: `encode_request_data` and `decode_request_data`. 14 | 15 | #### Encoding 16 | 17 | `encode_request_data` takes three parameters: `client_ip`, `content_group_id`, and `fqdn`. A Client Routing label is generated from this data and then that label is returned prepended as a subdomain to the `fqdn`. 18 | 19 | The `content_group_id` is set aside for future use and must be set to an empty string for now. 20 | 21 | 22 | ``` 23 | let encoded_label = amazon_cloudfront_client_routing_lib::encode_request_data("1.2.3.4", "", "example.com"); // encoded_label is abacaqdaaaaaaaamaaaaaaaaaaaaa.example.com 24 | ``` 25 | 26 | #### Decoding 27 | 28 | `decode_request_data` takes one parameter: `domain`. A result containing either a `DecodedClientRoutingLabel` struct or a `DecodeLengthError` is returned with each field set according to the `domain`. The `domain` can be either a FQDN or just the Client Routing label. 29 | 30 | ``` 31 | let decoded_label = amazon_cloudfront_client_routing_lib::decode_request_data("abacaqdaaaaaaaamaaaaaaaaaaaaa").unwrap(); 32 | // DecodedClientRoutingLabel { 33 | // client_sdk_version: 1, 34 | // is_ipv6: false, 35 | // client_subnet: [1, 2, 3, 0, 0, 0, 0, 0], 36 | // subnet_mask: 24, 37 | // cgid: 0, 38 | // } 39 | let decoded_label = amazon_cloudfront_client_routing_lib::decode_request_data("abacaqdaaaaaaaamaaaaaaaaaaaaa.example.com").unwrap(); 40 | // DecodedClientRoutingLabel { 41 | // client_sdk_version: 1, 42 | // is_ipv6: false, 43 | // client_subnet: [1, 2, 3, 4, 0, 0, 0, 0], 44 | // subnet_mask: 24, 45 | // cgid: 0, 46 | // } 47 | ``` 48 | 49 | If the first dns label of the domain is an invalid Client Routing label (eg. improper length) then the result will contain an error. 50 | 51 | ``` 52 | let decoded_label = amazon_cloudfront_client_routing_lib::decode_request_data("abacaqdaaaaaaaamnjg3oubcyv").unwrap(); 53 | // DecodeLengthError { 54 | // num_chars: 26, 55 | // expected_num_chars: 29 56 | // } 57 | ``` 58 | 59 | ## License 60 | 61 | This library is licensed under the Apache 2.0 License. 62 | -------------------------------------------------------------------------------- /src/bitwise.rs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | pub fn get_mask(num_bits: u8) -> u64 { 5 | ((1_u128 << num_bits) - 1) as u64 6 | } -------------------------------------------------------------------------------- /src/client_routing_label.rs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::bitwise::get_mask; 5 | use crate::encode_decode::Base32; 6 | use crate::errors::DecodeLengthError; 7 | use crate::ip::ClientSubnetEncodingData; 8 | 9 | const 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)] 38 | pub 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)] 87 | pub struct EncodableData { 88 | pub value: u64, 89 | pub num_bits: u8, 90 | } 91 | 92 | impl 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)] 194 | pub struct ClientRoutingLabel { 195 | pub encodable_data: [EncodableData; 5], 196 | pub encoding_system: Base32, 197 | } 198 | 199 | impl 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 | 228 | impl 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 { 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 | } 353 | -------------------------------------------------------------------------------- /src/encode_decode.rs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::{client_routing_label::EncodableData, errors::DecodeLengthError, bitwise::get_mask}; 5 | 6 | const BASE32_ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz234567"; 7 | const BASE32_NUM_BITS_IN_CHAR: u8 = 5; 8 | const MAX_DNS_LABEL_SIZE: u8 = 63; 9 | 10 | /// Struct for encoding, decoding, and validating [`EncodableData`] with Base32. 11 | /// 12 | /// Uses lowercase version of the RFC 4648 Base32 alphabet. Methods treat each 13 | /// set of 5 bits in [`EncodableData`] as a separate character. Invalid characters 14 | /// will be treated as 'a' instead of marking the entire label as invalid for 15 | /// efficiency. Contains no properties, for usage see individual functions or 16 | /// [`ClientRoutingLabel`](crate::client_routing_label::ClientRoutingLabel). 17 | #[derive(Copy, Clone, Debug)] 18 | pub struct Base32 {} 19 | 20 | impl Base32 { 21 | /// Returns a lowercase Base32 string encoded from `encodable_data`. 22 | /// 23 | /// Iterates over `encodable_data`, encoding bits from `value` until 24 | /// not enough bits remain to make a full char. Remaining bits are 25 | /// then used in the subsequent iteration. After iterating over 26 | /// everything, if there are not enough bits to make a char 0 will 27 | /// be used to pad the left over bits. Encoding uses a lowercase 28 | /// version of the RFC 4648 Base32 alphabet. 29 | /// 30 | /// # Examples: 31 | /// ``` 32 | /// use amazon_cloudfront_client_routing_lib::encode_decode::Base32; 33 | /// use amazon_cloudfront_client_routing_lib::client_routing_label::EncodableData; 34 | /// 35 | /// let encoding_system = Base32 {}; 36 | /// let encodable_data = &mut [ 37 | /// EncodableData { // 0b01010 => "k" 38 | /// value: 10, 39 | /// num_bits: 5 40 | /// }, 41 | /// EncodableData { // 0b00011_11011 => "d3" 42 | /// value: 123, 43 | /// num_bits: 10 44 | /// }, 45 | /// EncodableData { // 0b0 => "a" 46 | /// value: 0, 47 | /// num_bits: 1 48 | /// }, 49 | /// ]; 50 | /// 51 | /// assert_eq!("kd3a", encoding_system.encode(encodable_data)); 52 | /// ``` 53 | pub fn encode(&self, encodable_data: &mut [EncodableData]) -> String { 54 | let value_mask: u64 = get_mask(BASE32_NUM_BITS_IN_CHAR); 55 | let mut encoded_data: Vec = Vec::with_capacity(MAX_DNS_LABEL_SIZE as usize); 56 | let mut value_to_encode: u8 = 0; 57 | let mut num_bits_left_over: u8 = 0; 58 | for data in encodable_data.iter_mut() { 59 | while data.has_bits_for_char(BASE32_NUM_BITS_IN_CHAR - num_bits_left_over) { 60 | value_to_encode += data.get_next_bits_to_encode(BASE32_NUM_BITS_IN_CHAR - num_bits_left_over); 61 | encoded_data.push(BASE32_ALPHABET[value_to_encode as usize] as char); 62 | 63 | num_bits_left_over = 0; 64 | value_to_encode = 0; 65 | } 66 | 67 | value_to_encode |= ((data.value << (BASE32_NUM_BITS_IN_CHAR - (data.num_bits + num_bits_left_over))) & value_mask) as u8; 68 | num_bits_left_over += data.num_bits; 69 | } 70 | 71 | if num_bits_left_over > 0 { 72 | encoded_data.push(BASE32_ALPHABET[value_to_encode as usize] as char); 73 | } 74 | 75 | encoded_data.iter().collect() 76 | } 77 | 78 | /// Validates `client_routing_label` is the proper length to fit `total_num_bits`. 79 | /// 80 | /// Calculates how many chars would be encoded for `total_num_bits` and then 81 | /// checks if the `client_routing_label` has that many chars. Returns a [`Result`] 82 | /// with '()' if it's valid or a [`DecodeLengthError`] if it's not valid. 83 | /// 84 | /// # Examples: 85 | /// ``` 86 | /// use amazon_cloudfront_client_routing_lib::encode_decode::Base32; 87 | /// 88 | /// let encoding_system = Base32 {}; 89 | /// 90 | /// // valid 91 | /// match encoding_system.is_valid_client_routing_label(145, b"abaaaaaaaaaaaaaaaaaaaackvj5oa") { 92 | /// Ok(()) => (), 93 | /// Err(_e) => panic!("Threw error when shouldn't have.") 94 | /// }; 95 | /// 96 | /// // invalid 97 | /// match encoding_system.is_valid_client_routing_label(145, b"abaaaaaaaaaaaaaaaaaaaackvj5oabcd") { 98 | /// Ok(()) => (), 99 | /// Err(e) => assert_eq!("Passed 32 - expected 29 characters", e.to_string()) 100 | /// }; 101 | /// ``` 102 | pub fn is_valid_client_routing_label( 103 | &self, 104 | total_num_bits: u8, 105 | client_routing_label: &[u8], 106 | ) -> Result<(), DecodeLengthError> { 107 | if client_routing_label.len() as u8 108 | != (total_num_bits + BASE32_NUM_BITS_IN_CHAR - 1) / BASE32_NUM_BITS_IN_CHAR 109 | { 110 | let e = DecodeLengthError { 111 | num_chars: client_routing_label.len(), 112 | expected_num_chars: ((total_num_bits + BASE32_NUM_BITS_IN_CHAR - 1) 113 | / BASE32_NUM_BITS_IN_CHAR) as usize, 114 | }; 115 | return Err(e); 116 | } 117 | 118 | Ok(()) 119 | } 120 | 121 | /// Sets `encodable_data` based on passed `encoded_label`. 122 | /// 123 | /// Validates `encoded_label` is valid based on `total_num_bits`. If not valid, 124 | /// returns a [`Result`] containing [`DecodeLengthError`]. If valid, iterates 125 | /// over `encodable_data` and sets each value based on the label value. Invalid 126 | /// characters in a label are treated as if they had a value of 0. 127 | /// 128 | /// # Examples: 129 | /// ``` 130 | /// use amazon_cloudfront_client_routing_lib::encode_decode::Base32; 131 | /// use amazon_cloudfront_client_routing_lib::client_routing_label::EncodableData; 132 | /// 133 | /// let encoding_system = Base32 {}; 134 | /// 135 | /// // valid 136 | /// let encodable_data = &mut [ 137 | /// EncodableData { 138 | /// value: 0, 139 | /// num_bits: 5 140 | /// }, 141 | /// EncodableData { 142 | /// value: 0, 143 | /// num_bits: 10 144 | /// }, 145 | /// EncodableData { 146 | /// value: 0, 147 | /// num_bits: 1 148 | /// }, 149 | /// ]; 150 | /// 151 | /// match encoding_system.decode(encodable_data, b"kd3a", 16) { 152 | /// Ok(()) => { 153 | /// assert_eq!(10, encodable_data[0].value); 154 | /// assert_eq!(123, encodable_data[1].value); 155 | /// assert_eq!(0, encodable_data[2].value); 156 | /// }, 157 | /// Err(_e) => panic!("Threw error when shouldn't have.") 158 | /// }; 159 | /// 160 | /// // invalid 161 | /// match encoding_system.decode(encodable_data, b"kd3a", 10) { 162 | /// Ok(()) => panic!("Didn't throw error when should have."), 163 | /// Err(e) => assert_eq!("Passed 4 - expected 2 characters", e.to_string()) 164 | /// }; 165 | /// ``` 166 | pub fn decode( 167 | &self, 168 | encodable_data: &mut [EncodableData], 169 | encoded_label: &[u8], 170 | total_num_bits: u8, 171 | ) -> Result<(), DecodeLengthError> { 172 | match self.is_valid_client_routing_label(total_num_bits, encoded_label) { 173 | Ok(()) => (), 174 | Err(e) => return Err(e), 175 | }; 176 | 177 | let mut label_values: Vec = encoded_label 178 | .iter() 179 | .map(|a| BASE32_ALPHABET.iter().position(|b| a == b).unwrap_or(0) as u8) 180 | .collect(); 181 | 182 | let mut num_bits_in_char: u8 = BASE32_NUM_BITS_IN_CHAR; 183 | let mut label_index: usize = 0; 184 | for data in encodable_data.iter_mut() { 185 | let original_num_bits: u8 = data.num_bits; 186 | data.value = 0; 187 | 188 | while data.has_bits_for_char(num_bits_in_char) { 189 | data.add_bits(num_bits_in_char, label_values[label_index]); 190 | label_index += 1; 191 | num_bits_in_char = BASE32_NUM_BITS_IN_CHAR; 192 | } 193 | 194 | if data.num_bits > 0 { 195 | num_bits_in_char -= data.num_bits; 196 | data.add_bits(data.num_bits, label_values[label_index] >> num_bits_in_char); 197 | label_values[label_index] &= get_mask(num_bits_in_char) as u8; 198 | } 199 | 200 | data.num_bits = original_num_bits; 201 | } 202 | 203 | Ok(()) 204 | } 205 | } 206 | 207 | #[cfg(test)] 208 | mod tests { 209 | use super::*; 210 | use crate::client_routing_label::EncodableData; 211 | 212 | // All the data has values with bit size <= num_bits. 213 | // Total bits is divisible by 5 and can be encoded with no padding. 214 | #[test] 215 | fn validate_encode_value_proper_size_no_padding_needed() { 216 | let encoding_system = Base32 {}; 217 | let encodable_data = &mut [ 218 | EncodableData { 219 | value: 0, 220 | num_bits: 109, 221 | }, 222 | EncodableData { 223 | value: 1, 224 | num_bits: 5, 225 | }, 226 | EncodableData { 227 | value: 0, 228 | num_bits: 1, 229 | }, 230 | EncodableData { 231 | value: 6148494311290830848, 232 | num_bits: 64, 233 | }, 234 | EncodableData { 235 | value: 24, 236 | num_bits: 6, 237 | }, 238 | EncodableData { 239 | value: 957415, 240 | num_bits: 20, 241 | }, 242 | ]; 243 | 244 | assert_eq!("aaaaaaaaaaaaaaaaaaaaaackvj5oaaaaaaaay5g7h", encoding_system.encode(encodable_data)); 245 | } 246 | 247 | // Some data has a value with bit size > num_bits. 248 | // Total bits is divisible by 5 and can be encoded with no padding. 249 | #[test] 250 | fn validate_encode_value_proper_size_padding_needed() { 251 | let encoding_system = Base32 {}; 252 | let encodable_data = &mut [ 253 | EncodableData { 254 | value: 36, 255 | num_bits: 12, 256 | }, 257 | EncodableData { 258 | value: 3734643, 259 | num_bits: 22, 260 | }, 261 | EncodableData { 262 | value: 2367, 263 | num_bits: 14, 264 | }, 265 | ]; 266 | 267 | assert_eq!("ajhd6hgjh4", encoding_system.encode(encodable_data)); 268 | } 269 | 270 | // All the data has values with bit size <= num_bits. 271 | // Total bits is not divisible by 5 and will need padding to encode. 272 | #[test] 273 | fn validate_encode_value_too_large_no_padding_needed() { 274 | let encoding_system = Base32 {}; 275 | let encodable_data: &mut [EncodableData] = &mut [ 276 | EncodableData { 277 | value: 5346, 278 | num_bits: 5, 279 | }, 280 | EncodableData { 281 | value: 3474, 282 | num_bits: 56, 283 | }, 284 | EncodableData { 285 | value: 0, 286 | num_bits: 14, 287 | }, 288 | EncodableData { 289 | value: 0, 290 | num_bits: 8, 291 | }, 292 | EncodableData { 293 | value: 46374, 294 | num_bits: 83, 295 | }, 296 | ]; 297 | 298 | assert_eq!("caaaaaaaabwjaaaaaaaaaaaaaaaaaawuta", encoding_system.encode(encodable_data)); 299 | } 300 | 301 | // Some data has a value with bit size > num_bits. 302 | // Total bits is not divisible by 5 and will need padding to encode. 303 | #[test] 304 | fn validate_encode_value_too_large_padding_needed() { 305 | let encoding_system = Base32 {}; 306 | let encodable_data: &mut [EncodableData] = &mut [ 307 | EncodableData { 308 | value: 2423, 309 | num_bits: 5, 310 | }, 311 | EncodableData { 312 | value: 432, 313 | num_bits: 3, 314 | }, 315 | EncodableData { 316 | value: 31, 317 | num_bits: 12, 318 | }, 319 | EncodableData { 320 | value: 43, 321 | num_bits: 10, 322 | }, 323 | EncodableData { 324 | value: 64, 325 | num_bits: 6, 326 | }, 327 | ]; 328 | 329 | assert_eq!("xaa7blaa", encoding_system.encode(encodable_data)); 330 | } 331 | 332 | #[test] 333 | fn validate_encode_empty_data() { 334 | let encoding_system = Base32 {}; 335 | let encodable_data: &mut [EncodableData] = &mut []; 336 | 337 | assert_eq!("", encoding_system.encode(encodable_data)); 338 | } 339 | 340 | #[test] 341 | fn validate_encode_not_enough_data_for_char() { 342 | let encoding_system = Base32 {}; 343 | let encodable_data: &mut [EncodableData] = &mut [ 344 | EncodableData { 345 | value: 1, 346 | num_bits: 1, 347 | }, 348 | EncodableData { 349 | value: 2, 350 | num_bits: 2, 351 | }, 352 | ]; 353 | 354 | assert_eq!("y", encoding_system.encode(encodable_data)); 355 | } 356 | 357 | #[test] 358 | fn validate_decode_label_with_no_padding() { 359 | let encoding_system = Base32 {}; 360 | let encodable_data = &mut [ 361 | EncodableData { 362 | value: 0, 363 | num_bits: 109, 364 | }, 365 | EncodableData { 366 | value: 0, 367 | num_bits: 5, 368 | }, 369 | EncodableData { 370 | value: 0, 371 | num_bits: 1, 372 | }, 373 | EncodableData { 374 | value: 0, 375 | num_bits: 64, 376 | }, 377 | EncodableData { 378 | value: 0, 379 | num_bits: 6, 380 | }, 381 | EncodableData { 382 | value: 0, 383 | num_bits: 20, 384 | }, 385 | ]; 386 | 387 | match encoding_system.decode(encodable_data, b"aaaaaaaaaaaaaaaaaaaaaackvj5oaaaaaaaay5g7h", 205) { 388 | Ok(()) => { 389 | assert_eq!(0, encodable_data[0].value); 390 | assert_eq!(1, encodable_data[1].value); 391 | assert_eq!(0, encodable_data[2].value); 392 | assert_eq!(6148494311290830848, encodable_data[3].value); 393 | assert_eq!(24, encodable_data[4].value); 394 | assert_eq!(957415, encodable_data[5].value); 395 | }, 396 | Err(e) => panic!("Threw error when shouldn't have: {}", e.to_string()) 397 | }; 398 | } 399 | 400 | #[test] 401 | fn validate_decode_label_with_padding() { 402 | let encoding_system = Base32 {}; 403 | let encodable_data = &mut [ 404 | EncodableData { 405 | value: 0, 406 | num_bits: 12, 407 | }, 408 | EncodableData { 409 | value: 0, 410 | num_bits: 22, 411 | }, 412 | EncodableData { 413 | value: 0, 414 | num_bits: 14, 415 | }, 416 | ]; 417 | 418 | match encoding_system.decode(encodable_data, b"ajhd6hgjh4", 48) { 419 | Ok(()) => { 420 | assert_eq!(36, encodable_data[0].value); 421 | assert_eq!(3734643, encodable_data[1].value); 422 | assert_eq!(2367, encodable_data[2].value); 423 | }, 424 | Err(e) => panic!("Threw error when shouldn't have: {}", e.to_string()) 425 | }; 426 | } 427 | 428 | #[test] 429 | fn validate_decode_data_already_has_value() { 430 | let encoding_system = Base32 {}; 431 | let encodable_data = &mut [ 432 | EncodableData { 433 | value: 2423, 434 | num_bits: 5, 435 | }, 436 | EncodableData { 437 | value: 53, 438 | num_bits: 3, 439 | }, 440 | EncodableData { 441 | value: 43, 442 | num_bits: 12, 443 | }, 444 | EncodableData { 445 | value: 754, 446 | num_bits: 10, 447 | }, 448 | EncodableData { 449 | value: 34, 450 | num_bits: 6, 451 | }, 452 | ]; 453 | 454 | match encoding_system.decode(encodable_data, b"xaa7blaa", 36) { 455 | Ok(()) => { 456 | assert_eq!(23, encodable_data[0].value); 457 | assert_eq!(0, encodable_data[1].value); 458 | assert_eq!(31, encodable_data[2].value); 459 | assert_eq!(43, encodable_data[3].value); 460 | assert_eq!(0, encodable_data[4].value); 461 | }, 462 | Err(e) => panic!("Threw error when shouldn't have: {}", e.to_string()) 463 | }; 464 | } 465 | 466 | #[test] 467 | fn validate_decode_empty_label() { 468 | let encoding_system = Base32 {}; 469 | let encodable_data = &mut []; 470 | 471 | match encoding_system.decode(encodable_data, b"", 0) { 472 | Ok(()) => {}, 473 | Err(e) => panic!("Threw error when shouldn't have: {}", e.to_string()) 474 | }; 475 | } 476 | 477 | #[test] 478 | fn validate_decode_label_too_large() { 479 | let encoding_system = Base32 {}; 480 | let encodable_data = &mut [ 481 | EncodableData { 482 | value: 0, 483 | num_bits: 12, 484 | }, 485 | EncodableData { 486 | value: 0, 487 | num_bits: 22, 488 | }, 489 | EncodableData { 490 | value: 0, 491 | num_bits: 14, 492 | }, 493 | ]; 494 | 495 | match encoding_system.decode(encodable_data, b"abacabacdfed", 46) { 496 | Ok(()) => panic!("Didn't throw error when should have"), 497 | Err(e) => assert_eq!("Passed 12 - expected 10 characters", e.to_string()) 498 | }; 499 | } 500 | 501 | #[test] 502 | fn validate_decode_label_too_small() { 503 | let encoding_system = Base32 {}; 504 | let encodable_data = &mut [ 505 | EncodableData { 506 | value: 0, 507 | num_bits: 12, 508 | }, 509 | EncodableData { 510 | value: 0, 511 | num_bits: 22, 512 | }, 513 | EncodableData { 514 | value: 0, 515 | num_bits: 14, 516 | }, 517 | ]; 518 | 519 | match encoding_system.decode(encodable_data, b"aba", 46) { 520 | Ok(()) => panic!("Didn't throw error when should have"), 521 | Err(e) => assert_eq!("Passed 3 - expected 10 characters", e.to_string()) 522 | }; 523 | } 524 | } 525 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::fmt; 5 | 6 | /// Error struct used when decoding a client routing label key of an improper 7 | /// length. 8 | /// 9 | /// # Examples: 10 | /// ``` 11 | /// use amazon_cloudfront_client_routing_lib::errors::DecodeLengthError; 12 | /// 13 | /// let error = DecodeLengthError { 14 | /// num_chars: 10, 15 | /// expected_num_chars: 29, 16 | /// }; 17 | /// 18 | /// assert_eq!("Passed 10 - expected 29 characters", error.to_string()); 19 | /// ``` 20 | #[derive(Debug, Copy, Clone)] 21 | pub struct DecodeLengthError { 22 | pub num_chars: usize, 23 | pub expected_num_chars: usize, 24 | } 25 | 26 | impl std::error::Error for DecodeLengthError {} 27 | 28 | impl fmt::Display for DecodeLengthError { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | write!( 31 | f, 32 | "Passed {} - expected {} characters", 33 | self.num_chars, self.expected_num_chars, 34 | ) 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::DecodeLengthError; 41 | 42 | #[test] 43 | fn validate_decode_length_error_text() { 44 | let error = DecodeLengthError { 45 | num_chars: 10, 46 | expected_num_chars: 29, 47 | }; 48 | 49 | assert_eq!(error.to_string(), "Passed 10 - expected 29 characters"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::hash::Hasher; 5 | use twox_hash::XxHash64; 6 | 7 | /// Utilizes xxHash to hash a `cgid` into a 64 bit number and returns that 8 | /// number. 9 | /// 10 | /// Passing an empty string as the `cgid` will result in 0 being returned 11 | /// instead of the hash of `cgid`. 12 | /// 13 | /// # Examples 14 | /// ``` 15 | /// use amazon_cloudfront_client_routing_lib::hash::hash_cgid; 16 | /// 17 | /// // valid cgid 18 | /// let mut hashed_cgid = hash_cgid("f3663718-7699-4e6e-b482-daa2f690cf64"); 19 | /// assert_eq!(8517775255794402596, hashed_cgid); 20 | /// 21 | /// // empty cgid 22 | /// hashed_cgid = hash_cgid(""); 23 | /// assert_eq!(0, hashed_cgid); 24 | /// ``` 25 | pub fn hash_cgid(cgid: &str) -> u64 { 26 | if cgid.is_empty() { 27 | return 0; 28 | } 29 | 30 | let mut hasher = XxHash64::default(); 31 | hasher.write(cgid.as_bytes()); 32 | 33 | hasher.finish() 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::hash_cgid; 39 | 40 | #[test] 41 | fn validate_hash_cgid() { 42 | assert_eq!(9402033733208250942, hash_cgid("SM89P")); 43 | assert_eq!(16745045142164894816, hash_cgid("DP0124QHYT")); 44 | assert_eq!(15007018045908736946, hash_cgid("b086vx9VmK")); 45 | assert_eq!(15151312625956013430, hash_cgid("abcdefghijhjuio")); 46 | assert_eq!( 47 | 8696017447135811798, 48 | hash_cgid("VZ9C5G6H12PC5GH7Y0ABCDEFGHIJHJUIOZZAA1") 49 | ); 50 | } 51 | 52 | #[test] 53 | fn validate_hash_similar_cgids_not_equal() { 54 | assert_ne!(hash_cgid("SM89P"), hash_cgid("sm89p")); 55 | assert_ne!(hash_cgid("abcdefghijhjuio0"), hash_cgid("abcdefghijhjuio")); 56 | assert_ne!(hash_cgid("B086VX9VMK "), hash_cgid("B086VX9VMK")); 57 | assert_ne!( 58 | hash_cgid("hfquwah9tds\u{00}"), 59 | hash_cgid("hfquwah9tds\u{01}") 60 | ); 61 | } 62 | 63 | #[test] 64 | fn validate_hash_empty_cgid_zero() { 65 | assert_eq!(0, hash_cgid("")); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ip.rs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 5 | 6 | enum SubnetMask { 7 | Ipv4 = 24, 8 | Ipv6 = 48, 9 | } 10 | 11 | /// Struct containing 3 values needed for encoding: `client_subnet`, 12 | /// `subnet_mask`, and `is_ipv6`. 13 | /// 14 | /// Each property is a u64 because 15 | /// [`EncodableData`](crate::client_routing_label::EncodableData) expects a u64. 16 | /// `client_subnet` should be formatted as a u64 by shifting each octet into an 17 | /// unsigned integer, applying the subnet mask, then shifting until the number 18 | /// conforms to 64 bits. 19 | /// 20 | /// # Examples 21 | /// ``` 22 | /// use amazon_cloudfront_client_routing_lib::ip::ClientSubnetEncodingData; 23 | /// 24 | /// // raw values 25 | /// let client_ip = [1, 2, 3, 4]; 26 | /// let subnet_mask = 24; 27 | /// let is_ipv6 = 0; 28 | /// 29 | /// // shift bytes into int 30 | /// let mut client_subnet = u32::from_be_bytes(client_ip) as u64; 31 | /// // apply subnet mask 32 | /// client_subnet &= 0xffffff00; 33 | /// // conform to 64 bits 34 | /// client_subnet <<= 32; 35 | /// 36 | /// let client_subnet_encoding_data = ClientSubnetEncodingData { 37 | /// client_subnet, 38 | /// subnet_mask, 39 | /// is_ipv6, 40 | /// }; 41 | /// ``` 42 | pub struct ClientSubnetEncodingData { 43 | pub client_subnet: u64, 44 | pub subnet_mask: u64, 45 | pub is_ipv6: u64, 46 | } 47 | 48 | /// Parses passed `client_ip` into various data, returns 49 | /// [`ClientSubnetEncodingData`]. 50 | /// 51 | /// Takes in one param: `client_ip`. Attempts to parse the `client_ip` into an 52 | /// [`IpAddr`]. If successful, determines if it's an [`Ipv4Addr`] or an 53 | /// [`Ipv6Addr`]. Returns [`ClientSubnetEncodingData`] with the parsed 54 | /// information. If unsuccessful, returns [`ClientSubnetEncodingData`] with all 55 | /// properties set to 0. 56 | /// 57 | /// # Examples: 58 | /// ``` 59 | /// use amazon_cloudfront_client_routing_lib::ip::parse_client_ip; 60 | /// 61 | /// // Ipv4 62 | /// let mut client_subnet_encoding_data = parse_client_ip("1.2.3.4"); 63 | /// assert_eq!([1, 2, 3, 0, 0, 0, 0, 0], client_subnet_encoding_data.client_subnet.to_be_bytes()); 64 | /// assert_eq!(24, client_subnet_encoding_data.subnet_mask); 65 | /// assert_eq!(0, client_subnet_encoding_data.is_ipv6); 66 | /// 67 | /// // Ipv6 68 | /// client_subnet_encoding_data = parse_client_ip("0102:0304:0506:0708:090a:0b0c:0d0e:0f10"); 69 | /// assert_eq!([1, 2, 3, 4, 5, 6, 0, 0], client_subnet_encoding_data.client_subnet.to_be_bytes()); 70 | /// assert_eq!(48, client_subnet_encoding_data.subnet_mask); 71 | /// assert_eq!(1, client_subnet_encoding_data.is_ipv6); 72 | /// 73 | /// // Invalid client ip 74 | /// client_subnet_encoding_data = parse_client_ip("1.2.a"); 75 | /// assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], client_subnet_encoding_data.client_subnet.to_be_bytes()); 76 | /// assert_eq!(0, client_subnet_encoding_data.subnet_mask); 77 | /// assert_eq!(0, client_subnet_encoding_data.is_ipv6); 78 | /// ``` 79 | pub fn parse_client_ip(client_ip: &str) -> ClientSubnetEncodingData { 80 | if let Ok(addr) = client_ip.parse::() { 81 | if addr.is_ipv4() { 82 | // unwrap is ok here because we verify it is parsable before 83 | let ipv4_address: Ipv4Addr = client_ip.parse().unwrap(); 84 | ClientSubnetEncodingData { 85 | client_subnet: (u32::from_be_bytes(ipv4_address.octets()) as u64 & 0xffffff00) 86 | << 32, 87 | subnet_mask: SubnetMask::Ipv4 as u64, 88 | is_ipv6: 0, 89 | } 90 | } else { 91 | // unwrap is ok here because we verify it is parsable before 92 | let ipv6_address: Ipv6Addr = client_ip.parse().unwrap(); 93 | ClientSubnetEncodingData { 94 | client_subnet: ((u128::from_be_bytes(ipv6_address.octets()) >> 64) 95 | & 0xffffffffffff0000) as u64, 96 | subnet_mask: SubnetMask::Ipv6 as u64, 97 | is_ipv6: 1, 98 | } 99 | } 100 | } else { 101 | ClientSubnetEncodingData { 102 | client_subnet: 0, 103 | subnet_mask: 0, 104 | is_ipv6: 0, 105 | } 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use super::parse_client_ip; 112 | 113 | #[test] 114 | fn validate_parse_ipv4() { 115 | let client_subnet_encoding_data = parse_client_ip("85.83.215.126"); 116 | 117 | assert_eq!( 118 | 6148494311290830848, 119 | client_subnet_encoding_data.client_subnet 120 | ); 121 | assert_eq!(24, client_subnet_encoding_data.subnet_mask); 122 | assert_eq!(0, client_subnet_encoding_data.is_ipv6); 123 | } 124 | 125 | #[test] 126 | fn validate_parse_ipv6() { 127 | let client_subnet_encoding_data = 128 | parse_client_ip("819e:5c2e:21e4:0094:4805:1635:f8e4:049b"); 129 | 130 | assert_eq!( 131 | 9340004030419828736, 132 | client_subnet_encoding_data.client_subnet 133 | ); 134 | assert_eq!(48, client_subnet_encoding_data.subnet_mask); 135 | assert_eq!(1, client_subnet_encoding_data.is_ipv6); 136 | } 137 | 138 | #[test] 139 | fn validate_parse_abbreviated_ipv6() { 140 | let client_subnet_encoding_data = parse_client_ip("0319:7db1:f4d6::"); 141 | 142 | assert_eq!( 143 | 223347859801899008, 144 | client_subnet_encoding_data.client_subnet 145 | ); 146 | assert_eq!(48, client_subnet_encoding_data.subnet_mask); 147 | assert_eq!(1, client_subnet_encoding_data.is_ipv6); 148 | } 149 | 150 | #[test] 151 | fn validate_parse_invalid_client_ip() { 152 | let client_subnet_encoding_data = parse_client_ip("1.2"); 153 | 154 | assert_eq!(0, client_subnet_encoding_data.client_subnet); 155 | assert_eq!(0, client_subnet_encoding_data.subnet_mask); 156 | assert_eq!(0, client_subnet_encoding_data.is_ipv6); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/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 | 8 | mod bitwise; 9 | pub mod client_routing_label; 10 | pub mod encode_decode; 11 | pub mod errors; 12 | pub mod hash; 13 | pub mod ip; 14 | 15 | use client_routing_label::{ClientRoutingLabel, DecodedClientRoutingLabel}; 16 | use errors::DecodeLengthError; 17 | use hash::hash_cgid; 18 | use 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 | /// ``` 49 | pub 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 | /// ``` 136 | pub fn decode_request_data( 137 | domain: &str, 138 | ) -> Result { 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 | } 147 | -------------------------------------------------------------------------------- /tests/decode.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test_encode_request_data { 3 | use amazon_cloudfront_client_routing_lib::decode_request_data; 4 | 5 | #[test] 6 | fn validate_decode_with_ipv4() { 7 | let decoded_label = match decode_request_data("abfku6xaaaaaaaamotptyubibrji6") { 8 | Ok(label) => label, 9 | Err(e) => panic!("{}", e), 10 | }; 11 | 12 | assert_eq!(1, decoded_label.client_sdk_version); 13 | assert_eq!(16843032286346126622, decoded_label.cgid); 14 | assert_eq!(24, decoded_label.subnet_mask); 15 | assert_eq!(false, decoded_label.is_ipv6); 16 | assert_eq!([85, 83, 215, 0, 0, 0, 0, 0], decoded_label.client_subnet); 17 | } 18 | 19 | #[test] 20 | fn validate_decode_with_ipv6() { 21 | let decoded_label = match decode_request_data("abydhs4fyq6iaaaykudpmaxncecqs") { 22 | Ok(label) => label, 23 | Err(e) => panic!("{}", e), 24 | }; 25 | 26 | assert_eq!(1, decoded_label.client_sdk_version); 27 | assert_eq!(12253709671023643154, decoded_label.cgid); 28 | assert_eq!(48, decoded_label.subnet_mask); 29 | assert_eq!(true, decoded_label.is_ipv6); 30 | assert_eq!( 31 | [0x81, 0x9e, 0x5c, 0x2e, 0x21, 0xe4, 0, 0], 32 | decoded_label.client_subnet 33 | ); 34 | } 35 | 36 | #[test] 37 | fn validate_decode_with_fqdn() { 38 | let decoded_label = match decode_request_data("abydhs4fyq6iaaaykudpmaxncecqs.example.com") { 39 | Ok(label) => label, 40 | Err(e) => panic!("{}", e), 41 | }; 42 | 43 | assert_eq!(1, decoded_label.client_sdk_version); 44 | assert_eq!(12253709671023643154, decoded_label.cgid); 45 | assert_eq!(48, decoded_label.subnet_mask); 46 | assert_eq!(true, decoded_label.is_ipv6); 47 | assert_eq!( 48 | [0x81, 0x9e, 0x5c, 0x2e, 0x21, 0xe4, 0, 0], 49 | decoded_label.client_subnet 50 | ); 51 | 52 | let decoded_label = match decode_request_data("abfku6xaaaaaaaamotptyubibrji6.vod1.example.com") { 53 | Ok(label) => label, 54 | Err(e) => panic!("{}", e), 55 | }; 56 | 57 | assert_eq!(1, decoded_label.client_sdk_version); 58 | assert_eq!(16843032286346126622, decoded_label.cgid); 59 | assert_eq!(24, decoded_label.subnet_mask); 60 | assert_eq!(false, decoded_label.is_ipv6); 61 | assert_eq!([85, 83, 215, 0, 0, 0, 0, 0], decoded_label.client_subnet); 62 | } 63 | 64 | #[test] 65 | fn validate_decode_with_no_client_subnet() { 66 | let decoded_label = match decode_request_data("abaaaaaaaaaaaaaaoqysz2z3j45da") { 67 | Ok(label) => label, 68 | Err(e) => panic!("{}", e), 69 | }; 70 | 71 | assert_eq!(1, decoded_label.client_sdk_version); 72 | assert_eq!(16745045142164894816, decoded_label.cgid); 73 | assert_eq!(0, decoded_label.subnet_mask); 74 | assert_eq!(false, decoded_label.is_ipv6); 75 | assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], decoded_label.client_subnet); 76 | } 77 | 78 | #[test] 79 | fn validate_decode_with_no_cgid() { 80 | let decoded_label = match decode_request_data("abc4aydaaaaaaaamaaaaaaaaaaaaa") { 81 | Ok(label) => label, 82 | Err(e) => panic!("{}", e), 83 | }; 84 | 85 | assert_eq!(1, decoded_label.client_sdk_version); 86 | assert_eq!(0, decoded_label.cgid); 87 | assert_eq!(24, decoded_label.subnet_mask); 88 | assert_eq!(false, decoded_label.is_ipv6); 89 | assert_eq!([46, 3, 3, 0, 0, 0, 0, 0], decoded_label.client_subnet); 90 | } 91 | 92 | #[test] 93 | fn validate_decode_with_no_client_subnet_no_cgid() { 94 | let decoded_label = match decode_request_data("abaaaaaaaaaaaaaaaaaaaaaaaaaaa") { 95 | Ok(label) => label, 96 | Err(e) => panic!("{}", e), 97 | }; 98 | 99 | assert_eq!(1, decoded_label.client_sdk_version); 100 | assert_eq!(0, decoded_label.cgid); 101 | assert_eq!(0, decoded_label.subnet_mask); 102 | assert_eq!(false, decoded_label.is_ipv6); 103 | assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], decoded_label.client_subnet); 104 | } 105 | 106 | #[test] 107 | fn validate_decode_with_too_small_client_routing_label_returns_error() { 108 | match decode_request_data("abydhs4fyq6iaaaykudpmaxnce") { 109 | Ok(_dns_label) => { 110 | panic!("Didn't return an error when it should have") 111 | } 112 | Err(_e) => (), 113 | }; 114 | } 115 | 116 | #[test] 117 | fn validate_decode_with_too_large_client_routing_label_returns_error() { 118 | match decode_request_data("abydhs4fyq6iaaaykudpmaxncecqsaaaa") { 119 | Ok(_dns_label) => { 120 | panic!("Didn't return an error when it should have") 121 | } 122 | Err(_e) => (), 123 | }; 124 | } 125 | 126 | #[test] 127 | fn validate_decode_with_empty_domain_returns_error() { 128 | match decode_request_data("") { 129 | Ok(_dns_label) => { 130 | panic!("Didn't return an error when it should have") 131 | } 132 | Err(_e) => (), 133 | }; 134 | } 135 | 136 | #[test] 137 | fn validate_decode_with_client_routing_label_not_first_dns_label_returns_error() { 138 | match decode_request_data("vod1.abfku6xaaaaaaaamotptyubibrji6.example.com") { 139 | Ok(_dns_label) => { 140 | panic!("Didn't return an error when it should have") 141 | } 142 | Err(_e) => (), 143 | }; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/encode.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test_encode_request_data { 3 | use amazon_cloudfront_client_routing_lib::encode_request_data; 4 | 5 | #[test] 6 | fn validate_encode_with_ipv4() { 7 | let encoded_label = encode_request_data("85.83.215.126", "B086VX9VMK", "example.com"); 8 | 9 | assert_eq!("abfku6xaaaaaaaamotptyubibrji6.example.com", encoded_label); 10 | } 11 | 12 | #[test] 13 | fn validate_encode_with_ipv6() { 14 | let encoded_label = encode_request_data( 15 | "819e:5c2e:21e4:0094:4805:1635:f8e4:049b", 16 | "Q9OP1I23", 17 | "example.com", 18 | ); 19 | 20 | assert_eq!("abydhs4fyq6iaaaykudpmaxncecqs.example.com", encoded_label); 21 | } 22 | 23 | #[test] 24 | fn validate_encode_with_abbreviated_ipv6() { 25 | let encoded_label = encode_request_data("2c0f:f386:9f5b:a3ad::", "ZZAA12TP", "example.com"); 26 | 27 | assert_eq!("absyd7tq2pvwaaayipu4qwb2rlz4g.example.com", encoded_label); 28 | } 29 | 30 | #[test] 31 | fn validate_encode_with_subdomain_in_fqdn() { 32 | let encoded_label = encode_request_data( 33 | "122.71.138.53", 34 | "12PC5GH7Y0ABCDEFGHIJHJUIOZZAA1", 35 | "test.example2.com", 36 | ); 37 | 38 | assert_eq!( 39 | "abhur4kaaaaaaaampbtn52pincn7x.test.example2.com", 40 | encoded_label 41 | ); 42 | } 43 | 44 | #[test] 45 | fn validate_encode_with_path_in_fqdn() { 46 | let encoded_label = encode_request_data( 47 | "0319:7db1:f4d6:62ec:10cf:ffe4:4270:d2d5", 48 | "AC2Q2389", 49 | "example.com/movie/12ab4c?query=watch", 50 | ); 51 | 52 | assert_eq!( 53 | "abqggl5wh2nmaaaypv4i33wdvvtdk.example.com/movie/12ab4c?query=watch", 54 | encoded_label 55 | ); 56 | } 57 | 58 | #[test] 59 | fn validate_encode_with_invalid_client_ip() { 60 | let encoded_label = encode_request_data("122.71", "DP0124QHYT", "example.com"); 61 | 62 | assert_eq!("abaaaaaaaaaaaaaaoqysz2z3j45da.example.com", encoded_label); 63 | } 64 | 65 | #[test] 66 | fn validate_encode_with_no_cgid() { 67 | let encoded_label = encode_request_data("46.3.3.135", "", "example.com"); 68 | 69 | assert_eq!("abc4aydaaaaaaaamaaaaaaaaaaaaa.example.com", encoded_label); 70 | } 71 | 72 | #[test] 73 | fn validate_encode_with_no_fqdn() { 74 | let encoded_label = 75 | encode_request_data("6687:1cc9:0e87:2b33:1181:eff2:9a6a:786b", "DF97B6J1O0", ""); 76 | 77 | assert_eq!("abwnby4zehioaaaymv5p6exntn7z3.", encoded_label); 78 | } 79 | 80 | #[test] 81 | fn validate_encode_with_no_client_ip_no_cgid() { 82 | let encoded_label = encode_request_data("", "", "example.com"); 83 | 84 | assert_eq!("abaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com", encoded_label); 85 | } 86 | 87 | #[test] 88 | fn validate_encode_with_no_client_ip_no_cgid_no_fqdn() { 89 | let encoded_label = encode_request_data("", "", ""); 90 | 91 | assert_eq!("abaaaaaaaaaaaaaaaaaaaaaaaaaaa.", encoded_label); 92 | } 93 | } 94 | --------------------------------------------------------------------------------