├── .github └── bors.toml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ci ├── after_success.sh ├── install.sh └── script.sh ├── firmware ├── .cargo │ └── config ├── .gitignore ├── Cargo.toml ├── README.md ├── examples │ ├── hello.rs │ ├── ipv4.rs │ ├── ipv6.rs │ └── sixlowpan.rs ├── memory.x ├── openocd.cfg ├── openocd.gdb └── src │ └── lib.rs ├── mrf24j40 ├── Cargo.toml └── src │ ├── lib.rs │ ├── long.rs │ ├── reg.rs │ └── short.rs ├── panic-never ├── .cargo │ └── config ├── .gitignore ├── Cargo.toml ├── README.md ├── examples │ ├── arp.rs │ ├── coap.rs │ ├── ether.rs │ ├── icmp.rs │ ├── icmpv6.rs │ ├── ieee802154.rs │ ├── iphc.rs │ ├── ipv4.rs │ ├── nhc.rs │ └── udp.rs ├── memory.x └── src │ └── lib.rs ├── src ├── arp.rs ├── coap.rs ├── ether.rs ├── fmt.rs ├── icmp.rs ├── icmpv6.rs ├── ieee802154.rs ├── ipv4.rs ├── ipv6.rs ├── lib.rs ├── mac.rs ├── macros.rs ├── sealed.rs ├── sixlowpan.rs ├── sixlowpan │ ├── iphc.rs │ └── nhc.rs ├── traits.rs └── udp.rs ├── tests └── roundtrip.rs ├── tools ├── .gitignore ├── Cargo.toml └── src │ └── bin │ └── coap.rs └── ujson ├── .gitignore ├── Cargo.toml ├── macros ├── Cargo.toml └── src │ └── lib.rs ├── src ├── de.rs ├── lib.rs ├── macros.rs ├── ser.rs └── traits.rs └── tests ├── deserialize.rs └── serialize.rs /.github/bors.toml: -------------------------------------------------------------------------------- 1 | delete_merged_branches = true 2 | status = ["continuous-integration/travis-ci/push"] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.rs.bk 2 | .#* 3 | /target 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: stable 3 | 4 | matrix: 5 | include: 6 | - env: TARGET=x86_64-unknown-linux-gnu 7 | 8 | - env: TARGET=thumbv7m-none-eabi 9 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 10 | 11 | - env: TARGET=thumbv7m-none-eabi 12 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 13 | rust: nightly 14 | 15 | - env: TARGET=rustfmt 16 | if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) 17 | 18 | before_install: set -e 19 | 20 | install: 21 | - bash ci/install.sh 22 | 23 | script: 24 | - bash ci/script.sh 25 | 26 | after_script: set +e 27 | 28 | after_success: 29 | - bash ci/after_success.sh 30 | 31 | cache: cargo 32 | 33 | before_cache: 34 | # Travis can't cache files that are not readable by "others" 35 | - chmod -R a+r $HOME/.cargo 36 | 37 | branches: 38 | only: 39 | - master 40 | - staging 41 | - trying 42 | 43 | notifications: 44 | email: 45 | on_success: never 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | description = "JNeT: japaric's network thingies" 4 | edition = "2018" 5 | license = "MIT OR Apache-2.0" 6 | name = "jnet" 7 | repository = "https://github.com/japaric/jnet" 8 | version = "0.1.0" 9 | 10 | [dependencies] 11 | as-slice = "0.1.0" 12 | hash32 = "0.1.0" 13 | hash32-derive = "0.1.0" 14 | owning-slice = { git = "https://github.com/japaric/owning-slice" } 15 | 16 | [dependencies.byteorder] 17 | default-features = false 18 | version = "1.2.1" 19 | 20 | [dependencies.cast] 21 | default-features = false 22 | version = "0.2.2" 23 | 24 | [dev-dependencies] 25 | pretty_assertions = "0.5.0" 26 | rand = "0.6.5" 27 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Jorge Aparicio 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `jnet` 2 | 3 | > [Experiment] JNeT: japaric's network thingies 4 | 5 | --- 6 | 7 | **PLEASE READ** 8 | 9 | Thanks for checking out JNeT! 10 | 11 | Please understand that this is a personal experiment so *I* will *not* provide 12 | any kind of personalized support, or fulfill third-party feature requests. 13 | 14 | The API is unstable and can (will) change at any time. Only rely on these crates 15 | is you are OK with API instability. 16 | 17 | Also I don't intend to publish these crates on crates.io any time soon so please 18 | don't ask. 19 | 20 | Thanks for your understanding, 21 | 22 | @japaric 23 | 24 | --- 25 | 26 | ## [Documentation](https://japaric.github.io/jnet/jnet/index.html) 27 | 28 | ## License 29 | 30 | Licensed under either of 31 | 32 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 33 | http://www.apache.org/licenses/LICENSE-2.0) 34 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 35 | 36 | at your option. 37 | 38 | ### Contribution 39 | 40 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the 41 | work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 42 | additional terms or conditions. 43 | -------------------------------------------------------------------------------- /ci/after_success.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | if [ $TARGET != x86_64-unknown-linux-gnu ]; then 5 | return 6 | fi 7 | 8 | cargo doc 9 | 10 | mkdir ghp-import 11 | 12 | curl -Ls https://github.com/davisp/ghp-import/archive/master.tar.gz | \ 13 | tar --strip-components 1 -C ghp-import -xz 14 | 15 | ./ghp-import/ghp_import.py target/doc 16 | 17 | set +x 18 | git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && \ 19 | echo OK 20 | } 21 | 22 | if [ $TRAVIS_BRANCH = master ]; then 23 | main 24 | fi 25 | -------------------------------------------------------------------------------- /ci/install.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | if [ $TARGET != rustfmt ]; then 5 | rustup target add $TARGET 6 | else 7 | rustup component add rustfmt 8 | fi 9 | } 10 | 11 | main 12 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | main() { 4 | if [ $TARGET = rustfmt ]; then 5 | cargo fmt -- --check 6 | return 7 | fi 8 | 9 | cargo check --target $TARGET 10 | 11 | if [ $TARGET = x86_64-unknown-linux-gnu ]; then 12 | cargo test -p owning-slice --target $TARGET 13 | cargo test -p owning-slice --target $TARGET --release 14 | 15 | cargo test --target $TARGET 16 | cargo test --target $TARGET --release 17 | 18 | pushd tools 19 | cargo check --target $TARGET --bins 20 | popd 21 | 22 | if [ $TRAVIS_RUST_VERSION = nightly ]; then 23 | export RUSTFLAGS="-Z sanitizer=address" 24 | # export ASAN_OPTIONS="detect_odr_violation=0" 25 | 26 | cargo test --target $TARGET --lib 27 | cargo test --target $TARGET --lib --release 28 | fi 29 | elif [ $TARGET = thumbv7m-none-eabi ]; then 30 | ( cd panic-never && cargo build --examples --release ) 31 | 32 | if [ $TRAVIS_RUST_VERSION = nightly ]; then 33 | pushd firmware 34 | local examples=( 35 | hello 36 | ipv4 37 | ipv6 38 | sixlowpan 39 | ) 40 | 41 | cargo build --target $TARGET --examples --release 42 | cd target/$TARGET/release/examples/ 43 | size ${examples[@]} 44 | size -A ${examples[@]} 45 | popd 46 | fi 47 | fi 48 | } 49 | 50 | # fake Travis variables to be able to run this on a local machine 51 | if [ -z ${TRAVIS_BRANCH-} ]; then 52 | TRAVIS_BRANCH=auto 53 | fi 54 | 55 | if [ -z ${TRAVIS_RUST_VERSION-} ]; then 56 | case $(rustc -V) in 57 | *nightly*) 58 | TRAVIS_RUST_VERSION=nightly 59 | ;; 60 | *beta*) 61 | TRAVIS_RUST_VERSION=beta 62 | ;; 63 | *) 64 | TRAVIS_RUST_VERSION=stable 65 | ;; 66 | esac 67 | fi 68 | 69 | if [ -z ${TARGET-} ]; then 70 | TARGET=$(rustc -Vv | grep host | cut -d ' ' -f2) 71 | fi 72 | 73 | if [ $TRAVIS_BRANCH != master ] || [ $TRAVIS_PULL_REQUEST != false ]; then 74 | main 75 | fi 76 | -------------------------------------------------------------------------------- /firmware/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | runner = "arm-none-eabi-gdb -q -x openocd.gdb" 3 | 4 | rustflags = [ 5 | # "-C", "linker=arm-none-eabi-ld", 6 | "-C", "link-arg=-Tlink.x", 7 | "-C", "link-arg=-Tstlog.x", 8 | ] 9 | 10 | [build] 11 | target = "thumbv7m-none-eabi" 12 | -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /firmware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | readme = "README.md" 5 | name = "blue-pill" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | cast = { version = "0.2.2", default-features = false } 10 | cortex-m = { version = "0.5.8", features = ["inline-asm"] } 11 | cortex-m-rt = "0.6.7" 12 | cortex-m-rtfm = "0.4.0" 13 | cortex-m-semihosting = "0.3.2" 14 | embedded-hal = "0.2.2" 15 | enc28j60 = { git = "https://github.com/japaric/enc28j60", branch = "wip" } 16 | heapless = "0.4.1" 17 | jnet = { path = ".." } 18 | panic-abort = "0.3.1" 19 | panic-halt = "0.2.0" 20 | panic-semihosting = { version = "0.5.1", features = ["inline-asm"] } 21 | stlog = { git = "https://github.com/japaric/stlog", features = ["spanned"] } 22 | stm32f103xx-hal = { git = "https://github.com/japaric/stm32f103xx-hal", features = ["rt"], rev = "c9d9a86bcb4c493c72b6c5a805e2dd838e2e907f" } 23 | owning-slice = { git = "https://github.com/japaric/owning-slice" } 24 | mrf24j40 = { path = "../mrf24j40" } 25 | ujson = { path = "../ujson" } 26 | 27 | [profile.release] 28 | codegen-units = 1 # better optimizations 29 | debug = true # symbols are nice and they don't increase the size on Flash 30 | incremental = false 31 | lto = true # better optimizations 32 | # opt-level = "z" -------------------------------------------------------------------------------- /firmware/examples/hello.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_hygiene)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | extern crate panic_abort; 6 | extern crate stm32f103xx_hal; 7 | 8 | use blue_pill::ItmLogger; 9 | use cortex_m_rt::entry; 10 | use stlog::{global_logger, spanned::info}; 11 | 12 | #[global_logger] 13 | static LOGGER: ItmLogger = ItmLogger; 14 | 15 | #[entry] 16 | fn main() -> ! { 17 | info!("Hello, world!"); 18 | 19 | loop {} 20 | } 21 | -------------------------------------------------------------------------------- /firmware/examples/ipv4.rs: -------------------------------------------------------------------------------- 1 | //! Simplified IPv4 stack 2 | //! 3 | //! This stack responds to "ping"s and echoes back UDP packets. 4 | 5 | #![deny(rust_2018_compatibility)] 6 | #![deny(rust_2018_idioms)] 7 | #![deny(unsafe_code)] 8 | #![deny(warnings)] 9 | #![feature(never_type)] 10 | #![feature(proc_macro_hygiene)] 11 | #![no_main] 12 | #![no_std] 13 | 14 | #[allow(unused_extern_crates)] 15 | extern crate panic_abort; 16 | // extern crate panic_semihosting; // alternative panic handler 17 | 18 | use blue_pill::{Ethernet, Led, CACHE_SIZE, IP, MAC}; 19 | use cast::usize; 20 | use cortex_m_rt::entry; 21 | use heapless::FnvIndexMap; 22 | use jnet::{arp, coap, ether, icmp, ipv4, mac, udp}; 23 | use stlog::{ 24 | global_logger, 25 | spanned::{error, info, warning}, 26 | }; 27 | use stm32f103xx_hal::{prelude::*, stm32f103xx}; 28 | use ujson::{uDeserialize, uSerialize}; 29 | 30 | #[global_logger] 31 | static LOGGER: blue_pill::ItmLogger = blue_pill::ItmLogger; 32 | // static LOGGER: stlog::NullLogger = stlog::NullLogger; // alt: no logs 33 | // NOTE(^) LLD errors with `NullLogger` so you have to switch to GNU LD (see .cargo/config) 34 | 35 | #[entry] 36 | fn main() -> ! { 37 | info!("Initializing .."); 38 | 39 | let core = cortex_m::Peripherals::take().unwrap_or_else(|| { 40 | error!("cortex_m::Peripherals::take failed"); 41 | 42 | blue_pill::fatal(); 43 | }); 44 | 45 | let device = stm32f103xx::Peripherals::take().unwrap_or_else(|| { 46 | error!("stm32f103xx::Peripherals::take failed"); 47 | 48 | blue_pill::fatal(); 49 | }); 50 | 51 | let (ethernet, led) = blue_pill::init_enc28j60(core, device); 52 | 53 | info!("Done with initialization"); 54 | 55 | run(ethernet, led).unwrap_or_else(|| { 56 | error!("`run` failed"); 57 | 58 | blue_pill::fatal() 59 | }); 60 | } 61 | 62 | const BUF_SZ: usize = 256; 63 | 64 | // main logic 65 | fn run(mut ethernet: Ethernet, mut led: Led) -> Option { 66 | let mut cache = FnvIndexMap::new(); 67 | let mut buf = [0; BUF_SZ]; 68 | let mut extra_buf = [0; BUF_SZ]; 69 | loop { 70 | let packet = if let Some(packet) = ethernet 71 | .next_packet() 72 | .map_err(|_| error!("Enc28j60::next_packet failed")) 73 | .ok()? 74 | { 75 | if usize(packet.len()) > buf.len() { 76 | error!("packet too big for our buffer"); 77 | 78 | packet 79 | .ignore() 80 | .map_err(|_| error!("Packet::ignore failed")) 81 | .ok()?; 82 | 83 | continue; 84 | } else { 85 | packet 86 | .read(&mut buf[..]) 87 | .map_err(|_| error!("Packet::read failed")) 88 | .ok()? 89 | } 90 | } else { 91 | continue; 92 | }; 93 | 94 | info!("new packet"); 95 | 96 | let eth = match on_new_packet( 97 | &State { 98 | led: led.is_set_low(), 99 | }, 100 | packet, 101 | &mut cache, 102 | &mut extra_buf, 103 | ) { 104 | Action::ArpReply(eth) => { 105 | info!("sending ARP reply"); 106 | 107 | eth 108 | } 109 | 110 | Action::EchoReply(eth) => { 111 | info!("sending 'Echo Reply' ICMP message"); 112 | 113 | led.toggle(); 114 | 115 | eth 116 | } 117 | 118 | Action::CoAP(change, eth) => { 119 | if let Some(on) = change { 120 | info!("changing LED state"); 121 | 122 | if on { 123 | led.set_low() 124 | } else { 125 | led.set_high() 126 | } 127 | } 128 | 129 | info!("sending CoAP message"); 130 | 131 | eth 132 | } 133 | 134 | Action::UdpReply(eth) => { 135 | info!("sending UDP packet"); 136 | 137 | led.toggle(); 138 | 139 | eth 140 | } 141 | 142 | Action::Nop => continue, 143 | }; 144 | 145 | let bytes = eth.as_bytes(); 146 | if bytes.len() <= usize::from(ethernet.mtu()) { 147 | ethernet 148 | .transmit(eth.as_bytes()) 149 | .map_err(|_| error!("Enc28j60::transmit failed")) 150 | .ok()?; 151 | } else { 152 | error!("Ethernet frame exceeds MTU"); 153 | } 154 | } 155 | } 156 | 157 | struct State { 158 | led: bool, 159 | } 160 | 161 | #[derive(uDeserialize, uSerialize)] 162 | struct Payload { 163 | led: bool, 164 | } 165 | 166 | // IO-less / "pure" logic (NB logging does IO but it's easy to remove using `GLOBAL_LOGGER`) 167 | fn on_new_packet<'a>( 168 | state: &State, 169 | bytes: &'a mut [u8], 170 | cache: &mut FnvIndexMap, 171 | extra_buf: &'a mut [u8], 172 | ) -> Action<'a> { 173 | let mut eth = if let Ok(f) = ether::Frame::parse(bytes) { 174 | info!("valid Ethernet frame"); 175 | f 176 | } else { 177 | error!("not a valid Ethernet frame"); 178 | return Action::Nop; 179 | }; 180 | 181 | let src_mac = eth.get_source(); 182 | 183 | match eth.get_type() { 184 | ether::Type::Arp => { 185 | info!("EtherType: ARP"); 186 | 187 | if let Ok(arp) = arp::Packet::parse(eth.payload_mut()) { 188 | info!("valid ARP packet"); 189 | 190 | if let Ok(mut arp) = arp.downcast() { 191 | info!("valid IPv4-over-Ethernet ARP packet"); 192 | 193 | if !arp.is_a_probe() { 194 | info!("update ARP cache"); 195 | 196 | if cache.insert(arp.get_spa(), arp.get_sha()).is_err() { 197 | warning!("ARP cache is full"); 198 | } 199 | } 200 | 201 | // are they asking for our MAC address? 202 | if arp.get_oper() == arp::Operation::Request && arp.get_tpa() == IP { 203 | info!("ARP request addressed to us"); 204 | 205 | // construct a reply in-place 206 | // (the reply will have the same size as the request) 207 | let tha = arp.get_sha(); 208 | let tpa = arp.get_spa(); 209 | 210 | arp.set_oper(arp::Operation::Reply); 211 | arp.set_sha(MAC); 212 | arp.set_spa(IP); 213 | arp.set_tha(tha); 214 | arp.set_tpa(tpa); 215 | 216 | // update the Ethernet header 217 | eth.set_destination(tha); 218 | eth.set_source(MAC); 219 | 220 | return Action::ArpReply(eth); 221 | } 222 | } else { 223 | error!("not an IPv4-over-Ethernet ARP packet"); 224 | } 225 | } else { 226 | error!("invalid ARP packet"); 227 | } 228 | } 229 | 230 | ether::Type::Ipv4 => { 231 | info!("EtherType: IPv4"); 232 | 233 | let mut ip = if let Ok(ip) = ipv4::Packet::parse(eth.payload_mut()) { 234 | info!("valid IPv4 packet"); 235 | 236 | ip 237 | } else { 238 | error!("not a valid IPv4 packet"); 239 | 240 | return Action::Nop; 241 | }; 242 | 243 | let src_ip = ip.get_source(); 244 | 245 | if !src_mac.is_broadcast() { 246 | if cache.insert(src_ip, src_mac).is_err() { 247 | warning!("ARP cache is full"); 248 | } 249 | } 250 | 251 | match ip.get_protocol() { 252 | ipv4::Protocol::Icmp => { 253 | info!("IPv4 protocol: ICMP"); 254 | 255 | let icmp = if let Ok(icmp) = icmp::Message::parse(ip.payload_mut()) { 256 | info!("valid ICMP message"); 257 | 258 | icmp 259 | } else { 260 | error!("not a valid ICMP message"); 261 | 262 | return Action::Nop; 263 | }; 264 | 265 | if let Ok(request) = icmp.downcast::() { 266 | info!("ICMP message has type 'Echo Request'"); 267 | 268 | let src_mac = if let Some(mac) = cache.get(&src_ip) { 269 | mac 270 | } else { 271 | error!("IP address not in the ARP cache"); 272 | 273 | return Action::Nop; 274 | }; 275 | 276 | // construct a reply in-place 277 | // (the reply will have the same size as the request) 278 | let _reply: icmp::Message<_, icmp::EchoReply, _> = request.into(); 279 | 280 | // update the IP header 281 | let mut ip = ip.set_source(IP); 282 | ip.set_destination(src_ip); 283 | let _ip = ip.update_checksum(); 284 | 285 | // update the Ethernet header 286 | eth.set_destination(*src_mac); 287 | eth.set_source(MAC); 288 | 289 | return Action::EchoReply(eth); 290 | } else { 291 | error!("not a 'Echo Request' ICMP message"); 292 | } 293 | } 294 | 295 | ipv4::Protocol::Udp => { 296 | info!("IPv4 protocol: UDP"); 297 | 298 | if let Ok(mut udp) = udp::Packet::parse(ip.payload_mut()) { 299 | info!("valid UDP packet"); 300 | 301 | let src_mac = if let Some(mac) = cache.get(&src_ip) { 302 | mac 303 | } else { 304 | error!("the IP address of the sender is not in the ARP cache"); 305 | 306 | return Action::Nop; 307 | }; 308 | 309 | let dst_port = udp.get_destination(); 310 | 311 | if dst_port == coap::PORT { 312 | info!("UDP: destination port is our CoAP port"); 313 | 314 | let coap = if let Ok(m) = coap::Message::parse(udp.payload()) { 315 | info!("valid CoAP message"); 316 | 317 | m 318 | } else { 319 | warning!("invalid CoAP message; ignoring"); 320 | 321 | return Action::Nop; 322 | }; 323 | 324 | if !coap.get_code().is_request() 325 | || match coap.get_type() { 326 | coap::Type::Confirmable | coap::Type::NonConfirmable => false, 327 | _ => true, 328 | } 329 | { 330 | warning!("CoAP message is not a valid request; ignoring"); 331 | 332 | return Action::Nop; 333 | } 334 | 335 | let src_port = udp.get_source(); 336 | 337 | // prepare a response 338 | let mut eth = ether::Frame::new(extra_buf); 339 | eth.set_destination(*src_mac); 340 | eth.set_source(MAC); 341 | 342 | let mut change = None; 343 | eth.ipv4(|ip| { 344 | ip.set_source(IP); 345 | ip.set_destination(src_ip); 346 | 347 | ip.udp(|udp| { 348 | udp.set_source(coap::PORT); 349 | udp.set_destination(src_port); 350 | 351 | udp.coap(0, |resp| { 352 | on_coap_request(state, coap, resp, &mut change) 353 | }) 354 | }); 355 | }); 356 | 357 | return Action::CoAP(change, eth); 358 | } else { 359 | // echo back the packet 360 | let src_port = udp.get_source(); 361 | 362 | // we build the response in-place 363 | // update the UDP header 364 | udp.set_source(dst_port); 365 | udp.set_destination(src_port); 366 | udp.zero_checksum(); 367 | 368 | // update the IP header 369 | let mut ip = ip.set_source(IP); 370 | ip.set_destination(src_ip); 371 | let _ip = ip.update_checksum(); 372 | 373 | // update the Ethernet header 374 | eth.set_destination(*src_mac); 375 | eth.set_source(MAC); 376 | 377 | return Action::UdpReply(eth); 378 | } 379 | } else { 380 | error!("not a valid UDP packet"); 381 | 382 | return Action::Nop; 383 | } 384 | } 385 | 386 | _ => { 387 | info!("unexpected IPv4 protocol"); 388 | } 389 | } 390 | } 391 | 392 | _ => { 393 | info!("unexpected EtherType"); 394 | } 395 | } 396 | 397 | Action::Nop 398 | } 399 | 400 | fn on_coap_request<'a>( 401 | state: &State, 402 | req: coap::Message<&[u8]>, 403 | mut resp: coap::Message<&'a mut [u8], coap::Unset>, 404 | change: &mut Option, 405 | ) -> coap::Message<&'a mut [u8]> { 406 | let code = req.get_code(); 407 | 408 | resp.set_message_id(req.get_message_id()); 409 | resp.set_type(if req.get_type() == coap::Type::Confirmable { 410 | coap::Type::Acknowledgement 411 | } else { 412 | coap::Type::NonConfirmable 413 | }); 414 | 415 | if code == coap::Method::Get.into() { 416 | info!("CoAP: GET request"); 417 | 418 | let mut opts = req.options(); 419 | while let Some(opt) = opts.next() { 420 | if opt.number() == coap::OptionNumber::UriPath { 421 | if opt.value() == b"led" && opts.next().is_none() { 422 | info!("CoAP: GET /led"); 423 | 424 | let mut tmp = [0; 13]; 425 | let payload = 426 | ujson::write(&Payload { led: state.led }, &mut tmp).expect("unreachable"); 427 | 428 | resp.set_code(coap::Response::Content); 429 | return resp.set_payload(payload.as_bytes()); 430 | } else { 431 | // fall-through: Not Found 432 | break; 433 | } 434 | } else { 435 | error!("CoAP: Bad Option"); 436 | 437 | resp.set_code(coap::Response::BadOption); 438 | return resp.no_payload(); 439 | } 440 | } 441 | } else if code == coap::Method::Put.into() { 442 | info!("CoAP: PUT request"); 443 | 444 | let mut opts = req.options(); 445 | while let Some(opt) = opts.next() { 446 | if opt.number() == coap::OptionNumber::UriPath { 447 | if opt.value() == b"led" && opts.next().is_none() { 448 | info!("CoAP: PUT /led"); 449 | 450 | if let Ok(payload) = ujson::from_bytes::(req.payload()) { 451 | info!("CoAP: Changed"); 452 | 453 | *change = Some(payload.led); 454 | 455 | resp.set_code(coap::Response::Changed); 456 | return resp.no_payload(); 457 | } else { 458 | error!("CoAP: Bad Request"); 459 | 460 | resp.set_code(coap::Response::BadRequest); 461 | return resp.no_payload(); 462 | } 463 | } else { 464 | // fall-through: Not Found 465 | break; 466 | } 467 | } else { 468 | error!("CoAP: Bad Option"); 469 | 470 | resp.set_code(coap::Response::BadOption); 471 | return resp.no_payload(); 472 | } 473 | } 474 | } else { 475 | info!("CoAP: Method Not Allowed"); 476 | 477 | resp.set_code(coap::Response::MethodNotAllowed); 478 | return resp.no_payload(); 479 | } 480 | 481 | error!("CoAP: Not Found"); 482 | 483 | resp.set_code(coap::Response::NotFound); 484 | resp.no_payload() 485 | } 486 | 487 | enum Action<'a> { 488 | ArpReply(ether::Frame<&'a mut [u8]>), 489 | EchoReply(ether::Frame<&'a mut [u8]>), 490 | CoAP(Option, ether::Frame<&'a mut [u8]>), 491 | Nop, 492 | UdpReply(ether::Frame<&'a mut [u8]>), 493 | } 494 | -------------------------------------------------------------------------------- /firmware/memory.x: -------------------------------------------------------------------------------- 1 | /* Linker script for the STM32F103C8T6 */ 2 | MEMORY 3 | { 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 64K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 20K 6 | } 7 | -------------------------------------------------------------------------------- /firmware/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2-1.cfg] 2 | source [find target/stm32f1x.cfg] 3 | -------------------------------------------------------------------------------- /firmware/openocd.gdb: -------------------------------------------------------------------------------- 1 | target extended-remote :3333 2 | 3 | # limit the backtrace 4 | set backtrace limit 32 5 | 6 | # print demangled symbols 7 | set print asm-demangle on 8 | 9 | # detect unhandled exceptions, hard faults and panics 10 | break DefaultHandler 11 | break HardFault 12 | break rust_begin_unwind 13 | 14 | # *try* to stop at the user entry point (it might be gone due to inlining) 15 | break main 16 | 17 | monitor arm semihosting enable 18 | 19 | # OR: make the microcontroller SWO pin output compatible with UART (8N1) 20 | # 8000000 must match the core clock frequency 21 | # 2000000 is the frequency of the SWO pin 22 | monitor tpiu config external uart off 8000000 2000000 23 | 24 | # enable ITM port 0 25 | monitor itm port 0 on 26 | 27 | load 28 | 29 | # start the process but immediately halt the processor 30 | stepi 31 | -------------------------------------------------------------------------------- /firmware/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(rust_2018_compatibility)] 2 | #![deny(rust_2018_idioms)] 3 | #![deny(warnings)] 4 | #![feature(proc_macro_hygiene)] 5 | #![no_std] 6 | 7 | use cortex_m::interrupt; 8 | use cortex_m::peripheral::ITM; 9 | use enc28j60::{Enc28j60, Error}; 10 | use heapless::consts; 11 | use jnet::{ieee802154, ipv4, mac}; 12 | use mrf24j40::{Channel, Mrf24j40, Role}; 13 | use stlog::spanned::error; 14 | use stlog::GlobalLog; 15 | use stm32f103xx_hal::{ 16 | delay::Delay, 17 | gpio::{ 18 | gpioa::{PA3, PA4, PA5, PA6, PA7}, 19 | gpioc::PC13, 20 | Alternate, Floating, Input, Output, PushPull, 21 | }, 22 | prelude::*, 23 | spi::Spi, 24 | stm32f103xx::{self, SPI1}, 25 | }; 26 | 27 | /* Configuration */ 28 | pub const MAC: mac::Addr = mac::Addr([0x20, 0x19, 0x02, 0x01, 0x23, 0x59]); 29 | pub const IP: ipv4::Addr = ipv4::Addr([192, 168, 1, 33]); 30 | #[allow(non_camel_case_types)] 31 | pub type CACHE_SIZE = consts::U8; 32 | 33 | pub const PAN_ID: ieee802154::PanId = ieee802154::PanId(0xbeef); 34 | pub const EXTENDED_ADDRESS: ieee802154::ExtendedAddr = 35 | ieee802154::ExtendedAddr(0x20_19_02_20_00_23_59_59); 36 | 37 | /* Constants */ 38 | const KB: u16 = 1024; // bytes 39 | 40 | pub type Ethernet = Enc28j60< 41 | Spi< 42 | SPI1, 43 | ( 44 | PA5>, 45 | PA6>, 46 | PA7>, 47 | ), 48 | >, 49 | PA4>, 50 | enc28j60::Unconnected, 51 | PA3>, 52 | >; 53 | 54 | pub type Led = PC13>; 55 | 56 | pub fn init_enc28j60( 57 | core: cortex_m::Peripherals, 58 | device: stm32f103xx::Peripherals, 59 | ) -> (Ethernet, Led) { 60 | let mut rcc = device.RCC.constrain(); 61 | let mut afio = device.AFIO.constrain(&mut rcc.apb2); 62 | let mut flash = device.FLASH.constrain(); 63 | let mut gpioa = device.GPIOA.split(&mut rcc.apb2); 64 | 65 | let clocks = rcc.cfgr.freeze(&mut flash.acr); 66 | 67 | // LED 68 | let mut gpioc = device.GPIOC.split(&mut rcc.apb2); 69 | let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); 70 | // turn the LED off during initialization 71 | led.set_high(); 72 | 73 | // SPI 74 | let mut ncs = gpioa.pa4.into_push_pull_output(&mut gpioa.crl); 75 | ncs.set_high(); 76 | let sck = gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl); 77 | let miso = gpioa.pa6; 78 | let mosi = gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl); 79 | let spi = Spi::spi1( 80 | device.SPI1, 81 | (sck, miso, mosi), 82 | &mut afio.mapr, 83 | enc28j60::MODE, 84 | 1.mhz(), 85 | clocks, 86 | &mut rcc.apb2, 87 | ); 88 | 89 | // ENC28J60 90 | let mut reset = gpioa.pa3.into_push_pull_output(&mut gpioa.crl); 91 | reset.set_low(); // held in reset 92 | let mut delay = Delay::new(core.SYST, clocks); 93 | let enc28j60 = Enc28j60::new( 94 | spi, 95 | ncs, 96 | enc28j60::Unconnected, 97 | reset, 98 | &mut delay, 99 | 7 * KB, 100 | MAC.0, 101 | ) 102 | .unwrap_or_else(|e| { 103 | match e { 104 | Error::ErevidIsZero => { 105 | error!("EREVID = 0"); 106 | } 107 | _ => { 108 | error!("Enc28j60::new failed"); 109 | } 110 | } 111 | 112 | fatal() 113 | }); 114 | 115 | // LED on after initialization 116 | led.set_low(); 117 | 118 | (enc28j60, led) 119 | } 120 | 121 | // TODO 122 | pub type Radio = Mrf24j40< 123 | Spi< 124 | SPI1, 125 | ( 126 | PA5>, 127 | PA6>, 128 | PA7>, 129 | ), 130 | >, 131 | PA4>, 132 | mrf24j40::Unconnected, 133 | PA3>, 134 | >; 135 | 136 | pub fn init_mrf24j40( 137 | core: cortex_m::Peripherals, 138 | device: stm32f103xx::Peripherals, 139 | ) -> (Radio, Led) { 140 | let mut rcc = device.RCC.constrain(); 141 | let mut afio = device.AFIO.constrain(&mut rcc.apb2); 142 | let mut flash = device.FLASH.constrain(); 143 | let mut gpioa = device.GPIOA.split(&mut rcc.apb2); 144 | 145 | let clocks = rcc.cfgr.freeze(&mut flash.acr); 146 | 147 | // LED 148 | let mut gpioc = device.GPIOC.split(&mut rcc.apb2); 149 | let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); 150 | // turn the LED off during initialization 151 | led.set_high(); 152 | 153 | // SPI 154 | let mut ncs = gpioa.pa4.into_push_pull_output(&mut gpioa.crl); 155 | ncs.set_high(); 156 | let sck = gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl); 157 | let miso = gpioa.pa6; 158 | let mosi = gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl); 159 | let spi = Spi::spi1( 160 | device.SPI1, 161 | (sck, miso, mosi), 162 | &mut afio.mapr, 163 | mrf24j40::MODE, 164 | 1.mhz(), 165 | clocks, 166 | &mut rcc.apb2, 167 | ); 168 | 169 | // MRF24J40 170 | let mut reset = gpioa.pa3.into_push_pull_output(&mut gpioa.crl); 171 | reset.set_low(); // held in reset 172 | let mut delay = Delay::new(core.SYST, clocks); 173 | let mut mrf24j40 = Mrf24j40::new( 174 | Role::Device, 175 | Channel::_22, 176 | spi, 177 | ncs, 178 | mrf24j40::Unconnected, 179 | reset, 180 | &mut delay, 181 | ) 182 | .unwrap_or_else(|_| { 183 | error!("Enc28j60::new failed"); 184 | 185 | fatal() 186 | }); 187 | 188 | mrf24j40.set_pan_id(PAN_ID.0).unwrap_or_else(|_| { 189 | error!("Mrf24j40::set_pan_id failed"); 190 | 191 | fatal() 192 | }); 193 | mrf24j40 194 | .set_extended_address(EXTENDED_ADDRESS.0) 195 | .unwrap_or_else(|_| { 196 | error!("Mrf24j40::set_extended_address failed"); 197 | 198 | fatal() 199 | }); 200 | 201 | // LED on after initialization 202 | led.set_low(); 203 | 204 | (mrf24j40, led) 205 | } 206 | 207 | pub struct ItmLogger; 208 | 209 | impl GlobalLog for ItmLogger { 210 | #[allow(unsafe_code)] 211 | fn log(&self, addr: u8) { 212 | // as written this will sometimes lose traces but we are fine with that 213 | unsafe { (*ITM::ptr()).stim[0].write_u8(addr) } 214 | } 215 | } 216 | 217 | pub fn fatal() -> ! { 218 | interrupt::disable(); 219 | 220 | // (I wish this board had more than one LED) 221 | error!("fatal error"); 222 | 223 | loop {} 224 | } 225 | -------------------------------------------------------------------------------- /mrf24j40/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mrf24j40" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | as-slice = "0.1.0" 9 | owning-slice = { git = "https://github.com/japaric/owning-slice" } 10 | 11 | [dependencies.byteorder] 12 | default-features = false 13 | version = "1.3.1" 14 | 15 | [dependencies.embedded-hal] 16 | features = ["unproven"] 17 | version = "0.2.2" -------------------------------------------------------------------------------- /mrf24j40/src/long.rs: -------------------------------------------------------------------------------- 1 | //! Long addresses 2 | 3 | #![allow(dead_code)] 4 | 5 | use crate::Action; 6 | 7 | #[derive(Clone, Copy)] 8 | pub enum Register { 9 | // RF control 10 | RFCON0 = 0x200, 11 | RFCON1 = 0x201, 12 | RFCON2 = 0x202, 13 | RFCON3 = 0x203, 14 | RFCON5 = 0x205, 15 | RFCON6 = 0x206, 16 | RFCON7 = 0x207, 17 | RFCON8 = 0x208, 18 | 19 | // Sleep calibration 20 | SLPCAL0 = 0x209, 21 | SLPCAL1 = 0x20A, 22 | SLPCAL2 = 0x20B, 23 | 24 | // RF state 25 | RFSTATE = 0x20F, 26 | 27 | // Averaged RSSI value 28 | RSSI = 0x210, 29 | 30 | // Sleep clock control 31 | SLPCON0 = 0x211, 32 | SLPCON1 = 0x220, 33 | 34 | // Wake-up time match value 35 | WAKETIMEL = 0x222, 36 | WAKETIMEH = 0x223, 37 | 38 | // Remain counter 39 | REMCNTL = 0x224, 40 | REMCNTH = 0x225, 41 | 42 | // Main counter 43 | MAINCNT0 = 0x226, 44 | MAINCNT1 = 0x227, 45 | MAINCNT2 = 0x228, 46 | MAINCNT3 = 0x229, 47 | 48 | // Test mode 49 | TESTMODE = 0x22F, 50 | 51 | // Associated coordinator extended address 52 | ASSOEADR0 = 0x230, 53 | ASSOEADR1 = 0x231, 54 | ASSOEADR2 = 0x232, 55 | ASSOEADR3 = 0x233, 56 | ASSOEADR4 = 0x234, 57 | ASSOEADR5 = 0x235, 58 | ASSOEADR6 = 0x236, 59 | ASSOEADR7 = 0x237, 60 | 61 | // Associated coordinator short address 62 | ASSOSADR0 = 0x238, 63 | ASSOSADR1 = 0x239, 64 | 65 | // Upper nonce security 66 | UPNONCE0 = 0x240, 67 | UPNONCE1 = 0x241, 68 | UPNONCE2 = 0x242, 69 | UPNONCE3 = 0x243, 70 | UPNONCE4 = 0x244, 71 | UPNONCE5 = 0x245, 72 | UPNONCE6 = 0x246, 73 | UPNONCE7 = 0x247, 74 | UPNONCE8 = 0x248, 75 | UPNONCE9 = 0x249, 76 | UPNONCE10 = 0x24A, 77 | UPNONCE11 = 0x24B, 78 | UPNONCE12 = 0x24C, 79 | } 80 | 81 | // Other long addresses 82 | pub const TX_NORMAL_FIFO: u16 = 0x000; 83 | pub const TX_BEACON_FIFO: u16 = 0x080; 84 | pub const TX_GTS1_FIFO: u16 = 0x100; 85 | pub const TX_GTS2_FIFO: u16 = 0x180; 86 | pub const SECURITY_KEY_FIFO: u16 = 0x280; 87 | pub const RX_FIFO: u16 = 0x300; 88 | 89 | impl Register { 90 | pub(crate) fn addr(&self) -> u16 { 91 | *self as u16 92 | } 93 | 94 | pub(crate) fn opcode(&self, action: Action) -> u16 { 95 | opcode(self.addr(), action) 96 | } 97 | } 98 | 99 | impl Into for Register { 100 | fn into(self) -> crate::Register { 101 | crate::Register::Long(self) 102 | } 103 | } 104 | 105 | pub(crate) fn opcode(addr: u16, action: Action) -> u16 { 106 | // sanity check that this is a 10-bit address 107 | debug_assert!(addr < (1 << 10)); 108 | 109 | // MSB always set 110 | ((1 << 11) | (addr << 1) | (action as u16)) << 4 111 | } 112 | -------------------------------------------------------------------------------- /mrf24j40/src/reg.rs: -------------------------------------------------------------------------------- 1 | pub use crate::long::Register::*; 2 | pub use crate::short::Register::*; 3 | 4 | pub const RXMCR_PANCOORD: u8 = 1 << 3; 5 | pub const RXMCR_PROMI: u8 = 1 << 0; 6 | pub const RXMCR_ERRPKT: u8 = 1 << 1; 7 | 8 | pub const TXMCR_SLOTTED: u8 = 1 << 5; 9 | 10 | pub const TXNCON_TXNTRIG: u8 = 1 << 0; 11 | 12 | pub const INTCON_RXIE: u8 = 1 << 3; 13 | pub const INTCON_TXNIE: u8 = 1 << 0; 14 | 15 | pub const INTSTAT_TXNIF: u8 = 1 << 0; 16 | pub const INTSTAT_RXIF: u8 = 1 << 3; 17 | 18 | pub const TXSTAT_TXNSTAT: u8 = 1 << 0; 19 | -------------------------------------------------------------------------------- /mrf24j40/src/short.rs: -------------------------------------------------------------------------------- 1 | //! Short addresses 2 | 3 | use crate::Action; 4 | 5 | #[allow(dead_code)] 6 | #[derive(Clone, Copy)] 7 | pub enum Register { 8 | // Receive MAC control 9 | RXMCR = 0x00, 10 | 11 | // PAN ID 12 | PANIDL = 0x01, 13 | PANIDH = 0x02, 14 | 15 | // Short Address 16 | SADRL = 0x03, 17 | SADRH = 0x04, 18 | 19 | // 64-bit extended address 20 | EADR0 = 0x05, 21 | EADR1 = 0x06, 22 | EADR2 = 0x07, 23 | EADR3 = 0x08, 24 | EADR4 = 0x09, 25 | EADR5 = 0x0A, 26 | EADR6 = 0x0B, 27 | EADR7 = 0x0C, 28 | 29 | // Receive FIFO flush 30 | RXFLUSH = 0x0D, 31 | 32 | // Beacon and superframe order 33 | ORDER = 0x10, 34 | 35 | // CSMA-CA mode control 36 | TXMCR = 0x11, 37 | 38 | // MAC ACK time-out duration 39 | ACKTMOUT = 0x12, 40 | 41 | // GTS1 and cap end slot 42 | ESLOTG1 = 0x13, 43 | 44 | // Symbol period tick 45 | SYMTICKL = 0x14, 46 | SYMTICKH = 0x15, 47 | 48 | // Power amplifier control 49 | PACON0 = 0x16, 50 | PACON1 = 0x17, 51 | PACON2 = 0x18, 52 | 53 | // Transmit beacon FIFO control 0 54 | TXBCON0 = 0x1A, 55 | 56 | // Transmit normal FIFO control 57 | TXNCON = 0x1B, 58 | 59 | // GTS1 FIFO control 60 | TXG1CON = 0x1C, 61 | 62 | // GTS2 FIFO control 63 | TXG2CON = 0x1D, 64 | 65 | // End slot of GTS3 and GTS2 66 | ESLOTG23 = 0x1E, 67 | 68 | // End slot of GTS5 and GTS4 69 | ESLOTG45 = 0x1F, 70 | 71 | // End slot of GTS6 72 | ESLOTG67 = 0x20, 73 | 74 | // TX data pending 75 | TXPEND = 0x21, 76 | 77 | // Wake control 78 | WAKECON = 0x22, 79 | 80 | // Superframe counter offset to align beacon 81 | FRMOFFSET = 0x23, 82 | 83 | // TX MAC status 84 | TXSTAT = 0x24, 85 | 86 | // Transmit beacon FIFO control 1 87 | TXBCON1 = 0x25, 88 | 89 | // Gated clock control 90 | GATECLK = 0x26, 91 | 92 | // TX turnaround time 93 | TXTIME = 0x27, 94 | 95 | // Half symbol timer 96 | HSYMTMRL = 0x28, 97 | HSYMTMRH = 0x29, 98 | 99 | // Software reset 100 | SOFTRST = 0x2A, 101 | 102 | // Security control 103 | SECCON0 = 0x2C, 104 | SECCON1 = 0x2D, 105 | 106 | // TX stabilization 107 | TXSTBL = 0x2E, 108 | 109 | // RX MAC status 110 | RXSR = 0x30, 111 | 112 | // Interrupt status 113 | INTSTAT = 0x31, 114 | 115 | // Interrupt control 116 | INTCON = 0x32, 117 | 118 | // GPIO port 119 | GPIO = 0x33, 120 | 121 | // GPIO pin direction 122 | TRISGPIO = 0x34, 123 | 124 | // Sleep acknowledgment and wake-up counter 125 | SLPACK = 0x35, 126 | 127 | // RF mode control 128 | RFCTL = 0x36, 129 | 130 | // Security control 2 131 | SECCR2 = 0x37, 132 | 133 | // Baseband 134 | BBREG0 = 0x38, 135 | BBREG1 = 0x39, 136 | BBREG2 = 0x3A, 137 | BBREG3 = 0x3B, 138 | BBREG4 = 0x3C, 139 | BBREG6 = 0x3E, 140 | 141 | // Energy detection for CCA 142 | CCAEDTH = 0x3F, 143 | } 144 | 145 | impl Register { 146 | pub(crate) fn addr(&self) -> u8 { 147 | *self as u8 148 | } 149 | } 150 | 151 | pub(crate) fn opcode(addr: u8, action: Action) -> u8 { 152 | // sanity check that this is a 6-bit address 153 | debug_assert!(addr < (1 << 6)); 154 | 155 | (addr << 1) | (action as u8) 156 | } 157 | 158 | impl Into for Register { 159 | fn into(self) -> crate::Register { 160 | crate::Register::Short(self) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /panic-never/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] 2 | rustflags = [ 3 | "-C", "link-arg=-Tlink.x", 4 | ] 5 | 6 | [build] 7 | target = "thumbv7m-none-eabi" # Cortex-M3 8 | -------------------------------------------------------------------------------- /panic-never/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /panic-never/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jorge Aparicio "] 3 | edition = "2018" 4 | readme = "README.md" 5 | name = "panic-never" 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | cortex-m-rt = "0.6.7" 10 | jnet = { path = ".." } 11 | cortex-m = "0.5.8" 12 | 13 | [profile.release] 14 | codegen-units = 1 # better optimizations 15 | debug = true # symbols are nice and they don't increase the size on Flash 16 | incremental = false 17 | lto = true # better optimizations 18 | -------------------------------------------------------------------------------- /panic-never/README.md: -------------------------------------------------------------------------------- 1 | # `panic-never` 2 | 3 | This crate is used to verify that the code generated for the JNeT API doesn't 4 | contain any panicking branch where `Result` should capture all input errors 5 | (e.g. parsing errors). 6 | -------------------------------------------------------------------------------- /panic-never/examples/arp.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::{arp, mac, ipv4}; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut PACKET: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = arp::Packet::parse(&mut BUFFER[..]) { 17 | match p.downcast() { 18 | Ok(p) => { 19 | PACKET = Some(p); 20 | } 21 | Err(p) => { 22 | force_eval!(p.get_sha()); 23 | force_eval!(p.get_spa()); 24 | force_eval!(p.get_tha()); 25 | force_eval!(p.get_tpa()); 26 | } 27 | } 28 | } else { 29 | asm::nop(); 30 | } 31 | } 32 | 33 | #[exception] 34 | unsafe fn SVCall() { 35 | if let Some(mut p) = PACKET.take() { 36 | force_eval!(p.get_sha()); 37 | force_eval!(p.get_spa()); 38 | force_eval!(p.get_tha()); 39 | force_eval!(p.get_tpa()); 40 | force_eval!(p.is_a_probe()); 41 | 42 | force_eval!(p.set_sha(mac::Addr::BROADCAST)); 43 | force_eval!(p.set_spa(ipv4::Addr::UNSPECIFIED)); 44 | force_eval!(p.set_tha(mac::Addr::BROADCAST)); 45 | force_eval!(p.set_tpa(ipv4::Addr::UNSPECIFIED)); 46 | } 47 | } 48 | 49 | #[entry] 50 | fn main() -> ! { 51 | loop {} 52 | } 53 | -------------------------------------------------------------------------------- /panic-never/examples/coap.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::coap; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut MESSAGE: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = coap::Message::parse(&mut BUFFER[..]) { 17 | MESSAGE = Some(p); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(m) = MESSAGE.take() { 26 | force_eval!(m.get_version()); 27 | force_eval!(m.get_type()); 28 | force_eval!(m.get_token_length()); 29 | force_eval!(m.get_code()); 30 | force_eval!(m.get_message_id()); 31 | force_eval!(m.token()); 32 | force_eval!(m.payload()); 33 | for opt in m.options() { 34 | force_eval!(opt.number()); 35 | force_eval!(opt.value()); 36 | } 37 | } 38 | } 39 | 40 | #[entry] 41 | fn main() -> ! { 42 | loop {} 43 | } 44 | -------------------------------------------------------------------------------- /panic-never/examples/ether.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::ether; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut FRAME: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(f) = ether::Frame::parse(&mut BUFFER[..]) { 17 | FRAME = Some(f); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(f) = FRAME.take() { 26 | force_eval!(f.get_destination()); 27 | force_eval!(f.get_source()); 28 | force_eval!(f.get_type()); 29 | force_eval!(f.payload()); 30 | } 31 | } 32 | 33 | #[entry] 34 | fn main() -> ! { 35 | loop {} 36 | } 37 | -------------------------------------------------------------------------------- /panic-never/examples/icmp.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::{icmp, Unknown, Valid}; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut MESSAGE: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(m) = icmp::Message::parse(&mut BUFFER[..]) { 17 | MESSAGE = Some(m); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(m) = MESSAGE.take() { 26 | force_eval!(m.get_type()); 27 | force_eval!(m.get_code()); 28 | force_eval!(m.payload()); 29 | force_eval!(m.len()); 30 | } 31 | } 32 | 33 | #[entry] 34 | fn main() -> ! { 35 | loop {} 36 | } 37 | -------------------------------------------------------------------------------- /panic-never/examples/icmpv6.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::{ 9 | icmpv6::{self, EchoReply, EchoRequest, NeighborAdvertisement, NeighborSolicitation}, 10 | ipv6, Unknown, 11 | }; 12 | 13 | const LEN: usize = 128; 14 | static mut BUFFER: [u8; LEN] = [0; LEN]; 15 | static mut NA: Option> = None; 16 | static mut NS: Option> = None; 17 | static mut ERQ: Option> = None; 18 | static mut ERP: Option> = None; 19 | static mut U: Option> = None; 20 | 21 | #[exception] 22 | unsafe fn SysTick() { 23 | if let Ok(m) = icmpv6::Message::parse(&mut BUFFER[..]) { 24 | match m.downcast::() { 25 | Ok(na) => NA = Some(na), 26 | Err(m) => match m.downcast::() { 27 | Ok(ns) => NS = Some(ns), 28 | Err(m) => match m.downcast::() { 29 | Ok(erq) => ERQ = Some(erq), 30 | Err(m) => match m.downcast::() { 31 | Ok(erp) => ERP = Some(erp), 32 | Err(u) => U = Some(u), 33 | }, 34 | }, 35 | }, 36 | } 37 | } else { 38 | asm::nop(); 39 | } 40 | } 41 | 42 | #[exception] 43 | unsafe fn SVCall() { 44 | if let Some(na) = NA.take() { 45 | force_eval!(na.get_type()); 46 | force_eval!(na.get_code()); 47 | force_eval!(na.get_checksum()); 48 | force_eval!(na.get_router()); 49 | force_eval!(na.get_solicited()); 50 | force_eval!(na.get_override()); 51 | force_eval!(na.get_target()); 52 | force_eval!(na.get_target_ll()); 53 | } 54 | 55 | if let Some(ns) = NS.take() { 56 | force_eval!(ns.get_type()); 57 | force_eval!(ns.get_code()); 58 | force_eval!(ns.get_checksum()); 59 | force_eval!(ns.get_target()); 60 | force_eval!(ns.get_source_ll()); 61 | } 62 | 63 | if let Some(erq) = ERQ.take() { 64 | force_eval!(erq.get_type()); 65 | force_eval!(erq.get_code()); 66 | force_eval!(erq.get_checksum()); 67 | force_eval!(erq.get_identifier()); 68 | force_eval!(erq.get_sequence_number()); 69 | force_eval!(erq.payload()); 70 | } 71 | 72 | if let Some(erp) = ERP.take() { 73 | force_eval!(erp.get_type()); 74 | force_eval!(erp.get_code()); 75 | force_eval!(erp.get_checksum()); 76 | force_eval!(erp.get_identifier()); 77 | force_eval!(erp.get_sequence_number()); 78 | force_eval!(erp.payload()); 79 | } 80 | 81 | if let Some(u) = U.take() { 82 | force_eval!(u.get_type()); 83 | force_eval!(u.get_code()); 84 | force_eval!(u.get_checksum()); 85 | force_eval!(u.verify_checksum( 86 | ipv6::Addr::ALL_NODES, 87 | ipv6::Addr::ALL_NODES 88 | )); 89 | } 90 | } 91 | 92 | #[entry] 93 | fn main() -> ! { 94 | loop {} 95 | } 96 | -------------------------------------------------------------------------------- /panic-never/examples/ieee802154.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::ieee802154; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut FRAME: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = ieee802154::Frame::parse(&mut BUFFER[..]) { 17 | FRAME = Some(p); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(f) = FRAME.take() { 26 | force_eval!(f.get_type()); 27 | force_eval!(f.get_security_enabled()); 28 | force_eval!(f.get_frame_pending()); 29 | force_eval!(f.get_ack_request()); 30 | force_eval!(f.get_intra_pan()); 31 | force_eval!(f.get_dest_addr_mode()); 32 | force_eval!(f.get_src_addr_mode()); 33 | force_eval!(f.get_sequence_number()); 34 | force_eval!(f.get_dest_pan_id()); 35 | force_eval!(f.get_dest_addr()); 36 | force_eval!(f.get_src_pan_id()); 37 | force_eval!(f.get_src_addr()); 38 | force_eval!(f.header()); 39 | force_eval!(f.payload()); 40 | } 41 | } 42 | 43 | #[entry] 44 | fn main() -> ! { 45 | loop {} 46 | } 47 | -------------------------------------------------------------------------------- /panic-never/examples/iphc.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::sixlowpan::iphc; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut PACKET: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = iphc::Packet::parse(&mut BUFFER[..]) { 17 | PACKET = Some(p); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(p) = PACKET.take() { 26 | force_eval!(p.get_next_header()); 27 | force_eval!(p.get_hop_limit()); 28 | force_eval!(p.get_source()); 29 | force_eval!(p.get_destination()); 30 | force_eval!(p.payload()); 31 | force_eval!(p.get_tf()); 32 | force_eval!(p.get_nh()); 33 | force_eval!(p.get_hlim()); 34 | force_eval!(p.get_cid()); 35 | force_eval!(p.get_sac()); 36 | force_eval!(p.get_sam()); 37 | force_eval!(p.get_m()); 38 | force_eval!(p.get_dac()); 39 | force_eval!(p.get_dam()); 40 | } 41 | } 42 | 43 | #[entry] 44 | fn main() -> ! { 45 | loop {} 46 | } 47 | -------------------------------------------------------------------------------- /panic-never/examples/ipv4.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::{ipv4, Valid}; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut PACKET: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = ipv4::Packet::parse(&mut BUFFER[..]) { 17 | PACKET = Some(p); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(p) = PACKET.take() { 26 | force_eval!(p.get_version()); 27 | force_eval!(p.get_ihl()); 28 | force_eval!(p.get_dscp()); 29 | force_eval!(p.get_ecn()); 30 | force_eval!(p.get_total_length()); 31 | force_eval!(p.len()); 32 | force_eval!(p.get_identification()); 33 | force_eval!(p.get_df()); 34 | force_eval!(p.get_mf()); 35 | force_eval!(p.get_fragment_offset()); 36 | force_eval!(p.get_ttl()); 37 | force_eval!(p.get_protocol()); 38 | force_eval!(p.get_source()); 39 | force_eval!(p.get_destination()); 40 | force_eval!(p.payload()); 41 | } 42 | } 43 | 44 | #[entry] 45 | fn main() -> ! { 46 | loop {} 47 | } 48 | -------------------------------------------------------------------------------- /panic-never/examples/nhc.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::sixlowpan::nhc; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut PACKET: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = nhc::UdpPacket::parse(&mut BUFFER[..]) { 17 | PACKET = Some(p); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(mut p) = PACKET.take() { 26 | force_eval!(p.get_source()); 27 | force_eval!(p.get_destination()); 28 | force_eval!(p.get_checksum()); 29 | force_eval!(p.payload()); 30 | force_eval!(p.payload_mut()); 31 | } 32 | } 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | loop {} 37 | } 38 | -------------------------------------------------------------------------------- /panic-never/examples/udp.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::{entry, exception}; 6 | use panic_never::force_eval; 7 | 8 | use jnet::udp; 9 | 10 | const LEN: usize = 128; 11 | static mut BUFFER: [u8; LEN] = [0; LEN]; 12 | static mut PACKET: Option> = None; 13 | 14 | #[exception] 15 | unsafe fn SysTick() { 16 | if let Ok(p) = udp::Packet::parse(&mut BUFFER[..]) { 17 | PACKET = Some(p); 18 | } else { 19 | asm::nop(); 20 | } 21 | } 22 | 23 | #[exception] 24 | unsafe fn SVCall() { 25 | if let Some(p) = PACKET.take() { 26 | force_eval!(p.get_source()); 27 | force_eval!(p.get_destination()); 28 | force_eval!(p.get_length()); 29 | force_eval!(p.len()); 30 | force_eval!(p.payload()); 31 | } 32 | } 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | loop {} 37 | } 38 | -------------------------------------------------------------------------------- /panic-never/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ 4 | FLASH : ORIGIN = 0x00000000, LENGTH = 256K 5 | RAM : ORIGIN = 0x20000000, LENGTH = 64K 6 | } 7 | -------------------------------------------------------------------------------- /panic-never/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::panic::PanicInfo; 4 | 5 | use cortex_m::asm; 6 | 7 | #[macro_export] 8 | macro_rules! force_eval { 9 | ($e:expr) => { 10 | unsafe { core::ptr::read_volatile(&$e); } 11 | } 12 | } 13 | 14 | #[panic_handler] 15 | fn panic(_: &PanicInfo) -> ! { 16 | // uncomment to debug link errors 17 | // nop(); 18 | extern "C" { 19 | #[link_name = "This crate contains at least one panicking branch"] 20 | fn panic() -> !; 21 | } 22 | 23 | unsafe { panic() } 24 | } 25 | 26 | #[allow(dead_code)] 27 | #[inline(never)] 28 | fn nop() -> ! { 29 | loop { 30 | asm::nop(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/arp.rs: -------------------------------------------------------------------------------- 1 | //! ARP: Address Resolution Protocol 2 | //! 3 | //! # References 4 | //! 5 | //! - [RFC 826: An Ethernet Address Resolution Protocol][rfc] 6 | //! 7 | //! [rfc]: https://tools.ietf.org/html/rfc826 8 | 9 | use core::fmt; 10 | use core::marker::PhantomData; 11 | use core::ops::{Range, RangeFrom}; 12 | 13 | use as_slice::{AsMutSlice, AsSlice}; 14 | use byteorder::{ByteOrder, NetworkEndian as NE}; 15 | use cast::usize; 16 | use owning_slice::Truncate; 17 | 18 | use crate::{ 19 | ether, ipv4, mac, 20 | traits::{TryFrom, TryInto, UncheckedIndex}, 21 | Unknown, 22 | }; 23 | 24 | /* Packet structure */ 25 | const HTYPE: Range = 0..2; 26 | const PTYPE: Range = 2..4; 27 | const HLEN: usize = 4; 28 | const PLEN: usize = 5; 29 | const OPER: Range = 6..8; 30 | const PAYLOAD: RangeFrom = 8..; 31 | 32 | /// Size of the ARP header 33 | pub const HEADER_SIZE: u8 = PAYLOAD.start as u8; 34 | 35 | // NOTE Use only for Packet<_, Ethernet, Ipv4> 36 | const SHA: Range = 8..14; 37 | const SPA: Range = 14..18; 38 | const THA: Range = 18..24; 39 | const TPA: Range = 24..28; 40 | 41 | /// [Type state] The Ethernet hardware type 42 | pub enum Ethernet {} 43 | 44 | /// [Type state] The IPv4 protocol type 45 | pub enum Ipv4 {} 46 | 47 | /// ARP packet 48 | pub struct Packet 49 | where 50 | BUFFER: AsSlice, 51 | HTYPE: 'static, 52 | PTYPE: 'static, 53 | { 54 | buffer: BUFFER, 55 | _htype: PhantomData, 56 | _ptype: PhantomData, 57 | } 58 | 59 | /* Ethernet - Ipv4 */ 60 | impl Packet 61 | where 62 | B: AsSlice + AsMutSlice + Truncate, 63 | { 64 | /* Constructors */ 65 | /// Transforms the given buffer into an ARP packet 66 | /// 67 | /// This function populates the following header fields: 68 | /// 69 | /// - HTYPE = Ethernet 70 | /// - PTYPE = IPv4 71 | /// - HLEN = 6 72 | /// - PLEN = 4 73 | /// - OPER = Request 74 | pub fn new(buffer: B) -> Self { 75 | let len = HEADER_SIZE + 20; 76 | assert!(buffer.as_slice().len() >= usize(len)); 77 | 78 | let mut packet: Packet = Packet { 79 | buffer, 80 | _htype: PhantomData, 81 | _ptype: PhantomData, 82 | }; 83 | 84 | packet.buffer.truncate(len); 85 | packet.set_htype(HardwareType::Ethernet); 86 | packet.set_ptype(ether::Type::Ipv4); 87 | packet.buffer.as_mut_slice()[HLEN] = 6; 88 | packet.buffer.as_mut_slice()[PLEN] = 4; 89 | packet.set_oper(Operation::Request); 90 | 91 | Packet { 92 | buffer: packet.buffer, 93 | _htype: PhantomData, 94 | _ptype: PhantomData, 95 | } 96 | } 97 | } 98 | 99 | impl Packet 100 | where 101 | B: AsSlice, 102 | { 103 | /* Getters */ 104 | /// Returns the SHA (Sender Hardware Address) field of the payload 105 | pub fn get_sha(&self) -> mac::Addr { 106 | unsafe { mac::Addr(*(self.as_slice().as_ptr().add(SHA.start) as *const _)) } 107 | } 108 | 109 | /// Returns the SPA (Sender Protocol Address) field of the payload 110 | pub fn get_spa(&self) -> ipv4::Addr { 111 | unsafe { ipv4::Addr(*(self.as_slice().as_ptr().add(SPA.start) as *const _)) } 112 | } 113 | 114 | /// Returns the THA (Target Hardware Address) field of the payload 115 | pub fn get_tha(&self) -> mac::Addr { 116 | unsafe { mac::Addr(*(self.as_slice().as_ptr().add(THA.start) as *const _)) } 117 | } 118 | 119 | /// Returns the TPA (Target Protocol Address) field of the payload 120 | pub fn get_tpa(&self) -> ipv4::Addr { 121 | unsafe { ipv4::Addr(*(self.as_slice().as_ptr().add(TPA.start) as *const _)) } 122 | } 123 | 124 | /// Is this an ARP probe? 125 | pub fn is_a_probe(&self) -> bool { 126 | self.get_spa() == ipv4::Addr::UNSPECIFIED 127 | } 128 | } 129 | 130 | impl Packet 131 | where 132 | B: AsSlice + AsMutSlice, 133 | { 134 | /* Setters */ 135 | /// Sets the SHA (Sender Hardware Address) field of the payload 136 | pub fn set_sha(&mut self, sha: mac::Addr) { 137 | unsafe { 138 | self.as_mut_slice().rm(SHA).copy_from_slice(&sha.0); 139 | } 140 | } 141 | 142 | /// Sets the SPA (Sender Protocol Address) field of the payload 143 | pub fn set_spa(&mut self, spa: ipv4::Addr) { 144 | unsafe { 145 | self.as_mut_slice().rm(SPA).copy_from_slice(&spa.0); 146 | } 147 | } 148 | 149 | /// Sets the THA (Target Hardware Address) field of the payload 150 | pub fn set_tha(&mut self, tha: mac::Addr) { 151 | unsafe { 152 | self.as_mut_slice().rm(THA).copy_from_slice(&tha.0); 153 | } 154 | } 155 | 156 | /// Sets the TPA (Target Protocol Address) field of the payload 157 | pub fn set_tpa(&mut self, tpa: ipv4::Addr) { 158 | unsafe { 159 | self.as_mut_slice().rm(TPA).copy_from_slice(&tpa.0); 160 | } 161 | } 162 | 163 | /* Miscellaneous */ 164 | /// ARP announcement 165 | /// 166 | /// Shortcut for setting these fields 167 | /// 168 | /// - OPER = Request 169 | /// - SPA = TPA = addr 170 | /// - THA = 00:00:00:00:00:00 171 | pub fn announce(&mut self, addr: ipv4::Addr) { 172 | self.set_oper(Operation::Request); 173 | 174 | self.set_spa(addr); 175 | 176 | self.set_tha(mac::Addr([0; 6])); 177 | self.set_tpa(addr); 178 | } 179 | 180 | /// ARP probe 181 | /// 182 | /// Shortcut for setting these fields 183 | /// 184 | /// - OPER = Request 185 | /// - SPA = 0.0.0.0 186 | /// - THA = 00:00:00:00:00:00 187 | /// - TPA = addr 188 | pub fn probe(&mut self, addr: ipv4::Addr) { 189 | self.set_oper(Operation::Request); 190 | 191 | self.set_spa(ipv4::Addr::UNSPECIFIED); 192 | 193 | self.set_tha(mac::Addr([0; 6])); 194 | self.set_tpa(addr); 195 | } 196 | } 197 | 198 | /* Unknown - Unknown */ 199 | impl Packet 200 | where 201 | B: AsSlice, 202 | { 203 | /* Getters */ 204 | // htype: covered by the generic impl 205 | // ptype: covered by the generic impl 206 | // hlen: covered by the generic impl 207 | // plen: covered by the generic impl 208 | // oper: covered by the generic impl 209 | 210 | /// Returns the SHA (Sender Hardware Address) field of the payload 211 | pub fn get_sha(&self) -> &[u8] { 212 | let end = usize(self.get_hlen()); 213 | 214 | unsafe { self.payload().rt(..end) } 215 | } 216 | 217 | /// Returns the SPA (Sender Protocol Address) field of the payload 218 | pub fn get_spa(&self) -> &[u8] { 219 | let start = usize(self.get_hlen()); 220 | let end = start + usize(self.get_plen()); 221 | 222 | unsafe { self.payload().r(start..end) } 223 | } 224 | 225 | /// Returns the THA (Target Hardware Address) field of the payload 226 | pub fn get_tha(&self) -> &[u8] { 227 | let start = usize(self.get_hlen()) + usize(self.get_plen()); 228 | let end = start + usize(self.get_hlen()); 229 | 230 | unsafe { self.payload().r(start..end) } 231 | } 232 | 233 | /// Returns the TPA (Target Protocol Address) field of the payload 234 | pub fn get_tpa(&self) -> &[u8] { 235 | let start = 2 * usize(self.get_hlen()) + usize(self.get_plen()); 236 | let end = start + usize(self.get_plen()); 237 | 238 | unsafe { self.payload().r(start..end) } 239 | } 240 | 241 | /* Miscellaneous */ 242 | /// Interprets this packet as `Packet` 243 | pub fn downcast(self) -> Result, Self> { 244 | TryInto::try_into(self) 245 | } 246 | } 247 | 248 | impl Packet 249 | where 250 | B: AsSlice, 251 | { 252 | /// Parses bytes into an ARP packet 253 | pub fn parse(bytes: B) -> Result { 254 | if bytes.as_slice().len() < usize(HEADER_SIZE) { 255 | // too small; header doesn't fit 256 | return Err(bytes); 257 | } 258 | 259 | let p = Packet { 260 | buffer: bytes, 261 | _htype: PhantomData, 262 | _ptype: PhantomData, 263 | }; 264 | 265 | let hlen = p.get_hlen(); 266 | let plen = p.get_plen(); 267 | 268 | let payload_len = 2 * (usize(hlen) + usize(plen)); 269 | if p.as_slice().len() < usize(HEADER_SIZE) + payload_len { 270 | // too small; payload doesn't fit 271 | Err(p.buffer) 272 | } else { 273 | Ok(p) 274 | } 275 | } 276 | } 277 | 278 | impl Packet 279 | where 280 | B: AsSlice + AsMutSlice, 281 | { 282 | /* Setters */ 283 | /// Sets the HTYPE (Hardware TYPE) field of the header 284 | pub fn set_htype(&mut self, htype: HardwareType) { 285 | NE::write_u16(&mut self.as_mut_slice()[HTYPE], htype.into()); 286 | } 287 | 288 | /// Sets the PTYPE (Protocol TYPE) field of the header 289 | pub fn set_ptype(&mut self, ptype: ether::Type) { 290 | NE::write_u16(&mut self.as_mut_slice()[PTYPE], ptype.into()); 291 | } 292 | } 293 | 294 | impl TryFrom> for Packet 295 | where 296 | B: AsSlice, 297 | { 298 | type Error = Packet; 299 | 300 | fn try_from(p: Packet) -> Result> { 301 | if p.get_htype() == HardwareType::Ethernet 302 | && p.get_ptype() == ether::Type::Ipv4 303 | && p.get_hlen() == 6 304 | && p.get_plen() == 4 305 | { 306 | Ok(Packet { 307 | buffer: p.buffer, 308 | _htype: PhantomData, 309 | _ptype: PhantomData, 310 | }) 311 | } else { 312 | Err(p) 313 | } 314 | } 315 | } 316 | 317 | /* HTYPE - PTYPE */ 318 | impl Packet 319 | where 320 | B: AsSlice, 321 | { 322 | /* Getters */ 323 | /// Returns the HTYPE (Hardware TYPE) field of the header 324 | pub fn get_htype(&self) -> HardwareType { 325 | if typeid!(H == Ethernet) { 326 | HardwareType::Ethernet 327 | } else { 328 | NE::read_u16(&self.header_()[HTYPE]).into() 329 | } 330 | } 331 | 332 | /// Returns the PTYPE (Protocol TYPE) field of the header 333 | pub fn get_ptype(&self) -> ether::Type { 334 | if typeid!(P == Ipv4) { 335 | ether::Type::Ipv4 336 | } else { 337 | NE::read_u16(&self.header_()[PTYPE]).into() 338 | } 339 | } 340 | 341 | /// Returns the HLEN (Hardware LENgth) field of the header 342 | pub fn get_hlen(&self) -> u8 { 343 | if typeid!(H == Ethernet) { 344 | 6 345 | } else { 346 | self.header_()[HLEN] 347 | } 348 | } 349 | 350 | /// Returns the PLEN (Protocol LENgth) field of the header 351 | pub fn get_plen(&self) -> u8 { 352 | if typeid!(P == Ipv4) { 353 | 4 354 | } else { 355 | self.header_()[PLEN] 356 | } 357 | } 358 | 359 | /// Returns the OPER (OPERation) field of the header 360 | pub fn get_oper(&self) -> Operation { 361 | NE::read_u16(&self.header_()[OPER]).into() 362 | } 363 | 364 | /// View into the payload 365 | /// 366 | /// NOTE this may contain padding bytes at the end 367 | pub fn payload(&self) -> &[u8] { 368 | unsafe { self.as_slice().rf(PAYLOAD) } 369 | } 370 | 371 | /// Returns the canonical length of this packet 372 | /// 373 | /// This ignores padding bytes, if any 374 | pub fn len(&self) -> u8 { 375 | HEADER_SIZE + 2 * (self.get_hlen() + self.get_plen()) 376 | } 377 | 378 | /* Miscellaneous */ 379 | /// Frees the underlying buffer 380 | pub fn free(self) -> B { 381 | self.buffer 382 | } 383 | 384 | /* Private */ 385 | fn header_(&self) -> &[u8; 8] { 386 | debug_assert!(self.as_slice().len() >= 8); 387 | 388 | unsafe { &*(self.as_slice().as_ptr() as *const _) } 389 | } 390 | 391 | fn as_slice(&self) -> &[u8] { 392 | self.buffer.as_slice() 393 | } 394 | } 395 | 396 | impl Packet 397 | where 398 | B: AsSlice + AsMutSlice, 399 | { 400 | /* Setters */ 401 | /// Sets the OPER (OPERation) field of the header 402 | pub fn set_oper(&mut self, oper: Operation) { 403 | NE::write_u16(&mut self.as_mut_slice()[OPER], oper.into()) 404 | } 405 | 406 | /// Mutable view into the payload 407 | /// 408 | /// NOTE this may contain padding bytes at the end 409 | pub fn payload_mut(&mut self) -> &mut [u8] { 410 | &mut self.as_mut_slice()[PAYLOAD] 411 | } 412 | 413 | /* Private */ 414 | fn as_mut_slice(&mut self) -> &mut [u8] { 415 | self.buffer.as_mut_slice() 416 | } 417 | } 418 | 419 | impl Clone for Packet 420 | where 421 | B: Clone + AsSlice, 422 | { 423 | fn clone(&self) -> Self { 424 | Packet { 425 | buffer: self.buffer.clone(), 426 | _htype: PhantomData, 427 | _ptype: PhantomData, 428 | } 429 | } 430 | } 431 | 432 | impl Copy for Packet where B: Copy + AsSlice {} 433 | 434 | impl fmt::Debug for Packet 435 | where 436 | B: AsSlice, 437 | { 438 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 439 | f.debug_struct("arp::Packet") 440 | .field("oper", &self.get_oper()) 441 | .field("sha", &self.get_sha()) 442 | .field("spa", &self.get_spa()) 443 | .field("tha", &self.get_tha()) 444 | .field("tpa", &self.get_tpa()) 445 | .finish() 446 | } 447 | } 448 | 449 | impl fmt::Debug for Packet 450 | where 451 | B: AsSlice, 452 | { 453 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 454 | f.debug_struct("arp::Packet") 455 | .field("htype", &self.get_htype()) 456 | .field("ptype", &self.get_ptype()) 457 | .field("hlen", &self.get_hlen()) 458 | .field("plen", &self.get_plen()) 459 | .field("oper", &self.get_oper()) 460 | .field("sha", &self.get_sha()) 461 | .field("spa", &self.get_spa()) 462 | .field("tha", &self.get_tha()) 463 | .field("tpa", &self.get_tpa()) 464 | .finish() 465 | } 466 | } 467 | 468 | full_range!( 469 | u16, 470 | /// Hardware type 471 | #[derive(Clone, Copy, Debug, PartialEq)] 472 | pub enum HardwareType { 473 | /// Ethernet 474 | Ethernet = 1, 475 | } 476 | ); 477 | 478 | full_range!( 479 | u16, 480 | /// ARP operation 481 | #[derive(Clone, Copy, Debug, PartialEq)] 482 | pub enum Operation { 483 | /// Request operation 484 | Request = 1, 485 | /// Reply operation 486 | Reply = 2, 487 | } 488 | ); 489 | 490 | #[cfg(test)] 491 | mod tests { 492 | use rand::{self, RngCore}; 493 | 494 | use crate::{arp, ether, ipv4, mac}; 495 | 496 | const SIZE: usize = 46; 497 | 498 | const BYTES: &[u8; SIZE] = &[ 499 | 255, 255, 255, 255, 255, 255, // eth: destination 500 | 120, 68, 118, 217, 106, 124, // eth: source 501 | 8, 6, // eth: type 502 | 0, 1, // arp: HTYPE 503 | 8, 0, // arp: PTYPE 504 | 6, // arp: HLEN 505 | 4, // arp: PLEN 506 | 0, 2, // arp: OPER 507 | 120, 68, 118, 217, 106, 124, // arp: SHA 508 | 192, 168, 1, 1, // arp: SPA 509 | 32, 24, 3, 1, 0, 0, // arp: THA 510 | 192, 168, 1, 33, // arp: TPA 511 | 0, 0, 0, 0, // eth: padding 512 | ]; 513 | 514 | const SENDER_MAC: mac::Addr = mac::Addr([0x78, 0x44, 0x76, 0xd9, 0x6a, 0x7c]); 515 | const SENDER_IP: ipv4::Addr = ipv4::Addr([192, 168, 1, 1]); 516 | 517 | const TARGET_MAC: mac::Addr = mac::Addr([0x20, 0x18, 0x03, 0x01, 0x00, 0x00]); 518 | const TARGET_IP: ipv4::Addr = ipv4::Addr([192, 168, 1, 33]); 519 | 520 | #[test] 521 | fn construct() { 522 | // NOTE start with randomized array to make sure we set *everything* correctly 523 | let mut array: [u8; SIZE] = [0; SIZE]; 524 | rand::thread_rng().fill_bytes(&mut array); 525 | 526 | let mut eth = ether::Frame::new(&mut array[..]); 527 | 528 | eth.set_destination(mac::Addr::BROADCAST); 529 | eth.set_source(SENDER_MAC); 530 | 531 | eth.arp(|arp| { 532 | arp.set_oper(arp::Operation::Reply); 533 | arp.set_sha(SENDER_MAC); 534 | arp.set_spa(SENDER_IP); 535 | arp.set_tha(TARGET_MAC); 536 | arp.set_tpa(TARGET_IP); 537 | }); 538 | 539 | // ignore the padding 540 | assert_eq!(eth.as_bytes(), &BYTES[..SIZE - 4]); 541 | } 542 | 543 | #[test] 544 | fn parse() { 545 | let eth = ether::Frame::parse(&BYTES[..]).unwrap(); 546 | let packet = arp::Packet::parse(eth.payload()).unwrap(); 547 | 548 | assert_eq!(packet.get_htype(), arp::HardwareType::Ethernet); 549 | assert_eq!(packet.get_ptype(), ether::Type::Ipv4); 550 | assert_eq!(packet.get_hlen(), 6); 551 | assert_eq!(packet.get_plen(), 4); 552 | assert_eq!(packet.get_oper(), arp::Operation::Reply); 553 | assert_eq!(packet.get_sha(), &SENDER_MAC.0); 554 | assert_eq!(packet.get_spa(), &SENDER_IP.0); 555 | assert_eq!(packet.get_tha(), &TARGET_MAC.0); 556 | assert_eq!(packet.get_tpa(), &TARGET_IP.0); 557 | } 558 | } 559 | -------------------------------------------------------------------------------- /src/ether.rs: -------------------------------------------------------------------------------- 1 | //! Ethernet II 2 | 3 | use core::{ 4 | fmt, 5 | ops::{Range, RangeFrom}, 6 | }; 7 | 8 | use as_slice::{AsMutSlice, AsSlice}; 9 | use byteorder::{ByteOrder, NetworkEndian as NE}; 10 | use cast::{u16, usize}; 11 | use owning_slice::{IntoSliceFrom, Truncate}; 12 | 13 | use crate::{arp, ipv4, ipv6, mac, traits::UncheckedIndex, Invalid}; 14 | 15 | /* Frame format */ 16 | const DESTINATION: Range = 0..6; 17 | const SOURCE: Range = 6..12; 18 | const TYPE: Range = 12..14; 19 | const PAYLOAD: RangeFrom = 14..; 20 | 21 | /// Size of the MAC header 22 | pub const HEADER_SIZE: u8 = TYPE.end as u8; 23 | 24 | /// Layer 2 Ethernet frame 25 | /// 26 | /// # Structure 27 | /// 28 | /// - MAC destination. 6 bytes 29 | /// - MAC source. 6 bytes 30 | /// - Ethertype. 2 bytes 31 | /// - Payload. 46-1500 bytes (\*) 32 | /// - Frame check sequence. 4 bytes (\*) 33 | /// 34 | /// (\*) This frame representation does NOT include the frame check sequence nor (zero) pads the 35 | /// payload to the minimum size of 46 bytes. 36 | #[derive(Clone, Copy)] 37 | pub struct Frame 38 | where 39 | BUFFER: AsSlice, 40 | { 41 | buffer: BUFFER, 42 | } 43 | 44 | impl Frame 45 | where 46 | B: AsSlice, 47 | { 48 | /* Constructors */ 49 | /// Creates a new Ethernet frame from the given buffer 50 | pub fn new(buffer: B) -> Self { 51 | assert!(buffer.as_slice().len() >= usize(HEADER_SIZE)); 52 | 53 | Frame { buffer } 54 | } 55 | 56 | /// Parses bytes into an Ethernet frame 57 | pub fn parse(bytes: B) -> Result { 58 | if bytes.as_slice().len() < usize(HEADER_SIZE) { 59 | Err(bytes) 60 | } else { 61 | Ok(Frame { buffer: bytes }) 62 | } 63 | } 64 | 65 | /* Getters */ 66 | /// Returns the Destination field of the header 67 | pub fn get_destination(&self) -> mac::Addr { 68 | unsafe { mac::Addr(*(self.as_slice().as_ptr().add(DESTINATION.start) as *const _)) } 69 | } 70 | 71 | /// Returns the Source field of the header 72 | pub fn get_source(&self) -> mac::Addr { 73 | unsafe { mac::Addr(*(self.as_slice().as_ptr().add(SOURCE.start) as *const _)) } 74 | } 75 | 76 | /// Returns the Type field of the header 77 | pub fn get_type(&self) -> Type { 78 | NE::read_u16(&self.header_()[TYPE]).into() 79 | } 80 | 81 | /// View into the payload 82 | pub fn payload(&self) -> &[u8] { 83 | unsafe { &self.as_slice().rf(PAYLOAD) } 84 | } 85 | 86 | /* Miscellaneous */ 87 | /// Returns the byte representation of this frame 88 | pub fn as_bytes(&self) -> &[u8] { 89 | self.as_slice() 90 | } 91 | 92 | /// Frees the underlying buffer 93 | pub fn free(self) -> B { 94 | self.buffer 95 | } 96 | 97 | /// Returns the length (header + data) of this frame 98 | pub fn len(&self) -> u16 { 99 | u16(self.as_bytes().len()).unwrap() 100 | } 101 | 102 | /* Private */ 103 | fn as_slice(&self) -> &[u8] { 104 | self.buffer.as_slice() 105 | } 106 | 107 | fn header_(&self) -> &[u8; HEADER_SIZE as usize] { 108 | debug_assert!(self.as_slice().len() >= HEADER_SIZE as usize); 109 | 110 | unsafe { &*(self.as_slice().as_ptr() as *const _) } 111 | } 112 | } 113 | 114 | impl Frame 115 | where 116 | B: AsSlice + AsMutSlice, 117 | { 118 | /* Setters */ 119 | /// Sets the destination field of the header 120 | pub fn set_destination(&mut self, addr: mac::Addr) { 121 | self.header_mut_()[DESTINATION].copy_from_slice(&addr.0) 122 | } 123 | 124 | /// Sets the source field of the header 125 | pub fn set_source(&mut self, addr: mac::Addr) { 126 | self.header_mut_()[SOURCE].copy_from_slice(&addr.0) 127 | } 128 | 129 | /// Sets the type field of the header 130 | pub fn set_type(&mut self, type_: Type) { 131 | NE::write_u16(&mut self.header_mut_()[TYPE], type_.into()) 132 | } 133 | 134 | /* Miscellaneous */ 135 | /// Mutable view into the payload 136 | pub fn payload_mut(&mut self) -> &mut [u8] { 137 | &mut self.as_mut_slice()[PAYLOAD] 138 | } 139 | 140 | /* Private */ 141 | fn as_mut_slice(&mut self) -> &mut [u8] { 142 | self.buffer.as_mut_slice() 143 | } 144 | 145 | fn header_mut_(&mut self) -> &mut [u8; HEADER_SIZE as usize] { 146 | debug_assert!(self.as_slice().len() >= HEADER_SIZE as usize); 147 | 148 | unsafe { &mut *(self.as_mut_slice().as_mut_ptr() as *mut _) } 149 | } 150 | } 151 | 152 | impl Frame 153 | where 154 | B: AsSlice + IntoSliceFrom, 155 | { 156 | /// Returns the payload of this frame 157 | pub fn into_payload(self) -> B::SliceFrom { 158 | self.buffer.into_slice_from(HEADER_SIZE) 159 | } 160 | } 161 | 162 | impl Frame 163 | where 164 | B: AsSlice + AsMutSlice + Truncate, 165 | { 166 | /// Fills the payload with an ARP packet 167 | /// 168 | /// This method sets the Type field of this frame to ARP, and truncates the length of the frame 169 | /// to fit the ARP packet. 170 | /// 171 | /// The ARP packet will have its SHA set to the Ethernet frame Source address 172 | pub fn arp(&mut self, f: F) 173 | where 174 | F: FnOnce(&mut arp::Packet<&mut [u8]>), 175 | { 176 | self.set_type(Type::Arp); 177 | let sha = self.get_source(); 178 | let len = { 179 | let mut arp = arp::Packet::new(self.payload_mut()); 180 | arp.set_sha(sha); 181 | f(&mut arp); 182 | arp.len() 183 | }; 184 | self.buffer.truncate(HEADER_SIZE + len); 185 | } 186 | } 187 | 188 | impl Frame 189 | where 190 | B: AsSlice + AsMutSlice + Truncate, 191 | { 192 | /// Fills the payload with an IPv4 packet 193 | /// 194 | /// This method sets the Type field of this frame to IPv4, recomputes and updates the header 195 | /// checksum of the IPv4 payload, and truncates the length of the frame to fit the IPv4 packet. 196 | pub fn ipv4(&mut self, f: F) 197 | where 198 | F: FnOnce(&mut ipv4::Packet<&mut [u8], Invalid>), 199 | { 200 | self.set_type(Type::Ipv4); 201 | let len = { 202 | let mut ip = ipv4::Packet::new(self.payload_mut()); 203 | f(&mut ip); 204 | ip.update_checksum().get_total_length() 205 | }; 206 | self.buffer.truncate(u16(HEADER_SIZE) + len); 207 | } 208 | 209 | /// Fills the payload with an IPv6 packet 210 | pub fn ipv6(&mut self, f: F) 211 | where 212 | F: FnOnce(&mut ipv6::Packet<&mut [u8]>), 213 | { 214 | self.set_type(Type::Ipv6); 215 | let len = { 216 | let mut ip = ipv6::Packet::new(self.payload_mut()); 217 | f(&mut ip); 218 | ip.get_length() + u16(ipv6::HEADER_SIZE) 219 | }; 220 | self.buffer.truncate(u16(HEADER_SIZE) + len); 221 | } 222 | } 223 | 224 | /// NOTE excludes the payload 225 | impl fmt::Debug for Frame 226 | where 227 | B: AsSlice, 228 | { 229 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 230 | f.debug_struct("ether::Frame") 231 | .field("destination", &self.get_destination()) 232 | .field("source", &self.get_source()) 233 | .field("type", &self.get_type()) 234 | // .field("payload", &self.payload()) 235 | .finish() 236 | } 237 | } 238 | 239 | full_range!( 240 | u16, 241 | /// Ether Type 242 | #[derive(Clone, Copy, Debug, PartialEq)] 243 | pub enum Type { 244 | /// IPv4 245 | Ipv4 = 0x0800, 246 | 247 | /// ARP 248 | Arp = 0x0806, 249 | 250 | /// IPv6 251 | Ipv6 = 0x86DD, 252 | } 253 | ); 254 | 255 | #[cfg(test)] 256 | mod tests { 257 | use crate::ether; 258 | 259 | #[test] 260 | fn new() { 261 | const SZ: u16 = 128; 262 | 263 | let mut chunk = [0; SZ as usize]; 264 | let buf = &mut chunk; 265 | 266 | let eth = ether::Frame::new(buf); 267 | assert_eq!(eth.len(), SZ); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/fmt.rs: -------------------------------------------------------------------------------- 1 | //! Formatting helpers 2 | 3 | use core::fmt; 4 | 5 | pub struct Display(pub T); 6 | 7 | impl fmt::Debug for Display 8 | where 9 | T: fmt::Display, 10 | { 11 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 12 | write!(f, "{}", self.0) 13 | } 14 | } 15 | 16 | pub struct Hex(pub u16); 17 | 18 | impl fmt::Debug for Hex { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | write!(f, "0x{:04x}", self.0) 21 | } 22 | } 23 | 24 | pub struct Quoted(pub T); 25 | 26 | impl fmt::Debug for Quoted 27 | where 28 | T: fmt::Display, 29 | { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | write!(f, "\"{}\"", self.0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/icmp.rs: -------------------------------------------------------------------------------- 1 | //! ICMP: Internet Control Message Protocol 2 | //! 3 | //! # References 4 | //! 5 | //! - [RFC 792: Internet Control Message Protocol][rfc] 6 | //! 7 | //! [rfc]: https://tools.ietf.org/html/rfc792 8 | 9 | use core::fmt; 10 | use core::marker::PhantomData; 11 | use core::ops::{Range, RangeFrom}; 12 | 13 | use as_slice::{AsMutSlice, AsSlice}; 14 | use byteorder::{ByteOrder, NetworkEndian as NE}; 15 | use cast::usize; 16 | 17 | use crate::{ 18 | fmt::Hex, 19 | ipv4, 20 | sealed::Echo, 21 | traits::{TryFrom, TryInto, UncheckedIndex}, 22 | Invalid, Unknown, Valid, 23 | }; 24 | 25 | /* Message structure */ 26 | const TYPE: usize = 0; 27 | const CODE: usize = 1; 28 | const CHECKSUM: Range = 2..4; 29 | const IDENT: Range = 4..6; 30 | const SEQ_NO: Range = 6..8; 31 | const PAYLOAD: RangeFrom = 8..; 32 | 33 | /// Size of the ICMP header 34 | pub const HEADER_SIZE: u8 = PAYLOAD.start as u8; 35 | 36 | /// ICMP Message 37 | pub struct Message 38 | where 39 | BUFFER: AsSlice, 40 | TYPE: 'static, 41 | { 42 | buffer: BUFFER, 43 | _type: PhantomData, 44 | _checksum: PhantomData, 45 | } 46 | 47 | /// [Type State] The Echo Reply type 48 | pub enum EchoReply {} 49 | 50 | /// [Type State] The Echo Request type 51 | pub enum EchoRequest {} 52 | 53 | /* EchoRequest */ 54 | impl Message 55 | where 56 | B: AsSlice + AsMutSlice, 57 | { 58 | /* Constructors */ 59 | /// Transforms the input buffer into a Echo Request ICMP packet 60 | pub fn new(buffer: B) -> Self { 61 | assert!(buffer.as_slice().len() >= usize(HEADER_SIZE)); 62 | 63 | let mut packet: Message = unsafe { Message::unchecked(buffer) }; 64 | 65 | packet.set_type(Type::EchoRequest); 66 | packet.set_code(0); 67 | 68 | unsafe { Message::unchecked(packet.buffer) } 69 | } 70 | } 71 | 72 | /* EchoReply OR EchoRequest */ 73 | impl Message 74 | where 75 | B: AsSlice, 76 | E: Echo, 77 | { 78 | /* Getters */ 79 | /// Returns the Identifier field of the header 80 | pub fn get_identifier(&self) -> u16 { 81 | NE::read_u16(&self.header_()[IDENT]) 82 | } 83 | 84 | /// Returns the Identifier field of the header 85 | pub fn get_sequence_number(&self) -> u16 { 86 | NE::read_u16(&self.header_()[SEQ_NO]) 87 | } 88 | } 89 | 90 | impl Message 91 | where 92 | B: AsSlice + AsMutSlice, 93 | E: Echo, 94 | { 95 | /* Setters */ 96 | /// Returns the Identifier field of the header 97 | pub fn set_identifier(&mut self, ident: u16) { 98 | NE::write_u16(&mut self.header_mut_()[IDENT], ident) 99 | } 100 | 101 | /// Returns the Identifier field of the header 102 | pub fn set_sequence_number(&mut self, seq_no: u16) { 103 | NE::write_u16(&mut self.header_mut_()[SEQ_NO], seq_no) 104 | } 105 | } 106 | 107 | /* Unknown */ 108 | impl Message 109 | where 110 | B: AsSlice, 111 | { 112 | /* Constructors */ 113 | /// Parses the input bytes into a 114 | pub fn parse(bytes: B) -> Result { 115 | if bytes.as_slice().len() < usize(HEADER_SIZE) { 116 | return Err(bytes); 117 | } 118 | 119 | let packet: Self = unsafe { Message::unchecked(bytes) }; 120 | 121 | if ipv4::verify_checksum(packet.as_bytes()) { 122 | Ok(packet) 123 | } else { 124 | Err(packet.buffer) 125 | } 126 | } 127 | } 128 | 129 | impl Message 130 | where 131 | B: AsSlice + AsMutSlice, 132 | { 133 | /* Setters */ 134 | /// Sets the Type field of the header 135 | pub fn set_type(&mut self, type_: Type) { 136 | self.header_mut_()[TYPE] = type_.into(); 137 | } 138 | 139 | /// Sets the Code field of the header 140 | pub fn set_code(&mut self, code: u8) { 141 | self.header_mut_()[CODE] = code; 142 | } 143 | } 144 | 145 | impl Message 146 | where 147 | B: AsSlice + AsMutSlice, 148 | { 149 | /* Setters */ 150 | /// Sets the Type field of the header 151 | pub fn set_type(self, type_: Type) -> Message { 152 | let mut packet = self.invalidate_header_checksum(); 153 | packet.set_type(type_); 154 | packet 155 | } 156 | 157 | /// Sets the Code field of the header 158 | pub fn set_code(self, code: u8) -> Message { 159 | let mut packet = self.invalidate_header_checksum(); 160 | packet.set_code(code); 161 | packet 162 | } 163 | } 164 | 165 | impl Message 166 | where 167 | B: AsSlice, 168 | { 169 | /// Downcasts this packet with unknown type into a specific type 170 | pub fn downcast(self) -> Result, Self> 171 | where 172 | Self: TryInto, Error = Self>, 173 | { 174 | self.try_into() 175 | } 176 | } 177 | 178 | impl From> for Message 179 | where 180 | B: AsSlice + AsMutSlice, 181 | { 182 | fn from(p: Message) -> Self { 183 | let mut p: Message = unsafe { Message::unchecked(p.buffer) }; 184 | p.set_type(Type::EchoReply); 185 | let p: Message = unsafe { Message::unchecked(p.buffer) }; 186 | p.update_checksum() 187 | } 188 | } 189 | 190 | impl TryFrom> for Message 191 | where 192 | B: AsSlice, 193 | { 194 | type Error = Message; 195 | 196 | fn try_from(p: Message) -> Result> { 197 | if p.get_type() == Type::EchoReply && p.get_code() == 0 { 198 | Ok(unsafe { Message::unchecked(p.buffer) }) 199 | } else { 200 | Err(p) 201 | } 202 | } 203 | } 204 | 205 | impl TryFrom> for Message 206 | where 207 | B: AsSlice, 208 | { 209 | type Error = Message; 210 | 211 | fn try_from(p: Message) -> Result> { 212 | if p.get_type() == Type::EchoRequest && p.get_code() == 0 { 213 | Ok(unsafe { Message::unchecked(p.buffer) }) 214 | } else { 215 | Err(p) 216 | } 217 | } 218 | } 219 | 220 | /* TYPE */ 221 | impl Message 222 | where 223 | B: AsSlice, 224 | { 225 | /* Constructors */ 226 | unsafe fn unchecked(buffer: B) -> Self { 227 | Message { 228 | buffer, 229 | _checksum: PhantomData, 230 | _type: PhantomData, 231 | } 232 | } 233 | 234 | /* Getters */ 235 | /// Returns the Type field of the header 236 | pub fn get_type(&self) -> Type { 237 | if typeid!(T == EchoReply) { 238 | Type::EchoReply 239 | } else if typeid!(T == EchoRequest) { 240 | Type::EchoRequest 241 | } else { 242 | self.header_()[TYPE].into() 243 | } 244 | } 245 | 246 | /// Returns the Type field of the header 247 | pub fn get_code(&self) -> u8 { 248 | if typeid!(T == EchoReply) { 249 | 0 250 | } else if typeid!(T == EchoRequest) { 251 | 0 252 | } else { 253 | self.header_()[CODE] 254 | } 255 | } 256 | 257 | /// View into the payload 258 | pub fn payload(&self) -> &[u8] { 259 | unsafe { &self.as_slice().rf(PAYLOAD) } 260 | } 261 | 262 | /// Returns the length (header + data) of this packet 263 | pub fn len(&self) -> u16 { 264 | self.as_slice().len() as u16 265 | } 266 | 267 | /// Returns the byte representation of this packet 268 | pub fn as_bytes(&self) -> &[u8] { 269 | self.as_slice() 270 | } 271 | 272 | /* Private */ 273 | fn as_slice(&self) -> &[u8] { 274 | self.buffer.as_slice() 275 | } 276 | 277 | fn header_(&self) -> &[u8; HEADER_SIZE as usize] { 278 | debug_assert!(self.as_slice().len() >= HEADER_SIZE as usize); 279 | 280 | unsafe { &*(self.as_slice().as_ptr() as *const _) } 281 | } 282 | 283 | fn get_checksum(&self) -> u16 { 284 | NE::read_u16(&self.header_()[CHECKSUM]) 285 | } 286 | } 287 | 288 | impl Message 289 | where 290 | B: AsSlice + AsMutSlice, 291 | { 292 | /* Private */ 293 | fn as_mut_slice(&mut self) -> &mut [u8] { 294 | self.buffer.as_mut_slice() 295 | } 296 | 297 | fn header_mut_(&mut self) -> &mut [u8; HEADER_SIZE as usize] { 298 | debug_assert!(self.as_slice().len() >= HEADER_SIZE as usize); 299 | 300 | unsafe { &mut *(self.as_mut_slice().as_mut_ptr() as *mut _) } 301 | } 302 | } 303 | 304 | impl Message 305 | where 306 | B: AsSlice + AsMutSlice, 307 | { 308 | /// Mutable view into the payload 309 | pub fn payload_mut(&mut self) -> &mut [u8] { 310 | unsafe { self.as_mut_slice().rfm(PAYLOAD) } 311 | } 312 | 313 | /// Updates the Checksum field of the header 314 | pub fn update_checksum(mut self) -> Message { 315 | let cksum = ipv4::compute_checksum(&self.as_bytes(), CHECKSUM.start); 316 | NE::write_u16(&mut self.header_mut_()[CHECKSUM], cksum); 317 | 318 | unsafe { Message::unchecked(self.buffer) } 319 | } 320 | } 321 | 322 | impl Message 323 | where 324 | B: AsSlice, 325 | { 326 | fn invalidate_header_checksum(self) -> Message { 327 | unsafe { Message::unchecked(self.buffer) } 328 | } 329 | } 330 | 331 | impl Clone for Message 332 | where 333 | B: AsSlice + Clone, 334 | { 335 | fn clone(&self) -> Self { 336 | Message { 337 | buffer: self.buffer.clone(), 338 | _type: PhantomData, 339 | _checksum: PhantomData, 340 | } 341 | } 342 | } 343 | 344 | /// NOTE excludes the payload 345 | impl fmt::Debug for Message 346 | where 347 | B: AsSlice, 348 | E: Echo, 349 | { 350 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 351 | f.debug_struct("icmp::Message") 352 | .field("type", &self.get_type()) 353 | .field("code", &self.get_code()) 354 | .field("checksum", &Hex(self.get_checksum())) 355 | .field("id", &self.get_identifier()) 356 | .field("seq_no", &self.get_sequence_number()) 357 | // .field("payload", &self.payload()) 358 | .finish() 359 | } 360 | } 361 | 362 | impl fmt::Debug for Message 363 | where 364 | B: AsSlice, 365 | { 366 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 367 | f.debug_struct("icmp::Message") 368 | .field("type", &self.get_type()) 369 | .field("code", &self.get_code()) 370 | .field("checksum", &Hex(self.get_checksum())) 371 | // .field("payload", &self.payload()) 372 | .finish() 373 | } 374 | } 375 | 376 | full_range!( 377 | u8, 378 | /// ICMP types 379 | #[derive(Clone, Copy, Debug, PartialEq)] 380 | pub enum Type { 381 | /// Echo Reply 382 | EchoReply = 0, 383 | /// Destination Unreachable 384 | DestinationUnreachable = 3, 385 | /// Echo Request 386 | EchoRequest = 8, 387 | } 388 | ); 389 | 390 | #[cfg(test)] 391 | mod tests { 392 | use rand::{self, RngCore}; 393 | 394 | use crate::{ether, icmp, ipv4, mac}; 395 | 396 | const SIZE: usize = 42; 397 | 398 | const BYTES: [u8; SIZE] = [ 399 | 255, 255, 255, 255, 255, 255, // eth: destination 400 | 1, 1, 1, 1, 1, 1, // eth: source 401 | 8, 0, // eth: type 402 | 69, //ipv4: version & ihl 403 | 0, // ipv4: DSCP & ECN 404 | 0, 28, // ipv4: total length 405 | 0, 0, // ipv4: identification 406 | 64, 0, // ipv4: fragments 407 | 64, // ipv4: TTL 408 | 1, // ipv4: protocol 409 | 185, 110, // ipv4: checksum 410 | 192, 168, 0, 33, // ipv4: source 411 | 192, 168, 0, 1, // ipv4: destination 412 | 8, // icmp: type 413 | 0, // icmp: code 414 | 247, 249, // icmp: checksum 415 | 0, 4, // icmp: identifier 416 | 0, 2, // icmp: sequence number 417 | ]; 418 | 419 | const MAC_SRC: mac::Addr = mac::Addr([0x01; 6]); 420 | const MAC_DST: mac::Addr = mac::Addr([0xff; 6]); 421 | 422 | const IP_SRC: ipv4::Addr = ipv4::Addr([192, 168, 0, 33]); 423 | const IP_DST: ipv4::Addr = ipv4::Addr([192, 168, 0, 1]); 424 | 425 | #[test] 426 | fn construct() { 427 | // NOTE start with randomized array to make sure we set *everything* correctly 428 | let mut array: [u8; SIZE] = [0; SIZE]; 429 | rand::thread_rng().fill_bytes(&mut array); 430 | 431 | let mut eth = ether::Frame::new(&mut array[..]); 432 | 433 | eth.set_destination(MAC_DST); 434 | eth.set_source(MAC_SRC); 435 | 436 | eth.ipv4(|ip| { 437 | ip.set_destination(IP_DST); 438 | ip.set_source(IP_SRC); 439 | 440 | ip.echo_request(|icmp| { 441 | icmp.set_identifier(4); 442 | icmp.set_sequence_number(2); 443 | }); 444 | }); 445 | 446 | assert_eq!(eth.as_bytes(), &BYTES[..]); 447 | } 448 | 449 | #[test] 450 | fn parse() { 451 | let eth = ether::Frame::parse(&BYTES[..]).unwrap(); 452 | assert_eq!(eth.get_source(), MAC_SRC); 453 | assert_eq!(eth.get_destination(), MAC_DST); 454 | 455 | let ip = ipv4::Packet::parse(eth.payload()).unwrap(); 456 | assert_eq!(ip.get_destination(), IP_DST); 457 | assert_eq!(ip.get_source(), IP_SRC); 458 | 459 | let icmp = icmp::Message::parse(ip.payload()) 460 | .unwrap() 461 | .downcast::() 462 | .unwrap(); 463 | 464 | assert_eq!(icmp.get_identifier(), 4); 465 | assert_eq!(icmp.get_sequence_number(), 2); 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/ipv6.rs: -------------------------------------------------------------------------------- 1 | //! IPv6: Internet Protocol v6 2 | //! 3 | //! # References 4 | //! 5 | //! - [RFC 4291 IP Version 6 Addressing Architecture][rfc] 6 | //! 7 | //! [rfc]: https://tools.ietf.org/html/rfc4291 8 | 9 | use core::{ 10 | fmt, 11 | ops::{Range, RangeFrom, RangeTo}, 12 | u16, 13 | }; 14 | 15 | use as_slice::{AsMutSlice, AsSlice}; 16 | use byteorder::{ByteOrder, NetworkEndian as NE}; 17 | use cast::{u16, u32, usize}; 18 | use hash32_derive::Hash32; 19 | use owning_slice::Truncate; 20 | 21 | pub use crate::ipv4::Protocol as NextHeader; 22 | use crate::{fmt::Quoted, icmpv6, mac, traits::UncheckedIndex, udp}; 23 | 24 | /* Packet structure */ 25 | const V: usize = 0; 26 | mod v { 27 | pub const MASK: u8 = (1 << SIZE) - 1; 28 | pub const OFFSET: usize = 4; 29 | pub const SIZE: usize = 4; 30 | } 31 | 32 | const TC: RangeTo = ..2; 33 | mod tc { 34 | pub const MASK: u16 = (1 << SIZE) - 1; 35 | pub const OFFSET: usize = 4; 36 | pub const SIZE: usize = 8; 37 | } 38 | 39 | const FLH: usize = 1; 40 | const FLL: Range = 2..4; 41 | 42 | const LENGTH: Range = 4..6; 43 | const NEXT_HEADER: usize = 6; 44 | const HOP_LIMIT: usize = 7; 45 | const SOURCE: Range = 8..24; 46 | const DESTINATION: Range = 24..40; 47 | const PAYLOAD: RangeFrom = 40..; 48 | 49 | /// Fixed header size, in bytes 50 | pub const HEADER_SIZE: u8 = DESTINATION.end as u8; 51 | 52 | /// IPv6 packet 53 | pub struct Packet 54 | where 55 | BUFFER: AsSlice, 56 | { 57 | buffer: BUFFER, 58 | } 59 | 60 | impl Packet 61 | where 62 | B: AsSlice, 63 | { 64 | /* Constructors */ 65 | /// Parses bytes into an IPv6 packet 66 | pub fn parse(bytes: B) -> Result { 67 | if bytes.as_slice().len() < usize(HEADER_SIZE) { 68 | // smaller than header 69 | return Err(()); 70 | } 71 | 72 | let p = Packet { buffer: bytes }; 73 | 74 | if get!((p.header()[V]), v) != 6 { 75 | // version is not `6` 76 | return Err(()); 77 | } 78 | 79 | if p.get_next_header().is_ipv6_extension_header() { 80 | // currently unsupported 81 | return Err(()); 82 | } 83 | 84 | Ok(p) 85 | } 86 | 87 | /* Accessors */ 88 | /// Reads the 'Version' field 89 | /// 90 | /// This always returns `6` 91 | pub fn get_version(&self) -> u8 { 92 | debug_assert_eq!(get!(&self.header()[V], v), 6); 93 | 94 | 6 95 | } 96 | 97 | /// Reads the 'Traffic Class' field 98 | pub fn get_traffic_class(&self) -> u8 { 99 | get!(NE::read_u16(&self.header()[TC]), tc) as u8 100 | } 101 | 102 | /// Reads the 'Flow Label' field (20 bits) 103 | pub fn get_flow_label(&self) -> u32 { 104 | let mask = (1 << 4) - 1; 105 | 106 | (u32(self.header()[FLH]) & mask) << 16 | u32(NE::read_u16(&self.header()[FLL])) 107 | } 108 | 109 | /// Reads the 'Payload length' field 110 | pub fn get_length(&self) -> u16 { 111 | NE::read_u16(&self.header()[LENGTH]) 112 | } 113 | 114 | /// Reads the 'Next Header' field 115 | pub fn get_next_header(&self) -> NextHeader { 116 | self.header()[NEXT_HEADER].into() 117 | } 118 | 119 | /// Reads the 'Hop Limit' field 120 | pub fn get_hop_limit(&self) -> u8 { 121 | self.header()[HOP_LIMIT] 122 | } 123 | 124 | /// Reads the 'Source Address' field 125 | pub fn get_source(&self) -> Addr { 126 | unsafe { Addr(*(self.as_slice().as_ptr().add(SOURCE.start) as *const _)) } 127 | } 128 | 129 | /// Reads the 'Destination Address' field 130 | pub fn get_destination(&self) -> Addr { 131 | unsafe { Addr(*(self.as_slice().as_ptr().add(DESTINATION.start) as *const _)) } 132 | } 133 | 134 | /// Immutable view into the payload 135 | pub fn payload(&self) -> &[u8] { 136 | // NOTE we reject packets that contain extension headers in `parse` 137 | unsafe { self.as_slice().rf(PAYLOAD) } 138 | } 139 | 140 | /// Returns the byte representation of this packet 141 | pub fn as_bytes(&self) -> &[u8] { 142 | self.as_slice() 143 | } 144 | 145 | /* Private */ 146 | fn header(&self) -> &[u8; HEADER_SIZE as usize] { 147 | debug_assert!(self.as_slice().len() >= usize(HEADER_SIZE)); 148 | 149 | unsafe { &*(self.as_slice().as_ptr() as *const _) } 150 | } 151 | 152 | fn as_slice(&self) -> &[u8] { 153 | self.buffer.as_slice() 154 | } 155 | } 156 | 157 | impl Packet 158 | where 159 | B: AsMutSlice, 160 | { 161 | /* Constructors */ 162 | /// Transforms the given buffer into an IPv4 packet 163 | /// 164 | /// Most of the header will be filled with sensible defaults: 165 | /// 166 | /// - Version = 6 167 | /// - Traffic class = 0 168 | /// - Flow label = 0 169 | /// - Length = buffer.len() - HEADER_SIZE 170 | /// - Hop limit = 255 171 | /// 172 | /// The fields that are left unpopulated are: 173 | /// 174 | /// - Next header 175 | /// - Source address 176 | /// - Destination address 177 | /// 178 | /// # Panics 179 | /// 180 | /// This constructor panics if 181 | /// 182 | /// - the given `buffer` is smaller than `HEADER_SIZE` 183 | /// - the packet would result in a payload length larger than `u16::MAX`. 184 | pub fn new(buffer: B) -> Self { 185 | let blen = buffer.as_slice().len(); 186 | assert!(blen >= usize(HEADER_SIZE) || blen > usize(u16::MAX) + usize(HEADER_SIZE)); 187 | 188 | let mut p = Packet { buffer }; 189 | 190 | p.set_version(); 191 | p.set_traffic_class(0); 192 | p.set_flow_label(0); 193 | // NOTE(cast) see `assert` above 194 | unsafe { p.set_length((blen - usize(HEADER_SIZE)) as u16) } 195 | // p.set_next_header(..); 196 | p.set_hop_limit(255); 197 | // p.set_source(..); 198 | // p.set_destination(..); 199 | 200 | p 201 | } 202 | 203 | /// Sets the 'Traffic class' field 204 | pub fn set_traffic_class(&mut self, tc: u8) { 205 | let mask = (1 << 4) - 1; 206 | 207 | // low byte 208 | let tcl = &mut self.header_mut()[1]; 209 | *tcl &= !(mask << 4); 210 | *tcl |= (tc & mask) << 4; 211 | 212 | // high byte 213 | let tch = &mut self.header_mut()[0]; 214 | *tch &= !mask; 215 | *tch |= tc >> 4; 216 | } 217 | 218 | /// Sets the 'Flow label' field 219 | pub fn set_flow_label(&mut self, fl: u32) { 220 | // low half-word 221 | NE::write_u16(&mut self.header_mut()[2..4], fl as u16); 222 | 223 | // high byte 224 | let mask = (1 << 4) - 1; 225 | let flh = &mut self.header_mut()[1]; 226 | *flh &= !mask; 227 | *flh |= (fl >> 16) as u8; 228 | } 229 | 230 | /// Sets the 'Next Header' field 231 | /// 232 | /// # Panics 233 | /// 234 | /// This function panics if `nh` is an extension header (currently not supported) 235 | pub fn set_next_header(&mut self, nh: NextHeader) { 236 | assert!(!nh.is_ipv6_extension_header()); 237 | 238 | self.header_mut()[NEXT_HEADER] = nh.into(); 239 | } 240 | 241 | /// Sets the 'Hop limit' field 242 | pub fn set_hop_limit(&mut self, hl: u8) { 243 | self.header_mut()[HOP_LIMIT] = hl; 244 | } 245 | 246 | /// Sets the 'Source address' field 247 | pub fn set_source(&mut self, addr: Addr) { 248 | self.header_mut()[SOURCE].copy_from_slice(&addr.0) 249 | } 250 | 251 | /// Sets the 'Destination address' field 252 | pub fn set_destination(&mut self, addr: Addr) { 253 | self.header_mut()[DESTINATION].copy_from_slice(&addr.0) 254 | } 255 | 256 | /// Immutable view into the payload 257 | pub fn payload_mut(&mut self) -> &mut [u8] { 258 | // NOTE we reject packets that contain extension headers in `parse` 259 | unsafe { self.as_mut_slice().rfm(PAYLOAD) } 260 | } 261 | 262 | /* Private */ 263 | fn header_mut(&mut self) -> &mut [u8; HEADER_SIZE as usize] { 264 | debug_assert!(self.as_slice().len() >= usize(HEADER_SIZE)); 265 | 266 | unsafe { &mut *(self.as_mut_slice().as_mut_ptr() as *mut _) } 267 | } 268 | 269 | fn as_mut_slice(&mut self) -> &mut [u8] { 270 | self.buffer.as_mut_slice() 271 | } 272 | 273 | fn set_version(&mut self) { 274 | set!(self.header_mut()[V], v, 6); 275 | } 276 | 277 | // NOTE(unsafe) this does *not* truncate the buffer or check if `len` is greater than the 278 | // length of the current buffer 279 | unsafe fn set_length(&mut self, len: u16) { 280 | NE::write_u16(&mut self.header_mut()[LENGTH], len); 281 | } 282 | } 283 | 284 | impl Packet 285 | where 286 | B: AsMutSlice + Truncate, 287 | { 288 | /// Fills the payload with a Neighbor Advertisement ICMPv6 message 289 | pub fn neighbor_advertisement( 290 | &mut self, 291 | target_ll_addr: Option, 292 | f: impl FnOnce(&mut icmpv6::Message<&mut [u8], icmpv6::NeighborAdvertisement>), 293 | ) { 294 | let src = self.get_source(); 295 | let dest = self.get_destination(); 296 | 297 | self.set_next_header(NextHeader::Ipv6Icmp); 298 | 299 | let mut message = icmpv6::Message::neighbor_advertisement( 300 | self.payload_mut(), 301 | if target_ll_addr.is_some() { 1 } else { 0 }, 302 | ); 303 | 304 | f(&mut message); 305 | 306 | if let Some(target_ll_addr) = target_ll_addr { 307 | unsafe { 308 | message.set_target_mac_addr(target_ll_addr); 309 | } 310 | } 311 | 312 | message.update_checksum(src, dest); 313 | 314 | let len = message.as_bytes().len() as u16; 315 | self.truncate(len); 316 | } 317 | 318 | /// Fills the payload with a UDP packet 319 | pub fn udp(&mut self, f: impl FnOnce(&mut udp::Packet<&mut [u8]>)) { 320 | let src = self.get_source(); 321 | let dest = self.get_destination(); 322 | 323 | self.set_next_header(NextHeader::Udp); 324 | 325 | let mut packet = udp::Packet::new(self.payload_mut()); 326 | 327 | f(&mut packet); 328 | 329 | packet.update_ipv6_checksum(src, dest); 330 | 331 | let len = packet.as_bytes().len() as u16; 332 | self.truncate(len); 333 | } 334 | 335 | /// Truncates the *payload* to the specified length 336 | pub fn truncate(&mut self, len: u16) { 337 | if self.get_length() > len { 338 | unsafe { self.set_length(len) } 339 | self.buffer.truncate(len + u16(HEADER_SIZE)); 340 | } 341 | } 342 | } 343 | 344 | impl fmt::Debug for Packet 345 | where 346 | B: AsSlice, 347 | { 348 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 349 | f.debug_struct("ipv6::Packet") 350 | .field("version", &self.get_version()) 351 | .field("traffic_class", &self.get_traffic_class()) 352 | .field("flow_label", &self.get_flow_label()) 353 | .field("length", &self.get_length()) 354 | .field("next_header", &self.get_next_header()) 355 | .field("hop_limit", &self.get_hop_limit()) 356 | .field("source", &Quoted(self.get_source())) 357 | .field("destination", &Quoted(self.get_destination())) 358 | // .field("payload", &self.payload()) 359 | .finish() 360 | } 361 | } 362 | 363 | /// IPv6 address 364 | #[derive(Clone, Copy, Debug, Eq, Hash32, PartialEq)] 365 | pub struct Addr(pub [u8; 16]); 366 | 367 | impl Addr { 368 | // Section 2.5.2 369 | /// Unspecified address 370 | pub const UNSPECIFIED: Self = Addr([0; 16]); 371 | 372 | // Section 2.5.3 373 | /// Loopback address 374 | pub const LOOPBACK: Self = Addr([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); 375 | 376 | /// All link-local nodes multicast address 377 | pub const ALL_NODES: Self = Addr([0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); 378 | 379 | /// All link-local routers multicast address 380 | pub const ALL_ROUTERS: Self = Addr([0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); 381 | 382 | // Section 2.5.6 383 | /// Is this a link local address? 384 | pub fn is_link_local(&self) -> bool { 385 | self.0[..8] == [0xfe, 0x80, 0, 0, 0, 0, 0, 0] 386 | } 387 | 388 | /// Is this the loopback address? 389 | pub fn is_loopback(&self) -> bool { 390 | *self == Self::LOOPBACK 391 | } 392 | 393 | // Section 2.7 394 | /// Is this a multicast address? 395 | pub fn is_multicast(&self) -> bool { 396 | self.0[0] == 0xff 397 | } 398 | 399 | /// Is this a solicited node multicast address? 400 | pub fn is_solicited_node(&self) -> bool { 401 | self.0[..13] == [0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0xff] 402 | } 403 | 404 | /// Is this the unspecified address? 405 | pub fn is_unspecified(&self) -> bool { 406 | *self == Self::UNSPECIFIED 407 | } 408 | 409 | /// Turns this unicast or anycast address into a solicited node multicast address 410 | /// 411 | /// # Panics 412 | /// 413 | /// This function panics if `self` is a multicast address 414 | pub fn into_solicited_node(mut self) -> Self { 415 | assert!(!self.is_multicast()); 416 | 417 | self.0[..13].copy_from_slice(&[0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0xff]); 418 | self 419 | } 420 | } 421 | 422 | impl fmt::Display for Addr { 423 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 424 | let mut is_first = true; 425 | 426 | for chunk in self.0.chunks(2) { 427 | if is_first { 428 | is_first = false; 429 | } else { 430 | f.write_str(":")?; 431 | } 432 | 433 | write!(f, "{:x}", NE::read_u16(chunk))?; 434 | } 435 | 436 | Ok(()) 437 | } 438 | } 439 | 440 | #[cfg(test)] 441 | mod tests { 442 | use crate::ipv6; 443 | 444 | use super::HEADER_SIZE; 445 | 446 | #[test] 447 | fn solicited_node() { 448 | let unicast = ipv6::Addr([ 449 | 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0xec, 0x0b, 0xfb, 0x0f, 0x76, 0xb9, 0xf3, 0x93, 450 | ]); 451 | 452 | assert_eq!( 453 | unicast.into_solicited_node(), 454 | ipv6::Addr([0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0xff, 0xb9, 0xf3, 0x93]) 455 | ); 456 | } 457 | 458 | #[test] 459 | fn new() { 460 | const SZ: usize = 128; 461 | 462 | let mut chunk = [0; SZ]; 463 | 464 | let unspecified = ipv6::Addr::UNSPECIFIED; 465 | let next_header = ipv6::NextHeader::Udp; 466 | 467 | let mut ip = ipv6::Packet::new(&mut chunk[..]); 468 | ip.set_next_header(next_header); 469 | ip.set_destination(unspecified); 470 | ip.set_source(unspecified); 471 | 472 | assert_eq!(ip.get_version(), 6); 473 | assert_eq!(ip.get_traffic_class(), 0); 474 | assert_eq!(ip.get_flow_label(), 0); 475 | assert_eq!( 476 | usize::from(ip.get_length()), 477 | (SZ - usize::from(HEADER_SIZE)) 478 | ); 479 | assert_eq!(ip.get_next_header(), next_header); 480 | assert_eq!(ip.get_hop_limit(), 255); 481 | assert_eq!(ip.get_source(), unspecified); 482 | assert_eq!(ip.get_destination(), unspecified); 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! JNeT: japaric's network thingies 2 | //! 3 | //! There's no IO stuff in this crate. If you are looking for sockets and the like check out the 4 | //! [`smoltcp`] crate. 5 | //! 6 | //! [`smoltcp`]: https://crates.io/crates/smoltcp 7 | //! 8 | //! This crate mainly contains an API to work with frames and packets in `no_std` context. 9 | //! 10 | //! It doesn't provide you any real *parser* though; it simply provides an API to mutate or access 11 | //! the header and the payload of a frame / packet *in place*. This approach is the same as the one 12 | //! used in `smoltcp`. The difference is that the API of this crate uses type state to (a) avoid 13 | //! foot guns like sending an IPv4 packet with an invalid header checksum, and to (b) provide 14 | //! specialized APIs when the packet type, e.g. ICMP EchoRequest, is known. 15 | //! 16 | //! If type state is not your cup of tea check out `smoltcp`'s [wire module]; do note that, as of 17 | //! v0.4.0, `smoltcp` doesn't provide an API to work with CoAP messages whereas this crate does. 18 | //! 19 | //! [wire module]: https://docs.rs/smoltcp/0.4.0/smoltcp/wire/index.html 20 | //! 21 | //! # Examples 22 | //! 23 | //! - Parsing an ARP packet 24 | //! 25 | //! ``` 26 | //! use jnet::{arp, ether}; 27 | //! 28 | //! let bytes = &[ 29 | //! 255, 255, 255, 255, 255, 255, // eth: destination 30 | //! 120, 68, 118, 217, 106, 124, // eth: source 31 | //! 8, 6, // eth: type 32 | //! 0, 1, // arp: HTYPE = Ethernet 33 | //! 8, 0, // arp: PTYPE = IPv4 34 | //! 6, // arp: HLEN 35 | //! 4, // arp: PLEN 36 | //! 0, 2, // arp: OPER = Reply 37 | //! 120, 68, 118, 217, 106, 124, // arp: SHA 38 | //! 192, 168, 1, 1, // arp: SPA 39 | //! 32, 24, 3, 1, 0, 0, // arp: THA 40 | //! 192, 168, 1, 33, // arp: TPA 41 | //! 0, 0, 0, 0, // eth: padding 42 | //! ]; 43 | //! 44 | //! let eth = ether::Frame::parse(&bytes[..]).unwrap(); 45 | //! let arp = arp::Packet::parse(eth.payload()).unwrap(); 46 | //! 47 | //! assert_eq!(arp.get_htype(), arp::HardwareType::Ethernet); 48 | //! assert_eq!(arp.get_ptype(), ether::Type::Ipv4); 49 | //! assert_eq!(arp.get_oper(), arp::Operation::Reply); 50 | //! ``` 51 | //! 52 | //! - Constructing a CoAP message 53 | //! 54 | //! The general principle to building frames / packets / messages is to start with an (slightly) 55 | //! oversized buffer and then proceed to shrink it to the right length. 56 | //! 57 | //! ``` 58 | //! use jnet::{coap, ether, ipv4, mac, udp}; 59 | //! 60 | //! const MAC_SRC: mac::Addr = mac::Addr([0x20, 0x18, 0x03, 0x01, 0x00, 0x00]); 61 | //! const MAC_DST: mac::Addr = mac::Addr([0x20, 0x18, 0x03, 0x13, 0x00, 0x00]); 62 | //! 63 | //! const IP_SRC: ipv4::Addr = ipv4::Addr([192, 168, 1, 11]); 64 | //! const IP_DST: ipv4::Addr = ipv4::Addr([192, 168, 1, 33]); 65 | //! 66 | //! let mut bytes = [0; 60]; 67 | //! let mut buf = &mut bytes[..]; 68 | //! 69 | //! // clean slate Ethernet frame with a total length of 60 bytes 70 | //! let mut eth = ether::Frame::new(buf); 71 | //! eth.set_destination(MAC_DST); 72 | //! eth.set_source(MAC_SRC); 73 | //! 74 | //! eth.ipv4(|ip| { 75 | //! ip.set_source(IP_SRC); 76 | //! ip.set_destination(IP_DST); 77 | //! ip.udp(|udp| { 78 | //! udp.set_destination(coap::PORT); 79 | //! udp.coap(0, |mut coap| { 80 | //! coap.set_type(coap::Type::Confirmable); 81 | //! coap.set_code(coap::Method::Put); 82 | //! coap.add_option(coap::OptionNumber::UriPath, b"led"); 83 | //! coap.set_payload(b"on") 84 | //! }) 85 | //! }); 86 | //! }); 87 | //! 88 | //! // At this point the Ethernet frame has shrunk to the size of its contents 89 | //! // The excess memory is inaccessible 90 | //! assert_eq!(eth.len(), 53); 91 | //! ``` 92 | 93 | #![deny(missing_docs)] 94 | #![deny(rust_2018_compatibility)] 95 | #![deny(rust_2018_idioms)] 96 | #![deny(warnings)] 97 | #![no_std] 98 | 99 | #[cfg(test)] 100 | #[macro_use] 101 | extern crate pretty_assertions; 102 | 103 | #[macro_use] 104 | mod macros; 105 | 106 | mod fmt; 107 | mod sealed; 108 | mod traits; 109 | 110 | // Medium Access Control layer 111 | pub mod ether; 112 | pub mod ieee802154; 113 | pub mod mac; 114 | 115 | pub mod arp; 116 | 117 | // Network layer 118 | pub mod ipv4; 119 | pub mod ipv6; 120 | pub mod sixlowpan; 121 | 122 | pub mod icmp; 123 | pub mod icmpv6; 124 | 125 | // Transport layer 126 | pub mod udp; 127 | 128 | // Application layer 129 | pub mod coap; 130 | 131 | /// [Type State] Unknown 132 | pub enum Unknown {} 133 | 134 | /// [Type State] Valid checksum 135 | pub enum Valid {} 136 | 137 | /// [Type State] Invalid checksum 138 | pub enum Invalid {} 139 | -------------------------------------------------------------------------------- /src/mac.rs: -------------------------------------------------------------------------------- 1 | //! MAC: Medium Access Control 2 | 3 | use core::fmt; 4 | 5 | use hash32_derive::Hash32; 6 | 7 | use crate::ipv6; 8 | 9 | /// MAC address 10 | #[derive(Clone, Copy, Eq, Hash32, PartialEq)] 11 | pub struct Addr(pub [u8; 6]); 12 | 13 | impl Addr { 14 | /// Broadcast address 15 | pub const BROADCAST: Self = Addr([0xff; 6]); 16 | 17 | /// Is this a unicast address? 18 | pub fn is_unicast(&self) -> bool { 19 | !self.is_broadcast() && !self.is_multicast() 20 | } 21 | 22 | /// Is this the broadcast address? 23 | pub fn is_broadcast(&self) -> bool { 24 | *self == Self::BROADCAST 25 | } 26 | 27 | /// Is this a multicast address? 28 | /// 29 | /// NOTE `Addr::BROADCAST.is_multicast()` returns `false` 30 | pub fn is_multicast(&self) -> bool { 31 | !self.is_broadcast() && self.0[0] & 1 == 1 32 | } 33 | 34 | /// Is this an IPv4 multicast address? 35 | pub fn is_ipv4_multicast(&self) -> bool { 36 | self.0[0] == 0x01 && self.0[1] == 0x00 && self.0[2] == 0x5e && self.0[3] >> 7 == 0 37 | } 38 | 39 | /// Is this an IPv6 multicast address? 40 | pub fn is_ipv6_multicast(&self) -> bool { 41 | self.0[0] == 0x33 && self.0[1] == 0x33 42 | } 43 | 44 | /// Converts this MAC address into a link-local IPv6 address using the EUI-64 format (see 45 | /// RFC2464) 46 | pub fn into_link_local_address(self) -> ipv6::Addr { 47 | let mut bytes = [0; 16]; 48 | 49 | bytes[0] = 0xfe; 50 | bytes[1] = 0x80; 51 | 52 | bytes[8..].copy_from_slice(&self.eui_64()); 53 | 54 | ipv6::Addr(bytes) 55 | } 56 | 57 | fn eui_64(self) -> [u8; 8] { 58 | let mut bytes = [0; 8]; 59 | 60 | bytes[..3].copy_from_slice(&self.0[..3]); 61 | // toggle the Universal/Local (U/L) bit 62 | bytes[0] ^= 1 << 1; 63 | 64 | bytes[3] = 0xff; 65 | bytes[4] = 0xfe; 66 | 67 | bytes[5..].copy_from_slice(&self.0[3..]); 68 | 69 | bytes 70 | } 71 | } 72 | 73 | impl fmt::Debug for Addr { 74 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 75 | struct Hex<'a>(&'a [u8; 6]); 76 | 77 | impl<'a> fmt::Debug for Hex<'a> { 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | use core::fmt::Write; 80 | 81 | let mut is_first = true; 82 | 83 | f.write_char('[')?; 84 | for byte in self.0.iter() { 85 | if is_first { 86 | is_first = false; 87 | } else { 88 | f.write_str(", ")?; 89 | } 90 | 91 | write!(f, "0x{:02x}", byte)?; 92 | } 93 | f.write_char(']')?; 94 | 95 | Ok(()) 96 | } 97 | } 98 | 99 | f.debug_tuple("mac::Addr").field(&Hex(&self.0)).finish() 100 | } 101 | } 102 | 103 | impl fmt::Display for Addr { 104 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 105 | let mut is_first = true; 106 | for byte in &self.0 { 107 | if is_first { 108 | is_first = false; 109 | } else { 110 | f.write_str(":")?; 111 | } 112 | 113 | write!(f, "{:02x}", byte)?; 114 | } 115 | Ok(()) 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use super::Addr; 122 | 123 | #[test] 124 | fn eui_64() { 125 | assert_eq!( 126 | Addr([0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE]).eui_64(), 127 | [0x36, 0x56, 0x78, 0xFF, 0xFE, 0x9A, 0xBC, 0xDE] 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// CoAP code 2 | macro_rules! code { 3 | ( 4 | $(#[$enum_attr:meta])* 5 | pub enum $Enum:ident { 6 | $( 7 | #[$variant_attr:meta] 8 | $Variant:ident = ($class:expr, $detail:expr), 9 | )+ 10 | } 11 | ) => { 12 | $(#[$enum_attr])* 13 | pub enum $Enum { 14 | $( 15 | #[$variant_attr] 16 | $Variant, 17 | )+ 18 | } 19 | 20 | impl From<$Enum> for Code { 21 | fn from(e: $Enum) -> Code { 22 | match e { 23 | $( 24 | $Enum::$Variant => Code::from_parts($class, $detail), 25 | )+ 26 | } 27 | } 28 | } 29 | 30 | impl crate::traits::TryFrom for $Enum { 31 | type Error = (); 32 | 33 | fn try_from(c: Code) -> Result<$Enum, ()> { 34 | Ok(match (c.class(), c.detail()) { 35 | $( 36 | ($class, $detail) => $Enum::$Variant, 37 | )+ 38 | _ => return Err(()) 39 | }) 40 | } 41 | } 42 | } 43 | } 44 | 45 | /// Enum whose variants cover the full range of the integer type `$uxx` 46 | macro_rules! full_range { 47 | // Public 48 | ($uxx:ty, 49 | $(#[$enum_attr:meta])* 50 | pub enum $Enum:ident { 51 | $( 52 | #[$variant_attr:meta] 53 | $Variant:ident = $value:expr, 54 | )+ 55 | } 56 | ) => { 57 | $(#[$enum_attr])* 58 | pub enum $Enum { 59 | $( 60 | #[$variant_attr] 61 | $Variant, 62 | )+ 63 | /// Unknown 64 | Unknown($uxx), 65 | } 66 | 67 | impl From<$uxx> for $Enum { 68 | fn from(n: $uxx) -> $Enum { 69 | match n { 70 | $( 71 | $value => $Enum::$Variant, 72 | )+ 73 | _ => $Enum::Unknown(n), 74 | } 75 | } 76 | } 77 | 78 | impl From<$Enum> for $uxx { 79 | fn from(e: $Enum) -> $uxx { 80 | match e { 81 | $( 82 | $Enum::$Variant => $value, 83 | )+ 84 | $Enum::Unknown(n) => n, 85 | } 86 | } 87 | } 88 | }; 89 | 90 | // Private 91 | ($uxx:ty, 92 | $(#[$enum_attr:meta])* 93 | enum $Enum:ident { 94 | $( 95 | $Variant:ident = $value:expr, 96 | )+ 97 | } 98 | ) => { 99 | $(#[$enum_attr])* 100 | enum $Enum { 101 | $( 102 | $Variant, 103 | )+ 104 | /// Unknown 105 | Unknown($uxx), 106 | } 107 | 108 | impl From<$uxx> for $Enum { 109 | fn from(n: $uxx) -> $Enum { 110 | match n { 111 | $( 112 | $value => $Enum::$Variant, 113 | )+ 114 | _ => $Enum::Unknown(n), 115 | } 116 | } 117 | } 118 | 119 | impl From<$Enum> for $uxx { 120 | fn from(e: $Enum) -> $uxx { 121 | match e { 122 | $( 123 | $Enum::$Variant => $value, 124 | )+ 125 | $Enum::Unknown(n) => n, 126 | } 127 | } 128 | } 129 | }; 130 | } 131 | 132 | macro_rules! debug_unreachable { 133 | () => { 134 | if cfg!(debug_assertions) { 135 | unreachable!() 136 | } else { 137 | core::hint::unreachable_unchecked() 138 | } 139 | }; 140 | } 141 | 142 | /// Reads the bitfield of a byte / word 143 | macro_rules! get { 144 | ($byte:expr, $field:ident) => { 145 | ($byte >> self::$field::OFFSET) & self::$field::MASK 146 | }; 147 | } 148 | 149 | /// Writes to the bitfield of a byte / word 150 | macro_rules! set { 151 | ($byte:expr, $field:ident, $value:expr) => {{ 152 | let byte = &mut $byte; 153 | 154 | *byte &= !(self::$field::MASK << self::$field::OFFSET); 155 | *byte |= ($value & self::$field::MASK) << self::$field::OFFSET; 156 | }}; 157 | } 158 | 159 | /// Poor man's specialization 160 | macro_rules! typeid { 161 | ($type_parameter:ident == $concrete_type:ident) => { 162 | ::core::any::TypeId::of::<$type_parameter>() == ::core::any::TypeId::of::<$concrete_type>() 163 | }; 164 | } 165 | -------------------------------------------------------------------------------- /src/sealed.rs: -------------------------------------------------------------------------------- 1 | use crate::icmp::{EchoReply, EchoRequest}; 2 | 3 | // [Type State] EchoReply or EchoRequest 4 | pub trait Echo: 'static {} 5 | 6 | impl Echo for EchoReply {} 7 | impl Echo for EchoRequest {} 8 | -------------------------------------------------------------------------------- /src/sixlowpan.rs: -------------------------------------------------------------------------------- 1 | //! 6LoWPAN: IPv6 over Low-Power Wireless Personal Area Networks 2 | //! 3 | //! # References 4 | //! 5 | //! - [RFC 4944: Transmission of IPv6 Packets over IEEE 802.15.4 Networks][0] 6 | //! 7 | //! [0]: https://tools.ietf.org/html/rfc4944 8 | //! 9 | //! - [RFC 6282: Compression Format for IPv6 Datagrams over IEEE 802.15.4-Based Networks][1] 10 | //! 11 | //! [1]: https://tools.ietf.org/html/rfc6282 12 | 13 | pub mod iphc; 14 | pub mod nhc; 15 | -------------------------------------------------------------------------------- /src/sixlowpan/nhc.rs: -------------------------------------------------------------------------------- 1 | //! LOWPAN_NHC encoding 2 | 3 | use core::fmt; 4 | 5 | use as_slice::{AsMutSlice, AsSlice}; 6 | use byteorder::{ByteOrder, NetworkEndian as NE}; 7 | use owning_slice::Truncate; 8 | 9 | use crate::{ 10 | coap::{self, Unset}, 11 | ipv6, 12 | traits::UncheckedIndex, 13 | }; 14 | 15 | /* Header format */ 16 | const NHC: usize = 0; 17 | 18 | mod id { 19 | pub const MASK: u8 = (1 << SIZE) - 1; 20 | pub const OFFSET: usize = super::c::OFFSET + super::c::SIZE; 21 | pub const SIZE: usize = 5; 22 | pub const VALUE: u8 = 0b11110; 23 | } 24 | 25 | mod c { 26 | pub const MASK: u8 = (1 << SIZE) - 1; 27 | pub const OFFSET: usize = super::p::OFFSET + super::p::SIZE; 28 | pub const SIZE: usize = 1; 29 | } 30 | 31 | mod p { 32 | pub const MASK: u8 = (1 << SIZE) - 1; 33 | pub const OFFSET: usize = 0; 34 | pub const SIZE: usize = 2; 35 | } 36 | 37 | /// LOWPAN_NHC compressed UDP packet 38 | pub struct UdpPacket 39 | where 40 | BUFFER: AsSlice, 41 | { 42 | buffer: BUFFER, 43 | /// Index at which the payload starts 44 | payload: u8, 45 | } 46 | 47 | impl UdpPacket 48 | where 49 | B: AsSlice, 50 | { 51 | /* Constructors */ 52 | /// Parses the bytes as a LOWPAN_IPHC compressed IPv6 packet 53 | pub fn parse(buffer: B) -> Result { 54 | let mut start = 1u8; // NHC 55 | 56 | if buffer.as_slice().len() < usize::from(start) { 57 | return Err(buffer); 58 | } 59 | 60 | let mut p = UdpPacket { buffer, payload: 0 }; 61 | 62 | // check NHC ID 63 | if get!(p.header_(), id) != id::VALUE { 64 | return Err(p.buffer); 65 | } 66 | 67 | if !p.get_c() { 68 | start += 2; // checksum 69 | } 70 | 71 | start += p.ports_size(); 72 | 73 | p.payload = start; 74 | 75 | if p.buffer.as_slice().len() < usize::from(start) { 76 | Err(p.buffer) 77 | } else { 78 | Ok(p) 79 | } 80 | } 81 | 82 | /* Getters */ 83 | /// Reads the (potentially compressed) 'Source Port' field 84 | pub fn get_source(&self) -> u16 { 85 | unsafe { 86 | match self.get_p() { 87 | 0b00 | 0b01 => NE::read_u16(self.as_slice().r(1..3)), 88 | 0b10 => 0xf000 + u16::from(*self.as_slice().gu(1)), 89 | 0b11 => 0xf0b0 + u16::from(*self.as_slice().gu(1) >> 4), 90 | _ => unreachable!(), 91 | } 92 | } 93 | } 94 | 95 | /// Reads the (potentially compressed) 'Destination Port' field 96 | pub fn get_destination(&self) -> u16 { 97 | unsafe { 98 | match self.get_p() { 99 | 0b00 => NE::read_u16(self.as_slice().r(3..5)), 100 | 0b01 => 0xf000 + u16::from(*self.as_slice().gu(3)), 101 | 0b10 => NE::read_u16(self.as_slice().r(2..4)), 102 | 0b11 => 0xf0b0 + u16::from(*self.as_slice().gu(1) & 0x0f), 103 | _ => unreachable!(), 104 | } 105 | } 106 | } 107 | 108 | /// Reads the (potentially compressed) 'Checksum' field 109 | /// 110 | /// `None` means that the checksum has been elided by the compressor 111 | pub fn get_checksum(&self) -> Option { 112 | if !self.get_c() { 113 | let start = usize::from(1 + self.ports_size()); 114 | Some(NE::read_u16(unsafe { self.as_slice().r(start..start + 2) })) 115 | } else { 116 | None 117 | } 118 | } 119 | 120 | /// Immutable view into the UDP payload 121 | pub fn payload(&self) -> &[u8] { 122 | let start = usize::from(self.payload); 123 | unsafe { self.as_slice().rf(start..) } 124 | } 125 | 126 | /// Byte representation of this packet 127 | pub fn bytes(&self) -> &[u8] { 128 | self.as_slice() 129 | } 130 | 131 | /// Reads the 'Checksum' NHC field 132 | pub fn get_c(&self) -> bool { 133 | get!(self.header_(), c) != 0 134 | } 135 | 136 | /// Reads the 'Ports' NHC field 137 | pub fn get_p(&self) -> u8 { 138 | get!(self.header_(), p) 139 | } 140 | 141 | /// Verifies the 'Checksum' field 142 | pub fn verify_ipv6_checksum(&self, src: ipv6::Addr, dest: ipv6::Addr) -> bool { 143 | if let Some(cksum) = self.get_checksum() { 144 | self.compute_checksum(src, dest) == cksum 145 | } else { 146 | true 147 | } 148 | } 149 | 150 | /* Private */ 151 | fn ports_size(&self) -> u8 { 152 | match self.get_p() { 153 | // source & destination uncompressed 154 | 0b00 => 2 + 2, 155 | // destination compressed 156 | 0b01 => 2 /* source */ + 1, /* destination */ 157 | // source compressed 158 | 0b10 => 1 /* source */ + 2, /* destination */ 159 | // source and destination compressed 160 | 0b11 => 1, 161 | _ => unreachable!(), 162 | } 163 | } 164 | 165 | fn compute_checksum(&self, src: ipv6::Addr, dest: ipv6::Addr) -> u16 { 166 | const NEXT_HEADER: u8 = 17; 167 | 168 | let mut sum: u32 = 0; 169 | 170 | /* Pseudo-header */ 171 | for chunk in src.0.chunks_exact(2).chain(dest.0.chunks_exact(2)) { 172 | sum += u32::from(NE::read_u16(chunk)); 173 | } 174 | 175 | // length in pseudo-header 176 | // XXX should this be just `as u16`? 177 | let udp_len = self.payload().len() as u32 + 8; 178 | sum += udp_len >> 16; 179 | sum += udp_len & 0xffff; 180 | 181 | sum += u32::from(NEXT_HEADER); 182 | 183 | /* UDP packet */ 184 | sum += u32::from(self.get_source()); 185 | sum += u32::from(self.get_destination()); 186 | 187 | // length in UDP header (yes, again) 188 | sum += udp_len >> 16; 189 | sum += udp_len & 0xffff; 190 | 191 | for chunk in self.payload().chunks(2) { 192 | if chunk.len() == 2 { 193 | sum += u32::from(NE::read_u16(chunk)); 194 | } else { 195 | sum += u32::from(chunk[0]) << 8; 196 | } 197 | } 198 | 199 | // fold carry-over 200 | while sum >> 16 != 0 { 201 | sum = (sum & 0xffff) + (sum >> 16); 202 | } 203 | 204 | !(sum as u16) 205 | } 206 | 207 | fn header_(&self) -> u8 { 208 | unsafe { *self.as_slice().gu(NHC) } 209 | } 210 | 211 | fn as_slice(&self) -> &[u8] { 212 | self.buffer.as_slice() 213 | } 214 | } 215 | 216 | impl UdpPacket 217 | where 218 | B: AsMutSlice, 219 | { 220 | /* Constructors */ 221 | pub(crate) fn new(buffer: B, elide_checksum: bool, source: u16, destination: u16) -> Self { 222 | // NHC ID 223 | let mut len = 1; 224 | 225 | // ports 226 | let p = if source >> 4 == 0xf0b && destination >> 4 == 0xf0b { 227 | len += 1; 228 | 229 | 0b11 230 | } else if source >> 8 == 0xf0 { 231 | len += 3; 232 | 233 | 0b10 234 | } else if destination >> 8 == 0xf0 { 235 | len += 3; 236 | 237 | 0b01 238 | } else { 239 | len += 4; 240 | 241 | 0b00 242 | }; 243 | 244 | // checksum 245 | let mut payload = len as u8; 246 | let c = if !elide_checksum { 247 | len += 2; 248 | payload += 2; 249 | 0 250 | } else { 251 | 1 252 | }; 253 | 254 | if buffer.as_slice().len() < len { 255 | panic!(); // buffer too small 256 | } 257 | 258 | unsafe { 259 | let mut up = UdpPacket { buffer, payload }; 260 | 261 | // c = 0, p = 0 262 | *up.as_mut_slice().gum(0) = 263 | (id::VALUE << id::OFFSET) | (c << c::OFFSET) | (p << p::OFFSET); 264 | 265 | match p { 266 | 0b00 => { 267 | NE::write_u16(&mut up.as_mut_slice().rm(1..3), source); 268 | NE::write_u16(&mut up.as_mut_slice().rm(3..5), destination); 269 | } 270 | 271 | 0b01 => { 272 | NE::write_u16(&mut up.as_mut_slice().rm(1..3), source); 273 | *up.as_mut_slice().gum(3) = (destination & 0xff) as u8; 274 | } 275 | 276 | 0b10 => { 277 | *up.as_mut_slice().gum(1) = (source & 0xff) as u8; 278 | NE::write_u16(&mut up.as_mut_slice().rm(2..4), destination); 279 | } 280 | 281 | 0b11 => { 282 | *up.as_mut_slice().gum(1) = 283 | (((source & 0x0f) as u8) << 4) + (destination & 0x0f) as u8; 284 | } 285 | 286 | _ => {} // unreachable 287 | } 288 | 289 | up 290 | } 291 | } 292 | 293 | /// Mutable view into the payload 294 | pub fn payload_mut(&mut self) -> &mut [u8] { 295 | let start = usize::from(self.payload); 296 | unsafe { self.as_mut_slice().rfm(start..) } 297 | } 298 | 299 | /// Updates the checksum field, if not elided 300 | pub fn update_checksum(&mut self, src: ipv6::Addr, dest: ipv6::Addr) { 301 | if !self.get_c() { 302 | let cksum = self.compute_checksum(src, dest); 303 | unsafe { self.set_checksum(cksum) } 304 | } 305 | } 306 | 307 | /* Private */ 308 | unsafe fn set_checksum(&mut self, cksum: u16) { 309 | debug_assert!(!self.get_c()); 310 | 311 | let start = 1 + usize::from(self.ports_size()); 312 | NE::write_u16(self.as_mut_slice().rm(start..start + 2), cksum); 313 | } 314 | 315 | fn as_mut_slice(&mut self) -> &mut [u8] { 316 | self.buffer.as_mut_slice() 317 | } 318 | } 319 | 320 | impl UdpPacket 321 | where 322 | B: AsMutSlice + Truncate, 323 | { 324 | /// Fills the payload with the given data and adjusts the length of the CoAP message 325 | pub fn set_payload(&mut self, payload: &[u8]) { 326 | assert!(self.payload().len() >= payload.len()); 327 | 328 | let plen = payload.len(); 329 | 330 | self.payload_mut()[..plen].copy_from_slice(payload); 331 | self.buffer.truncate(self.payload + plen as u8); 332 | } 333 | 334 | /// Fills the payload with a CoAP message 335 | pub fn coap(&mut self, token_length: u8, f: F) 336 | where 337 | F: FnOnce(coap::Message<&mut [u8], Unset>) -> coap::Message<&mut [u8]>, 338 | { 339 | let plen = { 340 | let m = coap::Message::new(self.payload_mut(), token_length); 341 | f(m).len() 342 | }; 343 | 344 | self.buffer.truncate(self.payload + plen as u8); 345 | } 346 | } 347 | 348 | impl fmt::Debug for UdpPacket 349 | where 350 | B: AsSlice, 351 | { 352 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 353 | f.debug_struct("nhc::UdpPacket") 354 | .field("source", &self.get_source()) 355 | .field("destination", &self.get_destination()) 356 | .field("checksum", &self.get_checksum()) 357 | // .field("payload", &self.payload()) 358 | .finish() 359 | } 360 | } 361 | 362 | #[cfg(test)] 363 | mod tests { 364 | use rand::RngCore; 365 | 366 | use super::UdpPacket; 367 | 368 | #[test] 369 | fn new() { 370 | macro_rules! test { 371 | ($elide_checksum:expr, $source:expr, $destination:expr) => {{ 372 | let no_cksum = $elide_checksum; 373 | let s = $source; 374 | let d = $destination; 375 | 376 | let mut bytes = [0; 128]; 377 | rand::thread_rng().fill_bytes(&mut bytes); 378 | 379 | let mut packet = UdpPacket::new(&mut bytes[..], no_cksum, s, d); 380 | packet.set_payload(&[]); 381 | 382 | if no_cksum { 383 | assert_eq!(packet.get_checksum(), None); 384 | } 385 | assert_eq!(packet.get_source(), s); 386 | assert_eq!(packet.get_destination(), d); 387 | assert_eq!(packet.payload(), &[]); 388 | }}; 389 | } 390 | 391 | for elide in &[true, false] { 392 | // source and destination, both compressed 393 | test!(*elide, 0xf0b1, 0xf0b2); 394 | 395 | // source compressed 396 | test!(*elide, 0xf001, 1337); 397 | 398 | // destination compressed 399 | test!(*elide, 1337, 0xf001); 400 | 401 | // uncompressed ports 402 | test!(*elide, 1337, 1337); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Range, RangeFrom, RangeTo}; 2 | #[cfg(not(debug_assertions))] 3 | use core::slice; 4 | 5 | /// IMPLEMENTATION DETAIL 6 | pub trait UncheckedIndex { 7 | type T; 8 | 9 | // get_unchecked 10 | unsafe fn gu(&self, i: usize) -> &Self::T; 11 | // get_unchecked_mut 12 | unsafe fn gum(&mut self, i: usize) -> &mut Self::T; 13 | unsafe fn r(&self, r: Range) -> &Self; 14 | unsafe fn rm(&mut self, r: Range) -> &mut Self; 15 | unsafe fn rt(&self, r: RangeTo) -> &Self; 16 | unsafe fn rtm(&mut self, r: RangeTo) -> &mut Self; 17 | unsafe fn rf(&self, r: RangeFrom) -> &Self; 18 | unsafe fn rfm(&mut self, r: RangeFrom) -> &mut Self; 19 | } 20 | 21 | impl UncheckedIndex for [T] { 22 | type T = T; 23 | 24 | unsafe fn gu(&self, at: usize) -> &T { 25 | debug_assert!(at < self.len()); 26 | 27 | self.get_unchecked(at) 28 | } 29 | 30 | unsafe fn gum(&mut self, at: usize) -> &mut T { 31 | debug_assert!(at < self.len()); 32 | 33 | self.get_unchecked_mut(at) 34 | } 35 | 36 | #[cfg(debug_assertions)] 37 | unsafe fn r(&self, r: Range) -> &[T] { 38 | &self[r] 39 | } 40 | 41 | #[cfg(not(debug_assertions))] 42 | unsafe fn r(&self, r: Range) -> &[T] { 43 | let o = r.start; 44 | let l = r.end - o; 45 | slice::from_raw_parts(self.as_ptr().add(o), l) 46 | } 47 | 48 | #[cfg(debug_assertions)] 49 | unsafe fn rm(&mut self, r: Range) -> &mut [T] { 50 | &mut self[r] 51 | } 52 | 53 | #[cfg(not(debug_assertions))] 54 | unsafe fn rm(&mut self, r: Range) -> &mut [T] { 55 | let o = r.start; 56 | let l = r.end - o; 57 | slice::from_raw_parts_mut(self.as_mut_ptr().add(o), l) 58 | } 59 | 60 | #[cfg(debug_assertions)] 61 | unsafe fn rt(&self, r: RangeTo) -> &[T] { 62 | &self[r] 63 | } 64 | 65 | #[cfg(not(debug_assertions))] 66 | unsafe fn rt(&self, r: RangeTo) -> &[T] { 67 | slice::from_raw_parts(self.as_ptr(), r.end) 68 | } 69 | 70 | #[cfg(debug_assertions)] 71 | unsafe fn rtm(&mut self, r: RangeTo) -> &mut [T] { 72 | &mut self[r] 73 | } 74 | 75 | #[cfg(not(debug_assertions))] 76 | unsafe fn rtm(&mut self, r: RangeTo) -> &mut [T] { 77 | slice::from_raw_parts_mut(self.as_mut_ptr(), r.end) 78 | } 79 | 80 | #[cfg(debug_assertions)] 81 | unsafe fn rf(&self, r: RangeFrom) -> &[T] { 82 | &self[r] 83 | } 84 | 85 | #[cfg(not(debug_assertions))] 86 | unsafe fn rf(&self, r: RangeFrom) -> &[T] { 87 | let o = r.start; 88 | let l = self.len() - o; 89 | slice::from_raw_parts(self.as_ptr().add(o), l) 90 | } 91 | 92 | #[cfg(debug_assertions)] 93 | unsafe fn rfm(&mut self, r: RangeFrom) -> &mut [T] { 94 | &mut self[r] 95 | } 96 | 97 | #[cfg(not(debug_assertions))] 98 | unsafe fn rfm(&mut self, r: RangeFrom) -> &mut [T] { 99 | let o = r.start; 100 | let l = self.len() - o; 101 | slice::from_raw_parts_mut(self.as_mut_ptr().add(o), l) 102 | } 103 | } 104 | 105 | pub trait UxxExt { 106 | type Half; 107 | 108 | fn low(self) -> Self::Half; 109 | fn high(self) -> Self::Half; 110 | } 111 | 112 | impl UxxExt for u16 { 113 | type Half = u8; 114 | 115 | fn low(self) -> u8 { 116 | let mask = (1 << 8) - 1; 117 | (self & mask) as u8 118 | } 119 | 120 | fn high(self) -> u8 { 121 | (self >> 8) as u8 122 | } 123 | } 124 | 125 | impl UxxExt for u32 { 126 | type Half = u16; 127 | 128 | fn low(self) -> u16 { 129 | let mask = (1 << 16) - 1; 130 | (self & mask) as u16 131 | } 132 | 133 | fn high(self) -> u16 { 134 | (self >> 16) as u16 135 | } 136 | } 137 | 138 | pub trait TryFrom: Sized { 139 | type Error; 140 | 141 | fn try_from(value: T) -> Result; 142 | } 143 | 144 | pub trait TryInto { 145 | type Error; 146 | fn try_into(self) -> Result; 147 | } 148 | 149 | impl TryInto for T 150 | where 151 | U: TryFrom, 152 | { 153 | type Error = U::Error; 154 | 155 | fn try_into(self) -> Result { 156 | U::try_from(self) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/udp.rs: -------------------------------------------------------------------------------- 1 | //! UDP: User Datagram Protocol 2 | 3 | use core::ops::{Range, RangeFrom}; 4 | use core::{fmt, u16}; 5 | 6 | use as_slice::{AsMutSlice, AsSlice}; 7 | use byteorder::{ByteOrder, NetworkEndian as NE}; 8 | use cast::{u16, usize}; 9 | use owning_slice::Truncate; 10 | 11 | use crate::{ 12 | coap::{self, Unset}, 13 | ipv6, 14 | traits::UncheckedIndex, 15 | }; 16 | 17 | /* Packet structure */ 18 | const SOURCE: Range = 0..2; 19 | const DESTINATION: Range = 2..4; 20 | const LENGTH: Range = 4..6; 21 | const CHECKSUM: Range = 6..8; 22 | const PAYLOAD: RangeFrom = 8..; 23 | 24 | /// Size of the UDP header 25 | pub const HEADER_SIZE: u8 = PAYLOAD.start as u8; 26 | 27 | /// UDP packet 28 | pub struct Packet 29 | where 30 | BUFFER: AsSlice, 31 | { 32 | buffer: BUFFER, 33 | } 34 | 35 | impl Packet 36 | where 37 | B: AsSlice, 38 | { 39 | /* Constructors */ 40 | /// Parses the bytes as an UDP packet 41 | pub fn parse(bytes: B) -> Result { 42 | let nbytes = bytes.as_slice().len(); 43 | if nbytes < usize(HEADER_SIZE) { 44 | return Err(bytes); 45 | } 46 | 47 | let packet = Packet { buffer: bytes }; 48 | let len = packet.get_length(); 49 | 50 | if len < u16(HEADER_SIZE) || usize(len) > nbytes { 51 | Err(packet.buffer) 52 | } else { 53 | Ok(packet) 54 | } 55 | } 56 | 57 | /* Getters */ 58 | /// Returns the Source (port) field of the header 59 | pub fn get_source(&self) -> u16 { 60 | NE::read_u16(&self.header_()[SOURCE]) 61 | } 62 | 63 | /// Returns the Destination (port) field of the header 64 | pub fn get_destination(&self) -> u16 { 65 | NE::read_u16(&self.header_()[DESTINATION]) 66 | } 67 | 68 | /// Returns the Length field of the header 69 | pub fn get_length(&self) -> u16 { 70 | NE::read_u16(&self.header_()[LENGTH]) 71 | } 72 | 73 | fn get_checksum(&self) -> u16 { 74 | NE::read_u16(&self.header_()[CHECKSUM]) 75 | } 76 | 77 | /// Returns the length (header + data) of this packet 78 | pub fn len(&self) -> u16 { 79 | self.get_length() 80 | } 81 | 82 | /* Miscellaneous */ 83 | /// View into the payload 84 | pub fn payload(&self) -> &[u8] { 85 | unsafe { self.as_slice().rf(PAYLOAD) } 86 | } 87 | 88 | /// Returns the byte representation of this UDP packet 89 | pub fn as_bytes(&self) -> &[u8] { 90 | self.as_slice() 91 | } 92 | 93 | /* Miscellaneous */ 94 | pub(crate) fn compute_checksum(&self, src: ipv6::Addr, dest: ipv6::Addr) -> u16 { 95 | const NEXT_HEADER: u8 = 17; 96 | 97 | let mut sum: u32 = 0; 98 | 99 | // Pseudo-header 100 | for chunk in src.0.chunks_exact(2).chain(dest.0.chunks_exact(2)) { 101 | sum += u32::from(NE::read_u16(chunk)); 102 | } 103 | 104 | // XXX should this be just `as u16`? 105 | let len = self.as_slice().len() as u32; 106 | sum += len >> 16; 107 | sum += len & 0xffff; 108 | 109 | sum += u32::from(NEXT_HEADER); 110 | 111 | // UDP message 112 | for (i, chunk) in self.as_slice().chunks(2).enumerate() { 113 | if i == 3 { 114 | // this is the checksum field, skip 115 | continue; 116 | } 117 | 118 | if chunk.len() == 1 { 119 | sum += u32::from(chunk[0]) << 8; 120 | } else { 121 | sum += u32::from(NE::read_u16(chunk)); 122 | } 123 | } 124 | 125 | // fold carry-over 126 | while sum >> 16 != 0 { 127 | sum = (sum & 0xffff) + (sum >> 16); 128 | } 129 | 130 | !(sum as u16) 131 | } 132 | 133 | /// Verifies the 'Checksum' field 134 | pub fn verify_ipv6_checksum(&self, src: ipv6::Addr, dest: ipv6::Addr) -> bool { 135 | self.compute_checksum(src, dest) == self.get_checksum() 136 | } 137 | 138 | /* Private */ 139 | fn as_slice(&self) -> &[u8] { 140 | self.buffer.as_slice() 141 | } 142 | 143 | fn header_(&self) -> &[u8; HEADER_SIZE as usize] { 144 | debug_assert!(self.as_slice().len() >= HEADER_SIZE as usize); 145 | 146 | unsafe { &*(self.as_slice().as_ptr() as *const _) } 147 | } 148 | 149 | fn payload_len(&self) -> u16 { 150 | self.get_length() - u16(HEADER_SIZE) 151 | } 152 | } 153 | 154 | impl Packet 155 | where 156 | B: AsSlice + AsMutSlice, 157 | { 158 | /* Setters */ 159 | /// Sets the Source (port) field of the header 160 | pub fn set_source(&mut self, port: u16) { 161 | NE::write_u16(&mut self.header_mut_()[SOURCE], port) 162 | } 163 | 164 | /// Sets the Destination (port) field of the header 165 | pub fn set_destination(&mut self, port: u16) { 166 | NE::write_u16(&mut self.header_mut_()[DESTINATION], port) 167 | } 168 | 169 | unsafe fn set_length(&mut self, len: u16) { 170 | NE::write_u16(&mut self.header_mut_()[LENGTH], len) 171 | } 172 | 173 | /// Zeroes the Checksum field of the header 174 | pub fn zero_checksum(&mut self) { 175 | self.set_checksum(0); 176 | } 177 | 178 | /// Sets the Destination (port) field of the header 179 | fn set_checksum(&mut self, checksum: u16) { 180 | NE::write_u16(&mut self.header_mut_()[CHECKSUM], checksum) 181 | } 182 | 183 | /* Miscellaneous */ 184 | /// Mutable view into the payload 185 | pub fn payload_mut(&mut self) -> &mut [u8] { 186 | &mut self.as_mut_slice()[PAYLOAD] 187 | } 188 | 189 | /// Recomputes and updates the 'Checksum' field 190 | pub fn update_ipv6_checksum(&mut self, src: ipv6::Addr, dest: ipv6::Addr) { 191 | let cksum = self.compute_checksum(src, dest); 192 | self.set_checksum(cksum) 193 | } 194 | 195 | /* Private */ 196 | fn as_mut_slice(&mut self) -> &mut [u8] { 197 | self.buffer.as_mut_slice() 198 | } 199 | 200 | fn header_mut_(&mut self) -> &mut [u8; HEADER_SIZE as usize] { 201 | debug_assert!(self.as_slice().len() >= HEADER_SIZE as usize); 202 | 203 | unsafe { &mut *(self.as_mut_slice().as_mut_ptr() as *mut _) } 204 | } 205 | } 206 | 207 | impl Packet 208 | where 209 | B: AsSlice + AsMutSlice + Truncate, 210 | { 211 | /* Constructors */ 212 | /// Transforms the given buffer into an UDP packet 213 | /// 214 | /// NOTE The UDP packet will span the whole buffer and the Checksum field will be zeroed. 215 | /// 216 | /// # Panics 217 | /// 218 | /// This constructor panics if the given `buffer` is not large enough to contain the UDP header. 219 | pub fn new(mut buffer: B) -> Self { 220 | assert!(buffer.as_slice().len() >= usize(HEADER_SIZE)); 221 | 222 | let len = u16(buffer.as_slice().len()).unwrap_or(u16::MAX); 223 | buffer.truncate(len); 224 | let mut packet = Packet { buffer }; 225 | 226 | packet.set_checksum(0); 227 | unsafe { packet.set_length(len) } 228 | 229 | packet 230 | } 231 | 232 | /* Setters */ 233 | /// Fills the payload with the given data and adjusts the length of the UDP packet 234 | pub fn set_payload(&mut self, data: &[u8]) { 235 | let len = u16(data.len()).unwrap(); 236 | assert!(self.payload_len() >= len); 237 | 238 | self.truncate(len); 239 | self.payload_mut().copy_from_slice(data); 240 | } 241 | 242 | /* Miscellaneous */ 243 | /// Fills the payload with a CoAP message 244 | pub fn coap(&mut self, token_length: u8, f: F) 245 | where 246 | F: FnOnce(coap::Message<&mut [u8], Unset>) -> coap::Message<&mut [u8]>, 247 | { 248 | let len = { 249 | let m = coap::Message::new(self.payload_mut(), token_length); 250 | f(m).len() 251 | }; 252 | self.truncate(len); 253 | } 254 | 255 | /// Truncates the *payload* to the specified length 256 | pub fn truncate(&mut self, len: u16) { 257 | if len < self.payload_len() { 258 | let total_len = len + u16(HEADER_SIZE); 259 | self.buffer.truncate(total_len); 260 | unsafe { self.set_length(total_len) } 261 | } 262 | } 263 | } 264 | 265 | /// NOTE excludes the payload 266 | impl fmt::Debug for Packet 267 | where 268 | B: AsSlice, 269 | { 270 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 271 | f.debug_struct("udp::Packet") 272 | .field("source", &self.get_source()) 273 | .field("destination", &self.get_destination()) 274 | .field("length", &self.get_length()) 275 | .field("checksum", &self.get_checksum()) 276 | // .field("payload", &self.payload()) 277 | .finish() 278 | } 279 | } 280 | 281 | #[cfg(test)] 282 | mod tests { 283 | use cast::u16; 284 | use rand::{self, RngCore}; 285 | 286 | use crate::{ether, ipv4, mac, udp}; 287 | 288 | const SIZE: usize = 56; 289 | 290 | const BYTES: &[u8; SIZE] = &[ 291 | 255, 255, 255, 255, 255, 255, // ether: destination 292 | 1, 1, 1, 1, 1, 1, // ether: source 293 | 8, 0, // ether: type 294 | 69, // ipv4: version & IHL 295 | 0, // ipv4: DSCP & ECN 296 | 0, 42, //ipv4: total length 297 | 0, 0, // ipv4: identification 298 | 64, 0, // ipv4: fragment 299 | 64, //ipv4: ttl 300 | 17, //ipv4: protocol 301 | 185, 80, // ipv4: checksum 302 | 192, 168, 0, 33, // ipv4: source 303 | 192, 168, 0, 1, // ipv4: destination 304 | 0, 0, // udp: source 305 | 5, 57, // udp: destination 306 | 0, 22, // udp: length 307 | 0, 0, // udp: checksum 308 | 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 10, // udp: payload 309 | ]; 310 | 311 | const MAC_SRC: mac::Addr = mac::Addr([0x01; 6]); 312 | const MAC_DST: mac::Addr = mac::Addr([0xff; 6]); 313 | 314 | const IP_SRC: ipv4::Addr = ipv4::Addr([192, 168, 0, 33]); 315 | const IP_DST: ipv4::Addr = ipv4::Addr([192, 168, 0, 1]); 316 | 317 | const UDP_DST: u16 = 1337; 318 | 319 | const MESSAGE: &[u8] = b"Hello, world!\n"; 320 | 321 | #[test] 322 | fn construct() { 323 | // NOTE start with randomized array to make sure we set *everything* correctly 324 | let mut array: [u8; SIZE] = [0; SIZE]; 325 | rand::thread_rng().fill_bytes(&mut array); 326 | 327 | let mut eth = ether::Frame::new(&mut array[..]); 328 | 329 | eth.set_destination(MAC_DST); 330 | eth.set_source(MAC_SRC); 331 | 332 | eth.ipv4(|ip| { 333 | ip.set_destination(IP_DST); 334 | ip.set_source(IP_SRC); 335 | 336 | ip.udp(|udp| { 337 | udp.set_source(0); 338 | udp.set_destination(UDP_DST); 339 | udp.set_payload(MESSAGE); 340 | }); 341 | }); 342 | 343 | assert_eq!(eth.as_bytes(), &BYTES[..]); 344 | } 345 | 346 | #[test] 347 | fn new() { 348 | const SZ: u16 = 128; 349 | 350 | let mut chunk = [0; SZ as usize]; 351 | let buf = &mut chunk[..]; 352 | 353 | let udp = udp::Packet::new(buf); 354 | assert_eq!(udp.len(), SZ); 355 | assert_eq!(udp.get_length(), SZ); 356 | } 357 | 358 | #[test] 359 | fn parse() { 360 | let eth = ether::Frame::parse(&BYTES[..]).unwrap(); 361 | assert_eq!(eth.get_destination(), MAC_DST); 362 | assert_eq!(eth.get_source(), MAC_SRC); 363 | assert_eq!(eth.get_type(), ether::Type::Ipv4); 364 | 365 | let ip = ipv4::Packet::parse(eth.payload()).unwrap(); 366 | assert_eq!(ip.get_source(), IP_SRC); 367 | assert_eq!(ip.get_destination(), IP_DST); 368 | 369 | let udp = udp::Packet::parse(ip.payload()).unwrap(); 370 | assert_eq!(udp.get_source(), 0); 371 | assert_eq!(udp.get_destination(), UDP_DST); 372 | assert_eq!( 373 | udp.get_length(), 374 | MESSAGE.len() as u16 + u16(udp::HEADER_SIZE) 375 | ); 376 | assert_eq!(udp.payload(), MESSAGE); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /tests/roundtrip.rs: -------------------------------------------------------------------------------- 1 | use jnet::{coap, ether, ipv4, mac, udp}; 2 | 3 | #[test] 4 | fn coap4() { 5 | static PAYLOAD: &[u8] = b"Hello"; 6 | 7 | let buffer: &mut [u8] = &mut [0; 256]; 8 | 9 | let mut m = ether::Frame::new(buffer); 10 | m.set_source(mac::Addr::BROADCAST); 11 | m.set_destination(mac::Addr::BROADCAST); 12 | 13 | m.ipv4(|ip| { 14 | ip.set_source(ipv4::Addr::UNSPECIFIED); 15 | ip.set_destination(ipv4::Addr::UNSPECIFIED); 16 | 17 | ip.udp(|udp| { 18 | udp.set_source(coap::PORT); 19 | udp.set_destination(coap::PORT); 20 | 21 | udp.coap(0, |mut coap| { 22 | coap.set_code(coap::Response::Content); 23 | coap.set_payload(PAYLOAD) 24 | }); 25 | }); 26 | }); 27 | 28 | let bytes = m.as_bytes(); 29 | 30 | let eth = ether::Frame::parse(bytes).unwrap(); 31 | assert_eq!(eth.get_source(), mac::Addr::BROADCAST); 32 | assert_eq!(eth.get_destination(), mac::Addr::BROADCAST); 33 | 34 | let ip = ipv4::Packet::parse(eth.payload()).unwrap(); 35 | assert_eq!(ip.get_source(), ipv4::Addr::UNSPECIFIED); 36 | assert_eq!(ip.get_destination(), ipv4::Addr::UNSPECIFIED); 37 | 38 | let udp = udp::Packet::parse(ip.payload()).unwrap(); 39 | assert_eq!(udp.get_source(), coap::PORT); 40 | assert_eq!(udp.get_destination(), coap::PORT); 41 | 42 | let coap = coap::Message::parse(udp.payload()).unwrap(); 43 | assert_eq!(coap.get_code(), coap::Response::Content.into()); 44 | assert_eq!(coap.payload(), PAYLOAD); 45 | } 46 | -------------------------------------------------------------------------------- /tools/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tools" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "2.31.1" 9 | failure = "0.1.1" 10 | jnet = { path = ".." } 11 | url = "1.7.0" 12 | rand = "0.6.5" 13 | libc = "0.2.49" 14 | exitfailure = "0.5.1" 15 | -------------------------------------------------------------------------------- /tools/src/bin/coap.rs: -------------------------------------------------------------------------------- 1 | //! Very simple IPv4 CoAP client 2 | 3 | #![deny(rust_2018_compatibility)] 4 | #![deny(rust_2018_idioms)] 5 | 6 | use std::{ 7 | ffi::CString, 8 | io::{self, Write}, 9 | net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, UdpSocket}, 10 | str, 11 | time::{Duration, Instant}, 12 | }; 13 | 14 | use clap::{App, Arg}; 15 | use exitfailure::ExitFailure; 16 | use failure::{bail, Error, ResultExt}; 17 | use jnet::coap; 18 | use rand::{ 19 | distributions::{Distribution, Uniform}, 20 | Rng, 21 | }; 22 | use url::{Host, Url}; 23 | 24 | /* Transmission parameters */ 25 | const ACK_RANDOM_FACTOR: f64 = 1.5; 26 | const ACK_TIMEOUT: u16 = 2_000; // ms 27 | const DEFAULT_LEISURE: u8 = 5; // s 28 | const MAX_RETRANSMIT: u8 = 4; 29 | 30 | fn main() -> Result<(), ExitFailure> { 31 | run().map_err(|e| e.into()) 32 | } 33 | 34 | fn run() -> Result<(), Error> { 35 | let matches = App::new("coap") 36 | .arg( 37 | Arg::with_name("port") 38 | .help("local UDP port to bind (if omitted a random one will be chosen)") 39 | .required(false) 40 | .short("p") 41 | .takes_value(true) 42 | .value_name("PORT"), 43 | ) 44 | .arg( 45 | Arg::with_name("interface") 46 | .help("IPv6 interface to bind the client to") 47 | .required(false) 48 | .short("I") 49 | .takes_value(true) 50 | .value_name("IFACE"), 51 | ) 52 | .arg( 53 | Arg::with_name("method") 54 | .help("one of DELETE, GET, POST or PUT") 55 | .required(true) 56 | .value_name("METHOD"), 57 | ) 58 | .arg( 59 | Arg::with_name("url") 60 | .help("The scheme must be 'coap'") 61 | .required(true) 62 | .value_name("URL"), 63 | ) 64 | .arg( 65 | Arg::with_name("payload") 66 | .help("The payload of the request") 67 | .value_name("PAYLOAD"), 68 | ) 69 | .get_matches(); 70 | 71 | let method = match matches.value_of("method").unwrap() { 72 | "DELETE" => coap::Method::Delete, 73 | "GET" => coap::Method::Get, 74 | "POST" => coap::Method::Post, 75 | "PUT" => coap::Method::Put, 76 | _ => panic!(), 77 | }; 78 | 79 | let url = Url::parse(matches.value_of("url").unwrap()).context("parsing URL")?; 80 | if url.scheme() != "coap" { 81 | bail!("URL scheme must be 'coap'") 82 | } 83 | 84 | let mut rng = rand::thread_rng(); 85 | 86 | static M: &str = "URL host must be an IP address"; 87 | let port = url.port().unwrap_or(coap::PORT); 88 | let (client, server): (_, SocketAddr) = match url.host() { 89 | Some(Host::Domain(s)) => ( 90 | UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))?, 91 | SocketAddrV4::new(s.parse::().context(M)?, port).into(), 92 | ), 93 | 94 | Some(Host::Ipv6(ip)) => { 95 | let scope_id = if let Some(iface) = matches.value_of("interface") { 96 | let cstr = CString::new(iface)?; 97 | let out = unsafe { libc::if_nametoindex(cstr.as_ptr()) }; 98 | 99 | if out == 0 { 100 | return Err(io::Error::last_os_error().into()); 101 | } else { 102 | out 103 | } 104 | } else { 105 | 0 106 | }; 107 | 108 | ( 109 | // TODO use a port that results in port compression (6LoWPAN) 110 | UdpSocket::bind(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, scope_id))?, 111 | SocketAddrV6::new(ip, port, 0, scope_id).into(), 112 | ) 113 | } 114 | 115 | _ => bail!(M), 116 | }; 117 | 118 | let is_multicast = server.ip().is_multicast(); 119 | 120 | // construct outgoing message 121 | let mut buf = [0; 256]; 122 | let mut mtx = coap::Message::new(&mut buf[..], 0); 123 | // FIXME multicast messages must be Non-Confirmable 124 | mtx.set_type(if is_multicast { 125 | coap::Type::NonConfirmable 126 | } else { 127 | coap::Type::Confirmable 128 | }); 129 | let mid = rng.gen(); 130 | mtx.set_code(method); 131 | mtx.set_message_id(mid); 132 | if let Some(segments) = url.path_segments() { 133 | for segment in segments { 134 | mtx.add_option(coap::OptionNumber::UriPath, segment.as_bytes()); 135 | } 136 | } 137 | let mtx = mtx.set_payload( 138 | matches 139 | .value_of("payload") 140 | .map(|s| s.as_bytes()) 141 | .unwrap_or(&[]), 142 | ); 143 | 144 | let stderr = io::stderr(); 145 | let mut stderr = stderr.lock(); 146 | writeln!(stderr, "-> {:?}", mtx).ok(); 147 | 148 | let stdout = io::stdout(); 149 | let mut stdout = stdout.lock(); 150 | let mut rx_buf = [0; 256]; 151 | if is_multicast { 152 | 153 | client.send_to(mtx.as_bytes(), server).unwrap(); 154 | 155 | // we report all responses received during the leisure period 156 | let end = Instant::now() + Duration::from_secs(u64::from(DEFAULT_LEISURE)); 157 | 158 | loop { 159 | let now = Instant::now(); 160 | 161 | let timeout = if now > end { return Ok(()) } else { end - now }; 162 | 163 | client.set_read_timeout(Some(timeout))?; 164 | 165 | let (n, addr) = match client.recv_from(&mut rx_buf) { 166 | Ok((n, addr)) => (n, addr), 167 | Err(e) => { 168 | if e.kind() == io::ErrorKind::TimedOut || e.kind() == io::ErrorKind::WouldBlock 169 | { 170 | // leisure period is over 171 | return Ok(()); 172 | } else { 173 | return Err(e.into()); 174 | } 175 | } 176 | }; 177 | 178 | if let Ok(mrx) = coap::Message::parse(&rx_buf[..n]) { 179 | if mrx.get_type() == coap::Type::NonConfirmable && mrx.get_message_id() == mid { 180 | writeln!(stderr, "<- {:?} (from {})", mrx, addr).ok(); 181 | let payload = mrx.payload(); 182 | if !payload.is_empty() { 183 | if let Ok(s) = str::from_utf8(payload) { 184 | writeln!(stdout, "{}", s).ok(); 185 | } else { 186 | writeln!(stdout, "{:?}", payload).ok(); 187 | } 188 | } 189 | } else { 190 | bail!("received unrelated response"); 191 | } 192 | } else { 193 | bail!("parsing incoming CoAP message") 194 | } 195 | } 196 | } else { 197 | // if unicast, connect to the server 198 | client.connect(server)?; 199 | client.send(mtx.as_bytes()).unwrap(); 200 | 201 | let between = Uniform::new(1.0, ACK_RANDOM_FACTOR); 202 | let mut timeout = 203 | Duration::from_millis((between.sample(&mut rng) * ACK_TIMEOUT as f64) as u64); 204 | 205 | for _ in 0..MAX_RETRANSMIT { 206 | client.set_read_timeout(Some(timeout))?; 207 | 208 | let n = match client.recv(&mut rx_buf) { 209 | Ok(n) => n, 210 | Err(e) => { 211 | if e.kind() == io::ErrorKind::TimedOut || e.kind() == io::ErrorKind::WouldBlock 212 | { 213 | // try again 214 | timeout *= 2; 215 | 216 | continue; 217 | } else { 218 | return Err(e.into()); 219 | } 220 | } 221 | }; 222 | 223 | if let Ok(mrx) = coap::Message::parse(&rx_buf[..n]) { 224 | if mrx.get_type() == coap::Type::Acknowledgement && mrx.get_message_id() == mid { 225 | writeln!(stderr, "<- {:?}", mrx).ok(); 226 | let payload = mrx.payload(); 227 | if !payload.is_empty() { 228 | if let Ok(s) = str::from_utf8(payload) { 229 | writeln!(stdout, "{}", s).ok(); 230 | } else { 231 | writeln!(stdout, "{:?}", payload).ok(); 232 | } 233 | } 234 | 235 | return Ok(()); 236 | } else { 237 | bail!("received unrelated response"); 238 | } 239 | } else { 240 | bail!("parsing incoming CoAP message") 241 | } 242 | } 243 | 244 | bail!("timed out") 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /ujson/.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /ujson/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ujson" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ujson-macros = { path = "macros" } 9 | -------------------------------------------------------------------------------- /ujson/macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ujson-macros" 3 | version = "0.1.0" 4 | authors = ["Jorge Aparicio "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | quote = "1" 9 | syn = "1" 10 | 11 | [lib] 12 | proc-macro = true 13 | -------------------------------------------------------------------------------- /ujson/macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | extern crate proc_macro; 4 | 5 | use proc_macro::TokenStream; 6 | use quote::quote; 7 | use syn::{parse_macro_input, Data, DeriveInput, Fields, LitByteStr, Type}; 8 | 9 | #[proc_macro_derive(uSerialize)] 10 | pub fn serialize(input: TokenStream) -> TokenStream { 11 | let input = parse_macro_input!(input as DeriveInput); 12 | let error = quote!(compile_error!( 13 | "`#[derive(uSerialize)]` can only be used on `struct`s with named fields" 14 | )) 15 | .into(); 16 | 17 | match input.data { 18 | Data::Struct(s) => { 19 | let ident = input.ident; 20 | 21 | let fields = match s.fields { 22 | Fields::Named(fields) => fields.named, 23 | _ => return error, 24 | }; 25 | 26 | if fields.is_empty() { 27 | return error; 28 | } 29 | 30 | let mut exprs = vec![]; 31 | let mut is_first = true; 32 | for field in fields { 33 | if is_first { 34 | is_first = false; 35 | } else { 36 | exprs.push(quote!( 37 | cursor.push_byte(b',')?; 38 | )); 39 | } 40 | 41 | let ident = field.ident.expect("unreachable"); 42 | let lit = LitByteStr::new(ident.to_string().as_bytes(), ident.span()); 43 | exprs.push(quote!( 44 | ujson::ser::field_name(#lit, cursor)?; 45 | cursor.push_byte(b':')?; 46 | )); 47 | 48 | let ty = &field.ty; 49 | match ty { 50 | Type::Array(array) => { 51 | let ty = &array.elem; 52 | 53 | exprs.push(quote!( 54 | <[#ty]>::serialize(&self.#ident, cursor)?; 55 | )); 56 | } 57 | 58 | _ => { 59 | exprs.push(quote!( 60 | #ty::serialize(&self.#ident, cursor)?; 61 | )); 62 | } 63 | } 64 | } 65 | 66 | quote!( 67 | impl ujson::Serialize for #ident { 68 | #[deny(unused_must_use)] 69 | fn serialize(&self, cursor: &mut ujson::ser::Cursor) -> Result<(), ()> { 70 | use ujson::Serialize; 71 | 72 | cursor.push_byte(b'{')?; 73 | #(#exprs)* 74 | cursor.push_byte(b'}') 75 | } 76 | } 77 | ) 78 | .into() 79 | } 80 | 81 | _ => error, 82 | } 83 | } 84 | 85 | #[proc_macro_derive(uDeserialize)] 86 | pub fn deserialize(input: TokenStream) -> TokenStream { 87 | let input = parse_macro_input!(input as DeriveInput); 88 | let error = quote!(compile_error!( 89 | "`#[derive(uSerialize)]` can only be used on `struct`s with named fields" 90 | )) 91 | .into(); 92 | 93 | match input.data { 94 | Data::Struct(s) => { 95 | let ident = input.ident; 96 | 97 | let fields = match s.fields { 98 | Fields::Named(fields) => fields.named, 99 | _ => return error, 100 | }; 101 | 102 | if fields.is_empty() { 103 | return error; 104 | } 105 | 106 | let nfields = fields.len(); 107 | let mut field_names = vec![]; 108 | let mut field_exprs = vec![]; 109 | let mut branches = vec![]; 110 | for field in fields { 111 | let ident = field.ident.expect("unreachable"); 112 | let lit = LitByteStr::new(ident.to_string().as_bytes(), ident.span()); 113 | let ty = field.ty; 114 | field_exprs.push(quote!(#ident: #ident.ok_or(())?)); 115 | field_names.push(ident.clone()); 116 | 117 | branches.push(quote!( 118 | cursor.matches_byte_string(#lit)? { 119 | if #ident.is_some() { 120 | return Err(()); 121 | } 122 | 123 | cursor.parse_whitespace(); 124 | cursor.expect(b':')?; 125 | cursor.parse_whitespace(); 126 | #ident = Some(#ty::deserialize(cursor)?); 127 | is_first = false; 128 | cursor.parse_whitespace(); 129 | } 130 | )) 131 | } 132 | 133 | quote!( 134 | impl ujson::Deserialize for #ident { 135 | #[deny(unused_must_use)] 136 | fn deserialize(cursor: &mut ujson::de::Cursor) -> Result { 137 | use ujson::Deserialize; 138 | 139 | const FIELDS: usize = #nfields; 140 | 141 | #(let mut #field_names = None;)* 142 | let mut is_first = true; 143 | 144 | cursor.expect(b'{')?; 145 | cursor.parse_whitespace(); 146 | 147 | for _ in 0..FIELDS { 148 | if !is_first { 149 | cursor.expect(b',')?; 150 | cursor.parse_whitespace(); 151 | } 152 | 153 | #(if #branches else)* { 154 | return Err(()); 155 | } 156 | } 157 | 158 | cursor.expect(b'}')?; 159 | 160 | Ok(#ident { 161 | #(#field_exprs,)* 162 | }) 163 | } 164 | } 165 | ) 166 | .into() 167 | } 168 | 169 | _ => error, 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /ujson/src/de.rs: -------------------------------------------------------------------------------- 1 | //! Deserialization 2 | 3 | use core::str; 4 | 5 | use crate::traits::SliceExt; 6 | 7 | /// Deserializes `T` from the given `bytes` 8 | pub fn from_bytes(bytes: &[u8]) -> Result 9 | where 10 | T: Deserialize, 11 | { 12 | let mut cursor = Cursor::new(bytes); 13 | cursor.parse_whitespace(); 14 | let x = T::deserialize(&mut cursor)?; 15 | cursor.finish()?; 16 | Ok(x) 17 | } 18 | 19 | /// Types that can be deserialized into JSON 20 | pub trait Deserialize: Sized { 21 | // IMPLEMENTATION DETAIL 22 | #[doc(hidden)] 23 | fn deserialize(cursor: &mut Cursor<'_>) -> Result; 24 | } 25 | 26 | impl Deserialize for bool { 27 | fn deserialize(cursor: &mut Cursor<'_>) -> Result { 28 | match cursor.peek() { 29 | Some(b't') => cursor.parse_ident(b"true").map(|_| true), 30 | Some(b'f') => cursor.parse_ident(b"false").map(|_| false), 31 | _ => Err(()), 32 | } 33 | } 34 | } 35 | 36 | macro_rules! unsigned { 37 | ($($uN:ty),+) => { 38 | $( 39 | impl Deserialize for $uN { 40 | fn deserialize(cursor: &mut Cursor<'_>) -> Result { 41 | let mut out: $uN = 0; 42 | 43 | let mut is_first = true; 44 | for digit in cursor.parse_digits(false)? { 45 | if is_first { 46 | is_first = false; 47 | } else { 48 | out = out.checked_mul(10).ok_or(())?; 49 | } 50 | 51 | out = out.checked_add((digit - b'0') as $uN).ok_or(())?; 52 | } 53 | 54 | Ok(out) 55 | } 56 | } 57 | 58 | )+ 59 | } 60 | } 61 | 62 | unsigned!(u8, u16, u32, u64, usize); 63 | 64 | macro_rules! signed { 65 | ($($iN:ty),+) => { 66 | $( 67 | impl Deserialize for $iN { 68 | fn deserialize(cursor: &mut Cursor<'_>) -> Result { 69 | let mut out: $iN = 0; 70 | 71 | let digits = cursor.parse_digits(true)?; 72 | let is_negative = digits.get(0) == Some(&b'-'); 73 | let mut is_first = true; 74 | for digit in digits.iter().skip(if is_negative { 1 } else { 0 }) { 75 | if is_first { 76 | is_first = false; 77 | } else { 78 | out = out.checked_mul(10).ok_or(())?; 79 | } 80 | 81 | let digit = (digit - b'0') as $iN; 82 | if is_negative { 83 | out = out.checked_sub(digit).ok_or(())?; 84 | } else { 85 | out = out.checked_add(digit).ok_or(())?; 86 | } 87 | } 88 | 89 | Ok(out) 90 | } 91 | } 92 | 93 | )+ 94 | } 95 | } 96 | 97 | signed!(i8, i16, i32, i64, isize); 98 | 99 | // IMPLEMENTATION DETAIL 100 | #[doc(hidden)] 101 | pub struct Cursor<'a> { 102 | bytes: &'a [u8], 103 | index: usize, 104 | } 105 | 106 | impl<'a> Cursor<'a> { 107 | fn new(bytes: &'a [u8]) -> Self { 108 | Cursor { bytes, index: 0 } 109 | } 110 | 111 | // NOTE(unsafe) caller must ensure that the invariant is not broken 112 | unsafe fn bump(&mut self) { 113 | self.index += 1; 114 | 115 | invariant!(dbg!(self.index) <= dbg!(self.bytes.len())); 116 | } 117 | 118 | // IMPLEMENTATION DETAIL 119 | #[doc(hidden)] 120 | pub fn expect(&mut self, byte: u8) -> Result<(), ()> { 121 | if self.peek() == Some(byte) { 122 | unsafe { self.bump() } 123 | Ok(()) 124 | } else { 125 | Err(()) 126 | } 127 | } 128 | 129 | fn finish(mut self) -> Result<(), ()> { 130 | self.parse_whitespace(); 131 | if self.peek() == None { 132 | Ok(()) 133 | } else { 134 | Err(()) 135 | } 136 | } 137 | 138 | // IMPLEMENTATION DETAIL 139 | #[doc(hidden)] 140 | pub fn matches_byte_string(&mut self, ident: &[u8]) -> Result { 141 | let original = self.index; 142 | 143 | if self.peek() != Some(b'"') { 144 | return Err(()); 145 | } 146 | 147 | unsafe { self.bump() } 148 | 149 | if self.matches_ident(ident) { 150 | if self.peek() == Some(b'"') { 151 | unsafe { self.bump() } 152 | Ok(true) 153 | } else { 154 | self.index = original; 155 | Ok(false) 156 | } 157 | } else { 158 | self.index = original; 159 | Ok(false) 160 | } 161 | } 162 | 163 | fn matches_ident(&mut self, ident: &[u8]) -> bool { 164 | let len = ident.len(); 165 | let index = self.index; 166 | 167 | invariant!(dbg!(self.index) <= dbg!(self.bytes.len())); 168 | if self.bytes.len() - self.index < len { 169 | return false; 170 | } 171 | 172 | if unsafe { self.bytes.slice(index, len) } == ident { 173 | self.index += len; 174 | invariant!(dbg!(self.index) <= dbg!(self.bytes.len())); 175 | 176 | true 177 | } else { 178 | false 179 | } 180 | } 181 | 182 | fn peek(&self) -> Option { 183 | invariant!(dbg!(self.index) <= dbg!(self.bytes.len())); 184 | 185 | self.bytes.get(self.index).cloned() 186 | } 187 | 188 | #[allow(dead_code)] 189 | fn parse_unescaped_str(&mut self) -> Result<&str, ()> { 190 | self.expect(b'"')?; 191 | 192 | let start = self.index; 193 | let end = loop { 194 | match self.peek() { 195 | // EOF, 196 | None => return Err(()), 197 | 198 | // escaped string 199 | Some(b'\\') => return Err(()), 200 | 201 | // end of string 202 | Some(b'"') => { 203 | let end = self.index; 204 | unsafe { self.bump() } 205 | break end; 206 | } 207 | 208 | // control character 209 | Some(0..=31) => return Err(()), 210 | 211 | // UTF-8 validation (see RFC3629) 212 | Some(first) => { 213 | unsafe { self.bump() } 214 | 215 | match first { 216 | // UTF8-1 = %x00-7F 217 | 0..=0x7F => {} 218 | 219 | // UTF8-2 = %xC2-DF UTF8-tail 220 | 0xC2..=0xDF => { 221 | let next = self.peek().ok_or(())?; 222 | 223 | if next >> 6 == 0b10 { 224 | unsafe { self.bump() } 225 | } else { 226 | return Err(()); 227 | } 228 | } 229 | 230 | // UTF8-3 = %xE0 %xA0-BF UTF8-tail / 231 | // %xE1-EC 2( UTF8-tail ) / 232 | // %xED %x80-9F UTF8-tail / 233 | // %xEE-EF 2( UTF8-tail ) 234 | 0xE0..=0xEF => { 235 | let next = self.peek().ok_or(())?; 236 | 237 | match (first, next) { 238 | (0xE0, 0xA0..=0xBF) 239 | | (0xE1..=0xEC, 0x80..=0xBF) 240 | | (0xED, 0x80..=0x9F) 241 | | (0xEE..=0xEF, 0x80..=0xBF) => unsafe { self.bump() }, 242 | _ => return Err(()), 243 | } 244 | 245 | let next = self.peek().ok_or(())?; 246 | 247 | if next >> 6 == 0b10 { 248 | unsafe { self.bump() } 249 | } else { 250 | return Err(()); 251 | } 252 | } 253 | 254 | // UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / 255 | // %xF1-F3 3( UTF8-tail ) / 256 | // %xF4 %x80-8F 2( UTF8-tail ) 257 | 0xF0..=0xF4 => { 258 | let next = self.peek().ok_or(())?; 259 | 260 | match (first, next) { 261 | (0xF0, 0x90..=0xBF) 262 | | (0xF1..=0xF3, 0x80..=0xBF) 263 | | (0xF4, 0x80..=0x8F) => unsafe { self.bump() }, 264 | _ => return Err(()), 265 | } 266 | 267 | let next = self.peek().ok_or(())?; 268 | 269 | if next >> 6 == 0b10 { 270 | unsafe { self.bump() } 271 | } else { 272 | return Err(()); 273 | } 274 | 275 | let next = self.peek().ok_or(())?; 276 | 277 | if next >> 6 == 0b10 { 278 | unsafe { self.bump() } 279 | } else { 280 | return Err(()); 281 | } 282 | } 283 | 284 | _ => return Err(()), 285 | } 286 | } 287 | } 288 | }; 289 | 290 | unsafe { Ok(str::from_utf8_unchecked(&self.bytes[start..end])) } 291 | } 292 | 293 | // NOTE doesn't support floating point number 294 | fn parse_digits(&mut self, signed: bool) -> Result<&[u8], ()> { 295 | let start = self.index; 296 | 297 | if signed { 298 | if self.peek() == Some(b'-') { 299 | unsafe { self.bump() } 300 | } 301 | } 302 | 303 | let end = match self.peek() { 304 | Some(b'0') => { 305 | unsafe { self.bump() } 306 | self.index 307 | } 308 | 309 | Some(b'1'..=b'9') => { 310 | unsafe { self.bump() } 311 | loop { 312 | match self.peek() { 313 | Some(b'0'..=b'9') => unsafe { self.bump() }, 314 | 315 | _ => break self.index, 316 | } 317 | } 318 | } 319 | 320 | _ => return Err(()), 321 | }; 322 | 323 | Ok(&self.bytes[start..end]) 324 | } 325 | 326 | // IMPLEMENTATION DETAIL 327 | #[doc(hidden)] 328 | pub fn parse_whitespace(&mut self) { 329 | loop { 330 | match self.peek() { 331 | Some(b' ') | Some(b'\n') | Some(b'\t') | Some(b'\r') => unsafe { self.bump() }, 332 | 333 | _ => return, 334 | } 335 | } 336 | } 337 | 338 | fn parse_ident(&mut self, ident: &[u8]) -> Result<(), ()> { 339 | if self.matches_ident(ident) { 340 | Ok(()) 341 | } else { 342 | Err(()) 343 | } 344 | } 345 | } 346 | 347 | #[cfg(test)] 348 | mod tests { 349 | use super::Cursor; 350 | 351 | #[test] 352 | fn boolean() { 353 | assert_eq!(super::from_bytes::(b"true").unwrap(), true); 354 | assert_eq!(super::from_bytes::(b"false").unwrap(), false); 355 | } 356 | 357 | #[test] 358 | fn u8() { 359 | assert_eq!(super::from_bytes::(b"0").unwrap(), 0); 360 | assert_eq!(super::from_bytes::(b"10").unwrap(), 10); 361 | assert_eq!(super::from_bytes::(b"100").unwrap(), 100); 362 | assert_eq!(super::from_bytes::(b"255").unwrap(), 255); 363 | 364 | assert!(super::from_bytes::(b"-0").is_err()); 365 | assert!(super::from_bytes::(b"-1").is_err()); 366 | assert!(super::from_bytes::(b"256").is_err()); 367 | assert!(super::from_bytes::(b"1000").is_err()); 368 | } 369 | 370 | #[test] 371 | fn i8() { 372 | assert_eq!(super::from_bytes::(b"0").unwrap(), 0); 373 | assert_eq!(super::from_bytes::(b"10").unwrap(), 10); 374 | assert_eq!(super::from_bytes::(b"100").unwrap(), 100); 375 | assert_eq!(super::from_bytes::(b"-1").unwrap(), -1); 376 | assert_eq!(super::from_bytes::(b"-10").unwrap(), -10); 377 | assert_eq!(super::from_bytes::(b"-100").unwrap(), -100); 378 | assert_eq!(super::from_bytes::(b"127").unwrap(), 127); 379 | assert_eq!(super::from_bytes::(b"-128").unwrap(), -128); 380 | 381 | assert!(super::from_bytes::(b"128").is_err()); 382 | assert!(super::from_bytes::(b"-129").is_err()); 383 | assert!(super::from_bytes::(b"1_000").is_err()); 384 | assert!(super::from_bytes::(b"-200").is_err()); 385 | assert!(super::from_bytes::(b"-1_000").is_err()); 386 | } 387 | 388 | #[test] 389 | fn whitespace() { 390 | assert_eq!(super::from_bytes::(b" true").unwrap(), true); 391 | assert_eq!(super::from_bytes::(b"true ").unwrap(), true); 392 | assert_eq!(super::from_bytes::(b" true ").unwrap(), true); 393 | } 394 | 395 | #[test] 396 | fn str() { 397 | let mut cursor = Cursor::new("\"こんにちは\"".as_bytes()); 398 | assert_eq!(cursor.parse_unescaped_str().unwrap(), "こんにちは"); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /ujson/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `uJSON`: JSON (de)serialization for memory constrained devices 2 | //! 3 | //! # References 4 | //! 5 | //! - [RFC8259 The JavaScript Object Notation (JSON) Data Interchange Format][rfc8259] 6 | //! 7 | //! [rfc8259]: https://tools.ietf.org/html/rfc8259 8 | 9 | #![deny(missing_docs)] 10 | #![deny(rust_2018_compatibility)] 11 | #![deny(rust_2018_idioms)] 12 | #![deny(warnings)] 13 | #![no_std] 14 | 15 | #[cfg(test)] 16 | #[macro_use] 17 | extern crate std; 18 | 19 | pub use ujson_macros::{uDeserialize, uSerialize}; 20 | 21 | #[macro_use] 22 | mod macros; 23 | 24 | #[doc(hidden)] 25 | pub mod de; 26 | #[doc(hidden)] 27 | pub mod ser; 28 | 29 | mod traits; 30 | 31 | #[doc(inline)] 32 | pub use de::{from_bytes, Deserialize}; 33 | #[doc(inline)] 34 | pub use ser::{write, Serialize}; 35 | -------------------------------------------------------------------------------- /ujson/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(test))] 2 | macro_rules! dbg { 3 | ($e:expr) => { 4 | $e 5 | } 6 | } 7 | 8 | macro_rules! invariant { 9 | ($cond:expr) => { 10 | debug_assert!($cond) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ujson/src/ser.rs: -------------------------------------------------------------------------------- 1 | //! Serialization 2 | 3 | use core::{mem::MaybeUninit, slice, str}; 4 | 5 | use crate::traits::SliceExt; 6 | 7 | /// Serializes the `value` into the given `buffer` 8 | pub fn write<'a, T>(value: &T, buffer: &'a mut [u8]) -> Result<&'a str, ()> 9 | where 10 | T: Serialize + ?Sized, 11 | { 12 | let mut cursor = Cursor::new(buffer); 13 | value.serialize(&mut cursor)?; 14 | Ok(cursor.finish()) 15 | } 16 | 17 | // IMPLEMENTATION DETAIL 18 | // fast path: these don't contain unicode 19 | #[doc(hidden)] 20 | pub fn field_name(ident: &[u8], cursor: &mut Cursor<'_>) -> Result<(), ()> { 21 | cursor.push_byte(b'"')?; 22 | cursor.push(ident)?; 23 | cursor.push_byte(b'"') 24 | } 25 | 26 | /// Types that can be serialized into JSON 27 | pub trait Serialize { 28 | // IMPLEMENTATION DETAIL 29 | #[doc(hidden)] 30 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()>; 31 | } 32 | 33 | impl Serialize for bool { 34 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 35 | cursor.push(if *self { b"true" } else { b"false" }) 36 | } 37 | } 38 | 39 | unsafe fn uninitialized() -> T { 40 | MaybeUninit::uninit().assume_init() 41 | } 42 | 43 | macro_rules! unsigned { 44 | ($(($uN:ty, $N:expr),)+) => { 45 | $( 46 | impl Serialize for $uN { 47 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 48 | let mut x = *self; 49 | 50 | let mut buf: [u8; $N] = unsafe { uninitialized() }; 51 | let mut idx = $N - 1; 52 | loop { 53 | buf[idx] = b'0' + (x % 10) as u8; 54 | x /= 10; 55 | 56 | if x == 0 { 57 | break; 58 | } 59 | 60 | idx -= 1; 61 | } 62 | 63 | cursor.push(&buf[idx..]) 64 | } 65 | } 66 | )+ 67 | } 68 | } 69 | 70 | unsigned! { 71 | (u8, 3), 72 | (u16, 5), 73 | (u32, 10), 74 | (u64, 20), 75 | } 76 | 77 | macro_rules! signed { 78 | ($(($iN:ty, $uN:ty, $N:expr),)+) => { 79 | $( 80 | impl Serialize for $iN { 81 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 82 | let is_negative = *self < 0; 83 | let mut x = self.wrapping_abs() as $uN; 84 | 85 | let mut buf: [u8; $N] = unsafe { uninitialized() }; 86 | let mut idx = $N - 1; 87 | loop { 88 | buf[idx] = b'0' + (x % 10) as u8; 89 | x /= 10; 90 | 91 | if x == 0 { 92 | break; 93 | } 94 | 95 | idx -= 1; 96 | } 97 | 98 | if is_negative { 99 | idx -= 1; 100 | buf[idx] = b'-'; 101 | } 102 | 103 | cursor.push(&buf[idx..]) 104 | } 105 | } 106 | )+ 107 | } 108 | } 109 | 110 | signed! { 111 | (i8, u8, 4), 112 | (i16, u16, 6), 113 | (i32, u32, 11), 114 | (i64, u64, 20), 115 | } 116 | 117 | impl Serialize for str { 118 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 119 | cursor.push_byte(b'"')?; 120 | 121 | let bytes = self.as_bytes(); 122 | let mut start = 0; 123 | 124 | for (i, byte) in bytes.iter().enumerate() { 125 | if let Some(escape) = Escape::from(*byte) { 126 | if start < i { 127 | cursor.push(&bytes[start..i])?; 128 | } 129 | 130 | match escape { 131 | Escape::Backspace => cursor.push(b"\\b")?, 132 | Escape::CarriageReturn => cursor.push(b"\\r")?, 133 | Escape::FormFeed => cursor.push(b"\\f")?, 134 | Escape::LineFeed => cursor.push(b"\\n")?, 135 | Escape::QuotationMark => cursor.push(b"\\\"")?, 136 | Escape::ReverseSolidus => cursor.push(b"\\\\")?, 137 | Escape::Tab => cursor.push(b"\\t")?, 138 | Escape::Unicode => { 139 | static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef"; 140 | cursor.push(b"\\u00")?; 141 | cursor.push_byte(HEX_DIGITS[(byte >> 4) as usize])?; 142 | cursor.push_byte(HEX_DIGITS[(byte & 0xF) as usize])?; 143 | } 144 | } 145 | 146 | start = i + 1; 147 | } 148 | } 149 | 150 | if start < bytes.len() { 151 | cursor.push(&bytes[start..])?; 152 | } 153 | 154 | cursor.push_byte(b'"') 155 | } 156 | } 157 | 158 | // See RFC8259 Section 7 "Strings" 159 | enum Escape { 160 | QuotationMark, 161 | ReverseSolidus, 162 | // Solidus, 163 | Backspace, 164 | FormFeed, 165 | LineFeed, 166 | CarriageReturn, 167 | Tab, 168 | Unicode, 169 | } 170 | 171 | impl Escape { 172 | fn from(byte: u8) -> Option { 173 | Some(if byte == b'"' { 174 | Escape::QuotationMark 175 | } else if byte == b'\\' { 176 | Escape::ReverseSolidus 177 | // } else if byte == b'/' { 178 | // Escape::Solidus 179 | } else if byte == 0x08 { 180 | Escape::Backspace 181 | } else if byte == 0x0c { 182 | Escape::FormFeed 183 | } else if byte == 0x0a { 184 | Escape::LineFeed 185 | } else if byte == 0x0d { 186 | Escape::CarriageReturn 187 | } else if byte == 0x09 { 188 | Escape::Tab 189 | } else if byte < 0x20 { 190 | Escape::Unicode 191 | } else { 192 | return None; 193 | }) 194 | } 195 | } 196 | 197 | impl Serialize for [T] 198 | where 199 | T: Serialize, 200 | { 201 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 202 | cursor.push_byte(b'[')?; 203 | 204 | let mut first = true; 205 | for elem in self { 206 | if first { 207 | first = false; 208 | } else { 209 | cursor.push_byte(b',')?; 210 | } 211 | 212 | elem.serialize(cursor)?; 213 | } 214 | 215 | cursor.push_byte(b']') 216 | } 217 | } 218 | 219 | impl<'a, T> Serialize for &'a T 220 | where 221 | T: Serialize, 222 | { 223 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 224 | T::serialize(*self, cursor) 225 | } 226 | } 227 | 228 | macro_rules! arrays { 229 | ($($N:expr),+) => { 230 | $( 231 | impl Serialize for [T; $N] 232 | where 233 | T: Serialize, 234 | { 235 | fn serialize(&self, cursor: &mut Cursor<'_>) -> Result<(), ()> { 236 | <[T]>::serialize(self, cursor) 237 | } 238 | } 239 | )+ 240 | } 241 | } 242 | 243 | arrays!( 244 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 245 | 26, 27, 28, 29, 30, 31, 32 246 | ); 247 | 248 | // IMPLEMENTATION DETAIL 249 | #[doc(hidden)] 250 | pub struct Cursor<'a> { 251 | buffer: &'a mut [u8], 252 | index: usize, 253 | } 254 | 255 | impl<'a> Cursor<'a> { 256 | fn new(buffer: &'a mut [u8]) -> Self { 257 | Cursor { buffer, index: 0 } 258 | } 259 | 260 | unsafe fn bump(&mut self, n: usize) { 261 | self.index += n; 262 | 263 | invariant!(dbg!(self.index) <= dbg!(self.buffer.len())); 264 | } 265 | 266 | // IMPLEMENTATION DETAIL 267 | #[doc(hidden)] 268 | pub fn push_byte(&mut self, byte: u8) -> Result<(), ()> { 269 | let index = self.index; 270 | 271 | *self.buffer.get_mut(index).ok_or(())? = byte; 272 | unsafe { self.bump(1) } 273 | 274 | Ok(()) 275 | } 276 | 277 | // IMPLEMENTATION DETAIL 278 | #[doc(hidden)] 279 | pub fn push(&mut self, slice: &[u8]) -> Result<(), ()> { 280 | let index = self.index; 281 | let len = slice.len(); 282 | 283 | if len > self.buffer.len() - index { 284 | return Err(()); 285 | } 286 | 287 | unsafe { 288 | self.buffer.slice_mut(index, len).copy_from_slice(slice); 289 | self.bump(len); 290 | } 291 | 292 | Ok(()) 293 | } 294 | 295 | fn finish(self) -> &'a str { 296 | unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.buffer.as_ptr(), self.index)) } 297 | } 298 | } 299 | 300 | #[cfg(test)] 301 | mod tests { 302 | #[test] 303 | fn boolean() { 304 | assert_eq!(super::write(&false, &mut [0; 5]).unwrap(), "false"); 305 | assert_eq!(super::write(&true, &mut [0; 4]).unwrap(), "true"); 306 | } 307 | 308 | #[test] 309 | fn u8() { 310 | assert_eq!(super::write(&0u8, &mut [0; 3]).unwrap(), "0"); 311 | assert_eq!(super::write(&10u8, &mut [0; 3]).unwrap(), "10"); 312 | assert_eq!(super::write(&100u8, &mut [0; 3]).unwrap(), "100"); 313 | assert_eq!(super::write(&255u8, &mut [0; 3]).unwrap(), "255"); 314 | } 315 | 316 | #[test] 317 | fn i8() { 318 | assert_eq!(super::write(&0i8, &mut [0; 4]).unwrap(), "0"); 319 | assert_eq!(super::write(&-10i8, &mut [0; 4]).unwrap(), "-10"); 320 | assert_eq!(super::write(&-100i8, &mut [0; 4]).unwrap(), "-100"); 321 | assert_eq!(super::write(&-128i8, &mut [0; 4]).unwrap(), "-128"); 322 | } 323 | 324 | #[test] 325 | fn seq() { 326 | assert_eq!(super::write(&[0u8, 1, 2], &mut [0; 8]).unwrap(), "[0,1,2]"); 327 | } 328 | 329 | #[test] 330 | fn str() { 331 | assert_eq!(super::write("led", &mut [0; 8]).unwrap(), "\"led\""); 332 | assert_eq!( 333 | super::write("こんにちは", &mut [0; 32]).unwrap(), 334 | "\"こんにちは\"" 335 | ); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /ujson/src/traits.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | pub trait SliceExt { 4 | unsafe fn slice(&self, start: usize, len: usize) -> &Self; 5 | unsafe fn slice_mut(&mut self, start: usize, len: usize) -> &mut Self; 6 | } 7 | 8 | impl SliceExt for [T] { 9 | unsafe fn slice(&self, start: usize, len: usize) -> &[T] { 10 | debug_assert!(dbg!(start) < dbg!(self.len()) && dbg!(len) <= self.len() - start); 11 | 12 | slice::from_raw_parts(self.as_ptr().add(start), len) 13 | } 14 | 15 | unsafe fn slice_mut(&mut self, start: usize, len: usize) -> &mut [T] { 16 | debug_assert!(dbg!(start) < dbg!(self.len()) && dbg!(len) <= self.len() - start); 17 | 18 | slice::from_raw_parts_mut(self.as_mut_ptr().add(start), len) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ujson/tests/deserialize.rs: -------------------------------------------------------------------------------- 1 | use ujson::uDeserialize; 2 | 3 | #[test] 4 | fn one_field() { 5 | #[derive(uDeserialize, Debug, PartialEq)] 6 | struct Led { 7 | led: bool, 8 | } 9 | 10 | assert_eq!( 11 | ujson::from_bytes::("{\"led\":true}".as_bytes()).unwrap(), 12 | Led { led: true } 13 | ); 14 | 15 | assert_eq!( 16 | ujson::from_bytes::("{\"led\":false}".as_bytes()).unwrap(), 17 | Led { led: false } 18 | ); 19 | 20 | // with whitespace 21 | assert_eq!( 22 | ujson::from_bytes::("{ \"led\" : true }".as_bytes()).unwrap(), 23 | Led { led: true } 24 | ); 25 | } 26 | 27 | #[test] 28 | fn two_fields() { 29 | #[derive(uDeserialize, Debug, PartialEq)] 30 | struct Pair { 31 | x: u8, 32 | y: u16, 33 | } 34 | 35 | assert_eq!( 36 | ujson::from_bytes::("{\"x\":0,\"y\":1}".as_bytes()).unwrap(), 37 | Pair { x: 0, y: 1 } 38 | ); 39 | 40 | // reverse order 41 | assert_eq!( 42 | ujson::from_bytes::("{\"y\":0,\"x\":1}".as_bytes()).unwrap(), 43 | Pair { y: 0, x: 1 } 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /ujson/tests/serialize.rs: -------------------------------------------------------------------------------- 1 | use ujson::uSerialize; 2 | 3 | #[test] 4 | fn one_field() { 5 | #[derive(uSerialize)] 6 | struct Led { 7 | led: bool, 8 | } 9 | 10 | assert_eq!( 11 | ujson::write(&Led { led: true }, &mut [0; 16]).unwrap(), 12 | "{\"led\":true}" 13 | ); 14 | } 15 | 16 | #[test] 17 | fn two_fields() { 18 | #[derive(uSerialize)] 19 | struct Pair { 20 | x: u8, 21 | y: u16, 22 | } 23 | 24 | assert_eq!( 25 | ujson::write(&Pair { x: 0, y: 42 }, &mut [0; 16]).unwrap(), 26 | "{\"x\":0,\"y\":42}" 27 | ); 28 | } 29 | 30 | #[test] 31 | fn array() { 32 | #[derive(uSerialize)] 33 | struct X { 34 | x: [u8; 33], 35 | } 36 | 37 | assert_eq!( 38 | ujson::write(&X { x: [0; 33] }, &mut [0; 128]).unwrap(), 39 | "{\"x\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}" 40 | ); 41 | } 42 | --------------------------------------------------------------------------------