├── fuzz ├── .gitignore ├── fuzz_targets │ └── fuzz_target_1.rs └── Cargo.toml ├── hfuzz-bp7 ├── fuzz.sh ├── .gitignore ├── README.md ├── Cargo.toml └── src │ └── main.rs ├── examples ├── bp7kit │ ├── logo.png │ ├── main.css │ ├── main.js │ ├── index.html │ └── pure-min.css ├── ffi │ ├── build.sh │ ├── bp7-test.js │ └── bp7-test.c ├── benchmark.rs └── encoding_dump.rs ├── .cargo └── config.toml ├── .travis.yml ├── .devcontainer ├── Dockerfile ├── post-create.sh └── devcontainer.json ├── COPYRIGHT ├── .gitignore ├── tests ├── dtntime_tests.rs ├── crc_tests.rs ├── primary_tests.rs ├── administrative_record_tests.rs ├── canonical_tests.rs ├── security_tests.rs └── bundle_tests.rs ├── src ├── error.rs ├── lib.rs ├── dtntime.rs ├── flags.rs ├── crc.rs ├── wasm.rs ├── helpers.rs ├── main.rs ├── ffi.rs ├── primary.rs └── administrative_record.rs ├── LICENSE-MIT ├── .github └── workflows │ ├── ci.yml │ └── cd.yml ├── release.sh ├── cliff.toml ├── Cargo.toml ├── cbindgen.toml ├── CHANGELOG.md ├── benches └── benchmark.rs ├── doc └── encoding_samples.md ├── LICENSE-APACHE └── README.md /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /hfuzz-bp7/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo hfuzz run hfuzz-bp7 -------------------------------------------------------------------------------- /hfuzz-bp7/.gitignore: -------------------------------------------------------------------------------- 1 | hfuzz_workspace 2 | hfuzz_target 3 | target 4 | -------------------------------------------------------------------------------- /examples/bp7kit/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtn7/bp7-rs/HEAD/examples/bp7kit/logo.png -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | cache: cargo 7 | os: 8 | - linux 9 | - osx 10 | matrix: 11 | allow_failures: 12 | - rust: nightly 13 | fast_finish: true 14 | 15 | -------------------------------------------------------------------------------- /examples/ffi/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cargo build --release 4 | cd ../.. 5 | cbindgen -c cbindgen.toml > target/bp7.h 6 | cd examples/ffi 7 | gcc bp7-test.c -I ../../target -L../../target/release -lbp7 -static -lpthread -ldl -lm -O2 -o bp7-test 8 | 9 | -------------------------------------------------------------------------------- /hfuzz-bp7/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | - `sudo apt install build-essential binutils-dev libunwind-dev lldb` 4 | - `cargo install honggfuzz` 5 | 6 | ## Inspecting crahes in debugger 7 | 8 | `cargo hfuzz run-debug hfuzz-bp7 hfuzz_workspace/hfuzz-bp7/*.fuzz` 9 | -------------------------------------------------------------------------------- /hfuzz-bp7/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hfuzz-bp7" 3 | version = "0.1.0" 4 | authors = ["gh0st <1264131+gh0st42@users.noreply.github.com>"] 5 | edition = "2024" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | honggfuzz = "0.5" 11 | bp7 = { path = ".." } 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_target_1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use bp7::*; 3 | use libfuzzer_sys::fuzz_target; 4 | use std::convert::TryFrom; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let deserialized: std::result::Result = Bundle::try_from(Vec::from(data)); 8 | if deserialized.is_ok() { 9 | deserialized.unwrap().validate(); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | # Install basic dependencies 4 | RUN apt-get update && \ 5 | apt-get install -y \ 6 | curl \ 7 | wget \ 8 | git \ 9 | ca-certificates \ 10 | gnupg2 \ 11 | jq \ 12 | sudo \ 13 | nano \ 14 | vim \ 15 | build-essential \ 16 | openssl && \ 17 | rm -rf /var/lib/apt/lists/* 18 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2019-2022 Lars Baumgaertner 2 | 3 | Licensed under the Apache License, Version 2.0 or the MIT license 5 | , at your 6 | option. All files in the project carrying such notice may not be 7 | copied, modified, or distributed except according to those terms. 8 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "bp7-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2024" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.4" 14 | 15 | [dependencies.bp7] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "fuzz_target_1" 24 | path = "fuzz_targets/fuzz_target_1.rs" 25 | test = false 26 | doc = false 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | #Added by cargo 14 | # 15 | #already existing elements are commented out 16 | 17 | /target 18 | #**/*.rs.bk 19 | #Cargo.lock 20 | 21 | /devstuff 22 | /include 23 | -------------------------------------------------------------------------------- /.devcontainer/post-create.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # Install additional Rust toolchain 6 | rustup install nightly 7 | 8 | # Install common Rust tools (using cargo-binstall for speed) 9 | curl -L --proto '=https' --tlsv1.2 -sSf \ 10 | https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash 11 | 12 | cargo binstall -y \ 13 | cargo-expand \ 14 | cargo-edit \ 15 | cargo-audit \ 16 | cargo-geiger \ 17 | cbindgen \ 18 | wasm-pack \ 19 | wasmtime-cli 20 | -------------------------------------------------------------------------------- /examples/ffi/bp7-test.js: -------------------------------------------------------------------------------- 1 | const ffi = require('ffi-napi'); 2 | 3 | const lib = ffi.Library('libbp7-ffi', { 4 | bp7_working: ['int', []], 5 | bp7_test: ['pointer', []], 6 | helper_rnd_bundle: ['pointer', []], 7 | bundle_from_cbor: ['pointer', ['pointer']], 8 | }); 9 | 10 | 11 | console.log("bp7 test"); 12 | try { 13 | console.log(lib.bp7_working()); 14 | console.log(lib.bp7_test()); 15 | console.log(lib.bp7_test()); 16 | var cbor = lib.helper_rnd_bundle(); 17 | console.log(cbor); 18 | var bndl = lib.bundle_from_cbor(cbor); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | -------------------------------------------------------------------------------- /tests/dtntime_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::dtntime::CreationTimestamp; 2 | use std::thread::sleep; 3 | use std::time::Duration; 4 | 5 | #[test] 6 | fn test_creation_with_delay() { 7 | let ct1 = CreationTimestamp::now(); 8 | sleep(Duration::from_millis(50)); 9 | let ct2 = CreationTimestamp::now(); 10 | assert_eq!(ct1.seqno(), ct2.seqno()); 11 | assert_ne!(ct1.dtntime(), ct2.dtntime()); 12 | 13 | sleep(Duration::from_millis(1100)); 14 | let ct3 = CreationTimestamp::now(); 15 | let ct4 = CreationTimestamp::now(); 16 | assert_eq!(ct3.seqno(), 0); 17 | assert_eq!(ct4.seqno(), 1); 18 | 19 | assert_eq!(ct3.dtntime(), ct4.dtntime()); 20 | } 21 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use thiserror::Error; 4 | 5 | use crate::eid::EndpointIdError; 6 | 7 | #[derive(Debug, Error)] 8 | pub enum Error { 9 | CanonicalBlockError(String), 10 | PrimaryBlockError(String), 11 | EIDError(#[from] EndpointIdError), 12 | DtnTimeError(String), 13 | CrcError(String), 14 | BundleError(String), 15 | BundleControlFlagsError(String), 16 | BlockControlFlagsError(String), 17 | JsonDecodeError(#[from] serde_json::Error), 18 | CborDecodeError(#[from] serde_cbor::Error), 19 | } 20 | 21 | impl fmt::Display for Error { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | write!(f, "{:?}", self) 24 | // or, alternatively: 25 | // fmt::Debug::fmt(self, f) 26 | } 27 | } 28 | 29 | pub type ErrorList = Vec; 30 | -------------------------------------------------------------------------------- /tests/crc_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::crc::CrcBlock; 2 | use bp7::*; 3 | #[test] 4 | fn crc_valid_tests() { 5 | let mut b = helpers::rnd_bundle(dtntime::CreationTimestamp::now()); 6 | b.set_crc(crc::CRC_NO); 7 | b.calculate_crc(); 8 | assert!(b.crc_valid()); 9 | 10 | b.set_crc(crc::CRC_16); 11 | b.calculate_crc(); 12 | assert!(b.crc_valid()); 13 | 14 | b.set_crc(crc::CRC_32); 15 | b.calculate_crc(); 16 | assert!(b.crc_valid()); 17 | } 18 | 19 | #[test] 20 | fn crc_invalid_tests() { 21 | let mut b = helpers::rnd_bundle(dtntime::CreationTimestamp::now()); 22 | 23 | b.set_crc(crc::CRC_16); 24 | b.calculate_crc(); 25 | b.primary.set_crc(crc::CrcValue::Crc16([23, 42])); 26 | assert!(!b.crc_valid()); 27 | 28 | b.set_crc(crc::CRC_32); 29 | b.calculate_crc(); 30 | b.primary.set_crc(crc::CrcValue::Crc32([23, 42, 23, 42])); 31 | assert!(!b.crc_valid()); 32 | } 33 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bp7-rs Dev Container", 3 | "build": { 4 | "context": "..", 5 | "dockerfile": "Dockerfile" 6 | }, 7 | "remoteUser": "ubuntu", 8 | "features": { 9 | "ghcr.io/devcontainers/features/common-utils:2": { 10 | "configureZshAsDefaultShell": true 11 | }, 12 | "ghcr.io/devcontainers/features/rust:1": { 13 | "targets": "wasm32-unknown-unknown,wasm32-wasip1" 14 | } 15 | }, 16 | "customizations": { 17 | "vscode": { 18 | "extensions": [ 19 | "github.vscode-github-actions", 20 | "rust-lang.rust-analyzer", 21 | "fill-labs.dependi", 22 | "tamasfe.even-better-toml", 23 | "vadimcn.vscode-lldb" 24 | ] 25 | } 26 | }, 27 | "postCreateCommand": "./.devcontainer/post-create.sh" 28 | } -------------------------------------------------------------------------------- /examples/ffi/bp7-test.c: -------------------------------------------------------------------------------- 1 | #include "bp7.h" 2 | #include 3 | 4 | int main() { 5 | printf("bp7 c ffi test\n"); 6 | 7 | // for (int i = 0; i < 100000; i++) { 8 | printf("generating random bundle...\n"); 9 | Buffer *buf = helper_rnd_bundle(); 10 | 11 | printf("received buffer len: %d\n", buf->len); 12 | 13 | printf("parsing bundle again from cbor buffer...\n"); 14 | Bundle *bndl = bundle_from_cbor(buf); 15 | 16 | printf("getting metadata from parsed bundle...\n"); 17 | BundleMetaData *meta = bundle_get_metadata(bndl); 18 | printf(" meta.src: %s\n", meta->src); 19 | printf(" meta.dst: %s\n", meta->dst); 20 | bundle_metadata_free(meta); 21 | 22 | printf("getting payload from parsed bundle...\n"); 23 | Buffer *payload = bundle_payload(bndl); 24 | printf(" payload: %s\n", payload->data); 25 | 26 | buffer_free(payload); 27 | 28 | bundle_free(bndl); 29 | buffer_free(buf); 30 | //} 31 | return 0; 32 | } -------------------------------------------------------------------------------- /hfuzz-bp7/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate honggfuzz; 3 | 4 | use bp7::Bundle; 5 | use std::convert::TryFrom; 6 | 7 | fn main() { 8 | // Here you can parse `std::env::args and 9 | // setup / initialize your project 10 | 11 | // You have full control over the loop but 12 | // you're supposed to call `fuzz` ad vitam aeternam 13 | loop { 14 | // The fuzz macro gives an arbitrary object (see `arbitrary crate`) 15 | // to a closure-like block of code. 16 | // For performance reasons, it is recommended that you use the native type 17 | // `&[u8]` when possible. 18 | // Here, this slice will contain a "random" quantity of "random" data. 19 | fuzz!(|data: &[u8]| { 20 | let deserialized: std::result::Result = Bundle::try_from(Vec::from(data)); 21 | if deserialized.is_ok() { 22 | deserialized.unwrap().validate(); 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021 Lars Baumgaertner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | workflow_dispatch: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | cargo: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | task: 20 | - { name: check, cmd: "check --all-targets --all-features" } 21 | - { 22 | name: test, 23 | cmd: "test --all-targets --all-features --lib --bins --tests --examples", 24 | } 25 | - { name: fmt, cmd: "fmt --all -- --check" } 26 | - { 27 | name: clippy, 28 | cmd: "clippy --all-targets --all-features --tests --examples -- -D warnings", 29 | } 30 | - { 31 | name: doc, 32 | cmd: "doc --no-deps --all-features --document-private-items", 33 | } 34 | name: ${{ matrix.task.name }} 35 | 36 | steps: 37 | - name: Checkout Sources 38 | uses: actions/checkout@v5 39 | 40 | - name: Install Rust Toolchain 41 | uses: actions-rust-lang/setup-rust-toolchain@v1 42 | with: 43 | toolchain: stable 44 | components: clippy, rustfmt 45 | cache: true 46 | 47 | - name: Run ${{ matrix.task.name }} 48 | run: cargo ${{ matrix.task.cmd }} 49 | -------------------------------------------------------------------------------- /tests/primary_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::helpers::unhexify; 2 | use bp7::{Bundle, primary}; 3 | use std::time::Duration; 4 | 5 | #[test] 6 | fn test_lifetime() { 7 | let p1 = primary::new_primary_block( 8 | "dtn://node1/", 9 | "dtn://node2/", 10 | bp7::dtntime::CreationTimestamp::now(), 11 | Duration::from_secs(10), 12 | ); 13 | assert!(!p1.is_lifetime_exceeded()); 14 | 15 | let p2 = primary::new_primary_block( 16 | "dtn://node1/", 17 | "dtn://node2/", 18 | bp7::dtntime::CreationTimestamp::with_time_and_seq(0, 0), 19 | Duration::from_secs(10), 20 | ); 21 | assert!(!p2.is_lifetime_exceeded()); 22 | 23 | let p2 = primary::new_primary_block( 24 | "dtn://node1/", 25 | "dtn://node2/", 26 | bp7::dtntime::CreationTimestamp::with_time_and_seq(1, 0), 27 | Duration::from_secs(10), 28 | ); 29 | assert!(p2.is_lifetime_exceeded()); 30 | } 31 | 32 | #[test] 33 | fn test_ipn_accept() { 34 | let hex_bundle = "9f88070000820282020182028201018202820001821b00ff00bb0e20b4ea001a000927c08507020100410085010100004d48656c6f2c20576f726c642142ff"; 35 | let bytes = unhexify(hex_bundle); 36 | 37 | let bundle = Bundle::try_from(bytes.clone().unwrap().as_slice()).expect("CBOR decode"); 38 | assert!( 39 | bundle.validate().is_ok(), 40 | "ipn:0. should get accepted and treated as ipn:0.0" 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # takes the tag as an argument (e.g. v0.1.0) 4 | if [ -n "$1" ]; then 5 | # update the version 6 | msg="# managed by release.sh" 7 | 8 | # on macos use gsed instead of set 9 | if [ "$(uname)" == "Darwin" ]; then 10 | sed="gsed" 11 | else 12 | sed="sed" 13 | fi 14 | $sed "s/^version = .* $msg$/version = \"${1#v}\" $msg/" -i Cargo.toml 15 | 16 | # update the changelog 17 | git cliff --tag "$1" > CHANGELOG.md 18 | git add -A && git commit -m "chore(release): prepare for $1" 19 | git show 20 | # generate a changelog for the tag message 21 | export TEMPLATE="\ 22 | {% for group, commits in commits | group_by(attribute=\"group\") %} 23 | {{ group | upper_first }}\ 24 | {% for commit in commits %} 25 | - {{ commit.message | upper_first }} ({{ commit.id | truncate(length=7, end=\"\") }})\ 26 | {% endfor %} 27 | {% endfor %}" 28 | changelog=$(git cliff --unreleased --strip all) 29 | 30 | git tag -a "$1" -m "Release $1" -m "$changelog" 31 | read -p "Directly push to GitHub? " -n 1 -r 32 | echo # (optional) move to a new line 33 | if [[ $REPLY =~ ^[Yy]$ ]] 34 | then 35 | git push 36 | git push origin "$1" 37 | else 38 | echo "Don't forget to push tag to origin: " 39 | echo git push 40 | echo git push origin "$1" 41 | fi 42 | else 43 | echo Changes since last release: 44 | git cliff --unreleased --strip all 45 | echo 46 | echo 47 | echo "warn: please provide a tag" 48 | fi -------------------------------------------------------------------------------- /examples/benchmark.rs: -------------------------------------------------------------------------------- 1 | use bp7::{crc, helpers::*}; 2 | 3 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 4 | use web_sys::console; 5 | 6 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 7 | thread_local! { 8 | static PRINT_BUFFER: std::cell::RefCell = std::cell::RefCell::new(String::new()); 9 | } 10 | 11 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 12 | macro_rules! print { 13 | ($($tt:tt)*) => {{ 14 | let msg = format!($($tt)*); 15 | PRINT_BUFFER.with(|buffer| { 16 | buffer.borrow_mut().push_str(&msg); 17 | }); 18 | }} 19 | } 20 | 21 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 22 | macro_rules! println { 23 | ($($tt:tt)*) => {{ 24 | let msg = format!($($tt)*); 25 | PRINT_BUFFER.with(|buffer| { 26 | let mut buf = buffer.borrow_mut(); 27 | buf.push_str(&msg); 28 | console::log_1(&wasm_bindgen::JsValue::from_str(&buf)); 29 | buf.clear(); 30 | }); 31 | }} 32 | } 33 | 34 | const RUNS: i64 = 100_000; 35 | 36 | fn main() { 37 | let crcno = bench_bundle_create(RUNS, crc::CRC_NO); 38 | let crc16 = bench_bundle_create(RUNS, crc::CRC_16); 39 | let crc32 = bench_bundle_create(RUNS, crc::CRC_32); 40 | 41 | //print!("{:x?}", crcno[0]); 42 | //println!("{}", bp7::hexify(&crcno[0])); 43 | 44 | bench_bundle_encode(RUNS, crc::CRC_NO); 45 | bench_bundle_encode(RUNS, crc::CRC_16); 46 | bench_bundle_encode(RUNS, crc::CRC_32); 47 | 48 | bench_bundle_load(RUNS, crc::CRC_NO, crcno); 49 | bench_bundle_load(RUNS, crc::CRC_16, crc16); 50 | bench_bundle_load(RUNS, crc::CRC_32, crc32); 51 | 52 | //dbg!(crcno[0].len()); 53 | //dbg!(crc16[0].len()); 54 | //dbg!(crc32[0].len()); 55 | } 56 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # configuration file for git-cliff (0.1.0) 2 | 3 | [changelog] 4 | # changelog header 5 | header = """ 6 | # Changelog 7 | All notable changes to this project will be documented in this file.\n 8 | """ 9 | # template for the changelog body 10 | # https://tera.netlify.app/docs/#introduction 11 | body = """ 12 | {% if version %}\ 13 | ## [{{ version | replace(from="v", to="") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 14 | {% else %}\ 15 | ## [unreleased] 16 | {% endif %}\ 17 | {% for group, commits in commits | group_by(attribute="group") %} 18 | ### {{ group | upper_first }} 19 | {% for commit in commits %} 20 | - {{ commit.message | upper_first }}\ 21 | {% endfor %} 22 | {% endfor %}\n 23 | """ 24 | # remove the leading and trailing whitespaces from the template 25 | trim = true 26 | # changelog footer 27 | footer = """ 28 | 29 | """ 30 | 31 | [git] 32 | # allow only conventional commits 33 | # https://www.conventionalcommits.org 34 | conventional_commits = true 35 | # regex for parsing and grouping commits 36 | commit_parsers = [ 37 | { message = "^feat*", group = "Features"}, 38 | { message = "^fix*", group = "Bug Fixes"}, 39 | { message = "^doc*", group = "Documentation"}, 40 | { message = "^perf*", group = "Performance"}, 41 | { message = "^refactor*", group = "Refactor"}, 42 | { message = "^style*", group = "Styling"}, 43 | { message = "^test*", group = "Testing"}, 44 | { message = "^chore\\(release\\): prepare for*", skip = true}, 45 | { message = "^chore*", group = "Miscellaneous Tasks"}, 46 | { body = ".*security", group = "Security"}, 47 | ] 48 | # filter out the commits that are not matched by commit parsers 49 | filter_commits = false 50 | # glob pattern for matching git tags 51 | tag_pattern = "v[0-9]*" 52 | # regex for skipping tags 53 | skip_tags = "v0.1.0-beta.1" 54 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bp7" 3 | version = "0.10.7" # managed by release.sh 4 | authors = ["Lars Baumgaertner "] 5 | edition = "2024" 6 | description = "Rust implementation of dtn Bundle Protocol Version 7 ([RFC 9171]" 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/dtn7/bp7-rs" 9 | categories = ["encoding"] 10 | readme = "README.md" 11 | 12 | [profile.release] 13 | #debug = true 14 | lto = true 15 | 16 | [[bin]] 17 | name = "bp7" 18 | required-features = ["binary-build"] 19 | 20 | [lib] 21 | name = "bp7" 22 | crate-type = ["staticlib", "cdylib", "rlib"] 23 | 24 | [dev-dependencies] 25 | # criterion enables rayon by default; rayon is incompatible with wasm32, so we disable default features 26 | criterion = { version = "0.7.0", default-features = false, features = [ 27 | "cargo_bench_support", 28 | "plotters", 29 | ] } 30 | test-case = "3.3.1" 31 | 32 | [[bench]] 33 | name = "benchmark" 34 | harness = false 35 | 36 | [features] 37 | 38 | default = ["binary-build"] 39 | binary-build = ["wasm-js"] 40 | wasm-js = ["nanorand/getrandom"] 41 | bpsec = ["dep:sha2", "dep:hmac"] 42 | 43 | [dependencies] 44 | humantime = "2.2.0" 45 | serde = { version = "1.0.219", features = ["derive"] } 46 | serde_cbor = "0.11.2" 47 | serde_json = "1.0.143" 48 | serde_bytes = "0.11.17" 49 | crc = "3.3.0" 50 | thiserror = "2.0.16" 51 | bitflags = "2.9.4" 52 | web-time = "1.1.0" 53 | nanorand = "0.8.0" 54 | 55 | # bpsec dependencies 56 | sha2 = { version = "0.10.9", optional = true } 57 | hmac = { version = "0.12.1", optional = true } 58 | 59 | # wasm dependencies 60 | [target.'cfg(target_arch = "wasm32")'.dependencies] 61 | wasm-bindgen = { version = "0.2.100", features = ["serde-serialize"] } 62 | serde-wasm-bindgen = "0.6.5" 63 | web-sys = { version = "0.3.77", features = ["console"] } 64 | -------------------------------------------------------------------------------- /examples/bp7kit/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 5px; 3 | } 4 | 5 | .pure-g { 6 | margin: 5px; 7 | } 8 | .pure-u { 9 | margin-top: 5px; 10 | margin-bottom: 5px; 11 | } 12 | 13 | .pure-button { 14 | margin: 2px; 15 | } 16 | 17 | .pure-input { 18 | margin: 2px; 19 | } 20 | 21 | 22 | .collabsible-checkbox { 23 | display: none; 24 | } 25 | 26 | .lbl-toggle { 27 | display: block; 28 | 29 | font-weight: bold; 30 | font-family: monospace; 31 | font-size: 1.2rem; 32 | text-transform: uppercase; 33 | text-align: center; 34 | 35 | padding: 1rem; 36 | 37 | color: #A77B0E; 38 | background: #FAE042; 39 | 40 | cursor: pointer; 41 | 42 | border-radius: 7px; 43 | transition: all 0.25s ease-out; 44 | } 45 | 46 | .lbl-toggle:hover { 47 | color: #7C5A0B; 48 | } 49 | 50 | .lbl-toggle::before { 51 | content: ' '; 52 | display: inline-block; 53 | 54 | border-top: 5px solid transparent; 55 | border-bottom: 5px solid transparent; 56 | border-left: 5px solid currentColor; 57 | 58 | vertical-align: middle; 59 | margin-right: .7rem; 60 | transform: translateY(-2px); 61 | 62 | transition: transform .2s ease-out; 63 | } 64 | 65 | .collapsible-content .content-inner { 66 | background: rgba(250, 224, 66, .2); 67 | border-bottom: 1px solid rgba(250, 224, 66, .45); 68 | 69 | border-bottom-left-radius: 7px; 70 | border-bottom-right-radius: 7px; 71 | padding: .5rem 1rem; 72 | 73 | } 74 | 75 | .collapsible-content { 76 | max-height: 0px; 77 | overflow: hidden; 78 | 79 | transition: max-height .25s ease-in-out; 80 | } 81 | 82 | .toggle:checked + .lbl-toggle + .collapsible-content { 83 | max-height: 8000px; 84 | } 85 | 86 | .toggle:checked + .lbl-toggle::before { 87 | transform: rotate(90deg) translateX(-3px); 88 | } 89 | 90 | .toggle:checked + .lbl-toggle { 91 | border-bottom-right-radius: 0; 92 | border-bottom-left-radius: 0; 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Rust implementation of dtn Bundle Protocol Version 7 ([RFC 9171](https://datatracker.ietf.org/doc/rfc9171/)) 2 | 3 | //! 4 | //! # Examples 5 | //! 6 | //! ``` 7 | //! use bp7::{bundle, canonical, crc, dtntime, eid, primary, flags::BundleControlFlags, flags::BlockControlFlags}; 8 | //! use std::time::Duration; 9 | //! 10 | //! let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 11 | //! let src = eid::EndpointID::with_dtn("node1/123456").unwrap(); 12 | //! //let now = dtntime::CreationTimestamp::now(); 13 | //! let day0 = dtntime::CreationTimestamp::with_time_and_seq(dtntime::DTN_TIME_EPOCH, 0); 14 | //! let pblock = primary::PrimaryBlockBuilder::default() 15 | //! .bundle_control_flags( 16 | //! (BundleControlFlags::BUNDLE_MUST_NOT_FRAGMENTED | BundleControlFlags::BUNDLE_STATUS_REQUEST_DELIVERY).bits(), 17 | //! ) 18 | //! .destination(dst) 19 | //! .source(src.clone()) 20 | //! .report_to(src) 21 | //! .creation_timestamp(day0) 22 | //! .lifetime(Duration::from_secs(60 * 60)) 23 | //! .build() 24 | //! .unwrap(); 25 | //! let mut b = bundle::BundleBuilder::default() 26 | //! .primary(pblock) 27 | //! .canonicals(vec![canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec())]) 28 | //! .build() 29 | //! .unwrap(); 30 | //! b.set_crc(crc::CRC_16); 31 | //! let serialized = b.to_cbor(); 32 | //! let binary_bundle = [159, 137, 7, 26, 0, 2, 0, 4, 1, 130, 1, 109, 47, 47, 110, 111, 100, 101, 50, 33 | //! 47, 105, 110, 98, 111, 120, 130, 1, 110, 47, 47, 110, 111, 100, 101, 49, 47, 49, 50, 51, 52, 34 | //! 53, 54, 130, 1, 110, 47, 47, 110, 111, 100, 101, 49, 47, 49, 50, 51, 52, 53, 54, 130, 0, 0, 35 | //! 26, 0, 54, 238, 128, 66, 188, 152, 134, 1, 1, 0, 1, 67, 65, 66, 67, 66, 15, 86, 255]; 36 | //! assert_eq!(&binary_bundle[..], &serialized[..]); 37 | //! ``` 38 | //! 39 | //! 40 | 41 | pub mod administrative_record; 42 | pub mod bundle; 43 | pub mod canonical; 44 | pub mod crc; 45 | pub mod dtntime; 46 | pub mod eid; 47 | pub mod error; 48 | pub mod ffi; 49 | pub mod flags; 50 | pub mod helpers; 51 | pub mod primary; 52 | #[cfg(feature = "bpsec")] 53 | pub mod security; 54 | 55 | #[cfg(target_arch = "wasm32")] 56 | pub mod wasm; 57 | 58 | pub use bundle::{Bundle, ByteBuffer}; 59 | pub use canonical::*; 60 | pub use dtntime::{CreationTimestamp, DtnTime, dtn_time_now}; 61 | pub use eid::EndpointID; 62 | pub use helpers::hexify; 63 | 64 | #[cfg(target_arch = "wasm32")] 65 | pub use wasm::*; 66 | -------------------------------------------------------------------------------- /src/dtntime.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::sync::atomic::{AtomicUsize, Ordering}; 3 | use core::time::Duration; 4 | use humantime::format_rfc3339; 5 | use serde::{Deserialize, Serialize}; 6 | use std::time::UNIX_EPOCH; 7 | 8 | /// Time since the year 2k in milliseconds 9 | pub type DtnTime = u64; 10 | 11 | pub const SECONDS1970_TO2K: u64 = 946_684_800; 12 | const MS1970_TO2K: u64 = 946_684_800_000; 13 | 14 | pub const DTN_TIME_EPOCH: DtnTime = 0; 15 | 16 | pub trait DtnTimeHelpers { 17 | fn unix(self) -> u64; 18 | fn string(self) -> String; 19 | } 20 | 21 | impl DtnTimeHelpers for DtnTime { 22 | /// Convert to unix timestamp (in seconds). 23 | fn unix(self) -> u64 { 24 | (self + MS1970_TO2K) / 1000 25 | } 26 | 27 | /// Convert to human readable rfc3339 compliant time string. 28 | fn string(self) -> String { 29 | let d = UNIX_EPOCH + Duration::from_millis(self + MS1970_TO2K); 30 | format_rfc3339(d).to_string() 31 | } 32 | } 33 | 34 | /// Get current time as DtnTime timestamp 35 | pub fn dtn_time_now() -> DtnTime { 36 | crate::helpers::ts_ms() - MS1970_TO2K 37 | } 38 | 39 | /// Timestamp when a bundle was created, consisting of the DtnTime and a sequence number. 40 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] // hacked struct as tuple because bug in serialize_tuple 41 | pub struct CreationTimestamp(DtnTime, u64); 42 | 43 | impl fmt::Display for CreationTimestamp { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | write!(f, "{} {}", self.0.string(), self.1) 46 | } 47 | } 48 | 49 | impl CreationTimestamp { 50 | pub fn new() -> CreationTimestamp { 51 | Default::default() 52 | } 53 | pub fn with_time_and_seq(t: DtnTime, seqno: u64) -> CreationTimestamp { 54 | CreationTimestamp(t, seqno) 55 | } 56 | pub fn seqno(&self) -> u64 { 57 | self.1 58 | } 59 | pub fn dtntime(&self) -> DtnTime { 60 | self.0 61 | } 62 | /// Create a new timestamp with automatic sequence counting 63 | /// 64 | /// # Example 65 | /// ``` 66 | /// use bp7::dtntime::*; 67 | /// use std::{thread, time}; 68 | /// 69 | /// let time1 = CreationTimestamp::now(); 70 | /// let time2 = CreationTimestamp::now(); 71 | /// 72 | /// assert_eq!(time1.dtntime(), time2.dtntime()); 73 | /// assert_ne!(time1.seqno(), time2.seqno()); 74 | /// 75 | /// thread::sleep(time::Duration::from_secs(1)); 76 | /// let time3 = CreationTimestamp::now(); 77 | /// assert_eq!(time3.seqno(), 0); 78 | /// ``` 79 | pub fn now() -> CreationTimestamp { 80 | static LAST_CREATION_TIMESTAMP: AtomicUsize = AtomicUsize::new(0); 81 | static LAST_CREATION_SEQ: AtomicUsize = AtomicUsize::new(0); 82 | let now = dtn_time_now(); 83 | if now != LAST_CREATION_TIMESTAMP.swap(now as usize, Ordering::Relaxed) as u64 { 84 | LAST_CREATION_SEQ.store(0, Ordering::SeqCst) 85 | } 86 | let seq = LAST_CREATION_SEQ.fetch_add(1, Ordering::SeqCst); 87 | 88 | CreationTimestamp::with_time_and_seq(now, seq as u64) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /cbindgen.toml: -------------------------------------------------------------------------------- 1 | # This is a template cbindgen.toml file with all of the default values. 2 | # Some values are commented out because their absence is the real default. 3 | # 4 | # See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml 5 | # for detailed documentation of every option here. 6 | 7 | 8 | 9 | language = "C" 10 | 11 | 12 | 13 | ############## Options for Wrapping the Contents of the Header ################# 14 | 15 | # header = "/* Text to put at the beginning of the generated file. Probably a license. */" 16 | # trailer = "/* Text to put at the end of the generated file */" 17 | # include_guard = "my_bindings_h" 18 | # pragma_once = true 19 | # autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 20 | include_version = false 21 | # namespace = "my_namespace" 22 | namespaces = [] 23 | using_namespaces = [] 24 | sys_includes = [] 25 | includes = [] 26 | no_includes = false 27 | after_includes = "" 28 | 29 | 30 | 31 | 32 | ############################ Code Style Options ################################ 33 | 34 | braces = "SameLine" 35 | line_length = 100 36 | tab_width = 2 37 | documentation = true 38 | documentation_style = "auto" 39 | line_endings = "LF" # also "CR", "CRLF", "Native" 40 | 41 | 42 | 43 | 44 | ############################# Codegen Options ################################## 45 | 46 | style = "both" 47 | sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` 48 | usize_is_size_t = true 49 | 50 | 51 | 52 | [defines] 53 | # "target_os = freebsd" = "DEFINE_FREEBSD" 54 | # "feature = serde" = "DEFINE_SERDE" 55 | 56 | 57 | 58 | [export] 59 | include = [] 60 | exclude = [] 61 | # prefix = "CAPI_" 62 | item_types = [] 63 | renaming_overrides_prefixing = false 64 | 65 | 66 | 67 | [export.rename] 68 | 69 | 70 | 71 | [export.body] 72 | 73 | 74 | [export.mangle] 75 | 76 | 77 | [fn] 78 | rename_args = "None" 79 | # must_use = "MUST_USE_FUNC" 80 | # no_return = "NO_RETURN" 81 | # prefix = "START_FUNC" 82 | # postfix = "END_FUNC" 83 | args = "auto" 84 | sort_by = "Name" 85 | 86 | 87 | 88 | 89 | [struct] 90 | rename_fields = "None" 91 | # must_use = "MUST_USE_STRUCT" 92 | derive_constructor = false 93 | derive_eq = false 94 | derive_neq = false 95 | derive_lt = false 96 | derive_lte = false 97 | derive_gt = false 98 | derive_gte = false 99 | 100 | 101 | 102 | 103 | [enum] 104 | rename_variants = "None" 105 | # must_use = "MUST_USE_ENUM" 106 | add_sentinel = false 107 | prefix_with_name = false 108 | derive_helper_methods = false 109 | derive_const_casts = false 110 | derive_mut_casts = false 111 | # cast_assert_name = "ASSERT" 112 | derive_tagged_enum_destructor = false 113 | derive_tagged_enum_copy_constructor = false 114 | enum_class = true 115 | private_default_tagged_enum_constructor = false 116 | 117 | 118 | 119 | 120 | [const] 121 | allow_static_const = true 122 | allow_constexpr = false 123 | sort_by = "Name" 124 | 125 | 126 | 127 | 128 | [macro_expansion] 129 | bitflags = false 130 | 131 | 132 | 133 | 134 | 135 | 136 | ############## Options for How Your Rust library Should Be Parsed ############## 137 | 138 | [parse] 139 | parse_deps = false 140 | include = [] 141 | exclude = [] 142 | clean = false 143 | extra_bindings = [] 144 | 145 | 146 | 147 | [parse.expand] 148 | crates = [ ] 149 | all_features = false 150 | default_features = true 151 | features = [] -------------------------------------------------------------------------------- /tests/administrative_record_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::administrative_record::*; 2 | use bp7::flags::*; 3 | use bp7::*; 4 | use std::convert::{TryFrom, TryInto}; 5 | use std::time::Duration; 6 | 7 | fn new_complete_bundle(crc_type: bp7::crc::CrcRawType) -> Bundle { 8 | let dst = eid::EndpointID::try_from("dtn://node2/inbox").unwrap(); 9 | let src = eid::EndpointID::try_from("dtn://node1/123456").unwrap(); 10 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 11 | 12 | let pblock = primary::PrimaryBlockBuilder::default() 13 | .destination(dst) 14 | .source(src.clone()) 15 | .report_to(src) 16 | .creation_timestamp(now) 17 | .lifetime(Duration::from_secs(60 * 60)) 18 | .build() 19 | .unwrap(); 20 | let mut b = bundle::BundleBuilder::default() 21 | .primary(pblock) 22 | .canonicals(vec![ 23 | canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()), 24 | canonical::new_bundle_age_block( 25 | 2, // block number 26 | BlockControlFlags::empty(), // flags 27 | 0, // time elapsed 28 | ), 29 | canonical::new_hop_count_block( 30 | 3, // block number 31 | BlockControlFlags::empty(), // flags 32 | 16, // max hops 33 | ), 34 | canonical::new_previous_node_block( 35 | 4, // block number 36 | BlockControlFlags::empty(), // flags 37 | "dtn://node23".try_into().unwrap(), // previous node EID 38 | ), 39 | ]) 40 | .build() 41 | .unwrap(); 42 | b.set_crc(crc_type); 43 | b.calculate_crc(); 44 | assert!(b.validate().is_ok()); 45 | b 46 | } 47 | 48 | #[test] 49 | fn status_report_tests() { 50 | use bp7::flags::*; 51 | let mut bndl = new_complete_bundle(crc::CRC_NO); 52 | let mut flags = bndl.primary.bundle_control_flags.flags(); 53 | flags |= bp7::flags::BundleControlFlags::BUNDLE_STATUS_REQUEST_DELETION; 54 | bndl.primary.bundle_control_flags = flags.bits(); 55 | assert!(!bndl.is_administrative_record()); 56 | 57 | let sr1 = dbg!(new_status_report(&bndl, DELETED_BUNDLE, LIFETIME_EXPIRED,)); 58 | 59 | let expected_refbundle = format!( 60 | "dtn://node1/123456-{}-0", 61 | bndl.primary.creation_timestamp.dtntime() 62 | ); 63 | assert_eq!(sr1.refbundle(), expected_refbundle); 64 | 65 | let encoded_sr1 = serde_cbor::to_vec(&sr1).unwrap(); 66 | 67 | let sr1_dec: StatusReport = serde_cbor::from_slice(&encoded_sr1).unwrap(); 68 | 69 | assert_eq!(sr1, sr1_dec); 70 | 71 | let mut bndl = new_complete_bundle(crc::CRC_NO); 72 | let mut flags = bndl.primary.bundle_control_flags.flags(); 73 | flags |= BundleControlFlags::BUNDLE_STATUS_REQUEST_DELETION; 74 | flags |= BundleControlFlags::BUNDLE_REQUEST_STATUS_TIME; 75 | bndl.primary.bundle_control_flags = flags.bits(); 76 | 77 | let sr2 = dbg!(new_status_report(&bndl, DELETED_BUNDLE, LIFETIME_EXPIRED)); 78 | 79 | let encoded_sr2 = serde_cbor::to_vec(&sr2).unwrap(); 80 | 81 | let sr2_dec: StatusReport = serde_cbor::from_slice(&encoded_sr2).unwrap(); 82 | 83 | assert_eq!(sr2, sr2_dec); 84 | 85 | let mut bndl = new_complete_bundle(crc::CRC_NO); 86 | bndl.primary.bundle_control_flags = 87 | BundleControlFlags::BUNDLE_ADMINISTRATIVE_RECORD_PAYLOAD.bits(); 88 | assert!(bndl.is_administrative_record()); // actually not true since no payload block has been added 89 | } 90 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [0.10.7] - 2025-06-01 5 | 6 | ### Bug Fixes 7 | 8 | - Fixed release.sh to also work on macos and use gnu sed 9 | - Cleanup of bpsec tests, removed hex-literal crate 10 | - Checking if the payload block is the last canonical block in the bundle. fixes #5 11 | - Eid decoder now complains if dtn:none is encoded with a wrong integer 12 | 13 | ### Features 14 | 15 | - Added initial BP security module (optional feature): With Block Integrity Block defined in RFC 9172 and a test case defined in RFC 9173 (#3) 16 | - Bp7 cli tool now also validates decoded bundles and outputs potential errors. validate() now also checks crc (beware: internal clone for mutability) 17 | 18 | ### Miscellaneous Tasks 19 | 20 | - Eid imports cleanup 21 | - Updated various dependencies and pleased clippy 22 | 23 | ### Testing 24 | 25 | - Fixed tests for correct payload position in canonical block list 26 | 27 | ## [0.10.6] - 2023-11-24 28 | 29 | ### Bug Fixes 30 | 31 | - Calling node_id() on an IPN eid now returns correct URL instead of an dtn scheme formatted IPN address 32 | 33 | ### Documentation 34 | 35 | - Added matrix badge to dtn7 space in README 36 | 37 | ### Miscellaneous Tasks 38 | 39 | - Switched to most recent test-case crate, making "allow_result" feature obsolete 40 | - Upgraded dependencies 41 | - Pleased clippy 42 | - Updated dependencies 43 | 44 | ### Refactor 45 | 46 | - Replaced push_str with write in hexify helper, one less allocation 47 | 48 | ## [0.10.5] - 2022-02-10 49 | 50 | ### Features 51 | 52 | - Load bundles from slices, no need for an owned copy 53 | 54 | ## [0.10.4] - 2022-02-07 55 | 56 | ### Bug Fixes 57 | 58 | - Removed leftover dbg!() in canonical block deserializer 59 | - Workaround for bug in upstream test-case crate (v1.2.2) 60 | 61 | ### Refactor 62 | 63 | - Use println! instead of dbg! in CLI for printing decoded bundles 64 | 65 | ### Testing 66 | 67 | - Added tests for bundle ID and bundle ToString functionality 68 | 69 | ## [0.10.2] - 2022-02-05 70 | 71 | ### Bug Fixes 72 | 73 | - Fixed a bug where payload data was double encoded 74 | 75 | ## [0.10.1] - 2022-02-04 76 | 77 | ### Bug Fixes 78 | 79 | - With disabled default features the benchmark helpers did not work anymore. now they have the feature 'benchmark-helpers' 80 | 81 | ## [0.10.0] - 2022-02-04 82 | 83 | ### Bug Fixes 84 | 85 | - Explictly drop CString references in bundle_metadata_free of ffi 86 | - DtnAdress::new now adds '//' before node name 87 | - Enforce trailing slash for endpoint IDs that are the node ID 88 | 89 | ## [0.9.3] - 2022-02-03 90 | 91 | ### Bug Fixes 92 | 93 | - Validation now rejects bundles without payload 94 | - Fixed build script of ffi example, adding -lm flag 95 | - Marked extern C functions which can lead to UB as unsafe #2 96 | 97 | ### Documentation 98 | 99 | - Updated all documentation to point to rfc 9171 instead of the draft 100 | 101 | ### Miscellaneous Tasks 102 | 103 | - Updated flags and dtn URI parsing be in line with RFC 9171 104 | 105 | ## [0.9.2] - 2021-09-10 106 | 107 | ### Bug Fixes 108 | 109 | - Require a payload block in a new bundle as described in Bundle Protocol Draft 110 | - Changed unwraps into proper error handling 111 | 112 | ### Refactor 113 | 114 | - Using bitflags for bundle and block control flags 115 | - Eliminated derive_builder, added manual implementations 116 | 117 | ### Styling 118 | 119 | - Pleased clippy in builder 120 | 121 | ### Build 122 | 123 | - Updated Cargo.toml to be managed by release.sh 124 | 125 | ## [0.9.1] - 2021-09-09 126 | 127 | ### Refactor 128 | 129 | - Eliminated derive_builder, added manual implementations 130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/encoding_dump.rs: -------------------------------------------------------------------------------- 1 | use bp7::crc::CrcBlock; 2 | use bp7::flags::BlockControlFlags; 3 | use bp7::{ 4 | EndpointID, canonical, crc, 5 | helpers::{ser_dump, vec_dump}, 6 | primary, 7 | }; 8 | use std::convert::TryInto; 9 | 10 | fn main() { 11 | // Endpoint ID 12 | let hr = "dtn://node1/test"; 13 | let eid: EndpointID = hr.try_into().unwrap(); 14 | ser_dump(&eid, hr); 15 | 16 | let hr = "ipn:23.42"; 17 | let eid: EndpointID = hr.try_into().unwrap(); 18 | ser_dump(&eid, hr); 19 | 20 | let eid = EndpointID::none(); 21 | ser_dump(&eid, &eid.to_string()); 22 | 23 | // Creation Timestamp 24 | 25 | let ts = bp7::CreationTimestamp::now(); 26 | ser_dump(&ts, &ts.to_string()); 27 | 28 | // Canonical Blocks 29 | 30 | let cblock = canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()); 31 | ser_dump( 32 | &cblock, 33 | "payload block with no flags and `'ABC'` as content, no crc", 34 | ); 35 | 36 | let cblock = canonical::new_hop_count_block(1, BlockControlFlags::empty(), 32); 37 | ser_dump( 38 | &cblock, 39 | "hop count block with no flags, block number 1 and hop limit = 32, no crc", 40 | ); 41 | 42 | let cblock = canonical::new_bundle_age_block(2, BlockControlFlags::empty(), 1234); 43 | ser_dump( 44 | &cblock, 45 | "bundle age block with no flags, block number 2 and age = 1234us, no crc", 46 | ); 47 | 48 | let cblock = canonical::new_previous_node_block( 49 | 3, 50 | BlockControlFlags::empty(), 51 | "dtn://n1".try_into().unwrap(), 52 | ); 53 | ser_dump( 54 | &cblock, 55 | "previous node block with no flags, block number 3 and prev_node = `dtn://n1`, no crc", 56 | ); 57 | 58 | // Primary block 59 | 60 | let pblock = primary::new_primary_block( 61 | "dtn://n2/inbox", 62 | "dtn://n1/", 63 | bp7::CreationTimestamp::with_time_and_seq(2342, 2), 64 | std::time::Duration::from_secs(60), 65 | ); 66 | ser_dump( 67 | &pblock, 68 | "primary block with no flags, no fragmentation, lifetime of `60s`, creation timestamp `[2342, 2]` from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, no crc", 69 | ); 70 | 71 | let mut pblock = primary::new_primary_block( 72 | "dtn://n2/inbox", 73 | "dtn://n1/", 74 | bp7::CreationTimestamp::with_time_and_seq(2342, 2), 75 | std::time::Duration::from_secs(60), 76 | ); 77 | pblock.set_crc_type(crc::CRC_16); 78 | pblock.update_crc(); 79 | ser_dump( 80 | &pblock, 81 | "primary block with no flags, no fragmentation, lifetime of `60s`, creation timestamp `[2342, 2]` from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, crc16", 82 | ); 83 | 84 | let mut pblock = primary::new_primary_block( 85 | "dtn://n2/inbox", 86 | "dtn://n1/", 87 | bp7::CreationTimestamp::with_time_and_seq(2342, 2), 88 | std::time::Duration::from_secs(60), 89 | ); 90 | pblock.set_crc_type(crc::CRC_32); 91 | pblock.update_crc(); 92 | ser_dump( 93 | &pblock, 94 | "primary block with no flags, no fragmentation, lifetime of `60s`, creation timestamp `[2342, 2]` from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, crc32", 95 | ); 96 | 97 | // Complete Bundle 98 | 99 | let mut bndl = bp7::bundle::new_std_payload_bundle( 100 | "dtn://n1/".try_into().unwrap(), 101 | "dtn://n2/inbox".try_into().unwrap(), 102 | b"ABC".to_vec(), 103 | ); 104 | vec_dump( 105 | &bndl.clone(), 106 | bndl.to_cbor(), 107 | "bundle with no flags, no fragmentation, lifetime of `60 * 60s`, creation timestamp now from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, payload `'ABC'` and hop count block with 32 hop limit, no crc", 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | generate-changelog: 11 | name: Generate changelog 12 | runs-on: ubuntu-latest 13 | outputs: 14 | release_body: ${{ steps.release.outputs.release_body }} 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@main 18 | with: 19 | fetch-depth: 0 20 | - name: Generate a changelog 21 | uses: orhun/git-cliff-action@v1 22 | id: git-cliff 23 | with: 24 | config: cliff.toml 25 | args: -vv --latest --strip header 26 | env: 27 | OUTPUT: CHANGES.md 28 | - name: Set the release body 29 | id: release 30 | shell: bash 31 | run: | 32 | r=$(cat ${{ steps.git-cliff.outputs.changelog }}) 33 | r="$(printf "$r" | tail -n +3)" 34 | r="${r//'%'/'%25'}" 35 | r="${r//$'\n'/'%0A'}" 36 | r="${r//$'\r'/'%0D'}" 37 | echo "::set-output name=release_body::$r" 38 | publish-github: 39 | name: Publish on GitHub 40 | needs: generate-changelog 41 | runs-on: ${{ matrix.os }} 42 | strategy: 43 | matrix: 44 | build: [linux-gnu, linux-musl, macos] 45 | include: 46 | - BUILD: linux-gnu 47 | OS: ubuntu-latest 48 | TOOLCHAIN: stable 49 | TARGET: x86_64-unknown-linux-gnu 50 | - BUILD: linux-musl 51 | OS: ubuntu-latest 52 | TOOLCHAIN: stable 53 | TARGET: x86_64-unknown-linux-musl 54 | - BUILD: macos 55 | OS: macos-latest 56 | TOOLCHAIN: stable 57 | TARGET: x86_64-apple-darwin 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@main 61 | - name: Set the release version 62 | shell: bash 63 | run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 64 | - name: Install musl-tools 65 | if: matrix.TARGET == 'x86_64-unknown-linux-musl' 66 | run: | 67 | sudo apt-get update 68 | sudo apt-get install -y --no-install-recommends \ 69 | --allow-unauthenticated musl-tools 70 | - name: Install Rust toolchain 71 | uses: actions-rs/toolchain@v1 72 | with: 73 | toolchain: ${{ matrix.TOOLCHAIN }} 74 | target: ${{ matrix.TARGET }} 75 | override: true 76 | - name: Build 77 | run: cargo build --release --target ${{ matrix.TARGET }} 78 | - name: Prepare release assets 79 | shell: bash 80 | run: | 81 | mkdir -p release/bin 82 | cp {LICENSE*,COPYRIGHT,README.md,CHANGELOG.md} release/ 83 | cp -r doc release/doc 84 | cp target/${{ matrix.TARGET }}/release/bp7 release/ 85 | mv release/ bp7-${{ env.RELEASE_VERSION }}/ 86 | - name: Create release artifacts 87 | shell: bash 88 | run: | 89 | tar -czvf bp7-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz \ 90 | bp7-${{ env.RELEASE_VERSION }}/ 91 | shasum -a 512 bp7-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz \ 92 | > bp7-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}.tar.gz.sha512 93 | 94 | - name: Upload the release 95 | uses: svenstaro/upload-release-action@v2 96 | with: 97 | repo_token: ${{ secrets.GITHUB_TOKEN }} 98 | file: bp7-${{ env.RELEASE_VERSION }}-${{ matrix.TARGET }}* 99 | file_glob: true 100 | overwrite: true 101 | tag: ${{ github.ref }} 102 | release_name: "Release v${{ env.RELEASE_VERSION }}" 103 | body: "${{ needs.generate-changelog.outputs.release_body }}" 104 | publish-crates-io: 105 | name: Publish on crates.io 106 | needs: publish-github 107 | runs-on: ubuntu-latest 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@main 111 | - name: Set the release version 112 | run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 113 | - name: Publish the crate 114 | uses: actions-rs/cargo@v1 115 | with: 116 | command: publish 117 | args: | 118 | --allow-dirty --manifest-path Cargo.toml 119 | --locked --token ${{ secrets.CARGO_TOKEN }} -------------------------------------------------------------------------------- /benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | use criterion::Criterion; 5 | use std::convert::TryFrom; 6 | 7 | use bp7::{ 8 | Bundle, ByteBuffer, bundle, canonical, crc, dtntime, eid, flags::BlockControlFlags, primary, 9 | }; 10 | 11 | fn bench_bundle_create(crc_type: crc::CrcRawType) -> ByteBuffer { 12 | let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 13 | let src = eid::EndpointID::with_dtn("node1/123456").unwrap(); 14 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 15 | //let day0 = dtntime::CreationTimestamp::with_time_and_seq(dtntime::DTN_TIME_EPOCH, 0);; 16 | 17 | let pblock = primary::PrimaryBlockBuilder::default() 18 | .destination(dst) 19 | .source(src.clone()) 20 | .report_to(src) 21 | .creation_timestamp(now) 22 | .lifetime(std::time::Duration::from_secs(60 * 60)) 23 | .build() 24 | .unwrap(); 25 | 26 | let cblocks = vec![ 27 | canonical::new_bundle_age_block( 28 | 2, // block number 29 | BlockControlFlags::empty(), // flags 30 | 0, // time elapsed 31 | ), 32 | canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()), 33 | ]; 34 | let mut b = bundle::Bundle::new(pblock, cblocks); 35 | 36 | b.set_crc(crc_type); 37 | b.calculate_crc(); 38 | b.validate().unwrap(); 39 | b.to_cbor() 40 | } 41 | 42 | fn criterion_benchmark_bundle_create(c: &mut Criterion) { 43 | c.bench_function("create bundle no crc", |b| { 44 | b.iter(|| bench_bundle_create(crc::CRC_NO)) 45 | }); 46 | 47 | c.bench_function("create bundle crc 16", |b| { 48 | b.iter(|| bench_bundle_create(crc::CRC_16)) 49 | }); 50 | 51 | c.bench_function("create bundle crc 32", |b| { 52 | b.iter(|| bench_bundle_create(crc::CRC_32)) 53 | }); 54 | } 55 | fn criterion_benchmark_bundle_encode(c: &mut Criterion) { 56 | let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 57 | let src = eid::EndpointID::with_dtn("node1/123456").unwrap(); 58 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 59 | //let day0 = dtntime::CreationTimestamp::with_time_and_seq(dtntime::DTN_TIME_EPOCH, 0);; 60 | 61 | let pblock = primary::PrimaryBlockBuilder::default() 62 | .destination(dst) 63 | .source(src.clone()) 64 | .report_to(src) 65 | .creation_timestamp(now) 66 | .lifetime(std::time::Duration::from_secs(60 * 60)) 67 | .build() 68 | .unwrap(); 69 | 70 | let mut b = bundle::BundleBuilder::default() 71 | .primary(pblock) 72 | .canonicals(vec![ 73 | canonical::new_bundle_age_block( 74 | 2, // block number 75 | BlockControlFlags::empty(), // flags 76 | 0, // time elapsed 77 | ), 78 | canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()), 79 | ]) 80 | .build() 81 | .unwrap(); 82 | b.set_crc(crc::CRC_NO); 83 | b.calculate_crc(); 84 | b.validate().unwrap(); 85 | let mut bndl = b.clone(); 86 | c.bench_function("encode bundle no crc", move |bench| { 87 | bench.iter(|| bndl.to_cbor()) 88 | }); 89 | 90 | b.set_crc(crc::CRC_16); 91 | b.calculate_crc(); 92 | b.validate().unwrap(); 93 | let mut bndl = b.clone(); 94 | c.bench_function("encode bundle crc 16", move |bench| { 95 | bench.iter(|| bndl.to_cbor()) 96 | }); 97 | 98 | b.set_crc(crc::CRC_32); 99 | b.calculate_crc(); 100 | b.validate().unwrap(); 101 | let mut bndl = b; 102 | c.bench_function("encode bundle crc 32", move |bench| { 103 | bench.iter(|| bndl.to_cbor()) 104 | }); 105 | } 106 | 107 | fn criterion_benchmark_bundle_decode(c: &mut Criterion) { 108 | let b_no = bench_bundle_create(crc::CRC_NO); 109 | 110 | c.bench_function("decode bundle no crc", move |b| { 111 | b.iter(|| { 112 | let _deserialized: Bundle = Bundle::try_from(b_no.as_slice()).unwrap(); 113 | _deserialized.validate().unwrap(); 114 | }) 115 | }); 116 | 117 | let b_16 = bench_bundle_create(crc::CRC_16); 118 | 119 | c.bench_function("decode bundle crc 16", move |b| { 120 | b.iter(|| { 121 | let _deserialized: Bundle = Bundle::try_from(b_16.as_slice()).unwrap(); 122 | _deserialized.validate().unwrap(); 123 | }) 124 | }); 125 | 126 | let b_32 = bench_bundle_create(crc::CRC_32); 127 | 128 | c.bench_function("decode bundle crc 32", move |b| { 129 | b.iter(|| { 130 | let _deserialized: Bundle = Bundle::try_from(b_32.as_slice()).unwrap(); 131 | _deserialized.validate().unwrap(); 132 | }) 133 | }); 134 | } 135 | criterion_group!( 136 | benches, 137 | criterion_benchmark_bundle_create, 138 | criterion_benchmark_bundle_encode, 139 | criterion_benchmark_bundle_decode 140 | ); 141 | criterion_main!(benches); 142 | -------------------------------------------------------------------------------- /src/flags.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, ErrorList}; 2 | use bitflags::bitflags; 3 | 4 | /****************************** 5 | * 6 | * Block Control Flags 7 | * 8 | ******************************/ 9 | 10 | pub type BlockControlFlagsType = u8; 11 | 12 | bitflags! { 13 | pub struct BlockControlFlags: BlockControlFlagsType { 14 | /// This block must be replicated in every fragment. 15 | const BLOCK_REPLICATE = 0x01; 16 | 17 | /// Transmit status report if block can't be processed. 18 | const BLOCK_STATUS_REPORT = 0x02; 19 | 20 | /// Deleted bundle if block can't be processed. 21 | const BLOCK_DELETE_BUNDLE = 0x04; 22 | 23 | /// Discard block if it can't be processed. 24 | const BLOCK_REMOVE = 0x10; 25 | 26 | const BLOCK_CFRESERVED_FIELDS = 0xF0; 27 | } 28 | } 29 | 30 | pub trait BlockValidation { 31 | fn flags(&self) -> BlockControlFlags; 32 | 33 | fn validate(&self) -> Result<(), Error> 34 | where 35 | Self: Sized, 36 | { 37 | if self 38 | .flags() 39 | .contains(BlockControlFlags::BLOCK_CFRESERVED_FIELDS) 40 | { 41 | Err(Error::BlockControlFlagsError( 42 | "Given flag contains reserved bits".to_string(), 43 | )) 44 | } else { 45 | Ok(()) 46 | } 47 | } 48 | fn contains(&self, flags: BlockControlFlags) -> bool 49 | where 50 | Self: Sized, 51 | { 52 | self.flags().contains(flags) 53 | } 54 | fn set(&mut self, flags: BlockControlFlags); 55 | } 56 | impl BlockValidation for BlockControlFlagsType { 57 | fn flags(&self) -> BlockControlFlags { 58 | BlockControlFlags::from_bits_truncate(*self) 59 | } 60 | fn set(&mut self, flags: BlockControlFlags) 61 | where 62 | Self: Sized, 63 | { 64 | *self = flags.bits(); 65 | } 66 | } 67 | /****************************** 68 | * 69 | * Bundle Control Flags 70 | * 71 | ******************************/ 72 | 73 | pub type BundleControlFlagsType = u64; 74 | 75 | bitflags! { 76 | #[derive(Default)] 77 | pub struct BundleControlFlags: BundleControlFlagsType { 78 | 79 | /// Request reporting of bundle deletion. 80 | const BUNDLE_STATUS_REQUEST_DELETION = 0x040000; 81 | 82 | /// Request reporting of bundle delivery. 83 | const BUNDLE_STATUS_REQUEST_DELIVERY = 0x020000; 84 | 85 | /// Request reporting of bundle forwarding. 86 | const BUNDLE_STATUS_REQUEST_FORWARD = 0x010000; 87 | 88 | /// Request reporting of bundle reception. 89 | const BUNDLE_STATUS_REQUEST_RECEPTION = 0x004000; 90 | 91 | /// Status time requested in reports. 92 | const BUNDLE_REQUEST_STATUS_TIME = 0x000040; 93 | 94 | /// Acknowledgment by application is requested. 95 | const BUNDLE_REQUEST_USER_APPLICATION_ACK = 0x000020; 96 | 97 | /// Bundle must not be fragmented. 98 | const BUNDLE_MUST_NOT_FRAGMENTED = 0x000004; 99 | 100 | /// ADU is an administrative record. 101 | const BUNDLE_ADMINISTRATIVE_RECORD_PAYLOAD = 0x000002; 102 | 103 | /// The bundle is a fragment. 104 | const BUNDLE_IS_FRAGMENT = 0x000001; 105 | 106 | const BUNDLE_CFRESERVED_FIELDS = 0xE218; 107 | } 108 | } 109 | 110 | pub trait BundleValidation { 111 | fn flags(&self) -> BundleControlFlags; 112 | fn contains(&self, flags: BundleControlFlags) -> bool 113 | where 114 | Self: Sized, 115 | { 116 | self.flags().contains(flags) 117 | } 118 | fn set(&mut self, flags: BundleControlFlags); 119 | fn validate(&self) -> Result<(), ErrorList> 120 | where 121 | Self: Sized, 122 | { 123 | let mut errors: ErrorList = Vec::new(); 124 | let flags = self.flags(); 125 | if flags.contains(BundleControlFlags::BUNDLE_CFRESERVED_FIELDS) { 126 | errors.push(Error::BundleControlFlagsError( 127 | "Given flag contains reserved bits".to_string(), 128 | )); 129 | } 130 | 131 | let admin_rec_check = !flags 132 | .contains(BundleControlFlags::BUNDLE_ADMINISTRATIVE_RECORD_PAYLOAD) 133 | || (!flags.contains(BundleControlFlags::BUNDLE_STATUS_REQUEST_RECEPTION) 134 | && !flags.contains(BundleControlFlags::BUNDLE_STATUS_REQUEST_FORWARD) 135 | && !flags.contains(BundleControlFlags::BUNDLE_STATUS_REQUEST_DELIVERY) 136 | && !flags.contains(BundleControlFlags::BUNDLE_STATUS_REQUEST_DELETION)); 137 | if !admin_rec_check { 138 | errors.push(Error::BundleControlFlagsError( 139 | "\"payload is administrative record => no status report request flags\" failed" 140 | .to_string(), 141 | )) 142 | } 143 | if !errors.is_empty() { 144 | return Err(errors); 145 | } 146 | Ok(()) 147 | } 148 | } 149 | 150 | impl BundleValidation for BundleControlFlagsType { 151 | fn flags(&self) -> BundleControlFlags { 152 | BundleControlFlags::from_bits_truncate(*self) 153 | } 154 | fn set(&mut self, flags: BundleControlFlags) 155 | where 156 | Self: Sized, 157 | { 158 | *self = flags.bits(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /examples/bp7kit/main.js: -------------------------------------------------------------------------------- 1 | 2 | function toHexString(byteArray) { 3 | return Array.from(byteArray, function (byte) { 4 | return ('0' + (byte & 0xFF).toString(16)).slice(-2); 5 | }).join('') 6 | } 7 | // Convert a hex string to a byte array 8 | function hexToBytes(hexString) { 9 | var result = []; 10 | while (hexString.length >= 2) { 11 | result.push(parseInt(hexString.substring(0, 2), 16)); 12 | hexString = hexString.substring(2, hexString.length); 13 | } 14 | return result; 15 | } 16 | 17 | function insert_rnd_bundle() { 18 | Rust.bp7.then(function (bp7) { 19 | var b = bp7.rnd_bundle_now(); 20 | var bid = bp7.bid_from_bundle(b); 21 | console.log('inserting random bundle: ', bid); 22 | var cbor = bp7.encode_to_cbor(b) 23 | document.getElementById("hexout").textContent = toHexString(cbor); 24 | document.getElementById("hexout").onclick = function () { 25 | window.open("http://cbor.me/?bytes=" + toHexString(cbor)); 26 | }; 27 | document.getElementById("outlen").textContent = cbor.length; 28 | }); 29 | } 30 | 31 | 32 | 33 | function show_msg(buf) { 34 | Rust.bp7.then(function (bp7) { 35 | var b = bp7.decode_from_cbor(buf) 36 | var bid = bp7.bid_from_bundle(b); 37 | var payload = bp7.payload_from_bundle(b); 38 | var sender_eid = bp7.sender_from_bundle(b); 39 | var recepient_eid = bp7.recipient_from_bundle(b); 40 | var timestamp = bp7.timestamp_from_bundle(b); 41 | var msg_str = String.fromCharCode.apply(null, payload) 42 | alert("From: " + sender_eid + "\nTo: " + recepient_eid + "\nCreation Time: " + timestamp + "\nMessage: \n" + msg_str); 43 | }); 44 | } 45 | 46 | // copyTextToClipboard code from https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript 47 | function copyTextToClipboard(text) { 48 | var textArea = document.createElement("textarea"); 49 | 50 | // 51 | // *** This styling is an extra step which is likely not required. *** 52 | // 53 | // Why is it here? To ensure: 54 | // 1. the element is able to have focus and selection. 55 | // 2. if element was to flash render it has minimal visual impact. 56 | // 3. less flakyness with selection and copying which **might** occur if 57 | // the textarea element is not visible. 58 | // 59 | // The likelihood is the element won't even render, not even a 60 | // flash, so some of these are just precautions. However in 61 | // Internet Explorer the element is visible whilst the popup 62 | // box asking the user for permission for the web page to 63 | // copy to the clipboard. 64 | // 65 | 66 | // Place in top-left corner of screen regardless of scroll position. 67 | textArea.style.position = 'fixed'; 68 | textArea.style.top = 0; 69 | textArea.style.left = 0; 70 | 71 | // Ensure it has a small width and height. Setting to 1px / 1em 72 | // doesn't work as this gives a negative w/h on some browsers. 73 | textArea.style.width = '2em'; 74 | textArea.style.height = '2em'; 75 | 76 | // We don't need padding, reducing the size if it does flash render. 77 | textArea.style.padding = 0; 78 | 79 | // Clean up any borders. 80 | textArea.style.border = 'none'; 81 | textArea.style.outline = 'none'; 82 | textArea.style.boxShadow = 'none'; 83 | 84 | // Avoid flash of white box if rendered for any reason. 85 | textArea.style.background = 'transparent'; 86 | 87 | 88 | textArea.value = text; 89 | 90 | document.body.appendChild(textArea); 91 | textArea.focus(); 92 | textArea.select(); 93 | 94 | try { 95 | var successful = document.execCommand('copy'); 96 | var msg = successful ? 'successful' : 'unsuccessful'; 97 | console.log('Copying text command was ' + msg); 98 | } catch (err) { 99 | console.log('Oops, unable to copy'); 100 | } 101 | 102 | document.body.removeChild(textArea); 103 | } 104 | 105 | function send_msg() { 106 | console.log("send new message"); 107 | var sender_eid = document.getElementById("sender").value;; 108 | var receiver_eid = document.getElementById("receiver").value; 109 | var msg = document.getElementById("msg").value; 110 | console.log(sender_eid + " " + receiver_eid + " " + msg); 111 | Rust.bp7.then(function (bp7) { 112 | var b = bp7.new_std_bundle_now(sender_eid, receiver_eid, msg); 113 | if (bp7.valid_bundle(b)) { 114 | var bid = bp7.bid_from_bundle(b); 115 | var cbor = bp7.encode_to_cbor(b) 116 | document.getElementById("hexout").textContent = toHexString(cbor); 117 | document.getElementById("hexout").onclick = function () { 118 | window.open("http://cbor.me/?bytes=" + toHexString(cbor)); 119 | }; 120 | document.getElementById("outlen").textContent = cbor.length; 121 | document.getElementById("copy").onclick = function () { 122 | copyTextToClipboard(document.getElementById("hexout").textContent); 123 | }; 124 | } else { 125 | console.log("fatal error creating new bundle!"); 126 | } 127 | }); 128 | } -------------------------------------------------------------------------------- /src/crc.rs: -------------------------------------------------------------------------------- 1 | use super::bundle::*; 2 | 3 | /****************************** 4 | * 5 | * CRC 6 | * 7 | ******************************/ 8 | 9 | pub type CrcRawType = u8; 10 | 11 | use crc::{CRC_16_IBM_SDLC, CRC_32_ISCSI, Crc}; 12 | 13 | pub const X25: Crc = Crc::::new(&CRC_16_IBM_SDLC); 14 | pub const CASTAGNOLI: Crc = Crc::::new(&CRC_32_ISCSI); 15 | 16 | #[derive(Debug, Clone, PartialEq, Default)] 17 | pub enum CrcValue { 18 | #[default] 19 | CrcNo, 20 | Crc16Empty, 21 | Crc32Empty, 22 | Crc16([u8; 2]), 23 | Crc32([u8; 4]), 24 | Unknown(CrcRawType), 25 | } 26 | 27 | impl CrcValue { 28 | pub fn has_crc(&self) -> bool { 29 | // TODO: handle unknown 30 | *self != CrcValue::CrcNo 31 | } 32 | pub fn to_code(&self) -> CrcRawType { 33 | match self { 34 | CrcValue::CrcNo => CRC_NO, 35 | CrcValue::Crc16(_) => CRC_16, 36 | CrcValue::Crc16Empty => CRC_16, 37 | CrcValue::Crc32(_) => CRC_32, 38 | CrcValue::Crc32Empty => CRC_32, 39 | CrcValue::Unknown(code) => *code, 40 | } 41 | } 42 | pub fn bytes(&self) -> Option<&[u8]> { 43 | match self { 44 | CrcValue::Unknown(_) => None, 45 | CrcValue::CrcNo => None, 46 | CrcValue::Crc16(buf) => Some(buf), 47 | CrcValue::Crc16Empty => Some(&CRC16_EMPTY), 48 | CrcValue::Crc32(buf) => Some(buf), 49 | CrcValue::Crc32Empty => Some(&CRC32_EMPTY), 50 | } 51 | } 52 | } 53 | pub(crate) const CRC16_EMPTY: [u8; 2] = [0; 2]; 54 | pub(crate) const CRC32_EMPTY: [u8; 4] = [0; 4]; 55 | 56 | pub const CRC_NO: CrcRawType = 0; 57 | pub const CRC_16: CrcRawType = 1; 58 | pub const CRC_32: CrcRawType = 2; 59 | 60 | pub trait CRCFuncations { 61 | fn to_string(self) -> String; 62 | } 63 | impl CRCFuncations for CrcRawType { 64 | fn to_string(self) -> String { 65 | match self { 66 | CRC_NO => String::from("no"), 67 | CRC_16 => String::from("16"), 68 | CRC_32 => String::from("32"), 69 | _ => String::from("unknown"), 70 | } 71 | } 72 | } 73 | 74 | pub trait CrcBlock: Block + Clone { 75 | /// Convert block struct to a serializable enum 76 | fn has_crc(&self) -> bool { 77 | self.crc_value().has_crc() 78 | } 79 | /// Recalculate crc value 80 | fn update_crc(&mut self) { 81 | let new_crc = calculate_crc(self); 82 | self.set_crc(new_crc); 83 | } 84 | /// Check if crc value is valid 85 | fn check_crc(&mut self) -> bool { 86 | check_crc(self) 87 | } 88 | /// Reset crc field to an empty value 89 | fn reset_crc(&mut self) { 90 | if self.has_crc() { 91 | match self.crc_value() { 92 | CrcValue::Crc16(_) => self.set_crc(CrcValue::Crc16Empty), 93 | CrcValue::Crc32(_) => self.set_crc(CrcValue::Crc32Empty), 94 | _ => {} 95 | } 96 | } 97 | } 98 | /// Returns raw crc checksum 99 | fn crc(&self) -> Option<&[u8]> { 100 | self.crc_value().bytes() 101 | } 102 | /// Set crc type 103 | /// CRC_NO, CRC_16, CRC_32 104 | fn set_crc_type(&mut self, crc_value: CrcRawType) { 105 | if crc_value == CRC_NO { 106 | self.set_crc(CrcValue::CrcNo); 107 | } else if crc_value == CRC_16 { 108 | self.set_crc(CrcValue::Crc16Empty); 109 | } else if crc_value == CRC_32 { 110 | self.set_crc(CrcValue::Crc32Empty); 111 | } else { 112 | self.set_crc(CrcValue::Unknown(crc_value)); 113 | } 114 | } 115 | /// Return the crc type code 116 | fn crc_type(&self) -> CrcRawType { 117 | self.crc_value().to_code() 118 | } 119 | fn crc_value(&self) -> &CrcValue; 120 | fn set_crc(&mut self, crc: CrcValue); 121 | } 122 | 123 | pub fn calculate_crc(blck: &mut T) -> CrcValue { 124 | match blck.crc_type() { 125 | CRC_NO => CrcValue::CrcNo, 126 | CRC_16 => { 127 | let crc_bak = blck.crc_value().clone(); // Backup original crc 128 | blck.reset_crc(); // set empty crc 129 | let data = blck.to_cbor(); // TODO: optimize this encoding away 130 | // also tried crc16 crate, not a bit faster 131 | let chksm = X25.checksum(&data); 132 | let output_crc = chksm.to_be_bytes(); 133 | blck.set_crc(crc_bak); // restore orginal crc 134 | CrcValue::Crc16(output_crc) 135 | } 136 | CRC_32 => { 137 | let crc_bak = blck.crc_value().clone(); // Backup original crc 138 | blck.reset_crc(); // set empty crc 139 | let data = blck.to_cbor(); // TODO: optimize this encoding away 140 | // also tried crc32fast, was not significantly faster 141 | let chksm = CASTAGNOLI.checksum(&data); 142 | let output_crc = chksm.to_be_bytes(); 143 | blck.set_crc(crc_bak); // restore orginal crc 144 | CrcValue::Crc32(output_crc) 145 | } 146 | _ => { 147 | panic!("Unknown crc type"); 148 | } 149 | } 150 | } 151 | pub fn check_crc(blck: &mut T) -> bool { 152 | if !blck.has_crc() { 153 | return !blck.has_crc(); 154 | } 155 | calculate_crc(blck).bytes() == blck.crc() 156 | } 157 | -------------------------------------------------------------------------------- /tests/canonical_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::flags::*; 2 | use bp7::*; 3 | use std::convert::TryFrom; 4 | use std::convert::TryInto; 5 | 6 | #[test] 7 | fn canonical_data_tests() { 8 | let data = CanonicalData::Data(b"bla".to_vec()); 9 | let encoded_data = serde_cbor::to_vec(&data).expect("encoding error"); 10 | let decoded_data: CanonicalData = 11 | serde_cbor::from_slice(&encoded_data).expect("decoding error"); 12 | assert_eq!(data, decoded_data); 13 | 14 | let bundleage = dbg!(CanonicalData::BundleAge(23)); 15 | let encoded_bundleage = serde_cbor::to_vec(&bundleage).expect("encoding error"); 16 | let decoded_bundleage: CanonicalData = 17 | serde_cbor::from_slice(&encoded_bundleage).expect("decoding error"); 18 | assert_eq!(bundleage, decoded_bundleage); 19 | 20 | let hopcount = CanonicalData::HopCount(23, 42); 21 | let encoded_hopcount = serde_cbor::to_vec(&hopcount).expect("encoding error"); 22 | let decoded_hopcount: CanonicalData = 23 | serde_cbor::from_slice(&encoded_hopcount).expect("decoding error"); 24 | assert_eq!(hopcount, decoded_hopcount); 25 | 26 | let previous = CanonicalData::PreviousNode("dtn://node1".try_into().unwrap()); 27 | let encoded_previous = serde_cbor::to_vec(&previous).expect("encoding error"); 28 | let decoded_previous: CanonicalData = 29 | serde_cbor::from_slice(&encoded_previous).expect("decoding error"); 30 | assert_eq!(previous, decoded_previous); 31 | } 32 | 33 | fn encode_decode_test_canonical(data: CanonicalBlock) { 34 | let encoded_data = serde_cbor::to_vec(&data).expect("encoding error"); 35 | let decoded_data: CanonicalBlock = 36 | serde_cbor::from_slice(&encoded_data).expect("decoding error"); 37 | assert_eq!(data, decoded_data); 38 | 39 | println!("{:?}", decoded_data.data()); 40 | assert_eq!(decoded_data.data(), data.data()); 41 | if *decoded_data.data() == CanonicalData::DecodingError { 42 | panic!("Wrong Payload"); 43 | } 44 | } 45 | 46 | #[test] 47 | fn canonical_block_tests() { 48 | let data = new_payload_block(BlockControlFlags::empty(), b"ABCDEFG".to_vec()); 49 | encode_decode_test_canonical(data); 50 | 51 | let data = new_hop_count_block(1, BlockControlFlags::empty(), 32); 52 | encode_decode_test_canonical(data); 53 | 54 | let data = new_bundle_age_block(1, BlockControlFlags::empty(), 0); 55 | encode_decode_test_canonical(data); 56 | 57 | let data = new_previous_node_block( 58 | 1, 59 | BlockControlFlags::empty(), 60 | "dtn://node2".try_into().unwrap(), 61 | ); 62 | encode_decode_test_canonical(data); 63 | } 64 | 65 | #[test] 66 | fn hopcount_tests() { 67 | let mut block = new_hop_count_block(1, BlockControlFlags::empty(), 1); 68 | 69 | assert_eq!(block.block_type, bp7::HOP_COUNT_BLOCK); 70 | assert!(!block.hop_count_exceeded()); 71 | 72 | if let CanonicalData::HopCount(hc_limit, hc_count) = block.data() { 73 | assert!(*hc_limit == 1); 74 | assert!(*hc_count == 0); 75 | } else { 76 | panic!("Not a hop count block!"); 77 | } 78 | 79 | assert!(block.hop_count_increase()); 80 | if let Some((hc_limit, hc_count)) = block.hop_count_get() { 81 | assert!(hc_limit == 1); 82 | assert!(hc_count == 1); 83 | } else { 84 | panic!("Not a hop count block!"); 85 | } 86 | 87 | assert!(block.hop_count_increase()); 88 | assert!(block.hop_count_exceeded()); 89 | 90 | let mut wrong_block = new_bundle_age_block(1, BlockControlFlags::empty(), 0); 91 | assert!(!wrong_block.hop_count_increase()); 92 | assert!(!wrong_block.hop_count_exceeded()); 93 | assert_eq!(wrong_block.hop_count_get(), None); 94 | } 95 | 96 | #[test] 97 | fn previousnode_tests() { 98 | let mut block = new_previous_node_block( 99 | 1, 100 | BlockControlFlags::empty(), 101 | "dtn://node1".try_into().unwrap(), 102 | ); 103 | 104 | assert_eq!(block.block_type, bp7::PREVIOUS_NODE_BLOCK); 105 | if let Some(eid) = block.previous_node_get() { 106 | assert_eq!(*eid, EndpointID::try_from("dtn://node1").unwrap()); 107 | } else { 108 | panic!("Not a previous node block!"); 109 | } 110 | 111 | assert!(block.previous_node_update("dtn://node2".try_into().unwrap())); 112 | 113 | if let Some(eid) = block.previous_node_get() { 114 | assert_eq!(*eid, EndpointID::try_from("dtn://node2").unwrap()); 115 | } else { 116 | panic!("Not a previous node block!"); 117 | } 118 | 119 | let mut wrong_block = new_bundle_age_block(1, BlockControlFlags::empty(), 0); 120 | assert_eq!(wrong_block.previous_node_get(), None); 121 | assert!(!wrong_block.previous_node_update("dtn://node2".try_into().unwrap())); 122 | } 123 | 124 | #[test] 125 | fn bundleage_tests() { 126 | let mut block = new_bundle_age_block(1, BlockControlFlags::empty(), 0); 127 | 128 | assert_eq!(block.block_type, bp7::BUNDLE_AGE_BLOCK); 129 | if let Some(age) = block.bundle_age_get() { 130 | assert_eq!(age, 0); 131 | } else { 132 | panic!("Not a bundle age block!"); 133 | } 134 | 135 | assert!(block.bundle_age_update(200)); 136 | 137 | if let Some(age) = block.bundle_age_get() { 138 | assert_eq!(age, 200); 139 | } else { 140 | panic!("Not a bundle age block!"); 141 | } 142 | 143 | let mut wrong_block = new_hop_count_block(1, BlockControlFlags::empty(), 1); 144 | assert_eq!(wrong_block.bundle_age_get(), None); 145 | assert!(!wrong_block.bundle_age_update(2342)); 146 | } 147 | -------------------------------------------------------------------------------- /examples/bp7kit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | 30 | 31 | 32 | 33 | 34 |

