diff options
author | Yi Kong <yikong@google.com> | 2021-03-17 21:00:56 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-03-17 21:00:56 +0000 |
commit | 5f93fd47c495d44484b47b3d16560183ff85e4da (patch) | |
tree | 6b3f5ff8baf0cbcc19348b1807210b1239d10890 | |
parent | e84aa2e5214494bbf7c449026922a449454a503b (diff) | |
parent | fa024426499dec90254dafc770a373b5c7820698 (diff) | |
download | macaddr-5f93fd47c495d44484b47b3d16560183ff85e4da.tar.gz |
Import 'macaddr' crate version 1.0.1 am: 87d8889fe2 am: 528645dec9 am: ccdcc7c16e am: fa02442649
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/macaddr/+/1641879
Change-Id: If10bbe3cd0b17a20247cb6ede46d6cfa795e04a9
-rw-r--r-- | .cargo_vcs_info.json | 5 | ||||
-rw-r--r-- | .editorconfig | 9 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Android.bp | 14 | ||||
-rw-r--r-- | CHANGELOG.md | 31 | ||||
-rw-r--r-- | Cargo.toml | 40 | ||||
-rw-r--r-- | Cargo.toml.orig | 30 | ||||
l--------- | LICENSE | 1 | ||||
-rw-r--r-- | LICENSE-APACHE | 201 | ||||
-rw-r--r-- | LICENSE-MIT | 201 | ||||
-rw-r--r-- | METADATA | 20 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | OWNERS | 1 | ||||
-rw-r--r-- | README.md | 51 | ||||
-rw-r--r-- | rustfmt.toml | 9 | ||||
-rw-r--r-- | src/addr.rs | 108 | ||||
-rw-r--r-- | src/addr6.rs | 229 | ||||
-rw-r--r-- | src/addr8.rs | 227 | ||||
-rw-r--r-- | src/lib.rs | 48 | ||||
-rw-r--r-- | src/parser/mod.rs | 201 | ||||
-rw-r--r-- | src/parser/tests.rs | 176 |
21 files changed, 1605 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..9f9232a --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "4d23e48e8e16404b79c2a112acd72c462f92ba1e" + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f6da06d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..eb846ce --- /dev/null +++ b/Android.bp @@ -0,0 +1,14 @@ +// This file is generated by cargo2android.py --run --device --dependencies. +// Do not modify this file as changes will be overridden on upgrade. + +rust_library { + name: "libmacaddr", + host_supported: true, + crate_name: "macaddr", + srcs: ["src/lib.rs"], + edition: "2018", + features: [ + "default", + "std", + ], +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5fcf599 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.1] - 2020-02-28 + +### Added + +- `#![forbid(unsafe_code)]` attribute + +## [1.0.0] - 2020-01-02 + +### Added + +- `{}` formatting render colon-separated MAC address, e.g. `AB:0D:EF:12:34:56` +- `{:-}` formatting flag render hyphen-separated MAC address, e.g. `AB-0D-EF-12-34-56` +- `{:#}` formatting flag render period-separated MAC address, e.g. `AB0.DEF.123.456` + +## [0.1.2] - 2019-10-17 + +### Added + +- `MacAddr6::nil` and `MacAddr8::nil` methods to create new nil MAC addresses +- `MacAddr6::broadcast` and `MacAddr8::broadcast` methods to create new broadcast MAC addresses + +### Fixed + +- `std::fmt::Display` implementation for `MacAddr8` properly renders address in a canonical form (#1) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ef6d76b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,40 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "macaddr" +version = "1.0.1" +authors = ["svartalf <self@svartalf.info>"] +exclude = ["/.github", "/fuzz", "/benches"] +description = "MAC address types" +readme = "README.md" +keywords = ["mac", "macaddr", "mac-48", "eui-48", "eui-96"] +categories = ["data-structures", "network-programming", "no-std"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/svartalf/rust-macaddr" +[package.metadata.docs.rs] +features = ["serde", "serde_std"] +[dependencies.serde] +version = "^1.0" +features = ["derive"] +optional = true +default-features = false +[dev-dependencies.assert_matches] +version = "1.3.0" + +[features] +default = ["std"] +serde_std = ["std", "serde/std"] +std = [] +[badges.maintenance] +status = "passively-maintained" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..3bb4603 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,30 @@ +[package] +name = "macaddr" +version = "1.0.1" +authors = ["svartalf <self@svartalf.info>"] +edition = "2018" +description = "MAC address types" +repository = "https://github.com/svartalf/rust-macaddr" +keywords = ["mac", "macaddr", "mac-48", "eui-48", "eui-96"] +categories = ["data-structures", "network-programming", "no-std"] +readme = "README.md" +license = "Apache-2.0 OR MIT" +exclude = ["/.github", "/fuzz", "/benches"] + +[badges] +maintenance = { status = "passively-maintained" } + +[features] +default = ["std"] +std = [] +# https://github.com/rust-lang/cargo/issues/3494 +serde_std = ["std", "serde/std"] + +[dependencies] +serde = { version = "^1.0", default-features = false, features = ["derive"], optional = true } + +[dev-dependencies] +assert_matches = "1.3.0" + +[package.metadata.docs.rs] +features = ["serde", "serde_std"] @@ -0,0 +1 @@ +LICENSE-APACHE
\ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..9b37699 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019-NOW svartalf <https://svartalf.info> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..9b37699 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019-NOW svartalf <https://svartalf.info> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..5e8da4e --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "macaddr" +description: "MAC address types" +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/macaddr" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/macaddr/macaddr-1.0.1.crate" + } + version: "1.0.1" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2021 + month: 3 + day: 17 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +include platform/prebuilts/rust:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1a3e7f --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# macaddr + +> MAC address types for Rust + +[![Latest Version](https://img.shields.io/crates/v/macaddr.svg)](https://crates.io/crates/macaddr) +[![Latest Version](https://docs.rs/macaddr/badge.svg)](https://docs.rs/macaddr) +[![Build Status](https://github.com/svartalf/rust-macaddr/workflows/Continuous%20integration/badge.svg)](https://github.com/svartalf/rust-macaddr/actions) +[![Coverage Status](https://coveralls.io/repos/github/svartalf/rust-macaddr/badge.svg?branch=master)](https://coveralls.io/github/svartalf/rust-macaddr?branch=master) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.31+-green.svg) +![Apache 2.0 OR MIT licensed](https://img.shields.io/badge/license-Apache2.0%2FMIT-blue.svg) +![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg) + +This crate provides types for a [MAC address](https://en.wikipedia.org/wiki/MAC_address) +identifiers, both in IEEE *EUI-48* and *EUI-64* formats. + +It is like a [`std::net::SocketAddr`](https://doc.rust-lang.org/std/net/enum.SocketAddr.html) enum with a +[`std::net::SocketAddrV4`](https://doc.rust-lang.org/std/net/struct.SocketAddrV4.html) and +[`std::net::SocketAddrV6`](https://doc.rust-lang.org/std/net/struct.SocketAddrV6.html) members, +but for MAC addresses instead. + +Obviously, MAC address can be represented as a `[u8; 6]` or `[u8; 8]`, +but it is error-prone and inconvenient, so here they are — +[MacAddr6](https://docs.rs/macaddr/latest/macaddr/struct.MacAddr6.html) and +[MacAddr8](https://docs.rs/macaddr/latest/macaddr/struct.MacAddr8.html) +structs with helpful methods and standard Rust traits implementations, +intended to be the first-class Rust objects. + +And it is `serde`- and `no_std`-friendly also! + +## Installation + +Add this to your `Cargo.toml` + +```toml +[dependencies] +macaddr = "1.0" +``` + +## Usage + +Check out the [documentation](https://docs.rs/macaddr) for each type +available, all of them have a plenty of examples. + +## License + +Licensed under either of [Apache License 2.0](https://github.com/svartalf/rust-macaddr/blob/master/LICENSE-APACHE) +or [MIT license](https://github.com/svartalf/rust-macaddr/blob/master/LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, +as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..599ac80 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,9 @@ +unstable_features = true +edition = "2018" +version = "Two" +wrap_comments = true +comment_width = 120 +max_width = 120 +merge_imports = false +newline_style = "Unix" +struct_lit_single_line = false diff --git a/src/addr.rs b/src/addr.rs new file mode 100644 index 0000000..d761bdd --- /dev/null +++ b/src/addr.rs @@ -0,0 +1,108 @@ +use core::{fmt, str::FromStr}; + +use crate::{parser, MacAddr6, MacAddr8, ParseError}; + +/// A MAC address, either in *EUI-48* or *EUI-64* format. +#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +pub enum MacAddr { + V6(MacAddr6), + V8(MacAddr8), +} + +impl MacAddr { + /// Returns `true` if the address is `MacAddr6` address. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::{MacAddr, MacAddr6}; + /// let addr = MacAddr::from([0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67]); + /// + /// assert_eq!(addr.is_v6(), true); + /// assert_eq!(addr.is_v8(), false); + /// ``` + pub fn is_v6(&self) -> bool { + match self { + MacAddr::V6(_) => true, + MacAddr::V8(_) => false, + } + } + + /// Returns `true` if the address is `MacAddr8` address. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::{MacAddr, MacAddr8}; + /// let addr = MacAddr::from([0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67, 0x89, 0xAB]); + /// + /// assert_eq!(addr.is_v6(), false); + /// assert_eq!(addr.is_v8(), true); + /// ``` + pub fn is_v8(&self) -> bool { + match self { + MacAddr::V6(_) => false, + MacAddr::V8(_) => true, + } + } + + /// Converts a `MacAddr` address to a byte slice. + /// + /// Length of the returned slice is depends on the enum member used. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::{MacAddr, MacAddr6}; + /// let addr = MacAddr::from([0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67]); + /// + /// assert_eq!(addr.as_bytes(), &[0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67]); + /// ``` + pub fn as_bytes(&self) -> &[u8] { + match self { + MacAddr::V6(addr) => addr.as_bytes(), + MacAddr::V8(addr) => addr.as_bytes(), + } + } +} + +impl From<MacAddr6> for MacAddr { + fn from(addr: MacAddr6) -> Self { + MacAddr::V6(addr) + } +} + +impl From<MacAddr8> for MacAddr { + fn from(addr: MacAddr8) -> Self { + MacAddr::V8(addr) + } +} + +impl FromStr for MacAddr { + type Err = ParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + parser::Parser::new(s).read_addr() + } +} + +impl From<[u8; 6]> for MacAddr { + fn from(bytes: [u8; 6]) -> Self { + MacAddr::V6(MacAddr6::from(bytes)) + } +} + +impl From<[u8; 8]> for MacAddr { + fn from(bytes: [u8; 8]) -> Self { + MacAddr::V8(MacAddr8::from(bytes)) + } +} + +impl fmt::Display for MacAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + MacAddr::V6(v6) => fmt::Display::fmt(v6, f), + MacAddr::V8(v8) => fmt::Display::fmt(v8, f), + } + } +} diff --git a/src/addr6.rs b/src/addr6.rs new file mode 100644 index 0000000..289b9d7 --- /dev/null +++ b/src/addr6.rs @@ -0,0 +1,229 @@ +use core::{fmt, str::FromStr}; + +use crate::parser; + +/// MAC address in *EUI-48* format. +#[repr(C)] +#[derive(Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MacAddr6([u8; 6]); + +impl MacAddr6 { + /// Creates a new `MacAddr6` address from the bytes. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB); + /// ``` + #[allow(clippy::many_single_char_names)] + pub const fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> MacAddr6 { + MacAddr6([a, b, c, d, e, f]) + } + + /// Create a new nil `MacAddr6`. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::nil(); + /// assert!(addr.is_nil()); + /// ``` + pub const fn nil() -> MacAddr6 { + MacAddr6([0x00; 6]) + } + + /// Create a new broadcast `MacAddr6`. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::broadcast(); + /// assert!(addr.is_broadcast()); + /// ``` + pub const fn broadcast() -> MacAddr6 { + MacAddr6([0xFF; 6]) + } + + /// Returns `true` if the address is nil. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + /// + /// assert_eq!(addr.is_nil(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn is_nil(&self) -> bool { + self.0.iter().all(|&b| b == 0) + } + + /// Returns `true` if the address is broadcast. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + /// + /// assert_eq!(addr.is_broadcast(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn is_broadcast(&self) -> bool { + self.0.iter().all(|&b| b == 0xFF) + } + + /// Returns `true` if the address is unicast. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0x00, 0x01, 0x44, 0x55, 0x66, 0x77); + /// + /// assert_eq!(addr.is_unicast(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_unicast(&self) -> bool { + self.0[0] & 1 == 0 + } + + /// Returns `true` if the address is multicast. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0x01, 0x00, 0x0C, 0xCC, 0xCC, 0xCC); + /// + /// assert_eq!(addr.is_multicast(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_multicast(&self) -> bool { + self.0[0] & 1 == 1 + } + + /// Returns `true` if the address is universally administered address (UAA). + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0x01, 0x00, 0x0C, 0xCC, 0xCC, 0xCC); + /// + /// assert_eq!(addr.is_universal(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_universal(&self) -> bool { + self.0[0] & 1 << 1 == 0 + } + + /// Returns `true` if the address is locally administered (LAA). + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0x02, 0x00, 0x0C, 0xCC, 0xCC, 0xCC); + /// + /// assert_eq!(addr.is_local(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_local(&self) -> bool { + self.0[0] & 1 << 1 == 2 + } + + /// Converts a `MacAddr6` address to a byte slice. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67); + /// + /// assert_eq!(addr.as_bytes(), &[0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67]); + /// ``` + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Consumes `MacAddr6` address and returns raw bytes array. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr6; + /// let addr = MacAddr6::new(0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67); + /// + /// assert_eq!(addr.into_array(), [0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67]); + /// ``` + pub const fn into_array(self) -> [u8; 6] { + self.0 + } +} + +impl FromStr for MacAddr6 { + type Err = parser::ParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + parser::Parser::new(s).read_v6_addr() + } +} + +impl From<[u8; 6]> for MacAddr6 { + fn from(bytes: [u8; 6]) -> Self { + MacAddr6(bytes) + } +} + +impl AsRef<[u8]> for MacAddr6 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for MacAddr6 { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +/// `MacAddr6` can be displayed in different formats. +/// +/// # Example +/// +/// ``` +/// # use macaddr::MacAddr6; +/// let addr = MacAddr6::new(0xab, 0x0d, 0xef, 0x12, 0x34, 0x56); +/// +/// assert_eq!(&format!("{}", addr), "AB:0D:EF:12:34:56"); +/// assert_eq!(&format!("{:-}", addr), "AB-0D-EF-12-34-56"); +/// assert_eq!(&format!("{:#}", addr), "AB0.DEF.123.456"); +/// ``` +impl fmt::Display for MacAddr6 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if f.sign_minus() { + f.write_fmt(format_args!( + "{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], + )) + } else if f.alternate() { + let p1 = u16::from(self.0[0]) * 16 + u16::from(self.0[1] / 16); + let p2 = u16::from(self.0[1] % 16) * 256 + u16::from(self.0[2]); + let p3 = u16::from(self.0[3]) * 16 + u16::from(self.0[4] / 16); + let p4 = u16::from(self.0[4] % 16) * 256 + u16::from(self.0[5]); + + f.write_fmt(format_args!("{:03X}.{:03X}.{:03X}.{:03X}", p1, p2, p3, p4,)) + } else { + f.write_fmt(format_args!( + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], + )) + } + } +} diff --git a/src/addr8.rs b/src/addr8.rs new file mode 100644 index 0000000..9c33d76 --- /dev/null +++ b/src/addr8.rs @@ -0,0 +1,227 @@ +use core::{fmt, str::FromStr}; + +use crate::parser; + +/// MAC address in *EUI-64* format. +#[repr(C)] +#[derive(Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MacAddr8([u8; 8]); + +impl MacAddr8 { + /// Creates a new `MacAddr8` address from the bytes. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF); + /// ``` + #[allow(clippy::many_single_char_names, clippy::too_many_arguments)] + pub const fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8, g: u8, h: u8) -> MacAddr8 { + MacAddr8([a, b, c, d, e, f, g, h]) + } + + /// Create a new nil `MacAddr8`. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::nil(); + /// assert!(addr.is_nil()); + /// ``` + pub const fn nil() -> MacAddr8 { + MacAddr8([0x00; 8]) + } + + /// Create a new broadcast `MacAddr8`. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::broadcast(); + /// assert!(addr.is_broadcast()); + /// ``` + pub const fn broadcast() -> MacAddr8 { + MacAddr8([0xFF; 8]) + } + + /// Returns `true` if the address is nil. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + /// + /// assert_eq!(addr.is_nil(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn is_nil(&self) -> bool { + self.0.iter().all(|&b| b == 0) + } + + /// Returns `true` if the address is broadcast. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); + /// + /// assert_eq!(addr.is_broadcast(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn is_broadcast(&self) -> bool { + self.0.iter().all(|&b| b == 0xFF) + } + + /// Returns `true` if the address is unicast. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0x00, 0x01, 0x44, 0x55, 0x66, 0x77, 0xCD, 0xEF); + /// + /// assert_eq!(addr.is_unicast(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_unicast(&self) -> bool { + self.0[0] & 1 == 0 + } + + /// Returns `true` if the address is multicast. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0x01, 0x00, 0x0C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC); + /// + /// assert_eq!(addr.is_multicast(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_multicast(&self) -> bool { + self.0[0] & 1 == 1 + } + + /// Returns `true` if the address is universally administered address (UAA). + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0x01, 0x00, 0x0C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC); + /// + /// assert_eq!(addr.is_universal(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_universal(&self) -> bool { + self.0[0] & 1 << 1 == 0 + } + + /// Returns `true` if the address is locally administered (LAA). + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0x02, 0x00, 0x0C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC); + /// + /// assert_eq!(addr.is_local(), true); + /// ``` + #[allow(clippy::trivially_copy_pass_by_ref)] + pub const fn is_local(&self) -> bool { + self.0[0] & 1 << 1 == 2 + } + + /// Converts a `MacAddr8` address to a byte slice. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67, 0x89, 0xAB); + /// + /// assert_eq!(addr.as_bytes(), &[0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67, 0x89, 0xAB]); + /// ``` + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Consumes a `MacAddr8` address and returns raw bytes. + /// + /// ## Example + /// + /// ```rust + /// # use macaddr::MacAddr8; + /// let addr = MacAddr8::new(0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67, 0x89, 0xAB); + /// + /// assert_eq!(addr.into_array(), [0xAC, 0xDE, 0x48, 0x23, 0x45, 0x67, 0x89, 0xAB]); + /// ``` + pub const fn into_array(self) -> [u8; 8] { + self.0 + } +} + +impl FromStr for MacAddr8 { + type Err = parser::ParseError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + parser::Parser::new(s).read_v8_addr() + } +} + +impl From<[u8; 8]> for MacAddr8 { + fn from(bytes: [u8; 8]) -> Self { + MacAddr8(bytes) + } +} + +impl AsRef<[u8]> for MacAddr8 { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for MacAddr8 { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +/// `MacAddr8` can be displayed in different formats. +/// +/// # Example +/// +/// ``` +/// # use macaddr::MacAddr8; +/// let addr = MacAddr8::new(0xab, 0x0d, 0xef, 0x12, 0x34, 0x56, 0x78, 0x9A); +/// +/// assert_eq!(&format!("{}", addr), "AB:0D:EF:12:34:56:78:9A"); +/// assert_eq!(&format!("{:-}", addr), "AB-0D-EF-12-34-56-78-9A"); +/// assert_eq!(&format!("{:#}", addr), "AB0D.EF12.3456.789A"); +/// ``` +impl fmt::Display for MacAddr8 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if f.sign_minus() { + f.write_fmt(format_args!( + "{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7], + )) + } else if f.alternate() { + f.write_fmt(format_args!( + "{:02X}{:02X}.{:02X}{:02X}.{:02X}{:02X}.{:02X}{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7], + )) + } else { + f.write_fmt(format_args!( + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7], + )) + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..753ba47 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,48 @@ +//! This crate provides types for a [MAC address] identifiers, +//! both in IEEE *EUI-48* and *EUI-64* formats. +//! +//! It is like a `std::net::SocketAddr` enum +//! with `std::net::SocketAddrV4` and `std::net::SocketAddrV6` members, +//! but for MAC addresses instead. +//! +//! Obviously, MAC address can be represented as a `[u8; 6]` or `[u8; 8]`, +//! but it is error-prone and inconvenient, so here they are — +//! [MacAddr6] and [MacAddr8] structs with helpful methods and +//! standard Rust traits implementations to make them first-class +//! Rust objects. +//! +//! ## Serde support +//! +//! [Serde] support can be enabled with a `"serde_std"` feature +//! (disabled by default) if used in `std`-enabled builds. +//! +//! This feature is called like this because of [this Cargo bug].\ +//! `"serde"` feature is exists also, but it is intended to be used +//! in the `no_std` builds. +//! +//! ## No-std support +//! +//! This crate can be used in a `no_std` builds with +//! disabled `"std"` feature (enabled by default). +//! +//! Enabled `"serde"` feature will add support for `no_std` +//! serde serialization and deserialization. +//! +//! [Serde]: https://serde.rs +//! [MAC address]: https://en.wikipedia.org/wiki/MAC_address +//! [this Cargo bug]: https://github.com/rust-lang/cargo/issues/3494 +//! [MacAddr6]: struct.MacAddr6.html +//! [MacAddr8]: struct.MacAddr8.html +#![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_root_url = "https://docs.rs/macaddr/1.0.0")] +#![forbid(unsafe_code)] + +mod addr; +mod addr6; +mod addr8; +mod parser; + +pub use self::addr::MacAddr; +pub use self::addr6::MacAddr6; +pub use self::addr8::MacAddr8; +pub use self::parser::ParseError; diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..d7e81dc --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,201 @@ +#[cfg(feature = "std")] +use std::{error::Error, fmt}; + +#[cfg(not(feature = "std"))] +use core::fmt; + +use crate::{MacAddr, MacAddr6, MacAddr8}; + +/// An error which can be returned when parsing MAC address. +/// +/// This error is used as the error type for the `FromStr` implementation +/// for [MacAddr6] and [MacAddr8]. +/// +/// [MacAddr6]: ./struct.MacAddr6.html +/// [MacAddr8]: ./struct.MacAddr8.html +#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +pub enum ParseError { + /// Provided string can't be parsed into the given type, + /// because it is either too short or too long. + /// + /// For example, any trailing symbols will result in the error, + /// as in `"12-34-56-78-9A-BC\n"`. + /// + /// This enum member will contain the provided string length when returned. + InvalidLength(usize), + + /// Invalid character occurred in the provided string. + /// + /// Allowed characters are `0123456789abcdefABCDEF-:.`. + /// + /// This enum member will contain the wrong char and it's position when returned. + InvalidCharacter(char, usize), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParseError::InvalidLength(len) => f.write_fmt(format_args!("Invalid length of {} characters", len,)), + ParseError::InvalidCharacter(chr, pos) => { + f.write_fmt(format_args!("Unexpected character '{}' at position {}", chr, pos,)) + } + } + } +} + +#[cfg(feature = "std")] +impl Error for ParseError {} + +#[derive(Debug, Eq, PartialEq)] +enum Delimiter { + Hyphen, + Colon, + Dot, +} + +// Heavily based on the Rust' `std/net/parser.rs` sources. +#[derive(Debug)] +pub struct Parser<'a> { + source: &'a [u8], + pos: usize, + delimiter: Option<Delimiter>, +} + +impl<'a> Parser<'a> { + pub fn new(s: &'a str) -> Parser<'a> { + Parser { + source: s.as_bytes(), + pos: 0, + delimiter: None, + } + } + + fn is_eof(&self) -> bool { + self.pos == self.source.len() + } + + fn move_next(&mut self) { + if !self.is_eof() { + self.pos += 1; + } + } + + fn peek_char(&mut self) -> Option<char> { + if self.is_eof() { + None + } else { + Some(self.source[self.pos] as char) + } + } + + fn read_char(&mut self) -> Result<char, ParseError> { + if self.is_eof() { + Err(ParseError::InvalidLength(self.pos)) + } else { + let r = self.source[self.pos] as char; + self.pos += 1; + Ok(r) + } + } + + fn read_digit(&mut self) -> Result<u8, ParseError> { + let chr = self.read_char()?; + + match chr as u8 { + byte @ b'0'..=b'9' => Ok(byte - b'0'), + byte @ b'a'..=b'f' => Ok(byte - b'a' + 10), + byte @ b'A'..=b'F' => Ok(byte - b'A' + 10), + _ => Err(ParseError::InvalidCharacter(chr, self.pos)), + } + } + + fn probe_delimiter(&mut self) -> Result<Option<()>, ParseError> { + match self.peek_char() { + Some('-') if self.delimiter.is_none() => { + self.delimiter = Some(Delimiter::Hyphen); + Ok(Some(())) + } + Some('-') if self.delimiter != Some(Delimiter::Hyphen) => Err(ParseError::InvalidCharacter('-', self.pos)), + Some('-') => Ok(Some(())), + + Some(':') if self.delimiter.is_none() => { + self.delimiter = Some(Delimiter::Colon); + Ok(Some(())) + } + Some(':') if self.delimiter != Some(Delimiter::Colon) => Err(ParseError::InvalidCharacter(':', self.pos)), + Some(':') => Ok(Some(())), + + Some('.') if self.delimiter.is_none() => { + self.delimiter = Some(Delimiter::Dot); + Ok(Some(())) + } + Some('.') if self.delimiter != Some(Delimiter::Dot) => Err(ParseError::InvalidCharacter('.', self.pos)), + Some('.') => Ok(Some(())), + _ => Ok(None), + } + } + + pub fn read_v6_addr(&mut self) -> Result<MacAddr6, ParseError> { + let mut bytes = [0; 6]; + let mut i = 0; + + while i < 6 { + if self.probe_delimiter()?.is_some() { + self.move_next(); + } + + let mut digit = self.read_digit()? * 16; + digit += self.read_digit()?; + + bytes[i] = digit; + + i += 1; + } + + if self.is_eof() { + Ok(MacAddr6::from(bytes)) + } else { + Err(ParseError::InvalidLength(self.source.len())) + } + } + + pub fn read_v8_addr(&mut self) -> Result<MacAddr8, ParseError> { + let mut bytes = [0; 8]; + let mut i = 0; + + while i < 8 { + if self.probe_delimiter()?.is_some() { + self.move_next(); + } + + let mut digit = self.read_digit()? * 16; + digit += self.read_digit()?; + + bytes[i] = digit; + + i += 1; + } + + if self.is_eof() { + Ok(MacAddr8::from(bytes)) + } else { + Err(ParseError::InvalidLength(self.source.len())) + } + } + + pub fn read_addr(&mut self) -> Result<MacAddr, ParseError> { + match self.read_v6_addr() { + Ok(addr) => return Ok(addr.into()), + Err(err @ ParseError::InvalidCharacter(..)) => return Err(err), + Err(ParseError::InvalidLength(..)) => {} + } + + // Rolling back to the start. + self.pos = 0; + + self.read_v8_addr().map(Into::into) + } +} + +#[cfg(test)] +mod tests; diff --git a/src/parser/tests.rs b/src/parser/tests.rs new file mode 100644 index 0000000..dfea7a3 --- /dev/null +++ b/src/parser/tests.rs @@ -0,0 +1,176 @@ +#[cfg(feature = "std")] +use std::str::FromStr; + +#[cfg(not(feature = "std"))] +use core::str::FromStr; + +use assert_matches::assert_matches; + +use crate::{MacAddr, MacAddr6, MacAddr8}; + +#[test] +fn test_parse_v6_upper_case_canonical_format() { + let addr = MacAddr6::from_str("12-34-56-78-9A-BC"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC], addr.as_bytes()); +} + +#[test] +fn test_parse_v6_lower_case_canonical_format() { + let addr = MacAddr6::from_str("ab-cd-ef-ab-cd-ef"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF], addr.as_bytes()); +} + +#[test] +fn test_parse_v6_mixed_case_canonical_format() { + let addr = MacAddr6::from_str("AB-cd-Ef-Ab-cD-EF"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0xAB, 0xCD, 0xEF, 0xAB, 0xCD, 0xEF], addr.as_bytes()); +} + +#[test] +fn test_parse_v6_colon_format() { + let addr = MacAddr6::from_str("12:34:56:78:9A:BC"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC], addr.as_bytes()); +} + +#[test] +fn test_parse_v6_cisco_format() { + let addr = MacAddr6::from_str("1234.5678.9ABC"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC], addr.as_bytes()); +} + +#[test] +fn test_parse_v8_canonical_format() { + let addr = MacAddr8::from_str("12-34-56-78-9A-BC-DE-F0"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], addr.as_bytes()); +} + +#[test] +fn test_parse_v8_colon_format() { + let addr = MacAddr8::from_str("12:34:56:78:9A:BC:DE:F0"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], addr.as_bytes()); +} + +#[test] +fn test_parse_canonical_format() { + let addr = MacAddr::from_str("12-34-56-78-9A-BC-DE-F0"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + assert_matches!(addr, MacAddr::V8(..)); + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], addr.as_bytes()); +} + +#[test] +fn test_parse_colon_format() { + let addr = MacAddr::from_str("12:34:56:78:9A:BC:DE:F0"); + + assert!(addr.is_ok()); + let addr = addr.unwrap(); + assert_matches!(addr, MacAddr::V8(..)); + assert_eq!(&[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], addr.as_bytes()); +} + +#[test] +fn test_parse_v6_empty() { + let addr = MacAddr6::from_str(""); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v8_empty() { + let addr = MacAddr8::from_str(""); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_empty() { + let addr = MacAddr::from_str(""); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v6_partial_start() { + let addr = MacAddr6::from_str("b-cd-ef-12-34-56"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v8_partial_start() { + let addr = MacAddr8::from_str("b-cd-ef-12-34-56-78-9A"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v6_partial_end() { + let addr = MacAddr6::from_str("ab-cd-ef-12-34-5"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v8_partial_end() { + let addr = MacAddr8::from_str("ab-cd-ef-12-34-56-78-9"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v6_invalid_char() { + let addr = MacAddr6::from_str("ab-Qd-ef-12-34-56"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v8_invalid_char() { + let addr = MacAddr8::from_str("ab-Qd-ef-12-34-56-78-9A"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v6_different_delimiters() { + let addr = MacAddr6::from_str("ab-cd:ef-12-34-56"); + + assert!(addr.is_err()); +} + +#[test] +fn test_parse_v8_different_delimiters() { + let addr = MacAddr8::from_str("ab-cd-ef-12-34-56-78:9A"); + + assert!(addr.is_err()); +} |