BP7 Kit

35 | 36 |
37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 48 | 49 | This is a required field. 50 |
51 | 52 |
53 | 54 | 55 | This is a required field. 56 |
57 | 58 |
59 | 60 | 61 | This is a required field. 62 |
63 | 64 |
65 | 66 | 67 |
68 |
69 | 70 |
71 |
72 | Bundle Age Block
73 |
74 | 75 |
76 |
77 | 78 | Hop Count Block
79 |
80 | 81 |
82 | 83 | 84 |
85 |
86 | Previous Node Block
87 |
88 | 89 |
90 |
91 |

Empty canoncial block inputs will fall back to defaults.

92 |
93 |
94 |
95 | 97 |
98 |
99 | 100 | 101 |
102 | 103 | 104 |
105 |
106 |
107 |
108 | 109 |

Hex-encoded (0 bytes)

110 | 111 |
113 |
114 |
115 | Copy
116 |
117 |
118 |
119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /doc/encoding_samples.md: -------------------------------------------------------------------------------- 1 | # Bundle Protocol Version 7 CBOR Encoding Samples 2 | 3 | **Table of Contents** 4 | 5 | * [Data Structures](#data-structures) 6 | + [Creation Timestamp](#creation-timestamp) 7 | + [Endpoint ID](#endpoint-id) 8 | - [dtn scheme](#dtn-scheme) 9 | - [ipn scheme](#ipn-scheme) 10 | - [none endpoint](#none-endpoint) 11 | * [Blocks](#blocks) 12 | + [Primary Block](#primary-block) 13 | + [Canonical Blocks](#canonical-blocks) 14 | - [Payload Block](#payload-block) 15 | - [Hop Count Block](#hop-count-block) 16 | - [Bundle Age Block](#bundle-age-block) 17 | - [Previous Node Block](#previous-node-block) 18 | * [Administrative Records](#administrative-records) 19 | * [Bundles](#bundles) 20 | 21 | --- 22 | 23 | ## Data Structures 24 | 25 | ### Creation Timestamp 26 | 27 | Description | Value 28 | --- | --- 29 | human-readable | `2020-03-13T12:41:18Z 0` 30 | json | `[637418478,0]` 31 | hex string | [`821a25fe3bee00`](http://cbor.me/?bytes=821a25fe3bee00) 32 | byte array | `[130, 26, 37, 254, 59, 238, 0]` 33 | 34 | --- 35 | 36 | ### Endpoint ID 37 | 38 | #### dtn scheme 39 | 40 | Description | Value 41 | --- | --- 42 | human-readable | `dtn://node1/test` 43 | json | `[1,"node1/test"]` 44 | hex string | [`82016a6e6f6465312f74657374`](http://cbor.me/?bytes=82016a6e6f6465312f74657374) 45 | byte array | `[130, 1, 106, 110, 111, 100, 101, 49, 47, 116, 101, 115, 116]` 46 | 47 | #### ipn scheme 48 | 49 | Description | Value 50 | --- | --- 51 | human-readable | `ipn://23.42` 52 | json | `[2,[23,42]]` 53 | hex string | [`82028217182a`](http://cbor.me/?bytes=82028217182a) 54 | byte array | `[130, 2, 130, 23, 24, 42]` 55 | 56 | #### none endpoint 57 | 58 | Description | Value 59 | --- | --- 60 | human-readable | `dtn://none` 61 | json | `[1,0]` 62 | hex string | [`820100`](http://cbor.me/?bytes=820100) 63 | byte array | `[130, 1, 0]` 64 | 65 | --- 66 | 67 | ## Blocks 68 | 69 | ### Primary Block 70 | 71 | #### No CRC 72 | 73 | Description | Value 74 | --- | --- 75 | human-readable | primary block with no flags, no fragmentation, lifetime of `60s`, creation timestamp `[2342, 2]` from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, no crc 76 | json | `[7,0,0,[1,"n2/inbox"],[1,"n1"],[1,"n1"],[2342,2],60000000]` 77 | hex string | [`880700008201686e322f696e626f788201626e318201626e3182190926021a03938700`](http://cbor.me/?bytes=880700008201686e322f696e626f788201626e318201626e3182190926021a03938700) 78 | byte array | `[136, 7, 0, 0, 130, 1, 104, 110, 50, 47, 105, 110, 98, 111, 120, 130, 1, 98, 110, 49, 130, 1, 98, 110, 49, 130, 25, 9, 38, 2, 26, 3, 147, 135, 0]` 79 | 80 | #### CRC 16 81 | 82 | Description | Value 83 | --- | --- 84 | human-readable | primary block with no flags, no fragmentation, lifetime of `60s`, creation timestamp `[2342, 2]` from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, crc16 85 | json | `[7,0,1,[1,"n2/inbox"],[1,"n1"],[1,"n1"],[2342,2],60000000,[231,202]]` 86 | hex string | [`890700018201686e322f696e626f788201626e318201626e3182190926021a0393870042e7ca`](http://cbor.me/?bytes=890700018201686e322f696e626f788201626e318201626e3182190926021a0393870042e7ca) 87 | byte array | `[137, 7, 0, 1, 130, 1, 104, 110, 50, 47, 105, 110, 98, 111, 120, 130, 1, 98, 110, 49, 130, 1, 98, 110, 49, 130, 25, 9, 38, 2, 26, 3, 147, 135, 0, 66, 231, 202]` 88 | 89 | #### CRC 32 90 | 91 | Description | Value 92 | --- | --- 93 | human-readable | primary block with no flags, no fragmentation, lifetime of `60s`, creation timestamp `[2342, 2]` from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, crc32 94 | json | `[7,0,2,[1,"n2/inbox"],[1,"n1"],[1,"n1"],[2342,2],60000000,[202,79,195,104]]` 95 | hex string | [`890700028201686e322f696e626f788201626e318201626e3182190926021a0393870044ca4fc368`](http://cbor.me/?bytes=890700028201686e322f696e626f788201626e318201626e3182190926021a0393870044ca4fc368) 96 | byte array | `[137, 7, 0, 2, 130, 1, 104, 110, 50, 47, 105, 110, 98, 111, 120, 130, 1, 98, 110, 49, 130, 1, 98, 110, 49, 130, 25, 9, 38, 2, 26, 3, 147, 135, 0, 68, 202, 79, 195, 104]` 97 | 98 | --- 99 | 100 | ### Canonical Blocks 101 | 102 | #### Payload Block 103 | Description | Value 104 | --- | --- 105 | human-readable | payload block with no flags and `'ABC'` as content, no crc 106 | json | `[1,1,0,0,[65,66,67]]` 107 | hex string | [`850101000043414243`](http://cbor.me/?bytes=850101000043414243) 108 | byte array: | `[133, 1, 1, 0, 0, 67, 65, 66, 67]` 109 | 110 | 111 | #### Hop Count Block 112 | 113 | Description | Value 114 | --- | --- 115 | human-readable | hop count block with no flags, block number 1 and hop limit = 32, no crc 116 | json | `[10,1,0,0,[32,0]]` 117 | hex string | [`850a01000082182000`](http://cbor.me/?bytes=850a01000082182000) 118 | byte array | `[133, 10, 1, 0, 0, 130, 24, 32, 0]` 119 | 120 | #### Bundle Age Block 121 | 122 | Description | Value 123 | --- | --- 124 | human-readable | bundle age block with no flags, block number 2 and age = 1234us, no crc 125 | json | `[7,2,0,0,1234]` 126 | hex string | [`85070200001904d2`](http://cbor.me/?bytes=85070200001904d2) 127 | byte array | `[133, 7, 2, 0, 0, 25, 4, 210]` 128 | 129 | #### Previous Node Block 130 | 131 | Description | Value 132 | --- | --- 133 | human-readable | previous node block with no flags, block number 3 and prev_node = `dtn://n1`, no crc 134 | json | `[6,3,0,0,[1,"n1"]]` 135 | hex string | [`85060300008201626e31`](http://cbor.me/?bytes=85060300008201626e31) 136 | byte array | `[133, 6, 3, 0, 0, 130, 1, 98, 110, 49]` 137 | 138 | 139 | --- 140 | 141 | ## Administrative Records 142 | 143 | --- 144 | 145 | ## Bundles 146 | 147 | #### No CRC 148 | 149 | Description | Value 150 | --- | --- 151 | human-readable | bundle with no flags, no fragmentation, lifetime of `60 * 60s`, creation timestamp now from `dtn://n1` to `dtn://n2/inbox` with reporting to `dtn://n1`, payload `'ABC'` and hop count block with 32 hop limit, no crc 152 | json | `[[7,131076,0,[1,"n2/inbox"],[1,"n1"],[1,"n1"],[637421757,1],3600000000],[1,1,0,0,[65,66,67]],[10,2,0,0,[32,0]]]` 153 | hex string | [`9f88071a00020004008201686e322f696e626f788201626e318201626e31821a25fe48bd011ad693a400850101000043414243850a02000082182000ff`](http://cbor.me/?bytes=9f88071a00020004008201686e322f696e626f788201626e318201626e31821a25fe48bd011ad693a400850101000043414243850a02000082182000ff) 154 | byte array | `[159, 136, 7, 26, 0, 2, 0, 4, 0, 130, 1, 104, 110, 50, 47, 105, 110, 98, 111, 120, 130, 1, 98, 110, 49, 130, 1, 98, 110, 49, 130, 26, 37, 254, 72, 189, 1, 26, 214, 147, 164, 0, 133, 1, 1, 0, 0, 67, 65, 66, 67, 133, 10, 2, 0, 0, 130, 24, 32, 0, 255]` 155 | -------------------------------------------------------------------------------- /src/wasm.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | use crate::bundle::Bundle; 4 | use crate::dtntime::CreationTimestamp; 5 | use crate::eid::*; 6 | use core::convert::TryFrom; 7 | 8 | /// Create a new standard bundle with current timestamp 9 | #[wasm_bindgen] 10 | pub fn new_std_bundle_now(src: &str, dst: &str, payload: &str) -> Result { 11 | let src_eid = EndpointID::try_from(src.to_string()) 12 | .map_err(|e| JsValue::from_str(&format!("Invalid source address: {}", e)))?; 13 | let dst_eid = EndpointID::try_from(dst.to_string()) 14 | .map_err(|e| JsValue::from_str(&format!("Invalid destination address: {}", e)))?; 15 | 16 | let bundle = 17 | crate::bundle::new_std_payload_bundle(src_eid, dst_eid, payload.as_bytes().to_vec()); 18 | 19 | serde_wasm_bindgen::to_value(&bundle) 20 | .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) 21 | } 22 | 23 | /// Create a random bundle with current timestamp 24 | #[wasm_bindgen] 25 | pub fn rnd_bundle_now() -> Result { 26 | let bundle = crate::helpers::rnd_bundle(CreationTimestamp::now()); 27 | serde_wasm_bindgen::to_value(&bundle) 28 | .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) 29 | } 30 | 31 | /// Encode a bundle to CBOR bytes 32 | #[wasm_bindgen] 33 | pub fn encode_to_cbor(bundle_js: &JsValue) -> Result, JsValue> { 34 | let mut bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 35 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 36 | Ok(bundle.to_cbor()) 37 | } 38 | 39 | /// Decode a bundle from CBOR bytes 40 | #[wasm_bindgen] 41 | pub fn decode_from_cbor(buf: &[u8]) -> Result { 42 | let bundle = Bundle::try_from(buf.to_vec()) 43 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 44 | serde_wasm_bindgen::to_value(&bundle) 45 | .map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e))) 46 | } 47 | 48 | /// Get bundle ID from a bundle object 49 | #[wasm_bindgen] 50 | pub fn bid_from_bundle(bundle_js: &JsValue) -> Result { 51 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 52 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 53 | Ok(bundle.id()) 54 | } 55 | 56 | /// Get bundle ID from CBOR bytes 57 | #[wasm_bindgen] 58 | pub fn bid_from_cbor(buf: &[u8]) -> Result { 59 | let bundle = Bundle::try_from(buf.to_vec()) 60 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 61 | Ok(bundle.id()) 62 | } 63 | 64 | /// Get sender from a bundle object 65 | #[wasm_bindgen] 66 | pub fn sender_from_bundle(bundle_js: &JsValue) -> Result { 67 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 68 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 69 | Ok(bundle.primary.source.to_string()) 70 | } 71 | 72 | /// Get sender from CBOR bytes 73 | #[wasm_bindgen] 74 | pub fn sender_from_cbor(buf: &[u8]) -> Result { 75 | let bundle = Bundle::try_from(buf.to_vec()) 76 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 77 | Ok(bundle.primary.source.to_string()) 78 | } 79 | 80 | /// Get recipient from a bundle object 81 | #[wasm_bindgen] 82 | pub fn recipient_from_bundle(bundle_js: &JsValue) -> Result { 83 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 84 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 85 | Ok(bundle.primary.destination.to_string()) 86 | } 87 | 88 | /// Get recipient from CBOR bytes 89 | #[wasm_bindgen] 90 | pub fn recipient_from_cbor(buf: &[u8]) -> Result { 91 | let bundle = Bundle::try_from(buf.to_vec()) 92 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 93 | Ok(bundle.primary.destination.to_string()) 94 | } 95 | 96 | /// Get timestamp from a bundle object 97 | #[wasm_bindgen] 98 | pub fn timestamp_from_bundle(bundle_js: &JsValue) -> Result { 99 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 100 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 101 | Ok(bundle.primary.creation_timestamp.to_string()) 102 | } 103 | 104 | /// Get timestamp from CBOR bytes 105 | #[wasm_bindgen] 106 | pub fn timestamp_from_cbor(buf: &[u8]) -> Result { 107 | let bundle = Bundle::try_from(buf.to_vec()) 108 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 109 | Ok(bundle.primary.creation_timestamp.to_string()) 110 | } 111 | 112 | /// Extract payload from a bundle object 113 | #[wasm_bindgen] 114 | pub fn payload_from_bundle(bundle_js: &JsValue) -> Result>, JsValue> { 115 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 116 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 117 | Ok(bundle.payload().cloned()) 118 | } 119 | 120 | /// Extract payload from CBOR bytes 121 | #[wasm_bindgen] 122 | pub fn payload_from_cbor(buf: &[u8]) -> Result>, JsValue> { 123 | let bundle = Bundle::try_from(buf.to_vec()) 124 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 125 | Ok(bundle.payload().cloned()) 126 | } 127 | 128 | /// Validate a bundle object 129 | #[wasm_bindgen] 130 | pub fn valid_bundle(bundle_js: &JsValue) -> Result { 131 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 132 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 133 | Ok(!bundle.primary.is_lifetime_exceeded() && bundle.validate().is_ok()) 134 | } 135 | 136 | /// Validate CBOR bytes as a bundle 137 | #[wasm_bindgen] 138 | pub fn valid_cbor(buf: &[u8]) -> Result { 139 | match Bundle::try_from(buf.to_vec()) { 140 | Ok(bundle) => Ok(!bundle.primary.is_lifetime_exceeded() && bundle.validate().is_ok()), 141 | Err(_) => Ok(false), 142 | } 143 | } 144 | 145 | /// Check if bundle is an administrative record 146 | #[wasm_bindgen] 147 | pub fn bundle_is_administrative_record(bundle_js: &JsValue) -> Result { 148 | let bundle: Bundle = serde_wasm_bindgen::from_value(bundle_js.clone()) 149 | .map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?; 150 | Ok(bundle.is_administrative_record()) 151 | } 152 | 153 | /// Check if CBOR bytes represent an administrative record bundle 154 | #[wasm_bindgen] 155 | pub fn cbor_is_administrative_record(buf: &[u8]) -> Result { 156 | let bundle = Bundle::try_from(buf.to_vec()) 157 | .map_err(|e| JsValue::from_str(&format!("CBOR decode error: {}", e)))?; 158 | Ok(bundle.is_administrative_record()) 159 | } 160 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | use crate::ByteBuffer; 2 | use crate::{Bundle, bundle, canonical, crc, dtntime, eid, flags::BlockControlFlags, primary}; 3 | 4 | use core::num::ParseIntError; 5 | use nanorand::{Rng, WyRand}; 6 | use std::convert::TryFrom; 7 | use std::fmt::Write as _; 8 | use std::io::{Write, stdout}; 9 | use web_time::{Instant, SystemTime, UNIX_EPOCH}; 10 | 11 | pub fn unix_timestamp() -> u64 { 12 | SystemTime::now() 13 | .duration_since(UNIX_EPOCH) 14 | .expect("Time went backwards!!") 15 | .as_secs() 16 | } 17 | 18 | pub fn ts_ms() -> u64 { 19 | SystemTime::now() 20 | .duration_since(UNIX_EPOCH) 21 | .expect("Time went backwards!!") 22 | .as_millis() as u64 23 | } 24 | 25 | /// Convert byte slice into a hex string 26 | pub fn hexify(buf: &[u8]) -> String { 27 | let mut hexstr = String::new(); 28 | for &b in buf { 29 | let _ = write!(hexstr, "{:02x}", b); 30 | } 31 | hexstr 32 | } 33 | /// Convert a hex string into a byte vector 34 | pub fn unhexify(s: &str) -> Result, ParseIntError> { 35 | (0..s.len()) 36 | .step_by(2) 37 | .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) 38 | .collect() 39 | } 40 | 41 | pub fn ser_dump(input: &T, hr: &str) { 42 | println!("Description | Value"); 43 | println!("--- | ---"); 44 | println!("human-readable | {}", hr); 45 | let json = serde_json::to_string(input).unwrap(); 46 | println!("json | `{}`", json); 47 | let cbor = serde_cbor::to_vec(input).unwrap(); 48 | println!( 49 | "hex string | [`{}`](http://cbor.me/?bytes={})", 50 | hexify(&cbor), 51 | hexify(&cbor) 52 | ); 53 | println!("byte array | `{:?}`\n", cbor); 54 | } 55 | pub fn vec_dump(input: &T, cbor: Vec, hr: &str) { 56 | println!("Description | Value"); 57 | println!("--- | ---"); 58 | println!("human-readable | {}", hr); 59 | let json = serde_json::to_string(input).unwrap(); 60 | println!("json | `{}`", json); 61 | println!( 62 | "hex string | [`{}`](http://cbor.me/?bytes={})", 63 | hexify(&cbor), 64 | hexify(&cbor) 65 | ); 66 | println!("byte array | `{:?}`\n", cbor); 67 | } 68 | pub fn rnd_bundle(now: dtntime::CreationTimestamp) -> bundle::Bundle { 69 | let mut rng = WyRand::new(); 70 | let singletons = ["sms", "files", "123456", "incoming", "mavlink"]; 71 | let groups = ["~news", "~tele", "~mavlink"]; 72 | //rng.shuffle(&mut singletons); 73 | //rng.shuffle(&mut groups); 74 | let concatenated = [&singletons[..], &groups[..]].concat(); 75 | //rng.shuffle(&mut concatenated); 76 | let dst_string = format!( 77 | "//node{}/{}", 78 | rng.generate_range(1_u32..99), 79 | concatenated[rng.generate_range(0_usize..concatenated.len())] 80 | ); 81 | let src_string = format!( 82 | "//node{}/{}", 83 | rng.generate_range(1_u32..99), 84 | singletons[rng.generate_range(0_usize..singletons.len())] 85 | ); 86 | let dst = eid::EndpointID::with_dtn(&dst_string).unwrap(); 87 | let src = eid::EndpointID::with_dtn(&src_string).unwrap(); 88 | //let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0);; 89 | //let day0 = dtntime::CreationTimestamp::with_time_and_seq(dtntime::DTN_TIME_EPOCH, 0);; 90 | let mut b = bundle::new_std_payload_bundle(src, dst, b"ABC".to_vec()); 91 | b.primary.creation_timestamp = now; 92 | b 93 | } 94 | 95 | pub fn get_bench_bundle(crc_type: crc::CrcRawType) -> Bundle { 96 | let dst = eid::EndpointID::with_dtn("//node2/inbox").unwrap(); 97 | let src = eid::EndpointID::with_dtn("//node1/123456").unwrap(); 98 | //let dst = eid::EndpointID::with_ipn(eid::IpnAddress(1, 2)); 99 | //let src = eid::EndpointID::with_ipn(eid::IpnAddress(2, 3)); 100 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 101 | //let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::DTN_TIME_EPOCH, 0); 102 | 103 | //let pblock = primary::new_primary_block("dtn:node2/inbox".to_string(), "dtn:node1/123456".to_string(), now, 60 * 60 * 1_000_000); 104 | let pblock = primary::PrimaryBlockBuilder::default() 105 | .destination(dst) 106 | .source(src.clone()) 107 | .report_to(src) 108 | .creation_timestamp(now) 109 | .lifetime(std::time::Duration::from_secs(60 * 60)) 110 | .build() 111 | .unwrap(); 112 | let cblocks = vec![ 113 | canonical::new_bundle_age_block( 114 | 2, // block number 115 | BlockControlFlags::empty(), // flags 116 | 0, // time elapsed 117 | ), 118 | canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()), 119 | ]; 120 | //let cblocks = Vec::new(); 121 | let mut b = bundle::Bundle::new(pblock, cblocks); 122 | // bundle builder is significantly slower! 123 | /*let mut b = bundle::BundleBuilder::default() 124 | .primary(pblock) 125 | .canonicals(cblocks) 126 | .build() 127 | .unwrap();*/ 128 | b.set_crc(crc_type); 129 | b.calculate_crc(); 130 | b.validate().unwrap(); 131 | b 132 | } 133 | 134 | pub fn bench_bundle_create(runs: i64, crc_type: crc::CrcRawType) -> Vec { 135 | let crc_str = match crc_type { 136 | crc::CRC_NO => "CRC_NO", 137 | crc::CRC_16 => "CRC_16", 138 | crc::CRC_32 => "CRC_32", 139 | _ => panic!("CRC_unknown"), 140 | }; 141 | let mut bundles: Vec = Vec::with_capacity(runs as usize); 142 | 143 | print!("Creating {} bundles with {}: \t", runs, crc_str); 144 | stdout().flush().unwrap(); 145 | 146 | let bench_now = Instant::now(); 147 | 148 | for _x in 0..runs { 149 | let mut b = get_bench_bundle(crc_type); 150 | let _serialized = b.to_cbor(); 151 | //let _serialized = b.to_json(); 152 | bundles.push(_serialized); 153 | } 154 | let elapsed = bench_now.elapsed(); 155 | let sec = (elapsed.as_secs() as f64) + (f64::from(elapsed.subsec_nanos()) / 1_000_000_000.0); 156 | println!("{:>15} bundles/second", (runs as f64 / sec) as i64); 157 | bundles 158 | } 159 | 160 | pub fn bench_bundle_encode(runs: i64, crc_type: crc::CrcRawType) -> Vec { 161 | let crc_str = match crc_type { 162 | crc::CRC_NO => "CRC_NO", 163 | crc::CRC_16 => "CRC_16", 164 | crc::CRC_32 => "CRC_32", 165 | _ => panic!("CRC_unknown"), 166 | }; 167 | let mut bundles: Vec = Vec::with_capacity(runs as usize); 168 | //let mut bundles: Vec = Vec::new(); 169 | 170 | print!("Encoding {} bundles with {}: \t", runs, crc_str); 171 | stdout().flush().unwrap(); 172 | 173 | let bench_now = Instant::now(); 174 | 175 | let mut b = get_bench_bundle(crc_type); 176 | 177 | for _x in 0..runs { 178 | b.primary.lifetime += std::time::Duration::new(0, 1); 179 | let _serialized = b.to_cbor(); 180 | //let _serialized = b.to_json(); 181 | bundles.push(_serialized); 182 | } 183 | let elapsed = bench_now.elapsed(); 184 | let sec = (elapsed.as_secs() as f64) + (f64::from(elapsed.subsec_nanos()) / 1_000_000_000.0); 185 | println!("{:>15} bundles/second", (runs as f64 / sec) as i64); 186 | bundles 187 | } 188 | 189 | pub fn bench_bundle_load(runs: i64, crc_type: crc::CrcRawType, mut bundles: Vec) { 190 | let crc_str = match crc_type { 191 | crc::CRC_NO => "CRC_NO", 192 | crc::CRC_16 => "CRC_16", 193 | crc::CRC_32 => "CRC_32", 194 | _ => panic!("CRC_unknown"), 195 | }; 196 | print!("Loading {} bundles with {}: \t", runs, crc_str); 197 | stdout().flush().unwrap(); 198 | 199 | let bench_now = Instant::now(); 200 | for _x in 0..runs { 201 | let b = bundles.pop().unwrap(); 202 | let _deserialized: Bundle = Bundle::try_from(b.as_slice()).unwrap(); 203 | _deserialized.validate().unwrap(); 204 | } 205 | let elapsed = bench_now.elapsed(); 206 | let sec = (elapsed.as_secs() as f64) + (f64::from(elapsed.subsec_nanos()) / 1_000_000_000.0); 207 | println!("{:>15} bundles/second", (runs as f64 / sec) as i64); 208 | } 209 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use bp7::dtntime::DtnTimeHelpers; 2 | use bp7::flags::BlockControlFlags; 3 | use bp7::helpers::*; 4 | use bp7::primary::PrimaryBlock; 5 | use bp7::*; 6 | use std::convert::TryInto; 7 | use std::env; 8 | use std::fs; 9 | use std::io; 10 | use std::io::prelude::*; 11 | 12 | fn usage(filepath: &str) { 13 | println!("usage {:?} [args]", filepath); 14 | println!( 15 | "\t encode [-x] - encode bundle and output raw bytes or hex string (-x)" 16 | ); 17 | println!("\t decode [-p] - decode bundle or payload only (-p)"); 18 | println!( 19 | "\t dtntime [dtntimestamp] - prints current time as dtntimestamp or prints dtntime human readable" 20 | ); 21 | println!("\t d2u [dtntimestamp] - converts dtntime to unixstimestamp"); 22 | println!("\t rnd [-r] - return a random bundle either hexencoded or raw bytes (-r)"); 23 | println!("\t benchmark - run a simple benchmark encoding/decoding bundles"); 24 | } 25 | 26 | fn manifest_to_primary(manifest: &str) -> PrimaryBlock { 27 | let manifest = fs::read(manifest).expect("error reading bundle manifest"); 28 | let mut primary = bp7::primary::PrimaryBlockBuilder::default() 29 | .crc(bp7::crc::CrcValue::CrcNo) 30 | .creation_timestamp(bp7::CreationTimestamp::now()) 31 | .lifetime("1d".parse::().unwrap().into()); 32 | 33 | for line in String::from_utf8_lossy(&manifest) 34 | .split('\n') 35 | .map(|f| f.trim()) 36 | .filter(|l| !l.is_empty() || l.starts_with("^#")) 37 | .filter(|l| l.contains('=')) 38 | { 39 | let result: Vec<&str> = line.splitn(2, '=').map(|f| f.trim()).collect(); 40 | match result[0] { 41 | "destination" => { 42 | primary = primary.destination(result[1].try_into().unwrap()); 43 | } 44 | "source" => { 45 | primary = primary.source(result[1].try_into().unwrap()); 46 | } 47 | "report_to" => { 48 | primary = primary.report_to(result[1].try_into().unwrap()); 49 | } 50 | "lifetime" => { 51 | primary = 52 | primary.lifetime(result[1].parse::().unwrap().into()); 53 | } 54 | "flags" => { 55 | primary = primary.bundle_control_flags(result[1].parse().unwrap()); 56 | } 57 | _ => { 58 | eprintln!("unknown key: {}", result[0]); 59 | } 60 | } 61 | } 62 | primary.build().unwrap() 63 | } 64 | fn generate_bundle(primary_block: PrimaryBlock, payload: Vec, hex: bool) { 65 | let payload_block = bp7::new_payload_block(BlockControlFlags::empty(), payload); 66 | 67 | let mut b = bundle::Bundle::new(primary_block, vec![payload_block]); 68 | 69 | b.set_crc(bp7::crc::CRC_NO); 70 | b.validate().expect("created in invalid bundle"); 71 | let cbor = b.to_cbor(); 72 | 73 | if hex { 74 | println!("{}", bp7::helpers::hexify(&cbor)); 75 | } else { 76 | std::io::stdout().write_all(&cbor).unwrap(); 77 | } 78 | } 79 | 80 | fn encode(manifest: &str, data: &str, hex: bool) { 81 | let primary_block = manifest_to_primary(manifest); 82 | 83 | let payload = fs::read(data).expect("error reading bundle payload"); 84 | generate_bundle(primary_block, payload, hex); 85 | } 86 | 87 | fn encode_from_stdin(manifest: &str, hex: bool) { 88 | let primary_block = manifest_to_primary(manifest); 89 | let mut payload: Vec = Vec::new(); 90 | io::stdin() 91 | .read_to_end(&mut payload) 92 | .expect("Error reading from stdin."); 93 | generate_bundle(primary_block, payload, hex); 94 | } 95 | 96 | fn decode(bundle: &str, payload_only: bool) { 97 | let buf = unhexify(bundle).unwrap(); 98 | //println!("decode: {:02x?}", &buf); 99 | buf_to_bundle(buf, payload_only); 100 | } 101 | fn decode_from_stdin(payload_only: bool) { 102 | let mut buf: Vec = Vec::new(); 103 | io::stdin() 104 | .read_to_end(&mut buf) 105 | .expect("Error reading from stdin."); 106 | //println!("decode: {:02x?}", &buf); 107 | //serde_cbor::from_slice::(&buf).unwrap(); 108 | buf_to_bundle(buf, payload_only); 109 | } 110 | fn buf_to_bundle(buf: Vec, payload_only: bool) { 111 | let mut bndl: Bundle = buf.try_into().expect("Error decoding bundle!"); 112 | if payload_only { 113 | if bndl.payload().is_some() { 114 | std::io::stdout() 115 | .write_all(bndl.payload().unwrap()) 116 | .unwrap(); 117 | } 118 | } else { 119 | println!("{:#?}", &bndl); 120 | } 121 | if let Err(errs) = bndl.validate() { 122 | eprintln!("Error validating bundle: {:?}", errs); 123 | std::process::exit(1); 124 | } 125 | if !bndl.crc_valid() { 126 | eprintln!("Error validating bundle CRC"); 127 | std::process::exit(1); 128 | } 129 | } 130 | #[cfg(target_arch = "wasm32")] 131 | fn main() {} 132 | 133 | #[cfg(all(not(target_arch = "wasm32"), feature = "binary-build"))] 134 | fn main() { 135 | let args: Vec = env::args().collect(); 136 | 137 | if args.len() == 1 { 138 | usage(&args[0]); 139 | std::process::exit(1); 140 | } 141 | 142 | let cmd = &args[1]; 143 | match cmd.as_str() { 144 | "rnd" => { 145 | let mut bndl = rnd_bundle(bp7::CreationTimestamp::now()); 146 | eprintln!("{}", bndl.id()); 147 | if args.len() == 3 && args[2] == "-r" { 148 | std::io::stdout() 149 | .write_all(&bndl.to_cbor()) 150 | .expect("unable to write to stdout"); 151 | } else { 152 | println!("{}\n", hexify(&bndl.to_cbor())); 153 | } 154 | } 155 | "encode" => { 156 | let hex = if args.len() == 4 { 157 | false 158 | } else if args.len() == 5 { 159 | args[4] == "-x" 160 | } else { 161 | usage(&args[0]); 162 | std::process::exit(1); 163 | }; 164 | if args[3] == "-" { 165 | encode_from_stdin(&args[2], hex); 166 | } else { 167 | encode(&args[2], &args[3], hex); 168 | } 169 | } 170 | "decode" => { 171 | let payload_only = if args.len() == 3 { 172 | false 173 | } else if args.len() == 4 { 174 | args[3] == "-p" 175 | } else { 176 | usage(&args[0]); 177 | std::process::exit(1); 178 | }; 179 | if args[2] == "-" { 180 | decode_from_stdin(payload_only); 181 | } else { 182 | decode(&args[2], payload_only); 183 | } 184 | } 185 | "dtntime" => { 186 | if args.len() == 3 { 187 | let ts: bp7::dtntime::DtnTime = args[2].parse::().expect("invalid timestamp"); 188 | println!("{}", ts.string()); 189 | } else { 190 | println!("{}", bp7::dtn_time_now()); 191 | } 192 | } 193 | "d2u" => { 194 | if args.len() == 3 { 195 | let ts: bp7::dtntime::DtnTime = args[2].parse::().expect("invalid timestamp"); 196 | println!("{}", ts.unix()); 197 | } else { 198 | usage(&args[0]); 199 | } 200 | } 201 | "benchmark" => { 202 | let runs = 100_000; 203 | let crcno = bench_bundle_create(runs, crc::CRC_NO); 204 | let crc16 = bench_bundle_create(runs, crc::CRC_16); 205 | let crc32 = bench_bundle_create(runs, crc::CRC_32); 206 | 207 | //print!("{:x?}", crcno[0]); 208 | //println!("{}", bp7::hexify(&crcno[0])); 209 | 210 | bench_bundle_encode(runs, crc::CRC_NO); 211 | bench_bundle_encode(runs, crc::CRC_16); 212 | bench_bundle_encode(runs, crc::CRC_32); 213 | 214 | bench_bundle_load(runs, crc::CRC_NO, crcno); 215 | bench_bundle_load(runs, crc::CRC_16, crc16); 216 | bench_bundle_load(runs, crc::CRC_32, crc32); 217 | 218 | //dbg!(crcno[0].len()); 219 | //dbg!(crc16[0].len()); 220 | //dbg!(crc32[0].len()); 221 | } 222 | _ => usage(&args[0]), 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /tests/security_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::flags::*; 2 | #[cfg(feature = "bpsec")] 3 | use bp7::security::*; 4 | use bp7::*; 5 | use helpers::*; 6 | use std::convert::TryInto; 7 | use std::time::Duration; 8 | //use bp7::security::AES_128_GCM; 9 | 10 | #[test] 11 | #[cfg(feature = "bpsec")] 12 | fn security_data_tests() { 13 | let data = CanonicalData::Data(b"bla".to_vec()); 14 | let encoded_data = serde_cbor::to_vec(&data).expect("encoding error"); 15 | let decoded_data: CanonicalData = 16 | serde_cbor::from_slice(&encoded_data).expect("decoding error"); 17 | assert_eq!(data, decoded_data); 18 | } 19 | 20 | fn encode_decode_test_canonical(data: CanonicalBlock) { 21 | let encoded_data = serde_cbor::to_vec(&data).expect("encoding error"); 22 | let decoded_data: CanonicalBlock = 23 | serde_cbor::from_slice(&encoded_data).expect("decoding error"); 24 | assert_eq!(data, decoded_data); 25 | //println!("{:?}", hexify(&encoded_data)); 26 | //println!("{:?}", hexify(&decoded_data)); 27 | 28 | //println!("{:?}", decoded_data.data()); 29 | assert_eq!(decoded_data.data(), data.data()); 30 | } 31 | 32 | #[test] 33 | #[cfg(feature = "bpsec")] 34 | fn canonical_block_tests() { 35 | let data = 36 | bp7::security::new_integrity_block(1, BlockControlFlags::empty(), b"ABCDEFG".to_vec()); 37 | encode_decode_test_canonical(data); 38 | } 39 | 40 | #[test] 41 | #[cfg(feature = "bpsec")] 42 | fn rfc_example_tests() { 43 | simple_integrity_test(); 44 | 45 | //simple_confidentiality_test(); 46 | 47 | //multiple_sources_test(); 48 | 49 | // Example 4 - Security Blocks with Full Scope 50 | // https://www.rfc-editor.org/rfc/rfc9173.html#name-example-4-security-blocks-w 51 | //println!("Security Blocks with Full Scope"); 52 | // TODO 53 | //full_scope_test(); 54 | } 55 | 56 | #[test] 57 | #[cfg(feature = "bpsec")] 58 | /// # Example 1 - Simple Integrity 59 | /// 60 | /// ## Original Bundle 61 | /// 62 | /// ``` 63 | /// Block Block Block 64 | /// in Bundle Type Number 65 | /// +========================================+=======+========+ 66 | /// | Primary Block | N/A | 0 | 67 | /// +----------------------------------------+-------+--------+ 68 | /// | Payload Block | 1 | 1 | 69 | /// +----------------------------------------+-------+--------+ 70 | /// ``` 71 | /// 72 | /// ## Resulting Bundle 73 | /// 74 | /// ``` 75 | /// +========================================+=======+========+ 76 | /// | Primary Block | N/A | 0 | 77 | /// +----------------------------------------+-------+--------+ 78 | /// | Block Integrity Block | 11 | 2 | 79 | /// | OP(bib-integrity, target=1) | | | 80 | /// +----------------------------------------+-------+--------+ 81 | /// | Payload Block | 1 | 1 | 82 | /// +----------------------------------------+-------+--------+ 83 | /// ``` 84 | /// 85 | /// see rfc for more details: 86 | /// https://www.rfc-editor.org/rfc/rfc9173.html#name-example-1-simple-integrity 87 | fn simple_integrity_test() { 88 | println!("Simple Integrity Test"); 89 | 90 | // Create Original bundle 91 | let dst = eid::EndpointID::with_ipn(1, 2).unwrap(); 92 | let src = eid::EndpointID::with_ipn(2, 1).unwrap(); 93 | let now = dtntime::CreationTimestamp::with_time_and_seq(0, 40); 94 | 95 | let primary_block = primary::PrimaryBlockBuilder::default() 96 | .destination(dst) 97 | .source(src.clone()) 98 | .report_to(src) 99 | .creation_timestamp(now) 100 | .lifetime(Duration::from_millis(1000000)) 101 | .build() 102 | .unwrap(); 103 | let cbor_primary = serde_cbor::to_vec(&primary_block).unwrap(); 104 | let cbor_primary = hexify(&cbor_primary); 105 | let example_cbor_primary = "88070000820282010282028202018202820201820018281a000f4240"; 106 | assert_eq!(cbor_primary, example_cbor_primary); 107 | 108 | let payload_block = bp7::new_payload_block( 109 | BlockControlFlags::empty(), 110 | b"Ready to generate a 32-byte payload".to_vec(), 111 | ); 112 | let cbor_payload = serde_cbor::to_vec(&payload_block).unwrap(); 113 | let cbor_payload = hexify(&cbor_payload); 114 | let example_cbor_payload = 115 | "85010100005823526561647920746f2067656e657261746520612033322d62797465207061796c6f6164"; 116 | assert_eq!(cbor_payload, example_cbor_payload); 117 | 118 | // Create Block Integrity Block 119 | // Two Parts: First create IPPT then ASB 120 | 121 | // First Create Integrity-Protected Plaintext 122 | let sec_block_header: (CanonicalBlockType, u64, bp7::flags::BlockControlFlagsType) = ( 123 | bp7::security::INTEGRITY_BLOCK, 124 | 2, 125 | BlockControlFlags::empty().bits(), 126 | ); 127 | 128 | let _sec_ctx_para = BibSecurityContextParameter { 129 | sha_variant: Some((1, HMAC_SHA_512)), 130 | wrapped_key: None, 131 | integrity_scope_flags: Some((3, 0x0000)), 132 | }; 133 | 134 | let mut ippt = bp7::security::IpptBuilder::default() 135 | .primary_block(primary_block.clone()) 136 | .security_header(sec_block_header) 137 | .scope_flags(0x0000) 138 | .build(); 139 | let ippt_complete = ippt.create(&payload_block); 140 | let ippt_list = vec![(payload_block.block_number, &ippt_complete)]; 141 | 142 | let cbor_ippt_complete = hexify(&ippt_complete); 143 | let example_ippt_complete = 144 | "005823526561647920746f2067656e657261746520612033322d62797465207061796c6f6164"; 145 | //println!("{:?}", cbor_ippt_complete); 146 | 147 | assert_eq!(cbor_ippt_complete, example_ippt_complete); 148 | 149 | // Second Create Abstract Security Block 150 | let sec_ctx_para = 151 | bp7::security::BibSecurityContextParameter::new(Some((1, 7)), None, Some((3, 0x0000))); 152 | let mut sec_block_payload = bp7::security::IntegrityBlockBuilder::default() 153 | .security_targets(vec![1]) // Payload block 154 | .security_context_flags(1) // Parameters Present 155 | .security_source(EndpointID::with_ipn(2, 1).unwrap()) // ipn:2.1 156 | .security_context_parameters(sec_ctx_para) // 2 Parameters: HMAC 512/512 and No Additional Scope 157 | .build() 158 | .unwrap(); 159 | let key = unhexify("1a2b1a2b1a2b1a2b1a2b1a2b1a2b1a2b").unwrap(); 160 | let key_array: [u8; 16] = key.try_into().expect("slice with incorrect length"); 161 | sec_block_payload.compute_hmac(key_array, ippt_list); 162 | 163 | // TODO: key mgmt 164 | // used key: 1a2b1a2b1a2b1a2b1a2b1a2b1a2b1a2b 165 | // The Signature 166 | let signature = hexify(&sec_block_payload.security_results[0][0].1); 167 | let example_signature = "3bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e1"; 168 | assert_eq!(signature, example_signature); 169 | //println!("{:?}", hexify(&sec_block_payload.security_results[0][0].1)); 170 | 171 | // The CBOR encoding of the BIB block-type-specific data field (the abstract security block): 172 | let canonical_payload = sec_block_payload.to_cbor(); 173 | let cbor_canonical_payload = hexify(&canonical_payload); 174 | let example_canonical_payload = "810101018202820201828201078203008181820158403bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e1"; 175 | assert_eq!(cbor_canonical_payload, example_canonical_payload); 176 | 177 | // The BIB 178 | let block_integrity_block = 179 | bp7::security::new_integrity_block(2, BlockControlFlags::empty(), canonical_payload); 180 | let cbor_bib = serde_cbor::to_vec(&block_integrity_block).unwrap(); 181 | let cbor_bib = hexify(&cbor_bib); 182 | let example_bib = "850b0200005856810101018202820201828201078203008181820158403bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e1"; 183 | assert_eq!(cbor_bib, example_bib); 184 | 185 | // The CBOR encoding of the full output bundle, with the BIB: 186 | let mut b = bundle::BundleBuilder::default() 187 | .primary(primary_block) 188 | .canonicals(vec![payload_block, block_integrity_block]) 189 | .build() 190 | .unwrap(); 191 | b.set_crc(crc::CRC_NO); 192 | b.calculate_crc(); 193 | 194 | let cbor_bundle = hexify(&b.to_cbor()); 195 | let example_bundle = "9f88070000820282010282028202018202820201820018281a000f4240850b0200005856810101018202820201828201078203008181820158403bdc69b3a34a2b5d3a8554368bd1e808f606219d2a10a846eae3886ae4ecc83c4ee550fdfb1cc636b904e2f1a73e303dcd4b6ccece003e95e8164dcc89a156e185010100005823526561647920746f2067656e657261746520612033322d62797465207061796c6f6164ff"; 196 | assert_eq!(cbor_bundle, example_bundle); 197 | } 198 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::TryInto, 3 | ffi::{CStr, CString}, 4 | os::raw::c_char, 5 | }; 6 | 7 | use crate::{ 8 | Bundle, CreationTimestamp, EndpointID, flags::BlockControlFlags, flags::BundleControlFlags, 9 | helpers, new_payload_block, primary, 10 | }; 11 | 12 | #[repr(C)] 13 | pub struct Buffer { 14 | data: *mut u8, 15 | len: u32, 16 | } 17 | 18 | #[repr(C)] 19 | pub struct BundleMetaData { 20 | /// The source EndpointID 21 | src: *mut c_char, 22 | /// The destination EndpointID 23 | dst: *mut c_char, 24 | /// The creation timestamp in DTN time 25 | timestamp: u64, 26 | /// The sequence number 27 | seqno: u64, 28 | /// The lifetime of a bundle in ms 29 | lifetime: u64, 30 | } 31 | 32 | /// A simple test for the bp7 FFI interface. 33 | /// On success it should always return the number 23. 34 | #[unsafe(no_mangle)] 35 | pub extern "C" fn bp7_working() -> u8 { 36 | 23 37 | } 38 | 39 | /// Another simple test returning a dynamic buffer with a fixed content. 40 | /// The returned buffer contains the following bytes: [0x42, 0x43, 0x44, 0x45] 41 | #[unsafe(no_mangle)] 42 | pub extern "C" fn bp7_buffer_test() -> *mut Buffer { 43 | let input = vec![0x42, 0x43, 0x44, 0x45]; 44 | 45 | let mut buf = input.into_boxed_slice(); 46 | let data = buf.as_mut_ptr(); 47 | let len = buf.len() as u32; 48 | std::mem::forget(buf); 49 | Box::into_raw(Box::new(Buffer { data, len })) 50 | } 51 | 52 | /// Generate a random bundle as a raw buffer. 53 | #[unsafe(no_mangle)] 54 | pub extern "C" fn helper_rnd_bundle() -> *mut Buffer { 55 | let mut bndl = helpers::rnd_bundle(CreationTimestamp::now()); 56 | 57 | let mut buf = bndl.to_cbor().into_boxed_slice(); 58 | let data = buf.as_mut_ptr(); 59 | let len = buf.len() as u32; 60 | std::mem::forget(buf); 61 | Box::into_raw(Box::new(Buffer { data, len })) 62 | } 63 | 64 | /// Free the memory of a given buffer. 65 | /// 66 | /// # Safety 67 | /// 68 | /// Should only be called from FFI interface. 69 | /// This function can lead to UB as pointer cannot be validated! 70 | #[unsafe(no_mangle)] 71 | pub unsafe extern "C" fn buffer_free(buf: *mut Buffer) { 72 | unsafe { 73 | if buf.is_null() { 74 | return; 75 | } 76 | drop(Box::from_raw(buf)); 77 | } 78 | } 79 | 80 | /// Try to decode a bundle from a given buffer. 81 | /// 82 | /// In case of failure, a null pointer is returned instead of a bundle. 83 | /// 84 | /// # Safety 85 | /// 86 | /// Should only be called from FFI interface. 87 | /// This function can lead to UB as pointer cannot be validated! 88 | #[unsafe(no_mangle)] 89 | pub unsafe extern "C" fn bundle_from_cbor(ptr: *mut Buffer) -> *mut Bundle { 90 | unsafe { 91 | assert!(!ptr.is_null()); 92 | let buf = &mut *ptr; 93 | //println!("buf len {}", buf.len); 94 | assert!(!buf.data.is_null()); 95 | let buffer = core::slice::from_raw_parts(buf.data, buf.len as usize); 96 | //println!("buffer {}", helpers::hexify(buffer)); 97 | let bndl: Bundle = buffer 98 | .to_owned() 99 | .try_into() 100 | .expect("failed to load bundle from buffer"); 101 | if bndl.validate().is_ok() { 102 | Box::into_raw(Box::new(bndl)) 103 | } else { 104 | std::ptr::null_mut::() 105 | } 106 | } 107 | } 108 | 109 | /// Encode a given bundle a CBOR byte buffer 110 | /// 111 | /// # Safety 112 | /// 113 | /// Should only be called from FFI interface. 114 | /// This function can lead to UB as pointer cannot be validated! 115 | #[unsafe(no_mangle)] 116 | pub unsafe extern "C" fn bundle_to_cbor(bndl: *mut Bundle) -> *mut Buffer { 117 | unsafe { 118 | assert!(!bndl.is_null()); 119 | let bndl = &mut *bndl; 120 | let mut buf = bndl.to_cbor().into_boxed_slice(); 121 | let data = buf.as_mut_ptr(); 122 | let len = buf.len() as u32; 123 | std::mem::forget(buf); 124 | Box::into_raw(Box::new(Buffer { data, len })) 125 | } 126 | } 127 | 128 | /// Create a new bundle with standard configuration and a given payload 129 | /// 130 | /// # Safety 131 | /// 132 | /// Should only be called from FFI interface. 133 | /// This function can lead to UB as pointer cannot be validated! 134 | #[unsafe(no_mangle)] 135 | pub unsafe extern "C" fn bundle_new_default( 136 | src: *const c_char, 137 | dst: *const c_char, 138 | lifetime: u64, 139 | ptr: *mut Buffer, 140 | ) -> *mut Bundle { 141 | unsafe { 142 | assert!(!src.is_null()); 143 | let c_str_src = CStr::from_ptr(src); 144 | 145 | let r_src = c_str_src.to_str().unwrap(); 146 | let src_eid: EndpointID = r_src.try_into().unwrap(); 147 | 148 | assert!(!dst.is_null()); 149 | let c_str_dst = CStr::from_ptr(dst); 150 | let r_dst = c_str_dst.to_str().unwrap(); 151 | let dst_eid: EndpointID = r_dst.try_into().unwrap(); 152 | 153 | assert!(!ptr.is_null()); 154 | let payload = &mut *ptr; 155 | assert!(!payload.data.is_null()); 156 | let data = core::slice::from_raw_parts(payload.data, payload.len as usize); 157 | 158 | let pblock = primary::PrimaryBlockBuilder::default() 159 | .bundle_control_flags(BundleControlFlags::BUNDLE_MUST_NOT_FRAGMENTED.bits()) 160 | .destination(dst_eid) 161 | .source(src_eid.clone()) 162 | .report_to(src_eid) 163 | .creation_timestamp(CreationTimestamp::now()) 164 | .lifetime(core::time::Duration::from_millis(lifetime)) 165 | .build() 166 | .unwrap(); 167 | let mut b = Bundle::new( 168 | pblock, 169 | vec![new_payload_block( 170 | BlockControlFlags::empty(), 171 | data.to_owned(), 172 | )], 173 | ); 174 | b.set_crc(crate::crc::CRC_NO); 175 | b.sort_canonicals(); 176 | Box::into_raw(Box::new(b)) 177 | } 178 | } 179 | 180 | /// Frees the memory of a given bundle. 181 | /// # Safety 182 | /// 183 | /// Should only be called from FFI interface. 184 | /// This function can lead to UB as pointer cannot be validated! 185 | #[unsafe(no_mangle)] 186 | pub unsafe extern "C" fn bundle_free(ptr: *mut Bundle) { 187 | unsafe { 188 | if ptr.is_null() { 189 | return; 190 | } 191 | drop(Box::from_raw(ptr)); 192 | } 193 | } 194 | 195 | /// Get the metadata from a given bundle. 196 | /// 197 | /// # Safety 198 | /// 199 | /// Should only be called from FFI interface. 200 | /// This function can lead to UB as pointer cannot be validated! 201 | #[unsafe(no_mangle)] 202 | pub unsafe extern "C" fn bundle_get_metadata(bndl: *mut Bundle) -> *mut BundleMetaData { 203 | unsafe { 204 | assert!(!bndl.is_null()); 205 | let bndl = &mut *bndl; 206 | let timestamp = bndl.primary.creation_timestamp.dtntime(); 207 | let seqno = bndl.primary.creation_timestamp.seqno(); 208 | let lifetime = bndl.primary.lifetime.as_millis() as u64; 209 | let src_str = CString::new(bndl.primary.source.to_string()).unwrap(); 210 | let src = src_str.into_raw(); 211 | let dst_str = CString::new(bndl.primary.destination.to_string()).unwrap(); 212 | let dst = dst_str.into_raw(); 213 | Box::into_raw(Box::new(BundleMetaData { 214 | src, 215 | dst, 216 | timestamp, 217 | seqno, 218 | lifetime, 219 | })) 220 | } 221 | } 222 | 223 | /// Frees a BundleMetaData struct. 224 | /// 225 | /// # Safety 226 | /// 227 | /// Should only be called from FFI interface. 228 | /// This function can lead to UB as pointer cannot be validated! 229 | #[unsafe(no_mangle)] 230 | pub unsafe extern "C" fn bundle_metadata_free(ptr: *mut BundleMetaData) { 231 | unsafe { 232 | assert!(!ptr.is_null()); 233 | let meta = &mut *ptr; 234 | if !meta.src.is_null() { 235 | drop(CString::from_raw(meta.src)); 236 | } 237 | 238 | if !meta.dst.is_null() { 239 | drop(CString::from_raw(meta.dst)); 240 | } 241 | } 242 | } 243 | 244 | /// Check if a given bundle is valid. 245 | /// This checks the primary block as well as the validity of all canonical bundles. 246 | /// 247 | /// # Safety 248 | /// 249 | /// Should only be called from FFI interface. 250 | /// This function can lead to UB as pointer cannot be validated! 251 | #[unsafe(no_mangle)] 252 | pub unsafe extern "C" fn bundle_is_valid(bndl: *mut Bundle) -> bool { 253 | unsafe { 254 | assert!(!bndl.is_null()); 255 | let bndl = &mut *bndl; 256 | bndl.validate().is_ok() 257 | } 258 | } 259 | 260 | /// Get the payload of a given bundle. 261 | /// 262 | /// # Safety 263 | /// 264 | /// Should only be called from FFI interface. 265 | /// This function can lead to UB as pointer cannot be validated! 266 | #[unsafe(no_mangle)] 267 | pub unsafe extern "C" fn bundle_payload(bndl: *mut Bundle) -> *mut Buffer { 268 | unsafe { 269 | if !bndl.is_null() { 270 | let bndl = &mut *bndl; 271 | if let Some(payload) = bndl.payload() { 272 | let mut buf = payload.clone().into_boxed_slice(); 273 | let data = buf.as_mut_ptr(); 274 | let len = buf.len() as u32; 275 | std::mem::forget(buf); 276 | return Box::into_raw(Box::new(Buffer { data, len })); 277 | } 278 | } 279 | Box::into_raw(Box::new(Buffer { 280 | data: std::ptr::null_mut(), 281 | len: 0, 282 | })) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/bundle_tests.rs: -------------------------------------------------------------------------------- 1 | use bp7::flags::*; 2 | use bp7::*; 3 | use std::convert::TryFrom; 4 | use std::convert::TryInto; 5 | use std::time::Duration; 6 | 7 | fn new_complete_bundle(crc_type: bp7::crc::CrcRawType) -> Bundle { 8 | let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 9 | let src = eid::EndpointID::with_dtn("node1/123456").unwrap(); 10 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 11 | 12 | let pblock = primary::PrimaryBlockBuilder::default() 13 | .destination(dst) 14 | .source(src.clone()) 15 | .report_to(src) 16 | .creation_timestamp(now) 17 | .lifetime(Duration::from_secs(60 * 60)) 18 | .build() 19 | .unwrap(); 20 | 21 | let mut b = bundle::BundleBuilder::default() 22 | .primary(pblock) 23 | .canonicals(vec![ 24 | canonical::new_bundle_age_block( 25 | 2, // block number 26 | BlockControlFlags::empty(), // flags 27 | 0, // time elapsed 28 | ), 29 | canonical::new_hop_count_block( 30 | 3, // block number 31 | BlockControlFlags::empty(), // flags 32 | 16, // max hops 33 | ), 34 | canonical::new_previous_node_block( 35 | 4, // block number 36 | BlockControlFlags::empty(), // flags 37 | "dtn://node23".try_into().unwrap(), // previous node EID 38 | ), 39 | canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()), 40 | ]) 41 | .build() 42 | .unwrap(); 43 | b.set_crc(crc_type); 44 | b.calculate_crc(); 45 | assert!(b.validate().is_ok()); 46 | b 47 | } 48 | 49 | fn new_empty_bundle(crc_type: bp7::crc::CrcRawType) -> Bundle { 50 | let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 51 | let src = eid::EndpointID::with_dtn("node1/123456").unwrap(); 52 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 53 | 54 | let pblock = primary::PrimaryBlockBuilder::default() 55 | .destination(dst) 56 | .source(src.clone()) 57 | .report_to(src) 58 | .creation_timestamp(now) 59 | .lifetime(Duration::from_secs(60 * 60)) 60 | .build() 61 | .unwrap(); 62 | 63 | let mut b = bundle::Bundle { 64 | primary: pblock, 65 | canonicals: vec![], 66 | }; 67 | b.set_crc(crc_type); 68 | b.calculate_crc(); 69 | assert!(b.validate().is_err()); 70 | b 71 | } 72 | fn new_complete_bundle_invalid(crc_type: bp7::crc::CrcRawType) -> Bundle { 73 | let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 74 | let src = eid::EndpointID::with_dtn("node1/123456").unwrap(); 75 | let now = dtntime::CreationTimestamp::with_time_and_seq(dtntime::dtn_time_now(), 0); 76 | 77 | let pblock = primary::PrimaryBlockBuilder::default() 78 | .destination(dst) 79 | .source(src.clone()) 80 | .report_to(src) 81 | .creation_timestamp(now) 82 | .lifetime(Duration::from_secs(60 * 60)) 83 | .build() 84 | .unwrap(); 85 | 86 | let mut b = bundle::BundleBuilder::default() 87 | .primary(pblock) 88 | .canonicals(vec![ 89 | canonical::new_payload_block(BlockControlFlags::empty(), b"ABC".to_vec()), 90 | canonical::new_bundle_age_block( 91 | 2, // block number 92 | BlockControlFlags::empty(), // flags 93 | 0, // time elapsed 94 | ), 95 | canonical::new_hop_count_block( 96 | 2, // block number 97 | BlockControlFlags::empty(), // flags 98 | 16, // max hops 99 | ), 100 | canonical::new_previous_node_block( 101 | 2, // block number 102 | BlockControlFlags::empty(), // flags 103 | "dtn://node23".try_into().unwrap(), // previous node EID 104 | ), 105 | ]) 106 | .build() 107 | .unwrap(); 108 | b.set_crc(crc_type); 109 | b.calculate_crc(); 110 | b 111 | } 112 | 113 | #[test] 114 | fn bundle_tests() { 115 | let mut bndl = new_complete_bundle(crc::CRC_NO); 116 | let encoded = bndl.to_cbor(); 117 | let decoded: Bundle = encoded.try_into().unwrap(); 118 | assert_eq!(bndl, decoded); 119 | 120 | let mut bndl = new_complete_bundle(crc::CRC_16); 121 | let encoded = bndl.to_cbor(); 122 | let decoded: Bundle = encoded.try_into().unwrap(); 123 | assert_eq!(bndl, decoded); 124 | 125 | let mut bndl = new_complete_bundle(crc::CRC_32); 126 | let encoded = bndl.to_cbor(); 127 | let decoded: Bundle = encoded.try_into().unwrap(); 128 | assert_eq!(bndl, decoded); 129 | 130 | let mut bndl = new_complete_bundle(crc::CRC_32); 131 | let encoded = bndl.to_cbor(); 132 | let decoded: Bundle = encoded.as_slice().try_into().unwrap(); 133 | assert_eq!(bndl, decoded); 134 | } 135 | 136 | #[test] 137 | fn bundle_id_tests() { 138 | let dst = eid::EndpointID::with_dtn("node2/inbox").unwrap(); 139 | let src = eid::EndpointID::with_dtn("node1/outbox").unwrap(); 140 | 141 | let pblock = primary::PrimaryBlockBuilder::default() 142 | .destination(dst) 143 | .source(src.clone()) 144 | .report_to(src) 145 | .creation_timestamp(CreationTimestamp::with_time_and_seq(0, 0)) 146 | .lifetime(Duration::from_secs(60 * 60)) 147 | .build() 148 | .unwrap(); 149 | 150 | let mut b = bundle::Bundle { 151 | primary: pblock, 152 | canonicals: vec![], 153 | }; 154 | assert_eq!(b.id(), "dtn://node1/outbox-0-0"); 155 | 156 | assert_eq!(b.to_string(), "dtn://node1/outbox-0-0_dtn://node2/inbox"); 157 | 158 | b.primary.source = eid::EndpointID::with_dtn("node1").unwrap(); 159 | assert_eq!(b.id(), "dtn://node1/-0-0"); 160 | 161 | b.primary.destination = eid::EndpointID::with_dtn("node2").unwrap(); 162 | assert_eq!(b.to_string(), "dtn://node1/-0-0_dtn://node2/"); 163 | } 164 | 165 | #[test] 166 | fn bundle_helpers() { 167 | let bndl = new_complete_bundle(crc::CRC_NO); 168 | assert!(bndl.previous_node().is_some()); 169 | 170 | let bndl = new_empty_bundle(crc::CRC_NO); 171 | assert!(bndl.previous_node().is_none()); 172 | } 173 | #[test] 174 | fn bundle_invalid_cbor() { 175 | let invalid_cbor_bytes = vec![0x41, 0x41]; 176 | let maybe_bundle = Bundle::try_from(invalid_cbor_bytes); 177 | assert!(maybe_bundle.is_err()); 178 | } 179 | 180 | #[test] 181 | fn bundle_invalid_cblock_numbers_tests() { 182 | assert!(new_complete_bundle_invalid(crc::CRC_NO).validate().is_err()); 183 | assert!(new_complete_bundle_invalid(crc::CRC_16).validate().is_err()); 184 | assert!(new_complete_bundle_invalid(crc::CRC_32).validate().is_err()); 185 | } 186 | 187 | #[test] 188 | fn bundle_canonical_update_tests() { 189 | let mut bndl = new_complete_bundle(crc::CRC_NO); 190 | { 191 | let hcblock = bndl.extension_block_by_type_mut(HOP_COUNT_BLOCK).unwrap(); 192 | assert!(hcblock.hop_count_increase()); 193 | } 194 | let hcb2 = bndl.extension_block_by_type_mut(HOP_COUNT_BLOCK).unwrap(); 195 | assert!(hcb2.hop_count_get().unwrap() == (16, 1)); 196 | 197 | let mut bndl = new_complete_bundle(crc::CRC_NO); 198 | assert!(bndl.update_extensions("dtn://newnode".try_into().unwrap(), 23)); 199 | 200 | let cb = bndl.extension_block_by_type_mut(HOP_COUNT_BLOCK).unwrap(); 201 | assert!(cb.hop_count_get().unwrap() == (16, 1)); 202 | let cb = bndl.extension_block_by_type_mut(BUNDLE_AGE_BLOCK).unwrap(); 203 | assert!(cb.bundle_age_get().unwrap() == 23); 204 | let cb = bndl 205 | .extension_block_by_type_mut(PREVIOUS_NODE_BLOCK) 206 | .unwrap(); 207 | assert!(cb.previous_node_get().unwrap() == &EndpointID::try_from("dtn://newnode").unwrap()); 208 | } 209 | 210 | #[test] 211 | fn bundle_add_cblock() { 212 | let mut b = new_empty_bundle(crc::CRC_NO); 213 | assert!(b.canonicals.is_empty()); 214 | 215 | b.add_canonical_block(canonical::new_hop_count_block( 216 | 666, // block number 217 | BlockControlFlags::empty(), // flags 218 | 16, // max hops 219 | )); 220 | assert!(b.canonicals.len() == 1); 221 | 222 | b.add_canonical_block(canonical::new_hop_count_block( 223 | 666, // block number 224 | BlockControlFlags::empty(), // flags 225 | 16, // max hops 226 | )); 227 | // Already present, should be ignored 228 | assert!(b.canonicals.len() == 1); 229 | 230 | b.add_canonical_block(canonical::new_bundle_age_block( 231 | 666, // block number 232 | BlockControlFlags::empty(), // flags 233 | 0, // time elapsed 234 | )); 235 | assert!(b.canonicals.len() == 2); 236 | 237 | b.add_canonical_block(canonical::new_payload_block( 238 | BlockControlFlags::empty(), 239 | b"ABC".to_vec(), 240 | )); 241 | assert!(b.canonicals.len() == 3); 242 | 243 | let numbers: Vec = b.canonicals.iter().map(|c| c.block_number).collect(); 244 | //numbers.sort(); 245 | assert_eq!(numbers, vec![3, 2, 1]); 246 | 247 | let mut b = new_complete_bundle(crc::CRC_NO); 248 | assert_eq!(b.payload().unwrap(), b"ABC"); 249 | let pl = b 250 | .extension_block_by_type_mut(bp7::canonical::PAYLOAD_BLOCK) 251 | .unwrap(); 252 | pl.set_data(bp7::canonical::CanonicalData::Data(b"XYZ".to_vec())); 253 | assert_eq!(b.payload().unwrap(), b"XYZ"); 254 | 255 | b.set_payload("123".into()); 256 | assert_eq!(b.payload().unwrap(), b"123"); 257 | } 258 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /src/primary.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::error::ErrorList; 3 | 4 | use super::bundle::*; 5 | use super::crc::*; 6 | use super::dtntime::*; 7 | use super::eid::*; 8 | use super::flags::*; 9 | use core::fmt; 10 | use core::{convert::TryFrom, time::Duration}; 11 | use serde::de::{SeqAccess, Visitor}; 12 | use serde::ser::{SerializeSeq, Serializer}; 13 | use serde::{Deserialize, Deserializer, Serialize, de}; 14 | use thiserror::Error; 15 | 16 | /****************************** 17 | * 18 | * Primary Block 19 | * 20 | ******************************/ 21 | 22 | #[derive(Error, Debug)] 23 | pub enum PrimaryBuilderError { 24 | #[error("no destination endpoint was provided")] 25 | NoDestination, 26 | } 27 | 28 | #[derive(Default, Clone, Debug, PartialEq)] 29 | pub struct PrimaryBlockBuilder { 30 | bundle_control_flags: BundleControlFlagsType, 31 | crc: CrcValue, 32 | destination: EndpointID, 33 | source: EndpointID, 34 | report_to: EndpointID, 35 | creation_timestamp: CreationTimestamp, 36 | lifetime: Duration, 37 | fragmentation_offset: FragOffsetType, 38 | total_data_length: TotalDataLengthType, 39 | } 40 | impl PrimaryBlockBuilder { 41 | pub fn new() -> Self { 42 | // let mut builder = PrimaryBlockBuilder::default(); 43 | // should this be set by default? 44 | // builder.creation_timestamp = CreationTimestamp::now(); 45 | PrimaryBlockBuilder::default() 46 | } 47 | pub fn bundle_control_flags(mut self, flags: BundleControlFlagsType) -> Self { 48 | self.bundle_control_flags = flags; 49 | self 50 | } 51 | pub fn crc(mut self, crc: CrcValue) -> Self { 52 | self.crc = crc; 53 | self 54 | } 55 | pub fn destination(mut self, dest: EndpointID) -> Self { 56 | self.destination = dest; 57 | self 58 | } 59 | pub fn source(mut self, source: EndpointID) -> Self { 60 | self.source = source; 61 | self 62 | } 63 | pub fn report_to(mut self, report_to: EndpointID) -> Self { 64 | self.report_to = report_to; 65 | self 66 | } 67 | pub fn creation_timestamp(mut self, creation_timestamp: CreationTimestamp) -> Self { 68 | self.creation_timestamp = creation_timestamp; 69 | self 70 | } 71 | pub fn lifetime(mut self, lifetime: Duration) -> Self { 72 | self.lifetime = lifetime; 73 | self 74 | } 75 | pub fn fragmentation_offset(mut self, offset: FragOffsetType) -> Self { 76 | self.fragmentation_offset = offset; 77 | self 78 | } 79 | pub fn total_data_length(mut self, length: TotalDataLengthType) -> Self { 80 | self.total_data_length = length; 81 | self 82 | } 83 | pub fn build(self) -> Result { 84 | if self.destination == EndpointID::none() { 85 | Err(PrimaryBuilderError::NoDestination) 86 | } else { 87 | Ok(PrimaryBlock { 88 | version: DTN_VERSION, 89 | bundle_control_flags: self.bundle_control_flags, 90 | crc: self.crc, 91 | destination: self.destination, 92 | source: self.source, 93 | report_to: self.report_to, 94 | creation_timestamp: self.creation_timestamp, 95 | lifetime: self.lifetime, 96 | fragmentation_offset: self.fragmentation_offset, 97 | total_data_length: self.total_data_length, 98 | }) 99 | } 100 | } 101 | } 102 | 103 | //#[derive(Debug, Serialize_tuple, Deserialize_tuple, Clone)] 104 | #[derive(Debug, Clone, PartialEq)] 105 | pub struct PrimaryBlock { 106 | version: DtnVersionType, 107 | pub bundle_control_flags: BundleControlFlagsType, 108 | pub crc: CrcValue, 109 | pub destination: EndpointID, 110 | pub source: EndpointID, 111 | pub report_to: EndpointID, 112 | pub creation_timestamp: CreationTimestamp, 113 | /// in milliseconds 114 | pub lifetime: Duration, 115 | pub fragmentation_offset: FragOffsetType, 116 | pub total_data_length: TotalDataLengthType, 117 | } 118 | 119 | impl Serialize for PrimaryBlock { 120 | fn serialize(&self, serializer: S) -> Result 121 | where 122 | S: Serializer, 123 | { 124 | let num_elems = if !self.crc.has_crc() && !self.has_fragmentation() { 125 | 8 126 | } else if self.crc.has_crc() && !self.has_fragmentation() { 127 | 9 128 | } else if !self.crc.has_crc() && self.has_fragmentation() { 129 | 10 130 | } else { 131 | 11 132 | }; 133 | 134 | let mut seq = serializer.serialize_seq(Some(num_elems))?; 135 | seq.serialize_element(&self.version)?; 136 | seq.serialize_element(&self.bundle_control_flags)?; 137 | seq.serialize_element(&self.crc.to_code())?; 138 | seq.serialize_element(&self.destination)?; 139 | seq.serialize_element(&self.source)?; 140 | seq.serialize_element(&self.report_to)?; 141 | seq.serialize_element(&self.creation_timestamp)?; 142 | seq.serialize_element(&(self.lifetime.as_millis() as u64))?; 143 | if self.has_fragmentation() { 144 | seq.serialize_element(&self.fragmentation_offset)?; 145 | seq.serialize_element(&self.total_data_length)?; 146 | } 147 | 148 | if self.crc.has_crc() { 149 | seq.serialize_element(&serde_bytes::Bytes::new(self.crc.bytes().unwrap()))?; 150 | } 151 | 152 | seq.end() 153 | } 154 | } 155 | 156 | impl<'de> Deserialize<'de> for PrimaryBlock { 157 | fn deserialize(deserializer: D) -> Result 158 | where 159 | D: Deserializer<'de>, 160 | { 161 | struct PrimaryBlockVisitor; 162 | 163 | impl<'de> Visitor<'de> for PrimaryBlockVisitor { 164 | type Value = PrimaryBlock; 165 | 166 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 167 | formatter.write_str("PrimaryBlock") 168 | } 169 | 170 | fn visit_seq(self, mut seq: V) -> Result 171 | where 172 | V: SeqAccess<'de>, 173 | { 174 | let version: DtnVersionType = seq 175 | .next_element()? 176 | .ok_or_else(|| de::Error::invalid_length(0, &self))?; 177 | let bundle_control_flags: BundleControlFlagsType = seq 178 | .next_element()? 179 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 180 | let crc_type: CrcRawType = seq 181 | .next_element()? 182 | .ok_or_else(|| de::Error::invalid_length(2, &self))?; 183 | let destination: EndpointID = seq 184 | .next_element()? 185 | .ok_or_else(|| de::Error::invalid_length(3, &self))?; 186 | let source: EndpointID = seq 187 | .next_element()? 188 | .ok_or_else(|| de::Error::invalid_length(4, &self))?; 189 | let report_to: EndpointID = seq 190 | .next_element()? 191 | .ok_or_else(|| de::Error::invalid_length(5, &self))?; 192 | let creation_timestamp: CreationTimestamp = seq 193 | .next_element()? 194 | .ok_or_else(|| de::Error::invalid_length(6, &self))?; 195 | let lifetime_u64: u64 = seq 196 | .next_element()? 197 | .ok_or_else(|| de::Error::invalid_length(7, &self))?; 198 | let lifetime = Duration::from_millis(lifetime_u64); 199 | 200 | let rest = seq.size_hint().unwrap_or(0); 201 | let mut fragmentation_offset: FragOffsetType = 0; 202 | let mut total_data_length: TotalDataLengthType = 0; 203 | 204 | if rest > 1 { 205 | fragmentation_offset = seq 206 | .next_element()? 207 | .ok_or_else(|| de::Error::invalid_length(8, &self))?; 208 | total_data_length = seq 209 | .next_element()? 210 | .ok_or_else(|| de::Error::invalid_length(9, &self))?; 211 | } 212 | let crc = if crc_type == CRC_NO { 213 | CrcValue::CrcNo 214 | } else if crc_type == CRC_16 { 215 | let crcbuf: ByteBuffer = seq 216 | .next_element::()? 217 | .ok_or_else(|| de::Error::invalid_length(7 + rest, &self))? 218 | .into_vec(); 219 | let mut outbuf: [u8; 2] = [0; 2]; 220 | if crcbuf.len() != outbuf.len() { 221 | return Err(de::Error::invalid_length(7 + rest, &self)); 222 | } 223 | outbuf.copy_from_slice(&crcbuf); 224 | CrcValue::Crc16(outbuf) 225 | } else if crc_type == CRC_32 { 226 | let crcbuf: ByteBuffer = seq 227 | .next_element::()? 228 | .ok_or_else(|| de::Error::invalid_length(7 + rest, &self))? 229 | .into_vec(); 230 | let mut outbuf: [u8; 4] = [0; 4]; 231 | if crcbuf.len() != outbuf.len() { 232 | return Err(de::Error::invalid_length(7 + rest, &self)); 233 | } 234 | outbuf.copy_from_slice(&crcbuf); 235 | CrcValue::Crc32(outbuf) 236 | } else { 237 | CrcValue::Unknown(crc_type) 238 | }; 239 | Ok(PrimaryBlock { 240 | version, 241 | bundle_control_flags, 242 | crc, 243 | destination, 244 | source, 245 | report_to, 246 | creation_timestamp, 247 | lifetime, 248 | fragmentation_offset, 249 | total_data_length, 250 | }) 251 | } 252 | } 253 | 254 | deserializer.deserialize_any(PrimaryBlockVisitor) 255 | } 256 | } 257 | impl Default for PrimaryBlock { 258 | fn default() -> Self { 259 | PrimaryBlock::new() 260 | } 261 | } 262 | 263 | impl PrimaryBlock { 264 | pub fn new() -> PrimaryBlock { 265 | PrimaryBlock { 266 | version: DTN_VERSION, 267 | bundle_control_flags: 0, 268 | crc: CrcValue::CrcNo, 269 | destination: EndpointID::new(), 270 | source: EndpointID::new(), 271 | report_to: EndpointID::new(), 272 | creation_timestamp: CreationTimestamp::new(), 273 | lifetime: Duration::new(0, 0), 274 | fragmentation_offset: 0, 275 | total_data_length: 0, 276 | } 277 | } 278 | 279 | pub fn has_fragmentation(&self) -> bool { 280 | self.bundle_control_flags 281 | .contains(BundleControlFlags::BUNDLE_IS_FRAGMENT) 282 | } 283 | pub fn is_lifetime_exceeded(&self) -> bool { 284 | if self.creation_timestamp.dtntime() == 0 { 285 | return false; 286 | } 287 | 288 | let now = crate::dtn_time_now(); 289 | self.creation_timestamp.dtntime() + (self.lifetime.as_millis() as u64) <= now 290 | } 291 | pub fn validate(&self) -> Result<(), ErrorList> { 292 | let mut errors: ErrorList = Vec::new(); 293 | 294 | if self.version != DTN_VERSION { 295 | errors.push(Error::PrimaryBlockError(format!( 296 | "Wrong version, {} instead of {}", 297 | self.version, DTN_VERSION 298 | ))); 299 | } 300 | 301 | // bundle control flags 302 | if let Err(mut err) = self.bundle_control_flags.validate() { 303 | errors.append(&mut err); 304 | } 305 | 306 | if let Err(chk_err) = self.destination.validate() { 307 | errors.push(chk_err.into()); 308 | } 309 | 310 | if let Err(chk_err) = self.source.validate() { 311 | errors.push(chk_err.into()); 312 | } 313 | if let Err(chk_err) = self.report_to.validate() { 314 | errors.push(chk_err.into()); 315 | } 316 | 317 | if self.has_crc() && !self.clone().check_crc() { 318 | errors.push(Error::PrimaryBlockError("CRC check failed".to_string())); 319 | } 320 | 321 | if !errors.is_empty() { 322 | return Err(errors); 323 | } 324 | Ok(()) 325 | } 326 | } 327 | 328 | impl CrcBlock for PrimaryBlock { 329 | fn crc_value(&self) -> &CrcValue { 330 | &self.crc 331 | } 332 | fn set_crc(&mut self, crc: CrcValue) { 333 | self.crc = crc; 334 | } 335 | } 336 | impl Block for PrimaryBlock { 337 | fn to_cbor(&self) -> ByteBuffer { 338 | serde_cbor::to_vec(&self).expect("Error exporting primary block to cbor") 339 | } 340 | } 341 | pub fn new_primary_block( 342 | dst: &str, 343 | src: &str, 344 | creation_timestamp: CreationTimestamp, 345 | lifetime: Duration, 346 | ) -> PrimaryBlock { 347 | let dst_eid = EndpointID::try_from(dst).unwrap(); 348 | let src_eid = EndpointID::try_from(src).unwrap(); 349 | 350 | PrimaryBlock { 351 | version: DTN_VERSION, 352 | bundle_control_flags: 0, 353 | crc: CrcValue::CrcNo, 354 | destination: dst_eid, 355 | source: src_eid.clone(), 356 | report_to: src_eid, 357 | creation_timestamp, 358 | lifetime, 359 | fragmentation_offset: 0, 360 | total_data_length: 0, 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bp7-rs 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/bp7.svg)](https://crates.io/crates/bp7) 4 | [![Docs.rs](https://docs.rs/bp7/badge.svg)](https://docs.rs/bp7) 5 | [![Build status](https://api.travis-ci.org/dtn7/bp7-rs.svg?branch=master)](https://travis-ci.org/dtn7/bp7-rs) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT) 7 | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE-APACHE) 8 | [![Chat](https://img.shields.io/matrix/dtn7:matrix.org)](https://matrix.to/#/#dtn7:matrix.org) 9 | 10 | 11 | Rust implementation of dtn Bundle Protocol Version 7 ([RFC 9171](https://datatracker.ietf.org/doc/rfc9171/)) 12 | 13 | This library only handles encoding and decoding of bundles, not transmission or other processing of the data. 14 | A full daemon using this library can be found here: https://github.com/dtn7/dtn7-rs 15 | 16 | Through the provided FFI interface, this library can also be used from C/C++, nodejs or flutter. 17 | 18 | ## Benchmarking 19 | 20 | A simple benchmark is shipped with the library. It (de)serializes Bundles with a primary block, bundle age block and a payload block with the contents (`b"ABC"`). This benchmark can be used to compare the rust implementation to the golang, python or java implementations. 21 | 22 | ``` 23 | cargo run --release --example benchmark 24 | Finished release [optimized] target(s) in 0.29s 25 | Running `target/release/examples/benchmark` 26 | Creating 100000 bundles with CRC_NO: 510059 bundles/second 27 | Creating 100000 bundles with CRC_16: 293399 bundles/second 28 | Creating 100000 bundles with CRC_32: 291399 bundles/second 29 | Encoding 100000 bundles with CRC_NO: 1090996 bundles/second 30 | Encoding 100000 bundles with CRC_16: 436836 bundles/second 31 | Encoding 100000 bundles with CRC_32: 432774 bundles/second 32 | Loading 100000 bundles with CRC_NO: 564817 bundles/second 33 | Loading 100000 bundles with CRC_16: 473768 bundles/second 34 | Loading 100000 bundles with CRC_32: 462013 bundles/second 35 | ``` 36 | 37 | These numbers were generated on a MBP 13" 2018 with i5 CPU and 16GB of ram. 38 | 39 | ## bp7 helper tool 40 | 41 | For debugging a small helper tool is shipped providing basic functionality such as: 42 | - random bundle generation (as hex and raw bytes) 43 | - encoding of standard bundles (as hex and raw bytes) 44 | - decoding of bundles (from hex and raw bytes) 45 | - exporting raw payload of decoded bundles 46 | - time conversion helpers 47 | 48 | 49 | Some examples are given in the following shell session: 50 | ``` 51 | $ cargo install bp7 52 | [...] 53 | $ bp7 54 | usage "bp7" [args] 55 | encode [-x] - encode bundle and output raw bytes or hex string (-x) 56 | decode [-p] - decode bundle or payload only (-p) 57 | dtntime [dtntimestamp] - prints current time as dtntimestamp or prints dtntime human readable 58 | d2u [dtntimestamp] - converts dtntime to unixstimestamp 59 | rnd [-r] - return a random bundle either hexencoded or raw bytes (-r) 60 | benchmark - run a simple benchmark encoding/decoding bundles 61 | $ bp7 rnd 62 | dtn://node81/files-680971330872-0 63 | 9f88071a000200040082016e2f2f6e6f646531382f7e74656c6582016e2f2f6e6f646538312f66696c657382016e2f2f6e6f646538312f66696c6573821b0000009e8d0de538001a0036ee80850a020000448218200085010100004443414243ff 64 | 65 | $ bp7 decode 9f88071a000200040082016e2f2f6e6f646531382f7e74656c6582016e2f2f6e6f646538312f66696c657382016e2f2f6e6f646538312f66696c6573821b0000009e8d0de538001a0036ee80850a020000448218200085010100004443414243ff 66 | 67 | [src/main.rs:101] &bndl = Bundle { 68 | primary: PrimaryBlock { 69 | version: 7, 70 | bundle_control_flags: 131076, 71 | crc: CrcNo, 72 | destination: Dtn( 73 | 1, 74 | DtnAddress( 75 | "//node18/~tele", 76 | ), 77 | ), 78 | source: Dtn( 79 | 1, 80 | DtnAddress( 81 | "//node81/files", 82 | ), 83 | ), 84 | report_to: Dtn( 85 | 1, 86 | DtnAddress( 87 | "//node81/files", 88 | ), 89 | ), 90 | creation_timestamp: CreationTimestamp( 91 | 680971330872, 92 | 0, 93 | ), 94 | lifetime: 3600s, 95 | fragmentation_offset: 0, 96 | total_data_length: 0, 97 | }, 98 | canonicals: [ 99 | CanonicalBlock { 100 | block_type: 10, 101 | block_number: 2, 102 | block_control_flags: 0, 103 | crc: CrcNo, 104 | data: HopCount( 105 | 32, 106 | 0, 107 | ), 108 | }, 109 | CanonicalBlock { 110 | block_type: 1, 111 | block_number: 1, 112 | block_control_flags: 0, 113 | crc: CrcNo, 114 | data: Data( 115 | [ 116 | 65, 117 | 66, 118 | 67, 119 | ], 120 | ), 121 | }, 122 | ], 123 | } 124 | 125 | $ echo -e "source=dtn://node1/bla\ndestination=dtn://node2/incoming\nlifetime=1h" > /tmp/out.manifest 126 | $ echo "hallo welt" | bp7 encode /tmp/out.manifest - -x 127 | 9f880700008201702f2f6e6f6465322f696e636f6d696e6782016b2f2f6e6f6465312f626c61820100821b0000009e8d137d23001a0036ee8085010100004c4b68616c6c6f2077656c740aff 128 | 129 | $ bp7 decode 9f880700008201702f2f6e6f6465322f696e636f6d696e6782016b2f2f6e6f6465312f626c61820100821b0000009e8d137d23001a0036ee8085010100004c4b68616c6c6f2077656c740aff -p 130 | hallo welt 131 | 132 | ``` 133 | 134 | The generated hex string can also be directly discplayed as raw cbor on the awesome cbor.me website, e.g. http://cbor.me/?bytes=9f88071a000200040082016e2f2f6e6f646531382f7e74656c6582016e2f2f6e6f646538312f66696c657382016e2f2f6e6f646538312f66696c6573821b0000009e8d0de538001a0036ee80850a020000448218200085010100004443414243ff 135 | 136 | ## ffi support 137 | 138 | This library only handles encoding and decoding of bundles, not transmission or other processing of the data. 139 | 140 | The library can be used as a shared library or statically linked into other apps. 141 | With the help of `cbindgen` (`cargo install cbindgen`) the header file for this crate can be generated: 142 | ``` 143 | $ cbindgen -c cbindgen.toml > target/bp7.h 144 | ``` 145 | 146 | Example usages for Linux with C calling `bp7` as well as nodejs can be found in `examples/ffi`. 147 | 148 | ## WebAssembly Support 149 | 150 | The library provides WebAssembly support and automatically builds JavaScript bindings when targeting any `wasm32-*` platform. 151 | 152 | ### Quick Start 153 | 154 | Install `wasm-pack` if you haven't already: 155 | ```bash 156 | cargo install wasm-pack 157 | ``` 158 | 159 | Build for your target platform (see [`wasm-pack` documentation](https://drager.github.io/wasm-pack/book/commands/build.html) for details): 160 | ```bash 161 | # For web browsers 162 | wasm-pack build --target web --out-dir pkg-web 163 | 164 | # For Node.js 165 | wasm-pack build --target nodejs --out-dir pkg-node 166 | ``` 167 | 168 | ### Available Functions 169 | 170 | The WASM module exports these functions (all return `Result` for proper error handling): 171 | 172 | **Bundle Creation:** 173 | - `new_std_bundle_now(src, dst, payload)` - Create standard bundle with current timestamp 174 | - `rnd_bundle_now()` - Create random bundle for testing 175 | 176 | **Encoding/Decoding:** 177 | - `encode_to_cbor(bundle)` - Encode bundle to CBOR bytes 178 | - `decode_from_cbor(bytes)` - Decode CBOR bytes to bundle 179 | 180 | **Metadata Extraction:** 181 | - `bid_from_bundle(bundle)` / `bid_from_cbor(bytes)` - Get bundle ID 182 | - `sender_from_bundle(bundle)` / `sender_from_cbor(bytes)` - Get sender address 183 | - `recipient_from_bundle(bundle)` / `recipient_from_cbor(bytes)` - Get recipient address 184 | - `timestamp_from_bundle(bundle)` / `timestamp_from_cbor(bytes)` - Get creation timestamp 185 | - `payload_from_bundle(bundle)` / `payload_from_cbor(bytes)` - Extract payload bytes 186 | 187 | **Validation:** 188 | - `valid_bundle(bundle)` / `valid_cbor(bytes)` - Validate bundle structure and lifetime 189 | - `bundle_is_administrative_record(bundle)` / `cbor_is_administrative_record(bytes)` - Check if bundle is administrative 190 | 191 | ### JavaScript/Browser Usage 192 | 193 | ```html 194 | 195 | 196 | 197 | 198 | bp7-rs WASM Example 199 | 200 | 201 | 242 | 243 | 244 | ``` 245 | 246 | ### Node.js Usage 247 | 248 | ```javascript 249 | const bp7 = require('./pkg-node'); 250 | 251 | async function example() { 252 | // Create a bundle 253 | const bundle = bp7.new_std_bundle_now( 254 | "dtn://sender/app", 255 | "dtn://receiver/app", 256 | "Hello from Node.js!" 257 | ); 258 | 259 | // Extract metadata 260 | const bundleId = bp7.bid_from_bundle(bundle); 261 | const sender = bp7.sender_from_bundle(bundle); 262 | const recipient = bp7.recipient_from_bundle(bundle); 263 | const timestamp = bp7.timestamp_from_bundle(bundle); 264 | const payload = bp7.payload_from_bundle(bundle); 265 | const isAdmin = bp7.bundle_is_administrative_record(bundle); 266 | 267 | console.log(`Bundle: ${bundleId}`); 268 | console.log(`Route: ${sender} → ${recipient}`); 269 | console.log(`Created: ${timestamp}`); 270 | console.log(`Payload: "${new TextDecoder().decode(new Uint8Array(payload))}"`); 271 | console.log(`Administrative: ${isAdmin}`); 272 | 273 | // CBOR operations 274 | const cborBytes = bp7.encode_to_cbor(bundle); 275 | const isValid = bp7.valid_cbor(cborBytes); 276 | 277 | console.log(`CBOR: ${cborBytes.length} bytes, valid: ${isValid}`); 278 | 279 | // Decode and verify roundtrip 280 | const decoded = bp7.decode_from_cbor(cborBytes); 281 | const decodedId = bp7.bid_from_bundle(decoded); 282 | console.log(`Roundtrip success: ${bundleId === decodedId}`); 283 | } 284 | 285 | example().catch(console.error); 286 | ``` 287 | 288 | ### WASI Support & Benchmarking 289 | 290 | For server-side WASI environments, install the WASI runtime: 291 | 292 | ```bash 293 | cargo install wasmtime-cli 294 | ``` 295 | 296 | Build and run WASI applications: 297 | ```bash 298 | # Build for WASI 299 | cargo build --target wasm32-wasip1 --release --example benchmark 300 | 301 | # Run with wasmtime 302 | wasmtime run target/wasm32-wasip1/release/examples/benchmark.wasm 303 | ``` 304 | 305 | Example WASI benchmark performance: 306 | ``` 307 | Creating 100000 bundles with CRC_NO: 511956 bundles/second 308 | Creating 100000 bundles with CRC_16: 147850 bundles/second 309 | Creating 100000 bundles with CRC_32: 145072 bundles/second 310 | Encoding 100000 bundles with CRC_NO: 1072625 bundles/second 311 | Encoding 100000 bundles with CRC_16: 450680 bundles/second 312 | Encoding 100000 bundles with CRC_32: 447624 bundles/second 313 | Loading 100000 bundles with CRC_NO: 520731 bundles/second 314 | Loading 100000 bundles with CRC_16: 231351 bundles/second 315 | Loading 100000 bundles with CRC_32: 230073 bundles/second 316 | ``` 317 | 318 | ### Feature Flags 319 | 320 | For WASM targets, the library provides a `wasm-js` feature flag that enables proper randomness support via getrandom's `wasm_js` backend. Following [getrandom's recommendations](https://docs.rs/getrandom/latest/getrandom/#webassembly-support), this feature is included in the default feature set for convenience, but can be disabled for library users who want to choose their own `getrandom` backend. 321 | 322 | ### Acknowledging this work 323 | 324 | If you use this software in a scientific publication, please cite the following paper: 325 | 326 | ```BibTeX 327 | @INPROCEEDINGS{baumgaertner2019bdtn7, 328 | author={L. {Baumgärtner} and J. {Höchst} and T. {Meuser}}, 329 | booktitle={2019 International Conference on Information and Communication Technologies for Disaster Management (ICT-DM)}, 330 | title={B-DTN7: Browser-based Disruption-tolerant Networking via Bundle Protocol 7}, 331 | year={2019}, 332 | volume={}, 333 | number={}, 334 | pages={1-8}, 335 | keywords={Protocols;Browsers;Software;Convergence;Servers;Synchronization;Wireless fidelity}, 336 | doi={10.1109/ICT-DM47966.2019.9032944}, 337 | ISSN={2469-8822}, 338 | month={Dec}, 339 | } 340 | ``` 341 | 342 | ### License 343 | 344 | 345 | Licensed under either of Apache License, Version 346 | 2.0 or MIT license at your option. 347 | 348 | 349 | Unless you explicitly state otherwise, any contribution intentionally submitted 350 | for inclusion in bp7-rs by you, as defined in the Apache-2.0 license, shall be 351 | dual licensed as above, without any additional terms or conditions. 352 | -------------------------------------------------------------------------------- /examples/bp7kit/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v1.0.1 3 | Copyright 2013 Yahoo! 4 | Licensed under the BSD License. 5 | https://github.com/pure-css/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-content:flex-start;-ms-flex-line-pack:start;align-content:flex-start}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:none transparent;background-color:#e6e6e6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{background-image:-webkit-gradient(linear,left top,left bottom,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{-webkit-box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;opacity:.4;cursor:not-allowed;-webkit-box-shadow:none;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{margin:0;border-radius:0;border-right:1px solid #111;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 3px #ddd;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 3px #ddd;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input:not([type]):focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129fea;outline:1px auto #129fea}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned .pure-help-inline,.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0 0}.pure-form .pure-help-inline,.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-disabled,.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /src/administrative_record.rs: -------------------------------------------------------------------------------- 1 | use crate::bundle::Bundle; 2 | use crate::bundle::ByteBuffer; 3 | use crate::flags::BlockControlFlags; 4 | use crate::flags::BundleControlFlags; 5 | use crate::flags::BundleValidation; 6 | use crate::{bundle, crc, dtn_time_now, primary}; 7 | use core::fmt; 8 | use serde::de::{SeqAccess, Visitor}; 9 | use serde::ser::{SerializeSeq, Serializer}; 10 | use serde::{Deserialize, Deserializer, Serialize, de}; 11 | 12 | use crate::dtntime::CreationTimestamp; 13 | use crate::dtntime::DtnTime; 14 | use crate::eid::EndpointID; 15 | 16 | pub type AdministrativeRecordTypeCode = u32; 17 | 18 | pub const BUNDLE_STATUS_REPORT_TYPE_CODE: AdministrativeRecordTypeCode = 1; 19 | 20 | #[derive(Debug, Clone, PartialEq)] 21 | pub enum AdministrativeRecord { 22 | BundleStatusReport(StatusReport), 23 | Unknown(AdministrativeRecordTypeCode, ByteBuffer), 24 | Mismatched(AdministrativeRecordTypeCode, ByteBuffer), 25 | } 26 | 27 | impl Serialize for AdministrativeRecord { 28 | fn serialize(&self, serializer: S) -> Result 29 | where 30 | S: Serializer, 31 | { 32 | let mut seq = serializer.serialize_seq(Some(2))?; 33 | match self { 34 | AdministrativeRecord::BundleStatusReport(sr) => { 35 | seq.serialize_element(&BUNDLE_STATUS_REPORT_TYPE_CODE)?; 36 | seq.serialize_element(&sr)?; 37 | } 38 | AdministrativeRecord::Unknown(code, data) => { 39 | seq.serialize_element(&code)?; 40 | seq.serialize_element(&serde_bytes::Bytes::new(data))?; 41 | } 42 | AdministrativeRecord::Mismatched(code, data) => { 43 | seq.serialize_element(&code)?; 44 | seq.serialize_element(&serde_bytes::Bytes::new(data))?; 45 | } 46 | } 47 | 48 | seq.end() 49 | } 50 | } 51 | 52 | impl<'de> Deserialize<'de> for AdministrativeRecord { 53 | fn deserialize(deserializer: D) -> Result 54 | where 55 | D: Deserializer<'de>, 56 | { 57 | struct AdministrativeRecordVisitor; 58 | 59 | impl<'de> Visitor<'de> for AdministrativeRecordVisitor { 60 | type Value = AdministrativeRecord; 61 | 62 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 63 | formatter.write_str("AdministrativeRecord") 64 | } 65 | 66 | fn visit_seq(self, mut seq: V) -> Result 67 | where 68 | V: SeqAccess<'de>, 69 | { 70 | let code: AdministrativeRecordTypeCode = seq 71 | .next_element()? 72 | .ok_or_else(|| de::Error::invalid_length(0, &self))?; 73 | 74 | if code == BUNDLE_STATUS_REPORT_TYPE_CODE { 75 | let sr: StatusReport = seq 76 | .next_element()? 77 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 78 | // TODO: check for mixmatched 79 | Ok(AdministrativeRecord::BundleStatusReport(sr)) 80 | } else { 81 | let data: ByteBuffer = seq 82 | .next_element::()? 83 | .ok_or_else(|| de::Error::invalid_length(1, &self))? 84 | .into_vec(); 85 | 86 | Ok(AdministrativeRecord::Unknown(code, data)) 87 | } 88 | } 89 | } 90 | 91 | deserializer.deserialize_any(AdministrativeRecordVisitor) 92 | } 93 | } 94 | 95 | impl AdministrativeRecord { 96 | pub fn to_payload(&self) -> crate::canonical::CanonicalBlock { 97 | let data: ByteBuffer = serde_cbor::to_vec(&self).unwrap(); 98 | 99 | crate::canonical::new_payload_block(BlockControlFlags::empty(), data) 100 | } 101 | } 102 | // Bundle Status Report 103 | 104 | pub type StatusReportReason = u32; 105 | 106 | /// NO_INFORMATION is the "No additional information" bundle status report reason code. 107 | pub const NO_INFORMATION: StatusReportReason = 0; 108 | 109 | /// LIFETIME_EXPIRED is the "Lifetime expired" bundle status report reason code. 110 | pub const LIFETIME_EXPIRED: StatusReportReason = 1; 111 | 112 | /// FORWARD_UNIDIRECTIONAL_LINK is the "Forwarded over unidirectional link" bundle status report reason code. 113 | pub const FORWARD_UNIDIRECTIONAL_LINK: StatusReportReason = 2; 114 | 115 | /// TRANSMISSION_CANCELED is the "Transmission canceled" bundle status report reason code. 116 | pub const TRANSMISSION_CANCELED: StatusReportReason = 3; 117 | 118 | /// DEPLETED_STORAGE is the "Depleted storage" bundle status report reason code. 119 | pub const DEPLETED_STORAGE: StatusReportReason = 4; 120 | 121 | /// DEST_ENDPOINT_UNAVAILABLE is the "Destination endpoint ID unavailable" bundle status report reason code. 122 | pub const DEST_ENDPOINT_UNINTELLIGIBLE: StatusReportReason = 5; 123 | 124 | /// NO_ROUTE_TO_DESTINATION is the "No known route to destination from here" bundle status report reason code. 125 | pub const NO_ROUTE_TO_DESTINATION: StatusReportReason = 6; 126 | 127 | /// NO_NEXT_NODE_CONTACT is the "No timely contact with next node on route" bundle status report reason code. 128 | pub const NO_NEXT_NODE_CONTACT: StatusReportReason = 7; 129 | 130 | /// BLOCK_UNINTELLIGIBLE is the "Block unintelligible" bundle status report reason code. 131 | pub const BLOCK_UNINTELLIGIBLE: StatusReportReason = 8; 132 | 133 | /// HOP_LIMIT_EXCEEDED is the "Hop limit exceeded" bundle status report reason code. 134 | pub const HOP_LIMIT_EXCEEDED: StatusReportReason = 9; 135 | 136 | /// TRAFFIC_PARED bundle status report reason code. 137 | pub const TRAFFIC_PARED: StatusReportReason = 10; 138 | 139 | /// BLOCK_UNSUPPORTED bundle status report reason code. 140 | pub const BLOCK_UNSUPPORTED: StatusReportReason = 11; 141 | 142 | // BundleStatusItem represents the a bundle status item, as used as an element 143 | // in the bundle status information array of each Bundle Status Report. 144 | #[derive(Debug, Clone, PartialEq)] 145 | pub struct BundleStatusItem { 146 | pub asserted: bool, 147 | pub time: crate::DtnTime, 148 | pub status_requested: bool, 149 | } 150 | 151 | impl Serialize for BundleStatusItem { 152 | fn serialize(&self, serializer: S) -> Result 153 | where 154 | S: Serializer, 155 | { 156 | let num_elems = if self.asserted && self.status_requested { 157 | 2 158 | } else { 159 | 1 160 | }; 161 | 162 | let mut seq = serializer.serialize_seq(Some(num_elems))?; 163 | seq.serialize_element(&self.asserted)?; 164 | 165 | if self.asserted && self.status_requested { 166 | seq.serialize_element(&self.time)?; 167 | } 168 | seq.end() 169 | } 170 | } 171 | impl<'de> Deserialize<'de> for BundleStatusItem { 172 | fn deserialize(deserializer: D) -> Result 173 | where 174 | D: Deserializer<'de>, 175 | { 176 | struct BundleStatusItemVisitor; 177 | 178 | impl<'de> Visitor<'de> for BundleStatusItemVisitor { 179 | type Value = BundleStatusItem; 180 | 181 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 182 | formatter.write_str("BundleStatusItem") 183 | } 184 | 185 | fn visit_seq(self, mut seq: V) -> Result 186 | where 187 | V: SeqAccess<'de>, 188 | { 189 | let asserted: bool = seq 190 | .next_element()? 191 | .ok_or_else(|| de::Error::invalid_length(0, &self))?; 192 | 193 | let mut status_requested = false; 194 | 195 | let time: crate::DtnTime = if seq.size_hint() == Some(1) { 196 | status_requested = true; 197 | seq.next_element::()? 198 | .ok_or_else(|| de::Error::invalid_length(1, &self))? 199 | } else { 200 | 0 201 | }; 202 | 203 | Ok(BundleStatusItem { 204 | asserted, 205 | time, 206 | status_requested, 207 | }) 208 | } 209 | } 210 | 211 | deserializer.deserialize_any(BundleStatusItemVisitor) 212 | } 213 | } 214 | 215 | // NewBundleStatusItem returns a new BundleStatusItem, indicating an optional 216 | // assertion - givenas asserted -, but no status time request. 217 | fn new_bundle_status_item(asserted: bool) -> BundleStatusItem { 218 | BundleStatusItem { 219 | asserted, 220 | time: crate::dtntime::DTN_TIME_EPOCH, 221 | status_requested: false, 222 | } 223 | } 224 | 225 | // NewTimeReportingBundleStatusItem returns a new BundleStatusItem, indicating 226 | // both a positive assertion and a requested status time report. 227 | fn new_time_reporting_bundle_status_item(time: DtnTime) -> BundleStatusItem { 228 | BundleStatusItem { 229 | asserted: true, 230 | time, 231 | status_requested: true, 232 | } 233 | } 234 | 235 | // StatusInformationPos describes the different bundle status information 236 | // entries. Each bundle status report must contain at least the following 237 | // bundle status items. 238 | pub type StatusInformationPos = u32; 239 | 240 | // MAX_STATUS_INFORMATION_POS is the amount of different StatusInformationPos. 241 | pub const MAX_STATUS_INFORMATION_POS: u32 = 4; 242 | 243 | // RECEIVED_BUNDLE is the first bundle status information entry, indicating the reporting node received this bundle. 244 | pub const RECEIVED_BUNDLE: StatusInformationPos = 0; 245 | 246 | // FORWARDED_BUNDLE is the second bundle status information entry, indicating the reporting node forwarded this bundle. 247 | pub const FORWARDED_BUNDLE: StatusInformationPos = 1; 248 | 249 | // DELIVERED_BUNDLE is the third bundle status information entry, indicating the reporting node delivered this bundle. 250 | pub const DELIVERED_BUNDLE: StatusInformationPos = 2; 251 | 252 | // DELETED_BUNDLE is the fourth bundle status information entry, indicating the reporting node deleted this bundle. 253 | pub const DELETED_BUNDLE: StatusInformationPos = 3; 254 | 255 | // StatusReport is the bundle status report, used in an administrative record. 256 | #[derive(Debug, Clone, PartialEq)] 257 | pub struct StatusReport { 258 | pub status_information: Vec, 259 | pub report_reason: StatusReportReason, 260 | pub source_node: EndpointID, 261 | pub timestamp: CreationTimestamp, 262 | pub frag_offset: u64, 263 | pub frag_len: u64, 264 | } 265 | 266 | impl StatusReport { 267 | pub fn refbundle(&self) -> String { 268 | let mut id = format!( 269 | "{}-{}-{}", 270 | self.source_node, 271 | self.timestamp.dtntime(), 272 | self.timestamp.seqno(), 273 | //self.primary.destination 274 | ); 275 | if self.frag_len > 0 { 276 | id = format!("{}-{}", id, self.frag_offset); 277 | } 278 | id 279 | } 280 | } 281 | impl Serialize for StatusReport { 282 | fn serialize(&self, serializer: S) -> Result 283 | where 284 | S: Serializer, 285 | { 286 | let num_elems = if self.frag_len != 0 { 6 } else { 4 }; 287 | 288 | let mut seq = serializer.serialize_seq(Some(num_elems))?; 289 | seq.serialize_element(&self.status_information)?; 290 | seq.serialize_element(&self.report_reason)?; 291 | seq.serialize_element(&self.source_node)?; 292 | seq.serialize_element(&self.timestamp)?; 293 | if num_elems > 4 { 294 | seq.serialize_element(&self.frag_offset)?; 295 | seq.serialize_element(&self.frag_len)?; 296 | } 297 | seq.end() 298 | } 299 | } 300 | impl<'de> Deserialize<'de> for StatusReport { 301 | fn deserialize(deserializer: D) -> Result 302 | where 303 | D: Deserializer<'de>, 304 | { 305 | struct StatusReportVisitor; 306 | 307 | impl<'de> Visitor<'de> for StatusReportVisitor { 308 | type Value = StatusReport; 309 | 310 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 311 | formatter.write_str("StatusReport") 312 | } 313 | 314 | fn visit_seq(self, mut seq: V) -> Result 315 | where 316 | V: SeqAccess<'de>, 317 | { 318 | let status_information: Vec = seq 319 | .next_element()? 320 | .ok_or_else(|| de::Error::invalid_length(0, &self))?; 321 | let report_reason: StatusReportReason = seq 322 | .next_element()? 323 | .ok_or_else(|| de::Error::invalid_length(1, &self))?; 324 | 325 | let source_node: EndpointID = seq 326 | .next_element()? 327 | .ok_or_else(|| de::Error::invalid_length(2, &self))?; 328 | 329 | let timestamp: CreationTimestamp = seq 330 | .next_element()? 331 | .ok_or_else(|| de::Error::invalid_length(3, &self))?; 332 | 333 | let mut frag_offset = 0; 334 | let mut frag_len = 0; 335 | 336 | if seq.size_hint() == Some(2) { 337 | frag_offset = seq 338 | .next_element()? 339 | .ok_or_else(|| de::Error::invalid_length(4, &self))?; 340 | frag_len = seq 341 | .next_element()? 342 | .ok_or_else(|| de::Error::invalid_length(5, &self))?; 343 | } 344 | 345 | Ok(StatusReport { 346 | status_information, 347 | report_reason, 348 | source_node, 349 | timestamp, 350 | frag_offset, 351 | frag_len, 352 | }) 353 | } 354 | } 355 | 356 | deserializer.deserialize_any(StatusReportVisitor) 357 | } 358 | } 359 | 360 | // new_status_report creates a bundle status report for the given bundle and 361 | // StatusInformationPos, which creates the right bundle status item. The 362 | // bundle status report reason code will be used and the bundle status item 363 | // gets the given timestamp. 364 | pub fn new_status_report( 365 | bndl: &Bundle, 366 | status_item: StatusInformationPos, 367 | reason: StatusReportReason, 368 | ) -> StatusReport { 369 | let mut sr = StatusReport { 370 | status_information: Vec::new(), 371 | report_reason: reason, 372 | source_node: bndl.primary.source.clone(), 373 | timestamp: bndl.primary.creation_timestamp.clone(), 374 | frag_offset: 0, 375 | frag_len: 0, 376 | }; 377 | 378 | if bndl.primary.has_fragmentation() { 379 | // TODO: add frag code 380 | unimplemented!(); 381 | } 382 | 383 | for i in 0..MAX_STATUS_INFORMATION_POS { 384 | if i == status_item 385 | && bndl 386 | .primary 387 | .bundle_control_flags 388 | .contains(BundleControlFlags::BUNDLE_REQUEST_STATUS_TIME) 389 | { 390 | sr.status_information 391 | .push(new_time_reporting_bundle_status_item(dtn_time_now())); 392 | } else if i == status_item { 393 | sr.status_information.push(new_bundle_status_item(true)); 394 | } else { 395 | sr.status_information.push(new_bundle_status_item(false)); 396 | } 397 | } 398 | 399 | sr 400 | } 401 | 402 | pub fn new_status_report_bundle( 403 | orig_bundle: &Bundle, 404 | src: EndpointID, 405 | crc_type: crc::CrcRawType, 406 | status: StatusInformationPos, 407 | reason: StatusReportReason, 408 | ) -> Bundle { 409 | // TODO: implement sanity checks 410 | 411 | let adm_record = 412 | AdministrativeRecord::BundleStatusReport(new_status_report(orig_bundle, status, reason)); 413 | 414 | let pblock = primary::PrimaryBlockBuilder::default() 415 | .destination(orig_bundle.primary.report_to.clone()) 416 | .source(src.clone()) 417 | .report_to(src) 418 | .bundle_control_flags(BundleControlFlags::BUNDLE_ADMINISTRATIVE_RECORD_PAYLOAD.bits()) 419 | .creation_timestamp(CreationTimestamp::now()) 420 | .lifetime(orig_bundle.primary.lifetime) 421 | .build() 422 | .unwrap(); 423 | 424 | let mut b = bundle::BundleBuilder::default() 425 | .primary(pblock) 426 | .canonicals(vec![adm_record.to_payload()]) 427 | .build() 428 | .unwrap(); 429 | b.set_crc(crc_type); 430 | 431 | b 432 | } 433 | --------------------------------------------------------------------------------