diff --git a/.github/workflows/mirror-main-to-gitlab.yml b/.github/workflows/mirror-main-to-gitlab.yml index ff8639f0..4b13d6c2 100644 --- a/.github/workflows/mirror-main-to-gitlab.yml +++ b/.github/workflows/mirror-main-to-gitlab.yml @@ -1,65 +1,130 @@ -name: Mirror Main to GitLab +name: Mirror Branch to GitLab on: push: - branches: - - main permissions: - contents: write + contents: read jobs: - mirror-main: + mirror-branch: runs-on: ubuntu-latest + if: github.ref_type == 'branch' steps: - - name: Checkout main + - name: Checkout source uses: actions/checkout@v4 with: fetch-depth: 0 submodules: recursive - - name: Build evaluation snapshot commit - id: snapshot + - name: Flatten submodules and push temporary commit + id: mirror + env: + GITLAB_MIRROR_URL: ${{ secrets.GITLAB_MIRROR_URL }} run: | set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + # 推送指定 ref,最多重试 5 次以规避临时网络波动。 + retry_git_push() { + local source_ref="$1" + local target_branch="$2" + local attempt + for attempt in 1 2 3 4 5; do + echo "[mirror] step: push ${source_ref} to GitLab ${target_branch} (attempt ${attempt}/5)" + if git push --force gitlab "${source_ref}:refs/heads/${target_branch}"; then + echo "[mirror] done: push ${source_ref} to GitLab ${target_branch}" + return 0 + fi + if [ "$attempt" -eq 5 ]; then + echo "::error::Failed to push to GitLab after 5 attempts" + return 1 + fi + sleep "$((attempt * 5))" + done + } - source_commit_message_file="$(mktemp)" - git log -1 --pretty=%B > "$source_commit_message_file" + # 先用临时分支上传最大 blob,降低最终 main 推送的单包大小。 + seed_large_blob() { + local seed_file="CosmOS-rootfs/third-party/binutils-2.41.tar.xz" + local seed_branch="mirror-seed-large-blob" + local seed_index + local seed_tree_sha + local seed_commit_sha + local parent_args=() - bash scripts/export-eval-tree.sh + if [ ! -f "$seed_file" ]; then + echo "[mirror] skip: seed file not found: ${seed_file}" + return + fi - tree_sha="$(git write-tree)" + if git show-ref --verify --quiet refs/remotes/gitlab/main; then + parent_args=(-p refs/remotes/gitlab/main) + fi - if git ls-remote --exit-code --heads origin gitlab-output >/dev/null 2>&1; then - git fetch --depth=1 origin gitlab-output:refs/remotes/origin/gitlab-output - commit_sha="$(git commit-tree "$tree_sha" -F "$source_commit_message_file" -p refs/remotes/origin/gitlab-output)" - else - commit_sha="$(git commit-tree "$tree_sha" -F "$source_commit_message_file")" - fi + echo "[mirror] step: create seed commit for ${seed_file}" + seed_index="$(mktemp)" + rm -f "$seed_index" + GIT_INDEX_FILE="$seed_index" git add -- "$seed_file" + seed_tree_sha="$(GIT_INDEX_FILE="$seed_index" git write-tree)" + seed_commit_sha="$(git commit-tree "$seed_tree_sha" "${parent_args[@]}" -m "Seed large mirror blob ${seed_file}")" + rm -f "$seed_index" + echo "[mirror] done: create seed commit ${seed_commit_sha}" - git update-ref refs/heads/gitlab-output "$commit_sha" - echo "commit_sha=$commit_sha" >> "$GITHUB_OUTPUT" + MIRROR_SEED_BRANCH="$seed_branch" + MIRROR_SEED_COMMIT_SHA="$seed_commit_sha" + retry_git_push "$seed_commit_sha" "$seed_branch" + } - - name: Push snapshot branch to GitHub - run: | - set -euo pipefail - git push --force origin refs/heads/gitlab-output:refs/heads/gitlab-output + # 创建完整展开提交,父提交接 seed,确保最终推送不再携带最大 blob。 + create_mirror_commit() { + local full_tree_sha + local mirror_commit_sha + local parent_args=() - # 将 gitlab-output 快照分支同步到 GitLab 的 main 分支。 - - name: Push snapshot to GitLab main - env: - GITLAB_MIRROR_URL: ${{ secrets.GITLAB_MIRROR_URL }} - run: | - set -euo pipefail + if [ -n "$MIRROR_SEED_COMMIT_SHA" ]; then + parent_args=(-p "$MIRROR_SEED_COMMIT_SHA") + elif git show-ref --verify --quiet refs/remotes/gitlab/main; then + parent_args=(-p refs/remotes/gitlab/main) + fi + + full_tree_sha="$(git write-tree)" + mirror_commit_sha="$(git commit-tree "$full_tree_sha" "${parent_args[@]}" -m "Mirror ${GITHUB_REF_NAME}@${GITHUB_SHA} with flattened submodules")" + echo "$mirror_commit_sha" + } + + MIRROR_SEED_BRANCH="" + MIRROR_SEED_COMMIT_SHA="" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" if [ -z "$GITLAB_MIRROR_URL" ]; then echo "::error::Missing GITLAB_MIRROR_URL secret" exit 1 fi + # 在当前 GitHub 分支上创建临时提交,避免生成无父快照提交。 + echo "[mirror] step: prepare source branch ${GITHUB_REF_NAME}" + git checkout -B "$GITHUB_REF_NAME" "$GITHUB_SHA" + echo "[mirror] done: prepare source branch ${GITHUB_REF_NAME}" + + echo "[mirror] step: flatten submodules" + bash scripts/export-eval-tree.sh + echo "[mirror] done: flatten submodules" + + echo "[mirror] step: add GitLab remote" git remote add gitlab "$GITLAB_MIRROR_URL" - git push --force gitlab refs/heads/gitlab-output:refs/heads/main + echo "[mirror] done: add GitLab remote" + + echo "[mirror] step: fetch GitLab main" + git fetch --depth=1 gitlab main:refs/remotes/gitlab/main || true + echo "[mirror] done: fetch GitLab main" + + seed_large_blob + + echo "[mirror] step: create temporary mirror commit" + mirror_commit_sha="$(create_mirror_commit)" + echo "[mirror] done: create temporary mirror commit ${mirror_commit_sha}" + + retry_git_push "$mirror_commit_sha" main diff --git a/.gitignore b/.gitignore index a2ef51d9..ef64c5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ !.vscode/settings.json .idea/* os/target/* +os/.cargo/ os/.idea/* os/src/link_app.S os/last-* @@ -13,6 +14,7 @@ os/.gdb_history os/qemu-net.pcap user/build/* user/target/* +user/.cargo/ user/.idea/* user/Cargo.lock fs/Cargo.lock @@ -34,4 +36,4 @@ sdcard-rv.img second-easyfs.img second-fat32.img bootloader/loongarch64-direct/target/* -sdcard-la.img \ No newline at end of file +sdcard-la.img diff --git a/CosmOS-rootfs b/CosmOS-rootfs index 1061c13e..1533dad2 160000 --- a/CosmOS-rootfs +++ b/CosmOS-rootfs @@ -1 +1 @@ -Subproject commit 1061c13e89534acb41f61f76ef99400c131f1c40 +Subproject commit 1533dad21d327dc1edc68956e8d9ae4431574d06 diff --git a/Makefile b/Makefile index 24466afd..825eea8f 100644 --- a/Makefile +++ b/Makefile @@ -5,12 +5,14 @@ USER_MODE ?= release USER_BIN_DIR := user/target/$(TARGET)/$(USER_MODE) KERNEL_RV_ELF := os/target/$(TARGET)/release/os QEMU ?= qemu-system-riscv64 -MEM ?= 2G -SMP ?= 8 +MEM ?= 1G +SMP ?= 1 TEST_FS ?= sdcard-rv.img +# make run 使用写时复制副本,避免 QEMU 写坏原始测试镜像。 +RUN_TEST_FS ?= .make/sdcard-rv-run.img QEMU_NETDEV ?= user,id=net QEMU_TRACE_ARGS ?= -QEMU_COMP_BLK_ARGS = -drive file=$(TEST_FS),if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +QEMU_COMP_BLK_ARGS = -drive file=$(RUN_TEST_FS),if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 QEMU_COMP_EXTRA_BLK_ARGS = -drive file=disk.img,if=none,format=raw,id=x1 -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 STAMP_DIR := .make @@ -18,14 +20,17 @@ USER_BUILD_STAMP := $(STAMP_DIR)/user-build.stamp KERNEL_BUILD_STAMP := $(STAMP_DIR)/kernel-build.stamp USER_BUILD_DEPS := user/Makefile user/Cargo.toml $(shell find user/src -type f | sort) KERNEL_BUILD_DEPS := os/Makefile os/Cargo.toml os/build.rs $(shell find os/src fs/src -type f | sort) -ROOTFS_FILES := $(shell find rootfs -type f | sort) +ROOTFS_REPO := CosmOS-rootfs +ROOTFS_DIR := $(ROOTFS_REPO)/rootfs +ROOTFS_FILES := $(shell if [ -d $(ROOTFS_DIR) ]; then find $(ROOTFS_DIR) -type f | sort; fi) OPTIONAL_RUNTIME_FILES := $(wildcard lib/musl/ar lib/glibc/ar) -.PHONY: all submodules docker build_docker fmt user-apps clean run run-trace run-comp-rv debug gdbserver gdbclient +.PHONY: all submodules cargo-config docker build_docker fmt user-apps rootfs clean run run-trace run-comp-rv debug gdbserver gdbclient check-kernel check-user-apps check-rootfs prepare-run-test-fs all: $(MAKE) submodules - $(MAKE) kernel-rv kernel-la disk.img + $(MAKE) cargo-config + $(MAKE) user-apps kernel-rv kernel-la disk.img # 拉取所有子模块,确保后续构建依赖完整。 submodules: @@ -35,16 +40,22 @@ submodules: echo "No .gitmodules found; assuming dependencies are already vendored."; \ fi +# 评测会过滤隐藏目录,构建前从非隐藏目录恢复 Cargo 配置。 +cargo-config: + @mkdir -p os/.cargo user/.cargo + @cp os/cargo-config/config.toml os/.cargo/config.toml + @cp user/cargo-config/config.toml user/.cargo/config.toml + $(STAMP_DIR): mkdir -p $@ -$(USER_BUILD_STAMP): $(USER_BUILD_DEPS) | $(STAMP_DIR) +$(USER_BUILD_STAMP): $(USER_BUILD_DEPS) | $(STAMP_DIR) cargo-config $(MAKE) -C user build touch $@ user-apps: $(USER_BUILD_STAMP) -$(KERNEL_BUILD_STAMP): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) +$(KERNEL_BUILD_STAMP): $(KERNEL_BUILD_DEPS) | $(STAMP_DIR) cargo-config $(MAKE) -C os kernel touch $@ @@ -55,10 +66,42 @@ kernel-la: kernel-rv @echo "warning: LoongArch kernel is not implemented in this repository yet; using kernel-rv as a temporary placeholder." >&2 cp kernel-rv $@ -disk.img: $(USER_BUILD_STAMP) $(ROOTFS_FILES) $(OPTIONAL_RUNTIME_FILES) scripts/pack-disk-img.sh - ./scripts/pack-disk-img.sh rootfs $(USER_BIN_DIR) $@ +rootfs: + $(MAKE) -C $(ROOTFS_REPO) rootfs-init + +check-kernel: + @test -x kernel-rv || { \ + echo "missing kernel-rv; run 'make all' first" >&2; \ + exit 1; \ + } + +check-user-apps: + @test -d "$(USER_BIN_DIR)" || { \ + echo "missing user binaries in $(USER_BIN_DIR); run 'make all' first" >&2; \ + exit 1; \ + } + +check-rootfs: rootfs + @test -d "$(ROOTFS_DIR)" || { \ + echo "missing rootfs directory $(ROOTFS_DIR); run 'make all' first" >&2; \ + exit 1; \ + } + @test -d "$(ROOTFS_DIR)/root" || { \ + echo "rootfs is incomplete under $(ROOTFS_DIR); run 'make all' first" >&2; \ + exit 1; \ + } + +disk.img: check-user-apps check-rootfs $(OPTIONAL_RUNTIME_FILES) $(ROOTFS_FILES) scripts/pack-disk-img.sh + ./scripts/pack-disk-img.sh $(ROOTFS_DIR) $(USER_BIN_DIR) $@ + +prepare-run-test-fs: | $(STAMP_DIR) + @if [ ! -f "$(TEST_FS)" ]; then \ + echo "Test image not found: $(TEST_FS)"; \ + exit 2; \ + fi + cp -c "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp --reflink=auto "$(TEST_FS)" "$(RUN_TEST_FS)" 2>/dev/null || cp "$(TEST_FS)" "$(RUN_TEST_FS)" -run: kernel-rv disk.img +run: check-kernel disk.img prepare-run-test-fs $(QEMU) -machine virt -kernel kernel-rv -m $(MEM) -nographic -smp $(SMP) -bios default $(QEMU_COMP_BLK_ARGS) -device virtio-net-device,netdev=net -netdev $(QEMU_NETDEV) -no-reboot -rtc base=utc $(QEMU_COMP_EXTRA_BLK_ARGS) $(QEMU_TRACE_ARGS) run-trace: QEMU_TRACE_ARGS = -d int,in_asm -D qemu.log @@ -66,10 +109,10 @@ run-trace: run run-comp-rv: run -debug: kernel-rv disk.img +debug: check-kernel disk.img $(MAKE) -C os debug -gdbserver: kernel-rv disk.img +gdbserver: check-kernel disk.img $(MAKE) -C os gdbserver gdbclient: @@ -85,6 +128,6 @@ fmt: cd fs; cargo fmt; cd ../fs-fuse; cargo fmt; cd ../os; cargo fmt; cd ../user; cargo fmt; cd .. clean: - rm -rf $(STAMP_DIR) disk.img kernel-rv kernel-la + rm -rf $(STAMP_DIR) $(RUN_TEST_FS) disk.img kernel-rv kernel-la os/.cargo user/.cargo $(MAKE) -C os clean $(MAKE) -C user clean diff --git a/fs/src/errno.rs b/fs/src/errno.rs index a4de0d1b..575c68da 100644 --- a/fs/src/errno.rs +++ b/fs/src/errno.rs @@ -1,6 +1,7 @@ use ext4_rs::{Ext4, Ext4Error}; #[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(missing_docs)] pub enum FS_ERRNO { EPERM = 1, // Operation not permitted diff --git a/fs/src/ext4/mod.rs b/fs/src/ext4/mod.rs index e2be8b79..6f767c91 100644 --- a/fs/src/ext4/mod.rs +++ b/fs/src/ext4/mod.rs @@ -554,6 +554,12 @@ impl VfsNode for Ext4Inode { ext4.write_at(self.inode_num, offset, buf).unwrap_or(0) } + /// ext4 写入需要保留 ENOSPC/ENOTSUP,供 page cache 回写路径处理失败页。 + fn write_at_result(&self, offset: usize, buf: &[u8]) -> Result { + let ext4 = self.fs.ext4.lock(); + ext4.write_at(self.inode_num, offset, buf).map_err(FS_ERRNO::from) + } + fn ino(&self) -> u64 { self.inode_num as u64 } diff --git a/fs/src/ext4_rs b/fs/src/ext4_rs index d396d88e..016bbbba 160000 --- a/fs/src/ext4_rs +++ b/fs/src/ext4_rs @@ -1 +1 @@ -Subproject commit d396d88e9ea206ed137aaa0c41fe5ab9bbf6012b +Subproject commit 016bbbba3bb2670472eeda35b3b497c6ff2222ef diff --git a/fs/src/vfs.rs b/fs/src/vfs.rs index 1c3cb65a..9ccb8fb7 100644 --- a/fs/src/vfs.rs +++ b/fs/src/vfs.rs @@ -162,6 +162,10 @@ pub trait VfsNode: Send + Sync + Any + Debug { } fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize; fn write_at(&self, offset: usize, buf: &[u8]) -> usize; + /// 向固定偏移写入数据,并保留底层文件系统返回的真实错误。 + fn write_at_result(&self, offset: usize, buf: &[u8]) -> Result { + Ok(self.write_at(offset, buf)) + } /// Stable inode number for stat-like metadata. fn ino(&self) -> u64 { 0 @@ -454,6 +458,24 @@ impl Inode { written } + /// 向固定偏移写入数据,并把底层错误传给调用方。 + pub fn write_at_result(&self, offset: usize, buf: &[u8]) -> Result { + let written = self.inner.write_at_result(offset, buf).map_err(|err| { + log::error!( + "[vfs] write_at_result failed: ino={} offset={} len={} errno={}", + self.ino(), + offset, + buf.len(), + err as i32 + ); + err + })?; + if written != 0 { + self.invalidate_stat_cache(); + } + Ok(written) + } + pub fn ino(&self) -> u64 { self.inner.ino() } diff --git a/lib/glibc/ar b/lib/glibc/ar deleted file mode 100755 index a8d42f1c..00000000 Binary files a/lib/glibc/ar and /dev/null differ diff --git a/lib/musl/ar b/lib/musl/ar deleted file mode 100755 index 38635a27..00000000 Binary files a/lib/musl/ar and /dev/null differ diff --git a/os/Cargo.toml b/os/Cargo.toml index 0f9ada1c..7bc14e8c 100644 --- a/os/Cargo.toml +++ b/os/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } +riscv = { path = "../vendor/riscv", features = ["inline-asm"] } lazy_static = { version = "1.4.0", features = ["spin_no_std"] } log = "0.4" buddy_system_allocator = "0.6" @@ -16,7 +16,7 @@ xmas-elf = "0.7.0" virtio-drivers = "0.12.0" fs = { path = "../fs" } hashbrown = "0.12.3" -smoltcp = { git = "https://github.com/KyleMao2023/smoltcp", branch = "os", default-features = false, features = [ +smoltcp = { path = "../vendor/smoltcp", default-features = false, features = [ "alloc", "medium-ethernet", "proto-ipv4", diff --git a/os/Makefile b/os/Makefile index 119f096d..4dd55fd3 100644 --- a/os/Makefile +++ b/os/Makefile @@ -8,6 +8,7 @@ USER_MODE := release FS_IMG ?= ../user/target/$(TARGET)/$(USER_MODE)/fs.img # easyfs or fat32 or ext4 MAIN_FS := ext4 +MEM ?= 1G SMP ?= 1 # Optional prebuilt ext4 image path. When set and valid, fs-fuse clones this # image and injects user apps into it. @@ -56,9 +57,10 @@ build: env kernel fs-img env: ifeq ($(OFFLINE),) (rustup target list | grep "riscv64gc-unknown-none-elf (installed)") || rustup target add $(TARGET) - cargo install cargo-binutils - rustup component add rust-src - rustup component add llvm-tools-preview + # 评测镜像已预装 cargo-binutils;缺失时才尝试安装。 + command -v rust-objcopy >/dev/null || cargo install cargo-binutils + # rust-objcopy/rust-objdump 依赖 llvm-tools-preview。 + rustup component list --installed | grep -q "llvm-tools-preview" || rustup component add llvm-tools-preview endif fs-img: @@ -116,7 +118,7 @@ fast-run: kernel run-inner: @$(QEMU) \ - -m 2G \ + -m $(MEM) \ -machine virt \ -smp $(SMP) \ -nographic \ @@ -132,7 +134,7 @@ run-inner: run-inner-trace: @$(QEMU) \ - -m 512M \ + -m $(MEM) \ -machine virt \ -smp $(SMP) \ -nographic \ @@ -151,14 +153,14 @@ run-inner-trace: debug: MODE=debug debug: build @tmux new-session -d \ - "$(QEMU) -m 512M -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \ + "$(QEMU) -m $(MEM) -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) -s -S" && \ tmux split-window -h "riscv64-unknown-elf-gdb -ex 'file $(KERNEL_ELF)' -ex 'set arch riscv:rv64' -ex 'target remote localhost:1234'" && \ tmux -2 attach-session -d gdbserver: MODE=debug gdbserver: build - @$(QEMU) -m 512M -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ + @$(QEMU) -m $(MEM) -machine virt -smp $(SMP) -nographic -bios $(BOOTLOADER) -device loader,file=$(KERNEL_BIN),addr=$(KERNEL_ENTRY_PA) \ $(QEMU_PRIMARY_BLK_ARGS) $(QEMU_EXTRA_ARGS) \ -s -S diff --git a/os/.cargo/config.toml b/os/cargo-config/config.toml similarity index 100% rename from os/.cargo/config.toml rename to os/cargo-config/config.toml diff --git a/os/src/config.rs b/os/src/config.rs index a107e581..3d9592bf 100644 --- a/os/src/config.rs +++ b/os/src/config.rs @@ -12,14 +12,16 @@ pub const MAX_KERNEL_HEAP_SIZE: usize = 0x4000_0000; pub const KERNEL_HEAP_BASE: usize = 0xffff_ffc0_0000_0000; /// max harts reserved by the kernel SMP bootstrap path pub const MAX_HARTS: usize = 8; -/// physical memory end address -pub const MEMORY_END: usize = 0x100000000; +/// QEMU virt 1GiB 内存的物理结束地址,起始地址为 0x8000_0000。 +pub const MEMORY_END: usize = 0xC0000000; /// page size : 4KB pub const PAGE_SIZE: usize = 0x1000; /// page size bits: 12 pub const PAGE_SIZE_BITS: usize = 0xc; /// default base address for anonymous mmap allocations pub const USER_MMAP_BASE: usize = 0x1000_0000; +/// fixed load bias used for PIE main executables without an interpreter +pub const USER_PIE_BASE: usize = 0x0020_0000; /// default base address for the main thread's user stack region pub const USER_STACK_BASE: usize = 0x0800_0000; /// base address for loading dynamic linker (interpreter) diff --git a/os/src/fs/inode.rs b/os/src/fs/inode.rs index 515bb966..8aa34607 100644 --- a/os/src/fs/inode.rs +++ b/os/src/fs/inode.rs @@ -1,4 +1,4 @@ -use super::{page_cache, File, Stat, StatFs64, StatMode}; +use super::{discard_inode, page_cache, File, Stat, StatFs64, StatMode}; use super::devfs::{CpuDmaLatencyNode, NullDevNode, UrandomDevNode}; use super::rootfs::{VirtualDirNode, VIRT_ROOT}; use super::tmpfs::new_tmpfs_root; @@ -881,7 +881,18 @@ pub fn unlinkat(cwd: &str, path: &str, flags: u32) -> Result<(), ERRNO> { if flags & AT_REMOVEDIR != 0 { return Err(ERRNO::ENOTDIR); } - parent.unlink(name.as_str())? + // 删除普通文件前丢弃旧页,避免已经解除目录项后仍有脏页迟到回写。 + discard_inode(&inode); + if let Err(err) = parent.unlink(name.as_str()) { + error!( + "[unlinkat] unlink regular file failed: cwd={} path={} name={} errno={}", + cwd, + path, + name, + err as i32 + ); + return Err(err.into()); + } } Ok(()) } diff --git a/os/src/fs/mod.rs b/os/src/fs/mod.rs index 8110c393..f624d4e2 100644 --- a/os/src/fs/mod.rs +++ b/os/src/fs/mod.rs @@ -23,6 +23,7 @@ use crate::syscall::Pod; use core::any::Any; pub use fs::vfs::{InodeTime, VfsFileType}; pub use page_cache::{ + discard_inode, mapping_for_inode, sync_all as sync_page_cache_all, sync_fs as sync_page_cache_fs, sync_inode_range, truncate_inode, CachePage, mark_cached_page_dirty, release_mapped_page, retain_mapped_page, PAGE_CACHE_MANAGER diff --git a/os/src/fs/page_cache.rs b/os/src/fs/page_cache.rs index 62a9bcc7..bb3f63e1 100644 --- a/os/src/fs/page_cache.rs +++ b/os/src/fs/page_cache.rs @@ -144,8 +144,8 @@ impl PageMappingHandle { } /// 调整当前文件长度,并同步更新已有缓存页。 - pub fn truncate(&self, new_size: usize) { - truncate_mapping(&self.inner, new_size); + pub fn truncate(&self, new_size: usize) -> Result<(), FS_ERRNO> { + truncate_mapping(&self.inner, new_size) } /// Reserve file space without forcing eager page creation. @@ -278,10 +278,18 @@ pub fn truncate_inode(inode: &Arc, new_size: usize) -> Result<(), FS_ERRN if new_size < mapping.size() { invalidate_inode_mappings_after_truncate(inode, new_size); } - mapping.truncate(new_size); - return Ok(()); + return mapping.truncate(new_size); + } + if let Err(err) = inode.truncate(new_size) { + error!( + "[page_cache] truncate backing inode failed: fs_id={} ino={} new_size={} errno={}", + inode.fs_id(), + inode.ino(), + new_size, + err as i32 + ); + return Err(err); } - inode.truncate(new_size)?; Ok(()) } @@ -351,6 +359,35 @@ pub fn sync_inode_range(inode: &Arc, offset: usize, len: usize) -> Result mapping.sync_range(offset, len) } +/// 丢弃指定 inode 的 page cache,并断开旧页 owner,防止删除后脏页迟到回写。 +pub fn discard_inode(inode: &Arc) { + if !is_inode_page_cacheable(inode) { + return; + } + let Some(mapping) = inode.take_page_cache_state::>() else { + return; + }; + let removed_pages = { + let mut mapping_guard = mapping.lock(); + for page in mapping_guard.pages.values() { + let mut page_guard = page.lock(); + page_guard.owner = Weak::new(); + page_guard.state.remove(CachePageState::DIRTY | CachePageState::WRITEBACK); + page_guard.pin_count = page_guard.pin_count.saturating_sub(1); + page_guard.wait_queue.wake_all(); + } + let removed_pages = mapping_guard.pages.len(); + mapping_guard.pages.clear(); + mapping_guard.dirty_pages.clear(); + removed_pages + }; + { + let mut manager = PAGE_CACHE_MANAGER.lock(); + manager.cached_pages = manager.cached_pages.saturating_sub(removed_pages); + manager.mappings.remove(&InodeKey::from_inode(inode)); + } +} + /// 返回指定页对应的 cache page;供后续 `mmap` 缺页路径复用。 #[allow(dead_code)] pub fn get_cached_page(inode: &Arc, page_idx: u64) -> Option>> { @@ -513,7 +550,7 @@ fn write_mapping(mapping: &Arc>, offset: usize, buf: } /// 调整当前 mapping 长度,并同步更新已有缓存页。 -fn truncate_mapping(mapping: &Arc>, new_size: usize) { +fn truncate_mapping(mapping: &Arc>, new_size: usize) -> Result<(), FS_ERRNO> { let inode = { let mapping_guard = mapping.lock(); mapping_guard @@ -528,7 +565,7 @@ fn truncate_mapping(mapping: &Arc>, new_size: usize) "[page_cache] truncate skip: old_size == new_size == {}", new_size ); - return; + return Ok(()); } debug!( @@ -537,9 +574,17 @@ fn truncate_mapping(mapping: &Arc>, new_size: usize) new_size ); - // 这里底层 inode truncate 失败时直接 panic,避免 page cache 与底层长度分离。 - if inode.truncate(new_size).is_err() { - panic!("page cache truncate must stay consistent with backing inode"); + // 先让底层 inode 调整成功,再修改 page cache 视图,避免失败时两边长度分离。 + if let Err(err) = inode.truncate(new_size) { + error!( + "[page_cache] truncate backing inode failed: fs_id={} ino={} old_size={} new_size={} errno={}", + inode.fs_id(), + inode.ino(), + old_size, + new_size, + err as i32 + ); + return Err(err); } let old_last_valid = old_size.saturating_sub(1); @@ -625,6 +670,7 @@ fn truncate_mapping(mapping: &Arc>, new_size: usize) } } } + Ok(()) } fn fallocate_mapping( @@ -895,15 +941,26 @@ fn flush_page( page_idx, valid_bytes ); - let written = owner_inode.write_at(page_start(page_idx), &bytes[..valid_bytes]); - if written != valid_bytes { - error!( - "[page_cache] short writeback: page_idx={} expected={} actual={}", - page_idx, - valid_bytes, - written - ); - write_ok = false; + match owner_inode.write_at_result(page_start(page_idx), &bytes[..valid_bytes]) { + Ok(written) if written == valid_bytes => {} + Ok(written) => { + error!( + "[page_cache] short writeback: page_idx={} expected={} actual={}", + page_idx, + valid_bytes, + written + ); + write_ok = false; + } + Err(err) => { + error!( + "[page_cache] writeback failed: page_idx={} expected={} errno={}", + page_idx, + valid_bytes, + err as i32 + ); + write_ok = false; + } } } diff --git a/os/src/fs/pipe.rs b/os/src/fs/pipe.rs index 46ea309e..76283487 100644 --- a/os/src/fs/pipe.rs +++ b/os/src/fs/pipe.rs @@ -4,9 +4,12 @@ use crate::poll::{notify_poll_source, POLLHUP, POLLIN, POLLOUT}; use crate::sync::SpinNoIrqLock; use crate::syscall::errno::ERRNO; use alloc::sync::{Arc, Weak}; -use crate::fs::{Stat,StatMode}; +use crate::fs::{empty_statfs, Stat, StatFs64, StatMode}; use crate::task::{WaitQueue, WaitReason}; use core::any::Any; +use fs::{STATFS_MAGIC_PIPEFS, STATFS_NAMELEN_DEFAULT}; + +const PIPE_DEV_ID: u64 = STATFS_MAGIC_PIPEFS; /// IPC pipe pub struct Pipe { @@ -427,16 +430,16 @@ impl File for Pipe { fn stat(&self) -> Stat { Stat { - dev: 0, - ino: 0, - mode: StatMode::FIFO, + dev: PIPE_DEV_ID, + ino: self.source_id() as u64, + mode: StatMode::FIFO | StatMode::from_bits_truncate(0o600), nlink: 1, uid: 0, gid: 0, rdev: 0, pad0: 0, size: 0, - blksize: 0, + blksize: crate::config::PAGE_SIZE as u32, pad1: 0, blocks: 0, atime_sec: 0, @@ -448,6 +451,15 @@ impl File for Pipe { unused: [0; 2], } } + + fn statfs(&self) -> Result { + Ok(empty_statfs( + STATFS_MAGIC_PIPEFS, + crate::config::PAGE_SIZE as u64, + self.source_id() as u64, + STATFS_NAMELEN_DEFAULT, + )) + } } impl Drop for Pipe { diff --git a/os/src/fs/procfs.rs b/os/src/fs/procfs.rs index 2c4118e6..d978d960 100644 --- a/os/src/fs/procfs.rs +++ b/os/src/fs/procfs.rs @@ -89,6 +89,14 @@ fn parse_proc_usize(buf: &[u8]) -> Result { text.parse::().map_err(|_| FS_ERRNO::EINVAL) } +fn parse_proc_bool(buf: &[u8]) -> Result { + match parse_proc_u32(buf)? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(FS_ERRNO::EINVAL), + } +} + fn read_string_at(data: String, offset: usize, buf: &mut [u8]) -> usize { if buf.is_empty() { return 0; @@ -683,6 +691,7 @@ impl VfsNode for ProcStaticDirNode { ProcStaticDirKind::Sys => alloc::vec![(String::from("kernel"), VfsFileType::Directory)], ProcStaticDirKind::Kernel => alloc::vec![ (String::from("keys"), VfsFileType::Directory), + (String::from("sched_autogroup_enabled"), VfsFileType::Regular), (String::from("tainted"), VfsFileType::Regular), ], ProcStaticDirKind::Keys => alloc::vec![ @@ -701,6 +710,10 @@ impl VfsNode for ProcStaticDirNode { (ProcStaticDirKind::Kernel, "keys") => { Some(Arc::new(ProcStaticDirNode::new(ProcStaticDirKind::Keys)) as Arc) } + (ProcStaticDirKind::Kernel, "sched_autogroup_enabled") => Some( + Arc::new(ProcKernelSysctlNode::new(ProcKernelSysctlKind::SchedAutogroupEnabled)) + as Arc, + ), (ProcStaticDirKind::Kernel, "tainted") => { Some(Arc::new(ProcKernelTaintedNode::new()) as Arc) } @@ -794,6 +807,92 @@ impl VfsNode for ProcKernelTaintedNode { } } +#[derive(Debug, Clone, Copy)] +enum ProcKernelSysctlKind { + SchedAutogroupEnabled, +} + +#[derive(Debug)] +struct ProcKernelSysctlNode { + kind: ProcKernelSysctlKind, +} + +impl ProcKernelSysctlNode { + fn new(kind: ProcKernelSysctlKind) -> Self { + Self { kind } + } + + fn render(&self) -> String { + match self.kind { + ProcKernelSysctlKind::SchedAutogroupEnabled => { + alloc::format!("{}\n", crate::sched::autogroup_enabled() as u8) + } + } + } +} + +impl VfsNode for ProcKernelSysctlNode { + fn as_any(&self) -> &dyn Any { + self + } + + fn file_type(&self) -> VfsFileType { + VfsFileType::Regular + } + + fn size(&self) -> usize { + self.render().len() + } + + fn ls(&self) -> Vec<(String, VfsFileType)> { + Vec::new() + } + + fn find(&self, _name: &str) -> Option> { + None + } + + fn create(&self, _name: &str) -> Option> { + None + } + + fn mkdir(&self, _name: &str) -> Option> { + None + } + + fn clear(&self) {} + + fn truncate(&self, _new_size: usize) -> Result<(), FS_ERRNO> { + Ok(()) + } + + fn read_at(&self, offset: usize, buf: &mut [u8]) -> usize { + read_string_at(self.render(), offset, buf) + } + + fn write_at(&self, _offset: usize, buf: &[u8]) -> usize { + let result = match self.kind { + ProcKernelSysctlKind::SchedAutogroupEnabled => { + parse_proc_bool(buf).map(crate::sched::set_autogroup_enabled) + } + }; + if result.is_ok() { + buf.len() + } else { + 0 + } + } + + fn statfs(&self) -> Result { + Ok(crate::fs::empty_statfs( + fs::STATFS_MAGIC_PROC, + crate::config::PAGE_SIZE as u64, + 0x9fa0, + 255, + )) + } +} + #[derive(Default, Debug)] struct ProcKeyUsersNode; diff --git a/os/src/fs/tty.rs b/os/src/fs/tty.rs index fd511afc..d36516f0 100644 --- a/os/src/fs/tty.rs +++ b/os/src/fs/tty.rs @@ -29,6 +29,13 @@ const TIOCSCTTY: usize = 0x540E; const TIOCGPGRP: usize = 0x540F; /// `ioctl(TIOCSPGRP)`:设置前台进程组。 const TIOCSPGRP: usize = 0x5410; +/// Device ID of the devfs-like filesystem that contains the tty node. +const TTY_FS_DEV_ID: u64 = 0; +const TTY_INO: u64 = 1; +/// Linux-compatible `/dev/tty` character device number: major 5, minor 0. +const TTY_RDEV_MAJOR: u64 = 5; +const TTY_RDEV_MINOR: u64 = 0; +const TTY_RDEV: u64 = (TTY_RDEV_MAJOR << 8) | TTY_RDEV_MINOR; /// `ioctl(TIOCGWINSZ)`:读取窗口大小。 const TIOCGWINSZ: usize = 0x5413; /// `ioctl(TIOCSWINSZ)`:设置窗口大小。 @@ -698,16 +705,16 @@ impl TtyFile { /// 构造字符设备类型的 `stat` 结果。 fn stat_impl() -> Stat { Stat { - dev: 0, - ino: 0, - mode: StatMode::CHAR, + dev: TTY_FS_DEV_ID, + ino: TTY_INO, + mode: StatMode::CHAR | StatMode::from_bits_truncate(0o666), nlink: 1, uid: 0, gid: 0, - rdev: 0, + rdev: TTY_RDEV, pad0: 0, size: 0, - blksize: 0, + blksize: crate::config::PAGE_SIZE as u32, pad1: 0, blocks: 0, atime_sec: 0, @@ -999,7 +1006,7 @@ impl VfsNode for TtyDeviceNode { } fn mode(&self) -> Option { - Some(StatMode::CHAR.bits()) + Some((StatMode::CHAR | StatMode::from_bits_truncate(0o666)).bits()) } fn uid(&self) -> Option { diff --git a/os/src/mm/memory_set.rs b/os/src/mm/memory_set.rs index 1a6d8c8d..5e88489b 100644 --- a/os/src/mm/memory_set.rs +++ b/os/src/mm/memory_set.rs @@ -6,7 +6,7 @@ use super::{PageTable, PageTableEntry}; use super::{PhysAddr, PhysPageNum, USER_SPACE_END, VirtAddr, VirtPageNum}; use super::{StepByOne, VPNRange}; use crate::config::{ - MEMORY_END, MMIO, PAGE_SIZE, TRAMPOLINE, USER_MMAP_BASE, USER_STACK_BASE, USER_STACK_SIZE, + MEMORY_END, MMIO, PAGE_SIZE, TRAMPOLINE, USER_MMAP_BASE, USER_PIE_BASE, USER_STACK_BASE, USER_STACK_SIZE, USER_VDSO_BASE, }; use crate::fs::{ @@ -63,6 +63,9 @@ pub struct ElfLoadInfo { pub interp_path: Option, } +/// `R_RISCV_RELATIVE` relocation used by static PIE executables. +const R_RISCV_RELATIVE: u32 = 3; + lazy_static! { /// The kernel's initial memory mapping(kernel address space) pub static ref KERNEL_SPACE: Arc> = @@ -173,6 +176,185 @@ fn format_hex_bytes(bytes: &[u8]) -> String { out } +fn add_load_bias(addr: usize, load_bias: usize) -> Result { + addr.checked_add(load_bias).ok_or(MmError::InvalidElf) +} + +fn relocated_value(load_bias: usize, addend: i64) -> Result { + if addend >= 0 { + load_bias + .checked_add(addend as usize) + .ok_or(MmError::InvalidElf) + } else { + load_bias + .checked_sub(addend.unsigned_abs() as usize) + .ok_or(MmError::InvalidElf) + } +} + +fn write_user_usize(memory_set: &mut MemorySet, va: usize, value: usize) -> Result<(), MmError> { + for (idx, byte) in value.to_le_bytes().iter().copied().enumerate() { + let pa = memory_set + .page_table + .translate_va(VirtAddr(va + idx)) + .ok_or(MmError::NoMapping)?; + *pa.get_mut::() = byte; + } + Ok(()) +} + +fn file_offset_for_vaddr(elf: &xmas_elf::ElfFile<'_>, vaddr: usize) -> Option { + let ph_count = elf.header.pt2.ph_count(); + for i in 0..ph_count { + let ph = elf.program_header(i).ok()?; + if ph.get_type().ok()? != xmas_elf::program::Type::Load { + continue; + } + let seg_start = ph.virtual_addr() as usize; + let seg_size = ph.file_size() as usize; + let seg_end = seg_start.checked_add(seg_size)?; + if vaddr < seg_start || vaddr >= seg_end { + continue; + } + let within_seg = vaddr.checked_sub(seg_start)?; + return (ph.offset() as usize).checked_add(within_seg); + } + None +} + +fn read_dynsym_value( + elf: &xmas_elf::ElfFile<'_>, + symtab_vaddr: usize, + sym_ent: usize, + sym_index: u32, +) -> Result<(usize, u16), MmError> { + if sym_ent != 24 { + return Err(MmError::InvalidElf); + } + let symtab_offset = file_offset_for_vaddr(elf, symtab_vaddr).ok_or(MmError::InvalidElf)?; + let sym_offset = symtab_offset + .checked_add(sym_ent.checked_mul(sym_index as usize).ok_or(MmError::InvalidElf)?) + .ok_or(MmError::InvalidElf)?; + let sym_end = sym_offset.checked_add(sym_ent).ok_or(MmError::InvalidElf)?; + let sym = elf + .input + .get(sym_offset..sym_end) + .ok_or(MmError::InvalidElf)?; + let shndx = u16::from_le_bytes(sym[6..8].try_into().map_err(|_| MmError::InvalidElf)?); + let value = usize::from_le_bytes(sym[8..16].try_into().map_err(|_| MmError::InvalidElf)?); + Ok((value, shndx)) +} + +fn apply_static_pie_relocations( + memory_set: &mut MemorySet, + elf: &xmas_elf::ElfFile<'_>, + load_bias: usize, +) -> Result<(), MmError> { + let mut rela_vaddr: Option = None; + let mut rela_size = 0usize; + let mut rela_ent = 0usize; + let mut symtab_vaddr: Option = None; + let mut sym_ent = 0usize; + let ph_count = elf.header.pt2.ph_count(); + + for i in 0..ph_count { + let ph = elf.program_header(i).map_err(|_| MmError::InvalidElf)?; + if ph.get_type().map_err(|_| MmError::InvalidElf)? != xmas_elf::program::Type::Dynamic { + continue; + } + let entries = ph.get_data(elf).map_err(|_| MmError::InvalidElf)?; + let xmas_elf::program::SegmentData::Dynamic64(entries) = entries else { + return Err(MmError::InvalidElf); + }; + for entry in entries { + match entry.get_tag().map_err(|_| MmError::InvalidElf)? { + xmas_elf::dynamic::Tag::Rela => { + rela_vaddr = Some(entry.get_ptr().map_err(|_| MmError::InvalidElf)? as usize); + } + xmas_elf::dynamic::Tag::RelaSize => { + rela_size = entry.get_val().map_err(|_| MmError::InvalidElf)? as usize; + } + xmas_elf::dynamic::Tag::RelaEnt => { + rela_ent = entry.get_val().map_err(|_| MmError::InvalidElf)? as usize; + } + xmas_elf::dynamic::Tag::SymTab => { + symtab_vaddr = Some(entry.get_ptr().map_err(|_| MmError::InvalidElf)? as usize); + } + xmas_elf::dynamic::Tag::SymEnt => { + sym_ent = entry.get_val().map_err(|_| MmError::InvalidElf)? as usize; + } + xmas_elf::dynamic::Tag::Rel | xmas_elf::dynamic::Tag::JmpRel => { + return Err(MmError::InvalidElf); + } + _ => {} + } + } + } + + if rela_size == 0 { + return Ok(()); + } + if rela_ent != 24 { + return Err(MmError::InvalidElf); + } + + let rela_vaddr = rela_vaddr.ok_or(MmError::InvalidElf)?; + let rela_offset = file_offset_for_vaddr(elf, rela_vaddr).ok_or(MmError::InvalidElf)?; + let rela_end = rela_offset + .checked_add(rela_size) + .ok_or(MmError::InvalidElf)?; + let rela_bytes = elf + .input + .get(rela_offset..rela_end) + .ok_or(MmError::InvalidElf)?; + if rela_bytes.len() % rela_ent != 0 { + return Err(MmError::InvalidElf); + } + + for chunk in rela_bytes.chunks_exact(rela_ent) { + let offset = usize::from_le_bytes( + chunk[0..8] + .try_into() + .map_err(|_| MmError::InvalidElf)?, + ); + let info = u64::from_le_bytes( + chunk[8..16] + .try_into() + .map_err(|_| MmError::InvalidElf)?, + ); + let addend = i64::from_le_bytes( + chunk[16..24] + .try_into() + .map_err(|_| MmError::InvalidElf)?, + ); + let rel_type = info as u32; + let sym_index = (info >> 32) as u32; + let target = add_load_bias(offset, load_bias)?; + let value = match rel_type { + R_RISCV_RELATIVE => { + if sym_index != 0 { + return Err(MmError::InvalidElf); + } + relocated_value(load_bias, addend)? + } + 2 => { + let symtab_vaddr = symtab_vaddr.ok_or(MmError::InvalidElf)?; + let (sym_value, shndx) = + read_dynsym_value(elf, symtab_vaddr, sym_ent, sym_index)?; + if shndx == 0 { + return Err(MmError::InvalidElf); + } + let sym_addr = add_load_bias(sym_value, load_bias)?; + relocated_value(sym_addr, addend)? + } + _ => return Err(MmError::InvalidElf), + }; + write_user_usize(memory_set, target, value)?; + } + + Ok(()) +} + /// address space pub struct MemorySet { /// page table @@ -725,6 +907,12 @@ impl MemorySet { let elf_header = elf.header; let magic = elf_header.pt1.magic; assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!"); + let elf_type = elf_header.pt2.type_().as_type(); + let load_bias = if elf_type == xmas_elf::header::Type::SharedObject { + USER_PIE_BASE + } else { + 0 + }; let ph_count = elf_header.pt2.ph_count(); let mut max_end_vpn = VirtPageNum(0); @@ -754,8 +942,9 @@ impl MemorySet { } if ph_type == xmas_elf::program::Type::Load { - let start_va: VirtAddr = (ph.virtual_addr() as usize).into(); - let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into(); + let start_va: VirtAddr = add_load_bias(ph.virtual_addr() as usize, load_bias)?.into(); + let end_va: VirtAddr = + add_load_bias((ph.virtual_addr() + ph.mem_size()) as usize, load_bias)?.into(); // 检查程序头表是否在这个 LOAD 段内 if phdr_load_vaddr.is_none() { @@ -764,7 +953,8 @@ impl MemorySet { if phdr_vaddr >= seg_file_start && phdr_vaddr < seg_file_end { // 程序头表在此段内,计算其虚拟地址 let offset_in_seg = phdr_vaddr - seg_file_start; - phdr_load_vaddr = Some(ph.virtual_addr() as usize + offset_in_seg); + phdr_load_vaddr = + Some(add_load_bias(ph.virtual_addr() as usize + offset_in_seg, load_bias)?); } } @@ -801,6 +991,9 @@ impl MemorySet { memory_set.insert_vma(vma, Some(seg_data))?; } } + if elf_type == xmas_elf::header::Type::SharedObject && interp_path.is_none() { + apply_static_pie_relocations(&mut memory_set, &elf, load_bias)?; + } let max_end_va: VirtAddr = max_end_vpn.into(); let start_brk: usize = max_end_va.into(); let layout = UserSpaceLayout { @@ -811,7 +1004,7 @@ impl MemorySet { }; let load_info = ElfLoadInfo { - entry_point: elf.header.pt2.entry_point() as usize, + entry_point: add_load_bias(elf.header.pt2.entry_point() as usize, load_bias)?, phdr_vaddr: phdr_load_vaddr.unwrap_or(0), phent_size: elf.header.pt2.ph_entry_size() as usize, phnum: ph_count as usize, diff --git a/os/src/sched/autogroup.rs b/os/src/sched/autogroup.rs new file mode 100644 index 00000000..e18506b5 --- /dev/null +++ b/os/src/sched/autogroup.rs @@ -0,0 +1,20 @@ +//! Minimal autogroup state shared with procfs/sysctl exposure. +//! +//! xxOS does not yet maintain Linux-style autogroup scheduling entities, but +//! LTP expects the global enable knob to exist and be writable through +//! `/proc/sys/kernel/sched_autogroup_enabled`. + +use core::sync::atomic::{AtomicBool, Ordering}; + +/// Global autogroup enable flag exposed via `/proc/sys/kernel/sched_autogroup_enabled`. +static SCHED_AUTOGROUP_ENABLED: AtomicBool = AtomicBool::new(true); + +/// Return whether scheduler autogrouping is currently enabled. +pub fn autogroup_enabled() -> bool { + SCHED_AUTOGROUP_ENABLED.load(Ordering::Relaxed) +} + +/// Update the scheduler autogroup enable flag. +pub fn set_autogroup_enabled(enabled: bool) { + SCHED_AUTOGROUP_ENABLED.store(enabled, Ordering::Relaxed); +} diff --git a/os/src/sched/mod.rs b/os/src/sched/mod.rs index 307fa9d0..e330fdc8 100644 --- a/os/src/sched/mod.rs +++ b/os/src/sched/mod.rs @@ -3,6 +3,7 @@ //! This module owns CPU-local scheduling state and context switching //! primitives. Task and process object definitions remain under `task`. +mod autogroup; mod api; mod context; mod policy; @@ -10,6 +11,7 @@ mod processor; mod runqueue; mod switch; +pub use autogroup::{autogroup_enabled, set_autogroup_enabled}; pub use api::{ block_current_and_run_next, current_task_need_resched, mark_current_task_need_resched, on_timer_tick, request_current_task_resched, schedule_if_needed, suspend_current_and_run_next, diff --git a/os/src/syscall/fs.rs b/os/src/syscall/fs.rs index 53ae597b..a9ece4d1 100644 --- a/os/src/syscall/fs.rs +++ b/os/src/syscall/fs.rs @@ -1456,8 +1456,8 @@ fn filter_open_flags(flags: i32) -> Result { } else { AccessMode::from_open_bits(flags)? }; - let ignored_flags = flags & (O_LARGEFILE | O_NOFOLLOW); - let unsupported_flags = flags & O_NOCTTY; + let ignored_flags = flags & (O_LARGEFILE | O_NOFOLLOW | O_NOCTTY); + let unsupported_flags = 0; let status_flags = FileStatusFlags::from_bits_truncate(flags & (O_APPEND | O_NONBLOCK)); let effective_flags = flags & !(ignored_flags | path_flag | O_APPEND | O_NONBLOCK); @@ -1773,6 +1773,160 @@ pub fn sys_sendfile64(out_fd: i32, in_fd: i32, offset: *mut i64, count: usize) - }) } +/// splice syscall:在两个 fd 之间搬运数据。 +/// +/// GNU grep probes `splice(2)` on pipe input and falls back to normal reads +/// when the fd combination is not supported. Returning ENOSYS is treated as an +/// input error by that grep build, so unsupported combinations must return +/// EINVAL instead. +pub fn sys_splice( + fd_in: i32, + off_in: *mut i64, + fd_out: i32, + off_out: *mut i64, + len: usize, + _flags: u32, +) -> isize { + trace!( + "kernel:pid[{}] sys_splice", + current_task().unwrap().process.upgrade().unwrap().getpid() + ); + syscall_body!({ + if fd_in < 0 || fd_out < 0 { + return Err(ERRNO::EBADF); + } + if len > isize::MAX as usize { + return Err(ERRNO::EINVAL); + } + if len == 0 { + return Ok(0); + } + + let in_desc = get_readable_file(fd_in as usize)?; + let out_desc = get_writable_file(fd_out as usize)?; + + // GNU grep probes splice on pipe input. Until the pipe fast path can + // preserve grep's buffered-output semantics, report the combination as + // unsupported so userspace uses its normal write path. + if !in_desc.is_seekable() || !out_desc.is_seekable() { + return Err(ERRNO::EINVAL); + } + + if out_desc.status_flags().contains(FileStatusFlags::APPEND) { + return Err(ERRNO::EINVAL); + } + if !off_in.is_null() && !in_desc.is_seekable() { + return Err(ERRNO::ESPIPE); + } + if !off_out.is_null() && !out_desc.is_seekable() { + return Err(ERRNO::ESPIPE); + } + + let mut in_pos = if off_in.is_null() { + if in_desc.is_seekable() { + Some(usize::try_from(in_desc.seek(0, 1)?).map_err(|_| ERRNO::EINVAL)?) + } else { + None + } + } else { + Some(parse_pos64(read_pod_from_user(off_in as *const i64)?)?) + }; + let mut out_pos = if off_out.is_null() { + if out_desc.is_seekable() { + Some(usize::try_from(out_desc.seek(0, 1)?).map_err(|_| ERRNO::EINVAL)?) + } else { + None + } + } else { + Some(parse_pos64(read_pod_from_user(off_out as *const i64)?)?) + }; + + let chunk_len = len.min(SENDFILE_CHUNK_SIZE); + let mut buf = Vec::new(); + buf.try_reserve_exact(chunk_len).map_err(|_| ERRNO::ENOMEM)?; + buf.resize(chunk_len, 0); + + let mut copied = 0usize; + let result: Result = (|| { + while copied < len { + let want = (len - copied).min(buf.len()); + let read = match in_pos { + Some(pos) => in_desc.read_bytes_at(pos, &mut buf[..want]), + None => in_desc.read_bytes_at(0, &mut buf[..want]), + }; + let read = match read { + Ok(n) => n, + Err(err) if copied > 0 => return Ok(copied as isize), + Err(err) => return Err(err), + }; + if read == 0 { + break; + } + + let written = match out_pos { + Some(pos) if !off_out.is_null() => out_desc.write_bytes_at(pos, &buf[..read]), + _ => out_desc.write_bytes(&buf[..read]), + }; + let written = match written { + Ok(n) => n, + Err(err) if copied > 0 => return Ok(copied as isize), + Err(err) => return Err(err), + }; + if written == 0 { + break; + } + + if let Some(pos) = &mut in_pos { + *pos = pos.checked_add(written).ok_or(ERRNO::EINVAL)?; + } + if let Some(pos) = &mut out_pos { + *pos = pos.checked_add(written).ok_or(ERRNO::EINVAL)?; + } + copied = copied.checked_add(written).ok_or(ERRNO::EINVAL)?; + if written < read { + break; + } + } + Ok(copied as isize) + })(); + + if off_in.is_null() { + if let Some(pos) = in_pos { + in_desc.seek(pos as i64, 0)?; + } + } else if let Some(pos) = in_pos { + write_pod_to_user(off_in, &(pos as i64))?; + } + if off_out.is_null() { + if let Some(pos) = out_pos { + out_desc.seek(pos as i64, 0)?; + } + } else if let Some(pos) = out_pos { + write_pod_to_user(off_out, &(pos as i64))?; + } + + result + }) +} + +/// fadvise64 syscall:接受用户态的文件访问模式提示。 +pub fn sys_fadvise64(fd: i32, _offset: i64, _len: usize, advice: i32) -> isize { + trace!( + "kernel:pid[{}] sys_fadvise64", + current_task().unwrap().process.upgrade().unwrap().getpid() + ); + syscall_body!({ + if fd < 0 { + return Err(ERRNO::EBADF); + } + get_any_file(fd as usize)?; + if !(0..=5).contains(&advice) { + return Err(ERRNO::EINVAL); + } + Ok(0) + }) +} + /// readv syscall:按 `iovec` 顺序将多个用户缓冲区从同一个 fd 读出。 pub fn sys_readv(fd: usize, iov: *const IoVec, iovcnt: i32) -> isize { trace!( @@ -2531,6 +2685,72 @@ pub fn sys_faccessat(dirfd: isize, path: *const u8, mode: i32) -> isize { }) } +/// `faccessat2` 系统调用:带 flags 的路径可访问性检查。 +pub fn sys_faccessat2(dirfd: isize, path: *const u8, mode: i32, flags: i32) -> isize { + trace!( + "kernel:pid[{}] sys_faccessat2", + current_task().unwrap().process.upgrade().unwrap().getpid() + ); + const AT_EACCESS: i32 = 0x200; + const SUPPORTED_FLAGS: i32 = AT_EACCESS | AT_EMPTY_PATH as i32 | AT_SYMLINK_NOFOLLOW as i32; + syscall_body!({ + if mode & !(R_OK | W_OK | X_OK) != 0 { + return Err(ERRNO::EINVAL); + } + if flags & !SUPPORTED_FLAGS != 0 { + return Err(ERRNO::EINVAL); + } + + let path = read_cstring_from_user(path, PATH_MAX)?; + if path.is_empty() && flags & AT_EMPTY_PATH as i32 == 0 { + return Err(ERRNO::ENOENT); + } + + let target = resolve_at_target(dirfd, path.as_str(), flags)?; + if mode == F_OK { + return Ok(0); + } + + let process = current_process(); + let uid = if flags & AT_EACCESS != 0 { + process.geteuid() + } else { + process.getuid() + }; + let gid = if flags & AT_EACCESS != 0 { + process.getegid() + } else { + process.getgid() + }; + + match target { + ResolvedAtTarget::Inode(inode) => { + if mode & W_OK != 0 && !path.is_empty() { + let cwd = resolve_dirfd_base(dirfd, path.as_str())?; + let abs_path = canonicalize(cwd.as_str(), path.as_str()); + if mount_is_readonly(abs_path.as_str()) { + return Err(ERRNO::EROFS); + } + } + if inode_allows_access(&inode, uid, gid, mode as u32) { + Ok(0) + } else { + Err(ERRNO::EACCES) + } + } + ResolvedAtTarget::FileDesc(desc) => { + let readable = mode & R_OK == 0 || desc.readable(); + let writable = mode & W_OK == 0 || desc.writable(); + if readable && writable { + Ok(0) + } else { + Err(ERRNO::EACCES) + } + } + } + }) +} + /// linkat syscall pub fn sys_linkat( old_dirfd: isize, diff --git a/os/src/syscall/mod.rs b/os/src/syscall/mod.rs index 64876397..4b974897 100644 --- a/os/src/syscall/mod.rs +++ b/os/src/syscall/mod.rs @@ -88,6 +88,8 @@ pub const SYSCALL_PREADV: usize = 69; pub const SYSCALL_PWRITEV: usize = 70; /// sendfile64 syscall pub const SYSCALL_SENDFILE64: usize = 71; +/// splice syscall +pub const SYSCALL_SPLICE: usize = 76; /// pselect6_time32 syscall pub const SYSCALL_PSELECT6_TIME32: usize = 72; /// ppoll_time32 syscall @@ -158,6 +160,8 @@ pub const SYSCALL_KILL: usize = 129; pub const SYSCALL_TKILL: usize = 130; /// tgkill syscall pub const SYSCALL_TGKILL: usize = 131; +/// sigaltstack syscall +pub const SYSCALL_SIGALTSTACK: usize = 132; /// sigsuspend syscall pub const SYSCALL_SIGSUSPEND: usize = 133; /// sigaction syscall @@ -280,6 +284,8 @@ pub const SYSCALL_CLONE: usize = 220; pub const SYSCALL_EXECVE: usize = 221; /// mmap syscall pub const SYSCALL_MMAP: usize = 222; +/// fadvise64 syscall +pub const SYSCALL_FADVISE64: usize = 223; /// mprotect syscall pub const SYSCALL_MPROTECT: usize = 226; /// msync syscall @@ -334,6 +340,8 @@ pub const SYSCALL_FSOPEN: usize = 430; pub const SYSCALL_FSPICK: usize = 433; /// pidfd_open syscall pub const SYSCALL_PIDFD_OPEN: usize = 434; +/// faccessat2 syscall +pub const SYSCALL_FACCESSAT2: usize = 439; /// memfd_secret syscall pub const SYSCALL_MEMFD_SECRET: usize = 447; /* @@ -533,6 +541,9 @@ pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { SYSCALL_FTRUNCATE => sys_ftruncate(args[0] as u32, args[1] as isize), SYSCALL_FALLOCATE => sys_fallocate(args[0] as u32, args[1] as i32, args[2] as i64, args[3] as i64), SYSCALL_FACCESSAT => sys_faccessat(args[0] as isize, args[1] as *const u8, args[2] as i32), + SYSCALL_FACCESSAT2 => { + sys_faccessat2(args[0] as isize, args[1] as *const u8, args[2] as i32, args[3] as i32) + } SYSCALL_FCHMOD => sys_fchmod(args[0] as u32, args[1] as u32), SYSCALL_FCHMODAT => sys_fchmodat(args[0] as isize, args[1] as *const u8, args[2] as u32), SYSCALL_FCHOWNAT => sys_fchownat( @@ -572,6 +583,15 @@ pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { ), SYSCALL_SENDFILE64 => sys_sendfile64(args[0] as i32, args[1] as i32, args[2] as *mut i64, args[3]), + SYSCALL_SPLICE => sys_splice( + args[0] as i32, + args[1] as *mut i64, + args[2] as i32, + args[3] as *mut i64, + args[4], + args[5] as u32, + ), + SYSCALL_FADVISE64 => sys_fadvise64(args[0] as i32, args[1] as i64, args[2], args[3] as i32), SYSCALL_READLINKAT => sys_readlinkat( args[0] as isize, args[1] as *const u8, @@ -817,6 +837,9 @@ pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { SYSCALL_MEMFD_CREATE => sys_memfd_create(args[0] as *const u8, args[1] as u32), SYSCALL_BPF => sys_bpf(args[0] as u32, args[1], args[2] as u32), SYSCALL_USERFAULTFD => sys_userfaultfd(args[0] as i32), + SYSCALL_SIGALTSTACK => { + sys_sigaltstack(args[0] as *const SigAltStack, args[1] as *mut SigAltStack) + } SYSCALL_SIGACTION => sys_sigaction( args[0] as i32, args[1] as *const UserSigAction, diff --git a/os/src/syscall/signal.rs b/os/src/syscall/signal.rs index a2eca1f2..5041280f 100644 --- a/os/src/syscall/signal.rs +++ b/os/src/syscall/signal.rs @@ -26,6 +26,41 @@ struct SigSet64(u64); impl Pod for SigSet64 {} +/// Linux `stack_t` used by `sigaltstack(2)`. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct SigAltStack { + ss_sp: usize, + ss_flags: i32, + ss_size: usize, +} + +impl Pod for SigAltStack {} + +const SS_DISABLE: i32 = 2; + +pub fn sys_sigaltstack(new_stack: *const SigAltStack, old_stack: *mut SigAltStack) -> isize { + syscall_body!({ + if !old_stack.is_null() { + write_pod_to_user( + old_stack, + &SigAltStack { + ss_sp: 0, + ss_flags: SS_DISABLE, + ss_size: 0, + }, + )?; + } + if !new_stack.is_null() { + let stack = read_pod_from_user(new_stack)?; + if stack.ss_flags & !SS_DISABLE != 0 { + return Err(ERRNO::EINVAL); + } + } + Ok(0) + }) +} + fn read_user_sigset(mask: *const u64, sigsetsize: usize) -> Result { if mask.is_null() { return Err(ERRNO::EFAULT); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 25a66473..92e20275 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] profile = "minimal" # use the nightly version of the last stable toolchain, see -channel = "nightly-2025-01-17" -components = ["rust-src", "llvm-tools-preview", "rustfmt", "clippy"] +channel = "nightly-2025-01-18" +components = ["llvm-tools-preview"] targets = ["riscv64gc-unknown-none-elf"] diff --git a/scripts/export-eval-tree.sh b/scripts/export-eval-tree.sh index 598f5a8a..4d3b4751 100644 --- a/scripts/export-eval-tree.sh +++ b/scripts/export-eval-tree.sh @@ -6,6 +6,16 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$PROJECT_ROOT" +flatten_regular_submodule() { + local submodule_path="$1" + + # 普通子模块直接展开成目录,避免评测环境依赖 submodule。 + git rm --cached -f -q "$submodule_path" + rm -rf "$submodule_path/.git" + git add -A "$submodule_path" + echo "[INFO] done: flatten submodule $submodule_path" +} + flatten_submodules() { if [ ! -f .gitmodules ]; then echo "[INFO] .gitmodules not found, skipping submodule flatten step" @@ -15,6 +25,7 @@ flatten_submodules() { echo "[INFO] syncing and updating submodules" git submodule sync --recursive git submodule update --init --recursive + rm -f rootfs.tar while IFS= read -r submodule_path; do [ -n "$submodule_path" ] || continue @@ -25,29 +36,14 @@ flatten_submodules() { fi echo "[INFO] flatten submodule: $submodule_path" - git rm --cached -f -q "$submodule_path" - rm -rf "$submodule_path/.git" - git add -A "$submodule_path" + flatten_regular_submodule "$submodule_path" done < <(git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk '{print $2}') git rm --cached -f -q .gitmodules || true rm -f .gitmodules -} - -strip_hidden_files() { - echo "[INFO] removing hidden files to match evaluation environment" - - while IFS= read -r relpath; do - [ -n "$relpath" ] || continue - rm -rf -- "$relpath" - done < <( - find . \ - \( -path './.git' -o -path './.git/*' \) -prune -o \ - -name '.*' -printf '%P\n' | sort - ) - git add -A + echo "[INFO] done: remove .gitmodules from evaluation snapshot" } flatten_submodules -strip_hidden_files +echo "[INFO] done: export evaluation tree" diff --git a/scripts/pack-disk-img.sh b/scripts/pack-disk-img.sh index d58bdbbd..e737cee3 100755 --- a/scripts/pack-disk-img.sh +++ b/scripts/pack-disk-img.sh @@ -1,12 +1,15 @@ #!/usr/bin/env bash set -euo pipefail -ROOTFS_DIR="${1:-rootfs}" +ROOTFS_DIR="${1:-CosmOS-rootfs/rootfs}" USER_BIN_DIR="${2:-user/target/riscv64gc-unknown-none-elf/release}" OUT_IMG="${3:-disk.img}" EXTRA_MIB="${EXTRA_MIB:-512}" MIN_SIZE_MIB="${MIN_SIZE_MIB:-1024}" LABEL="${LABEL:-COSMOSDISK}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +USER_APP_SRC_DIR="${USER_APP_SRC_DIR:-$PROJECT_ROOT/user/src/bin}" require_tool() { if ! command -v "$1" >/dev/null 2>&1; then @@ -33,6 +36,11 @@ if [ ! -d "$USER_BIN_DIR" ]; then exit 1 fi +if [ ! -d "$USER_APP_SRC_DIR" ]; then + echo "user app source directory not found: $USER_APP_SRC_DIR" >&2 + exit 1 +fi + STAGE_DIR="$(mktemp -d /tmp/pack-disk-img.XXXXXX)" cleanup() { rm -rf "$STAGE_DIR" @@ -46,16 +54,19 @@ if [ ! -d "$STAGE_DIR/root" ]; then exit 1 fi -for host_path in "$USER_BIN_DIR"/*; do - [ -f "$host_path" ] || continue - [ -x "$host_path" ] || continue +for app_src in "$USER_APP_SRC_DIR"/*.rs; do + [ -f "$app_src" ] || continue - name="$(basename "$host_path")" - case "$name" in - *.bin|*.elf) - continue - ;; - esac + name="$(basename "$app_src" .rs)" + host_path="$USER_BIN_DIR/$name" + if [ ! -f "$host_path" ]; then + echo "user app binary not found: $host_path" >&2 + exit 1 + fi + if [ ! -x "$host_path" ]; then + echo "user app binary is not executable: $host_path" >&2 + exit 1 + fi cp -f "$host_path" "$STAGE_DIR/root/$name" done @@ -68,6 +79,10 @@ if [ -f lib/glibc/ar ] && [ -d "$STAGE_DIR/glibc/lib" ]; then cp -f lib/glibc/ar "$STAGE_DIR/glibc/lib/ar" fi +if [ -e "$STAGE_DIR/lib/libc.so" ] && [ ! -e "$STAGE_DIR/lib/ld-musl-riscv64-sf.so.1" ]; then + ln -sf libc.so "$STAGE_DIR/lib/ld-musl-riscv64-sf.so.1" +fi + rootfs_mib="$(du -sm "$STAGE_DIR" | awk '{print $1}')" size_mib=$((rootfs_mib + EXTRA_MIB)) if [ "$size_mib" -lt "$MIN_SIZE_MIB" ]; then diff --git a/user/.cargo/config.toml b/user/cargo-config/config.toml similarity index 100% rename from user/.cargo/config.toml rename to user/cargo-config/config.toml diff --git a/user/rust-toolchain.toml b/user/rust-toolchain.toml index c027a33c..080afe11 100644 --- a/user/rust-toolchain.toml +++ b/user/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] profile = "minimal" # use the nightly version of the last stable toolchain, see -channel = "nightly-2025-01-17" -components = ["rust-src", "llvm-tools-preview", "rustfmt", "clippy"] +channel = "nightly-2025-01-18" +components = ["llvm-tools-preview"] diff --git a/vendor/README.md b/vendor/README.md new file mode 100644 index 00000000..db61f550 --- /dev/null +++ b/vendor/README.md @@ -0,0 +1,108 @@ +# Vendor 依赖说明 + +本目录保存评测环境不能访问 GitHub 时仍需使用的 Cargo Git 依赖源码。 + +当前 `os/Cargo.toml` 直接使用本地 path 依赖: + +```toml +riscv = { path = "../vendor/riscv", features = ["inline-asm"] } +smoltcp = { path = "../vendor/smoltcp", default-features = false, features = [ + "alloc", + "medium-ethernet", + "proto-ipv4", + "proto-ipv4-fragmentation", + "socket-udp", + "socket-tcp", + "fragmentation-buffer-size-16384", +] } +``` + +因此 Cargo 会直接读取本目录中的源码;如果目录缺失或内容不完整,会直接报错,不会自动回退到 GitHub。 + +## 当前来源 + +- `vendor/riscv` + - 原依赖:`https://github.com/rcore-os/riscv` + - 当前提交:`11d43cf7cccb3b62a3caaf3e07a1db7449588f9a` +- `vendor/smoltcp` + - 原依赖:`https://github.com/KyleMao2023/smoltcp` + - 当前分支:`os` + - 当前提交:`1e0289f919427e009f216da6eed774a4ccb5da2b` + +## 手动更新流程 + +1. 临时把 `os/Cargo.toml` 改回 Git 依赖。 + +```toml +riscv = { git = "https://github.com/rcore-os/riscv", features = ["inline-asm"] } +smoltcp = { git = "https://github.com/KyleMao2023/smoltcp", branch = "os", default-features = false, features = [ + "alloc", + "medium-ethernet", + "proto-ipv4", + "proto-ipv4-fragmentation", + "socket-udp", + "socket-tcp", + "fragmentation-buffer-size-16384", +] } +``` + +2. 让 Cargo 拉取并锁定新版本。 + +```sh +cd os +cargo update -p riscv -p smoltcp +``` + +3. 从 `os/Cargo.lock` 中确认新的提交号。 + +```sh +rg 'name = "riscv"|name = "smoltcp"|source = "git\\+' Cargo.lock +``` + +4. 找到 Cargo git 缓存中的对应源码目录。 + +```sh +find ~/.cargo/git/checkouts -maxdepth 3 -type d -name '<短提交号>' +``` + +例如提交 `11d43cf7cccb3b62a3caaf3e07a1db7449588f9a` 对应的短提交号通常是 `11d43cf`。 + +5. 用缓存内容覆盖项目内 vendor 目录。 + +```sh +rsync -a --delete --exclude .git --exclude .cargo-ok \ + ~/.cargo/git/checkouts/// \ + ../vendor/riscv/ + +rsync -a --delete --exclude .git --exclude .cargo-ok \ + ~/.cargo/git/checkouts/// \ + ../vendor/smoltcp/ +``` + +6. 把 `os/Cargo.toml` 恢复为本地 path 依赖。 + +```toml +riscv = { path = "../vendor/riscv", features = ["inline-asm"] } +smoltcp = { path = "../vendor/smoltcp", default-features = false, features = [ + "alloc", + "medium-ethernet", + "proto-ipv4", + "proto-ipv4-fragmentation", + "socket-udp", + "socket-tcp", + "fragmentation-buffer-size-16384", +] } +``` + +7. 离线验证。 + +```sh +cd os +cargo check --offline +``` + +8. 确认没有 GitHub Git 依赖残留。 + +```sh +rg 'git\\+https://github\\.com|git\\s*=\\s*"https://github\\.com' -n --glob 'Cargo.toml' --glob 'Cargo.lock' +``` diff --git a/vendor/riscv/.github/CODEOWNERS b/vendor/riscv/.github/CODEOWNERS new file mode 100644 index 00000000..87f6c03f --- /dev/null +++ b/vendor/riscv/.github/CODEOWNERS @@ -0,0 +1 @@ +* @rust-embedded/riscv \ No newline at end of file diff --git a/vendor/riscv/.github/bors.toml b/vendor/riscv/.github/bors.toml new file mode 100644 index 00000000..ca42be0a --- /dev/null +++ b/vendor/riscv/.github/bors.toml @@ -0,0 +1,4 @@ +block_labels = ["needs-decision"] +delete_merged_branches = true +required_approvals = 1 +status = ["continuous-integration/travis-ci/push"] diff --git a/vendor/riscv/.gitignore b/vendor/riscv/.gitignore new file mode 100644 index 00000000..e38997ad --- /dev/null +++ b/vendor/riscv/.gitignore @@ -0,0 +1,5 @@ +Cargo.lock +target/ +bin/*.after +bin/*.before +bin/*.o diff --git a/vendor/riscv/.travis.yml b/vendor/riscv/.travis.yml new file mode 100644 index 00000000..b70d20ab --- /dev/null +++ b/vendor/riscv/.travis.yml @@ -0,0 +1,51 @@ +language: rust + +env: + - TARGET=x86_64-unknown-linux-gnu + - TARGET=riscv32imac-unknown-none-elf + - TARGET=riscv64imac-unknown-none-elf + - TARGET=riscv64gc-unknown-none-elf + +rust: + - nightly + - stable + - 1.42.0 # MSRV + +if: (branch = staging OR branch = trying OR branch = master) OR (type = pull_request AND branch = master) + +matrix: + allow_failures: + - rust: nightly + + include: + - env: CHECK_BLOBS=1 + rust: + language: bash + if: (branch = staging OR branch = trying OR branch = master) OR (type = pull_request AND branch = master) + + - env: RUSTFMT=1 + rust: stable + if: (branch = staging OR branch = trying OR branch = master) OR (type = pull_request AND branch = master) + + +install: + - ci/install.sh + +script: + - ci/script.sh + + +cache: + cargo: true + directories: + - gcc + +branches: + only: + - master + - staging + - trying + +notifications: + email: + on_success: never diff --git a/vendor/riscv/CHANGELOG.md b/vendor/riscv/CHANGELOG.md new file mode 100644 index 00000000..e4d7b23d --- /dev/null +++ b/vendor/riscv/CHANGELOG.md @@ -0,0 +1,45 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +## [v0.6.0] - 2020-06-20 + +### Changed + +- `Mtvec::trap_mode()`, `Stvec::trap_mode()` and `Utvec::trap_mode()` functions now return `Option` (breaking change) +- Updated Minimum Supported Rust Version to 1.42.0 +- Use `llvm_asm!` instead of `asm!` + +### Removed + +- vexriscv-specific registers were moved to the `vexriscv` crate + +## [v0.5.6] - 2020-03-14 + +### Added + +- Added vexriscv-specific registers + +## [v0.5.5] - 2020-02-28 + +### Added + +- Added `riscv32i-unknown-none-elf` target support +- Added user trap setup and handling registers +- Added write methods for the `mip` and `satp` registers +- Added `mideleg` register +- Added Changelog + +### Changed + +- Fixed MSRV by restricting the upper bound of `bare-metal` version + +[Unreleased]: https://github.com/rust-embedded/riscv/compare/v0.6.0...HEAD +[v0.6.0]: https://github.com/rust-embedded/riscv/compare/v0.5.6...v0.6.0 +[v0.5.6]: https://github.com/rust-embedded/riscv/compare/v0.5.5...v0.5.6 +[v0.5.5]: https://github.com/rust-embedded/riscv/compare/v0.5.4...v0.5.5 diff --git a/vendor/riscv/CODE_OF_CONDUCT.md b/vendor/riscv/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..fccadf9a --- /dev/null +++ b/vendor/riscv/CODE_OF_CONDUCT.md @@ -0,0 +1,37 @@ +# The Rust Code of Conduct + +## Conduct + +**Contact**: [RISC-V team](https://github.com/rust-embedded/wg#the-riscv-team) + +* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. +* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. +* Please be kind and courteous. There's no need to be mean or rude. +* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. +* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. +* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the [Citizen Code of Conduct](http://citizencodeofconduct.org/); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. +* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [RISC-V team][team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. + +## Moderation + +These are the policies for upholding our community's standards of conduct. + +1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. +3. Moderators will first respond to such remarks with a warning. +4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. +5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. +6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. +7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. +8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. + +In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. + +And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. + +The enforcement policies listed above apply to all official embedded WG venues; including official IRC channels (#rust-embedded); GitHub repositories under rust-embedded; and all forums under rust-embedded.org (forum.rust-embedded.org). + +*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* + +[team]: https://github.com/rust-embedded/wg#the-riscv-team diff --git a/vendor/riscv/Cargo.toml b/vendor/riscv/Cargo.toml new file mode 100644 index 00000000..4e231932 --- /dev/null +++ b/vendor/riscv/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "riscv" +version = "0.6.0" +repository = "https://github.com/rust-embedded/riscv" +authors = ["The RISC-V Team "] +categories = ["embedded", "hardware-support", "no-std"] +description = "Low level access to RISC-V processors" +keywords = ["riscv", "register", "peripheral"] +license = "ISC" + +[dependencies] +bare-metal = "0.2.5" +bitflags = "1.0" +bit_field = "0.10.0" +log = "0.4" + +[build-dependencies] +riscv-target = "0.1.2" + +[features] +inline-asm = [] diff --git a/vendor/riscv/README.md b/vendor/riscv/README.md new file mode 100644 index 00000000..98c7a42c --- /dev/null +++ b/vendor/riscv/README.md @@ -0,0 +1,41 @@ +[![crates.io](https://img.shields.io/crates/d/riscv.svg)](https://crates.io/crates/riscv) +[![crates.io](https://img.shields.io/crates/v/riscv.svg)](https://crates.io/crates/riscv) +[![Build Status](https://travis-ci.org/rust-embedded/riscv.svg?branch=master)](https://travis-ci.org/rust-embedded/riscv) + +# `riscv` + +> Low level access to RISC-V processors + +This project is developed and maintained by the [RISC-V team][team]. + +## [Documentation](https://docs.rs/crate/riscv) + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.42.0 and up. It *might* +compile with older versions but that may change in any new patch release. + +## License + +Copyright 2019-2020 [RISC-V team][team] + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +## Code of Conduct + +Contribution to this crate is organized under the terms of the [Rust Code of +Conduct][CoC], the maintainer of this crate, the [RISC-V team][team], promises +to intervene to uphold that code of conduct. + +[CoC]: CODE_OF_CONDUCT.md +[team]: https://github.com/rust-embedded/wg#the-riscv-team diff --git a/vendor/riscv/asm.S b/vendor/riscv/asm.S new file mode 100644 index 00000000..9ae7aaa8 --- /dev/null +++ b/vendor/riscv/asm.S @@ -0,0 +1,454 @@ +#include "asm.h" + +.section .text.__ebreak +.global __ebreak +__ebreak: + ebreak + ret + +.section .text.__wfi +.global __wfi +__wfi: + wfi + ret + +.section .text.__sfence_vma_all +.global __sfence_vma_all +__sfence_vma_all: + sfence.vma + ret + +.section .text.__sfence_vma +.global __sfence_vma +__sfence_vma: + sfence.vma a0, a1 + ret + +// RISC-V hypervisor instructions. + +// The switch for enabling LLVM support for asm generation. +// #define LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + + +.section .text.__hfence_gvma +.global __hfence_gvma +__hfence_gvma: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hfence.gvma a0, a1 +#else + .word 1656029299 +#endif + ret +.section .text.__hfence_vvma +.global __hfence_vvma +__hfence_vvma: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hfence.vvma a0, a1 +#else + .word 582287475 +#endif + ret +.section .text.__hlv_b +.global __hlv_b +__hlv_b: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.b a0, a0 +#else + .word 1610958195 +#endif + ret +.section .text.__hlv_bu +.global __hlv_bu +__hlv_bu: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.bu a0, a0 +#else + .word 1612006771 +#endif + ret +.section .text.__hlv_h +.global __hlv_h +__hlv_h: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.h a0, a0 +#else + .word 1678067059 +#endif + ret +.section .text.__hlv_hu +.global __hlv_hu +__hlv_hu: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.hu a0, a0 +#else + .word 1679115635 +#endif + ret +.section .text.__hlvx_hu +.global __hlvx_hu +__hlvx_hu: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlvx.hu a0, a0 +#else + .word 1681212787 +#endif + ret +.section .text.__hlv_w +.global __hlv_w +__hlv_w: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.w a0, a0 +#else + .word 1745175923 +#endif + ret +.section .text.__hlvx_wu +.global __hlvx_wu +__hlvx_wu: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlvx.wu a0, a0 +#else + .word 1748321651 +#endif + ret +.section .text.__hsv_b +.global __hsv_b +__hsv_b: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hsv.b a0, a1 +#else + .word 1656045683 +#endif + ret +.section .text.__hsv_h +.global __hsv_h +__hsv_h: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hsv.h a0, a1 +#else + .word 1723154547 +#endif + ret +.section .text.__hsv_w +.global __hsv_w +__hsv_w: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hsv.w a0, a1 +#else + .word 1790263411 +#endif + ret +.section .text.__hlv_wu +.global __hlv_wu +__hlv_wu: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.wu a0, a0 +#else + .word 1746224499 +#endif + ret +.section .text.__hlv_d +.global __hlv_d +__hlv_d: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hlv.d a0, a0 +#else + .word 1812284787 +#endif + ret +.section .text.__hsv_d +.global __hsv_d +__hsv_d: +#ifdef LLVM_RISCV_HYPERVISOR_EXTENSION_SUPPORT + hsv.d a0, a1 +#else + .word 1857372275 +#endif + ret + + +// User Trap Setup +RW(0x000, ustatus) // User status register +RW(0x004, uie) // User interrupt-enable register +RW(0x005, utvec) // User trap handler base address + +// User Trap Handling +RW(0x040, uscratch) // Scratch register for user trap handlers +RW(0x041, uepc) // User exception program counter +RW(0x042, ucause) // User trap cause +RW(0x043, utval) // User bad address or instruction +RW(0x044, uip) // User interrupt pending + +// User Floating-Point CSRs +RW(0x001, fflags) // Floating-Point Accrued Exceptions +RW(0x002, frm) // Floating-Point Dynamic Rounding Mode +RW(0x003, fcsr) // Floating-Point Control and Status Register (frm + fflags) + +// User Counter/Timers +RO( 0xC00, cycle) // Cycle counter for RDCYCLE instruction +RO( 0xC01, time) // Timer for RDTIME instruction +RO( 0xC02, instret) // Instructions-retired counter for RDINSTRET instruction +RO( 0xC03, hpmcounter3) // Performance-monitoring counter +RO( 0xC04, hpmcounter4) // Performance-monitoring counter +RO( 0xC05, hpmcounter5) // Performance-monitoring counter +RO( 0xC06, hpmcounter6) // Performance-monitoring counter +RO( 0xC07, hpmcounter7) // Performance-monitoring counter +RO( 0xC08, hpmcounter8) // Performance-monitoring counter +RO( 0xC09, hpmcounter9) // Performance-monitoring counter +RO( 0xC0A, hpmcounter10) // Performance-monitoring counter +RO( 0xC0B, hpmcounter11) // Performance-monitoring counter +RO( 0xC0C, hpmcounter12) // Performance-monitoring counter +RO( 0xC0D, hpmcounter13) // Performance-monitoring counter +RO( 0xC0E, hpmcounter14) // Performance-monitoring counter +RO( 0xC0F, hpmcounter15) // Performance-monitoring counter +RO( 0xC10, hpmcounter16) // Performance-monitoring counter +RO( 0xC11, hpmcounter17) // Performance-monitoring counter +RO( 0xC12, hpmcounter18) // Performance-monitoring counter +RO( 0xC13, hpmcounter19) // Performance-monitoring counter +RO( 0xC14, hpmcounter20) // Performance-monitoring counter +RO( 0xC15, hpmcounter21) // Performance-monitoring counter +RO( 0xC16, hpmcounter22) // Performance-monitoring counter +RO( 0xC17, hpmcounter23) // Performance-monitoring counter +RO( 0xC18, hpmcounter24) // Performance-monitoring counter +RO( 0xC19, hpmcounter25) // Performance-monitoring counter +RO( 0xC1A, hpmcounter26) // Performance-monitoring counter +RO( 0xC1B, hpmcounter27) // Performance-monitoring counter +RO( 0xC1C, hpmcounter28) // Performance-monitoring counter +RO( 0xC1D, hpmcounter29) // Performance-monitoring counter +RO( 0xC1E, hpmcounter30) // Performance-monitoring counter +RO( 0xC1F, hpmcounter31) // Performance-monitoring counter +RO32(0xC80, cycleh) // Upper 32 bits of cycle, RV32I only +RO32(0xC81, timeh) // Upper 32 bits of time, RV32I only +RO32(0xC82, instreth) // Upper 32 bits of instret, RV32I only +RO32(0xC83, hpmcounter3h) // Upper 32 bits of hpmcounter3, RV32I only +RO32(0xC84, hpmcounter4h) +RO32(0xC85, hpmcounter5h) +RO32(0xC86, hpmcounter6h) +RO32(0xC87, hpmcounter7h) +RO32(0xC88, hpmcounter8h) +RO32(0xC89, hpmcounter9h) +RO32(0xC8A, hpmcounter10h) +RO32(0xC8B, hpmcounter11h) +RO32(0xC8C, hpmcounter12h) +RO32(0xC8D, hpmcounter13h) +RO32(0xC8E, hpmcounter14h) +RO32(0xC8F, hpmcounter15h) +RO32(0xC90, hpmcounter16h) +RO32(0xC91, hpmcounter17h) +RO32(0xC92, hpmcounter18h) +RO32(0xC93, hpmcounter19h) +RO32(0xC94, hpmcounter20h) +RO32(0xC95, hpmcounter21h) +RO32(0xC96, hpmcounter22h) +RO32(0xC97, hpmcounter23h) +RO32(0xC98, hpmcounter24h) +RO32(0xC99, hpmcounter25h) +RO32(0xC9A, hpmcounter26h) +RO32(0xC9B, hpmcounter27h) +RO32(0xC9C, hpmcounter28h) +RO32(0xC9D, hpmcounter29h) +RO32(0xC9E, hpmcounter30h) +RO32(0xC9F, hpmcounter31h) + +// Supervisor Trap Setup +RW(0x100, sstatus) // Supervisor status register +RW(0x102, sedeleg) // Supervisor exception delegation register +RW(0x103, sideleg) // Supervisor interrupt delegation register +RW(0x104, sie) // Supervisor interrupt-enable register +RW(0x105, stvec) // Supervisor trap handler base address +RW(0x106, scounteren) // Supervisor counter enable + +// Supervisor Trap Handling +RW(0x140, sscratch) // Scratch register for supervisor trap handlers +RW(0x141, sepc) // Supervisor exception program counter +RW(0x142, scause) // Supervisor trap cause +RW(0x143, stval) // Supervisor bad address or instruction +RW(0x144, sip) // Supervisor interrupt pending + +// Supervisor Protection and Translation +RW(0x180, satp) // Supervisor address translation and protection + +// Machine Information Registers +RO(0xF11, mvendorid) // Vendor ID +RO(0xF12, marchid) // Architecture ID +RO(0xF13, mimpid) // Implementation ID +RO(0xF14, mhartid) // Hardware thread ID + +// Machine Trap Setup +RW(0x300, mstatus) // Machine status register +RW(0x301, misa) // ISA and extensions +RW(0x302, medeleg) // Machine exception delegation register +RW(0x303, mideleg) // Machine interrupt delegation register +RW(0x304, mie) // Machine interrupt-enable register +RW(0x305, mtvec) // Machine trap handler base address +RW(0x306, mcounteren) // Machine counter enable + +// Machine Trap Handling +RW(0x340, mscratch) // Scratch register for machine trap handlers +RW(0x341, mepc) // Machine exception program counter +RW(0x342, mcause) // Machine trap cause +RW(0x343, mtval) // Machine bad address or instruction +RW(0x344, mip) // Machine interrupt pending + +// Machine Protection and Translation +RW( 0x3A0, pmpcfg0) // Physical memory protection configuration +RW32(0x3A1, pmpcfg1) // Physical memory protection configuration, RV32 only +RW( 0x3A2, pmpcfg2) // Physical memory protection configuration +RW32(0x3A3, pmpcfg3) // Physical memory protection configuration, RV32 only +RW( 0x3B0, pmpaddr0) // Physical memory protection address register +RW( 0x3B1, pmpaddr1) // Physical memory protection address register +RW( 0x3B2, pmpaddr2) // Physical memory protection address register +RW( 0x3B3, pmpaddr3) // Physical memory protection address register +RW( 0x3B4, pmpaddr4) // Physical memory protection address register +RW( 0x3B5, pmpaddr5) // Physical memory protection address register +RW( 0x3B6, pmpaddr6) // Physical memory protection address register +RW( 0x3B7, pmpaddr7) // Physical memory protection address register +RW( 0x3B8, pmpaddr8) // Physical memory protection address register +RW( 0x3B9, pmpaddr9) // Physical memory protection address register +RW( 0x3BA, pmpaddr10) // Physical memory protection address register +RW( 0x3BB, pmpaddr11) // Physical memory protection address register +RW( 0x3BC, pmpaddr12) // Physical memory protection address register +RW( 0x3BD, pmpaddr13) // Physical memory protection address register +RW( 0x3BE, pmpaddr14) // Physical memory protection address register +RW( 0x3BF, pmpaddr15) // Physical memory protection address register + +// Machine Counter/Timers +RO( 0xB00, mcycle) // Machine cycle counter +RO( 0xB02, minstret) // Machine instructions-retired counter +RO( 0xB03, mhpmcounter3) // Machine performance-monitoring counter +RO( 0xB04, mhpmcounter4) // Machine performance-monitoring counter +RO( 0xB05, mhpmcounter5) // Machine performance-monitoring counter +RO( 0xB06, mhpmcounter6) // Machine performance-monitoring counter +RO( 0xB07, mhpmcounter7) // Machine performance-monitoring counter +RO( 0xB08, mhpmcounter8) // Machine performance-monitoring counter +RO( 0xB09, mhpmcounter9) // Machine performance-monitoring counter +RO( 0xB0A, mhpmcounter10) // Machine performance-monitoring counter +RO( 0xB0B, mhpmcounter11) // Machine performance-monitoring counter +RO( 0xB0C, mhpmcounter12) // Machine performance-monitoring counter +RO( 0xB0D, mhpmcounter13) // Machine performance-monitoring counter +RO( 0xB0E, mhpmcounter14) // Machine performance-monitoring counter +RO( 0xB0F, mhpmcounter15) // Machine performance-monitoring counter +RO( 0xB10, mhpmcounter16) // Machine performance-monitoring counter +RO( 0xB11, mhpmcounter17) // Machine performance-monitoring counter +RO( 0xB12, mhpmcounter18) // Machine performance-monitoring counter +RO( 0xB13, mhpmcounter19) // Machine performance-monitoring counter +RO( 0xB14, mhpmcounter20) // Machine performance-monitoring counter +RO( 0xB15, mhpmcounter21) // Machine performance-monitoring counter +RO( 0xB16, mhpmcounter22) // Machine performance-monitoring counter +RO( 0xB17, mhpmcounter23) // Machine performance-monitoring counter +RO( 0xB18, mhpmcounter24) // Machine performance-monitoring counter +RO( 0xB19, mhpmcounter25) // Machine performance-monitoring counter +RO( 0xB1A, mhpmcounter26) // Machine performance-monitoring counter +RO( 0xB1B, mhpmcounter27) // Machine performance-monitoring counter +RO( 0xB1C, mhpmcounter28) // Machine performance-monitoring counter +RO( 0xB1D, mhpmcounter29) // Machine performance-monitoring counter +RO( 0xB1E, mhpmcounter30) // Machine performance-monitoring counter +RO( 0xB1F, mhpmcounter31) // Machine performance-monitoring counter +RO32(0xB80, mcycleh) // Upper 32 bits of mcycle, RV32I only +RO32(0xB82, minstreth) // Upper 32 bits of minstret, RV32I only +RO32(0xB83, mhpmcounter3h) // Upper 32 bits of mhpmcounter3, RV32I only +RO32(0xB84, mhpmcounter4h) +RO32(0xB85, mhpmcounter5h) +RO32(0xB86, mhpmcounter6h) +RO32(0xB87, mhpmcounter7h) +RO32(0xB88, mhpmcounter8h) +RO32(0xB89, mhpmcounter9h) +RO32(0xB8A, mhpmcounter10h) +RO32(0xB8B, mhpmcounter11h) +RO32(0xB8C, mhpmcounter12h) +RO32(0xB8D, mhpmcounter13h) +RO32(0xB8E, mhpmcounter14h) +RO32(0xB8F, mhpmcounter15h) +RO32(0xB90, mhpmcounter16h) +RO32(0xB91, mhpmcounter17h) +RO32(0xB92, mhpmcounter18h) +RO32(0xB93, mhpmcounter19h) +RO32(0xB94, mhpmcounter20h) +RO32(0xB95, mhpmcounter21h) +RO32(0xB96, mhpmcounter22h) +RO32(0xB97, mhpmcounter23h) +RO32(0xB98, mhpmcounter24h) +RO32(0xB99, mhpmcounter25h) +RO32(0xB9A, mhpmcounter26h) +RO32(0xB9B, mhpmcounter27h) +RO32(0xB9C, mhpmcounter28h) +RO32(0xB9D, mhpmcounter29h) +RO32(0xB9E, mhpmcounter30h) +RO32(0xB9F, mhpmcounter31h) + +RW(0x323, mhpmevent3) // Machine performance-monitoring event selector +RW(0x324, mhpmevent4) // Machine performance-monitoring event selector +RW(0x325, mhpmevent5) // Machine performance-monitoring event selector +RW(0x326, mhpmevent6) // Machine performance-monitoring event selector +RW(0x327, mhpmevent7) // Machine performance-monitoring event selector +RW(0x328, mhpmevent8) // Machine performance-monitoring event selector +RW(0x329, mhpmevent9) // Machine performance-monitoring event selector +RW(0x32A, mhpmevent10) // Machine performance-monitoring event selector +RW(0x32B, mhpmevent11) // Machine performance-monitoring event selector +RW(0x32C, mhpmevent12) // Machine performance-monitoring event selector +RW(0x32D, mhpmevent13) // Machine performance-monitoring event selector +RW(0x32E, mhpmevent14) // Machine performance-monitoring event selector +RW(0x32F, mhpmevent15) // Machine performance-monitoring event selector +RW(0x330, mhpmevent16) // Machine performance-monitoring event selector +RW(0x331, mhpmevent17) // Machine performance-monitoring event selector +RW(0x332, mhpmevent18) // Machine performance-monitoring event selector +RW(0x333, mhpmevent19) // Machine performance-monitoring event selector +RW(0x334, mhpmevent20) // Machine performance-monitoring event selector +RW(0x335, mhpmevent21) // Machine performance-monitoring event selector +RW(0x336, mhpmevent22) // Machine performance-monitoring event selector +RW(0x337, mhpmevent23) // Machine performance-monitoring event selector +RW(0x338, mhpmevent24) // Machine performance-monitoring event selector +RW(0x339, mhpmevent25) // Machine performance-monitoring event selector +RW(0x33A, mhpmevent26) // Machine performance-monitoring event selector +RW(0x33B, mhpmevent27) // Machine performance-monitoring event selector +RW(0x33C, mhpmevent28) // Machine performance-monitoring event selector +RW(0x33D, mhpmevent29) // Machine performance-monitoring event selector +RW(0x33E, mhpmevent30) // Machine performance-monitoring event selector +RW(0x33F, mhpmevent31) // Machine performance-monitoring event selector + +// Debug/Trace Registers (shared with Debug Mode) +RW(0x7A0, tselect) // Debug/Trace trigger register select +RW(0x7A1, tdata1) // First Debug/Trace trigger data register +RW(0x7A2, tdata2) // Second Debug/Trace trigger data register +RW(0x7A3, tdata3) // Third Debug/Trace trigger data register + +// Debug Mode Registers +RW(0x7B0, dcsr) // Debug control and status register +RW(0x7B1, dpc) // Debug PC +RW(0x7B2, dscratch) // Debug scratch register + +// Hypervisor Trap Setup +RW(0x600, hstatus) // Hypervisor status register +RW(0x602, hedeleg) // Hypervisor exception delegation register +RW(0x603, hideleg) // Hypervisor interrupt delegation register +RW(0x604, hie) // Hypervisor interrupt-enable register +RW(0x606, hcounteren) // Hypervisor counter enable +RW(0x607, hgeie) // Hypervisor guest external interrupt-enable register + +// Hypervisor Trap Handling +RW(0x643, htval) // Hypervisor bad guest physical address +RW(0x644, hip) // Hypervisor interrupt pending +RW(0x645, hvip) // Hypervisor virtual interrupt pending +RW(0x64a, htinst) // Hypervisor trap instruction (transformed) +RW(0xe12, hgeip) // Hypervisor guest external interrupt pending + +// Hypervisor Protection and Translation +RO(0x680, hgatp) // Hypervisor guest address translation and protection + +// Debug/Trace Registers +RW(0x6a8, hcontext) // Hypervisor-mode context register + +// Hypervisor Counter/Timer Virtualization Registers +RW(0x605, htimedelta) // Delta for VS/VU-mode timer +RW32(0x615, htimedeltah) // Upper 32 bits of {\tt htimedelta}, RV32 only + +// Virtual Supervisor Registers +RW(0x200, vsstatus) // Virtual supervisor status register +RW(0x204, vsie) // Virtual supervisor interrupt-enable register +RW(0x205, vstvec) // Virtual supervisor trap handler base address +RW(0x240, vsscratch) // Virtual supervisor scratch register +RW(0x241, vsepc) // Virtual supervisor exception program counter +RW(0x242, vscause) // Virtual supervisor trap cause +RW(0x243, vstval) // Virtual supervisor bad address or instruction +RW(0x244, vsip) // Virtual supervisor interrupt pending +RW(0x280, vsatp) // Virtual supervisor address translation and protection diff --git a/vendor/riscv/asm.h b/vendor/riscv/asm.h new file mode 100644 index 00000000..2b675e7c --- /dev/null +++ b/vendor/riscv/asm.h @@ -0,0 +1,48 @@ +#ifndef __ASM_H +#define __ASM_H + +#define REG_READ(name, offset) \ +.section .text.__read_ ## name; \ +.global __read_ ## name; \ +__read_ ## name: \ + csrrs a0, offset, x0; \ + ret + +#define REG_WRITE(name, offset) \ +.section .text.__write_ ## name; \ +.global __write_ ## name; \ +__write_ ## name: \ + csrrw x0, offset, a0; \ + ret + +#define REG_SET(name, offset) \ +.section .text.__set_ ## name; \ +.global __set_ ## name; \ +__set_ ## name: \ + csrrs x0, offset, a0; \ + ret + +#define REG_CLEAR(name, offset) \ +.section .text.__clear_ ## name; \ +.global __clear_ ## name; \ +__clear_ ## name: \ + csrrc x0, offset, a0; \ + ret + + +#define REG_READ_WRITE(name, offset) REG_READ(name, offset); REG_WRITE(name, offset) +#define REG_SET_CLEAR(name, offset) REG_SET(name, offset); REG_CLEAR(name, offset) + +#define RW(offset, name) REG_READ_WRITE(name, offset); REG_SET_CLEAR(name, offset) +#define RO(offset, name) REG_READ(name, offset) + +#if __riscv_xlen == 32 +#define RW32(offset, name) RW(offset, name) +#define RO32(offset, name) RO(offset, name) +#else +#define RW32(offset, name) +#define RO32(offset, name) +#endif + +#endif /* __ASM_H */ + diff --git a/vendor/riscv/assemble.ps1 b/vendor/riscv/assemble.ps1 new file mode 100644 index 00000000..0b8e8807 --- /dev/null +++ b/vendor/riscv/assemble.ps1 @@ -0,0 +1,20 @@ +New-Item -Force -Name bin -Type Directory + +# remove existing blobs because otherwise this will append object files to the old blobs +Remove-Item -Force bin/*.a + +$crate = "riscv" + +riscv64-unknown-elf-gcc -c -mabi=ilp32 -march=rv32i asm.S -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv32i-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -c -mabi=ilp32 -march=rv32ic asm.S -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv32ic-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -c -mabi=lp64 -march=rv64i asm.S -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv64i-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -c -mabi=lp64 -march=rv64ic asm.S -o bin/$crate.o +riscv64-unknown-elf-ar crs bin/riscv64ic-unknown-none-elf.a bin/$crate.o + +Remove-Item bin/$crate.o diff --git a/vendor/riscv/assemble.sh b/vendor/riscv/assemble.sh new file mode 100755 index 00000000..217131dc --- /dev/null +++ b/vendor/riscv/assemble.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euxo pipefail + +crate=riscv + +# remove existing blobs because otherwise this will append object files to the old blobs +rm -f bin/*.a + +riscv64-unknown-elf-gcc -c -mabi=ilp32 -march=rv32i asm.S -o bin/$crate.o +ar crs bin/riscv32i-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -c -mabi=ilp32 -march=rv32ic asm.S -o bin/$crate.o +ar crs bin/riscv32ic-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -c -mabi=lp64 -march=rv64i asm.S -o bin/$crate.o +ar crs bin/riscv64i-unknown-none-elf.a bin/$crate.o + +riscv64-unknown-elf-gcc -c -mabi=lp64 -march=rv64ic asm.S -o bin/$crate.o +ar crs bin/riscv64ic-unknown-none-elf.a bin/$crate.o + +rm bin/$crate.o diff --git a/vendor/riscv/bin/riscv32i-unknown-none-elf.a b/vendor/riscv/bin/riscv32i-unknown-none-elf.a new file mode 100644 index 00000000..883d6556 Binary files /dev/null and b/vendor/riscv/bin/riscv32i-unknown-none-elf.a differ diff --git a/vendor/riscv/bin/riscv32ic-unknown-none-elf.a b/vendor/riscv/bin/riscv32ic-unknown-none-elf.a new file mode 100644 index 00000000..be6352dc Binary files /dev/null and b/vendor/riscv/bin/riscv32ic-unknown-none-elf.a differ diff --git a/vendor/riscv/bin/riscv64i-unknown-none-elf.a b/vendor/riscv/bin/riscv64i-unknown-none-elf.a new file mode 100644 index 00000000..a4117f11 Binary files /dev/null and b/vendor/riscv/bin/riscv64i-unknown-none-elf.a differ diff --git a/vendor/riscv/bin/riscv64ic-unknown-none-elf.a b/vendor/riscv/bin/riscv64ic-unknown-none-elf.a new file mode 100644 index 00000000..3201ce3c Binary files /dev/null and b/vendor/riscv/bin/riscv64ic-unknown-none-elf.a differ diff --git a/vendor/riscv/build.rs b/vendor/riscv/build.rs new file mode 100644 index 00000000..d1c33289 --- /dev/null +++ b/vendor/riscv/build.rs @@ -0,0 +1,35 @@ +extern crate riscv_target; + +use riscv_target::Target; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + let target = env::var("TARGET").unwrap(); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let name = env::var("CARGO_PKG_NAME").unwrap(); + + if target.starts_with("riscv") && env::var_os("CARGO_FEATURE_INLINE_ASM").is_none() { + let mut target = Target::from_target_str(&target); + target.retain_extensions("ic"); + + let target = target.to_string(); + + fs::copy( + format!("bin/{}.a", target), + out_dir.join(format!("lib{}.a", name)), + ) + .unwrap(); + + println!("cargo:rustc-link-lib=static={}", name); + println!("cargo:rustc-link-search={}", out_dir.display()); + } + + if target.contains("riscv32") { + println!("cargo:rustc-cfg=riscv"); + println!("cargo:rustc-cfg=riscv32"); + } else if target.contains("riscv64") { + println!("cargo:rustc-cfg=riscv"); + println!("cargo:rustc-cfg=riscv64"); + } +} diff --git a/vendor/riscv/check-blobs.sh b/vendor/riscv/check-blobs.sh new file mode 100755 index 00000000..36d885e6 --- /dev/null +++ b/vendor/riscv/check-blobs.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# Checks that the blobs are up to date with the committed assembly files + +set -euxo pipefail + +for lib in $(ls bin/*.a); do + filename=$(basename $lib) + riscv64-unknown-elf-objdump -Cd $lib > bin/${filename%.a}.before +done + +./assemble.sh + +for lib in $(ls bin/*.a); do + filename=$(basename $lib) + riscv64-unknown-elf-objdump -Cd $lib > bin/${filename%.a}.after +done + +for cksum in $(ls bin/*.after); do + diff -u $cksum ${cksum%.after}.before +done diff --git a/vendor/riscv/ci/install.sh b/vendor/riscv/ci/install.sh new file mode 100755 index 00000000..2378704d --- /dev/null +++ b/vendor/riscv/ci/install.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +if [ -n "${TARGET:-}" ]; then + rustup target add $TARGET +fi + +if [ -n "${CHECK_BLOBS:-}" ]; then + if [ ! -d gcc/bin ]; then + mkdir -p gcc + curl -L https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.1.0-2018.12.0-x86_64-linux-ubuntu14.tar.gz | tar --strip-components=1 -C gcc -xz + fi +fi + +if [ -n "${RUSTFMT:-}" ]; then + rustup component add rustfmt +fi diff --git a/vendor/riscv/ci/script.sh b/vendor/riscv/ci/script.sh new file mode 100755 index 00000000..be42e3da --- /dev/null +++ b/vendor/riscv/ci/script.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +export PATH=$PATH:~/.cargo/bin + +if [ -n "${TARGET:-}" ]; then + cargo check --target $TARGET + + if [ $TRAVIS_RUST_VERSION = nightly ]; then + cargo check --target $TARGET --features inline-asm + fi +fi + +if [ -n "${CHECK_BLOBS:-}" ]; then + PATH="$PATH:$PWD/gcc/bin" + ./check-blobs.sh +fi + +if [ -n "${RUSTFMT:-}" ]; then + cargo fmt -- --check +fi diff --git a/vendor/riscv/descriptor/generate_hypervisor_csr.sh b/vendor/riscv/descriptor/generate_hypervisor_csr.sh new file mode 100755 index 00000000..d6e009aa --- /dev/null +++ b/vendor/riscv/descriptor/generate_hypervisor_csr.sh @@ -0,0 +1,8 @@ +#!/bin/bash +rustc generator.rs +rm -f ../src/register/hypervisorx64/mod.rs; +for i in *.txt; do + ./generator <$i > ../src/register/hypervisorx64/`basename -s .txt $i`.rs; + echo "pub mod $(basename -s .txt $i);" >> ../src/register/hypervisorx64/mod.rs; +done +rm -f generator \ No newline at end of file diff --git a/vendor/riscv/descriptor/generator.rs b/vendor/riscv/descriptor/generator.rs new file mode 100644 index 00000000..35d69b5e --- /dev/null +++ b/vendor/riscv/descriptor/generator.rs @@ -0,0 +1,355 @@ +use std::fmt::*; +macro_rules! CSR_ACCESSOR { + () => { + r#" +pub mod csr {{ + pub const CSR_ID: usize = {}; + #[inline] + pub unsafe fn csrrw(rs1: usize)->usize{{ + let mut rd; + llvm_asm!("csrrw $0, $2, $1" :"=r"(rd): "r"(rs1), "i"(CSR_ID) :: "volatile"); + rd + }} + #[inline] + pub unsafe fn csrrw_x0(rs1: usize){{ + llvm_asm!("csrrw x0, $1, $0" :: "r"(rs1), "i"(CSR_ID) :: "volatile"); + }} + #[inline] + pub unsafe fn csrrs(rs1: usize)->usize{{ + let mut rd; + llvm_asm!("csrrs $0, $2, $1" :"=r"(rd): "r"(rs1), "i"(CSR_ID) :: "volatile"); + rd + }} + #[inline] + pub unsafe fn csrrs_x0()->usize{{ + let mut rd; + llvm_asm!("csrrs $0, $1, x0" :"=r"(rd): "i"(CSR_ID) :: "volatile"); + rd + }} + #[inline] + pub unsafe fn csrrc(rs1: usize)->usize{{ + let mut rd; + llvm_asm!("csrrc $0, $2, $1" :"=r"(rd): "r"(rs1), "i"(CSR_ID) :: "volatile"); + rd + }} + #[inline] + pub unsafe fn csrrc_x0()->usize{{ + let mut rd; + llvm_asm!("csrrc $0, $1, x0" :"=r"(rd): "i"(CSR_ID) :: "volatile"); + rd + }} +}} +"# + }; +} + +macro_rules! as_str_polyfill { + ($x: expr, $r: expr) => {{ + let mut y = $x.clone(); + if let Some(x) = y.next() { + $r.split_at(x.as_ptr() as usize - $r.as_ptr() as usize).1 + } else { + "" + } + }}; +} +#[derive(Debug, Clone)] +struct EnumerationDescriptor<'a> { + enumerations: Vec<(&'a str, usize)>, +} +impl<'a> EnumerationDescriptor<'a> { + pub fn parse(enums: &'a str) -> Self { + let mut counter = 0; + let list = enums.split(";"); + let mut e = Vec::new(); + for tup in list { + let mut t = tup.split("="); + let n = t.next().unwrap(); + if let Some(new_id) = t.next() { + counter = new_id.parse().unwrap(); + } + e.push((n, counter)); + counter += 1; + } + EnumerationDescriptor { enumerations: e } + } + fn generate_enum(&self, name: &str) -> String { + let mut ret = String::new(); + write!( + &mut ret, + "#[derive(Copy, Clone, Debug)] +#[repr(usize)] +" + ) + .unwrap(); + write!(&mut ret, "pub enum {}{{\n", name).unwrap(); + let mut branches = String::new(); + for e in self.enumerations.iter() { + write!(&mut ret, " {} = {},\n", e.0, e.1).unwrap(); + write!(&mut branches, " {} => Self::{},\n", e.1, e.0).unwrap(); + } + + write!( + &mut ret, + "}} +impl {}{{ + fn from(x: usize)->Self{{ + match x{{ +{} _ => unreachable!() + }} + }} +}} +", + name, branches + ) + .unwrap(); + return ret; + } +} +#[derive(Debug, Clone)] +struct BitFieldDescriptor<'a> { + name: &'a str, + description: &'a str, + lo: usize, + hi: usize, + ed: Option<(&'a str, EnumerationDescriptor<'a>)>, +} + +impl<'a> BitFieldDescriptor<'a> { + pub fn parse(desc: &'a str) -> Self { + let mut parts = desc.split(","); + let name = parts.next().unwrap(); + let hi = parts.next().unwrap().parse::().unwrap(); + let lo = parts.next().unwrap().parse::().unwrap(); + let (lo, hi) = if lo < hi { (lo, hi) } else { (hi, lo) }; + let use_enum = parts.next().unwrap(); + let ed = if use_enum != "number" { + let opts = parts.next().unwrap(); + Some((use_enum, EnumerationDescriptor::parse(opts))) + } else { + None + }; + let description = as_str_polyfill!(parts, desc); + BitFieldDescriptor { + name, + lo, + hi, + description, + ed, + } + } + pub fn generate_enum(&self) -> Option { + if let Some((n, e)) = &self.ed { + Some(e.generate_enum(n)) + } else { + None + } + } + pub fn flag_type(&self) -> &str { + if let Some((n, _)) = self.ed { + n + } else { + if self.lo == self.hi { + "bool" + } else { + "usize" + } + } + } + fn mask(&self) -> String { + format!("{}", (1usize << (self.hi - self.lo + 1)) - 1) + } + fn getter(&self) -> String { + if self.lo == self.hi { + return format!("self.bits.get_bit({})", self.lo); + } else if self.flag_type() != "usize" { + return format!( + "{}::from(self.bits.get_bits({}..{}))", + self.flag_type(), + self.lo, + self.hi + 1 + ); + } else { + return format!("self.bits.get_bits({}..{})", self.lo, self.hi + 1); + } + } + fn setter(&self) -> String { + if self.lo == self.hi { + return format!("self.bits.set_bit({}, val);", self.lo); + } else if self.flag_type() != "usize" { + return format!( + "self.bits.set_bits({}..{}, val as usize);", + self.lo, + self.hi + 1 + ); + } else { + return format!("self.bits.set_bits({}..{}, val);", self.lo, self.hi + 1); + } + } + fn generate_read_write(&self) -> String { + format!( + " /// {} + #[inline] + pub fn {}(&self)->{}{{ + {} + }} + #[inline] + pub fn set_{}(&mut self, val: {}){{ + {} + }}\n", + self.description, + self.name, + self.flag_type(), + self.getter(), + self.name, + self.flag_type(), + self.setter() + ) + } + + fn generate_bit_set(&self) -> String { + format!( + " pub fn set_{}()->bool{{ + unsafe {{csr::csrrc({}) & {} !=0}} + }} + pub fn clear_{}()->bool{{ + unsafe {{csr::csrrs({}) & {} !=0 }} + }}\n", + self.name, + 1usize << self.lo, + 1usize << self.lo, + self.name, + 1usize << self.lo, + 1usize << self.lo + ) + } + fn generate_bitops(&self) -> String { + format!( + " set_clear_csr!( + ///{} + , set_{}, clear_{}, 1 << {});\n", + self.description, self.name, self.name, self.lo + ) + } +} + +#[derive(Debug, Clone)] +struct CSRDescriptor<'a> { + name: &'a str, + id: usize, + description: &'a str, + bfs: Vec>, +} + +impl<'a> CSRDescriptor<'a> { + fn canonical_name(&self) -> String { + self.name.to_lowercase() + } + pub fn parse(d: &'a str) -> Self { + let mut parts = d.split("\n"); + let name = parts.next().unwrap(); + let id = parts.next().unwrap().parse::().unwrap(); + let mut bfs = Vec::new(); + while let Some(x) = parts.next() { + if x == "end" { + break; + } else { + bfs.push(BitFieldDescriptor::parse(x)); + } + } + CSRDescriptor { + name, + id, + description: as_str_polyfill!(parts, d), + bfs, + } + } + pub fn generate(&self) -> String { + let mut trait_impls = String::new(); + let mut bit_sets = String::new(); + let mut enums = String::new(); + for bf in self.bfs.iter() { + if bf.lo == bf.hi { + write!(&mut bit_sets, "{}", bf.generate_bitops()).unwrap(); + //write!(&mut trait_impls, "{}",bf.generate_bit_set()).unwrap(); + } + write!(&mut trait_impls, "{}", bf.generate_read_write()).unwrap(); + if let Some(x) = bf.generate_enum() { + write!(&mut enums, "{}", x).unwrap(); + } + } + if &trait_impls == "" && &bit_sets == "" { + format!( + " +//! {} +read_csr_as_usize!({}, __read_{}); +write_csr_as_usize!({}, __write_{}); +", + self.description, + self.id, + self.canonical_name(), + self.id, + self.canonical_name() + ) + } else { + format!( + " +//! {} + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct {}{{\n bits: usize,\n}} +impl {}{{ + #[inline] + pub fn bits(&self) -> usize{{ + return self.bits; + }} + #[inline] + pub fn from_bits(x: usize) -> Self{{ + return {}{{bits: x}}; + }} + #[inline] + pub unsafe fn write(&self){{ + _write(self.bits); + }} +{} +}} +read_csr_as!({}, {}, __read_{}); +write_csr!({}, __write_{}); +set!({}, __set_{}); +clear!({}, __clear_{}); +// bit ops +{} +// enums +{} + +", + self.description, + self.name, + self.name, + self.name, + trait_impls, + self.name, + self.id, + self.canonical_name(), + self.id, + self.canonical_name(), + self.id, + self.canonical_name(), + self.id, + self.canonical_name(), + bit_sets, + enums, + ) + } + } +} + +fn main() { + use std::io::Read; + let mut buffer = String::new(); + std::io::stdin().read_to_string(&mut buffer).unwrap(); + let csr = CSRDescriptor::parse(&buffer); + println!("{}", csr.generate()); +} diff --git a/vendor/riscv/descriptor/hcounteren.txt b/vendor/riscv/descriptor/hcounteren.txt new file mode 100644 index 00000000..2df37ba4 --- /dev/null +++ b/vendor/riscv/descriptor/hcounteren.txt @@ -0,0 +1,36 @@ +Hcounteren +3602 +cy,0,0,number, +tm,1,1,number, +ir,2,2,number, +hpm3,3,3,number, +hpm4,4,4,number, +hpm5,5,5,number, +hpm6,6,6,number, +hpm7,7,7,number, +hpm8,8,8,number, +hpm9,9,9,number, +hpm10,10,10,number, +hpm11,11,11,number, +hpm12,12,12,number, +hpm13,13,13,number, +hpm14,14,14,number, +hpm15,15,15,number, +hpm16,16,16,number, +hpm17,17,17,number, +hpm18,18,18,number, +hpm19,19,19,number, +hpm20,20,20,number, +hpm21,21,21,number, +hpm22,22,22,number, +hpm23,23,23,number, +hpm24,24,24,number, +hpm25,25,25,number, +hpm26,26,26,number, +hpm27,27,27,number, +hpm28,28,28,number, +hpm29,29,29,number, +hpm30,30,30,number, +hpm31,31,31,number, +end +Hypervisor Guest External Interrupt Pending Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hedeleg.txt b/vendor/riscv/descriptor/hedeleg.txt new file mode 100644 index 00000000..988f5f9c --- /dev/null +++ b/vendor/riscv/descriptor/hedeleg.txt @@ -0,0 +1,16 @@ +Hedeleg +1538 +ex0,0,0,number,Instruction address misaligned +ex1,1,1,number,Instruction access fault +ex2,2,2,number,Illegal instruction +ex3,3,3,number,Breakpoint +ex4,4,4,number,Load address misaligned +ex5,5,5,number,Load access fault +ex6,6,6,number,Store/AMO address misaligned +ex7,7,7,number,Store/AMO access fault +ex8,8,8,number,Environment call from U-mode or VU-mode +ex12,12,12,number,Instruction page fault +ex13,13,13,number,Load page fault +ex15,15,15,number,Store/AMO page fault +end +Hypervisor Exception Delegation Register. diff --git a/vendor/riscv/descriptor/hgatp.txt b/vendor/riscv/descriptor/hgatp.txt new file mode 100644 index 00000000..3763903d --- /dev/null +++ b/vendor/riscv/descriptor/hgatp.txt @@ -0,0 +1,7 @@ +Hgatp +1664 +mode,63,60,HgatpValues,Bare=0;Sv39x4=8;Sv48x4=9,Guest address translation mode. +vmid,57,44,number,Virtual machine ID. +ppn,43,0,number,Physical Page Number for root page table. +end +Hypervisor Guest Address Translation and Protection Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hgeie.txt b/vendor/riscv/descriptor/hgeie.txt new file mode 100644 index 00000000..c5af3c04 --- /dev/null +++ b/vendor/riscv/descriptor/hgeie.txt @@ -0,0 +1,4 @@ +Hgeie +1543 +end +Hypervisor Guest External Interrupt Enable Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hgeip.txt b/vendor/riscv/descriptor/hgeip.txt new file mode 100644 index 00000000..3b354d28 --- /dev/null +++ b/vendor/riscv/descriptor/hgeip.txt @@ -0,0 +1,4 @@ +Hgeip +3602 +end +Hypervisor Guest External Interrupt Pending Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hideleg.txt b/vendor/riscv/descriptor/hideleg.txt new file mode 100644 index 00000000..ea8f5ea5 --- /dev/null +++ b/vendor/riscv/descriptor/hideleg.txt @@ -0,0 +1,7 @@ +Hideleg +1539 +sip,2,2,number,Software Interrupt +tip,6,6,number,Timer Interrupt +eip,10,10,number,External Interrupt +end +Hypervisor Interrupt Delegation Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hie.txt b/vendor/riscv/descriptor/hie.txt new file mode 100644 index 00000000..f67ffc6a --- /dev/null +++ b/vendor/riscv/descriptor/hie.txt @@ -0,0 +1,8 @@ +Hie +1540 +vssie,2,2,number,Software Interrupt +vstie,6,6,number,Timer Interrupt +vseie,10,10,number,External Interrupt +sgeie,12,12,number,Guest External Interrupt +end +Hypervisor Interrupt Enable Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hip.txt b/vendor/riscv/descriptor/hip.txt new file mode 100644 index 00000000..e067ef14 --- /dev/null +++ b/vendor/riscv/descriptor/hip.txt @@ -0,0 +1,8 @@ +Hip +1604 +vssip,2,2,number,Software Interrupt +vstip,6,6,number,Timer Interrupt +vseip,10,10,number,External Interrupt +sgeip,12,12,number,Guest External Interrupt +end +Hypervisor Interrupt Pending Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hstatus.txt b/vendor/riscv/descriptor/hstatus.txt new file mode 100644 index 00000000..11ee9855 --- /dev/null +++ b/vendor/riscv/descriptor/hstatus.txt @@ -0,0 +1,14 @@ +Hstatus +1536 +vsxl,33,32,VsxlValues,Vsxl32=1;Vsxl64;Vsxl128,Effective XLEN for VM. +vtsr,22,22,number,TSR for VM. +vtw,21,21,number,TW for VM. +vtvm,20,20,number,TVM for VM. +vgein,17,12,number,Virtual Guest External Interrupt Number. +hu,9,9,number,Hypervisor User mode. +spvp,8,8,number,Supervisor Previous Virtual Privilege. +spv,7,7,number,Supervisor Previous Virtualization mode. +gva,6,6,number,Guest Virtual Address. +vsbe,5,5,number,VS access endianness. +end +HStatus Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/htimedelta.txt b/vendor/riscv/descriptor/htimedelta.txt new file mode 100644 index 00000000..d850625f --- /dev/null +++ b/vendor/riscv/descriptor/htimedelta.txt @@ -0,0 +1,5 @@ +Htimedelta +1541 +end +Hypervisor Time Delta Register. +read_composite_csr!(super::htimedeltah::read(), read()); \ No newline at end of file diff --git a/vendor/riscv/descriptor/htimedeltah.txt b/vendor/riscv/descriptor/htimedeltah.txt new file mode 100644 index 00000000..1b6147f6 --- /dev/null +++ b/vendor/riscv/descriptor/htimedeltah.txt @@ -0,0 +1,4 @@ +Htimedeltah +1557 +end +Hypervisor Time Delta Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/htinst.txt b/vendor/riscv/descriptor/htinst.txt new file mode 100644 index 00000000..b3efc33b --- /dev/null +++ b/vendor/riscv/descriptor/htinst.txt @@ -0,0 +1,4 @@ +Htinst +1610 +end +Hypervisor Trap Instruction Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/htval.txt b/vendor/riscv/descriptor/htval.txt new file mode 100644 index 00000000..eec176f0 --- /dev/null +++ b/vendor/riscv/descriptor/htval.txt @@ -0,0 +1,4 @@ +Htval +1603 +end +Hypervisor Trap Value Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/hvip.txt b/vendor/riscv/descriptor/hvip.txt new file mode 100644 index 00000000..5f8dfbea --- /dev/null +++ b/vendor/riscv/descriptor/hvip.txt @@ -0,0 +1,7 @@ +Hvip +1605 +vssip,2,2,number,Software Interrupt +vstip,6,6,number,Timer Interrupt +vseip,10,10,number,External Interrupt +end +Hypervisor Virtual Interrupt Pending Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vsatp.txt b/vendor/riscv/descriptor/vsatp.txt new file mode 100644 index 00000000..789ecfb8 --- /dev/null +++ b/vendor/riscv/descriptor/vsatp.txt @@ -0,0 +1,7 @@ +Vsatp +640 +mode,63,60,HgatpValues,Bare=0;Sv39x4=8;Sv48x4=9,Guest address translation mode. +asid,59,44,number,ASID. +ppn,43,0,number,Physical Page Number for root page table. +end +Virtual Supervisor Guest Address Translation and Protection Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vscause.txt b/vendor/riscv/descriptor/vscause.txt new file mode 100644 index 00000000..fabf4b4c --- /dev/null +++ b/vendor/riscv/descriptor/vscause.txt @@ -0,0 +1,6 @@ +Vscause +578 +interrupt,63,63,number,Is cause interrupt. +code,62,0,number,Exception code +end +Virtual Supervisor Cause Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vsepc.txt b/vendor/riscv/descriptor/vsepc.txt new file mode 100644 index 00000000..c1db586c --- /dev/null +++ b/vendor/riscv/descriptor/vsepc.txt @@ -0,0 +1,4 @@ +Vsepc +577 +end +Virtual Supervisor Exception Program Counter. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vsie.txt b/vendor/riscv/descriptor/vsie.txt new file mode 100644 index 00000000..b3dfaae2 --- /dev/null +++ b/vendor/riscv/descriptor/vsie.txt @@ -0,0 +1,7 @@ +Vsie +516 +ssie,1,1,number,Software Interrupt +stie,5,5,number,Timer Interrupt +seie,9,9,number,External Interrupt +end +Virtual Supevisor Interrupt Enable Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vsip.txt b/vendor/riscv/descriptor/vsip.txt new file mode 100644 index 00000000..14d7245f --- /dev/null +++ b/vendor/riscv/descriptor/vsip.txt @@ -0,0 +1,7 @@ +Vsip +580 +ssip,1,1,number,Software Interrupt +stip,5,5,number,Timer Interrupt +seip,9,9,number,External Interrupt +end +Virtual Supevisor Interrupt Pending Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vsscratch.txt b/vendor/riscv/descriptor/vsscratch.txt new file mode 100644 index 00000000..9f1f5aec --- /dev/null +++ b/vendor/riscv/descriptor/vsscratch.txt @@ -0,0 +1,4 @@ +Vsscratch +576 +end +Virtual Supervisor Scratch Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vsstatus.txt b/vendor/riscv/descriptor/vsstatus.txt new file mode 100644 index 00000000..02577f15 --- /dev/null +++ b/vendor/riscv/descriptor/vsstatus.txt @@ -0,0 +1,14 @@ +Vsstatus +512 +sd,63,60,number, +uxl,33,32,UxlValues,Uxl32=1;Uxl64;Uxl128,Effective User XLEN. +mxr,19,19,number, +sum,18,18,number, +xs,16,15,number, +fs,14,13,number, +spp,8,8,number, +ube,6,6,number, +spie,5,5,number, +sie,1,1,number, +end +Hypervisor Guest External Interrupt Pending Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vstval.txt b/vendor/riscv/descriptor/vstval.txt new file mode 100644 index 00000000..c3a9e461 --- /dev/null +++ b/vendor/riscv/descriptor/vstval.txt @@ -0,0 +1,4 @@ +Vstval +579 +end +Virtual Supervisor Trap Value Register. \ No newline at end of file diff --git a/vendor/riscv/descriptor/vstvec.txt b/vendor/riscv/descriptor/vstvec.txt new file mode 100644 index 00000000..c2c4a578 --- /dev/null +++ b/vendor/riscv/descriptor/vstvec.txt @@ -0,0 +1,6 @@ +Vstvec +517 +base,63,2,number, +mode,1,0,number, +end +Virtual Supervisor Trap Vector Base Address Register. \ No newline at end of file diff --git a/vendor/riscv/src/addr/gpax4.rs b/vendor/riscv/src/addr/gpax4.rs new file mode 100644 index 00000000..c7bc7344 --- /dev/null +++ b/vendor/riscv/src/addr/gpax4.rs @@ -0,0 +1,211 @@ +use super::*; +use bit_field::BitField; +use core::convert::TryInto; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct GPAddrSv32X4(u64); + +impl Address for GPAddrSv32X4 { + fn new(addr: usize) -> Self { + Self::new_u64(addr as u64) + } + fn as_usize(&self) -> usize { + self.0 as usize + } + fn page_number(&self) -> usize { + self.0.get_bits(12..34) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + GPAddrSv32X4((self.0 >> 12) << 12) + } +} + +impl VirtualAddress for GPAddrSv32X4 { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T { + &mut *(self.0 as *mut T) + } +} + +impl AddressL2 for GPAddrSv32X4 { + fn p2_index(&self) -> usize { + self.0.get_bits(22..34) as usize + } + fn p1_index(&self) -> usize { + self.0.get_bits(12..22) as usize + } + fn from_page_table_indices(p2_index: usize, p1_index: usize, offset: usize) -> Self { + let p2_index = p2_index as u64; + let p1_index = p1_index as u64; + let offset = offset as u64; + assert!(p2_index.get_bits(12..) == 0, "p2_index exceeding 12 bits"); + assert!(p1_index.get_bits(10..) == 0, "p1_index exceeding 10 bits"); + assert!(offset.get_bits(12..) == 0, "offset exceeding 12 bits"); + GPAddrSv32X4::new_u64((p2_index << 22) | (p1_index << 12) | offset) + } +} + +impl AddressX64 for GPAddrSv32X4 { + fn new_u64(addr: u64) -> Self { + assert!( + addr.get_bits(34..64) == 0, + "Sv32x4 does not allow pa 34..64!=0" + ); + GPAddrSv32X4(addr) + } + fn as_u64(&self) -> u64 { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct GPAddrSv39X4(u64); + +impl Address for GPAddrSv39X4 { + fn new(addr: usize) -> Self { + GPAddrSv39X4(addr.try_into().unwrap()) + } + fn as_usize(&self) -> usize { + self.0 as usize + } + fn page_number(&self) -> usize { + self.0.get_bits(12..41) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + GPAddrSv39X4((self.0 >> 12) << 12) + } +} + +impl VirtualAddress for GPAddrSv39X4 { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T { + &mut *(self.0 as *mut T) + } +} + +impl AddressL3 for GPAddrSv39X4 { + fn p3_index(&self) -> usize { + self.0.get_bits(30..41) as usize + } + fn p2_index(&self) -> usize { + self.0.get_bits(21..30) as usize + } + fn p1_index(&self) -> usize { + self.0.get_bits(12..21) as usize + } + fn from_page_table_indices( + p3_index: usize, + p2_index: usize, + p1_index: usize, + offset: usize, + ) -> Self { + let p3_index = p3_index as u64; + let p2_index = p2_index as u64; + let p1_index = p1_index as u64; + let offset = offset as u64; + assert!(p3_index.get_bits(11..) == 0, "p3_index exceeding 11 bits"); + assert!(p2_index.get_bits(9..) == 0, "p2_index exceeding 9 bits"); + assert!(p1_index.get_bits(9..) == 0, "p1_index exceeding 9 bits"); + assert!(offset.get_bits(12..) == 0, "offset exceeding 12 bits"); + GPAddrSv39X4::new_u64( + (p3_index << 12 << 9 << 9) | (p2_index << 12 << 9) | (p1_index << 12) | offset, + ) + } +} + +impl AddressX64 for GPAddrSv39X4 { + fn new_u64(addr: u64) -> Self { + assert!( + addr.get_bits(41..64) == 0, + "Sv39x4 does not allow pa 41..64!=0" + ); + GPAddrSv39X4(addr) + } + fn as_u64(&self) -> u64 { + self.0 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct GPAddrSv48X4(u64); + +impl Address for GPAddrSv48X4 { + fn new(addr: usize) -> Self { + GPAddrSv48X4(addr.try_into().unwrap()) + } + fn as_usize(&self) -> usize { + self.0 as usize + } + fn page_number(&self) -> usize { + self.0.get_bits(12..50) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + GPAddrSv48X4((self.0 >> 12) << 12) + } +} + +impl VirtualAddress for GPAddrSv48X4 { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T { + &mut *(self.0 as *mut T) + } +} + +impl AddressL4 for GPAddrSv48X4 { + fn p4_index(&self) -> usize { + self.0.get_bits(39..50) as usize + } + fn p3_index(&self) -> usize { + self.0.get_bits(30..39) as usize + } + fn p2_index(&self) -> usize { + self.0.get_bits(21..30) as usize + } + fn p1_index(&self) -> usize { + self.0.get_bits(12..21) as usize + } + fn from_page_table_indices( + p4_index: usize, + p3_index: usize, + p2_index: usize, + p1_index: usize, + offset: usize, + ) -> Self { + let p4_index = p4_index as u64; + let p3_index = p3_index as u64; + let p2_index = p2_index as u64; + let p1_index = p1_index as u64; + let offset = offset as u64; + assert!(p4_index.get_bits(11..) == 0, "p4_index exceeding 11 bits"); + assert!(p3_index.get_bits(9..) == 0, "p3_index exceeding 9 bits"); + assert!(p2_index.get_bits(9..) == 0, "p2_index exceeding 9 bits"); + assert!(p1_index.get_bits(9..) == 0, "p1_index exceeding 9 bits"); + assert!(offset.get_bits(12..) == 0, "offset exceeding 12 bits"); + GPAddrSv48X4::new_u64( + (p4_index << 12 << 9 << 9 << 9) + | (p3_index << 12 << 9 << 9) + | (p2_index << 12 << 9) + | (p1_index << 12) + | offset, + ) + } +} + +impl AddressX64 for GPAddrSv48X4 { + fn new_u64(addr: u64) -> Self { + assert!( + addr.get_bits(50..64) == 0, + "Sv48x4 does not allow pa 50..64!=0" + ); + GPAddrSv48X4(addr) + } + fn as_u64(&self) -> u64 { + self.0 + } +} diff --git a/vendor/riscv/src/addr/mod.rs b/vendor/riscv/src/addr/mod.rs new file mode 100644 index 00000000..9b33ce8a --- /dev/null +++ b/vendor/riscv/src/addr/mod.rs @@ -0,0 +1,98 @@ +pub trait Address: core::fmt::Debug + Copy + Clone + PartialEq + Eq + PartialOrd + Ord { + fn new(addr: usize) -> Self; + fn page_number(&self) -> usize; + fn page_offset(&self) -> usize; + fn to_4k_aligned(&self) -> Self; + fn as_usize(&self) -> usize; +} + +pub trait VirtualAddress: Address { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T; +} + +pub trait AddressX32: Address { + fn new_u32(addr: u32) -> Self; + fn as_u32(&self) -> u32; +} +pub trait AddressX64: Address { + fn new_u64(addr: u64) -> Self; + fn as_u64(&self) -> u64; +} + +pub trait PhysicalAddress: AddressX64 {} + +pub trait AddressL3: Address { + fn p3_index(&self) -> usize; + fn p2_index(&self) -> usize; + fn p1_index(&self) -> usize; + fn from_page_table_indices( + p3_index: usize, + p2_index: usize, + p1_index: usize, + offset: usize, + ) -> Self; +} + +pub trait AddressL4: Address { + fn p4_index(&self) -> usize; + fn p3_index(&self) -> usize; + fn p2_index(&self) -> usize; + fn p1_index(&self) -> usize; + fn from_page_table_indices( + p4_index: usize, + p3_index: usize, + p2_index: usize, + p1_index: usize, + offset: usize, + ) -> Self; +} + +pub trait AddressL2: Address { + fn p2_index(&self) -> usize; + fn p1_index(&self) -> usize; + fn from_page_table_indices(p2_index: usize, p1_index: usize, offset: usize) -> Self; +} +pub mod gpax4; +pub mod page; +pub mod sv32; +pub mod sv39; +pub mod sv48; + +pub use self::gpax4::*; +pub use self::page::*; +pub use self::sv32::*; +pub use self::sv39::*; +pub use self::sv48::*; + +#[macro_export] +macro_rules! use_sv32 { + () => { + pub type VirtAddr = VirtAddrSv32; + pub type PhysAddr = PhysAddrSv32; + pub type Page = PageWith; + pub type Frame = FrameWith; + }; +} +#[macro_export] +macro_rules! use_sv39 { + () => { + pub type VirtAddr = VirtAddrSv39; + pub type PhysAddr = PhysAddrSv39; + pub type Page = PageWith; + pub type Frame = FrameWith; + }; +} +#[macro_export] +macro_rules! use_sv48 { + () => { + pub type VirtAddr = VirtAddrSv48; + pub type PhysAddr = PhysAddrSv48; + pub type Page = PageWith; + pub type Frame = FrameWith; + }; +} +#[cfg(target_arch = "riscv64")] +use_sv48!(); + +#[cfg(target_arch = "riscv32")] +use_sv32!(); diff --git a/vendor/riscv/src/addr/page.rs b/vendor/riscv/src/addr/page.rs new file mode 100644 index 00000000..2c36f9b5 --- /dev/null +++ b/vendor/riscv/src/addr/page.rs @@ -0,0 +1,174 @@ +pub use super::*; +pub use bit_field::BitField; + +pub trait PageWithL4 { + fn p4_index(&self) -> usize; + fn p3_index(&self) -> usize; + fn p2_index(&self) -> usize; + fn p1_index(&self) -> usize; + fn from_page_table_indices( + p4_index: usize, + p3_index: usize, + p2_index: usize, + p1_index: usize, + ) -> Self; +} + +pub trait PageWithL3 { + fn p3_index(&self) -> usize; + fn p2_index(&self) -> usize; + fn p1_index(&self) -> usize; + fn from_page_table_indices(p3_index: usize, p2_index: usize, p1_index: usize) -> Self; +} + +pub trait PageWithL2 { + fn p2_index(&self) -> usize; + fn p1_index(&self) -> usize; + fn from_page_table_indices(p2_index: usize, p1_index: usize) -> Self; +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PageWith(T); + +impl PageWithL4 for PageWith { + fn p4_index(&self) -> usize { + self.0.p4_index() + } + fn p3_index(&self) -> usize { + self.0.p3_index() + } + fn p2_index(&self) -> usize { + self.0.p2_index() + } + fn p1_index(&self) -> usize { + self.0.p1_index() + } + fn from_page_table_indices( + p4_index: usize, + p3_index: usize, + p2_index: usize, + p1_index: usize, + ) -> Self { + PageWith::of_addr(T::from_page_table_indices( + p4_index, p3_index, p2_index, p1_index, 0, + )) + } +} +impl PageWithL3 for PageWith { + fn p3_index(&self) -> usize { + self.0.p3_index() + } + fn p2_index(&self) -> usize { + self.0.p2_index() + } + fn p1_index(&self) -> usize { + self.0.p1_index() + } + fn from_page_table_indices(p3_index: usize, p2_index: usize, p1_index: usize) -> Self { + PageWith::of_addr(T::from_page_table_indices(p3_index, p2_index, p1_index, 0)) + } +} +impl PageWithL2 for PageWith { + fn p2_index(&self) -> usize { + self.0.p2_index() + } + fn p1_index(&self) -> usize { + self.0.p1_index() + } + fn from_page_table_indices(p2_index: usize, p1_index: usize) -> Self { + PageWith::of_addr(T::from_page_table_indices(p2_index, p1_index, 0)) + } +} +impl PageWith { + pub fn of_addr(addr: T) -> Self { + PageWith(addr.to_4k_aligned()) + } + + pub fn of_vpn(vpn: usize) -> Self { + PageWith(T::new(vpn << 12)) + } + + pub fn start_address(&self) -> T { + self.0.clone() + } + + pub fn number(&self) -> usize { + self.0.page_number() + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct FrameWith(T); + +impl PageWithL4 for FrameWith { + fn p4_index(&self) -> usize { + self.0.p4_index() + } + fn p3_index(&self) -> usize { + self.0.p3_index() + } + fn p2_index(&self) -> usize { + self.0.p2_index() + } + fn p1_index(&self) -> usize { + self.0.p1_index() + } + fn from_page_table_indices( + p4_index: usize, + p3_index: usize, + p2_index: usize, + p1_index: usize, + ) -> Self { + FrameWith::of_addr(T::from_page_table_indices( + p4_index, p3_index, p2_index, p1_index, 0, + )) + } +} +impl PageWithL3 for FrameWith { + fn p3_index(&self) -> usize { + self.0.p3_index() + } + fn p2_index(&self) -> usize { + self.0.p2_index() + } + fn p1_index(&self) -> usize { + self.0.p1_index() + } + fn from_page_table_indices(p3_index: usize, p2_index: usize, p1_index: usize) -> Self { + FrameWith::of_addr(T::from_page_table_indices(p3_index, p2_index, p1_index, 0)) + } +} +impl PageWithL2 for FrameWith { + fn p2_index(&self) -> usize { + self.0.p2_index() + } + fn p1_index(&self) -> usize { + self.0.p1_index() + } + fn from_page_table_indices(p2_index: usize, p1_index: usize) -> Self { + FrameWith::of_addr(T::from_page_table_indices(p2_index, p1_index, 0)) + } +} + +impl FrameWith { + pub fn of_addr(addr: T) -> Self { + FrameWith(addr.to_4k_aligned()) + } + + #[inline(always)] + pub fn of_ppn(ppn: usize) -> Self { + FrameWith(T::new_u64((ppn as u64) << 12)) + } + + pub fn start_address(&self) -> T { + self.0.clone() + } + + pub fn number(&self) -> usize { + self.0.page_number() + } + + pub unsafe fn as_kernel_mut<'a, 'b, U>(&'a self, linear_offset: u64) -> &'b mut U { + &mut *(((self.0).as_u64() + linear_offset) as *mut U) + } +} diff --git a/vendor/riscv/src/addr/sv32.rs b/vendor/riscv/src/addr/sv32.rs new file mode 100644 index 00000000..193b7f6d --- /dev/null +++ b/vendor/riscv/src/addr/sv32.rs @@ -0,0 +1,91 @@ +use super::*; +use bit_field::BitField; +use core::convert::TryInto; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VirtAddrSv32(u32); +impl Address for VirtAddrSv32 { + fn new(addr: usize) -> Self { + VirtAddrSv32(addr.try_into().unwrap()) + } + fn as_usize(&self) -> usize { + self.0 as usize + } + fn page_number(&self) -> usize { + self.0.get_bits(12..32) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + VirtAddrSv32((self.0 >> 12) << 12) + } +} +impl VirtualAddress for VirtAddrSv32 { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T { + &mut *(self.0 as *mut T) + } +} + +impl AddressL2 for VirtAddrSv32 { + fn p2_index(&self) -> usize { + self.0.get_bits(22..32) as usize + } + + fn p1_index(&self) -> usize { + self.0.get_bits(12..22) as usize + } + fn from_page_table_indices(p2_index: usize, p1_index: usize, offset: usize) -> Self { + assert!(p2_index.get_bits(10..) == 0, "p2_index exceeding 10 bits"); + assert!(p1_index.get_bits(10..) == 0, "p1_index exceeding 10 bits"); + assert!(offset.get_bits(12..) == 0, "offset exceeding 12 bits"); + VirtAddrSv32::new((p2_index << 22) | (p1_index << 12) | offset) + } +} + +impl AddressX32 for VirtAddrSv32 { + fn new_u32(addr: u32) -> Self { + VirtAddrSv32(addr) + } + fn as_u32(&self) -> u32 { + self.0 + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PhysAddrSv32(u64); +impl Address for PhysAddrSv32 { + fn new(addr: usize) -> Self { + Self::new_u64(addr as u64) + } + fn as_usize(&self) -> usize { + assert!( + self.0.get_bits(32..34) == 0, + "Downcasting an Sv32 pa >4GB (32..34!=0) will cause address loss." + ); + self.0 as usize + } + fn page_number(&self) -> usize { + self.0.get_bits(12..34) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + PhysAddrSv32((self.0 >> 12) << 12) + } +} + +impl AddressX64 for PhysAddrSv32 { + fn new_u64(addr: u64) -> Self { + assert!( + addr.get_bits(34..64) == 0, + "Sv32 does not allow pa 34..64!=0" + ); + PhysAddrSv32(addr) + } + fn as_u64(&self) -> u64 { + self.0 + } +} + +impl PhysicalAddress for PhysAddrSv32 {} diff --git a/vendor/riscv/src/addr/sv39.rs b/vendor/riscv/src/addr/sv39.rs new file mode 100644 index 00000000..b059192d --- /dev/null +++ b/vendor/riscv/src/addr/sv39.rs @@ -0,0 +1,115 @@ +use super::*; +use bit_field::BitField; +use core::convert::TryInto; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VirtAddrSv39(u64); + +impl VirtualAddress for VirtAddrSv39 { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T { + &mut *(self.0 as *mut T) + } +} +impl Address for VirtAddrSv39 { + fn new(addr: usize) -> Self { + Self::new_u64(addr as u64) + } + fn as_usize(&self) -> usize { + self.0.try_into().unwrap() + } + fn page_number(&self) -> usize { + self.0.get_bits(12..39).try_into().unwrap() + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + VirtAddrSv39((self.0 >> 12) << 12) + } +} + +impl AddressL3 for VirtAddrSv39 { + fn p3_index(&self) -> usize { + self.0.get_bits(30..39) as usize + } + + fn p2_index(&self) -> usize { + self.0.get_bits(21..30) as usize + } + fn p1_index(&self) -> usize { + self.0.get_bits(12..21) as usize + } + fn from_page_table_indices( + p3_index: usize, + p2_index: usize, + p1_index: usize, + offset: usize, + ) -> Self { + let p3_index = p3_index as u64; + let p2_index = p2_index as u64; + let p1_index = p1_index as u64; + let offset = offset as u64; + assert!(p3_index.get_bits(11..) == 0, "p3_index exceeding 11 bits"); + assert!(p2_index.get_bits(9..) == 0, "p2_index exceeding 9 bits"); + assert!(p1_index.get_bits(9..) == 0, "p1_index exceeding 9 bits"); + assert!(offset.get_bits(12..) == 0, "offset exceeding 12 bits"); + let mut addr = + (p3_index << 12 << 9 << 9) | (p2_index << 12 << 9) | (p1_index << 12) | offset; + if addr.get_bit(38) { + addr.set_bits(39..64, (1 << (64 - 39)) - 1); + } else { + addr.set_bits(39..64, 0x0000); + } + VirtAddrSv39::new_u64(addr) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PhysAddrSv39(u64); +impl Address for PhysAddrSv39 { + fn new(addr: usize) -> Self { + Self::new_u64(addr as u64) + } + fn as_usize(&self) -> usize { + self.0.try_into().unwrap() + } + fn page_number(&self) -> usize { + self.0.get_bits(12..56) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + PhysAddrSv39((self.0 >> 12) << 12) + } +} + +impl AddressX64 for VirtAddrSv39 { + fn new_u64(addr: u64) -> Self { + if addr.get_bit(38) { + assert!( + addr.get_bits(39..64) == (1 << (64 - 39)) - 1, + "va 39..64 is not sext" + ); + } else { + assert!(addr.get_bits(39..64) == 0x0000, "va 39..64 is not sext"); + } + VirtAddrSv39(addr as u64) + } + fn as_u64(&self) -> u64 { + self.0 + } +} +impl AddressX64 for PhysAddrSv39 { + fn new_u64(addr: u64) -> Self { + assert!( + addr.get_bits(56..64) == 0, + "Sv39 does not allow pa 56..64!=0" + ); + PhysAddrSv39(addr) + } + fn as_u64(&self) -> u64 { + self.0 + } +} + +impl PhysicalAddress for PhysAddrSv39 {} diff --git a/vendor/riscv/src/addr/sv48.rs b/vendor/riscv/src/addr/sv48.rs new file mode 100644 index 00000000..880bd3e9 --- /dev/null +++ b/vendor/riscv/src/addr/sv48.rs @@ -0,0 +1,125 @@ +use super::*; +use bit_field::BitField; +use core::convert::TryInto; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VirtAddrSv48(u64); + +impl VirtualAddress for VirtAddrSv48 { + unsafe fn as_mut<'a, 'b, T>(&'a self) -> &'b mut T { + &mut *(self.0 as *mut T) + } +} +impl Address for VirtAddrSv48 { + fn new(addr: usize) -> Self { + Self::new_u64(addr as u64) + } + fn as_usize(&self) -> usize { + self.0.try_into().unwrap() + } + fn page_number(&self) -> usize { + self.0.get_bits(12..48).try_into().unwrap() + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + VirtAddrSv48((self.0 >> 12) << 12) + } +} + +impl AddressL4 for VirtAddrSv48 { + fn p4_index(&self) -> usize { + self.0.get_bits(39..48) as usize + } + + fn p3_index(&self) -> usize { + self.0.get_bits(30..39) as usize + } + + fn p2_index(&self) -> usize { + self.0.get_bits(21..30) as usize + } + fn p1_index(&self) -> usize { + self.0.get_bits(12..21) as usize + } + fn from_page_table_indices( + p4_index: usize, + p3_index: usize, + p2_index: usize, + p1_index: usize, + offset: usize, + ) -> Self { + let p4_index = p4_index as u64; + let p3_index = p3_index as u64; + let p2_index = p2_index as u64; + let p1_index = p1_index as u64; + let offset = offset as u64; + assert!(p4_index.get_bits(9..) == 0, "p4_index exceeding 9 bits"); + assert!(p3_index.get_bits(9..) == 0, "p3_index exceeding 9 bits"); + assert!(p2_index.get_bits(9..) == 0, "p2_index exceeding 9 bits"); + assert!(p1_index.get_bits(9..) == 0, "p1_index exceeding 9 bits"); + assert!(offset.get_bits(12..) == 0, "offset exceeding 12 bits"); + let mut addr = (p4_index << 12 << 9 << 9 << 9) + | (p3_index << 12 << 9 << 9) + | (p2_index << 12 << 9) + | (p1_index << 12) + | offset; + if addr.get_bit(47) { + addr.set_bits(48..64, (1 << (64 - 48)) - 1); + } else { + addr.set_bits(48..64, 0x0000); + } + VirtAddrSv48::new_u64(addr) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PhysAddrSv48(u64); +impl Address for PhysAddrSv48 { + fn new(addr: usize) -> Self { + Self::new_u64(addr as u64) + } + fn as_usize(&self) -> usize { + self.0.try_into().unwrap() + } + fn page_number(&self) -> usize { + self.0.get_bits(12..56) as usize + } + fn page_offset(&self) -> usize { + self.0.get_bits(0..12) as usize + } + fn to_4k_aligned(&self) -> Self { + PhysAddrSv48((self.0 >> 12) << 12) + } +} + +impl AddressX64 for VirtAddrSv48 { + fn new_u64(addr: u64) -> Self { + if addr.get_bit(47) { + assert!( + addr.get_bits(48..64) == (1 << (64 - 48)) - 1, + "va 48..64 is not sext" + ); + } else { + assert!(addr.get_bits(48..64) == 0x0000, "va 48..64 is not sext"); + } + VirtAddrSv48(addr as u64) + } + fn as_u64(&self) -> u64 { + self.0 + } +} +impl AddressX64 for PhysAddrSv48 { + fn new_u64(addr: u64) -> Self { + assert!( + addr.get_bits(56..64) == 0, + "Sv48 does not allow pa 56..64!=0" + ); + PhysAddrSv48(addr) + } + fn as_u64(&self) -> u64 { + self.0 + } +} + +impl PhysicalAddress for PhysAddrSv48 {} diff --git a/vendor/riscv/src/asm.rs b/vendor/riscv/src/asm.rs new file mode 100644 index 00000000..ca007d99 --- /dev/null +++ b/vendor/riscv/src/asm.rs @@ -0,0 +1,152 @@ +//! Assembly instructions + +macro_rules! instruction { + ($(#[$attr:meta])*, $fnname:ident, $asm:expr, $asm_fn:ident) => ( + $(#[$attr])* + #[inline] + pub unsafe fn $fnname() { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => core::arch::asm!($asm), + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(); + } + + $asm_fn(); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + ) +} + +instruction!( + /// `EBREAK` instruction wrapper + /// + /// Generates a breakpoint exception. + , ebreak, "ebreak", __ebreak); +instruction!( + /// `WFI` instruction wrapper + /// + /// Provides a hint to the implementation that the current hart can be stalled until an interrupt might need servicing. + /// The WFI instruction is just a hint, and a legal implementation is to implement WFI as a NOP. + , wfi, "wfi", __wfi); +instruction!( + /// `SFENCE.VMA` instruction wrapper (all address spaces and page table levels) + /// + /// Synchronizes updates to in-memory memory-management data structures with current execution. + /// Instruction execution causes implicit reads and writes to these data structures; however, these implicit references + /// are ordinarily not ordered with respect to loads and stores in the instruction stream. + /// Executing an `SFENCE.VMA` instruction guarantees that any stores in the instruction stream prior to the + /// `SFENCE.VMA` are ordered before all implicit references subsequent to the `SFENCE.VMA`. + , sfence_vma_all, "sfence.vma", __sfence_vma_all); + +/// `SFENCE.VMA` instruction wrapper +/// +/// Synchronizes updates to in-memory memory-management data structures with current execution. +/// Instruction execution causes implicit reads and writes to these data structures; however, these implicit references +/// are ordinarily not ordered with respect to loads and stores in the instruction stream. +/// Executing an `SFENCE.VMA` instruction guarantees that any stores in the instruction stream prior to the +/// `SFENCE.VMA` are ordered before all implicit references subsequent to the `SFENCE.VMA`. +#[inline] +#[allow(unused_variables)] +pub unsafe fn sfence_vma(asid: usize, addr: usize) { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => core::arch::asm!("sfence.vma {0}, {1}", in(reg) addr, in(reg) asid), + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn __sfence_vma(asid: usize, addr: usize); + } + + __sfence_vma(asid, addr); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } +} + +mod hypervisor_extension { + // Generating instructions for Hypervisor extension. + // There are two kinds of instructions: rs1/rs2 type and rs1/rd type. + // Also special register handling is required before LLVM could generate inline assembly for extended instructions. + macro_rules! instruction_hypervisor_extension { + (RS1_RS2, $(#[$attr:meta])*, $fnname:ident, $asm:expr, $asm_fn:ident) => ( + $(#[$attr])* + #[inline] + #[allow(unused_variables)] + pub unsafe fn $fnname(rs1: usize, rs2: usize) { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + // Since LLVM does not recognize the two registers, we assume they are placed in a0 and a1, correspondingly. + () => core::arch::asm!($asm, in("x10") rs1, in("x11") rs2), + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(rs1: usize, rs2: usize); + } + + $asm_fn(rs1, rs2); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + ); + (RS1_RD, $(#[$attr:meta])*, $fnname:ident, $asm:expr, $asm_fn:ident) => ( + $(#[$attr])* + #[inline] + #[allow(unused_variables)] + pub unsafe fn $fnname(rs1: usize)->usize { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => { + let mut result : usize; + core::arch::asm!($asm, inlateout("x10") rs1 => result); + return result; + } + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(rs1: usize)->usize; + } + + return $asm_fn(rs1); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + ) + } + + instruction_hypervisor_extension!(RS1_RS2,,hfence_gvma,".word 1656029299",__hfence_gvma); + instruction_hypervisor_extension!(RS1_RS2,,hfence_vvma,".word 582287475",__hfence_vvma); + instruction_hypervisor_extension!(RS1_RD,,hlv_b,".word 1610958195",__hlv_b); + instruction_hypervisor_extension!(RS1_RD,,hlv_bu,".word 1612006771",__hlv_bu); + instruction_hypervisor_extension!(RS1_RD,,hlv_h,".word 1678067059",__hlv_h); + instruction_hypervisor_extension!(RS1_RD,,hlv_hu,".word 1679115635",__hlv_hu); + instruction_hypervisor_extension!(RS1_RD,,hlvx_hu,".word 1681212787",__hlvx_hu); + instruction_hypervisor_extension!(RS1_RD,,hlv_w,".word 1745175923",__hlv_w); + instruction_hypervisor_extension!(RS1_RD,,hlvx_wu,".word 1748321651",__hlvx_wu); + instruction_hypervisor_extension!(RS1_RS2,,hsv_b,".word 1656045683",__hsv_b); + instruction_hypervisor_extension!(RS1_RS2,,hsv_h,".word 1723154547",__hsv_h); + instruction_hypervisor_extension!(RS1_RS2,,hsv_w,".word 1790263411",__hsv_w); + instruction_hypervisor_extension!(RS1_RD,,hlv_wu,".word 1746224499",__hlv_wu); + instruction_hypervisor_extension!(RS1_RD,,hlv_d,".word 1812284787",__hlv_d); + instruction_hypervisor_extension!(RS1_RS2,,hsv_d,".word 1857372275",__hsv_d); +} + +pub use self::hypervisor_extension::*; diff --git a/vendor/riscv/src/interrupt.rs b/vendor/riscv/src/interrupt.rs new file mode 100644 index 00000000..9b8598d8 --- /dev/null +++ b/vendor/riscv/src/interrupt.rs @@ -0,0 +1,58 @@ +//! Interrupts + +// NOTE: Adapted from cortex-m/src/interrupt.rs +pub use bare_metal::{CriticalSection, Mutex, Nr}; +use register::mstatus; + +/// Disables all interrupts +#[inline] +pub unsafe fn disable() { + match () { + #[cfg(riscv)] + () => mstatus::clear_mie(), + #[cfg(not(riscv))] + () => unimplemented!(), + } +} + +/// Enables all the interrupts +/// +/// # Safety +/// +/// - Do not call this function inside an `interrupt::free` critical section +#[inline] +pub unsafe fn enable() { + match () { + #[cfg(riscv)] + () => mstatus::set_mie(), + #[cfg(not(riscv))] + () => unimplemented!(), + } +} + +/// Execute closure `f` in an interrupt-free context. +/// +/// This as also known as a "critical section". +pub fn free(f: F) -> R +where + F: FnOnce(&CriticalSection) -> R, +{ + let mstatus = mstatus::read(); + + // disable interrupts + unsafe { + disable(); + } + + let r = f(unsafe { &CriticalSection::new() }); + + // If the interrupts were active before our `disable` call, then re-enable + // them. Otherwise, keep them disabled + if mstatus.mie() { + unsafe { + enable(); + } + } + + r +} diff --git a/vendor/riscv/src/lib.rs b/vendor/riscv/src/lib.rs new file mode 100644 index 00000000..27b53287 --- /dev/null +++ b/vendor/riscv/src/lib.rs @@ -0,0 +1,28 @@ +//! Low level access to RISC-V processors +//! +//! # Minimum Supported Rust Version (MSRV) +//! +//! This crate is guaranteed to compile on stable Rust 1.42 and up. It *might* +//! compile with older versions but that may change in any new patch release. +//! +//! # Features +//! +//! This crate provides: +//! +//! - Access to core registers like `mstatus` or `mcause`. +//! - Interrupt manipulation mechanisms. +//! - Wrappers around assembly instructions like `WFI`. + + +#![no_std] +#![cfg_attr(feature = "inline-asm", feature(asm_const))] +extern crate bare_metal; +#[macro_use] +extern crate bitflags; +extern crate bit_field; + +pub mod addr; +pub mod asm; +pub mod interrupt; +pub mod paging; +pub mod register; diff --git a/vendor/riscv/src/paging/frame_alloc.rs b/vendor/riscv/src/paging/frame_alloc.rs new file mode 100644 index 00000000..89b4ada6 --- /dev/null +++ b/vendor/riscv/src/paging/frame_alloc.rs @@ -0,0 +1,40 @@ +//! Traits for abstracting away frame allocation and deallocation. + +use addr::*; +/// A trait for types that can allocate a frame of memory. +pub trait FrameAllocatorFor { + /// Allocate a frame of the appropriate size and return it if possible. + fn alloc(&mut self) -> Option>; +} + +/// A trait for types that can deallocate a frame of memory. +pub trait FrameDeallocatorFor { + /// Deallocate the given frame of memory. + fn dealloc(&mut self, frame: FrameWith

); +} + +/// Polyfill for default use cases. + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +pub trait FrameAllocator { + fn alloc(&mut self) -> Option; +} +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +pub trait FrameDeallocator { + fn dealloc(&mut self, frame: Frame); +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +impl FrameAllocatorFor for T { + #[inline] + fn alloc(&mut self) -> Option { + FrameAllocator::alloc(self) + } +} +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +impl FrameDeallocatorFor for T { + #[inline] + fn dealloc(&mut self, frame: Frame) { + FrameDeallocator::dealloc(self, frame) + } +} diff --git a/vendor/riscv/src/paging/mapper.rs b/vendor/riscv/src/paging/mapper.rs new file mode 100644 index 00000000..6e477303 --- /dev/null +++ b/vendor/riscv/src/paging/mapper.rs @@ -0,0 +1,136 @@ +use super::frame_alloc::*; +use super::page_table::*; +use addr::*; + +pub trait Mapper { + type P: PhysicalAddress; + type V: VirtualAddress; + type MapperFlush: MapperFlushable; + type Entry: PTE; + + /// Creates a new mapping in the page table. + /// + /// This function might need additional physical frames to create new page tables. These + /// frames are allocated from the `allocator` argument. At most three frames are required. + fn map_to( + &mut self, + page: PageWith, + frame: FrameWith, + flags: PageTableFlags, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result; + + /// Removes a mapping from the page table and returns the frame that used to be mapped. + /// + /// Note that no page tables or pages are deallocated. + fn unmap( + &mut self, + page: PageWith, + ) -> Result<(FrameWith, Self::MapperFlush), UnmapError<::P>>; + + /// Get the reference of the specified `page` entry + fn ref_entry(&mut self, page: PageWith) -> Result<&mut Self::Entry, FlagUpdateError>; + + /// Updates the flags of an existing mapping. + fn update_flags( + &mut self, + page: PageWith, + flags: PageTableFlags, + ) -> Result { + self.ref_entry(page).map(|e| { + e.set(e.frame::(), flags); + Self::MapperFlush::new(page) + }) + } + + /// Return the frame that the specified page is mapped to. + fn translate_page(&mut self, page: PageWith) -> Option> { + match self.ref_entry(page) { + Ok(e) => { + if e.is_unused() { + None + } else { + Some(e.frame()) + } + } + Err(_) => None, + } + } + + /// Maps the given frame to the virtual page with the same address. + fn identity_map( + &mut self, + frame: FrameWith, + flags: PageTableFlags, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result { + let page = PageWith::of_addr(Self::V::new(frame.start_address().as_usize())); + self.map_to(page, frame, flags, allocator) + } +} + +pub trait MapperFlushable { + /// Create a new flush promise + fn new(page: PageWith) -> Self; + /// Flush the page from the TLB to ensure that the newest mapping is used. + fn flush(self); + /// Don't flush the TLB and silence the “must be used” warning. + fn ignore(self); +} + +#[must_use = "Page Table changes must be flushed or ignored."] +pub struct MapperFlush(usize); + +impl MapperFlushable for MapperFlush { + fn new(page: PageWith) -> Self { + MapperFlush(page.start_address().as_usize()) + } + fn flush(self) { + unsafe { + crate::asm::sfence_vma(0, self.0); + } + } + fn ignore(self) {} +} + +/// This error is returned from `map_to` and similar methods. +#[derive(Debug)] +pub enum MapToError { + /// An additional frame was needed for the mapping process, but the frame allocator + /// returned `None`. + FrameAllocationFailed, + /// An upper level page table entry has the `HUGE_PAGE` flag set, which means that the + /// given page is part of an already mapped huge page. + ParentEntryHugePage, + /// The given page is already mapped to a physical frame. + PageAlreadyMapped, +} + +/// An error indicating that an `unmap` call failed. +#[derive(Debug)] +pub enum UnmapError { + /// An upper level page table entry has the `HUGE_PAGE` flag set, which means that the + /// given page is part of a huge page and can't be freed individually. + ParentEntryHugePage, + /// The given page is not mapped to a physical frame. + PageNotMapped, + /// The page table entry for the given page points to an invalid physical address. + InvalidFrameAddress(P), +} + +/// An error indicating that an `update_flags` call failed. +#[derive(Debug)] +pub enum FlagUpdateError { + /// The given page is not mapped to a physical frame. + PageNotMapped, +} + +pub trait MapperExt { + type Page; + type Frame; +} + +impl MapperExt for T { + type Page = PageWith<::V>; + type Frame = FrameWith<::P>; +} diff --git a/vendor/riscv/src/paging/mod.rs b/vendor/riscv/src/paging/mod.rs new file mode 100644 index 00000000..7df9b062 --- /dev/null +++ b/vendor/riscv/src/paging/mod.rs @@ -0,0 +1,13 @@ +mod frame_alloc; +mod mapper; +mod multi_level; +mod multi_level_x4; +mod page_table; +mod page_table_x4; + +pub use self::frame_alloc::*; +pub use self::mapper::*; +pub use self::multi_level::*; +pub use self::multi_level_x4::*; +pub use self::page_table::*; +pub use self::page_table_x4::*; diff --git a/vendor/riscv/src/paging/multi_level.rs b/vendor/riscv/src/paging/multi_level.rs new file mode 100644 index 00000000..ae1a114d --- /dev/null +++ b/vendor/riscv/src/paging/multi_level.rs @@ -0,0 +1,354 @@ +use super::frame_alloc::*; +use super::mapper::*; +use super::page_table::{PageTableFlags as F, *}; +use crate::addr::*; +use core::marker::PhantomData; + +/// This struct is a two level page table with `Mapper` trait implemented. +pub struct Rv32PageTableWith<'a, V: VirtualAddress + AddressL2, FL: MapperFlushable> { + root_table: &'a mut PageTableX32, + linear_offset: u64, // VA = PA + linear_offset + phantom: PhantomData<(V, FL)>, +} + +impl<'a, V: VirtualAddress + AddressL2, FL: MapperFlushable> Rv32PageTableWith<'a, V, FL> { + pub fn new(table: &'a mut PageTableX32, linear_offset: usize) -> Self { + Rv32PageTableWith { + root_table: table, + linear_offset: linear_offset as u64, + phantom: PhantomData, + } + } + + fn create_p1_if_not_exist( + &mut self, + p2_index: usize, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result<&mut PageTableX32, MapToError> { + if self.root_table[p2_index].is_unused() { + let frame = allocator.alloc().ok_or(MapToError::FrameAllocationFailed)?; + self.root_table[p2_index].set(frame.clone(), F::VALID); + let p1_table: &mut PageTableX32 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + p1_table.zero(); + Ok(p1_table) + } else { + let frame = self.root_table[p2_index].frame::(); + let p1_table: &mut PageTableX32 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + Ok(p1_table) + } + } +} + +impl<'a, V: VirtualAddress + AddressL2, FL: MapperFlushable> Mapper + for Rv32PageTableWith<'a, V, FL> +{ + type P = PhysAddrSv32; + type V = V; + type MapperFlush = FL; + type Entry = PageTableEntryX32; + fn map_to( + &mut self, + page: ::Page, + frame: ::Frame, + flags: PageTableFlags, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result { + let p1_table = self.create_p1_if_not_exist(page.p2_index(), allocator)?; + if !p1_table[page.p1_index()].is_unused() { + return Err(MapToError::PageAlreadyMapped); + } + p1_table[page.p1_index()].set(frame, flags); + Ok(Self::MapperFlush::new(page)) + } + + fn unmap( + &mut self, + page: ::Page, + ) -> Result<(::Frame, Self::MapperFlush), UnmapError<::P>> + { + if self.root_table[page.p2_index()].is_unused() { + return Err(UnmapError::PageNotMapped); + } + let p1_frame = self.root_table[page.p2_index()].frame::(); + let p1_table: &mut PageTableX32 = unsafe { p1_frame.as_kernel_mut(self.linear_offset) }; + let p1_entry = &mut p1_table[page.p1_index()]; + if !p1_entry.flags().contains(F::VALID) { + return Err(UnmapError::PageNotMapped); + } + let frame = p1_entry.frame(); + p1_entry.set_unused(); + Ok((frame, Self::MapperFlush::new(page))) + } + + fn ref_entry( + &mut self, + page: ::Page, + ) -> Result<&mut PageTableEntryX32, FlagUpdateError> { + if self.root_table[page.p2_index()].is_unused() { + return Err(FlagUpdateError::PageNotMapped); + } + let p1_frame = self.root_table[page.p2_index()].frame::(); + let p1_table: &mut PageTableX32 = unsafe { p1_frame.as_kernel_mut(self.linear_offset) }; + Ok(&mut p1_table[page.p1_index()]) + } +} + +/// This struct is a three level page table with `Mapper` trait implemented. + +pub struct Rv39PageTableWith<'a, V: VirtualAddress + AddressL3, FL: MapperFlushable> { + root_table: &'a mut PageTableX64, + linear_offset: u64, // VA = PA + linear_offset + phantom: PhantomData<(V, FL)>, +} + +impl<'a, V: VirtualAddress + AddressL3, FL: MapperFlushable> Rv39PageTableWith<'a, V, FL> { + pub fn new(table: &'a mut PageTableX64, linear_offset: usize) -> Self { + Rv39PageTableWith { + root_table: table, + linear_offset: linear_offset as u64, + phantom: PhantomData, + } + } + + fn create_p1_if_not_exist( + &mut self, + p3_index: usize, + p2_index: usize, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result<&mut PageTableX64, MapToError> { + let p2_table = if self.root_table[p3_index].is_unused() { + let frame = allocator.alloc().ok_or(MapToError::FrameAllocationFailed)?; + self.root_table[p3_index].set(frame.clone(), F::VALID); + let p2_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + p2_table.zero(); + p2_table + } else { + let frame = self.root_table[p3_index].frame::(); + unsafe { frame.as_kernel_mut(self.linear_offset) } + }; + if p2_table[p2_index].is_unused() { + let frame = allocator.alloc().ok_or(MapToError::FrameAllocationFailed)?; + p2_table[p2_index].set(frame.clone(), F::VALID); + let p1_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + p1_table.zero(); + Ok(p1_table) + } else { + let frame = p2_table[p2_index].frame::(); + let p1_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + Ok(p1_table) + } + } +} + +impl<'a, V: VirtualAddress + AddressL3, FL: MapperFlushable> Mapper + for Rv39PageTableWith<'a, V, FL> +{ + type P = PhysAddrSv39; + type V = V; + type MapperFlush = FL; + type Entry = PageTableEntryX64; + fn map_to( + &mut self, + page: ::Page, + frame: ::Frame, + flags: PageTableFlags, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result { + let p1_table = self.create_p1_if_not_exist(page.p3_index(), page.p2_index(), allocator)?; + if !p1_table[page.p1_index()].is_unused() { + return Err(MapToError::PageAlreadyMapped); + } + p1_table[page.p1_index()].set(frame, flags); + Ok(Self::MapperFlush::new(page)) + } + + fn unmap( + &mut self, + page: ::Page, + ) -> Result<(::Frame, Self::MapperFlush), UnmapError<::P>> + { + if self.root_table[page.p3_index()].is_unused() { + return Err(UnmapError::PageNotMapped); + } + let p2_frame = self.root_table[page.p3_index()].frame::(); + let p2_table: &mut PageTableX64 = unsafe { p2_frame.as_kernel_mut(self.linear_offset) }; + + if p2_table[page.p2_index()].is_unused() { + return Err(UnmapError::PageNotMapped); + } + let p1_frame = p2_table[page.p2_index()].frame::(); + let p1_table: &mut PageTableX64 = unsafe { p1_frame.as_kernel_mut(self.linear_offset) }; + let p1_entry = &mut p1_table[page.p1_index()]; + if !p1_entry.flags().contains(F::VALID) { + return Err(UnmapError::PageNotMapped); + } + let frame = p1_entry.frame(); + p1_entry.set_unused(); + Ok((frame, Self::MapperFlush::new(page))) + } + + fn ref_entry( + &mut self, + page: ::Page, + ) -> Result<&mut PageTableEntryX64, FlagUpdateError> { + if self.root_table[page.p3_index()].is_unused() { + return Err(FlagUpdateError::PageNotMapped); + } + let p2_frame = self.root_table[page.p3_index()].frame::(); + let p2_table: &mut PageTableX64 = unsafe { p2_frame.as_kernel_mut(self.linear_offset) }; + if p2_table[page.p2_index()].is_unused() { + return Err(FlagUpdateError::PageNotMapped); + } + + let p1_frame = p2_table[page.p2_index()].frame::(); + let p1_table: &mut PageTableX64 = unsafe { p1_frame.as_kernel_mut(self.linear_offset) }; + Ok(&mut p1_table[page.p1_index()]) + } +} + +/// This struct is a four level page table with `Mapper` trait implemented. + +pub struct Rv48PageTableWith<'a, V: VirtualAddress + AddressL4, FL: MapperFlushable> { + root_table: &'a mut PageTableX64, + linear_offset: u64, // VA = PA + linear_offset + phantom: PhantomData<(V, FL)>, +} + +impl<'a, V: VirtualAddress + AddressL4, FL: MapperFlushable> Rv48PageTableWith<'a, V, FL> { + pub fn new(table: &'a mut PageTableX64, linear_offset: usize) -> Self { + Rv48PageTableWith { + root_table: table, + linear_offset: linear_offset as u64, + phantom: PhantomData, + } + } + + fn create_p1_if_not_exist( + &mut self, + p4_index: usize, + p3_index: usize, + p2_index: usize, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result<&mut PageTableX64, MapToError> { + let p3_table = if self.root_table[p4_index].is_unused() { + let frame = allocator.alloc().ok_or(MapToError::FrameAllocationFailed)?; + self.root_table[p4_index].set(frame.clone(), F::VALID); + let p3_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + p3_table.zero(); + p3_table + } else { + let frame = self.root_table[p4_index].frame::(); + unsafe { frame.as_kernel_mut(self.linear_offset) } + }; + + let p2_table = if p3_table[p3_index].is_unused() { + let frame = allocator.alloc().ok_or(MapToError::FrameAllocationFailed)?; + p3_table[p3_index].set(frame.clone(), F::VALID); + let p2_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + p2_table.zero(); + p2_table + } else { + let frame = p3_table[p3_index].frame::(); + unsafe { frame.as_kernel_mut(self.linear_offset) } + }; + + if p2_table[p2_index].is_unused() { + let frame = allocator.alloc().ok_or(MapToError::FrameAllocationFailed)?; + p2_table[p2_index].set(frame.clone(), F::VALID); + let p1_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + p1_table.zero(); + Ok(p1_table) + } else { + let frame = p2_table[p2_index].frame::(); + let p1_table: &mut PageTableX64 = unsafe { frame.as_kernel_mut(self.linear_offset) }; + Ok(p1_table) + } + } +} + +impl<'a, V: VirtualAddress + AddressL4, FL: MapperFlushable> Mapper + for Rv48PageTableWith<'a, V, FL> +{ + type P = PhysAddrSv48; + type V = V; + type MapperFlush = FL; + type Entry = PageTableEntryX64; + fn map_to( + &mut self, + page: ::Page, + frame: ::Frame, + flags: PageTableFlags, + allocator: &mut impl FrameAllocatorFor<::P>, + ) -> Result { + let p1_table = self.create_p1_if_not_exist( + page.p4_index(), + page.p3_index(), + page.p2_index(), + allocator, + )?; + if !p1_table[page.p1_index()].is_unused() { + return Err(MapToError::PageAlreadyMapped); + } + p1_table[page.p1_index()].set(frame, flags); + Ok(Self::MapperFlush::new(page)) + } + + fn unmap( + &mut self, + page: ::Page, + ) -> Result<(::Frame, Self::MapperFlush), UnmapError<::P>> + { + if self.root_table[page.p4_index()].is_unused() { + return Err(UnmapError::PageNotMapped); + } + let p3_frame = self.root_table[page.p4_index()].frame::(); + let p3_table: &mut PageTableX64 = unsafe { p3_frame.as_kernel_mut(self.linear_offset) }; + + if p3_table[page.p3_index()].is_unused() { + return Err(UnmapError::PageNotMapped); + } + let p2_frame = p3_table[page.p3_index()].frame::(); + let p2_table: &mut PageTableX64 = unsafe { p2_frame.as_kernel_mut(self.linear_offset) }; + + if p2_table[page.p2_index()].is_unused() { + return Err(UnmapError::PageNotMapped); + } + let p1_frame = p2_table[page.p2_index()].frame::(); + let p1_table: &mut PageTableX64 = unsafe { p1_frame.as_kernel_mut(self.linear_offset) }; + let p1_entry = &mut p1_table[page.p1_index()]; + if !p1_entry.flags().contains(F::VALID) { + return Err(UnmapError::PageNotMapped); + } + let frame = p1_entry.frame::(); + p1_entry.set_unused(); + Ok((frame, Self::MapperFlush::new(page))) + } + + fn ref_entry( + &mut self, + page: ::Page, + ) -> Result<&mut PageTableEntryX64, FlagUpdateError> { + if self.root_table[page.p4_index()].is_unused() { + return Err(FlagUpdateError::PageNotMapped); + } + let p3_frame = self.root_table[page.p4_index()].frame::(); + let p3_table: &mut PageTableX64 = unsafe { p3_frame.as_kernel_mut(self.linear_offset) }; + + if p3_table[page.p3_index()].is_unused() { + return Err(FlagUpdateError::PageNotMapped); + } + let p2_frame = p3_table[page.p3_index()].frame::(); + let p2_table: &mut PageTableX64 = unsafe { p2_frame.as_kernel_mut(self.linear_offset) }; + if p2_table[page.p2_index()].is_unused() { + return Err(FlagUpdateError::PageNotMapped); + } + + let p1_frame = p2_table[page.p2_index()].frame::(); + let p1_table: &mut PageTableX64 = unsafe { p1_frame.as_kernel_mut(self.linear_offset) }; + Ok(&mut p1_table[page.p1_index()]) + } +} + +pub type Rv32PageTable<'a> = Rv32PageTableWith<'a, VirtAddrSv32, MapperFlush>; +pub type Rv39PageTable<'a> = Rv39PageTableWith<'a, VirtAddrSv39, MapperFlush>; +pub type Rv48PageTable<'a> = Rv48PageTableWith<'a, VirtAddrSv48, MapperFlush>; diff --git a/vendor/riscv/src/paging/multi_level_x4.rs b/vendor/riscv/src/paging/multi_level_x4.rs new file mode 100644 index 00000000..a719e041 --- /dev/null +++ b/vendor/riscv/src/paging/multi_level_x4.rs @@ -0,0 +1,42 @@ +use crate::addr::*; +use crate::asm::{hfence_gvma, hfence_vvma}; +use crate::paging::mapper::MapperFlushable; +use crate::paging::multi_level::Rv32PageTableWith; +use crate::paging::multi_level::{Rv39PageTableWith, Rv48PageTableWith}; + +#[must_use = "Guest Physical Address Table changes must be flushed or ignored."] +pub struct MapperFlushGPA(usize); + +impl MapperFlushable for MapperFlushGPA { + fn new(page: PageWith) -> Self { + MapperFlushGPA(page.start_address().as_usize()) + } + fn flush(self) { + unsafe { + hfence_gvma(self.0, 0); + } + } + fn ignore(self) {} +} + +#[must_use = "Guest Page Table changes must be flushed or ignored."] +pub struct MapperFlushGPT(usize); + +impl MapperFlushable for MapperFlushGPT { + fn new(page: PageWith) -> Self { + MapperFlushGPT(page.start_address().as_usize()) + } + fn flush(self) { + unsafe { + hfence_vvma(self.0, 0); + } + } + fn ignore(self) {} +} + +pub type Rv32PageTableX4<'a> = Rv32PageTableWith<'a, GPAddrSv32X4, MapperFlushGPA>; +pub type Rv39PageTableX4<'a> = Rv39PageTableWith<'a, GPAddrSv39X4, MapperFlushGPA>; +pub type Rv48PageTableX4<'a> = Rv48PageTableWith<'a, GPAddrSv48X4, MapperFlushGPA>; +pub type Rv32PageTableGuest<'a> = Rv32PageTableWith<'a, VirtAddrSv32, MapperFlushGPT>; +pub type Rv39PageTableGuest<'a> = Rv39PageTableWith<'a, VirtAddrSv39, MapperFlushGPT>; +pub type Rv48PageTableGuest<'a> = Rv48PageTableWith<'a, VirtAddrSv48, MapperFlushGPT>; diff --git a/vendor/riscv/src/paging/page_table.rs b/vendor/riscv/src/paging/page_table.rs new file mode 100644 index 00000000..56896fe2 --- /dev/null +++ b/vendor/riscv/src/paging/page_table.rs @@ -0,0 +1,252 @@ +use addr::*; +use core::convert::TryInto; +use core::fmt::{Debug, Error, Formatter}; +use core::marker::PhantomData; +use core::ops::{Index, IndexMut}; + +pub type Entries32 = [PageTableEntryX32; RV32_ENTRY_COUNT]; +pub type Entries64 = [PageTableEntryX64; RV64_ENTRY_COUNT]; + +// To avoid const generic. +pub trait PTEIterableSlice { + fn to_pte_slice<'a>(&'a self) -> &'a [T]; + fn to_pte_slice_mut<'a>(&'a mut self) -> &'a mut [T]; + fn pte_index(&self, index: usize) -> &T; + fn pte_index_mut(&mut self, index: usize) -> &mut T; +} + +impl PTEIterableSlice for Entries32 { + fn to_pte_slice(&self) -> &[PageTableEntryX32] { + self + } + fn to_pte_slice_mut(&mut self) -> &mut [PageTableEntryX32] { + self + } + fn pte_index(&self, index: usize) -> &PageTableEntryX32 { + &self[index] + } + fn pte_index_mut(&mut self, index: usize) -> &mut PageTableEntryX32 { + &mut self[index] + } +} +impl PTEIterableSlice for Entries64 { + fn to_pte_slice(&self) -> &[PageTableEntryX64] { + self + } + fn to_pte_slice_mut(&mut self) -> &mut [PageTableEntryX64] { + self + } + fn pte_index(&self, index: usize) -> &PageTableEntryX64 { + &self[index] + } + fn pte_index_mut(&mut self, index: usize) -> &mut PageTableEntryX64 { + &mut self[index] + } +} + +#[repr(C)] +pub struct PageTableWith, E: PTE> { + entries: T, + phantom: PhantomData, +} + +impl, E: PTE> PageTableWith { + /// Clears all entries. + pub fn zero(&mut self) { + for entry in self.entries.to_pte_slice_mut().iter_mut() { + entry.set_unused(); + } + } +} + +impl, E: PTE> Index for PageTableWith { + type Output = E; + + fn index(&self, index: usize) -> &Self::Output { + self.entries.pte_index(index) + } +} + +impl, E: PTE> IndexMut for PageTableWith { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.entries.pte_index_mut(index) + } +} + +impl, E: PTE + Debug> Debug for PageTableWith { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_map() + .entries( + self.entries + .to_pte_slice() + .iter() + .enumerate() + .filter(|p| !p.1.is_unused()), + ) + .finish() + } +} + +pub trait PTE { + fn is_unused(&self) -> bool; + fn set_unused(&mut self); + fn flags(&self) -> PageTableFlags; + fn ppn(&self) -> usize; + fn ppn_u64(&self) -> u64; + fn addr(&self) -> T; + fn frame(&self) -> FrameWith; + fn set(&mut self, frame: FrameWith, flags: PageTableFlags); + fn flags_mut(&mut self) -> &mut PageTableFlags; +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct PageTableEntryX32(u32); + +impl PTE for PageTableEntryX32 { + fn is_unused(&self) -> bool { + self.0 == 0 + } + fn set_unused(&mut self) { + self.0 = 0; + } + fn flags(&self) -> PageTableFlags { + PageTableFlags::from_bits_truncate(self.0 as usize) + } + fn ppn(&self) -> usize { + self.ppn_u64().try_into().unwrap() + } + fn ppn_u64(&self) -> u64 { + (self.0 >> 10) as u64 + } + fn addr(&self) -> T { + T::new_u64((self.ppn() as u64) << 12) + } + fn frame(&self) -> FrameWith { + FrameWith::of_addr(self.addr()) + } + fn set(&mut self, frame: FrameWith, mut flags: PageTableFlags) { + // U540 will raise page fault when accessing page with A=0 or D=0 + flags |= EF::ACCESSED | EF::DIRTY; + self.0 = ((frame.number() << 10) | flags.bits()) as u32; + } + fn flags_mut(&mut self) -> &mut PageTableFlags { + unsafe { &mut *(self as *mut _ as *mut PageTableFlags) } + } +} + +impl Debug for PageTableEntryX32 { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_struct("PageTableEntryX32") + .field("frame", &self.frame::()) + .field("flags", &self.flags()) + .finish() + } +} + +#[derive(Copy, Clone)] +pub struct PageTableEntryX64(u64); + +impl PTE for PageTableEntryX64 { + fn is_unused(&self) -> bool { + self.0 == 0 + } + fn set_unused(&mut self) { + self.0 = 0; + } + fn flags(&self) -> PageTableFlags { + PageTableFlags::from_bits_truncate(self.0 as usize) + } + fn ppn(&self) -> usize { + self.ppn_u64().try_into().unwrap() + } + fn ppn_u64(&self) -> u64 { + (self.0 >> 10) as u64 + } + fn addr(&self) -> T { + T::new_u64((self.ppn() as u64) << 12) + } + fn frame(&self) -> FrameWith { + FrameWith::of_addr(self.addr()) + } + fn set(&mut self, frame: FrameWith, mut flags: PageTableFlags) { + // U540 will raise page fault when accessing page with A=0 or D=0 + flags |= EF::ACCESSED | EF::DIRTY; + self.0 = ((frame.number() << 10) | flags.bits()) as u64; + } + fn flags_mut(&mut self) -> &mut PageTableFlags { + unsafe { &mut *(self as *mut _ as *mut PageTableFlags) } + } +} + +pub struct PageTableEntryX64Printer<'a, P: PhysicalAddress>( + &'a PageTableEntryX64, + PhantomData<*const P>, +); + +impl<'a, P: PhysicalAddress> Debug for PageTableEntryX64Printer<'a, P> { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + f.debug_struct("PageTableEntryX64") + .field("frame", &self.0.frame::

()) + .field("flags", &self.0.flags()) + .finish() + } +} + +impl PageTableEntryX64 { + pub fn debug_sv39<'a>(&'a self) -> PageTableEntryX64Printer<'a, PhysAddrSv39> { + PageTableEntryX64Printer(self, PhantomData) + } + pub fn debug_sv48<'a>(&'a self) -> PageTableEntryX64Printer<'a, PhysAddrSv48> { + PageTableEntryX64Printer(self, PhantomData) + } +} + +impl Debug for PageTableEntryX64 { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + self.debug_sv48().fmt(f) + } +} + +pub const RV64_ENTRY_COUNT: usize = 1 << 9; +pub const RV32_ENTRY_COUNT: usize = 1 << 10; +#[cfg(riscv64)] +pub const ENTRY_COUNT: usize = RV64_ENTRY_COUNT; +#[cfg(riscv32)] +pub const ENTRY_COUNT: usize = RV32_ENTRY_COUNT; +#[cfg(riscv64)] +pub type PageTableEntry = PageTableEntryX64; +#[cfg(riscv32)] +pub type PageTableEntry = PageTableEntryX32; +#[cfg(riscv64)] +pub type Entries = Entries64; +#[cfg(riscv32)] +pub type Entries = Entries32; +#[cfg(not(any(riscv32, riscv64)))] +pub const ENTRY_COUNT: usize = 1 << 0; +#[cfg(not(any(riscv32, riscv64)))] +pub type Entries = Entries64; + +pub type PageTableX32 = PageTableWith; +pub type PageTableX64 = PageTableWith; +#[cfg(riscv64)] +pub type PageTable = PageTableX64; +#[cfg(riscv32)] +pub type PageTable = PageTableX32; +bitflags! { + /// Possible flags for a page table entry. + pub struct PageTableFlags: usize { + const VALID = 1 << 0; + const READABLE = 1 << 1; + const WRITABLE = 1 << 2; + const EXECUTABLE = 1 << 3; + const USER = 1 << 4; + const GLOBAL = 1 << 5; + const ACCESSED = 1 << 6; + const DIRTY = 1 << 7; + const RESERVED1 = 1 << 8; + const RESERVED2 = 1 << 9; + } +} + +type EF = PageTableFlags; diff --git a/vendor/riscv/src/paging/page_table_x4.rs b/vendor/riscv/src/paging/page_table_x4.rs new file mode 100644 index 00000000..0d24193c --- /dev/null +++ b/vendor/riscv/src/paging/page_table_x4.rs @@ -0,0 +1,44 @@ +/// This file is for Hypervisor-related x4 page tables, including Sv32x4, Sv39x4 and Sv48x4. +/// In fact, these x4 page tables are Phys-to-Phys page tables from GPAs to real PAs. +use super::page_table::{ + PTEIterableSlice, PageTableEntryX32, PageTableEntryX64, PageTableWith, RV32_ENTRY_COUNT, + RV64_ENTRY_COUNT, +}; + +// The root page table is 4 times larger. +pub const RV32_X4_ENTRY_COUNT: usize = RV32_ENTRY_COUNT << 2; +pub const RV64_X4_ENTRY_COUNT: usize = RV64_ENTRY_COUNT << 2; + +pub type Entries32X4 = [PageTableEntryX32; RV32_X4_ENTRY_COUNT]; +pub type Entries64X4 = [PageTableEntryX64; RV64_X4_ENTRY_COUNT]; + +impl PTEIterableSlice for Entries32X4 { + fn to_pte_slice(&self) -> &[PageTableEntryX32] { + self + } + fn to_pte_slice_mut(&mut self) -> &mut [PageTableEntryX32] { + self + } + fn pte_index(&self, index: usize) -> &PageTableEntryX32 { + &self[index] + } + fn pte_index_mut(&mut self, index: usize) -> &mut PageTableEntryX32 { + &mut self[index] + } +} +impl PTEIterableSlice for Entries64X4 { + fn to_pte_slice(&self) -> &[PageTableEntryX64] { + self + } + fn to_pte_slice_mut(&mut self) -> &mut [PageTableEntryX64] { + self + } + fn pte_index(&self, index: usize) -> &PageTableEntryX64 { + &self[index] + } + fn pte_index_mut(&mut self, index: usize) -> &mut PageTableEntryX64 { + &mut self[index] + } +} +pub type PageTable32X4 = PageTableWith; +pub type PageTable64X4 = PageTableWith; diff --git a/vendor/riscv/src/register/fcsr.rs b/vendor/riscv/src/register/fcsr.rs new file mode 100644 index 00000000..855eb5d6 --- /dev/null +++ b/vendor/riscv/src/register/fcsr.rs @@ -0,0 +1,134 @@ +//! Floating-point control and status register + +use bit_field::BitField; + +/// Floating-point control and status register +#[derive(Clone, Copy, Debug)] +pub struct FCSR { + bits: u32, +} + +/// Accrued Exception Flags +#[derive(Clone, Copy, Debug)] +pub struct Flags(u32); + +/// Accrued Exception Flag +#[derive(Clone, Copy, Debug)] +pub enum Flag { + /// Inexact + NX = 0b00001, + + /// Underflow + UF = 0b00010, + + /// Overflow + OF = 0b00100, + + /// Divide by Zero + DZ = 0b01000, + + /// Invalid Operation + NV = 0b10000, +} + +impl Flags { + /// Inexact + #[inline] + pub fn nx(&self) -> bool { + self.0.get_bit(0) + } + + /// Underflow + #[inline] + pub fn uf(&self) -> bool { + self.0.get_bit(1) + } + + /// Overflow + #[inline] + pub fn of(&self) -> bool { + self.0.get_bit(2) + } + + /// Divide by Zero + #[inline] + pub fn dz(&self) -> bool { + self.0.get_bit(3) + } + + /// Invalid Operation + #[inline] + pub fn nv(&self) -> bool { + self.0.get_bit(4) + } +} + +/// Rounding Mode +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RoundingMode { + RoundToNearestEven = 0b000, + RoundTowardsZero = 0b001, + RoundDown = 0b010, + RoundUp = 0b011, + RoundToNearestMaxMagnitude = 0b100, + Invalid = 0b111, +} + +impl FCSR { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> u32 { + self.bits + } + + /// Accrued Exception Flags + #[inline] + pub fn fflags(&self) -> Flags { + Flags(self.bits.get_bits(0..5)) + } + + /// Rounding Mode + #[inline] + pub fn frm(&self) -> RoundingMode { + match self.bits.get_bits(5..8) { + 0b000 => RoundingMode::RoundToNearestEven, + 0b001 => RoundingMode::RoundTowardsZero, + 0b010 => RoundingMode::RoundDown, + 0b011 => RoundingMode::RoundUp, + 0b100 => RoundingMode::RoundToNearestMaxMagnitude, + _ => RoundingMode::Invalid, + } + } +} + +read_csr!(0x003, __read_fcsr); +write_csr!(0x003, __write_fcsr); +clear!(0x003, __clear_fcsr); + +/// Reads the CSR +#[inline] +pub fn read() -> FCSR { + FCSR { + bits: unsafe { _read() as u32 }, + } +} + +/// Writes the CSR +#[inline] +pub unsafe fn set_rounding_mode(frm: RoundingMode) { + let old = read(); + let bits = ((frm as u32) << 5) | old.fflags().0; + _write(bits as usize); +} + +/// Resets `fflags` field bits +#[inline] +pub unsafe fn clear_flags() { + let mask = 0b11111; + _clear(mask); +} + +/// Resets `fflags` field bit +#[inline] +pub unsafe fn clear_flag(flag: Flag) { + _clear(flag as usize); +} diff --git a/vendor/riscv/src/register/hpmcounterx.rs b/vendor/riscv/src/register/hpmcounterx.rs new file mode 100644 index 00000000..5eb15b07 --- /dev/null +++ b/vendor/riscv/src/register/hpmcounterx.rs @@ -0,0 +1,82 @@ +macro_rules! reg { + ( + $addr:expr, $csrl:ident, $csrh:ident, $readf:ident, $writef:ident + ) => { + /// Performance-monitoring counter + pub mod $csrl { + read_csr_as_usize!($addr, $readf); + read_composite_csr!(super::$csrh::read(), read()); + } + } +} + +macro_rules! regh { + ( + $addr:expr, $csrh:ident, $readf:ident, $writef:ident + ) => { + /// Upper 32 bits of performance-monitoring counter (RV32I only) + pub mod $csrh { + read_csr_as_usize_rv32!($addr, $readf); + } + } +} + +reg!(0xC03, hpmcounter3, hpmcounter3h, __read_hpmcounter3, __write_hpmcounter3); +reg!(0xC04, hpmcounter4, hpmcounter4h, __read_hpmcounter4, __write_hpmcounter4); +reg!(0xC05, hpmcounter5, hpmcounter5h, __read_hpmcounter5, __write_hpmcounter5); +reg!(0xC06, hpmcounter6, hpmcounter6h, __read_hpmcounter6, __write_hpmcounter6); +reg!(0xC07, hpmcounter7, hpmcounter7h, __read_hpmcounter7, __write_hpmcounter7); +reg!(0xC08, hpmcounter8, hpmcounter8h, __read_hpmcounter8, __write_hpmcounter8); +reg!(0xC09, hpmcounter9, hpmcounter9h, __read_hpmcounter9, __write_hpmcounter9); +reg!(0xC0A, hpmcounter10, hpmcounter10h, __read_hpmcounter10, __write_hpmcounter10); +reg!(0xC0B, hpmcounter11, hpmcounter11h, __read_hpmcounter11, __write_hpmcounter11); +reg!(0xC0C, hpmcounter12, hpmcounter12h, __read_hpmcounter12, __write_hpmcounter12); +reg!(0xC0D, hpmcounter13, hpmcounter13h, __read_hpmcounter13, __write_hpmcounter13); +reg!(0xC0E, hpmcounter14, hpmcounter14h, __read_hpmcounter14, __write_hpmcounter14); +reg!(0xC0F, hpmcounter15, hpmcounter15h, __read_hpmcounter15, __write_hpmcounter15); +reg!(0xC10, hpmcounter16, hpmcounter16h, __read_hpmcounter16, __write_hpmcounter16); +reg!(0xC11, hpmcounter17, hpmcounter17h, __read_hpmcounter17, __write_hpmcounter17); +reg!(0xC12, hpmcounter18, hpmcounter18h, __read_hpmcounter18, __write_hpmcounter18); +reg!(0xC13, hpmcounter19, hpmcounter19h, __read_hpmcounter19, __write_hpmcounter19); +reg!(0xC14, hpmcounter20, hpmcounter20h, __read_hpmcounter20, __write_hpmcounter20); +reg!(0xC15, hpmcounter21, hpmcounter21h, __read_hpmcounter21, __write_hpmcounter21); +reg!(0xC16, hpmcounter22, hpmcounter22h, __read_hpmcounter22, __write_hpmcounter22); +reg!(0xC17, hpmcounter23, hpmcounter23h, __read_hpmcounter23, __write_hpmcounter23); +reg!(0xC18, hpmcounter24, hpmcounter24h, __read_hpmcounter24, __write_hpmcounter24); +reg!(0xC19, hpmcounter25, hpmcounter25h, __read_hpmcounter25, __write_hpmcounter25); +reg!(0xC1A, hpmcounter26, hpmcounter26h, __read_hpmcounter26, __write_hpmcounter26); +reg!(0xC1B, hpmcounter27, hpmcounter27h, __read_hpmcounter27, __write_hpmcounter27); +reg!(0xC1C, hpmcounter28, hpmcounter28h, __read_hpmcounter28, __write_hpmcounter28); +reg!(0xC1D, hpmcounter29, hpmcounter29h, __read_hpmcounter29, __write_hpmcounter29); +reg!(0xC1E, hpmcounter30, hpmcounter30h, __read_hpmcounter30, __write_hpmcounter30); +reg!(0xC1F, hpmcounter31, hpmcounter31h, __read_hpmcounter31, __write_hpmcounter31); + +regh!(0xC83, hpmcounter3h, __read_hpmcounter3h, __write_hpmcounter3h); +regh!(0xC84, hpmcounter4h, __read_hpmcounter4h, __write_hpmcounter4h); +regh!(0xC85, hpmcounter5h, __read_hpmcounter5h, __write_hpmcounter5h); +regh!(0xC86, hpmcounter6h, __read_hpmcounter6h, __write_hpmcounter6h); +regh!(0xC87, hpmcounter7h, __read_hpmcounter7h, __write_hpmcounter7h); +regh!(0xC88, hpmcounter8h, __read_hpmcounter8h, __write_hpmcounter8h); +regh!(0xC89, hpmcounter9h, __read_hpmcounter9h, __write_hpmcounter9h); +regh!(0xC8A, hpmcounter10h, __read_hpmcounter10h, __write_hpmcounter10h); +regh!(0xC8B, hpmcounter11h, __read_hpmcounter11h, __write_hpmcounter11h); +regh!(0xC8C, hpmcounter12h, __read_hpmcounter12h, __write_hpmcounter12h); +regh!(0xC8D, hpmcounter13h, __read_hpmcounter13h, __write_hpmcounter13h); +regh!(0xC8E, hpmcounter14h, __read_hpmcounter14h, __write_hpmcounter14h); +regh!(0xC8F, hpmcounter15h, __read_hpmcounter15h, __write_hpmcounter15h); +regh!(0xC90, hpmcounter16h, __read_hpmcounter16h, __write_hpmcounter16h); +regh!(0xC91, hpmcounter17h, __read_hpmcounter17h, __write_hpmcounter17h); +regh!(0xC92, hpmcounter18h, __read_hpmcounter18h, __write_hpmcounter18h); +regh!(0xC93, hpmcounter19h, __read_hpmcounter19h, __write_hpmcounter19h); +regh!(0xC94, hpmcounter20h, __read_hpmcounter20h, __write_hpmcounter20h); +regh!(0xC95, hpmcounter21h, __read_hpmcounter21h, __write_hpmcounter21h); +regh!(0xC96, hpmcounter22h, __read_hpmcounter22h, __write_hpmcounter22h); +regh!(0xC97, hpmcounter23h, __read_hpmcounter23h, __write_hpmcounter23h); +regh!(0xC98, hpmcounter24h, __read_hpmcounter24h, __write_hpmcounter24h); +regh!(0xC99, hpmcounter25h, __read_hpmcounter25h, __write_hpmcounter25h); +regh!(0xC9A, hpmcounter26h, __read_hpmcounter26h, __write_hpmcounter26h); +regh!(0xC9B, hpmcounter27h, __read_hpmcounter27h, __write_hpmcounter27h); +regh!(0xC9C, hpmcounter28h, __read_hpmcounter28h, __write_hpmcounter28h); +regh!(0xC9D, hpmcounter29h, __read_hpmcounter29h, __write_hpmcounter29h); +regh!(0xC9E, hpmcounter30h, __read_hpmcounter30h, __write_hpmcounter30h); +regh!(0xC9F, hpmcounter31h, __read_hpmcounter31h, __write_hpmcounter31h); diff --git a/vendor/riscv/src/register/hypervisorx64/hcounteren.rs b/vendor/riscv/src/register/hypervisorx64/hcounteren.rs new file mode 100644 index 00000000..7f846900 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hcounteren.rs @@ -0,0 +1,413 @@ +//! Hypervisor Guest External Interrupt Pending Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hcounteren { + bits: usize, +} +impl Hcounteren { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hcounteren { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// + #[inline] + pub fn cy(&self) -> bool { + self.bits.get_bit(0) + } + #[inline] + pub fn set_cy(&mut self, val: bool) { + self.bits.set_bit(0, val); + } + /// + #[inline] + pub fn tm(&self) -> bool { + self.bits.get_bit(1) + } + #[inline] + pub fn set_tm(&mut self, val: bool) { + self.bits.set_bit(1, val); + } + /// + #[inline] + pub fn ir(&self) -> bool { + self.bits.get_bit(2) + } + #[inline] + pub fn set_ir(&mut self, val: bool) { + self.bits.set_bit(2, val); + } + /// + #[inline] + pub fn hpm3(&self) -> bool { + self.bits.get_bit(3) + } + #[inline] + pub fn set_hpm3(&mut self, val: bool) { + self.bits.set_bit(3, val); + } + /// + #[inline] + pub fn hpm4(&self) -> bool { + self.bits.get_bit(4) + } + #[inline] + pub fn set_hpm4(&mut self, val: bool) { + self.bits.set_bit(4, val); + } + /// + #[inline] + pub fn hpm5(&self) -> bool { + self.bits.get_bit(5) + } + #[inline] + pub fn set_hpm5(&mut self, val: bool) { + self.bits.set_bit(5, val); + } + /// + #[inline] + pub fn hpm6(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_hpm6(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// + #[inline] + pub fn hpm7(&self) -> bool { + self.bits.get_bit(7) + } + #[inline] + pub fn set_hpm7(&mut self, val: bool) { + self.bits.set_bit(7, val); + } + /// + #[inline] + pub fn hpm8(&self) -> bool { + self.bits.get_bit(8) + } + #[inline] + pub fn set_hpm8(&mut self, val: bool) { + self.bits.set_bit(8, val); + } + /// + #[inline] + pub fn hpm9(&self) -> bool { + self.bits.get_bit(9) + } + #[inline] + pub fn set_hpm9(&mut self, val: bool) { + self.bits.set_bit(9, val); + } + /// + #[inline] + pub fn hpm10(&self) -> bool { + self.bits.get_bit(10) + } + #[inline] + pub fn set_hpm10(&mut self, val: bool) { + self.bits.set_bit(10, val); + } + /// + #[inline] + pub fn hpm11(&self) -> bool { + self.bits.get_bit(11) + } + #[inline] + pub fn set_hpm11(&mut self, val: bool) { + self.bits.set_bit(11, val); + } + /// + #[inline] + pub fn hpm12(&self) -> bool { + self.bits.get_bit(12) + } + #[inline] + pub fn set_hpm12(&mut self, val: bool) { + self.bits.set_bit(12, val); + } + /// + #[inline] + pub fn hpm13(&self) -> bool { + self.bits.get_bit(13) + } + #[inline] + pub fn set_hpm13(&mut self, val: bool) { + self.bits.set_bit(13, val); + } + /// + #[inline] + pub fn hpm14(&self) -> bool { + self.bits.get_bit(14) + } + #[inline] + pub fn set_hpm14(&mut self, val: bool) { + self.bits.set_bit(14, val); + } + /// + #[inline] + pub fn hpm15(&self) -> bool { + self.bits.get_bit(15) + } + #[inline] + pub fn set_hpm15(&mut self, val: bool) { + self.bits.set_bit(15, val); + } + /// + #[inline] + pub fn hpm16(&self) -> bool { + self.bits.get_bit(16) + } + #[inline] + pub fn set_hpm16(&mut self, val: bool) { + self.bits.set_bit(16, val); + } + /// + #[inline] + pub fn hpm17(&self) -> bool { + self.bits.get_bit(17) + } + #[inline] + pub fn set_hpm17(&mut self, val: bool) { + self.bits.set_bit(17, val); + } + /// + #[inline] + pub fn hpm18(&self) -> bool { + self.bits.get_bit(18) + } + #[inline] + pub fn set_hpm18(&mut self, val: bool) { + self.bits.set_bit(18, val); + } + /// + #[inline] + pub fn hpm19(&self) -> bool { + self.bits.get_bit(19) + } + #[inline] + pub fn set_hpm19(&mut self, val: bool) { + self.bits.set_bit(19, val); + } + /// + #[inline] + pub fn hpm20(&self) -> bool { + self.bits.get_bit(20) + } + #[inline] + pub fn set_hpm20(&mut self, val: bool) { + self.bits.set_bit(20, val); + } + /// + #[inline] + pub fn hpm21(&self) -> bool { + self.bits.get_bit(21) + } + #[inline] + pub fn set_hpm21(&mut self, val: bool) { + self.bits.set_bit(21, val); + } + /// + #[inline] + pub fn hpm22(&self) -> bool { + self.bits.get_bit(22) + } + #[inline] + pub fn set_hpm22(&mut self, val: bool) { + self.bits.set_bit(22, val); + } + /// + #[inline] + pub fn hpm23(&self) -> bool { + self.bits.get_bit(23) + } + #[inline] + pub fn set_hpm23(&mut self, val: bool) { + self.bits.set_bit(23, val); + } + /// + #[inline] + pub fn hpm24(&self) -> bool { + self.bits.get_bit(24) + } + #[inline] + pub fn set_hpm24(&mut self, val: bool) { + self.bits.set_bit(24, val); + } + /// + #[inline] + pub fn hpm25(&self) -> bool { + self.bits.get_bit(25) + } + #[inline] + pub fn set_hpm25(&mut self, val: bool) { + self.bits.set_bit(25, val); + } + /// + #[inline] + pub fn hpm26(&self) -> bool { + self.bits.get_bit(26) + } + #[inline] + pub fn set_hpm26(&mut self, val: bool) { + self.bits.set_bit(26, val); + } + /// + #[inline] + pub fn hpm27(&self) -> bool { + self.bits.get_bit(27) + } + #[inline] + pub fn set_hpm27(&mut self, val: bool) { + self.bits.set_bit(27, val); + } + /// + #[inline] + pub fn hpm28(&self) -> bool { + self.bits.get_bit(28) + } + #[inline] + pub fn set_hpm28(&mut self, val: bool) { + self.bits.set_bit(28, val); + } + /// + #[inline] + pub fn hpm29(&self) -> bool { + self.bits.get_bit(29) + } + #[inline] + pub fn set_hpm29(&mut self, val: bool) { + self.bits.set_bit(29, val); + } + /// + #[inline] + pub fn hpm30(&self) -> bool { + self.bits.get_bit(30) + } + #[inline] + pub fn set_hpm30(&mut self, val: bool) { + self.bits.set_bit(30, val); + } + /// + #[inline] + pub fn hpm31(&self) -> bool { + self.bits.get_bit(31) + } + #[inline] + pub fn set_hpm31(&mut self, val: bool) { + self.bits.set_bit(31, val); + } +} +read_csr_as!(Hcounteren, 3602, __read_hcounteren); +write_csr!(3602, __write_hcounteren); +set!(3602, __set_hcounteren); +clear!(3602, __clear_hcounteren); +// bit ops +set_clear_csr!( + /// + , set_cy, clear_cy, 1 << 0); +set_clear_csr!( + /// + , set_tm, clear_tm, 1 << 1); +set_clear_csr!( + /// + , set_ir, clear_ir, 1 << 2); +set_clear_csr!( + /// + , set_hpm3, clear_hpm3, 1 << 3); +set_clear_csr!( + /// + , set_hpm4, clear_hpm4, 1 << 4); +set_clear_csr!( + /// + , set_hpm5, clear_hpm5, 1 << 5); +set_clear_csr!( + /// + , set_hpm6, clear_hpm6, 1 << 6); +set_clear_csr!( + /// + , set_hpm7, clear_hpm7, 1 << 7); +set_clear_csr!( + /// + , set_hpm8, clear_hpm8, 1 << 8); +set_clear_csr!( + /// + , set_hpm9, clear_hpm9, 1 << 9); +set_clear_csr!( + /// + , set_hpm10, clear_hpm10, 1 << 10); +set_clear_csr!( + /// + , set_hpm11, clear_hpm11, 1 << 11); +set_clear_csr!( + /// + , set_hpm12, clear_hpm12, 1 << 12); +set_clear_csr!( + /// + , set_hpm13, clear_hpm13, 1 << 13); +set_clear_csr!( + /// + , set_hpm14, clear_hpm14, 1 << 14); +set_clear_csr!( + /// + , set_hpm15, clear_hpm15, 1 << 15); +set_clear_csr!( + /// + , set_hpm16, clear_hpm16, 1 << 16); +set_clear_csr!( + /// + , set_hpm17, clear_hpm17, 1 << 17); +set_clear_csr!( + /// + , set_hpm18, clear_hpm18, 1 << 18); +set_clear_csr!( + /// + , set_hpm19, clear_hpm19, 1 << 19); +set_clear_csr!( + /// + , set_hpm20, clear_hpm20, 1 << 20); +set_clear_csr!( + /// + , set_hpm21, clear_hpm21, 1 << 21); +set_clear_csr!( + /// + , set_hpm22, clear_hpm22, 1 << 22); +set_clear_csr!( + /// + , set_hpm23, clear_hpm23, 1 << 23); +set_clear_csr!( + /// + , set_hpm24, clear_hpm24, 1 << 24); +set_clear_csr!( + /// + , set_hpm25, clear_hpm25, 1 << 25); +set_clear_csr!( + /// + , set_hpm26, clear_hpm26, 1 << 26); +set_clear_csr!( + /// + , set_hpm27, clear_hpm27, 1 << 27); +set_clear_csr!( + /// + , set_hpm28, clear_hpm28, 1 << 28); +set_clear_csr!( + /// + , set_hpm29, clear_hpm29, 1 << 29); +set_clear_csr!( + /// + , set_hpm30, clear_hpm30, 1 << 30); +set_clear_csr!( + /// + , set_hpm31, clear_hpm31, 1 << 31); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/hedeleg.rs b/vendor/riscv/src/register/hypervisorx64/hedeleg.rs new file mode 100644 index 00000000..aac1eb93 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hedeleg.rs @@ -0,0 +1,173 @@ +//! Hypervisor Exception Delegation Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hedeleg { + bits: usize, +} +impl Hedeleg { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hedeleg { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Instruction address misaligned + #[inline] + pub fn ex0(&self) -> bool { + self.bits.get_bit(0) + } + #[inline] + pub fn set_ex0(&mut self, val: bool) { + self.bits.set_bit(0, val); + } + /// Instruction access fault + #[inline] + pub fn ex1(&self) -> bool { + self.bits.get_bit(1) + } + #[inline] + pub fn set_ex1(&mut self, val: bool) { + self.bits.set_bit(1, val); + } + /// Illegal instruction + #[inline] + pub fn ex2(&self) -> bool { + self.bits.get_bit(2) + } + #[inline] + pub fn set_ex2(&mut self, val: bool) { + self.bits.set_bit(2, val); + } + /// Breakpoint + #[inline] + pub fn ex3(&self) -> bool { + self.bits.get_bit(3) + } + #[inline] + pub fn set_ex3(&mut self, val: bool) { + self.bits.set_bit(3, val); + } + /// Load address misaligned + #[inline] + pub fn ex4(&self) -> bool { + self.bits.get_bit(4) + } + #[inline] + pub fn set_ex4(&mut self, val: bool) { + self.bits.set_bit(4, val); + } + /// Load access fault + #[inline] + pub fn ex5(&self) -> bool { + self.bits.get_bit(5) + } + #[inline] + pub fn set_ex5(&mut self, val: bool) { + self.bits.set_bit(5, val); + } + /// Store/AMO address misaligned + #[inline] + pub fn ex6(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_ex6(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// Store/AMO access fault + #[inline] + pub fn ex7(&self) -> bool { + self.bits.get_bit(7) + } + #[inline] + pub fn set_ex7(&mut self, val: bool) { + self.bits.set_bit(7, val); + } + /// Environment call from U-mode or VU-mode + #[inline] + pub fn ex8(&self) -> bool { + self.bits.get_bit(8) + } + #[inline] + pub fn set_ex8(&mut self, val: bool) { + self.bits.set_bit(8, val); + } + /// Instruction page fault + #[inline] + pub fn ex12(&self) -> bool { + self.bits.get_bit(12) + } + #[inline] + pub fn set_ex12(&mut self, val: bool) { + self.bits.set_bit(12, val); + } + /// Load page fault + #[inline] + pub fn ex13(&self) -> bool { + self.bits.get_bit(13) + } + #[inline] + pub fn set_ex13(&mut self, val: bool) { + self.bits.set_bit(13, val); + } + /// Store/AMO page fault + #[inline] + pub fn ex15(&self) -> bool { + self.bits.get_bit(15) + } + #[inline] + pub fn set_ex15(&mut self, val: bool) { + self.bits.set_bit(15, val); + } +} +read_csr_as!(Hedeleg, 1538, __read_hedeleg); +write_csr!(1538, __write_hedeleg); +set!(1538, __set_hedeleg); +clear!(1538, __clear_hedeleg); +// bit ops +set_clear_csr!( + ///Instruction address misaligned + , set_ex0, clear_ex0, 1 << 0); +set_clear_csr!( + ///Instruction access fault + , set_ex1, clear_ex1, 1 << 1); +set_clear_csr!( + ///Illegal instruction + , set_ex2, clear_ex2, 1 << 2); +set_clear_csr!( + ///Breakpoint + , set_ex3, clear_ex3, 1 << 3); +set_clear_csr!( + ///Load address misaligned + , set_ex4, clear_ex4, 1 << 4); +set_clear_csr!( + ///Load access fault + , set_ex5, clear_ex5, 1 << 5); +set_clear_csr!( + ///Store/AMO address misaligned + , set_ex6, clear_ex6, 1 << 6); +set_clear_csr!( + ///Store/AMO access fault + , set_ex7, clear_ex7, 1 << 7); +set_clear_csr!( + ///Environment call from U-mode or VU-mode + , set_ex8, clear_ex8, 1 << 8); +set_clear_csr!( + ///Instruction page fault + , set_ex12, clear_ex12, 1 << 12); +set_clear_csr!( + ///Load page fault + , set_ex13, clear_ex13, 1 << 13); +set_clear_csr!( + ///Store/AMO page fault + , set_ex15, clear_ex15, 1 << 15); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/hgatp.rs b/vendor/riscv/src/register/hypervisorx64/hgatp.rs new file mode 100644 index 00000000..798b55c8 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hgatp.rs @@ -0,0 +1,73 @@ +//! Hypervisor Guest Address Translation and Protection Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hgatp { + bits: usize, +} +impl Hgatp { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hgatp { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Guest address translation mode. + #[inline] + pub fn mode(&self) -> HgatpValues { + HgatpValues::from(self.bits.get_bits(60..64)) + } + #[inline] + pub fn set_mode(&mut self, val: HgatpValues) { + self.bits.set_bits(60..64, val as usize); + } + /// Virtual machine ID. + #[inline] + pub fn vmid(&self) -> usize { + self.bits.get_bits(44..58) + } + #[inline] + pub fn set_vmid(&mut self, val: usize) { + self.bits.set_bits(44..58, val); + } + /// Physical Page Number for root page table. + #[inline] + pub fn ppn(&self) -> usize { + self.bits.get_bits(0..44) + } + #[inline] + pub fn set_ppn(&mut self, val: usize) { + self.bits.set_bits(0..44, val); + } +} +read_csr_as!(Hgatp, 1664, __read_hgatp); +write_csr!(1664, __write_hgatp); +set!(1664, __set_hgatp); +clear!(1664, __clear_hgatp); +// bit ops + +// enums +#[derive(Copy, Clone, Debug)] +#[repr(usize)] +pub enum HgatpValues { + Bare = 0, + Sv39x4 = 8, + Sv48x4 = 9, +} +impl HgatpValues { + fn from(x: usize) -> Self { + match x { + 0 => Self::Bare, + 8 => Self::Sv39x4, + 9 => Self::Sv48x4, + _ => unreachable!(), + } + } +} diff --git a/vendor/riscv/src/register/hypervisorx64/hgeie.rs b/vendor/riscv/src/register/hypervisorx64/hgeie.rs new file mode 100644 index 00000000..09d97256 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hgeie.rs @@ -0,0 +1,3 @@ +//! Hypervisor Guest External Interrupt Enable Register. +read_csr_as_usize!(1543, __read_hgeie); +write_csr_as_usize!(1543, __write_hgeie); diff --git a/vendor/riscv/src/register/hypervisorx64/hgeip.rs b/vendor/riscv/src/register/hypervisorx64/hgeip.rs new file mode 100644 index 00000000..75f1cab7 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hgeip.rs @@ -0,0 +1,3 @@ +//! Hypervisor Guest External Interrupt Pending Register. +read_csr_as_usize!(3602, __read_hgeip); +write_csr_as_usize!(3602, __write_hgeip); diff --git a/vendor/riscv/src/register/hypervisorx64/hideleg.rs b/vendor/riscv/src/register/hypervisorx64/hideleg.rs new file mode 100644 index 00000000..f138ab0c --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hideleg.rs @@ -0,0 +1,65 @@ +//! Hypervisor Interrupt Delegation Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hideleg { + bits: usize, +} +impl Hideleg { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hideleg { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Software Interrupt + #[inline] + pub fn sip(&self) -> bool { + self.bits.get_bit(2) + } + #[inline] + pub fn set_sip(&mut self, val: bool) { + self.bits.set_bit(2, val); + } + /// Timer Interrupt + #[inline] + pub fn tip(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_tip(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// External Interrupt + #[inline] + pub fn eip(&self) -> bool { + self.bits.get_bit(10) + } + #[inline] + pub fn set_eip(&mut self, val: bool) { + self.bits.set_bit(10, val); + } +} +read_csr_as!(Hideleg, 1539, __read_hideleg); +write_csr!(1539, __write_hideleg); +set!(1539, __set_hideleg); +clear!(1539, __clear_hideleg); +// bit ops +set_clear_csr!( + ///Software Interrupt + , set_sip, clear_sip, 1 << 2); +set_clear_csr!( + ///Timer Interrupt + , set_tip, clear_tip, 1 << 6); +set_clear_csr!( + ///External Interrupt + , set_eip, clear_eip, 1 << 10); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/hie.rs b/vendor/riscv/src/register/hypervisorx64/hie.rs new file mode 100644 index 00000000..64d1d0c8 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hie.rs @@ -0,0 +1,77 @@ +//! Hypervisor Interrupt Enable Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hie { + bits: usize, +} +impl Hie { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hie { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Software Interrupt + #[inline] + pub fn vssie(&self) -> bool { + self.bits.get_bit(2) + } + #[inline] + pub fn set_vssie(&mut self, val: bool) { + self.bits.set_bit(2, val); + } + /// Timer Interrupt + #[inline] + pub fn vstie(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_vstie(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// External Interrupt + #[inline] + pub fn vseie(&self) -> bool { + self.bits.get_bit(10) + } + #[inline] + pub fn set_vseie(&mut self, val: bool) { + self.bits.set_bit(10, val); + } + /// Guest External Interrupt + #[inline] + pub fn sgeie(&self) -> bool { + self.bits.get_bit(12) + } + #[inline] + pub fn set_sgeie(&mut self, val: bool) { + self.bits.set_bit(12, val); + } +} +read_csr_as!(Hie, 1540, __read_hie); +write_csr!(1540, __write_hie); +set!(1540, __set_hie); +clear!(1540, __clear_hie); +// bit ops +set_clear_csr!( + ///Software Interrupt + , set_vssie, clear_vssie, 1 << 2); +set_clear_csr!( + ///Timer Interrupt + , set_vstie, clear_vstie, 1 << 6); +set_clear_csr!( + ///External Interrupt + , set_vseie, clear_vseie, 1 << 10); +set_clear_csr!( + ///Guest External Interrupt + , set_sgeie, clear_sgeie, 1 << 12); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/hip.rs b/vendor/riscv/src/register/hypervisorx64/hip.rs new file mode 100644 index 00000000..dd297639 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hip.rs @@ -0,0 +1,77 @@ +//! Hypervisor Interrupt Pending Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hip { + bits: usize, +} +impl Hip { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hip { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Software Interrupt + #[inline] + pub fn vssip(&self) -> bool { + self.bits.get_bit(2) + } + #[inline] + pub fn set_vssip(&mut self, val: bool) { + self.bits.set_bit(2, val); + } + /// Timer Interrupt + #[inline] + pub fn vstip(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_vstip(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// External Interrupt + #[inline] + pub fn vseip(&self) -> bool { + self.bits.get_bit(10) + } + #[inline] + pub fn set_vseip(&mut self, val: bool) { + self.bits.set_bit(10, val); + } + /// Guest External Interrupt + #[inline] + pub fn sgeip(&self) -> bool { + self.bits.get_bit(12) + } + #[inline] + pub fn set_sgeip(&mut self, val: bool) { + self.bits.set_bit(12, val); + } +} +read_csr_as!(Hip, 1604, __read_hip); +write_csr!(1604, __write_hip); +set!(1604, __set_hip); +clear!(1604, __clear_hip); +// bit ops +set_clear_csr!( + ///Software Interrupt + , set_vssip, clear_vssip, 1 << 2); +set_clear_csr!( + ///Timer Interrupt + , set_vstip, clear_vstip, 1 << 6); +set_clear_csr!( + ///External Interrupt + , set_vseip, clear_vseip, 1 << 10); +set_clear_csr!( + ///Guest External Interrupt + , set_sgeip, clear_sgeip, 1 << 12); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/hstatus.rs b/vendor/riscv/src/register/hypervisorx64/hstatus.rs new file mode 100644 index 00000000..386c1a68 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hstatus.rs @@ -0,0 +1,160 @@ +//! HStatus Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hstatus { + bits: usize, +} +impl Hstatus { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hstatus { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Effective XLEN for VM. + #[inline] + pub fn vsxl(&self) -> VsxlValues { + VsxlValues::from(self.bits.get_bits(32..34)) + } + #[inline] + pub fn set_vsxl(&mut self, val: VsxlValues) { + self.bits.set_bits(32..34, val as usize); + } + /// TSR for VM. + #[inline] + pub fn vtsr(&self) -> bool { + self.bits.get_bit(22) + } + #[inline] + pub fn set_vtsr(&mut self, val: bool) { + self.bits.set_bit(22, val); + } + /// TW for VM. + #[inline] + pub fn vtw(&self) -> bool { + self.bits.get_bit(21) + } + #[inline] + pub fn set_vtw(&mut self, val: bool) { + self.bits.set_bit(21, val); + } + /// TVM for VM. + #[inline] + pub fn vtvm(&self) -> bool { + self.bits.get_bit(20) + } + #[inline] + pub fn set_vtvm(&mut self, val: bool) { + self.bits.set_bit(20, val); + } + /// Virtual Guest External Interrupt Number. + #[inline] + pub fn vgein(&self) -> usize { + self.bits.get_bits(12..18) + } + #[inline] + pub fn set_vgein(&mut self, val: usize) { + self.bits.set_bits(12..18, val); + } + /// Hypervisor User mode. + #[inline] + pub fn hu(&self) -> bool { + self.bits.get_bit(9) + } + #[inline] + pub fn set_hu(&mut self, val: bool) { + self.bits.set_bit(9, val); + } + /// Supervisor Previous Virtual Privilege. + #[inline] + pub fn spvp(&self) -> bool { + self.bits.get_bit(8) + } + #[inline] + pub fn set_spvp(&mut self, val: bool) { + self.bits.set_bit(8, val); + } + /// Supervisor Previous Virtualization mode. + #[inline] + pub fn spv(&self) -> bool { + self.bits.get_bit(7) + } + #[inline] + pub fn set_spv(&mut self, val: bool) { + self.bits.set_bit(7, val); + } + /// Guest Virtual Address. + #[inline] + pub fn gva(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_gva(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// VS access endianness. + #[inline] + pub fn vsbe(&self) -> bool { + self.bits.get_bit(5) + } + #[inline] + pub fn set_vsbe(&mut self, val: bool) { + self.bits.set_bit(5, val); + } +} +read_csr_as!(Hstatus, 1536, __read_hstatus); +write_csr!(1536, __write_hstatus); +set!(1536, __set_hstatus); +clear!(1536, __clear_hstatus); +// bit ops +set_clear_csr!( + ///TSR for VM. + , set_vtsr, clear_vtsr, 1 << 22); +set_clear_csr!( + ///TW for VM. + , set_vtw, clear_vtw, 1 << 21); +set_clear_csr!( + ///TVM for VM. + , set_vtvm, clear_vtvm, 1 << 20); +set_clear_csr!( + ///Hypervisor User mode. + , set_hu, clear_hu, 1 << 9); +set_clear_csr!( + ///Supervisor Previous Virtual Privilege. + , set_spvp, clear_spvp, 1 << 8); +set_clear_csr!( + ///Supervisor Previous Virtualization mode. + , set_spv, clear_spv, 1 << 7); +set_clear_csr!( + ///Guest Virtual Address. + , set_gva, clear_gva, 1 << 6); +set_clear_csr!( + ///VS access endianness. + , set_vsbe, clear_vsbe, 1 << 5); + +// enums +#[derive(Copy, Clone, Debug)] +#[repr(usize)] +pub enum VsxlValues { + Vsxl32 = 1, + Vsxl64 = 2, + Vsxl128 = 3, +} +impl VsxlValues { + fn from(x: usize) -> Self { + match x { + 1 => Self::Vsxl32, + 2 => Self::Vsxl64, + 3 => Self::Vsxl128, + _ => unreachable!(), + } + } +} diff --git a/vendor/riscv/src/register/hypervisorx64/htimedelta.rs b/vendor/riscv/src/register/hypervisorx64/htimedelta.rs new file mode 100644 index 00000000..c3874b22 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/htimedelta.rs @@ -0,0 +1,4 @@ +//! Hypervisor Time Delta Register. +read_composite_csr!(super::htimedeltah::read(), read()); +read_csr_as_usize!(1541, __read_htimedelta); +write_csr_as_usize!(1541, __write_htimedelta); diff --git a/vendor/riscv/src/register/hypervisorx64/htimedeltah.rs b/vendor/riscv/src/register/hypervisorx64/htimedeltah.rs new file mode 100644 index 00000000..8d7d2188 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/htimedeltah.rs @@ -0,0 +1,3 @@ +//! Hypervisor Time Delta Register. +read_csr_as_usize!(1557, __read_htimedeltah); +write_csr_as_usize!(1557, __write_htimedeltah); diff --git a/vendor/riscv/src/register/hypervisorx64/htinst.rs b/vendor/riscv/src/register/hypervisorx64/htinst.rs new file mode 100644 index 00000000..0619ccb5 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/htinst.rs @@ -0,0 +1,3 @@ +//! Hypervisor Trap Instruction Register. +read_csr_as_usize!(1610, __read_htinst); +write_csr_as_usize!(1610, __write_htinst); diff --git a/vendor/riscv/src/register/hypervisorx64/htval.rs b/vendor/riscv/src/register/hypervisorx64/htval.rs new file mode 100644 index 00000000..89694e4c --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/htval.rs @@ -0,0 +1,3 @@ +//! Hypervisor Trap Value Register. +read_csr_as_usize!(1603, __read_htval); +write_csr_as_usize!(1603, __write_htval); diff --git a/vendor/riscv/src/register/hypervisorx64/hvip.rs b/vendor/riscv/src/register/hypervisorx64/hvip.rs new file mode 100644 index 00000000..fcfb8c31 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/hvip.rs @@ -0,0 +1,65 @@ +//! Hypervisor Virtual Interrupt Pending Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Hvip { + bits: usize, +} +impl Hvip { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Hvip { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Software Interrupt + #[inline] + pub fn vssip(&self) -> bool { + self.bits.get_bit(2) + } + #[inline] + pub fn set_vssip(&mut self, val: bool) { + self.bits.set_bit(2, val); + } + /// Timer Interrupt + #[inline] + pub fn vstip(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_vstip(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// External Interrupt + #[inline] + pub fn vseip(&self) -> bool { + self.bits.get_bit(10) + } + #[inline] + pub fn set_vseip(&mut self, val: bool) { + self.bits.set_bit(10, val); + } +} +read_csr_as!(Hvip, 1605, __read_hvip); +write_csr!(1605, __write_hvip); +set!(1605, __set_hvip); +clear!(1605, __clear_hvip); +// bit ops +set_clear_csr!( + ///Software Interrupt + , set_vssip, clear_vssip, 1 << 2); +set_clear_csr!( + ///Timer Interrupt + , set_vstip, clear_vstip, 1 << 6); +set_clear_csr!( + ///External Interrupt + , set_vseip, clear_vseip, 1 << 10); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/mod.rs b/vendor/riscv/src/register/hypervisorx64/mod.rs new file mode 100644 index 00000000..06706b12 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/mod.rs @@ -0,0 +1,23 @@ +pub mod hcounteren; +pub mod hedeleg; +pub mod hgatp; +pub mod hgeie; +pub mod hgeip; +pub mod hideleg; +pub mod hie; +pub mod hip; +pub mod hstatus; +pub mod htimedelta; +pub mod htimedeltah; +pub mod htinst; +pub mod htval; +pub mod hvip; +pub mod vsatp; +pub mod vscause; +pub mod vsepc; +pub mod vsie; +pub mod vsip; +pub mod vsscratch; +pub mod vsstatus; +pub mod vstval; +pub mod vstvec; diff --git a/vendor/riscv/src/register/hypervisorx64/vsatp.rs b/vendor/riscv/src/register/hypervisorx64/vsatp.rs new file mode 100644 index 00000000..0781acce --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vsatp.rs @@ -0,0 +1,73 @@ +//! Virtual Supervisor Guest Address Translation and Protection Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Vsatp { + bits: usize, +} +impl Vsatp { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Vsatp { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Guest address translation mode. + #[inline] + pub fn mode(&self) -> HgatpValues { + HgatpValues::from(self.bits.get_bits(60..64)) + } + #[inline] + pub fn set_mode(&mut self, val: HgatpValues) { + self.bits.set_bits(60..64, val as usize); + } + /// ASID. + #[inline] + pub fn asid(&self) -> usize { + self.bits.get_bits(44..60) + } + #[inline] + pub fn set_asid(&mut self, val: usize) { + self.bits.set_bits(44..60, val); + } + /// Physical Page Number for root page table. + #[inline] + pub fn ppn(&self) -> usize { + self.bits.get_bits(0..44) + } + #[inline] + pub fn set_ppn(&mut self, val: usize) { + self.bits.set_bits(0..44, val); + } +} +read_csr_as!(Vsatp, 640, __read_vsatp); +write_csr!(640, __write_vsatp); +set!(640, __set_vsatp); +clear!(640, __clear_vsatp); +// bit ops + +// enums +#[derive(Copy, Clone, Debug)] +#[repr(usize)] +pub enum HgatpValues { + Bare = 0, + Sv39x4 = 8, + Sv48x4 = 9, +} +impl HgatpValues { + fn from(x: usize) -> Self { + match x { + 0 => Self::Bare, + 8 => Self::Sv39x4, + 9 => Self::Sv48x4, + _ => unreachable!(), + } + } +} diff --git a/vendor/riscv/src/register/hypervisorx64/vscause.rs b/vendor/riscv/src/register/hypervisorx64/vscause.rs new file mode 100644 index 00000000..cab545f8 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vscause.rs @@ -0,0 +1,50 @@ +//! Virtual Supervisor Cause Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Vscause { + bits: usize, +} +impl Vscause { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Vscause { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Is cause interrupt. + #[inline] + pub fn interrupt(&self) -> bool { + self.bits.get_bit(63) + } + #[inline] + pub fn set_interrupt(&mut self, val: bool) { + self.bits.set_bit(63, val); + } + /// Exception code + #[inline] + pub fn code(&self) -> usize { + self.bits.get_bits(0..63) + } + #[inline] + pub fn set_code(&mut self, val: usize) { + self.bits.set_bits(0..63, val); + } +} +read_csr_as!(Vscause, 578, __read_vscause); +write_csr!(578, __write_vscause); +set!(578, __set_vscause); +clear!(578, __clear_vscause); +// bit ops +set_clear_csr!( + ///Is cause interrupt. + , set_interrupt, clear_interrupt, 1 << 63); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/vsepc.rs b/vendor/riscv/src/register/hypervisorx64/vsepc.rs new file mode 100644 index 00000000..c5b56707 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vsepc.rs @@ -0,0 +1,3 @@ +//! Virtual Supervisor Exception Program Counter. +read_csr_as_usize!(577, __read_vsepc); +write_csr_as_usize!(577, __write_vsepc); diff --git a/vendor/riscv/src/register/hypervisorx64/vsie.rs b/vendor/riscv/src/register/hypervisorx64/vsie.rs new file mode 100644 index 00000000..7a8891d8 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vsie.rs @@ -0,0 +1,65 @@ +//! Virtual Supevisor Interrupt Enable Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Vsie { + bits: usize, +} +impl Vsie { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Vsie { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Software Interrupt + #[inline] + pub fn ssie(&self) -> bool { + self.bits.get_bit(1) + } + #[inline] + pub fn set_ssie(&mut self, val: bool) { + self.bits.set_bit(1, val); + } + /// Timer Interrupt + #[inline] + pub fn stie(&self) -> bool { + self.bits.get_bit(5) + } + #[inline] + pub fn set_stie(&mut self, val: bool) { + self.bits.set_bit(5, val); + } + /// External Interrupt + #[inline] + pub fn seie(&self) -> bool { + self.bits.get_bit(9) + } + #[inline] + pub fn set_seie(&mut self, val: bool) { + self.bits.set_bit(9, val); + } +} +read_csr_as!(Vsie, 516, __read_vsie); +write_csr!(516, __write_vsie); +set!(516, __set_vsie); +clear!(516, __clear_vsie); +// bit ops +set_clear_csr!( + ///Software Interrupt + , set_ssie, clear_ssie, 1 << 1); +set_clear_csr!( + ///Timer Interrupt + , set_stie, clear_stie, 1 << 5); +set_clear_csr!( + ///External Interrupt + , set_seie, clear_seie, 1 << 9); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/vsip.rs b/vendor/riscv/src/register/hypervisorx64/vsip.rs new file mode 100644 index 00000000..0989e798 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vsip.rs @@ -0,0 +1,65 @@ +//! Virtual Supevisor Interrupt Pending Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Vsip { + bits: usize, +} +impl Vsip { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Vsip { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// Software Interrupt + #[inline] + pub fn ssip(&self) -> bool { + self.bits.get_bit(1) + } + #[inline] + pub fn set_ssip(&mut self, val: bool) { + self.bits.set_bit(1, val); + } + /// Timer Interrupt + #[inline] + pub fn stip(&self) -> bool { + self.bits.get_bit(5) + } + #[inline] + pub fn set_stip(&mut self, val: bool) { + self.bits.set_bit(5, val); + } + /// External Interrupt + #[inline] + pub fn seip(&self) -> bool { + self.bits.get_bit(9) + } + #[inline] + pub fn set_seip(&mut self, val: bool) { + self.bits.set_bit(9, val); + } +} +read_csr_as!(Vsip, 580, __read_vsip); +write_csr!(580, __write_vsip); +set!(580, __set_vsip); +clear!(580, __clear_vsip); +// bit ops +set_clear_csr!( + ///Software Interrupt + , set_ssip, clear_ssip, 1 << 1); +set_clear_csr!( + ///Timer Interrupt + , set_stip, clear_stip, 1 << 5); +set_clear_csr!( + ///External Interrupt + , set_seip, clear_seip, 1 << 9); + +// enums diff --git a/vendor/riscv/src/register/hypervisorx64/vsscratch.rs b/vendor/riscv/src/register/hypervisorx64/vsscratch.rs new file mode 100644 index 00000000..e297575f --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vsscratch.rs @@ -0,0 +1,3 @@ +//! Virtual Supervisor Scratch Register. +read_csr_as_usize!(576, __read_vsscratch); +write_csr_as_usize!(576, __write_vsscratch); diff --git a/vendor/riscv/src/register/hypervisorx64/vsstatus.rs b/vendor/riscv/src/register/hypervisorx64/vsstatus.rs new file mode 100644 index 00000000..c2585b87 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vsstatus.rs @@ -0,0 +1,154 @@ +//! Hypervisor Guest External Interrupt Pending Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Vsstatus { + bits: usize, +} +impl Vsstatus { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Vsstatus { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// + #[inline] + pub fn sd(&self) -> usize { + self.bits.get_bits(60..64) + } + #[inline] + pub fn set_sd(&mut self, val: usize) { + self.bits.set_bits(60..64, val); + } + /// Effective User XLEN. + #[inline] + pub fn uxl(&self) -> UxlValues { + UxlValues::from(self.bits.get_bits(32..34)) + } + #[inline] + pub fn set_uxl(&mut self, val: UxlValues) { + self.bits.set_bits(32..34, val as usize); + } + /// + #[inline] + pub fn mxr(&self) -> bool { + self.bits.get_bit(19) + } + #[inline] + pub fn set_mxr(&mut self, val: bool) { + self.bits.set_bit(19, val); + } + /// + #[inline] + pub fn sum(&self) -> bool { + self.bits.get_bit(18) + } + #[inline] + pub fn set_sum(&mut self, val: bool) { + self.bits.set_bit(18, val); + } + /// + #[inline] + pub fn xs(&self) -> usize { + self.bits.get_bits(15..17) + } + #[inline] + pub fn set_xs(&mut self, val: usize) { + self.bits.set_bits(15..17, val); + } + /// + #[inline] + pub fn fs(&self) -> usize { + self.bits.get_bits(13..15) + } + #[inline] + pub fn set_fs(&mut self, val: usize) { + self.bits.set_bits(13..15, val); + } + /// + #[inline] + pub fn spp(&self) -> bool { + self.bits.get_bit(8) + } + #[inline] + pub fn set_spp(&mut self, val: bool) { + self.bits.set_bit(8, val); + } + /// + #[inline] + pub fn ube(&self) -> bool { + self.bits.get_bit(6) + } + #[inline] + pub fn set_ube(&mut self, val: bool) { + self.bits.set_bit(6, val); + } + /// + #[inline] + pub fn spie(&self) -> bool { + self.bits.get_bit(5) + } + #[inline] + pub fn set_spie(&mut self, val: bool) { + self.bits.set_bit(5, val); + } + /// + #[inline] + pub fn sie(&self) -> bool { + self.bits.get_bit(1) + } + #[inline] + pub fn set_sie(&mut self, val: bool) { + self.bits.set_bit(1, val); + } +} +read_csr_as!(Vsstatus, 512, __read_vsstatus); +write_csr!(512, __write_vsstatus); +set!(512, __set_vsstatus); +clear!(512, __clear_vsstatus); +// bit ops +set_clear_csr!( + /// + , set_mxr, clear_mxr, 1 << 19); +set_clear_csr!( + /// + , set_sum, clear_sum, 1 << 18); +set_clear_csr!( + /// + , set_spp, clear_spp, 1 << 8); +set_clear_csr!( + /// + , set_ube, clear_ube, 1 << 6); +set_clear_csr!( + /// + , set_spie, clear_spie, 1 << 5); +set_clear_csr!( + /// + , set_sie, clear_sie, 1 << 1); + +// enums +#[derive(Copy, Clone, Debug)] +#[repr(usize)] +pub enum UxlValues { + Uxl32 = 1, + Uxl64 = 2, + Uxl128 = 3, +} +impl UxlValues { + fn from(x: usize) -> Self { + match x { + 1 => Self::Uxl32, + 2 => Self::Uxl64, + 3 => Self::Uxl128, + _ => unreachable!(), + } + } +} diff --git a/vendor/riscv/src/register/hypervisorx64/vstval.rs b/vendor/riscv/src/register/hypervisorx64/vstval.rs new file mode 100644 index 00000000..95866469 --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vstval.rs @@ -0,0 +1,3 @@ +//! Virtual Supervisor Trap Value Register. +read_csr_as_usize!(579, __read_vstval); +write_csr_as_usize!(579, __write_vstval); diff --git a/vendor/riscv/src/register/hypervisorx64/vstvec.rs b/vendor/riscv/src/register/hypervisorx64/vstvec.rs new file mode 100644 index 00000000..adece54c --- /dev/null +++ b/vendor/riscv/src/register/hypervisorx64/vstvec.rs @@ -0,0 +1,47 @@ +//! Virtual Supervisor Trap Vector Base Address Register. + +use bit_field::BitField; + +#[derive(Copy, Clone, Debug)] +pub struct Vstvec { + bits: usize, +} +impl Vstvec { + #[inline] + pub fn bits(&self) -> usize { + return self.bits; + } + #[inline] + pub fn from_bits(x: usize) -> Self { + return Vstvec { bits: x }; + } + #[inline] + pub unsafe fn write(&self) { + _write(self.bits); + } + /// + #[inline] + pub fn base(&self) -> usize { + self.bits.get_bits(2..64) + } + #[inline] + pub fn set_base(&mut self, val: usize) { + self.bits.set_bits(2..64, val); + } + /// + #[inline] + pub fn mode(&self) -> usize { + self.bits.get_bits(0..2) + } + #[inline] + pub fn set_mode(&mut self, val: usize) { + self.bits.set_bits(0..2, val); + } +} +read_csr_as!(Vstvec, 517, __read_vstvec); +write_csr!(517, __write_vstvec); +set!(517, __set_vstvec); +clear!(517, __clear_vstvec); +// bit ops + +// enums diff --git a/vendor/riscv/src/register/macros.rs b/vendor/riscv/src/register/macros.rs new file mode 100644 index 00000000..543f7092 --- /dev/null +++ b/vendor/riscv/src/register/macros.rs @@ -0,0 +1,273 @@ +macro_rules! read_csr { + ($csr_number:expr, $asm_fn: ident) => { + /// Reads the CSR + #[inline] + unsafe fn _read() -> usize { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => { + let r: usize; + core::arch::asm!("csrrs {0}, {1}, x0", out(reg) r, const $csr_number); + r + } + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn() -> usize; + } + + $asm_fn() + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + }; +} + +macro_rules! read_csr_rv32 { + ($csr_number:expr, $asm_fn: ident) => { + /// Reads the CSR + #[inline] + unsafe fn _read() -> usize { + match () { + #[cfg(all(riscv32, feature = "inline-asm"))] + () => { + let r: usize; + core::arch::asm!("csrrs {0}, {1}, x0", out(reg) r, const $csr_number); + r + } + + #[cfg(all(riscv32, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn() -> usize; + } + + $asm_fn() + } + + #[cfg(not(riscv32))] + () => unimplemented!(), + } + } + }; +} + +macro_rules! read_csr_as { + ($register:ident, $csr_number:expr, $asm_fn: ident) => { + read_csr!($csr_number, $asm_fn); + + /// Reads the CSR + #[inline] + pub fn read() -> $register { + $register { + bits: unsafe { _read() }, + } + } + }; +} + +macro_rules! read_csr_as_usize { + ($csr_number:expr, $asm_fn: ident) => { + read_csr!($csr_number, $asm_fn); + + /// Reads the CSR + #[inline] + pub fn read() -> usize { + unsafe { _read() } + } + }; +} + +macro_rules! read_csr_as_usize_rv32 { + ($csr_number:expr, $asm_fn: ident) => { + read_csr_rv32!($csr_number, $asm_fn); + + /// Reads the CSR + #[inline] + pub fn read() -> usize { + unsafe { _read() } + } + }; +} + +macro_rules! write_csr { + ($csr_number:expr, $asm_fn: ident) => { + /// Writes the CSR + #[inline] + #[allow(unused_variables)] + unsafe fn _write(bits: usize) { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => core::arch::asm!("csrrw x0, {1}, {0}", in(reg) bits, const $csr_number), + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(bits: usize); + } + + $asm_fn(bits); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + }; +} + +macro_rules! write_csr_rv32 { + ($csr_number:expr, $asm_fn: ident) => { + /// Writes the CSR + #[inline] + #[allow(unused_variables)] + unsafe fn _write(bits: usize) { + match () { + #[cfg(all(riscv32, feature = "inline-asm"))] + () => core::arch::asm!("csrrw x0, {1}, {0}", in(reg) bits, const $csr_number), + + #[cfg(all(riscv32, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(bits: usize); + } + + $asm_fn(bits); + } + + #[cfg(not(riscv32))] + () => unimplemented!(), + } + } + }; +} + +macro_rules! write_csr_as_usize { + ($csr_number:expr, $asm_fn: ident) => { + write_csr!($csr_number, $asm_fn); + + /// Writes the CSR + #[inline] + pub fn write(bits: usize) { + unsafe { _write(bits) } + } + }; +} + +macro_rules! write_csr_as_usize_rv32 { + ($csr_number:expr, $asm_fn: ident) => { + write_csr_rv32!($csr_number, $asm_fn); + + /// Writes the CSR + #[inline] + pub fn write(bits: usize) { + unsafe { _write(bits) } + } + }; +} + +macro_rules! set { + ($csr_number:expr, $asm_fn: ident) => { + /// Set the CSR + #[inline] + #[allow(unused_variables)] + unsafe fn _set(bits: usize) { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => core::arch::asm!("csrrs x0, {1}, {0}", in(reg) bits, const $csr_number), + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(bits: usize); + } + + $asm_fn(bits); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + }; +} + +macro_rules! clear { + ($csr_number:expr, $asm_fn: ident) => { + /// Clear the CSR + #[inline] + #[allow(unused_variables)] + unsafe fn _clear(bits: usize) { + match () { + #[cfg(all(riscv, feature = "inline-asm"))] + () => core::arch::asm!("csrrc x0, {1}, {0}", in(reg) bits, const $csr_number), + + #[cfg(all(riscv, not(feature = "inline-asm")))] + () => { + extern "C" { + fn $asm_fn(bits: usize); + } + + $asm_fn(bits); + } + + #[cfg(not(riscv))] + () => unimplemented!(), + } + } + }; +} + +macro_rules! set_csr { + ($(#[$attr:meta])*, $set_field:ident, $e:expr) => { + $(#[$attr])* + #[inline] + pub unsafe fn $set_field() { + _set($e); + } + }; +} + +macro_rules! clear_csr { + ($(#[$attr:meta])*, $clear_field:ident, $e:expr) => { + $(#[$attr])* + #[inline] + pub unsafe fn $clear_field() { + _clear($e); + } + }; +} + +macro_rules! set_clear_csr { + ($(#[$attr:meta])*, $set_field:ident, $clear_field:ident, $e:expr) => { + set_csr!($(#[$attr])*, $set_field, $e); + clear_csr!($(#[$attr])*, $clear_field, $e); + } +} + +macro_rules! read_composite_csr { + ($hi:expr, $lo:expr) => { + /// Reads the CSR as a 64-bit value + #[inline] + pub fn read64() -> u64 { + match () { + #[cfg(riscv32)] + () => loop { + let hi = $hi; + let lo = $lo; + if hi == $hi { + return ((hi as u64) << 32) | lo as u64; + } + }, + + #[cfg(not(riscv32))] + () => $lo as u64, + } + } + }; +} + diff --git a/vendor/riscv/src/register/marchid.rs b/vendor/riscv/src/register/marchid.rs new file mode 100644 index 00000000..a5e3fb9c --- /dev/null +++ b/vendor/riscv/src/register/marchid.rs @@ -0,0 +1,27 @@ +//! marchid register + +use core::num::NonZeroUsize; + +/// marchid register +#[derive(Clone, Copy, Debug)] +pub struct Marchid { + bits: NonZeroUsize, +} + +impl Marchid { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits.get() + } +} + +read_csr!(0xF11, __read_marchid); + +/// Reads the CSR +#[inline] +pub fn read() -> Option { + let r = unsafe { _read() }; + // When marchid is hardwired to zero it means that the marchid + // csr isn't implemented. + NonZeroUsize::new(r).map(|bits| Marchid { bits }) +} diff --git a/vendor/riscv/src/register/mcause.rs b/vendor/riscv/src/register/mcause.rs new file mode 100644 index 00000000..068266b2 --- /dev/null +++ b/vendor/riscv/src/register/mcause.rs @@ -0,0 +1,138 @@ +//! mcause register + +/// mcause register +#[derive(Clone, Copy, Debug)] +pub struct Mcause { + bits: usize, +} + +/// Trap Cause +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Trap { + Interrupt(Interrupt), + Exception(Exception), +} + +/// Interrupt +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Interrupt { + UserSoft, + SupervisorSoft, + MachineSoft, + UserTimer, + SupervisorTimer, + MachineTimer, + UserExternal, + SupervisorExternal, + MachineExternal, + Unknown, +} + +/// Exception +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Exception { + InstructionMisaligned, + InstructionFault, + IllegalInstruction, + Breakpoint, + LoadMisaligned, + LoadFault, + StoreMisaligned, + StoreFault, + UserEnvCall, + SupervisorEnvCall, + MachineEnvCall, + InstructionPageFault, + LoadPageFault, + StorePageFault, + Unknown, +} + +impl Interrupt { + pub fn from(nr: usize) -> Self { + match nr { + 0 => Interrupt::UserSoft, + 1 => Interrupt::SupervisorSoft, + 3 => Interrupt::MachineSoft, + 4 => Interrupt::UserTimer, + 5 => Interrupt::SupervisorTimer, + 7 => Interrupt::MachineTimer, + 8 => Interrupt::UserExternal, + 9 => Interrupt::SupervisorExternal, + 11 => Interrupt::MachineExternal, + _ => Interrupt::Unknown, + } + } +} + +impl Exception { + pub fn from(nr: usize) -> Self { + match nr { + 0 => Exception::InstructionMisaligned, + 1 => Exception::InstructionFault, + 2 => Exception::IllegalInstruction, + 3 => Exception::Breakpoint, + 4 => Exception::LoadMisaligned, + 5 => Exception::LoadFault, + 6 => Exception::StoreMisaligned, + 7 => Exception::StoreFault, + 8 => Exception::UserEnvCall, + 9 => Exception::SupervisorEnvCall, + 11 => Exception::MachineEnvCall, + 12 => Exception::InstructionPageFault, + 13 => Exception::LoadPageFault, + 15 => Exception::StorePageFault, + _ => Exception::Unknown, + } + } +} +impl Mcause { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// Returns the code field + pub fn code(&self) -> usize { + match () { + #[cfg(target_pointer_width = "32")] + () => self.bits & !(1 << 31), + #[cfg(target_pointer_width = "64")] + () => self.bits & !(1 << 63), + #[cfg(target_pointer_width = "128")] + () => self.bits & !(1 << 127), + } + } + + /// Trap Cause + #[inline] + pub fn cause(&self) -> Trap { + if self.is_interrupt() { + Trap::Interrupt(Interrupt::from(self.code())) + } else { + Trap::Exception(Exception::from(self.code())) + } + } + + /// Is trap cause an interrupt. + #[inline] + pub fn is_interrupt(&self) -> bool { + match () { + #[cfg(target_pointer_width = "32")] + () => self.bits & (1 << 31) == 1 << 31, + #[cfg(target_pointer_width = "64")] + () => self.bits & (1 << 63) == 1 << 63, + #[cfg(target_pointer_width = "128")] + () => self.bits & (1 << 127) == 1 << 127, + } + } + + /// Is trap cause an exception. + #[inline] + pub fn is_exception(&self) -> bool { + !self.is_interrupt() + } +} + +read_csr_as!(Mcause, 0x342, __read_mcause); diff --git a/vendor/riscv/src/register/mcycle.rs b/vendor/riscv/src/register/mcycle.rs new file mode 100644 index 00000000..95d172b2 --- /dev/null +++ b/vendor/riscv/src/register/mcycle.rs @@ -0,0 +1,4 @@ +//! mcycle register + +read_csr_as_usize!(0xB00, __read_mcycle); +read_composite_csr!(super::mcycleh::read(), read()); diff --git a/vendor/riscv/src/register/mcycleh.rs b/vendor/riscv/src/register/mcycleh.rs new file mode 100644 index 00000000..784dca4f --- /dev/null +++ b/vendor/riscv/src/register/mcycleh.rs @@ -0,0 +1,3 @@ +//! mcycleh register + +read_csr_as_usize_rv32!(0xB80, __read_mcycleh); diff --git a/vendor/riscv/src/register/medeleg.rs b/vendor/riscv/src/register/medeleg.rs new file mode 100644 index 00000000..6b86641d --- /dev/null +++ b/vendor/riscv/src/register/medeleg.rs @@ -0,0 +1,148 @@ +//! medeleg register + +use bit_field::BitField; + +/// medeleg register +#[derive(Clone, Copy, Debug)] +pub struct Medeleg { + bits: usize, +} + +impl Medeleg { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// Instruction Address Misaligned Delegate + #[inline] + pub fn instruction_misaligned(&self) -> bool { + self.bits.get_bit(0) + } + + /// Instruction Access Fault Delegate + #[inline] + pub fn instruction_fault(&self) -> bool { + self.bits.get_bit(1) + } + + /// Illegal Instruction Delegate + #[inline] + pub fn illegal_instruction(&self) -> bool { + self.bits.get_bit(2) + } + + /// Breakpoint Delegate + #[inline] + pub fn breakpoint(&self) -> bool { + self.bits.get_bit(3) + } + + /// Load Address Misaligned Delegate + #[inline] + pub fn load_misaligned(&self) -> bool { + self.bits.get_bit(4) + } + + /// Load Access Fault Delegate + #[inline] + pub fn load_fault(&self) -> bool { + self.bits.get_bit(5) + } + + /// Store/AMO Address Misaligned Delegate + #[inline] + pub fn store_misaligned(&self) -> bool { + self.bits.get_bit(6) + } + + /// Store/AMO Access Fault Delegate + #[inline] + pub fn store_fault(&self) -> bool { + self.bits.get_bit(7) + } + + /// Environment Call from U-mode Delegate + #[inline] + pub fn user_env_call(&self) -> bool { + self.bits.get_bit(8) + } + + /// Environment Call from S-mode Delegate + #[inline] + pub fn supervisor_env_call(&self) -> bool { + self.bits.get_bit(9) + } + + /// Environment Call from M-mode Delegate + #[inline] + pub fn machine_env_call(&self) -> bool { + self.bits.get_bit(11) + } + + /// Instruction Page Fault Delegate + #[inline] + pub fn instruction_page_fault(&self) -> bool { + self.bits.get_bit(12) + } + + /// Load Page Fault Delegate + #[inline] + pub fn load_page_fault(&self) -> bool { + self.bits.get_bit(13) + } + + /// Store/AMO Page Fault Delegate + #[inline] + pub fn store_page_fault(&self) -> bool { + self.bits.get_bit(15) + } +} + +read_csr_as!(Medeleg, 0x302, __read_medeleg); +set!(0x302, __set_medeleg); +clear!(0x302, __clear_medeleg); + +set_clear_csr!( + /// Instruction Address Misaligned Delegate + , set_instruction_misaligned, clear_instruction_misaligned, 1 << 0); +set_clear_csr!( + /// Instruction Access Fault Delegate + , set_instruction_fault, clear_instruction_fault, 1 << 1); +set_clear_csr!( + /// Illegal Instruction Delegate + , set_illegal_instruction, clear_illegal_instruction, 1 << 2); +set_clear_csr!( + /// Breakpoint Delegate + , set_breakpoint, clear_breakpoint, 1 << 3); +set_clear_csr!( + /// Load Address Misaligned Delegate + , set_load_misaligned, clear_load_misaligned, 1 << 4); +set_clear_csr!( + /// Load Access Fault Delegate + , set_load_fault, clear_load_fault, 1 << 5); +set_clear_csr!( + /// Store/AMO Address Misaligned Delegate + , set_store_misaligned, clear_store_misaligned, 1 << 6); +set_clear_csr!( + /// Store/AMO Access fault + , set_store_fault, clear_store_fault, 1 << 7); +set_clear_csr!( + /// Environment Call from U-mode Delegate + , set_user_env_call, clear_user_env_call, 1 << 8); +set_clear_csr!( + /// Environment Call from S-mode Delegate + , set_supervisor_env_call, clear_supervisor_env_call, 1 << 9); +set_clear_csr!( + /// Environment Call from M-mode Delegate + , set_machine_env_call, clear_machine_env_call, 1 << 11); +set_clear_csr!( + /// Instruction Page Fault Delegate + , set_instruction_page_fault, clear_instruction_page_fault, 1 << 12); +set_clear_csr!( + /// Load Page Fault Delegate + , set_load_page_fault, clear_load_page_fault, 1 << 13); +set_clear_csr!( + /// Store/AMO Page Fault Delegate + , set_store_page_fault, clear_store_page_fault, 1 << 15); diff --git a/vendor/riscv/src/register/mepc.rs b/vendor/riscv/src/register/mepc.rs new file mode 100644 index 00000000..160dff56 --- /dev/null +++ b/vendor/riscv/src/register/mepc.rs @@ -0,0 +1,4 @@ +//! mepc register + +read_csr_as_usize!(0x341, __read_mepc); +write_csr_as_usize!(0x341, __write_mepc); diff --git a/vendor/riscv/src/register/mhartid.rs b/vendor/riscv/src/register/mhartid.rs new file mode 100644 index 00000000..39603882 --- /dev/null +++ b/vendor/riscv/src/register/mhartid.rs @@ -0,0 +1,3 @@ +//! mhartid register + +read_csr_as_usize!(0xf14, __read_mhartid); diff --git a/vendor/riscv/src/register/mhpmcounterx.rs b/vendor/riscv/src/register/mhpmcounterx.rs new file mode 100644 index 00000000..df3b6c42 --- /dev/null +++ b/vendor/riscv/src/register/mhpmcounterx.rs @@ -0,0 +1,84 @@ +macro_rules! reg { + ( + $addr:expr, $csrl:ident, $csrh:ident, $readf:ident, $writef:ident + ) => { + /// Machine performance-monitoring counter + pub mod $csrl { + read_csr_as_usize!($addr, $readf); + write_csr_as_usize!($addr, $writef); + read_composite_csr!(super::$csrh::read(), read()); + } + } +} + +macro_rules! regh { + ( + $addr:expr, $csrh:ident, $readf:ident, $writef:ident + ) => { + /// Upper 32 bits of machine performance-monitoring counter (RV32I only) + pub mod $csrh { + read_csr_as_usize_rv32!($addr, $readf); + write_csr_as_usize_rv32!($addr, $writef); + } + } +} + +reg!(0xB03, mhpmcounter3, mhpmcounter3h, __read_mhpmcounter3, __write_mhpmcounter3); +reg!(0xB04, mhpmcounter4, mhpmcounter4h, __read_mhpmcounter4, __write_mhpmcounter4); +reg!(0xB05, mhpmcounter5, mhpmcounter5h, __read_mhpmcounter5, __write_mhpmcounter5); +reg!(0xB06, mhpmcounter6, mhpmcounter6h, __read_mhpmcounter6, __write_mhpmcounter6); +reg!(0xB07, mhpmcounter7, mhpmcounter7h, __read_mhpmcounter7, __write_mhpmcounter7); +reg!(0xB08, mhpmcounter8, mhpmcounter8h, __read_mhpmcounter8, __write_mhpmcounter8); +reg!(0xB09, mhpmcounter9, mhpmcounter9h, __read_mhpmcounter9, __write_mhpmcounter9); +reg!(0xB0A, mhpmcounter10, mhpmcounter10h, __read_mhpmcounter10, __write_mhpmcounter10); +reg!(0xB0B, mhpmcounter11, mhpmcounter11h, __read_mhpmcounter11, __write_mhpmcounter11); +reg!(0xB0C, mhpmcounter12, mhpmcounter12h, __read_mhpmcounter12, __write_mhpmcounter12); +reg!(0xB0D, mhpmcounter13, mhpmcounter13h, __read_mhpmcounter13, __write_mhpmcounter13); +reg!(0xB0E, mhpmcounter14, mhpmcounter14h, __read_mhpmcounter14, __write_mhpmcounter14); +reg!(0xB0F, mhpmcounter15, mhpmcounter15h, __read_mhpmcounter15, __write_mhpmcounter15); +reg!(0xB10, mhpmcounter16, mhpmcounter16h, __read_mhpmcounter16, __write_mhpmcounter16); +reg!(0xB11, mhpmcounter17, mhpmcounter17h, __read_mhpmcounter17, __write_mhpmcounter17); +reg!(0xB12, mhpmcounter18, mhpmcounter18h, __read_mhpmcounter18, __write_mhpmcounter18); +reg!(0xB13, mhpmcounter19, mhpmcounter19h, __read_mhpmcounter19, __write_mhpmcounter19); +reg!(0xB14, mhpmcounter20, mhpmcounter20h, __read_mhpmcounter20, __write_mhpmcounter20); +reg!(0xB15, mhpmcounter21, mhpmcounter21h, __read_mhpmcounter21, __write_mhpmcounter21); +reg!(0xB16, mhpmcounter22, mhpmcounter22h, __read_mhpmcounter22, __write_mhpmcounter22); +reg!(0xB17, mhpmcounter23, mhpmcounter23h, __read_mhpmcounter23, __write_mhpmcounter23); +reg!(0xB18, mhpmcounter24, mhpmcounter24h, __read_mhpmcounter24, __write_mhpmcounter24); +reg!(0xB19, mhpmcounter25, mhpmcounter25h, __read_mhpmcounter25, __write_mhpmcounter25); +reg!(0xB1A, mhpmcounter26, mhpmcounter26h, __read_mhpmcounter26, __write_mhpmcounter26); +reg!(0xB1B, mhpmcounter27, mhpmcounter27h, __read_mhpmcounter27, __write_mhpmcounter27); +reg!(0xB1C, mhpmcounter28, mhpmcounter28h, __read_mhpmcounter28, __write_mhpmcounter28); +reg!(0xB1D, mhpmcounter29, mhpmcounter29h, __read_mhpmcounter29, __write_mhpmcounter29); +reg!(0xB1E, mhpmcounter30, mhpmcounter30h, __read_mhpmcounter30, __write_mhpmcounter30); +reg!(0xB1F, mhpmcounter31, mhpmcounter31h, __read_mhpmcounter31, __write_mhpmcounter31); + +regh!(0xB83, mhpmcounter3h, __read_mhpmcounter3h, __write_mhpmcounter3h); +regh!(0xB84, mhpmcounter4h, __read_mhpmcounter4h, __write_mhpmcounter4h); +regh!(0xB85, mhpmcounter5h, __read_mhpmcounter5h, __write_mhpmcounter5h); +regh!(0xB86, mhpmcounter6h, __read_mhpmcounter6h, __write_mhpmcounter6h); +regh!(0xB87, mhpmcounter7h, __read_mhpmcounter7h, __write_mhpmcounter7h); +regh!(0xB88, mhpmcounter8h, __read_mhpmcounter8h, __write_mhpmcounter8h); +regh!(0xB89, mhpmcounter9h, __read_mhpmcounter9h, __write_mhpmcounter9h); +regh!(0xB8A, mhpmcounter10h, __read_mhpmcounter10h, __write_mhpmcounter10h); +regh!(0xB8B, mhpmcounter11h, __read_mhpmcounter11h, __write_mhpmcounter11h); +regh!(0xB8C, mhpmcounter12h, __read_mhpmcounter12h, __write_mhpmcounter12h); +regh!(0xB8D, mhpmcounter13h, __read_mhpmcounter13h, __write_mhpmcounter13h); +regh!(0xB8E, mhpmcounter14h, __read_mhpmcounter14h, __write_mhpmcounter14h); +regh!(0xB8F, mhpmcounter15h, __read_mhpmcounter15h, __write_mhpmcounter15h); +regh!(0xB90, mhpmcounter16h, __read_mhpmcounter16h, __write_mhpmcounter16h); +regh!(0xB91, mhpmcounter17h, __read_mhpmcounter17h, __write_mhpmcounter17h); +regh!(0xB92, mhpmcounter18h, __read_mhpmcounter18h, __write_mhpmcounter18h); +regh!(0xB93, mhpmcounter19h, __read_mhpmcounter19h, __write_mhpmcounter19h); +regh!(0xB94, mhpmcounter20h, __read_mhpmcounter20h, __write_mhpmcounter20h); +regh!(0xB95, mhpmcounter21h, __read_mhpmcounter21h, __write_mhpmcounter21h); +regh!(0xB96, mhpmcounter22h, __read_mhpmcounter22h, __write_mhpmcounter22h); +regh!(0xB97, mhpmcounter23h, __read_mhpmcounter23h, __write_mhpmcounter23h); +regh!(0xB98, mhpmcounter24h, __read_mhpmcounter24h, __write_mhpmcounter24h); +regh!(0xB99, mhpmcounter25h, __read_mhpmcounter25h, __write_mhpmcounter25h); +regh!(0xB9A, mhpmcounter26h, __read_mhpmcounter26h, __write_mhpmcounter26h); +regh!(0xB9B, mhpmcounter27h, __read_mhpmcounter27h, __write_mhpmcounter27h); +regh!(0xB9C, mhpmcounter28h, __read_mhpmcounter28h, __write_mhpmcounter28h); +regh!(0xB9D, mhpmcounter29h, __read_mhpmcounter29h, __write_mhpmcounter29h); +regh!(0xB9E, mhpmcounter30h, __read_mhpmcounter30h, __write_mhpmcounter30h); +regh!(0xB9F, mhpmcounter31h, __read_mhpmcounter31h, __write_mhpmcounter31h); diff --git a/vendor/riscv/src/register/mhpmeventx.rs b/vendor/riscv/src/register/mhpmeventx.rs new file mode 100644 index 00000000..db7d4b0f --- /dev/null +++ b/vendor/riscv/src/register/mhpmeventx.rs @@ -0,0 +1,41 @@ +macro_rules! reg { + ( + $addr:expr, $csr:ident, $readf:ident, $writef:ident + ) => { + /// Machine performance-monitoring event selector + pub mod $csr { + read_csr_as_usize!($addr, $readf); + write_csr_as_usize!($addr, $writef); + } + }; +} + +reg!(0x323, mhpmevent3, __read_mhpmevent3, __write_mhpmevent3); +reg!(0x324, mhpmevent4, __read_mhpmevent4, __write_mhpmevent4); +reg!(0x325, mhpmevent5, __read_mhpmevent5, __write_mhpmevent5); +reg!(0x326, mhpmevent6, __read_mhpmevent6, __write_mhpmevent6); +reg!(0x327, mhpmevent7, __read_mhpmevent7, __write_mhpmevent7); +reg!(0x328, mhpmevent8, __read_mhpmevent8, __write_mhpmevent8); +reg!(0x329, mhpmevent9, __read_mhpmevent9, __write_mhpmevent9); +reg!(0x32A, mhpmevent10, __read_mhpmevent10, __write_mhpmevent10); +reg!(0x32B, mhpmevent11, __read_mhpmevent11, __write_mhpmevent11); +reg!(0x32C, mhpmevent12, __read_mhpmevent12, __write_mhpmevent12); +reg!(0x32D, mhpmevent13, __read_mhpmevent13, __write_mhpmevent13); +reg!(0x32E, mhpmevent14, __read_mhpmevent14, __write_mhpmevent14); +reg!(0x32F, mhpmevent15, __read_mhpmevent15, __write_mhpmevent15); +reg!(0x330, mhpmevent16, __read_mhpmevent16, __write_mhpmevent16); +reg!(0x331, mhpmevent17, __read_mhpmevent17, __write_mhpmevent17); +reg!(0x332, mhpmevent18, __read_mhpmevent18, __write_mhpmevent18); +reg!(0x333, mhpmevent19, __read_mhpmevent19, __write_mhpmevent19); +reg!(0x334, mhpmevent20, __read_mhpmevent20, __write_mhpmevent20); +reg!(0x335, mhpmevent21, __read_mhpmevent21, __write_mhpmevent21); +reg!(0x336, mhpmevent22, __read_mhpmevent22, __write_mhpmevent22); +reg!(0x337, mhpmevent23, __read_mhpmevent23, __write_mhpmevent23); +reg!(0x338, mhpmevent24, __read_mhpmevent24, __write_mhpmevent24); +reg!(0x339, mhpmevent25, __read_mhpmevent25, __write_mhpmevent25); +reg!(0x33A, mhpmevent26, __read_mhpmevent26, __write_mhpmevent26); +reg!(0x33B, mhpmevent27, __read_mhpmevent27, __write_mhpmevent27); +reg!(0x33C, mhpmevent28, __read_mhpmevent28, __write_mhpmevent28); +reg!(0x33D, mhpmevent29, __read_mhpmevent29, __write_mhpmevent29); +reg!(0x33E, mhpmevent30, __read_mhpmevent30, __write_mhpmevent30); +reg!(0x33F, mhpmevent31, __read_mhpmevent31, __write_mhpmevent31); diff --git a/vendor/riscv/src/register/mideleg.rs b/vendor/riscv/src/register/mideleg.rs new file mode 100644 index 00000000..207c18eb --- /dev/null +++ b/vendor/riscv/src/register/mideleg.rs @@ -0,0 +1,76 @@ +//! mideleg register + +use bit_field::BitField; + +/// mideleg register +#[derive(Clone, Copy, Debug)] +pub struct Mideleg { + bits: usize, +} + +impl Mideleg { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Delegate + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Software Interrupt Delegate + #[inline] + pub fn ssoft(&self) -> bool { + self.bits.get_bit(1) + } + + /// User Timer Interrupt Delegate + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Timer Interrupt Delegate + #[inline] + pub fn stimer(&self) -> bool { + self.bits.get_bit(5) + } + + /// User External Interrupt Delegate + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } + + /// Supervisor External Interrupt Delegate + #[inline] + pub fn sext(&self) -> bool { + self.bits.get_bit(9) + } +} + +read_csr_as!(Mideleg, 0x303, __read_mideleg); +set!(0x303, __set_mideleg); +clear!(0x303, __clear_mideleg); + +set_clear_csr!( + /// User Software Interrupt Delegate + , set_usoft, clear_usoft, 1 << 0); +set_clear_csr!( + /// Supervisor Software Interrupt Delegate + , set_ssoft, clear_ssoft, 1 << 1); +set_clear_csr!( + /// User Timer Interrupt Delegate + , set_utimer, clear_utimer, 1 << 4); +set_clear_csr!( + /// Supervisor Timer Interrupt Delegate + , set_stimer, clear_stimer, 1 << 5); +set_clear_csr!( + /// User External Interrupt Delegate + , set_uext, clear_uext, 1 << 8); +set_clear_csr!( + /// Supervisor External Interrupt Delegate + , set_sext, clear_sext, 1 << 9); diff --git a/vendor/riscv/src/register/mie.rs b/vendor/riscv/src/register/mie.rs new file mode 100644 index 00000000..121a5b5b --- /dev/null +++ b/vendor/riscv/src/register/mie.rs @@ -0,0 +1,103 @@ +//! mie register + +use bit_field::BitField; + +/// mie register +#[derive(Clone, Copy, Debug)] +pub struct Mie { + bits: usize, +} + +impl Mie { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Enable + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Software Interrupt Enable + #[inline] + pub fn ssoft(&self) -> bool { + self.bits.get_bit(1) + } + + /// Machine Software Interrupt Enable + #[inline] + pub fn msoft(&self) -> bool { + self.bits.get_bit(3) + } + + /// User Timer Interrupt Enable + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Timer Interrupt Enable + #[inline] + pub fn stimer(&self) -> bool { + self.bits.get_bit(5) + } + + /// Machine Timer Interrupt Enable + #[inline] + pub fn mtimer(&self) -> bool { + self.bits.get_bit(7) + } + + /// User External Interrupt Enable + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } + + /// Supervisor External Interrupt Enable + #[inline] + pub fn sext(&self) -> bool { + self.bits.get_bit(9) + } + + /// Machine External Interrupt Enable + #[inline] + pub fn mext(&self) -> bool { + self.bits.get_bit(11) + } +} + +read_csr_as!(Mie, 0x304, __read_mie); +set!(0x304, __set_mie); +clear!(0x304, __clear_mie); + +set_clear_csr!( + /// User Software Interrupt Enable + , set_usoft, clear_usoft, 1 << 0); +set_clear_csr!( + /// Supervisor Software Interrupt Enable + , set_ssoft, clear_ssoft, 1 << 1); +set_clear_csr!( + /// Machine Software Interrupt Enable + , set_msoft, clear_msoft, 1 << 3); +set_clear_csr!( + /// User Timer Interrupt Enable + , set_utimer, clear_utimer, 1 << 4); +set_clear_csr!( + /// Supervisor Timer Interrupt Enable + , set_stimer, clear_stimer, 1 << 5); +set_clear_csr!( + /// Machine Timer Interrupt Enable + , set_mtimer, clear_mtimer, 1 << 7); +set_clear_csr!( + /// User External Interrupt Enable + , set_uext, clear_uext, 1 << 8); +set_clear_csr!( + /// Supervisor External Interrupt Enable + , set_sext, clear_sext, 1 << 9); +set_clear_csr!( + /// Machine External Interrupt Enable + , set_mext, clear_mext, 1 << 11); diff --git a/vendor/riscv/src/register/mimpid.rs b/vendor/riscv/src/register/mimpid.rs new file mode 100644 index 00000000..39555a87 --- /dev/null +++ b/vendor/riscv/src/register/mimpid.rs @@ -0,0 +1,27 @@ +//! mimpid register + +use core::num::NonZeroUsize; + +/// mimpid register +#[derive(Clone, Copy, Debug)] +pub struct Mimpid { + bits: NonZeroUsize, +} + +impl Mimpid { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits.get() + } +} + +read_csr!(0xF11, __read_mimpid); + +/// Reads the CSR +#[inline] +pub fn read() -> Option { + let r = unsafe { _read() }; + // When mimpid is hardwired to zero it means that the mimpid + // csr isn't implemented. + NonZeroUsize::new(r).map(|bits| Mimpid { bits }) +} diff --git a/vendor/riscv/src/register/minstret.rs b/vendor/riscv/src/register/minstret.rs new file mode 100644 index 00000000..d553dd8f --- /dev/null +++ b/vendor/riscv/src/register/minstret.rs @@ -0,0 +1,4 @@ +//! minstret register + +read_csr_as_usize!(0xB02, __read_minstret); +read_composite_csr!(super::minstreth::read(), read()); diff --git a/vendor/riscv/src/register/minstreth.rs b/vendor/riscv/src/register/minstreth.rs new file mode 100644 index 00000000..56bc54ef --- /dev/null +++ b/vendor/riscv/src/register/minstreth.rs @@ -0,0 +1,3 @@ +//! minstreth register + +read_csr_as_usize_rv32!(0xB82, __read_minstreth); diff --git a/vendor/riscv/src/register/mip.rs b/vendor/riscv/src/register/mip.rs new file mode 100644 index 00000000..1a4cf82c --- /dev/null +++ b/vendor/riscv/src/register/mip.rs @@ -0,0 +1,100 @@ +//! mip register + +use bit_field::BitField; + +/// mip register +#[derive(Clone, Copy, Debug)] +pub struct Mip { + bits: usize, +} + +impl Mip { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Pending + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Software Interrupt Pending + #[inline] + pub fn ssoft(&self) -> bool { + self.bits.get_bit(1) + } + + /// Machine Software Interrupt Pending + #[inline] + pub fn msoft(&self) -> bool { + self.bits.get_bit(3) + } + + /// User Timer Interrupt Pending + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Timer Interrupt Pending + #[inline] + pub fn stimer(&self) -> bool { + self.bits.get_bit(5) + } + + /// Machine Timer Interrupt Pending + #[inline] + pub fn mtimer(&self) -> bool { + self.bits.get_bit(7) + } + + /// User External Interrupt Pending + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } + + /// Supervisor External Interrupt Pending + #[inline] + pub fn sext(&self) -> bool { + self.bits.get_bit(9) + } + + /// Machine External Interrupt Pending + #[inline] + pub fn mext(&self) -> bool { + self.bits.get_bit(11) + } +} + +read_csr_as!(Mip, 0x344, __read_mip); +set!(0x344, __set_mip); +clear!(0x344, __clear_mip); + +set_clear_csr!( + /// User Software Interrupt Pending + , set_usoft, clear_usoft, 1 << 0); +set_clear_csr!( + /// Supervisor Software Interrupt Pending + , set_ssoft, clear_ssoft, 1 << 1); +set_clear_csr!( + /// Machine Software Interrupt Pending + , set_msoft, clear_msoft, 1 << 3); +set_clear_csr!( + /// User Timer Interrupt Pending + , set_utimer, clear_utimer, 1 << 4); +set_clear_csr!( + /// Supervisor Timer Interrupt Pending + , set_stimer, clear_stimer, 1 << 5); +set_clear_csr!( + /// Machine Timer Interrupt Pending + , set_mtimer, clear_mtimer, 1 << 7); +set_clear_csr!( + /// User External Interrupt Pending + , set_uext, clear_uext, 1 << 8); +set_clear_csr!( + /// Supervisor External Interrupt Pending + , set_sext, clear_sext, 1 << 9); diff --git a/vendor/riscv/src/register/misa.rs b/vendor/riscv/src/register/misa.rs new file mode 100644 index 00000000..6c3d860e --- /dev/null +++ b/vendor/riscv/src/register/misa.rs @@ -0,0 +1,60 @@ +//! misa register + +use core::num::NonZeroUsize; + +/// misa register +#[derive(Clone, Copy, Debug)] +pub struct Misa { + bits: NonZeroUsize, +} + +/// Machine XLEN +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MXL { + XLEN32, + XLEN64, + XLEN128, +} + +impl Misa { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits.get() + } + + /// Returns the machine xlen. + pub fn mxl(&self) -> MXL { + let value = match () { + #[cfg(target_pointer_width = "32")] + () => (self.bits() >> 30) as u8, + #[cfg(target_pointer_width = "64")] + () => (self.bits() >> 62) as u8, + }; + match value { + 1 => MXL::XLEN32, + 2 => MXL::XLEN64, + 3 => MXL::XLEN128, + _ => unreachable!(), + } + } + + /// Returns true when the atomic extension is implemented. + pub fn has_extension(&self, extension: char) -> bool { + let bit = extension as u8 - 65; + if bit > 25 { + return false; + } + self.bits() & (1 << bit) == (1 << bit) + } +} + +read_csr!(0x301, __read_misa); + +/// Reads the CSR +#[inline] +pub fn read() -> Option { + let r = unsafe { _read() }; + // When misa is hardwired to zero it means that the misa csr + // isn't implemented. + NonZeroUsize::new(r).map(|bits| Misa { bits }) +} diff --git a/vendor/riscv/src/register/mod.rs b/vendor/riscv/src/register/mod.rs new file mode 100644 index 00000000..d0804c10 --- /dev/null +++ b/vendor/riscv/src/register/mod.rs @@ -0,0 +1,104 @@ +//! RISC-V CSR's +//! +//! The following registers are not available on 64-bit implementations. +//! +//! - cycleh +//! - timeh +//! - instreth +//! - hpmcounter[3-31]h +//! - mcycleh +//! - minstreth +//! - mhpmcounter[3-31]h + +#[macro_use] +mod macros; + +// User Trap Setup +pub mod uie; +pub mod ustatus; +pub mod utvec; + +// User Trap Handling +pub mod ucause; +pub mod uepc; +pub mod uip; +pub mod uscratch; +pub mod utval; + +// User Floating-Point CSRs +// TODO: frm, fflags +pub mod fcsr; + +// User Counter/Timers +// TODO: cycle[h], instret[h] +pub mod time; +#[rustfmt::skip] // long macro use +mod hpmcounterx; +pub use self::hpmcounterx::*; +pub mod timeh; + +// Supervisor Trap Setup +// TODO: sedeleg, sideleg +pub mod sie; +pub mod sstatus; +pub mod stvec; +// TODO: scounteren + +// Supervisor Trap Handling +pub mod scause; +pub mod sepc; +pub mod sip; +pub mod sscratch; +pub mod stval; + +// Supervisor Protection and Translation +pub mod satp; + +// Machine Information Registers +pub mod marchid; +pub mod mhartid; +pub mod mimpid; +pub mod mvendorid; + +// Machine Trap Setup +pub mod medeleg; +pub mod mideleg; +pub mod mie; +pub mod misa; +pub mod mstatus; +pub mod mtvec; +// TODO: mcounteren + +// Machine Trap Handling +pub mod mcause; +pub mod mepc; +pub mod mip; +pub mod mscratch; +pub mod mtval; + +// Machine Protection and Translation +mod pmpcfgx; +pub use self::pmpcfgx::*; +mod pmpaddrx; +pub use self::pmpaddrx::*; + +// Machine Counter/Timers +pub mod mcycle; +#[rustfmt::skip] // long macro use +mod mhpmcounterx; +pub mod minstret; +pub use self::mhpmcounterx::*; +pub mod mcycleh; +pub mod minstreth; + +// Machine Counter Setup +mod mhpmeventx; +pub use self::mhpmeventx::*; + +// TODO: Debug/Trace Registers (shared with Debug Mode) + +// TODO: Debug Mode Registers + +// Hypervisor Extension Registers +mod hypervisorx64; +pub use self::hypervisorx64::*; diff --git a/vendor/riscv/src/register/mscratch.rs b/vendor/riscv/src/register/mscratch.rs new file mode 100644 index 00000000..c5ef9fec --- /dev/null +++ b/vendor/riscv/src/register/mscratch.rs @@ -0,0 +1,4 @@ +//! mscratch register + +read_csr_as_usize!(0x340, __read_mscratch); +write_csr_as_usize!(0x340, __write_mscratch); diff --git a/vendor/riscv/src/register/mstatus.rs b/vendor/riscv/src/register/mstatus.rs new file mode 100644 index 00000000..97b7b713 --- /dev/null +++ b/vendor/riscv/src/register/mstatus.rs @@ -0,0 +1,214 @@ +//! mstatus register +// TODO: Virtualization, Memory Privilege and Extension Context Fields + +use bit_field::BitField; +use core::mem::size_of; + +/// mstatus register +#[derive(Clone, Copy, Debug)] +pub struct Mstatus { + bits: usize, +} + +/// Additional extension state +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum XS { + /// All off + AllOff = 0, + + /// None dirty or clean, some on + NoneDirtyOrClean = 1, + + /// None dirty, some clean + NoneDirtySomeClean = 2, + + /// Some dirty + SomeDirty = 3, +} + +/// Floating-point extension state +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FS { + Off = 0, + Initial = 1, + Clean = 2, + Dirty = 3, +} + +/// Machine Previous Privilege Mode +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MPP { + Machine = 3, + Supervisor = 1, + User = 0, +} + +/// Supervisor Previous Privilege Mode +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SPP { + Supervisor = 1, + User = 0, +} + +impl Mstatus { + /// User Interrupt Enable + #[inline] + pub fn uie(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Interrupt Enable + #[inline] + pub fn sie(&self) -> bool { + self.bits.get_bit(1) + } + + /// Machine Interrupt Enable + #[inline] + pub fn mie(&self) -> bool { + self.bits.get_bit(3) + } + + /// User Previous Interrupt Enable + #[inline] + pub fn upie(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Previous Interrupt Enable + #[inline] + pub fn spie(&self) -> bool { + self.bits.get_bit(5) + } + + /// Machine Previous Interrupt Enable + #[inline] + pub fn mpie(&self) -> bool { + self.bits.get_bit(7) + } + + /// Supervisor Previous Privilege Mode + #[inline] + pub fn spp(&self) -> SPP { + match self.bits.get_bit(8) { + true => SPP::Supervisor, + false => SPP::User, + } + } + + /// Machine Previous Privilege Mode + #[inline] + pub fn mpp(&self) -> MPP { + match self.bits.get_bits(11..13) { + 0b00 => MPP::User, + 0b01 => MPP::Supervisor, + 0b11 => MPP::Machine, + _ => unreachable!(), + } + } + + #[inline] + pub fn set_mpie(&mut self, val: bool) { + self.bits.set_bit(7, val); + } + + #[inline] + pub fn set_mie(&mut self, val: bool) { + self.bits.set_bit(3, val); + } + + #[inline] + pub fn set_mpp(&mut self, val: MPP) { + self.bits.set_bits(11..13, val as usize); + } + + /// Floating-point extension state + /// + /// Encodes the status of the floating-point unit, + /// including the CSR `fcsr` and floating-point data registers `f0–f31`. + #[inline] + pub fn fs(&self) -> FS { + match self.bits.get_bits(13..15) { + 0b00 => FS::Off, + 0b01 => FS::Initial, + 0b10 => FS::Clean, + 0b11 => FS::Dirty, + _ => unreachable!(), + } + } + + /// Additional extension state + /// + /// Encodes the status of additional user-mode extensions and associated state. + #[inline] + pub fn xs(&self) -> XS { + match self.bits.get_bits(15..17) { + 0b00 => XS::AllOff, + 0b01 => XS::NoneDirtyOrClean, + 0b10 => XS::NoneDirtySomeClean, + 0b11 => XS::SomeDirty, + _ => unreachable!(), + } + } + + /// Whether either the FS field or XS field + /// signals the presence of some dirty state + #[inline] + pub fn sd(&self) -> bool { + self.bits.get_bit(size_of::() * 8 - 1) + } +} + +read_csr_as!(Mstatus, 0x300, __read_mstatus); +write_csr!(0x300, __write_mstatus); +set!(0x300, __set_mstatus); +clear!(0x300, __clear_mstatus); + +set_clear_csr!( + /// User Interrupt Enable + , set_uie, clear_uie, 1 << 0); + +set_clear_csr!( + /// Supervisor Interrupt Enable + , set_sie, clear_sie, 1 << 1); + +set_clear_csr!( + /// Machine Interrupt Enable + , set_mie, clear_mie, 1 << 3); + +set_csr!( + /// User Previous Interrupt Enable + , set_upie, 1 << 4); + +set_csr!( + /// Supervisor Previous Interrupt Enable + , set_spie, 1 << 5); + +set_csr!( + /// Machine Previous Interrupt Enable + , set_mpie, 1 << 7); + +/// Supervisor Previous Privilege Mode +#[inline] +pub unsafe fn set_spp(spp: SPP) { + match spp { + SPP::Supervisor => _set(1 << 8), + SPP::User => _clear(1 << 8), + } +} + +/// Machine Previous Privilege Mode +#[inline] +pub unsafe fn set_mpp(mpp: MPP) { + let mut value = _read(); + value.set_bits(11..13, mpp as usize); + _write(value); +} + +/// Floating-point extension state +#[inline] +pub unsafe fn set_fs(fs: FS) { + let mut value = _read(); + value.set_bits(13..15, fs as usize); + _write(value); +} diff --git a/vendor/riscv/src/register/mtval.rs b/vendor/riscv/src/register/mtval.rs new file mode 100644 index 00000000..2afb7cb6 --- /dev/null +++ b/vendor/riscv/src/register/mtval.rs @@ -0,0 +1,3 @@ +//! mtval register + +read_csr_as_usize!(0x343, __read_mtval); diff --git a/vendor/riscv/src/register/mtvec.rs b/vendor/riscv/src/register/mtvec.rs new file mode 100644 index 00000000..6a069d1c --- /dev/null +++ b/vendor/riscv/src/register/mtvec.rs @@ -0,0 +1,47 @@ +//! mtvec register + +/// mtvec register +#[derive(Clone, Copy, Debug)] +pub struct Mtvec { + bits: usize, +} + +/// Trap mode +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TrapMode { + Direct = 0, + Vectored = 1, +} + +impl Mtvec { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits + } + + /// Returns the trap-vector base-address + pub fn address(&self) -> usize { + self.bits - (self.bits & 0b11) + } + + /// Returns the trap-vector mode + pub fn trap_mode(&self) -> Option { + let mode = self.bits & 0b11; + match mode { + 0 => Some(TrapMode::Direct), + 1 => Some(TrapMode::Vectored), + _ => None, + } + } +} + +read_csr_as!(Mtvec, 0x305, __read_mtvec); + +write_csr!(0x305, __write_mtvec); + +/// Writes the CSR +#[inline] +pub unsafe fn write(addr: usize, mode: TrapMode) { + let bits = addr + mode as usize; + _write(bits); +} diff --git a/vendor/riscv/src/register/mvendorid.rs b/vendor/riscv/src/register/mvendorid.rs new file mode 100644 index 00000000..9b99d478 --- /dev/null +++ b/vendor/riscv/src/register/mvendorid.rs @@ -0,0 +1,32 @@ +//! mvendorid register + +use core::num::NonZeroUsize; + +/// mvendorid register +#[derive(Clone, Copy, Debug)] +pub struct Mvendorid { + bits: NonZeroUsize, +} + +impl Mvendorid { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits.get() + } + + /// Returns the JEDEC manufacturer ID + pub fn jedec_manufacturer(&self) -> usize { + self.bits() >> 7 + } +} + +read_csr!(0xF11, __read_mvendorid); + +/// Reads the CSR +#[inline] +pub fn read() -> Option { + let r = unsafe { _read() }; + // When mvendorid is hardwired to zero it means that the mvendorid + // csr isn't implemented. + NonZeroUsize::new(r).map(|bits| Mvendorid { bits }) +} diff --git a/vendor/riscv/src/register/pmpaddrx.rs b/vendor/riscv/src/register/pmpaddrx.rs new file mode 100644 index 00000000..cfa43fcb --- /dev/null +++ b/vendor/riscv/src/register/pmpaddrx.rs @@ -0,0 +1,28 @@ +macro_rules! reg { + ( + $addr:expr, $csr:ident, $readf:ident, $writef:ident + ) => { + /// Physical memory protection address register + pub mod $csr { + read_csr_as_usize!($addr, $readf); + write_csr_as_usize!($addr, $writef); + } + }; +} + +reg!(0x3B0, pmpaddr0, __read_pmpaddr0, __write_pmpaddr0); +reg!(0x3B1, pmpaddr1, __read_pmpaddr1, __write_pmpaddr1); +reg!(0x3B2, pmpaddr2, __read_pmpaddr2, __write_pmpaddr2); +reg!(0x3B3, pmpaddr3, __read_pmpaddr3, __write_pmpaddr3); +reg!(0x3B4, pmpaddr4, __read_pmpaddr4, __write_pmpaddr4); +reg!(0x3B5, pmpaddr5, __read_pmpaddr5, __write_pmpaddr5); +reg!(0x3B6, pmpaddr6, __read_pmpaddr6, __write_pmpaddr6); +reg!(0x3B7, pmpaddr7, __read_pmpaddr7, __write_pmpaddr7); +reg!(0x3B8, pmpaddr8, __read_pmpaddr8, __write_pmpaddr8); +reg!(0x3B9, pmpaddr9, __read_pmpaddr9, __write_pmpaddr9); +reg!(0x3BA, pmpaddr10, __read_pmpaddr10, __write_pmpaddr10); +reg!(0x3BB, pmpaddr11, __read_pmpaddr11, __write_pmpaddr11); +reg!(0x3BC, pmpaddr12, __read_pmpaddr12, __write_pmpaddr12); +reg!(0x3BD, pmpaddr13, __read_pmpaddr13, __write_pmpaddr13); +reg!(0x3BE, pmpaddr14, __read_pmpaddr14, __write_pmpaddr14); +reg!(0x3BF, pmpaddr15, __read_pmpaddr15, __write_pmpaddr15); diff --git a/vendor/riscv/src/register/pmpcfgx.rs b/vendor/riscv/src/register/pmpcfgx.rs new file mode 100644 index 00000000..ec27251c --- /dev/null +++ b/vendor/riscv/src/register/pmpcfgx.rs @@ -0,0 +1,23 @@ +/// Physical memory protection configuration +pub mod pmpcfg0 { + read_csr_as_usize!(0x3A0, __read_pmpcfg0); + write_csr_as_usize!(0x3A0, __write_pmpcfg0); +} + +/// Physical memory protection configuration, RV32 only +pub mod pmpcfg1 { + read_csr_as_usize_rv32!(0x3A1, __read_pmpcfg1); + write_csr_as_usize_rv32!(0x3A1, __write_pmpcfg1); +} + +/// Physical memory protection configuration +pub mod pmpcfg2 { + read_csr_as_usize!(0x3A2, __read_pmpcfg2); + write_csr_as_usize!(0x3A2, __write_pmpcfg2); +} + +/// Physical memory protection configuration, RV32 only +pub mod pmpcfg3 { + read_csr_as_usize_rv32!(0x3A3, __read_pmpcfg3); + write_csr_as_usize_rv32!(0x3A3, __write_pmpcfg3); +} diff --git a/vendor/riscv/src/register/satp.rs b/vendor/riscv/src/register/satp.rs new file mode 100644 index 00000000..481c8ca7 --- /dev/null +++ b/vendor/riscv/src/register/satp.rs @@ -0,0 +1,119 @@ +//! satp register + +#[cfg(riscv)] +use addr::Frame; +#[cfg(riscv)] +use bit_field::BitField; + +/// satp register +#[derive(Clone, Copy, Debug)] +pub struct Satp { + bits: usize, +} + +impl Satp { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// Current address-translation scheme + #[inline] + #[cfg(riscv32)] + pub fn mode(&self) -> Mode { + match self.bits.get_bit(31) { + false => Mode::Bare, + true => Mode::Sv32, + } + } + + /// Current address-translation scheme + #[inline] + #[cfg(riscv64)] + pub fn mode(&self) -> Mode { + match self.bits.get_bits(60..64) { + 0 => Mode::Bare, + 8 => Mode::Sv39, + 9 => Mode::Sv48, + 10 => Mode::Sv57, + 11 => Mode::Sv64, + _ => unreachable!(), + } + } + + /// Address space identifier + #[inline] + #[cfg(riscv32)] + pub fn asid(&self) -> usize { + self.bits.get_bits(22..31) + } + + /// Address space identifier + #[inline] + #[cfg(riscv64)] + pub fn asid(&self) -> usize { + self.bits.get_bits(44..60) + } + + /// Physical page number + #[inline] + #[cfg(riscv32)] + pub fn ppn(&self) -> usize { + self.bits.get_bits(0..22) + } + + /// Physical page number + #[inline] + #[cfg(riscv64)] + pub fn ppn(&self) -> usize { + self.bits.get_bits(0..44) + } + + /// Physical frame + #[inline] + #[cfg(riscv)] + pub fn frame(&self) -> Frame { + Frame::of_ppn(self.ppn()) + } +} + +#[cfg(riscv32)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Mode { + Bare = 0, + Sv32 = 1, +} + +#[cfg(riscv64)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Mode { + Bare = 0, + Sv39 = 8, + Sv48 = 9, + Sv57 = 10, + Sv64 = 11, +} + +read_csr_as!(Satp, 0x180, __read_satp); +write_csr_as_usize!(0x180, __write_satp); + +#[inline] +#[cfg(riscv32)] +pub unsafe fn set(mode: Mode, asid: usize, ppn: usize) { + let mut bits = 0usize; + bits.set_bits(31..32, mode as usize); + bits.set_bits(22..31, asid); + bits.set_bits(0..22, ppn); + _write(bits); +} + +#[inline] +#[cfg(riscv64)] +pub unsafe fn set(mode: Mode, asid: usize, ppn: usize) { + let mut bits = 0usize; + bits.set_bits(60..64, mode as usize); + bits.set_bits(44..60, asid); + bits.set_bits(0..44, ppn); + _write(bits); +} diff --git a/vendor/riscv/src/register/scause.rs b/vendor/riscv/src/register/scause.rs new file mode 100644 index 00000000..5117c15d --- /dev/null +++ b/vendor/riscv/src/register/scause.rs @@ -0,0 +1,133 @@ +//! scause register + +use bit_field::BitField; +use core::mem::size_of; + +/// scause register +#[derive(Clone, Copy)] +pub struct Scause { + bits: usize, +} + +/// Trap Cause +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Trap { + Interrupt(Interrupt), + Exception(Exception), +} + +/// Interrupt +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Interrupt { + UserSoft, + VirtualSupervisorSoft, + SupervisorSoft, + UserTimer, + VirtualSupervisorTimer, + SupervisorTimer, + UserExternal, + VirtualSupervisorExternal, + SupervisorExternal, + Unknown, +} + +/// Exception +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Exception { + InstructionMisaligned, + InstructionFault, + IllegalInstruction, + Breakpoint, + LoadFault, + StoreMisaligned, + StoreFault, + UserEnvCall, + VirtualSupervisorEnvCall, + InstructionPageFault, + LoadPageFault, + StorePageFault, + InstructionGuestPageFault, + LoadGuestPageFault, + VirtualInstruction, + StoreGuestPageFault, + Unknown, +} + +impl Interrupt { + pub fn from(nr: usize) -> Self { + match nr { + 0 => Interrupt::UserSoft, + 1 => Interrupt::SupervisorSoft, + 2 => Interrupt::VirtualSupervisorSoft, + 4 => Interrupt::UserTimer, + 5 => Interrupt::SupervisorTimer, + 6 => Interrupt::VirtualSupervisorTimer, + 8 => Interrupt::UserExternal, + 9 => Interrupt::SupervisorExternal, + 10 => Interrupt::VirtualSupervisorExternal, + _ => Interrupt::Unknown, + } + } +} + +impl Exception { + pub fn from(nr: usize) -> Self { + match nr { + 0 => Exception::InstructionMisaligned, + 1 => Exception::InstructionFault, + 2 => Exception::IllegalInstruction, + 3 => Exception::Breakpoint, + 5 => Exception::LoadFault, + 6 => Exception::StoreMisaligned, + 7 => Exception::StoreFault, + 8 => Exception::UserEnvCall, + 10 => Exception::VirtualSupervisorEnvCall, + 12 => Exception::InstructionPageFault, + 13 => Exception::LoadPageFault, + 15 => Exception::StorePageFault, + 20 => Exception::InstructionGuestPageFault, + 21 => Exception::LoadGuestPageFault, + 22 => Exception::VirtualInstruction, + 23 => Exception::StoreGuestPageFault, + _ => Exception::Unknown, + } + } +} + +impl Scause { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// Returns the code field + pub fn code(&self) -> usize { + let bit = 1 << (size_of::() * 8 - 1); + self.bits & !bit + } + + /// Trap Cause + #[inline] + pub fn cause(&self) -> Trap { + if self.is_interrupt() { + Trap::Interrupt(Interrupt::from(self.code())) + } else { + Trap::Exception(Exception::from(self.code())) + } + } + + /// Is trap cause an interrupt. + #[inline] + pub fn is_interrupt(&self) -> bool { + self.bits.get_bit(size_of::() * 8 - 1) + } + + /// Is trap cause an exception. + #[inline] + pub fn is_exception(&self) -> bool { + !self.is_interrupt() + } +} + +read_csr_as!(Scause, 0x142, __read_scause); diff --git a/vendor/riscv/src/register/sepc.rs b/vendor/riscv/src/register/sepc.rs new file mode 100644 index 00000000..aba69dfc --- /dev/null +++ b/vendor/riscv/src/register/sepc.rs @@ -0,0 +1,4 @@ +//! sepc register + +read_csr_as_usize!(0x141, __read_sepc); +write_csr_as_usize!(0x141, __write_sepc); diff --git a/vendor/riscv/src/register/sie.rs b/vendor/riscv/src/register/sie.rs new file mode 100644 index 00000000..b6521109 --- /dev/null +++ b/vendor/riscv/src/register/sie.rs @@ -0,0 +1,76 @@ +//! sie register + +use bit_field::BitField; + +/// sie register +#[derive(Clone, Copy, Debug)] +pub struct Sie { + bits: usize, +} + +impl Sie { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Enable + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Software Interrupt Enable + #[inline] + pub fn ssoft(&self) -> bool { + self.bits.get_bit(1) + } + + /// User Timer Interrupt Enable + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Timer Interrupt Enable + #[inline] + pub fn stimer(&self) -> bool { + self.bits.get_bit(5) + } + + /// User External Interrupt Enable + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } + + /// Supervisor External Interrupt Enable + #[inline] + pub fn sext(&self) -> bool { + self.bits.get_bit(9) + } +} + +read_csr_as!(Sie, 0x104, __read_sie); +set!(0x104, __set_sie); +clear!(0x104, __clear_sie); + +set_clear_csr!( + /// User Software Interrupt Enable + , set_usoft, clear_usoft, 1 << 0); +set_clear_csr!( + /// Supervisor Software Interrupt Enable + , set_ssoft, clear_ssoft, 1 << 1); +set_clear_csr!( + /// User Timer Interrupt Enable + , set_utimer, clear_utimer, 1 << 4); +set_clear_csr!( + /// Supervisor Timer Interrupt Enable + , set_stimer, clear_stimer, 1 << 5); +set_clear_csr!( + /// User External Interrupt Enable + , set_uext, clear_uext, 1 << 8); +set_clear_csr!( + /// Supervisor External Interrupt Enable + , set_sext, clear_sext, 1 << 9); diff --git a/vendor/riscv/src/register/sip.rs b/vendor/riscv/src/register/sip.rs new file mode 100644 index 00000000..f625661a --- /dev/null +++ b/vendor/riscv/src/register/sip.rs @@ -0,0 +1,55 @@ +//! sip register + +use bit_field::BitField; + +/// sip register +#[derive(Clone, Copy, Debug)] +pub struct Sip { + bits: usize, +} + +impl Sip { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Pending + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Software Interrupt Pending + #[inline] + pub fn ssoft(&self) -> bool { + self.bits.get_bit(1) + } + + /// User Timer Interrupt Pending + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Timer Interrupt Pending + #[inline] + pub fn stimer(&self) -> bool { + self.bits.get_bit(5) + } + + /// User External Interrupt Pending + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } + + /// Supervisor External Interrupt Pending + #[inline] + pub fn sext(&self) -> bool { + self.bits.get_bit(9) + } +} + +read_csr_as!(Sip, 0x144, __read_sip); diff --git a/vendor/riscv/src/register/sscratch.rs b/vendor/riscv/src/register/sscratch.rs new file mode 100644 index 00000000..349812c1 --- /dev/null +++ b/vendor/riscv/src/register/sscratch.rs @@ -0,0 +1,4 @@ +//! sscratch register + +read_csr_as_usize!(0x140, __read_sscratch); +write_csr_as_usize!(0x140, __write_sscratch); diff --git a/vendor/riscv/src/register/sstatus.rs b/vendor/riscv/src/register/sstatus.rs new file mode 100644 index 00000000..37a17534 --- /dev/null +++ b/vendor/riscv/src/register/sstatus.rs @@ -0,0 +1,161 @@ +//! sstatus register + +pub use super::mstatus::FS; +use bit_field::BitField; +use core::mem::size_of; + +/// Supervisor Status Register +#[derive(Clone, Copy, Debug)] +pub struct Sstatus { + bits: usize, +} + +/// Supervisor Previous Privilege Mode +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SPP { + Supervisor = 1, + User = 0, +} + +impl Sstatus { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Interrupt Enable + #[inline] + pub fn uie(&self) -> bool { + self.bits.get_bit(0) + } + + /// Supervisor Interrupt Enable + #[inline] + pub fn sie(&self) -> bool { + self.bits.get_bit(1) + } + + /// User Previous Interrupt Enable + #[inline] + pub fn upie(&self) -> bool { + self.bits.get_bit(4) + } + + /// Supervisor Previous Interrupt Enable + #[inline] + pub fn spie(&self) -> bool { + self.bits.get_bit(5) + } + + /// Supervisor Previous Privilege Mode + #[inline] + pub fn spp(&self) -> SPP { + match self.bits.get_bit(8) { + true => SPP::Supervisor, + false => SPP::User, + } + } + + /// The status of the floating-point unit + #[inline] + pub fn fs(&self) -> FS { + match self.bits.get_bits(13..15) { + 0 => FS::Off, + 1 => FS::Initial, + 2 => FS::Clean, + 3 => FS::Dirty, + _ => unreachable!(), + } + } + + /// The status of additional user-mode extensions + /// and associated state + #[inline] + pub fn xs(&self) -> FS { + match self.bits.get_bits(15..17) { + 0 => FS::Off, + 1 => FS::Initial, + 2 => FS::Clean, + 3 => FS::Dirty, + _ => unreachable!(), + } + } + + /// Permit Supervisor User Memory access + #[inline] + pub fn sum(&self) -> bool { + self.bits.get_bit(18) + } + + /// Make eXecutable Readable + #[inline] + pub fn mxr(&self) -> bool { + self.bits.get_bit(19) + } + + /// Whether either the FS field or XS field + /// signals the presence of some dirty state + #[inline] + pub fn sd(&self) -> bool { + self.bits.get_bit(size_of::() * 8 - 1) + } + + #[inline] + pub fn set_spie(&mut self, val: bool) { + self.bits.set_bit(5, val); + } + + #[inline] + pub fn set_sie(&mut self, val: bool) { + self.bits.set_bit(1, val); + } + + #[inline] + pub fn set_spp(&mut self, val: SPP) { + self.bits.set_bit(8, val == SPP::Supervisor); + } +} + +read_csr_as!(Sstatus, 0x100, __read_sstatus); +write_csr!(0x100, __write_sstatus); +set!(0x100, __set_sstatus); +clear!(0x100, __clear_sstatus); + +set_clear_csr!( + /// User Interrupt Enable + , set_uie, clear_uie, 1 << 0); +set_clear_csr!( + /// Supervisor Interrupt Enable + , set_sie, clear_sie, 1 << 1); +set_csr!( + /// User Previous Interrupt Enable + , set_upie, 1 << 4); +set_csr!( + /// Supervisor Previous Interrupt Enable + , set_spie, 1 << 5); +set_clear_csr!( + /// Make eXecutable Readable + , set_mxr, clear_mxr, 1 << 19); +set_clear_csr!( + /// Permit Supervisor User Memory access + , set_sum, clear_sum, 1 << 18); + +/// Supervisor Previous Privilege Mode +#[inline] +#[cfg(riscv)] +pub unsafe fn set_spp(spp: SPP) { + match spp { + SPP::Supervisor => _set(1 << 8), + SPP::User => _clear(1 << 8), + } +} + +/// The status of the floating-point unit +#[inline] +#[cfg(riscv)] +pub unsafe fn set_fs(fs: FS) { + let mut value = _read(); + value.set_bits(13..15, fs as usize); + _write(value); +} diff --git a/vendor/riscv/src/register/stval.rs b/vendor/riscv/src/register/stval.rs new file mode 100644 index 00000000..722cc194 --- /dev/null +++ b/vendor/riscv/src/register/stval.rs @@ -0,0 +1,3 @@ +//! stval register + +read_csr_as_usize!(0x143, __read_stval); diff --git a/vendor/riscv/src/register/stvec.rs b/vendor/riscv/src/register/stvec.rs new file mode 100644 index 00000000..7df0027e --- /dev/null +++ b/vendor/riscv/src/register/stvec.rs @@ -0,0 +1,40 @@ +//! stvec register + +pub use crate::register::mtvec::TrapMode; + +/// stvec register +#[derive(Clone, Copy, Debug)] +pub struct Stvec { + bits: usize, +} + +impl Stvec { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits + } + + /// Returns the trap-vector base-address + pub fn address(&self) -> usize { + self.bits - (self.bits & 0b11) + } + + /// Returns the trap-vector mode + pub fn trap_mode(&self) -> Option { + let mode = self.bits & 0b11; + match mode { + 0 => Some(TrapMode::Direct), + 1 => Some(TrapMode::Vectored), + _ => None, + } + } +} + +read_csr_as!(Stvec, 0x105, __read_stvec); +write_csr!(0x105, __write_stvec); + +/// Writes the CSR +#[inline] +pub unsafe fn write(addr: usize, mode: TrapMode) { + _write(addr + mode as usize); +} diff --git a/vendor/riscv/src/register/time.rs b/vendor/riscv/src/register/time.rs new file mode 100644 index 00000000..665b5079 --- /dev/null +++ b/vendor/riscv/src/register/time.rs @@ -0,0 +1,4 @@ +//! time register + +read_csr_as_usize!(0xC01, __read_time); +read_composite_csr!(super::timeh::read(), read()); diff --git a/vendor/riscv/src/register/timeh.rs b/vendor/riscv/src/register/timeh.rs new file mode 100644 index 00000000..ff725dbe --- /dev/null +++ b/vendor/riscv/src/register/timeh.rs @@ -0,0 +1,3 @@ +//! timeh register + +read_csr_as_usize_rv32!(0xC81, __read_timeh); diff --git a/vendor/riscv/src/register/ucause.rs b/vendor/riscv/src/register/ucause.rs new file mode 100644 index 00000000..06dd8a33 --- /dev/null +++ b/vendor/riscv/src/register/ucause.rs @@ -0,0 +1,17 @@ +//! ucause register + +/// ucause register +#[derive(Clone, Copy, Debug)] +pub struct Ucause { + bits: usize, +} + +impl Ucause { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } +} + +read_csr_as!(Ucause, 0x042, __read_ucause); diff --git a/vendor/riscv/src/register/uepc.rs b/vendor/riscv/src/register/uepc.rs new file mode 100644 index 00000000..1c9fa0e3 --- /dev/null +++ b/vendor/riscv/src/register/uepc.rs @@ -0,0 +1,4 @@ +//! uepc register + +read_csr_as_usize!(0x041, __read_uepc); +write_csr_as_usize!(0x041, __write_uepc); diff --git a/vendor/riscv/src/register/uie.rs b/vendor/riscv/src/register/uie.rs new file mode 100644 index 00000000..4a5e9e03 --- /dev/null +++ b/vendor/riscv/src/register/uie.rs @@ -0,0 +1,49 @@ +//! uie register + +use bit_field::BitField; + +/// uie register +#[derive(Clone, Copy, Debug)] +pub struct Uie { + bits: usize, +} + +impl Uie { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Enable + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// User Timer Interrupt Enable + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// User External Interrupt Enable + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } +} + +read_csr_as!(Uie, 0x004, __read_uie); +set!(0x004, __set_uie); +clear!(0x004, __clear_uie); + +set_clear_csr!( + /// User Software Interrupt Enable + , set_usoft, clear_usoft, 1 << 0); +set_clear_csr!( + /// User Timer Interrupt Enable + , set_utimer, clear_utimer, 1 << 4); +set_clear_csr!( + /// User External Interrupt Enable + , set_uext, clear_uext, 1 << 8); diff --git a/vendor/riscv/src/register/uip.rs b/vendor/riscv/src/register/uip.rs new file mode 100644 index 00000000..ec92ad87 --- /dev/null +++ b/vendor/riscv/src/register/uip.rs @@ -0,0 +1,37 @@ +//! uip register + +use bit_field::BitField; + +/// uip register +#[derive(Clone, Copy, Debug)] +pub struct Uip { + bits: usize, +} + +impl Uip { + /// Returns the contents of the register as raw bits + #[inline] + pub fn bits(&self) -> usize { + self.bits + } + + /// User Software Interrupt Pending + #[inline] + pub fn usoft(&self) -> bool { + self.bits.get_bit(0) + } + + /// User Timer Interrupt Pending + #[inline] + pub fn utimer(&self) -> bool { + self.bits.get_bit(4) + } + + /// User External Interrupt Pending + #[inline] + pub fn uext(&self) -> bool { + self.bits.get_bit(8) + } +} + +read_csr_as!(Uip, 0x044, __read_uip); diff --git a/vendor/riscv/src/register/uscratch.rs b/vendor/riscv/src/register/uscratch.rs new file mode 100644 index 00000000..2bc22539 --- /dev/null +++ b/vendor/riscv/src/register/uscratch.rs @@ -0,0 +1,4 @@ +//! uscratch register + +read_csr_as_usize!(0x040, __read_uscratch); +write_csr_as_usize!(0x040, __write_uscratch); diff --git a/vendor/riscv/src/register/ustatus.rs b/vendor/riscv/src/register/ustatus.rs new file mode 100644 index 00000000..81890bad --- /dev/null +++ b/vendor/riscv/src/register/ustatus.rs @@ -0,0 +1,37 @@ +//! ustatus register +// TODO: Virtualization, Memory Privilege and Extension Context Fields + +use bit_field::BitField; + +/// ustatus register +#[derive(Clone, Copy, Debug)] +pub struct Ustatus { + bits: usize, +} + +impl Ustatus { + /// User Interrupt Enable + #[inline] + pub fn uie(&self) -> bool { + self.bits.get_bit(0) + } + + /// User Previous Interrupt Enable + #[inline] + pub fn upie(&self) -> bool { + self.bits.get_bit(4) + } +} + +read_csr_as!(Ustatus, 0x000, __read_ustatus); +write_csr!(0x000, __write_ustatus); +set!(0x000, __set_ustatus); +clear!(0x000, __clear_ustatus); + +set_clear_csr!( + /// User Interrupt Enable + , set_uie, clear_uie, 1 << 0); + +set_csr!( + /// User Previous Interrupt Enable + , set_upie, 1 << 4); diff --git a/vendor/riscv/src/register/utval.rs b/vendor/riscv/src/register/utval.rs new file mode 100644 index 00000000..b87dca63 --- /dev/null +++ b/vendor/riscv/src/register/utval.rs @@ -0,0 +1,3 @@ +//! utval register + +read_csr_as_usize!(0x043, __read_utval); diff --git a/vendor/riscv/src/register/utvec.rs b/vendor/riscv/src/register/utvec.rs new file mode 100644 index 00000000..7111078a --- /dev/null +++ b/vendor/riscv/src/register/utvec.rs @@ -0,0 +1,40 @@ +//! stvec register + +pub use crate::register::mtvec::TrapMode; + +/// stvec register +#[derive(Clone, Copy, Debug)] +pub struct Utvec { + bits: usize, +} + +impl Utvec { + /// Returns the contents of the register as raw bits + pub fn bits(&self) -> usize { + self.bits + } + + /// Returns the trap-vector base-address + pub fn address(&self) -> usize { + self.bits - (self.bits & 0b11) + } + + /// Returns the trap-vector mode + pub fn trap_mode(&self) -> Option { + let mode = self.bits & 0b11; + match mode { + 0 => Some(TrapMode::Direct), + 1 => Some(TrapMode::Vectored), + _ => None, + } + } +} + +read_csr_as!(Utvec, 0x005, __read_utvec); +write_csr!(0x005, __write_utvec); + +/// Writes the CSR +#[inline] +pub unsafe fn write(addr: usize, mode: TrapMode) { + _write(addr + mode as usize); +} diff --git a/vendor/smoltcp/.github/codecov.yml b/vendor/smoltcp/.github/codecov.yml new file mode 100644 index 00000000..c4e6b3f3 --- /dev/null +++ b/vendor/smoltcp/.github/codecov.yml @@ -0,0 +1,11 @@ +github_checks: + annotations: false + +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true diff --git a/vendor/smoltcp/.github/workflows/coverage.yml b/vendor/smoltcp/.github/workflows/coverage.yml new file mode 100644 index 00000000..86d21023 --- /dev/null +++ b/vendor/smoltcp/.github/workflows/coverage.yml @@ -0,0 +1,21 @@ +on: + pull_request: + merge_group: + +name: Coverage + +jobs: + coverage: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install `cargo llvm-cov` + uses: taiki-e/install-action@cargo-llvm-cov + - name: Run Coverage + run: ./ci.sh coverage + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: false diff --git a/vendor/smoltcp/.github/workflows/fuzz.yml b/vendor/smoltcp/.github/workflows/fuzz.yml new file mode 100644 index 00000000..68d1ba0c --- /dev/null +++ b/vendor/smoltcp/.github/workflows/fuzz.yml @@ -0,0 +1,17 @@ +on: + pull_request: + merge_group: + +name: Fuzz + +jobs: + fuzz: + runs-on: ubuntu-22.04 + env: + RUSTUP_TOOLCHAIN: nightly + steps: + - uses: actions/checkout@v4 + - name: Install cargo-fuzz + run: cargo install cargo-fuzz + - name: Fuzz + run: cargo fuzz run packet_parser -- -max_len=1536 -max_total_time=30 diff --git a/vendor/smoltcp/.github/workflows/matrix-bot.yml b/vendor/smoltcp/.github/workflows/matrix-bot.yml new file mode 100644 index 00000000..510e0fa7 --- /dev/null +++ b/vendor/smoltcp/.github/workflows/matrix-bot.yml @@ -0,0 +1,68 @@ +name: Matrix bot +on: + pull_request_target: + types: [opened, closed] + +jobs: + notify: + if: github.repository == 'smoltcp-rs/smoltcp' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: send message + uses: actions/github-script@v8 + with: + script: | + const action = context.payload.action; + const merged = context.payload.pull_request.merged; + const title = context.payload.pull_request.title; + const url = context.payload.pull_request.html_url; + + // Determine message prefix + let prefix; + if (action === 'opened') { + prefix = 'New PR'; + } else if (action === 'closed' && merged) { + prefix = 'PR merged'; + } else if (action === 'closed' && !merged) { + prefix = 'PR closed without merging'; + } else { + return; // Unknown action, skip + } + + // HTML escape the title + const titleEscaped = title + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + const body = `${prefix}: ${title} - ${url}`; + const formattedBody = `${prefix}: ${titleEscaped}`; + + const roomId = '!jY1WnZYXKZmPkxLiTBTsybQg9pa4EWTYjDkLDeP4WUI'; + const accessToken = process.env.MATRIX_ACCESS_TOKEN; + + const response = await fetch( + `https://matrix.org/_matrix/client/r0/rooms/${roomId}/send/m.room.message`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${accessToken}` + }, + body: JSON.stringify({ + msgtype: 'm.text', + format: 'org.matrix.custom.html', + body: body, + formatted_body: formattedBody + }) + } + ); + + if (!response.ok) { + throw new Error(`Matrix API request failed: ${response.status} ${response.statusText}`); + } + env: + MATRIX_ACCESS_TOKEN: ${{ secrets.MATRIX_ACCESS_TOKEN }} diff --git a/vendor/smoltcp/.github/workflows/rustfmt.yaml b/vendor/smoltcp/.github/workflows/rustfmt.yaml new file mode 100644 index 00000000..b7155894 --- /dev/null +++ b/vendor/smoltcp/.github/workflows/rustfmt.yaml @@ -0,0 +1,12 @@ +on: + pull_request: + merge_group: + +name: Rustfmt check +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check fmt + run: cargo fmt -- --check diff --git a/vendor/smoltcp/.github/workflows/test.yml b/vendor/smoltcp/.github/workflows/test.yml new file mode 100644 index 00000000..e5eb6075 --- /dev/null +++ b/vendor/smoltcp/.github/workflows/test.yml @@ -0,0 +1,65 @@ +on: + pull_request: + merge_group: + +name: Test + +jobs: + tests: + runs-on: ubuntu-22.04 + needs: [check-msrv, test-msrv, test-stable, clippy, test-netsim] + steps: + - name: Done + run: exit 0 + + check-msrv: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Run Checks MSRV + run: ./ci.sh check msrv + + test-msrv: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Run Tests MSRV + run: ./ci.sh test msrv + + clippy: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Run Clippy + run: ./ci.sh clippy + + test-stable: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Run Tests stable + run: ./ci.sh test stable + + test-nightly: + runs-on: ubuntu-22.04 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Run Tests nightly + run: ./ci.sh test nightly + + test-netsim: + runs-on: ubuntu-22.04 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Run network-simulation tests + run: ./ci.sh netsim + + test-build-16bit: + runs-on: ubuntu-22.04 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - name: Build for target with 16 bit pointer + run: ./ci.sh build_16bit diff --git a/vendor/smoltcp/.gitignore b/vendor/smoltcp/.gitignore new file mode 100644 index 00000000..41ca801d --- /dev/null +++ b/vendor/smoltcp/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*.pcap diff --git a/vendor/smoltcp/CHANGELOG.md b/vendor/smoltcp/CHANGELOG.md new file mode 100644 index 00000000..c0090655 --- /dev/null +++ b/vendor/smoltcp/CHANGELOG.md @@ -0,0 +1,398 @@ + # Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +No unreleased changes yet. Please send PRs! + +## [0.13.0] - 2026-03-20 + +Highlights of this release are IPv6 SLAAC support, TCP improvements (zero window probes, retransmit fixes, RFC compliance), raw socket enhancements, and a bump to Rust Edition 2024. + +- Minimum Supported Rust Version (MSRV) bumped to 1.91. +- Rust Edition bumped to 2024. ([#1084](https://github.com/smoltcp-rs/smoltcp/pull/1084)) +- `heapless` dependency bumped to v0.9. ([#1083](https://github.com/smoltcp-rs/smoltcp/pull/1083)) +- iface + - IPv6 SLAAC (Stateless Address Autoconfiguration), gated behind a new `slaac` feature flag. ([#1039](https://github.com/smoltcp-rs/smoltcp/pull/1039)) + - Add `slaac_updated_at` method to check when SLAAC addresses were last updated. ([#1130](https://github.com/smoltcp-rs/smoltcp/pull/1130)) + - Add getters for the default IPv4/IPv6 route and `is_ipv4_gateway`/`is_ipv6_gateway` helpers. ([#1129](https://github.com/smoltcp-rs/smoltcp/pull/1129)) + - Exhaust egress socket state on `poll`, so sockets don't have to wait for the next poll to send queued data. ([#1059](https://github.com/smoltcp-rs/smoltcp/pull/1059)) + - Remove always-Ok `Result`s in `consume()`. ([#1093](https://github.com/smoltcp-rs/smoltcp/pull/1093)) + - Honor `any_ip` when checking local address. ([#1119](https://github.com/smoltcp-rs/smoltcp/pull/1119)) + - Add `iface_max_route_count-0` feature flag to disable the routing table entirely. ([#1057](https://github.com/smoltcp-rs/smoltcp/pull/1057)) + - Improve IPv4 source address selection for multi-subnet interfaces. ([#1074](https://github.com/smoltcp-rs/smoltcp/pull/1074)) + - Fix `poll_at` returning stale timestamps due to `silent_until` expiry check. ([#1127](https://github.com/smoltcp-rs/smoltcp/pull/1127)) + - Fix fragment payload sizes not being a multiple of eight octets. ([#1116](https://github.com/smoltcp-rs/smoltcp/pull/1116)) + - Fix incorrect packet length after defragmentation. ([#1094](https://github.com/smoltcp-rs/smoltcp/pull/1094)) + - Fix compilation of IPv6+Multicast without Ethernet. ([#1043](https://github.com/smoltcp-rs/smoltcp/pull/1043)) + - Log and drop IPv6 packets requiring fragmentation (IPv6 routers must not fragment). ([#1038](https://github.com/smoltcp-rs/smoltcp/pull/1038)) +- tcp + - Add zero window probe support. ([#1026](https://github.com/smoltcp-rs/smoltcp/pull/1026)) + - Close socket if the local IP is no longer assigned to the interface. ([#1113](https://github.com/smoltcp-rs/smoltcp/pull/1113)) + - Reject bytes outside the receive window. ([#1079](https://github.com/smoltcp-rs/smoltcp/pull/1079)) + - Don't accept RST packets on listening sockets. ([#1058](https://github.com/smoltcp-rs/smoltcp/pull/1058)) + - Don't send TCP RST when packet is handled by a raw socket. ([#1069](https://github.com/smoltcp-rs/smoltcp/pull/1069)) + - Send challenge ACK for duplicate ACK in LAST-ACK state. ([#1126](https://github.com/smoltcp-rs/smoltcp/pull/1126)) + - Fix retransmit exponential backoff, align to RFC 6298. ([#1023](https://github.com/smoltcp-rs/smoltcp/pull/1023)) + - Restart retransmit timer on new data ACK. ([#1018](https://github.com/smoltcp-rs/smoltcp/pull/1018)) + - Fix FIN retransmit in CLOSING state. ([#1026](https://github.com/smoltcp-rs/smoltcp/pull/1026)) + - Add `pause_synack` feature flag to allow user code to withhold SYN|ACK. ([#1063](https://github.com/smoltcp-rs/smoltcp/pull/1063)) +- socket + - ICMP: support binding to a TCP port. ([#1089](https://github.com/smoltcp-rs/smoltcp/pull/1089)) + - ICMP: add `no-auto-icmp-echo-reply` feature flag to disable automatic echo replies. ([#1106](https://github.com/smoltcp-rs/smoltcp/pull/1106)) + - Raw: allow receiving all protocols and IP versions (unfiltered mode). ([#1067](https://github.com/smoltcp-rs/smoltcp/pull/1067)) + - Raw: fix panic when payload buffer exceeds packet size during fragmentation. ([#1077](https://github.com/smoltcp-rs/smoltcp/pull/1077)) + - DHCPv4: reuse DHCPOFFER transaction ID in DHCPREQUEST. ([#1061](https://github.com/smoltcp-rs/smoltcp/pull/1061)) + - DHCPv4: fix panic when T1 < T2 < lease duration is not respected by server. ([#1029](https://github.com/smoltcp-rs/smoltcp/pull/1029)) + - DNS: fix compilation when `socket-dns` is enabled but `socket-udp` isn't. ([#1041](https://github.com/smoltcp-rs/smoltcp/pull/1041)) + - Implement `AnySocket` for the `Socket` enum. ([#1092](https://github.com/smoltcp-rs/smoltcp/pull/1092)) +- phy + - `Tracer`: update public API to allow custom inspection and printing of packets. ([#1076](https://github.com/smoltcp-rs/smoltcp/pull/1076)) + - `TunTapInterface`: no longer automatically enables `medium-ethernet` feature. ([#1055](https://github.com/smoltcp-rs/smoltcp/pull/1055)) +- wire + - Add generic IPv4/IPv6 packet parsing support to `IpRepr`. ([#1087](https://github.com/smoltcp-rs/smoltcp/pull/1087)) + - Make `Cidr` initialization `const`. ([#1036](https://github.com/smoltcp-rs/smoltcp/pull/1036)) + - Add conversion from `Endpoint` into `SocketAddr`. ([#1124](https://github.com/smoltcp-rs/smoltcp/pull/1124)) + - Use newly stable IP methods from `core::net`. ([#1115](https://github.com/smoltcp-rs/smoltcp/pull/1115)) + - Fix `RawHardwareAddress` panic when parsing to specific link-layer address. ([#1027](https://github.com/smoltcp-rs/smoltcp/pull/1027)) + - Fix multicast panic when `max_resp_code` is zero. ([#1047](https://github.com/smoltcp-rs/smoltcp/pull/1047)) + - Fix partial checksum in tcpdump/pcap. ([#1015](https://github.com/smoltcp-rs/smoltcp/pull/1015)) + +## [0.12.0] - 2024-11-28 + +Almost a year in the making, the highlights of the release are the migration to `core::net` IP types, IPv6 multicast, TCP improvements, and many fixes. Smoltcp now connects your gadgets to the Internet better than ever. + +- Minimum Supported Rust Version (MSRV) bumped to 1.80. +- iface + - IPv6 multicast ([#914](https://github.com/smoltcp-rs/smoltcp/pull/914), [#976](https://github.com/smoltcp-rs/smoltcp/pull/976), [#988](https://github.com/smoltcp-rs/smoltcp/pull/988), [#1009](https://github.com/smoltcp-rs/smoltcp/pull/1009), [#1012](https://github.com/smoltcp-rs/smoltcp/pull/1012)) + - Add `poll_egress()` and `poll_ingress_single()` methods for finer-grained control of what and how many packets are processed. ([#954](https://github.com/smoltcp-rs/smoltcp/pull/954), [#991](https://github.com/smoltcp-rs/smoltcp/pull/991), [#993](https://github.com/smoltcp-rs/smoltcp/pull/993)) + - Multicast join/leave no longer requires access to device+timestamp. ([#985](https://github.com/smoltcp-rs/smoltcp/pull/985)) + - Reset expiry of entries in the neighbor cache on packet reception ([#966](https://github.com/smoltcp-rs/smoltcp/pull/966)) + - Honor `any_ip` for ARP ([#880](https://github.com/smoltcp-rs/smoltcp/pull/880)) + - Honor `any_ip` for IPv6 ([#900](https://github.com/smoltcp-rs/smoltcp/pull/900)) + - Use own source address for ARP and NDISC Solicitations ([#984](https://github.com/smoltcp-rs/smoltcp/pull/984)) + - fix panic when discarding HBH Option with multicast destination address ([#996](https://github.com/smoltcp-rs/smoltcp/pull/996)) + - fix panic with 6lowpan frag datagram_size < 40 ([#997](https://github.com/smoltcp-rs/smoltcp/pull/997)) + - fix panic if no suitable IPv6 src_addr is found ([#895](https://github.com/smoltcp-rs/smoltcp/pull/895)) + - Fix specific length IP packets not being fragmented ([#1008](https://github.com/smoltcp-rs/smoltcp/pull/1008)) +- tcp + - Add support for congestion control ([#907](https://github.com/smoltcp-rs/smoltcp/pull/907)) + - Add support for simultaneous open ([#1001](https://github.com/smoltcp-rs/smoltcp/pull/1001)) + - Add support for Timestamp option ([#939](https://github.com/smoltcp-rs/smoltcp/pull/939)) + - Send immediate ACKs after RMSS bytes of data ([#1002](https://github.com/smoltcp-rs/smoltcp/pull/1002)) + - Do not ignore FIN if segment is partially outside the window. ([#977](https://github.com/smoltcp-rs/smoltcp/pull/977)) + - Correctly set internal sACK flag for client sockets ([#995](https://github.com/smoltcp-rs/smoltcp/pull/995)) + - Only reset remote_last_ts if some data is enqueued ([#917](https://github.com/smoltcp-rs/smoltcp/pull/917)) + - Don't delay ACKs for significant window updates ([#935](https://github.com/smoltcp-rs/smoltcp/pull/935)) + - Add `listen_endpoint` getter ([#1005](https://github.com/smoltcp-rs/smoltcp/pull/1005)) +- socket + - UDP,ICMP,raw: Add `send_queue`/`recv_queue` ([#1003](https://github.com/smoltcp-rs/smoltcp/pull/1003)) + - ICMP: split ICMPv4/v6 accept and process ([#887](https://github.com/smoltcp-rs/smoltcp/pull/887)) + - UDP: Store local and use local address in metadata ([#904](https://github.com/smoltcp-rs/smoltcp/pull/904)) + - DNS: fix panic if server list is too long ([#986](https://github.com/smoltcp-rs/smoltcp/pull/986)) + - DNS: fix panic if no valid source address is found ([#987](https://github.com/smoltcp-rs/smoltcp/pull/987)) +- phy + - Change mutability of `RxToken`'s `consume` argument. ([#924](https://github.com/smoltcp-rs/smoltcp/pull/924)) + - Add support for NetBSD ([#883](https://github.com/smoltcp-rs/smoltcp/pull/883)) + - Add minimum support for iOS ([#896](https://github.com/smoltcp-rs/smoltcp/pull/896)) + - Add BPF support for FreeBSD ([#906](https://github.com/smoltcp-rs/smoltcp/pull/906)) + - disable checksums on loopback ([#919](https://github.com/smoltcp-rs/smoltcp/pull/919)) +- wire + - Use core::net types for IP addresses. ([#937](https://github.com/smoltcp-rs/smoltcp/pull/937), [#994](https://github.com/smoltcp-rs/smoltcp/pull/994)) + - Add missing exports in wire for DNS ([#891](https://github.com/smoltcp-rs/smoltcp/pull/891)) + - rename Scope to MulticastScope ([#898](https://github.com/smoltcp-rs/smoltcp/pull/898)) + - Re-export `dhcpv4::Flags` and `dhcpv4::OpCode` ([#901](https://github.com/smoltcp-rs/smoltcp/pull/901)) + - Make Address:v6() constructor const ([#975](https://github.com/smoltcp-rs/smoltcp/pull/975)) + - Ipv6RoutingHeader::clear_reserved: fix offsets for Type 2 routing headers. ([#882](https://github.com/smoltcp-rs/smoltcp/pull/882)) + +## [0.11.0] - 2023-12-23 + +### Additions + +- wire/ipsec: add basic IPsec parsing/emitting ([#821](https://github.com/smoltcp-rs/smoltcp/pull/821)). +- phy: add support for `TUNSETIFF` on MIPS, PPC and SPARC ([#839](https://github.com/smoltcp-rs/smoltcp/pull/839)). +- socket/tcp: accept FIN on zero window ([#845](https://github.com/smoltcp-rs/smoltcp/pull/845)). +- wire/ipv6: add `is_unique_local()` to IPv6 addresses ([#862](https://github.com/smoltcp-rs/smoltcp/pull/862)). +- wire/ipv6: add `is_global_unicast()` to IPv6 addresses ([#864](https://github.com/smoltcp-rs/smoltcp/pull/864)). +- iface/neigh: add `fill_with_expiration` ([#871](https://github.com/smoltcp-rs/smoltcp/pull/871)). + +### Fixes + +- icmpv6: truncate packet to MTU ([#807](https://github.com/smoltcp-rs/smoltcp/pull/807), [#808](https://github.com/smoltcp-rs/smoltcp/pull/810)). +- wire/rpl: DAO-ACK DODAG ID was wrongly read ([#824](https://github.com/smoltcp-rs/smoltcp/pull/824)). +- socket/tcp: don't panic when calling `listen` again on the same local endpoint ([#841](https://github.com/smoltcp-rs/smoltcp/pull/841)). +- wire/dhcpv4: don't panic when parsing addresses with incorrect amount of bytes ([#843](https://github.com/smoltcp-rs/smoltcp/pull/843)). +- iface/ndisc: prevent ndisc when the medium is IP ([#865](https://github.com/smoltcp-rs/smoltcp/pull/865)). +- wire/ieee802154: better parsing of security fields. Correctly parse frame type (3 bits instead of 2 bits) ([#868](https://github.com/smoltcp-rs/smoltcp/pull/864)). +- wire/ieee802154: better handle address fields for new frame version ([#870](https://github.com/smoltcp-rs/smoltcp/pull/870)). +- iface/tcp: don't send TCP RST with unspecified addresses ([#867](https://github.com/smoltcp-rs/smoltcp/pull/867)). +- iface: don't handle empty packets (this would panic when reading the IP version) ([#866](https://github.com/smoltcp-rs/smoltcp/pull/866)). +- socket/dhcp: Add an upper bound to the renew/rebind timeout in `RetryConfig` ([#835](https://github.com/smoltcp-rs/smoltcp/pull/835)). + +### Changes + +- iface: rewrite `IpPacket` such that IPv6 packets can contain owned extension headers ([#802](https://github.com/smoltcp-rs/smoltcp/pull/802)). +- iface: remove generic `T: [u8]` in functions. This reduced the server example by 10KB ([#810](https://github.com/smoltcp-rs/smoltcp/pull/810)). +- SocketSet: add comment about using static lifetime for SocketSets with owned storage ([#813](https://github.com/smoltcp-rs/smoltcp/pull/813)). +- phy/RawSocket: open raw socket with `O_NONBLOCK` ([#817](https://github.com/smoltcp-rs/smoltcp/pull/817)). +- tests/rstest: use rstest for fixture based testing ([#823](https://github.com/smoltcp-rs/smoltcp/pull/823)). +- docs/readme: update readme about IEEE802.15.4 and 6LoWPAN ([#826](https://github.com/smoltcp-rs/smoltcp/pull/826)). +- wire/ipv6-hbh: IPv6 HBH has owned options instead of references ([#827](https://github.com/smoltcp-rs/smoltcp/pull/827)). +- wire/sixlowpan: 6LoWPAN is split into multiple modules ([#828](https://github.com/smoltcp-rs/smoltcp/pull/828)). +- sockets: match the behaviour of `peek_slice` and `recv_slice` ([#834](https://github.com/smoltcp-rs/smoltcp/pull/834)). +- dependencies: update to headpless v0.8 ([#853](https://github.com/smoltcp-rs/smoltcp/pull/853)). +- config: make `config` constants public ([#855](https://github.com/smoltcp-rs/smoltcp/pull/855)). +- phy/ieee802154: clarify `mtu+=2` for IEEE802.15.4 ([#857](https://github.com/smoltcp-rs/smoltcp/pull/857)). +- sockets: `recv_slice` returns `RcvError::Truncated` when the length of the slice is smaller than the data received by the socket ([#859](https://github.com/smoltcp-rs/smoltcp/pull/859)). +- iface/ipv6: `get_source_address` uses [RFC 6724](https://www.rfc-editor.org/rfc/rfc6724) for address selection ([#864](https://github.com/smoltcp-rs/smoltcp/pull/864)). +- pcap: use IEEE 802.15.4 without FCS for PCAP link types ([#874](https://github.com/smoltcp-rs/smoltcp/pull/874)). +- iface: rename `IpPacket`/`Ipv4Packet`/`Ipv6Packet` to `Pacet`/`PacketV4`/`PacketV4`. This is to remove the ambiguity with `IpPacket` in `src/wire/` ([#873](https://github.com/smoltcp-rs/smoltcp/pull/873)). +- wire/ndisc: rewrite parse function (3.1KiB -> 1.9KiB) ([#878](https://github.com/smoltcp-rs/smoltcp/pull/878)) +- iface: Check IPv6 address after processing HBH ([#861](https://github.com/smoltcp-rs/smoltcp/pull/861)) + +## [0.10.0] - 2023-06-26 + +- Add optional packet metadata. Allows tracking packets by ID across the whole stack, between the `Device` impl and sockets. One application is timestamping packets with the PHY's collaboration, allowing implementing PTP (#628) +- Work-in-progress implementation of RPL (Routing Protocol for Low-Power and Lossy Networks), commonly used for IEEE 802.15.4 / 6LoWPAN networks. Wire is mostly complete, full functionality will be in 0.11 hopefully! (#627, #766, #767, #772, #773, #777, #790, #798, #804) +- dhcp: Add support for rebinding (#744) + +- iface: + - add support for sending to subnet-local broadcast addrs (like 192.168.1.255). (#801) + - Creating an interface requires passing in the time. (#799) + - fix wrong payload length of first IPv4 fragment (#791, #792) + - Don't discard from unspecified IPv4 src addresses (#787) + +- tcp: + - do not count window updates as duplicate acks. (#748) + - consider segments partially overlapping the window as acceptable (#749) + - Perform a reset() after an abort() (#788) + +- 6lowpan: + - Hop-by-Hop Header compression (#765) + - Routing Header compression (#770) + +- wire: + - reexport DNS opcode, rcode, flag. (#763, #806) + - refactor IPv6 Extension Headers to make them more consistent and easier to parse. (#781) + - check length field of NDISC redirected head (#784) + +- Modify `hardware_addr` and `neighbor_cache` to be not `Option`, add `HardwareAddress::Ip` (#745) +- Add file descriptor support for tuntap devices, needed for the Android VPN API. (#776) +- implement Display and Error for error types (#750, #756, #757) +- Better defmt for Instant, Duration and Ipv6Address (#754, #758) +- Add Hash trait for enum_with_unknown macro (#755) + +## [0.9.1] - 2023-02-08 + +- iface: make MulticastError public. (#747) +- Fix parsing of ieee802154 link layer address for NDISC options (#746) + +## [0.9.0] - 2023-02-06 + +- Minimum Supported Rust Version (MSRV) **bumped** from 1.56 to 1.65 +- Added DNS client support. + - Add DnsSocket (#465) + - Add support for one-shot mDNS resolution (#669) +- Added support for packet fragmentation and reassembly, both for IPv4 and 6LoWPAN. (#591, #580, #624, #634, #645, #653, #684) +- Major error handling overhaul. + - Previously, _smoltcp_ had a single `Error` enum that all methods returned. Now methods that can fail have their own error enums, with only the actual errors they can return. (#617, #667, #730) + - Consuming `phy::Device` tokens is now infallible. + - In the case of "buffer full", `phy::Device` implementations must return `None` from the `transmit`/`receive` methods. (Previously, they could either do that, or return tokens and then return `Error::Exhausted` when consuming them. The latter wasted computation since it'd make _smoltcp_ pointlessly spend effort preparing the packet, and is now disallowed). + - For all other phy errors, `phy::Device` implementations should drop the packet and handle the error themselves. (Either log it and forget it, or buffer/count it and offer methods to let the user retrieve the error queue/counts.) Returning the error to have it bubble up to `Interface::poll()` is no longer supported. +- phy: the `trait Device` now uses Generic Associated Types (GAT) for the TX and RX tokens. The main impact of this is `Device` impls can now borrow data (because previously, the`for<'a> T: Device<'a>` bounds required to workaround the lack of GATs essentially implied `T: 'static`.) (#572) +- iface: The `Interface` API has been significantly simplified and cleaned up. + - The builder has been removed (#736) + - SocketSet and Device are now borrowed in methods that need them, instead of owning them. (#619) + - `Interface` now owns the list of addresses (#719), routes, neighbor cache (#722), 6LoWPAN address contexts, and fragmentation buffers (#736) instead of borrowing them with `managed`. + - A new compile-time configuration mechanism has been added, to configure the size of the (now owned) buffers (#742) +- iface: Change neighbor discovery timeout from 3s to 1s, to match Linux's behavior. (#620) +- iface: Remove implicit sized bound on device generics (#679) +- iface/6lowpan: Add address context information for resolving 6LoWPAN addresses (#687) +- iface/6lowpan: fix incorrect SAM value in IPHC when address is not compressed (#630) +- iface/6lowpan: packet parsing fuzz fixes (#636) +- socket: Add send_with to udp, raw, and icmp sockets. These methods enable reserving a packet buffer with a greater size than you need, and then shrinking the size once you know it. (#625) +- socket: Make `trait AnySocket` object-safe (#718) +- socket/dhcpv4: add waker support (#623) +- socket/dhcpv4: indicate new config if there's a packet buffer provided (#685) +- socket/dhcpv4: Use renewal time from DHCP server ACK, if given (#683) +- socket/dhcpv4: allow for extra configuration + - setting arbitrary options in the request. (#650) + - retrieving arbitrary options from the response. (#650) + - setting custom parameter request list. (#650) + - setting custom timing for retries. (#650) + - Allow specifying different server/client DHCP ports (#738) +- socket/raw: Add `peek` and `peek_slice` methods (#734) +- socket/raw: When sending packets, send the source IP address unmodified (it was previously replaced with the interface's address if it was unspecified). (#616) +- socket/tcp: Do not reset socket-level settings, such as keepalive, on reset (#603) +- socket/tcp: ensure we always accept the segment at offset=0 even if the assembler is full. (#735, #452) +- socket/tcp: Refactored assembler, now more robust and faster (#726, #735) +- socket/udp: accept packets with checksum field set to `0`, since that means the checksum is not computed (#632) +- wire: make many functions const (#693) +- wire/dhcpv4: remove Option enum (#656) +- wire/dhcpv4: use heapless Vec for DNS server list (#678) +- wire/icmpv4: add support for TimeExceeded packets (#609) +- wire/ip: Remove `IpRepr::Unspecified`, `IpVersion::Unspecified`, `IpAddress::Unspecified` (#579, #616) +- wire/ip: support parsing unspecified IPv6 IpEndpoints from string (like `[::]:12345`) (#732) +- wire/ipv6: Make Public Ipv6RoutingType (#691) +- wire/ndisc: do not error on unrecognized options. (#737) +- Switch to Rust 2021 edition. (#729) +- Remove obsolete Cargo feature `rust-1_28` (#725) + +## [0.8.2] - 2022-11-27 + +- tcp: Fix return value of nagle_enable ([#642](https://github.com/smoltcp-rs/smoltcp/pull/642)) +- tcp: Only clear retransmit timer when all packets are acked ([#662](https://github.com/smoltcp-rs/smoltcp/pull/662)) +- tcp: Send incomplete fin packets even if nagle enabled ([#665](https://github.com/smoltcp-rs/smoltcp/pull/665)) +- phy: Fix mtu calculation for raw_socket ([#611](https://github.com/smoltcp-rs/smoltcp/pull/611)) +- wire: Fix ipv6 contains_addr function ([#605](https://github.com/smoltcp-rs/smoltcp/pull/605)) + +## [0.8.1] - 2022-05-12 + +- Remove unused `rand_core` dep. ([#589](https://github.com/smoltcp-rs/smoltcp/pull/589)) +- Use socklen_t instead of u32 for RawSocket bind() parameter. Fixes build on 32bit Android. ([#593](https://github.com/smoltcp-rs/smoltcp/pull/593)) +- Propagate phy::RawSocket send errors to caller ([#588](https://github.com/smoltcp-rs/smoltcp/pull/588)) +- Fix Interface set_hardware_addr, get_hardware_addr for ieee802154/6lowpan. ([#584](https://github.com/smoltcp-rs/smoltcp/pull/584)) + +## [0.8.0] - 2021-12-11 + +- Minimum Supported Rust Version (MSRV) **bumped** from 1.40 to 1.56 +- Add support for IEEE 802.15.4 + 6LoWPAN medium ([#469](https://github.com/smoltcp-rs/smoltcp/pull/469)) +- Add support for IP medium ([#401](https://github.com/smoltcp-rs/smoltcp/pull/401)) +- Add `defmt` logging support ([#455](https://github.com/smoltcp-rs/smoltcp/pull/455)) +- Add RNG infrastructure ([#547](https://github.com/smoltcp-rs/smoltcp/pull/547), [#573](https://github.com/smoltcp-rs/smoltcp/pull/573)) +- Add `Context` struct that must be passed to some socket methods ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500)) +- Remove `SocketSet`, sockets are owned by `Interface` now. ([#557](https://github.com/smoltcp-rs/smoltcp/pull/557), [#571](https://github.com/smoltcp-rs/smoltcp/pull/571)) +- TCP: Add Nagle's Algorithm. ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500)) +- TCP crash and correctness fixes: + - Add Nagle's Algorithm. ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500)) + - Window scaling fixes. ([#500](https://github.com/smoltcp-rs/smoltcp/pull/500)) + - Fix delayed ack causing ack not to be sent after 3 packets. ([#530](https://github.com/smoltcp-rs/smoltcp/pull/530)) + - Fix RTT estimation for RTTs longer than 1 second ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538)) + - Fix infinite loop when remote side sets a MSS of 0 ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538)) + - Fix infinite loop when retransmit when remote window is 0 ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538)) + - Fix crash when receiving a FIN in SYN_SENT state ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538)) + - Fix overflow crash when receiving a wrong ACK seq in SYN_RECEIVED state ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538)) + - Fix overflow crash when initial sequence number is u32::MAX ([#538](https://github.com/smoltcp-rs/smoltcp/pull/538)) + - Fix infinite loop on challenge ACKs ([#542](https://github.com/smoltcp-rs/smoltcp/pull/542)) + - Reply with RST to invalid packets in SynReceived state. ([#542](https://github.com/smoltcp-rs/smoltcp/pull/542)) + - Do not abort socket when receiving some invalid packets. ([#542](https://github.com/smoltcp-rs/smoltcp/pull/542)) + - Make initial sequence number random. ([#547](https://github.com/smoltcp-rs/smoltcp/pull/547)) + - Reply with RST to ACKs with invalid ackno in SYN_SENT. ([#522](https://github.com/smoltcp-rs/smoltcp/pull/522)) +- ARP fixes to deal better with broken networks: + - Fill cache only from ARP packets, not any packets. ([#544](https://github.com/smoltcp-rs/smoltcp/pull/544)) + - Fill cache only from ARP packets directed at us. ([#544](https://github.com/smoltcp-rs/smoltcp/pull/544)) + - Reject ARP packets with a source address not in the local network. ([#536](https://github.com/smoltcp-rs/smoltcp/pull/536), [#544](https://github.com/smoltcp-rs/smoltcp/pull/544)) + - Ignore unknown ARP packets. ([#544](https://github.com/smoltcp-rs/smoltcp/pull/544)) + - Flush neighbor cache on IP change ([#564](https://github.com/smoltcp-rs/smoltcp/pull/564)) +- UDP: Add `close()` method to unbind socket. ([#475](https://github.com/smoltcp-rs/smoltcp/pull/475), [#482](https://github.com/smoltcp-rs/smoltcp/pull/482)) +- DHCP client improvements: + - Refactored implementation to improve reliability and RFC compliance ([#459](https://github.com/smoltcp-rs/smoltcp/pull/459)) + - Convert to socket ([#459](https://github.com/smoltcp-rs/smoltcp/pull/459)) + - Added `max_lease_duration` option ([#459](https://github.com/smoltcp-rs/smoltcp/pull/459)) + - Do not set the BROADCAST flag ([#548](https://github.com/smoltcp-rs/smoltcp/pull/548)) + - Add option to ignore NAKs ([#548](https://github.com/smoltcp-rs/smoltcp/pull/548)) +- DHCP wire: + - Fix DhcpRepr::buffer_len not accounting for lease time, router and subnet options ([#478](https://github.com/smoltcp-rs/smoltcp/pull/478)) + - Emit DNS servers in DhcpRepr ([#510](https://github.com/smoltcp-rs/smoltcp/pull/510)) + - Fix incorrect bit for BROADCAST flag ([#548](https://github.com/smoltcp-rs/smoltcp/pull/548)) +- Improve resilience against packet ingress processing errors ([#281](https://github.com/smoltcp-rs/smoltcp/pull/281), [#483](https://github.com/smoltcp-rs/smoltcp/pull/483)) +- Implement `std::error::Error` for `smoltcp::Error` ([#485](https://github.com/smoltcp-rs/smoltcp/pull/485)) +- Update `managed` from 0.7 to 0.8 ([442](https://github.com/smoltcp-rs/smoltcp/pull/442)) +- Fix incorrect timestamp in PCAP captures ([#513](https://github.com/smoltcp-rs/smoltcp/pull/513)) +- Use microseconds instead of milliseconds in Instant and Duration ([#514](https://github.com/smoltcp-rs/smoltcp/pull/514)) +- Expose inner `Device` in `PcapWriter` ([#524](https://github.com/smoltcp-rs/smoltcp/pull/524)) +- Fix assert with any_ip + broadcast dst_addr. ([#533](https://github.com/smoltcp-rs/smoltcp/pull/533), [#534](https://github.com/smoltcp-rs/smoltcp/pull/534)) +- Simplify PcapSink trait ([#535](https://github.com/smoltcp-rs/smoltcp/pull/535)) +- Fix wrong operation order in FuzzInjector ([#525](https://github.com/smoltcp-rs/smoltcp/pull/525), [#535](https://github.com/smoltcp-rs/smoltcp/pull/535)) + +## [0.7.5] - 2021-06-28 + +- dhcpv4: emit DNS servers in repr ([#505](https://github.com/smoltcp-rs/smoltcp/pull/505)) + +## [0.7.4] - 2021-06-11 + +- tcp: fix "subtract sequence numbers with underflow" on remote window shrink. ([#490](https://github.com/smoltcp-rs/smoltcp/pull/490)) +- tcp: fix subtract with overflow when receiving a SYNACK with unincremented ACK number. ([#491](https://github.com/smoltcp-rs/smoltcp/pull/491)) +- tcp: use nonzero initial sequence number to workaround misbehaving servers. ([#492](https://github.com/smoltcp-rs/smoltcp/pull/492)) + +## [0.7.3] - 2021-05-29 + +- Fix "unused attribute" error in recent nightlies. + +## [0.7.2] - 2021-05-29 + +- iface: check for ipv4 subnet broadcast addrs everywhere ([#462](https://github.com/smoltcp-rs/smoltcp/pull/462)) +- dhcp: always send parameter_request_list. ([#456](https://github.com/smoltcp-rs/smoltcp/pull/456)) +- dhcp: Clear expiration time on reset. ([#456](https://github.com/smoltcp-rs/smoltcp/pull/456)) +- phy: fix FaultInjector returning a too big buffer when simulating a drop on tx ([#463](https://github.com/smoltcp-rs/smoltcp/pull/463)) +- tcp rtte: fix "attempt to multiply with overflow". ([#476](https://github.com/smoltcp-rs/smoltcp/pull/476)) +- tcp: LastAck should only change to Closed on ack of fin. ([#477](https://github.com/smoltcp-rs/smoltcp/pull/477)) +- wire/dhcpv4: account for lease time, router and subnet options in DhcpRepr::buffer_len ([#478](https://github.com/smoltcp-rs/smoltcp/pull/478)) + +## [0.7.1] - 2021-03-27 + +- ndisc: Fix NeighborSolicit incorrectly asking for src addr instead of dst addr ([419](https://github.com/smoltcp-rs/smoltcp/pull/419)) +- dhcpv4: respect lease time from the server instead of renewing every 60 seconds. ([437](https://github.com/smoltcp-rs/smoltcp/pull/437)) +- Fix build errors due to invalid combinations of features ([416](https://github.com/smoltcp-rs/smoltcp/pull/416), [447](https://github.com/smoltcp-rs/smoltcp/pull/447)) +- wire/ipv4: make some functions const ([420](https://github.com/smoltcp-rs/smoltcp/pull/420)) +- phy: fix BPF on OpenBSD ([421](https://github.com/smoltcp-rs/smoltcp/pull/421), [427](https://github.com/smoltcp-rs/smoltcp/pull/427)) +- phy: enable RawSocket, TapInterface on Android ([435](https://github.com/smoltcp-rs/smoltcp/pull/435)) +- phy: fix phy_wait for waits longer than 1 second ([449](https://github.com/smoltcp-rs/smoltcp/pull/449)) + +## [0.7.0] - 2021-01-20 + +- Minimum Supported Rust Version (MSRV) **bumped** from 1.36 to 1.40 + +### New features +- tcp: Allow distinguishing between graceful (FIN) and ungraceful (RST) close. On graceful close, `recv()` now returns `Error::Finished`. On ungraceful close, `Error::Illegal` is returned, as before. ([351](https://github.com/smoltcp-rs/smoltcp/pull/351)) +- sockets: Add support for attaching async/await Wakers to sockets. Wakers are woken on socket state changes. ([394](https://github.com/smoltcp-rs/smoltcp/pull/394)) +- tcp: Set retransmission timeout based on an RTT estimation, instead of the previously fixed 100ms. This improves performance on high-latency links, such as mobile networks. ([406](https://github.com/smoltcp-rs/smoltcp/pull/406)) +- tcp: add Delayed ACK support. On by default, with a 10ms delay. ([404](https://github.com/smoltcp-rs/smoltcp/pull/404)) +- ip: Process broadcast packets directed to the subnet's broadcast address, such as 192.168.1.255. Previously broadcast packets were +only processed when directed to the 255.255.255.255 address. ([377](https://github.com/smoltcp-rs/smoltcp/pull/377)) + +### Fixes +- udp,raw,icmp: Fix packet buffer panic caused by large payload ([332](https://github.com/smoltcp-rs/smoltcp/pull/332)) +- dhcpv4: use offered ip in requested ip option ([310](https://github.com/smoltcp-rs/smoltcp/pull/310)) +- dhcpv4: Re-export dhcp::clientv4::Config +- dhcpv4: Enable `proto-dhcpv4` feature by default. ([327](https://github.com/smoltcp-rs/smoltcp/pull/327)) +- ethernet,arp: Allow for ARP retry during egress ([368](https://github.com/smoltcp-rs/smoltcp/pull/368)) +- ethernet,arp: Only limit the neighbor cache rate after sending a request packet ([369](https://github.com/smoltcp-rs/smoltcp/pull/369)) +- tcp: use provided ip for TcpSocket::connect instead of 0.0.0.0 ([329](https://github.com/smoltcp-rs/smoltcp/pull/329)) +- tcp: Accept data packets in FIN_WAIT_2 state. ([350](https://github.com/smoltcp-rs/smoltcp/pull/350)) +- tcp: Always send updated ack number in `ack_reply()`. ([353](https://github.com/smoltcp-rs/smoltcp/pull/353)) +- tcp: allow sending ACKs in FinWait2 state. ([388](https://github.com/smoltcp-rs/smoltcp/pull/388)) +- tcp: fix racey simultaneous close not sending FIN. ([398](https://github.com/smoltcp-rs/smoltcp/pull/398)) +- tcp: Do not send window updates in states that shouldn't do so ([360](https://github.com/smoltcp-rs/smoltcp/pull/360)) +- tcp: Return RST to unexpected ACK in SYN-SENT state. ([367](https://github.com/smoltcp-rs/smoltcp/pull/367)) +- tcp: Take MTU into account during TcpSocket dispatch. ([384](https://github.com/smoltcp-rs/smoltcp/pull/384)) +- tcp: don't send data outside the remote window ([387](https://github.com/smoltcp-rs/smoltcp/pull/387)) +- phy: Take Ethernet header into account for MTU of RawSocket and TapInterface. ([393](https://github.com/smoltcp-rs/smoltcp/pull/393)) +- phy: add null terminator to c-string passed to libc API ([372](https://github.com/smoltcp-rs/smoltcp/pull/372)) + +### Quality of Life™ improvements +- Update to Rust 2018 edition ([396](https://github.com/smoltcp-rs/smoltcp/pull/396)) +- Migrate CI to Github Actions ([390](https://github.com/smoltcp-rs/smoltcp/pull/390)) +- Fix clippy lints, enforce clippy in CI ([395](https://github.com/smoltcp-rs/smoltcp/pull/395), [402](https://github.com/smoltcp-rs/smoltcp/pull/402), [403](https://github.com/smoltcp-rs/smoltcp/pull/403), [405](https://github.com/smoltcp-rs/smoltcp/pull/405), [407](https://github.com/smoltcp-rs/smoltcp/pull/407)) +- Use #[non_exhaustive] for enums and structs ([409](https://github.com/smoltcp-rs/smoltcp/pull/409), [411](https://github.com/smoltcp-rs/smoltcp/pull/411)) +- Simplify lifetime parameters of sockets, SocketSet, EthernetInterface ([410](https://github.com/smoltcp-rs/smoltcp/pull/410), [413](https://github.com/smoltcp-rs/smoltcp/pull/413)) + +[Unreleased]: https://github.com/smoltcp-rs/smoltcp/compare/v0.13.0...HEAD +[0.13.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.12.0...v0.13.0 +[0.12.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.11.0...v0.12.0 +[0.11.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.10.0...v0.11.0 +[0.10.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.9.1...v0.10.0 +[0.9.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.9.0...v0.9.1 +[0.9.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.8.2...v0.9.0 +[0.8.2]: https://github.com/smoltcp-rs/smoltcp/compare/v0.8.1...v0.8.2 +[0.8.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.8.0...v0.8.1 +[0.8.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.0...v0.8.0 +[0.7.5]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.4...v0.7.5 +[0.7.4]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.3...v0.7.4 +[0.7.3]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.2...v0.7.3 +[0.7.2]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.1...v0.7.2 +[0.7.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.7.0...v0.7.1 +[0.7.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.6.0...v0.7.0 diff --git a/vendor/smoltcp/Cargo.toml b/vendor/smoltcp/Cargo.toml new file mode 100644 index 00000000..3f2a8742 --- /dev/null +++ b/vendor/smoltcp/Cargo.toml @@ -0,0 +1,360 @@ +[package] +name = "smoltcp" +version = "0.13.0" +edition = "2024" +rust-version = "1.86" +authors = ["whitequark "] +description = "A TCP/IP stack designed for bare-metal, real-time systems without a heap." +documentation = "https://docs.rs/smoltcp/" +homepage = "https://github.com/smoltcp-rs/smoltcp" +repository = "https://github.com/smoltcp-rs/smoltcp.git" +readme = "README.md" +keywords = ["ip", "tcp", "udp", "ethernet", "network"] +categories = ["embedded", "network-programming"] +license = "0BSD" +# Each example should have an explicit `[[example]]` section here to +# ensure that the correct features are enabled. +autoexamples = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + +[dependencies] +managed = { version = "0.8", default-features = false, features = ["map"] } +byteorder = { version = "1.0", default-features = false } +log = { version = "0.4.4", default-features = false, optional = true } +libc = { version = "0.2.18", optional = true } +bitflags = { version = "1.0", default-features = false } +defmt = { version = "0.3.8", optional = true, features = ["ip_in_core"] } +cfg-if = "1.0.0" +heapless = "0.8" + +[dev-dependencies] +env_logger = "0.10" +getopts = "0.2" +rand = "0.8" +url = "2.0" +rstest = "0.17" +insta = "1.41.1" +rand_chacha = "0.3.1" +idna = { version = "=1.0.1" } + +[features] +std = ["managed/std", "alloc"] +alloc = ["managed/alloc", "defmt?/alloc"] +verbose = [] +defmt = ["dep:defmt", "heapless/defmt-03"] +"medium-ethernet" = ["socket"] +"medium-ip" = ["socket"] +"medium-ieee802154" = ["socket", "proto-sixlowpan"] + +"phy-raw_socket" = ["std", "libc"] +"phy-tuntap_interface" = ["std", "libc"] + +"proto-ipv4" = [] +"proto-ipv4-fragmentation" = ["proto-ipv4", "_proto-fragmentation"] +"proto-dhcpv4" = ["proto-ipv4"] +"proto-ipv6" = [] +"proto-ipv6-hbh" = ["proto-ipv6"] +"proto-ipv6-fragmentation" = ["proto-ipv6", "_proto-fragmentation"] +"proto-ipv6-routing" = ["proto-ipv6"] +"proto-ipv6-slaac" = ["proto-ipv6"] +"proto-rpl" = ["proto-ipv6-hbh", "proto-ipv6-routing"] +"proto-sixlowpan" = ["proto-ipv6"] +"proto-sixlowpan-fragmentation" = ["proto-sixlowpan", "_proto-fragmentation"] +"proto-dns" = [] +"proto-ipsec" = ["proto-ipsec-ah", "proto-ipsec-esp"] +"proto-ipsec-ah" = [] +"proto-ipsec-esp" = [] + +"multicast" = [] + +"socket" = [] +"socket-raw" = ["socket"] +"socket-udp" = ["socket"] +"socket-tcp" = ["socket"] +"socket-tcp-pause-synack" = ["socket-tcp"] +"socket-icmp" = ["socket"] +"socket-dhcpv4" = ["socket", "medium-ethernet", "proto-dhcpv4"] +"socket-dns" = ["socket", "proto-dns"] +"socket-mdns" = ["socket-dns"] + +# Enable Cubic TCP congestion control algorithm, and it is used as a default congestion controller. +# +# Cubic relies on double precision (`f64`) floating point operations, which may cause issues in some contexts: +# +# * Small embedded processors (such as Cortex-M0, Cortex-M1, and Cortex-M3) do not have an FPU, +# and floating point operations consume significant amounts of CPU time and Flash space. +# * Interrupt handlers should almost always avoid floating-point operations. +# * Kernel-mode code on desktop processors usually avoids FPU operations to reduce the penalty of saving and restoring FPU registers. +# +# In all these cases, `CongestionControl::Reno` is a better choice of congestion control algorithm. +"socket-tcp-cubic" = [] + +# Enable Reno TCP congestion control algorithm, and it is used as a default congestion controller. +"socket-tcp-reno" = [] + +"packetmeta-id" = [] + +"async" = [] + +# Automatically reply on an ICMP echo request +"auto-icmp-echo-reply" = [] + +default = [ + "std", "log", # needed for `cargo test --no-default-features --features default` :/ + "medium-ethernet", "medium-ip", "medium-ieee802154", + "phy-raw_socket", "phy-tuntap_interface", + "proto-ipv4", "proto-dhcpv4", "proto-ipv6", "proto-ipv6-slaac", "proto-dns", + "proto-ipv4-fragmentation", "proto-sixlowpan-fragmentation", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dhcpv4", "socket-dns", "socket-mdns", + "packetmeta-id", "async", "multicast", "auto-icmp-echo-reply" +] + +# Private features +# Features starting with "_" are considered private. They should not be enabled by +# other crates, and they are not considered semver-stable. + +"_proto-fragmentation" = [] + +"_netsim" = [] + +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +iface-max-addr-count-1 = [] +iface-max-addr-count-2 = [] # Default +iface-max-addr-count-3 = [] +iface-max-addr-count-4 = [] +iface-max-addr-count-5 = [] +iface-max-addr-count-6 = [] +iface-max-addr-count-7 = [] +iface-max-addr-count-8 = [] + +iface-max-multicast-group-count-1 = [] +iface-max-multicast-group-count-2 = [] +iface-max-multicast-group-count-3 = [] +iface-max-multicast-group-count-4 = [] # Default +iface-max-multicast-group-count-5 = [] +iface-max-multicast-group-count-6 = [] +iface-max-multicast-group-count-7 = [] +iface-max-multicast-group-count-8 = [] +iface-max-multicast-group-count-16 = [] +iface-max-multicast-group-count-32 = [] +iface-max-multicast-group-count-64 = [] +iface-max-multicast-group-count-128 = [] +iface-max-multicast-group-count-256 = [] +iface-max-multicast-group-count-512 = [] +iface-max-multicast-group-count-1024 = [] + +iface-max-sixlowpan-address-context-count-1 = [] +iface-max-sixlowpan-address-context-count-2 = [] +iface-max-sixlowpan-address-context-count-3 = [] +iface-max-sixlowpan-address-context-count-4 = [] # Default +iface-max-sixlowpan-address-context-count-5 = [] +iface-max-sixlowpan-address-context-count-6 = [] +iface-max-sixlowpan-address-context-count-7 = [] +iface-max-sixlowpan-address-context-count-8 = [] +iface-max-sixlowpan-address-context-count-16 = [] +iface-max-sixlowpan-address-context-count-32 = [] +iface-max-sixlowpan-address-context-count-64 = [] +iface-max-sixlowpan-address-context-count-128 = [] +iface-max-sixlowpan-address-context-count-256 = [] +iface-max-sixlowpan-address-context-count-512 = [] +iface-max-sixlowpan-address-context-count-1024 = [] + +iface-neighbor-cache-count-1 = [] +iface-neighbor-cache-count-2 = [] +iface-neighbor-cache-count-3 = [] +iface-neighbor-cache-count-4 = [] +iface-neighbor-cache-count-5 = [] +iface-neighbor-cache-count-6 = [] +iface-neighbor-cache-count-7 = [] +iface-neighbor-cache-count-8 = [] # Default +iface-neighbor-cache-count-16 = [] +iface-neighbor-cache-count-32 = [] +iface-neighbor-cache-count-64 = [] +iface-neighbor-cache-count-128 = [] +iface-neighbor-cache-count-256 = [] +iface-neighbor-cache-count-512 = [] +iface-neighbor-cache-count-1024 = [] + +iface-max-route-count-0 = [] +iface-max-route-count-1 = [] +iface-max-route-count-2 = [] # Default +iface-max-route-count-3 = [] +iface-max-route-count-4 = [] +iface-max-route-count-5 = [] +iface-max-route-count-6 = [] +iface-max-route-count-7 = [] +iface-max-route-count-8 = [] +iface-max-route-count-16 = [] +iface-max-route-count-32 = [] +iface-max-route-count-64 = [] +iface-max-route-count-128 = [] +iface-max-route-count-256 = [] +iface-max-route-count-512 = [] +iface-max-route-count-1024 = [] + +iface-max-prefix-count-1 = [] # Default +iface-max-prefix-count-2 = [] +iface-max-prefix-count-3 = [] +iface-max-prefix-count-4 = [] +iface-max-prefix-count-5 = [] +iface-max-prefix-count-6 = [] +iface-max-prefix-count-7 = [] +iface-max-prefix-count-8 = [] + +fragmentation-buffer-size-256 = [] +fragmentation-buffer-size-512 = [] +fragmentation-buffer-size-1024 = [] +fragmentation-buffer-size-1500 = [] # Default +fragmentation-buffer-size-2048 = [] +fragmentation-buffer-size-4096 = [] +fragmentation-buffer-size-8192 = [] +fragmentation-buffer-size-16384 = [] +fragmentation-buffer-size-32768 = [] +fragmentation-buffer-size-65536 = [] + +assembler-max-segment-count-1 = [] +assembler-max-segment-count-2 = [] +assembler-max-segment-count-3 = [] +assembler-max-segment-count-4 = [] # Default +assembler-max-segment-count-8 = [] +assembler-max-segment-count-16 = [] +assembler-max-segment-count-32 = [] + +reassembly-buffer-size-256 = [] +reassembly-buffer-size-512 = [] +reassembly-buffer-size-1024 = [] +reassembly-buffer-size-1500 = [] # Default +reassembly-buffer-size-2048 = [] +reassembly-buffer-size-4096 = [] +reassembly-buffer-size-8192 = [] +reassembly-buffer-size-16384 = [] +reassembly-buffer-size-32768 = [] +reassembly-buffer-size-65536 = [] + +reassembly-buffer-count-1 = [] # Default +reassembly-buffer-count-2 = [] +reassembly-buffer-count-3 = [] +reassembly-buffer-count-4 = [] +reassembly-buffer-count-8 = [] +reassembly-buffer-count-16 = [] +reassembly-buffer-count-32 = [] + +ipv6-hbh-max-options-1 = [] +ipv6-hbh-max-options-2 = [] +ipv6-hbh-max-options-3 = [] +ipv6-hbh-max-options-4 = [] # Default +ipv6-hbh-max-options-8 = [] +ipv6-hbh-max-options-16 = [] +ipv6-hbh-max-options-32 = [] + +dns-max-result-count-1 = [] # Default +dns-max-result-count-2 = [] +dns-max-result-count-3 = [] +dns-max-result-count-4 = [] +dns-max-result-count-8 = [] +dns-max-result-count-16 = [] +dns-max-result-count-32 = [] + +dns-max-server-count-1 = [] # Default +dns-max-server-count-2 = [] +dns-max-server-count-3 = [] +dns-max-server-count-4 = [] +dns-max-server-count-8 = [] +dns-max-server-count-16 = [] +dns-max-server-count-32 = [] + +dns-max-name-size-64 = [] +dns-max-name-size-128 = [] +dns-max-name-size-255 = [] # Default + +rpl-relations-buffer-count-1 = [] +rpl-relations-buffer-count-2 = [] +rpl-relations-buffer-count-4 = [] +rpl-relations-buffer-count-8 = [] +rpl-relations-buffer-count-16 = [] # Default +rpl-relations-buffer-count-32 = [] +rpl-relations-buffer-count-64 = [] +rpl-relations-buffer-count-128 = [] + +rpl-parents-buffer-count-2 = [] +rpl-parents-buffer-count-4 = [] +rpl-parents-buffer-count-8 = [] # Default +rpl-parents-buffer-count-16 = [] +rpl-parents-buffer-count-32 = [] + +# END AUTOGENERATED CONFIG FEATURES + +[[test]] +name = "netsim" +required-features = ["_netsim"] + +[[example]] +name = "packet2pcap" +path = "utils/packet2pcap.rs" +required-features = ["std"] + +[[example]] +name = "tcpdump" +required-features = ["std", "phy-raw_socket", "proto-ipv4"] + +[[example]] +name = "httpclient" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-ipv6", "socket-tcp"] + +[[example]] +name = "ping" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-ipv6", "socket-icmp"] + +[[example]] +name = "server" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-tcp", "socket-udp"] + +[[example]] +name = "client" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-tcp", "socket-udp"] + +[[example]] +name = "loopback" +required-features = ["log", "medium-ethernet", "proto-ipv4", "socket-tcp"] + +[[example]] +name = "loopback_benchmark" +required-features = ["std", "log", "medium-ethernet", "proto-ipv4", "socket-tcp"] + +[[example]] +name = "multicast" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "multicast", "socket-udp"] + +[[example]] +name = "multicast6" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv6", "socket-udp"] + +[[example]] +name = "benchmark" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-raw", "socket-udp"] + +[[example]] +name = "dhcp_client" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "proto-dhcpv4", "socket-raw"] + +[[example]] +name = "sixlowpan" +required-features = ["std", "medium-ieee802154", "phy-raw_socket", "proto-sixlowpan", "proto-sixlowpan-fragmentation", "socket-udp"] + +[[example]] +name = "sixlowpan_benchmark" +required-features = ["std", "medium-ieee802154", "phy-raw_socket", "proto-sixlowpan", "proto-sixlowpan-fragmentation", "socket-udp"] + +[[example]] +name = "dns" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv4", "socket-dns"] + +[[example]] +name = "slaac" +required-features = ["std", "medium-ethernet", "medium-ip", "phy-tuntap_interface", "proto-ipv6", "socket-udp"] + +[profile.release] +debug = 2 diff --git a/vendor/smoltcp/LICENSE-0BSD.txt b/vendor/smoltcp/LICENSE-0BSD.txt new file mode 100644 index 00000000..f4d92fe7 --- /dev/null +++ b/vendor/smoltcp/LICENSE-0BSD.txt @@ -0,0 +1,13 @@ +Copyright (C) smoltcp contributors + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/vendor/smoltcp/README.md b/vendor/smoltcp/README.md new file mode 100644 index 00000000..408fbe29 --- /dev/null +++ b/vendor/smoltcp/README.md @@ -0,0 +1,589 @@ +# smoltcp + +[![docs.rs](https://docs.rs/smoltcp/badge.svg)](https://docs.rs/smoltcp) +[![crates.io](https://img.shields.io/crates/v/smoltcp.svg)](https://crates.io/crates/smoltcp) +[![crates.io](https://img.shields.io/crates/d/smoltcp.svg)](https://crates.io/crates/smoltcp) +[![crates.io](https://img.shields.io/matrix/smoltcp:matrix.org)](https://matrix.to/#/#smoltcp:matrix.org) +[![codecov](https://codecov.io/github/smoltcp-rs/smoltcp/branch/master/graph/badge.svg?token=3KbAR9xH1t)](https://codecov.io/github/smoltcp-rs/smoltcp) + +_smoltcp_ is a standalone, event-driven TCP/IP stack that is designed for bare-metal, +real-time systems. Its design goals are simplicity and robustness. Its design anti-goals +include complicated compile-time computations, such as macro or type tricks, even +at cost of performance degradation. + +_smoltcp_ does not need heap allocation *at all*, is [extensively documented][docs], +and compiles on stable Rust 1.86 and later. + +_smoltcp_ achieves [~Gbps of throughput](#examplesbenchmarkrs) when tested against +the Linux TCP stack in loopback mode. + +[docs]: https://docs.rs/smoltcp/ + +## Features + +_smoltcp_ is missing many widely deployed features, usually because no one implemented them yet. +To set expectations right, both implemented and omitted features are listed. + +### Media layer + +There are 3 supported mediums. + +* Ethernet + * Regular Ethernet II frames are supported. + * Unicast, broadcast and multicast packets are supported. + * ARP packets (including gratuitous requests and replies) are supported. + * ARP requests are sent at a rate not exceeding one per second. + * Cached ARP entries expire after one minute. + * 802.3 frames and 802.1Q are **not** supported. + * Jumbo frames are **not** supported. +* IP + * Unicast, broadcast and multicast packets are supported. +* IEEE 802.15.4 + * Only support for data frames. + +### IP layer + +#### IPv4 + + * IPv4 header checksum is generated and validated. + * IPv4 time-to-live value is configurable per socket, set to 64 by default. + * IPv4 default gateway is supported. + * Routing outgoing IPv4 packets is supported, through a default gateway or a CIDR route table. + * IPv4 fragmentation and reassembly is supported. + * IPv4 options are **not** supported and are silently ignored. + +#### IPv6 + + * IPv6 hop-limit value is configurable per socket, set to 64 by default. + * Routing outgoing IPv6 packets is supported, through a default gateway or a CIDR route table. + * IPv6 hop-by-hop header is supported. + * ICMPv6 parameter problem message is generated in response to an unrecognized IPv6 next header. + * ICMPv6 parameter problem message is **not** generated in response to an unknown IPv6 + hop-by-hop option. + +#### 6LoWPAN + + * Implementation of [RFC6282](https://tools.ietf.org/rfc/rfc6282.txt). + * Fragmentation is supported, as defined in [RFC4944](https://tools.ietf.org/rfc/rfc4944.txt). + * UDP header compression/decompression is supported. + * Extension header compression/decompression is supported. + * Uncompressed IPv6 Extension Headers are **not** supported. + +### IP multicast + +#### IGMP + +The IGMPv1 and IGMPv2 protocols are supported, and IPv4 multicast is available. + + * Membership reports are sent in response to membership queries at + equal intervals equal to the maximum response time divided by the + number of groups to be reported. + +### ICMP layer + +#### ICMPv4 + +The ICMPv4 protocol is supported, and ICMP sockets are available. + + * ICMPv4 header checksum is supported. + * ICMPv4 echo replies are generated in response to echo requests by default. + * ICMP sockets can listen to ICMPv4 Port Unreachable messages, or any ICMPv4 messages with + a given IPv4 identifier field. + * ICMPv4 protocol unreachable messages are **not** passed to higher layers when received. + * ICMPv4 parameter problem messages are **not** generated. + +#### ICMPv6 + +The ICMPv6 protocol is supported, and ICMP sockets are available. + + * ICMPv6 header checksum is supported. + * ICMPv6 echo replies are generated in response to echo requests by default. + * ICMPv6 protocol unreachable messages are **not** passed to higher layers when received. + +#### NDISC + + * Neighbor Advertisement messages are generated in response to Neighbor Solicitations. + * Router Advertisement messages are read, but **not** generated. + * Router Solicitation messages are generated, but **not** read. + * Redirected Header messages are **not** generated or read. + +### UDP layer + +The UDP protocol is supported over IPv4 and IPv6, and UDP sockets are available. + + * Header checksum is always generated and validated. + * In response to a packet arriving at a port without a listening socket, + an ICMP destination unreachable message is generated. + +### TCP layer + +The TCP protocol is supported over IPv4 and IPv6, and server and client TCP sockets are available. + + * Header checksum is generated and validated. + * Maximum segment size is negotiated. + * Window scaling is negotiated. + * Multiple packets are transmitted without waiting for an acknowledgement. + * Reassembly of out-of-order segments is supported, with no more than 4 or 32 gaps in sequence space. + * Keep-alive packets may be sent at a configurable interval. + * Retransmission timeout starts at at an estimate of RTT, and doubles every time. + * Time-wait timeout has a fixed interval of 10 s. + * User timeout has a configurable interval. + * Delayed acknowledgements are supported, with configurable delay. + * Nagle's algorithm is implemented. + * Selective acknowledgements are **not** implemented. + * Silly window syndrome avoidance is **not** implemented. + * Congestion control is optional, `CUBIC` and `Reno` are implemented. + * Timestamping is **not** supported. + * Urgent pointer is **ignored**. + * Probing Zero Windows is implemented. + * Packetization Layer Path MTU Discovery [PLPMTU](https://tools.ietf.org/rfc/rfc4821.txt) is **not** implemented. + +## Installation + +To use the _smoltcp_ library in your project, add the following to `Cargo.toml`: + +```toml +[dependencies] +smoltcp = "0.10.0" +``` + +The default configuration assumes a hosted environment, for ease of evaluation. +You probably want to disable default features and configure them one by one: + +```toml +[dependencies] +smoltcp = { version = "0.10.0", default-features = false, features = ["log"] } +``` + +## Feature flags + +### Feature `std` + +The `std` feature enables use of objects and slices owned by the networking stack through a +dependency on `std::boxed::Box` and `std::vec::Vec`. + +This feature is enabled by default. + +### Feature `alloc` + +The `alloc` feature enables use of objects owned by the networking stack through a dependency +on collections from the `alloc` crate. This only works on nightly rustc. + +This feature is disabled by default. + +### Feature `log` + +The `log` feature enables logging of events within the networking stack through +the [log crate][log]. Normal events (e.g. buffer level or TCP state changes) are emitted with +the TRACE log level. Exceptional events (e.g. malformed packets) are emitted with +the DEBUG log level. + +[log]: https://crates.io/crates/log + +This feature is enabled by default. + +### Feature `defmt` + +The `defmt` feature enables logging of events with the [defmt crate][defmt]. + +[defmt]: https://crates.io/crates/defmt + +This feature is disabled by default, and cannot be used at the same time as `log`. + +### Feature `verbose` + +The `verbose` feature enables logging of events where the logging itself may incur very high +overhead. For example, emitting a log line every time an application reads or writes as little +as 1 octet from a socket is likely to overwhelm the application logic unless a `BufReader` +or `BufWriter` is used, which are of course not available on heap-less systems. + +This feature is disabled by default. + +### Features `phy-raw_socket` and `phy-tuntap_interface` + +Enable `smoltcp::phy::RawSocket` and `smoltcp::phy::TunTapInterface`, respectively. + +These features are enabled by default. + +### Features `socket-raw`, `socket-udp`, `socket-tcp`, `socket-icmp`, `socket-dhcpv4`, `socket-dns` + +Enable the corresponding socket type. + +These features are enabled by default. + +### Features `proto-ipv4`, `proto-ipv6` and `proto-sixlowpan` + +Enable [IPv4], [IPv6] and [6LoWPAN] respectively. + +[IPv4]: https://tools.ietf.org/rfc/rfc791.txt +[IPv6]: https://tools.ietf.org/rfc/rfc8200.txt +[6LoWPAN]: https://tools.ietf.org/rfc/rfc6282.txt + +## Configuration + +_smoltcp_ has some configuration settings that are set at compile time, affecting sizes +and counts of buffers. + +They can be set in two ways: + +- Via Cargo features: enable a feature like `-`. `name` must be in lowercase and +use dashes instead of underscores. For example. `iface-max-addr-count-3`. Only a selection of values +is available, check `Cargo.toml` for the list. +- Via environment variables at build time: set the variable named `SMOLTCP_`. For example +`SMOLTCP_IFACE_MAX_ADDR_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. +Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +### `IFACE_MAX_ADDR_COUNT` + +Max amount of IP addresses that can be assigned to one interface (counting both IPv4 and IPv6 addresses). Default: 2. + +### `IFACE_MAX_MULTICAST_GROUP_COUNT` + +Max amount of multicast groups that can be joined by one interface. Default: 4. + +### `IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT` + +Max amount of 6LoWPAN address contexts that can be assigned to one interface. Default: 4. + +### `IFACE_NEIGHBOR_CACHE_COUNT` + +Amount of "IP address -> hardware address" entries the neighbor cache (also known as the "ARP cache" or the "ARP table") holds. Default: 4. + +### `IFACE_MAX_ROUTE_COUNT` + +Max amount of routes that can be added to one interface. Includes the default route. Includes both IPv4 and IPv6. Default: 2. + +### `IFACE_MAX_PREFIX_COUNT` + +Max amount of IPv6 prefixes that can be added to one interface via SLAAC. +Should be lower or equal to `IFACE_MAX_ADDR_COUNT`. + +### `FRAGMENTATION_BUFFER_SIZE` + +Size of the buffer used for fragmenting outgoing packets larger than the MTU. Packets larger than this setting will be dropped instead of fragmented. Default: 1500. + +### `ASSEMBLER_MAX_SEGMENT_COUNT` + +Maximum number of non-contiguous segments the assembler can hold. Used for both packet reassembly and TCP stream reassembly. Default: 4. + +### `REASSEMBLY_BUFFER_SIZE` + +Size of the buffer used for reassembling (de-fragmenting) incoming packets. If the reassembled packet is larger than this setting, it will be dropped instead of reassembled. Default: 1500. + +### `REASSEMBLY_BUFFER_COUNT` + +Number of reassembly buffers, i.e how many different incoming packets can be reassembled at the same time. Default: 1. + +### `DNS_MAX_RESULT_COUNT` + +Maximum amount of address results for a given DNS query that will be kept. For example, if this is set to 2 and the queried name has 4 `A` records, only the first 2 will be returned. Default: 1. + +### `DNS_MAX_SERVER_COUNT` + +Maximum amount of DNS servers that can be configured in one DNS socket. Default: 1. + +### `DNS_MAX_NAME_SIZE` + +Maximum length of DNS names that can be queried. Default: 255. + +### IPV6_HBH_MAX_OPTIONS + +The maximum amount of parsed options the IPv6 Hop-by-Hop header can hold. Default: 4. + +## Hosted usage examples + +_smoltcp_, being a freestanding networking stack, needs to be able to transmit and receive +raw frames. For testing purposes, we will use a regular OS, and run _smoltcp_ in +a userspace process. Only Linux is supported (right now). + +On \*nix OSes, transmitting and receiving raw frames normally requires superuser privileges, but +on Linux it is possible to create a _persistent tap interface_ that can be manipulated by +a specific user: + +```sh +sudo ip tuntap add name tap0 mode tap user $USER +sudo ip link set tap0 up +sudo ip addr add 192.168.69.100/24 dev tap0 +sudo ip -6 addr add fe80::100/64 dev tap0 +sudo ip -6 addr add fdaa::100/64 dev tap0 +sudo ip -6 route add fe80::/64 dev tap0 +sudo ip -6 route add fdaa::/64 dev tap0 +``` + +It's possible to let _smoltcp_ access Internet by enabling routing for the tap interface: + +```sh +sudo iptables -t nat -A POSTROUTING -s 192.168.69.0/24 -j MASQUERADE +sudo sysctl net.ipv4.ip_forward=1 +sudo ip6tables -t nat -A POSTROUTING -s fdaa::/64 -j MASQUERADE +sudo sysctl -w net.ipv6.conf.all.forwarding=1 + +# Some distros have a default policy of DROP. This allows the traffic. +sudo iptables -A FORWARD -i tap0 -s 192.168.69.0/24 -j ACCEPT +sudo iptables -A FORWARD -o tap0 -d 192.168.69.0/24 -j ACCEPT +``` + +### Bridged connection + +Instead of the routed connection above, you may also set up a bridged (switched) +connection. This will make smoltcp speak directly to your LAN, with real ARP, etc. +It is needed to run the DHCP example. + +NOTE: In this case, the examples' IP configuration must match your LAN's! + +NOTE: this ONLY works with actual wired Ethernet connections. It +will NOT work on a WiFi connection. + +```sh +# Replace with your wired Ethernet interface name +ETH=enp0s20f0u1u1 + +sudo modprobe bridge +sudo modprobe br_netfilter + +sudo sysctl -w net.bridge.bridge-nf-call-arptables=0 +sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0 +sudo sysctl -w net.bridge.bridge-nf-call-iptables=0 + +sudo ip tuntap add name tap0 mode tap user $USER +sudo brctl addbr br0 +sudo brctl addif br0 tap0 +sudo brctl addif br0 $ETH +sudo ip link set tap0 up +sudo ip link set $ETH up +sudo ip link set br0 up + +# This connects your host system to the internet, so you can use it +# at the same time you run the examples. +sudo dhcpcd br0 +``` + +To tear down: + +``` +sudo killall dhcpcd +sudo ip link set br0 down +sudo brctl delbr br0 +``` + +### Fault injection + +In order to demonstrate the response of _smoltcp_ to adverse network conditions, all examples +implement fault injection, available through command-line options: + + * The `--drop-chance` option randomly drops packets, with given probability in percents. + * The `--corrupt-chance` option randomly mutates one octet in a packet, with given + probability in percents. + * The `--size-limit` option drops packets larger than specified size. + * The `--tx-rate-limit` and `--rx-rate-limit` options set the amount of tokens for + a token bucket rate limiter, in packets per bucket. + * The `--shaping-interval` option sets the refill interval of a token bucket rate limiter, + in milliseconds. + +A good starting value for `--drop-chance` and `--corrupt-chance` is 15%. A good starting +value for `--?x-rate-limit` is 4 and `--shaping-interval` is 50 ms. + +Note that packets dropped by the fault injector still get traced; +the `rx: randomly dropping a packet` message indicates that the packet *above* it got dropped, +and the `tx: randomly dropping a packet` message indicates that the packet *below* it was. + +### Packet dumps + +All examples provide a `--pcap` option that writes a [libpcap] file containing a view of every +packet as it is seen by _smoltcp_. + +[libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat + +### examples/tcpdump.rs + +_examples/tcpdump.rs_ is a tiny clone of the _tcpdump_ utility. + +Unlike the rest of the examples, it uses raw sockets, and so it can be used on regular interfaces, +e.g. `eth0` or `wlan0`, as well as the `tap0` interface we've created above. + +Read its [source code](/examples/tcpdump.rs), then run it as: + +```sh +cargo build --example tcpdump +sudo ./target/debug/examples/tcpdump eth0 +``` + +### examples/httpclient.rs + +_examples/httpclient.rs_ emulates a network host that can initiate HTTP requests. + +The host is assigned the hardware address `02-00-00-00-00-02`, IPv4 address `192.168.69.1`, and IPv6 address `fdaa::1`. + +Read its [source code](/examples/httpclient.rs), then run it as: + +```sh +cargo run --example httpclient -- --tap tap0 ADDRESS URL +``` + +For example: + +```sh +cargo run --example httpclient -- --tap tap0 93.184.216.34 http://example.org/ +``` + +or: + +```sh +cargo run --example httpclient -- --tap tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/ +``` + +It connects to the given address (not a hostname) and URL, and prints any returned response data. +The TCP socket buffers are limited to 1024 bytes to make packet traces more interesting. + +### examples/ping.rs + +_examples/ping.rs_ implements a minimal version of the `ping` utility using raw sockets. + +The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`. + +Read its [source code](/examples/ping.rs), then run it as: + +```sh +cargo run --example ping -- --tap tap0 ADDRESS +``` + +It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and +prints out a status line on each valid ECHO\_RESPONSE received. + +The first ECHO\_REQUEST packet is expected to be lost since arp\_cache is empty after startup; +the ECHO\_REQUEST packet is dropped and an ARP request is sent instead. + +Currently, netmasks are not implemented, and so the only address this example can reach +is the other endpoint of the tap interface, `192.168.69.100`. It cannot reach itself because +packets entering a tap interface do not loop back. + +### examples/server.rs + +_examples/server.rs_ emulates a network host that can respond to basic requests. + +The host is assigned the hardware address `02-00-00-00-00-01` and IPv4 address `192.168.69.1`. + +Read its [source code](/examples/server.rs), then run it as: + +```sh +cargo run --example server -- --tap tap0 +``` + +It responds to: + + * pings (`ping 192.168.69.1`); + * UDP packets on port 6969 (`socat stdio udp4-connect:192.168.69.1:6969 <<<"abcdefg"`), + where it will respond with reversed chunks of the input indefinitely; + * TCP connections on port 6969 (`socat stdio tcp4-connect:192.168.69.1:6969`), + where it will respond "hello" to any incoming connection and immediately close it; + * TCP connections on port 6970 (`socat stdio tcp4-connect:192.168.69.1:6970 <<<"abcdefg"`), + where it will respond with reversed chunks of the input indefinitely. + * TCP connections on port 6971 (`socat stdio tcp4-connect:192.168.69.1:6971 /dev/null`), + which will source data. + +Except for the socket on port 6971. the buffers are only 64 bytes long, for convenience +of testing resource exhaustion conditions. + +### examples/client.rs + +_examples/client.rs_ emulates a network host that can initiate basic requests. + +The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.2`. + +Read its [source code](/examples/client.rs), then run it as: + +```sh +cargo run --example client -- --tap tap0 ADDRESS PORT +``` + +It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen:1234`), +and will respond with reversed chunks of the input indefinitely. + +### examples/benchmark.rs + +_examples/benchmark.rs_ implements a simple throughput benchmark. + +Read its [source code](/examples/benchmark.rs), then run it as: + +```sh +cargo run --release --example benchmark -- --tap tap0 [reader|writer] +``` + +It establishes a connection to itself from a different thread and reads or writes a large amount +of data in one direction. + +A typical result (achieved on a Intel Core i5-13500H CPU and a Linux 6.9.9 x86_64 kernel running +on a LENOVO XiaoXinPro 14 IRH8 laptop) is as follows: + +``` +$ cargo run -q --release --example benchmark -- --tap tap0 reader +throughput: 3.673 Gbps +$ cargo run -q --release --example benchmark -- --tap tap0 writer +throughput: 7.905 Gbps +``` + +## Bare-metal usage examples + +Examples that use no services from the host OS are necessarily less illustrative than examples +that do. Because of this, only one such example is provided. + +### examples/loopback.rs + +_examples/loopback.rs_ sets up _smoltcp_ to talk with itself via a loopback interface. +Although it does not require `std`, this example still requires the `alloc` feature to run, as well as `log`, `proto-ipv4` and `socket-tcp`. + +Read its [source code](/examples/loopback.rs), then run it without `std`: + +```sh +cargo run --example loopback --no-default-features --features="log proto-ipv4 socket-tcp alloc" +``` + +... or with `std` (in this case the features don't have to be explicitly listed): + +```sh +cargo run --example loopback -- --pcap loopback.pcap +``` + +It opens a server and a client TCP socket, and transfers a chunk of data. You can examine +the packet exchange by opening `loopback.pcap` in [Wireshark]. + +If the `std` feature is enabled, it will print logs and packet dumps, and fault injection +is possible; otherwise, nothing at all will be displayed and no options are accepted. + +[wireshark]: https://wireshark.org + +### examples/loopback\_benchmark.rs + +_examples/loopback_benchmark.rs_ is another simple throughput benchmark. + +Read its [source code](/examples/loopback_benchmark.rs), then run it as: + +```sh +cargo run --release --example loopback_benchmark +``` + +It establishes a connection to itself via a loopback interface and transfers a large amount +of data in one direction. + +A typical result (achieved on a Intel Core i5-13500H CPU and a Linux 6.9.9 x86_64 kernel running +on a LENOVO XiaoXinPro 14 IRH8 laptop) is as follows: + +``` +$ cargo run --release --example loopback_benchmark +done in 0.558 s, bandwidth is 15.395083 Gbps +``` + +Note: Although the loopback interface can be used in bare-metal environments, +this benchmark _does_ rely on `std` to be able to measure the time cost. + +## License + +_smoltcp_ is distributed under the terms of 0-clause BSD license. + +See [LICENSE-0BSD](LICENSE-0BSD.txt) for details. diff --git a/vendor/smoltcp/benches/bench.rs b/vendor/smoltcp/benches/bench.rs new file mode 100644 index 00000000..a05ccff9 --- /dev/null +++ b/vendor/smoltcp/benches/bench.rs @@ -0,0 +1,114 @@ +#![feature(test)] + +mod wire { + use smoltcp::phy::ChecksumCapabilities; + use smoltcp::wire::{IpAddress, IpProtocol}; + #[cfg(feature = "proto-ipv4")] + use smoltcp::wire::{Ipv4Address, Ipv4Packet, Ipv4Repr}; + #[cfg(feature = "proto-ipv6")] + use smoltcp::wire::{Ipv6Address, Ipv6Packet, Ipv6Repr}; + use smoltcp::wire::{TcpControl, TcpPacket, TcpRepr, TcpSeqNumber}; + use smoltcp::wire::{UdpPacket, UdpRepr}; + + extern crate test; + + #[cfg(feature = "proto-ipv6")] + const SRC_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); + #[cfg(feature = "proto-ipv6")] + const DST_ADDR: IpAddress = IpAddress::Ipv6(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2)); + + #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] + const SRC_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address::new(192, 168, 1, 1)); + #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] + const DST_ADDR: IpAddress = IpAddress::Ipv4(Ipv4Address::new(192, 168, 1, 2)); + + #[bench] + #[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))] + fn bench_emit_tcp(b: &mut test::Bencher) { + static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400]; + let repr = TcpRepr { + src_port: 48896, + dst_port: 80, + control: TcpControl::Syn, + seq_number: TcpSeqNumber(0x01234567), + ack_number: None, + window_len: 0x0123, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + payload: &PAYLOAD_BYTES, + timestamp: None, + }; + let mut bytes = vec![0xa5; repr.buffer_len()]; + + b.iter(|| { + let mut packet = TcpPacket::new_unchecked(&mut bytes); + repr.emit( + &mut packet, + &SRC_ADDR, + &DST_ADDR, + &ChecksumCapabilities::default(), + ); + }); + } + + #[bench] + #[cfg(any(feature = "proto-ipv6", feature = "proto-ipv4"))] + fn bench_emit_udp(b: &mut test::Bencher) { + static PAYLOAD_BYTES: [u8; 400] = [0x2a; 400]; + let repr = UdpRepr { + src_port: 48896, + dst_port: 80, + }; + let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()]; + + b.iter(|| { + let mut packet = UdpPacket::new_unchecked(&mut bytes); + repr.emit( + &mut packet, + &SRC_ADDR, + &DST_ADDR, + PAYLOAD_BYTES.len(), + |buf| buf.copy_from_slice(&PAYLOAD_BYTES), + &ChecksumCapabilities::default(), + ); + }); + } + + #[bench] + #[cfg(feature = "proto-ipv4")] + fn bench_emit_ipv4(b: &mut test::Bencher) { + let repr = Ipv4Repr { + src_addr: Ipv4Address::new(192, 168, 1, 1), + dst_addr: Ipv4Address::new(192, 168, 1, 2), + next_header: IpProtocol::Tcp, + payload_len: 100, + hop_limit: 64, + }; + let mut bytes = vec![0xa5; repr.buffer_len()]; + + b.iter(|| { + let mut packet = Ipv4Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet, &ChecksumCapabilities::default()); + }); + } + + #[bench] + #[cfg(feature = "proto-ipv6")] + fn bench_emit_ipv6(b: &mut test::Bencher) { + let repr = Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Tcp, + payload_len: 100, + hop_limit: 64, + }; + let mut bytes = vec![0xa5; repr.buffer_len()]; + + b.iter(|| { + let mut packet = Ipv6Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet); + }); + } +} diff --git a/vendor/smoltcp/build.rs b/vendor/smoltcp/build.rs new file mode 100644 index 00000000..0eab8edb --- /dev/null +++ b/vendor/smoltcp/build.rs @@ -0,0 +1,105 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("IFACE_MAX_ADDR_COUNT", 2), + ("IFACE_MAX_MULTICAST_GROUP_COUNT", 4), + ("IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT", 4), + ("IFACE_NEIGHBOR_CACHE_COUNT", 8), + ("IFACE_MAX_ROUTE_COUNT", 2), + ("IFACE_MAX_PREFIX_COUNT", 1), + ("FRAGMENTATION_BUFFER_SIZE", 1500), + ("ASSEMBLER_MAX_SEGMENT_COUNT", 4), + ("REASSEMBLY_BUFFER_SIZE", 1500), + ("REASSEMBLY_BUFFER_COUNT", 1), + ("IPV6_HBH_MAX_OPTIONS", 4), + ("DNS_MAX_RESULT_COUNT", 1), + ("DNS_MAX_SERVER_COUNT", 1), + ("DNS_MAX_NAME_SIZE", 255), + ("RPL_RELATIONS_BUFFER_COUNT", 16), + ("RPL_PARENTS_BUFFER_COUNT", 8), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed=SMOLTCP_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix("SMOLTCP_") { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!( + "multiple values set for feature {}: {} and {}", + name, cfg.value, value + ); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); +} diff --git a/vendor/smoltcp/ci.sh b/vendor/smoltcp/ci.sh new file mode 100755 index 00000000..14451b63 --- /dev/null +++ b/vendor/smoltcp/ci.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +set -eox pipefail + +export DEFMT_LOG=trace + +MSRV="1.86.0" + +RUSTC_VERSIONS=( + $MSRV + "stable" + "nightly" +) + +FEATURES_TEST=( + "default" + "std,proto-ipv4" + "std,medium-ethernet,phy-raw_socket,proto-ipv6,socket-udp,socket-dns" + "std,medium-ethernet,phy-tuntap_interface,proto-ipv6,socket-udp" + "std,medium-ethernet,proto-ipv4,proto-ipv4-fragmentation,socket-raw,socket-dns" + "std,medium-ethernet,proto-ipv4,multicast,socket-raw,socket-dns" + "std,medium-ethernet,proto-ipv4,socket-udp,socket-tcp,socket-dns" + "std,medium-ethernet,proto-ipv4,proto-dhcpv4,socket-udp" + "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv6,multicast,proto-rpl,socket-udp,socket-dns,auto-icmp-echo-reply" + "std,medium-ethernet,proto-ipv6,socket-tcp" + "std,medium-ethernet,proto-ipv6,socket-tcp,proto-ipv6-slaac" + "std,medium-ethernet,medium-ip,proto-ipv4,socket-icmp,socket-tcp" + "std,medium-ip,proto-ipv6,socket-icmp,socket-tcp" + "std,medium-ieee802154,proto-sixlowpan,socket-udp,auto-icmp-echo-reply" + "std,medium-ieee802154,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp,auto-icmp-echo-reply" + "std,medium-ieee802154,proto-rpl,proto-sixlowpan,proto-sixlowpan-fragmentation,socket-udp,auto-icmp-echo-reply" + "std,medium-ip,proto-ipv4,proto-ipv6,socket-tcp,socket-udp" + "std,medium-ethernet,medium-ip,medium-ieee802154,proto-ipv4,proto-ipv6,multicast,proto-rpl,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async,auto-icmp-echo-reply,proto-ipv6-slaac" + "std,medium-ip,proto-ipv4,proto-ipv6,multicast,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "std,medium-ieee802154,medium-ip,proto-ipv4,socket-raw,auto-icmp-echo-reply" + "std,medium-ethernet,proto-ipv4,proto-ipsec,socket-raw" + "alloc,medium-ethernet,proto-ipv4,proto-ipv6,socket-raw,socket-udp,socket-tcp,socket-icmp,proto-ipv6-slaac" +) + +FEATURES_CHECK=( + "medium-ip,medium-ethernet,medium-ieee802154,proto-ipv6,proto-ipv6-slaac,multicast,proto-dhcpv4,proto-ipsec,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "defmt,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6-slaac,multicast,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "defmt,alloc,medium-ip,medium-ethernet,proto-ipv6,proto-ipv6-slaac,multicast,proto-dhcpv4,socket-raw,socket-udp,socket-tcp,socket-icmp,socket-dns,async" + "medium-ieee802154,proto-sixlowpan,socket-dns,auto-icmp-echo-reply" +) + +test() { + local version=$1 + rustup toolchain install $version + + for features in ${FEATURES_TEST[@]}; do + cargo +$version test --no-default-features --features "$features" + done +} + +netsim() { + cargo test --release --features _netsim netsim +} + +check() { + local version=$1 + rustup toolchain install $version + + export DEFMT_LOG="trace" + + for features in ${FEATURES_CHECK[@]}; do + cargo +$version check --no-default-features --features "$features" + done + + cargo +$version check --examples + + if [[ $version == "nightly" ]]; then + cargo +$version check --benches + fi +} + +clippy() { + rustup toolchain install $MSRV + rustup component add clippy --toolchain=$MSRV + cargo +$MSRV clippy --tests --examples -- -D warnings +} + +build_16bit() { + rustup toolchain install nightly + rustup +nightly component add rust-src + + TARGET_WITH_16BIT_POINTER=msp430-none-elf + for features in ${FEATURES_CHECK[@]}; do + cargo +nightly build -Z build-std=core,alloc --target $TARGET_WITH_16BIT_POINTER --no-default-features --features=$features + done +} + +coverage() { + for features in ${FEATURES_TEST[@]}; do + cargo llvm-cov --no-report --no-default-features --features "$features" + done + cargo llvm-cov report --lcov --output-path lcov.info +} + +if [[ $1 == "test" || $1 == "all" ]]; then + if [[ -n $2 ]]; then + if [[ $2 == "msrv" ]]; then + test $MSRV + else + test $2 + fi + else + for version in ${RUSTC_VERSIONS[@]}; do + test $version + done + fi +fi + +if [[ $1 == "check" || $1 == "all" ]]; then + if [[ -n $2 ]]; then + if [[ $2 == "msrv" ]]; then + check $MSRV + else + check $2 + fi + else + for version in ${RUSTC_VERSIONS[@]}; do + check $version + done + fi +fi + +if [[ $1 == "clippy" || $1 == "all" ]]; then + clippy +fi + +if [[ $1 == "build_16bit" || $1 == "all" ]]; then + build_16bit +fi + +if [[ $1 == "coverage" || $1 == "all" ]]; then + coverage +fi + +if [[ $1 == "netsim" || $1 == "all" ]]; then + netsim +fi diff --git a/vendor/smoltcp/examples/benchmark.rs b/vendor/smoltcp/examples/benchmark.rs new file mode 100644 index 00000000..59482e36 --- /dev/null +++ b/vendor/smoltcp/examples/benchmark.rs @@ -0,0 +1,158 @@ +mod utils; + +use std::cmp; +use std::io::{Read, Write}; +use std::net::TcpStream; +use std::os::unix::io::AsRawFd; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::socket::tcp; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; + +const AMOUNT: usize = 1_000_000_000; + +enum Client { + Reader, + Writer, +} + +fn client(kind: Client) { + let port = match kind { + Client::Reader => 1234, + Client::Writer => 1235, + }; + let mut stream = TcpStream::connect(("192.168.69.1", port)).unwrap(); + let mut buffer = vec![0; 1_000_000]; + + let start = Instant::now(); + + let mut processed = 0; + while processed < AMOUNT { + let length = cmp::min(buffer.len(), AMOUNT - processed); + let result = match kind { + Client::Reader => stream.read(&mut buffer[..length]), + Client::Writer => stream.write(&buffer[..length]), + }; + match result { + Ok(0) => break, + Ok(result) => { + // print!("(P:{})", result); + processed += result + } + Err(err) => panic!("cannot process: {err}"), + } + } + + let end = Instant::now(); + + let elapsed = (end - start).total_millis() as f64 / 1000.0; + + println!("throughput: {:.3} Gbps", AMOUNT as f64 / elapsed / 0.125e9); + + CLIENT_DONE.store(true, Ordering::SeqCst); +} + +static CLIENT_DONE: AtomicBool = AtomicBool::new(false); + +fn main() { + #[cfg(feature = "log")] + utils::setup_logging("info"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + free.push("MODE"); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + let mode = match matches.free[0].as_ref() { + "reader" => Client::Reader, + "writer" => Client::Writer, + _ => panic!("invalid mode"), + }; + + let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer); + + let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer); + + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + }); + + let mut sockets = SocketSet::new(vec![]); + let tcp1_handle = sockets.add(tcp1_socket); + let tcp2_handle = sockets.add(tcp2_socket); + let default_timeout = Some(Duration::from_millis(1000)); + + thread::spawn(move || client(mode)); + let mut processed = 0; + while !CLIENT_DONE.load(Ordering::SeqCst) { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + // tcp:1234: emit data + let socket = sockets.get_mut::(tcp1_handle); + if !socket.is_open() { + socket.listen(1234).unwrap(); + } + + while socket.can_send() && processed < AMOUNT { + let length = socket + .send(|buffer| { + let length = cmp::min(buffer.len(), AMOUNT - processed); + (length, length) + }) + .unwrap(); + processed += length; + } + + // tcp:1235: sink data + let socket = sockets.get_mut::(tcp2_handle); + if !socket.is_open() { + socket.listen(1235).unwrap(); + } + + while socket.can_recv() && processed < AMOUNT { + let length = socket + .recv(|buffer| { + let length = cmp::min(buffer.len(), AMOUNT - processed); + (length, length) + }) + .unwrap(); + processed += length; + } + + match iface.poll_at(timestamp, &sockets) { + Some(poll_at) if timestamp < poll_at => { + phy_wait(fd, Some(poll_at - timestamp)).expect("wait error"); + } + Some(_) => (), + None => { + phy_wait(fd, default_timeout).expect("wait error"); + } + } + } +} diff --git a/vendor/smoltcp/examples/client.rs b/vendor/smoltcp/examples/client.rs new file mode 100644 index 00000000..b089f7b9 --- /dev/null +++ b/vendor/smoltcp/examples/client.rs @@ -0,0 +1,118 @@ +mod utils; + +use log::debug; +use std::os::unix::io::AsRawFd; +use std::str::{self, FromStr}; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::socket::tcp; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; + +fn main() { + utils::setup_logging(""); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + free.push("ADDRESS"); + free.push("PORT"); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); + let port = u16::from_str(&matches.free[1]).expect("invalid port format"); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) + .unwrap(); + iface + .routes_mut() + .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) + .unwrap(); + + // Create sockets + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1500]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + let socket = sockets.get_mut::(tcp_handle); + socket + .connect(iface.context(), (address, port), 49500) + .unwrap(); + + let mut tcp_active = false; + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let socket = sockets.get_mut::(tcp_handle); + if socket.is_active() && !tcp_active { + debug!("connected"); + } else if !socket.is_active() && tcp_active { + debug!("disconnected"); + break; + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + let data = socket + .recv(|data| { + let mut data = data.to_owned(); + if !data.is_empty() { + debug!( + "recv data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") + ); + data = data.split(|&b| b == b'\n').collect::>().concat(); + data.reverse(); + data.extend(b"\n"); + } + (data.len(), data) + }) + .unwrap(); + if socket.can_send() && !data.is_empty() { + debug!( + "send data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") + ); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + debug!("close"); + socket.close(); + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/dhcp_client.rs b/vendor/smoltcp/examples/dhcp_client.rs new file mode 100644 index 00000000..7ca30588 --- /dev/null +++ b/vendor/smoltcp/examples/dhcp_client.rs @@ -0,0 +1,95 @@ +#![allow(clippy::option_map_unit_fn)] +mod utils; + +use log::*; +use std::os::unix::io::AsRawFd; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::socket::dhcpv4; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Cidr}; +use smoltcp::{ + phy::{Device, Medium, wait as phy_wait}, + time::Duration, +}; + +fn main() { + #[cfg(feature = "log")] + utils::setup_logging(""); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + let mut iface = Interface::new(config, &mut device, Instant::now()); + + // Create sockets + let mut dhcp_socket = dhcpv4::Socket::new(); + + // Set a ridiculously short max lease time to show DHCP renews work properly. + // This will cause the DHCP client to start renewing after 5 seconds, and give up the + // lease after 10 seconds if renew hasn't succeeded. + // IMPORTANT: This should be removed in production. + dhcp_socket.set_max_lease_duration(Some(Duration::from_secs(10))); + + let mut sockets = SocketSet::new(vec![]); + let dhcp_handle = sockets.add(dhcp_socket); + + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let event = sockets.get_mut::(dhcp_handle).poll(); + match event { + None => {} + Some(dhcpv4::Event::Configured(config)) => { + debug!("DHCP config acquired!"); + + debug!("IP address: {}", config.address); + set_ipv4_addr(&mut iface, config.address); + + if let Some(router) = config.router { + debug!("Default gateway: {}", router); + iface.routes_mut().add_default_ipv4_route(router).unwrap(); + } else { + debug!("Default gateway: None"); + iface.routes_mut().remove_default_ipv4_route(); + } + + for (i, s) in config.dns_servers.iter().enumerate() { + debug!("DNS server {}: {}", i, s); + } + } + Some(dhcpv4::Event::Deconfigured) => { + debug!("DHCP lost config!"); + iface.update_ip_addrs(|addrs| addrs.clear()); + iface.routes_mut().remove_default_ipv4_route(); + } + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} + +/// Clear any existing IP addresses & add the new one +fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) { + iface.update_ip_addrs(|addrs| { + addrs.clear(); + addrs.push(IpCidr::Ipv4(cidr)).unwrap(); + }); +} diff --git a/vendor/smoltcp/examples/dns.rs b/vendor/smoltcp/examples/dns.rs new file mode 100644 index 00000000..d89f5d55 --- /dev/null +++ b/vendor/smoltcp/examples/dns.rs @@ -0,0 +1,92 @@ +mod utils; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::Device; +use smoltcp::phy::{Medium, wait as phy_wait}; +use smoltcp::socket::dns::{self, GetQueryResultError}; +use smoltcp::time::Instant; +use smoltcp::wire::{DnsQueryType, EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; +use std::os::unix::io::AsRawFd; + +fn main() { + utils::setup_logging("warn"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + free.push("ADDRESS"); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + let name = &matches.free[0]; + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) + .unwrap(); + iface + .routes_mut() + .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) + .unwrap(); + + // Create sockets + let servers = &[ + Ipv4Address::new(8, 8, 4, 4).into(), + Ipv4Address::new(8, 8, 8, 8).into(), + ]; + let dns_socket = dns::Socket::new(servers, vec![]); + + let mut sockets = SocketSet::new(vec![]); + let dns_handle = sockets.add(dns_socket); + + let socket = sockets.get_mut::(dns_handle); + let query = socket + .start_query(iface.context(), name, DnsQueryType::A) + .unwrap(); + + loop { + let timestamp = Instant::now(); + log::debug!("timestamp {:?}", timestamp); + + iface.poll(timestamp, &mut device, &mut sockets); + + match sockets + .get_mut::(dns_handle) + .get_query_result(query) + { + Ok(addrs) => { + println!("Query done: {addrs:?}"); + break; + } + Err(GetQueryResultError::Pending) => {} // not done yet + Err(e) => panic!("query failed: {e:?}"), + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/httpclient.rs b/vendor/smoltcp/examples/httpclient.rs new file mode 100644 index 00000000..a15a0b2b --- /dev/null +++ b/vendor/smoltcp/examples/httpclient.rs @@ -0,0 +1,123 @@ +mod utils; + +use log::debug; +use std::os::unix::io::AsRawFd; +use std::str::{self, FromStr}; +use url::Url; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::socket::tcp; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; + +fn main() { + utils::setup_logging(""); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + free.push("ADDRESS"); + free.push("URL"); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); + let url = Url::parse(&matches.free[1]).expect("invalid url format"); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) + .unwrap(); + iface + .routes_mut() + .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) + .unwrap(); + + // Create sockets + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + enum State { + Connect, + Request, + Response, + } + let mut state = State::Connect; + + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let socket = sockets.get_mut::(tcp_handle); + let cx = iface.context(); + + state = match state { + State::Connect if !socket.is_active() => { + debug!("connecting"); + let local_port = 49152 + rand::random::() % 16384; + socket + .connect(cx, (address, url.port().unwrap_or(80)), local_port) + .unwrap(); + State::Request + } + State::Request if socket.may_send() => { + debug!("sending request"); + let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n"; + socket.send_slice(http_get.as_ref()).expect("cannot send"); + let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n"; + socket.send_slice(http_host.as_ref()).expect("cannot send"); + socket + .send_slice(b"Connection: close\r\n") + .expect("cannot send"); + socket.send_slice(b"\r\n").expect("cannot send"); + State::Response + } + State::Response if socket.can_recv() => { + socket + .recv(|data| { + println!("{}", str::from_utf8(data).unwrap_or("(invalid utf8)")); + (data.len(), ()) + }) + .unwrap(); + State::Response + } + State::Response if !socket.may_recv() => { + debug!("received complete response"); + break; + } + _ => state, + }; + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/loopback.rs b/vendor/smoltcp/examples/loopback.rs new file mode 100644 index 00000000..f0b8204d --- /dev/null +++ b/vendor/smoltcp/examples/loopback.rs @@ -0,0 +1,181 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(unused_mut)] +#![allow(clippy::collapsible_if)] + +#[cfg(feature = "std")] +mod utils; + +use core::str; +use log::{debug, error, info}; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Loopback, Medium}; +use smoltcp::socket::tcp; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; + +#[cfg(not(feature = "std"))] +mod mock { + use core::cell::Cell; + use smoltcp::time::{Duration, Instant}; + + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct Clock(Cell); + + impl Clock { + pub fn new() -> Clock { + Clock(Cell::new(Instant::from_millis(0))) + } + + pub fn advance(&self, duration: Duration) { + self.0.set(self.0.get() + duration) + } + + pub fn elapsed(&self) -> Instant { + self.0.get() + } + } +} + +#[cfg(feature = "std")] +mod mock { + use smoltcp::time::{Duration, Instant}; + use std::sync::Arc; + use std::sync::atomic::{AtomicU64, Ordering}; + + #[derive(Debug, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct Clock(Arc); + + impl Clock { + pub fn new() -> Clock { + Clock(Arc::new(AtomicU64::new(0))) + } + + pub fn advance(&self, duration: Duration) { + self.0.fetch_add(duration.total_millis(), Ordering::SeqCst); + } + + pub fn elapsed(&self) -> Instant { + Instant::from_millis(self.0.load(Ordering::SeqCst) as i64) + } + } +} + +fn main() { + let clock = mock::Clock::new(); + let device = Loopback::new(Medium::Ethernet); + + #[cfg(feature = "std")] + let mut device = { + let clock = clock.clone(); + utils::setup_logging_with_clock("", move || clock.elapsed()); + + let (mut opts, mut free) = utils::create_options(); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ true) + }; + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) + .unwrap(); + }); + + // Create sockets + let server_socket = { + // It is not strictly necessary to use a `static mut` and unsafe code here, but + // on embedded systems that smoltcp targets it is far better to allocate the data + // statically to verify that it fits into RAM rather than get undefined behavior + // when stack overflows. + static mut TCP_SERVER_RX_DATA: [u8; 1024] = [0; 1024]; + static mut TCP_SERVER_TX_DATA: [u8; 1024] = [0; 1024]; + let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let client_socket = { + static mut TCP_CLIENT_RX_DATA: [u8; 1024] = [0; 1024]; + static mut TCP_CLIENT_TX_DATA: [u8; 1024] = [0; 1024]; + let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_RX_DATA[..] }); + let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_TX_DATA[..] }); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let mut sockets: [_; 2] = Default::default(); + let mut sockets = SocketSet::new(&mut sockets[..]); + let server_handle = sockets.add(server_socket); + let client_handle = sockets.add(client_socket); + + let mut did_listen = false; + let mut did_connect = false; + let mut done = false; + while !done && clock.elapsed() < Instant::from_millis(10_000) { + iface.poll(clock.elapsed(), &mut device, &mut sockets); + + let mut socket = sockets.get_mut::(server_handle); + if !socket.is_active() && !socket.is_listening() { + if !did_listen { + debug!("listening"); + socket.listen(1234).unwrap(); + did_listen = true; + } + } + + if socket.can_recv() { + debug!( + "got {:?}", + socket.recv(|buffer| { (buffer.len(), str::from_utf8(buffer).unwrap()) }) + ); + socket.close(); + done = true; + } + + let mut socket = sockets.get_mut::(client_handle); + let cx = iface.context(); + if !socket.is_open() { + if !did_connect { + debug!("connecting"); + socket + .connect(cx, (IpAddress::v4(127, 0, 0, 1), 1234), 65000) + .unwrap(); + did_connect = true; + } + } + + if socket.can_send() { + debug!("sending"); + socket.send_slice(b"0123456789abcdef").unwrap(); + socket.close(); + } + + match iface.poll_delay(clock.elapsed(), &sockets) { + Some(Duration::ZERO) => debug!("resuming"), + Some(delay) => { + debug!("sleeping for {} ms", delay); + clock.advance(delay) + } + None => clock.advance(Duration::from_millis(1)), + } + } + + if done { + info!("done") + } else { + error!("this is taking too long, bailing out") + } +} diff --git a/vendor/smoltcp/examples/loopback_benchmark.rs b/vendor/smoltcp/examples/loopback_benchmark.rs new file mode 100644 index 00000000..f49602ff --- /dev/null +++ b/vendor/smoltcp/examples/loopback_benchmark.rs @@ -0,0 +1,101 @@ +mod utils; + +use log::debug; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Loopback, Medium}; +use smoltcp::socket::tcp; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; + +fn main() { + let device = Loopback::new(Medium::Ethernet); + + let mut device = { + utils::setup_logging("info"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ true) + }; + + // Create interface + let config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) + .unwrap(); + }); + + // Create sockets + let server_socket = { + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let client_socket = { + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 65536]); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let mut sockets: [_; 2] = Default::default(); + let mut sockets = SocketSet::new(&mut sockets[..]); + let server_handle = sockets.add(server_socket); + let client_handle = sockets.add(client_socket); + + let start_time = Instant::now(); + + let mut did_listen = false; + let mut did_connect = false; + let mut processed = 0; + while processed < 1024 * 1024 * 1024 { + iface.poll(Instant::now(), &mut device, &mut sockets); + + let socket = sockets.get_mut::(server_handle); + if !socket.is_active() && !socket.is_listening() && !did_listen { + debug!("listening"); + socket.listen(1234).unwrap(); + did_listen = true; + } + + while socket.can_recv() { + let received = socket.recv(|buffer| (buffer.len(), buffer.len())).unwrap(); + debug!("got {:?}", received,); + processed += received; + } + + let socket = sockets.get_mut::(client_handle); + let cx = iface.context(); + if !socket.is_open() && !did_connect { + debug!("connecting"); + socket + .connect(cx, (IpAddress::v4(127, 0, 0, 1), 1234), 65000) + .unwrap(); + did_connect = true; + } + + while socket.can_send() { + debug!("sending"); + socket.send(|buffer| (buffer.len(), ())).unwrap(); + } + } + + let duration = Instant::now() - start_time; + println!( + "done in {} s, bandwidth is {} Gbps", + duration.total_millis() as f64 / 1000.0, + (processed as u64 * 8 / duration.total_millis()) as f64 / 1000000.0 + ); +} diff --git a/vendor/smoltcp/examples/multicast.rs b/vendor/smoltcp/examples/multicast.rs new file mode 100644 index 00000000..fa1f135d --- /dev/null +++ b/vendor/smoltcp/examples/multicast.rs @@ -0,0 +1,123 @@ +mod utils; + +use std::os::unix::io::AsRawFd; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::socket::{raw, udp}; +use smoltcp::time::Instant; +use smoltcp::wire::{ + EthernetAddress, IgmpPacket, IgmpRepr, IpAddress, IpCidr, IpProtocol, IpVersion, Ipv4Address, + Ipv4Packet, Ipv6Address, +}; + +const MDNS_PORT: u16 = 5353; +const MDNS_GROUP: Ipv4Address = Ipv4Address::new(224, 0, 0, 251); + +fn main() { + utils::setup_logging("warn"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) + .unwrap(); + iface + .routes_mut() + .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) + .unwrap(); + + // Create sockets + let mut sockets = SocketSet::new(vec![]); + + // Must fit at least one IGMP packet + let raw_rx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; 2], vec![0; 512]); + // Will not send IGMP + let raw_tx_buffer = raw::PacketBuffer::new(vec![], vec![]); + let raw_socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Igmp), + raw_rx_buffer, + raw_tx_buffer, + ); + let raw_handle = sockets.add(raw_socket); + + // Must fit mDNS payload of at least one packet + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 4], vec![0; 1024]); + // Will not send mDNS + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 0]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let udp_handle = sockets.add(udp_socket); + + // Join a multicast group to receive mDNS traffic + iface.join_multicast_group(MDNS_GROUP).unwrap(); + + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let socket = sockets.get_mut::(raw_handle); + + if socket.can_recv() { + // For display purposes only - normally we wouldn't process incoming IGMP packets + // in the application layer + match socket.recv() { + Err(e) => println!("Recv IGMP error: {e:?}"), + Ok(buf) => { + Ipv4Packet::new_checked(buf) + .and_then(|ipv4_packet| IgmpPacket::new_checked(ipv4_packet.payload())) + .and_then(|igmp_packet| IgmpRepr::parse(&igmp_packet)) + .map(|igmp_repr| println!("IGMP packet: {igmp_repr:?}")) + .unwrap_or_else(|e| println!("parse IGMP error: {e:?}")); + } + } + } + + let socket = sockets.get_mut::(udp_handle); + if !socket.is_open() { + socket.bind(MDNS_PORT).unwrap() + } + + if socket.can_recv() { + socket + .recv() + .map(|(data, sender)| { + println!("mDNS traffic: {} UDP bytes from {}", data.len(), sender) + }) + .unwrap_or_else(|e| println!("Recv UDP error: {e:?}")); + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/multicast6.rs b/vendor/smoltcp/examples/multicast6.rs new file mode 100644 index 00000000..e9a2712b --- /dev/null +++ b/vendor/smoltcp/examples/multicast6.rs @@ -0,0 +1,87 @@ +mod utils; + +use std::os::unix::io::AsRawFd; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::wait as phy_wait; +use smoltcp::phy::{Device, Medium}; +use smoltcp::socket::udp; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv6Address}; + +// Note: If testing with a tap interface in linux, you may need to specify the +// interface index when addressing. E.g., +// +// ``` +// ncat -u ff02::1234%tap0 8123 +// ``` +// +// will send packets to the multicast group we join below on tap0. + +const PORT: u16 = 8123; +const GROUP: Ipv6Address = Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1234); +const LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x101); +const ROUTER_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100); + +fn main() { + utils::setup_logging("warn"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + // Create interface + let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]); + let mut config = match device.capabilities().medium { + Medium::Ethernet => Config::new(ethernet_addr.into()), + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from(LOCAL_ADDR), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv6_route(ROUTER_ADDR) + .unwrap(); + + // Create sockets + let mut sockets = SocketSet::new(vec![]); + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 4], vec![0; 1024]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 0]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let udp_handle = sockets.add(udp_socket); + + // Join a multicast group + iface.join_multicast_group(GROUP).unwrap(); + + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let socket = sockets.get_mut::(udp_handle); + if !socket.is_open() { + socket.bind(PORT).unwrap() + } + + if socket.can_recv() { + socket + .recv() + .map(|(data, sender)| println!("traffic: {} UDP bytes from {}", data.len(), sender)) + .unwrap_or_else(|e| println!("Recv UDP error: {:?}", e)); + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/ping.rs b/vendor/smoltcp/examples/ping.rs new file mode 100644 index 00000000..ad7562db --- /dev/null +++ b/vendor/smoltcp/examples/ping.rs @@ -0,0 +1,275 @@ +mod utils; + +use byteorder::{ByteOrder, NetworkEndian}; +use smoltcp::iface::{Interface, SocketSet}; +use std::cmp; +use std::collections::HashMap; +use std::os::unix::io::AsRawFd; +use std::str::FromStr; + +use smoltcp::iface::Config; +use smoltcp::phy::Device; +use smoltcp::phy::wait as phy_wait; +use smoltcp::socket::icmp; +use smoltcp::wire::{ + EthernetAddress, Icmpv4Packet, Icmpv4Repr, Icmpv6Packet, Icmpv6Repr, IpAddress, IpCidr, + Ipv4Address, Ipv6Address, +}; +use smoltcp::{ + phy::Medium, + time::{Duration, Instant}, +}; + +macro_rules! send_icmp_ping { + ( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr, + $echo_payload:expr, $socket:expr, $remote_addr:expr ) => {{ + let icmp_repr = $repr_type::EchoRequest { + ident: $ident, + seq_no: $seq_no, + data: &$echo_payload, + }; + + let icmp_payload = $socket.send(icmp_repr.buffer_len(), $remote_addr).unwrap(); + + let icmp_packet = $packet_type::new_unchecked(icmp_payload); + (icmp_repr, icmp_packet) + }}; +} + +macro_rules! get_icmp_pong { + ( $repr_type:ident, $repr:expr, $payload:expr, $waiting_queue:expr, $remote_addr:expr, + $timestamp:expr, $received:expr ) => {{ + if let $repr_type::EchoReply { seq_no, data, .. } = $repr { + if let Some(_) = $waiting_queue.get(&seq_no) { + let packet_timestamp_ms = NetworkEndian::read_i64(data); + println!( + "{} bytes from {}: icmp_seq={}, time={}ms", + data.len(), + $remote_addr, + seq_no, + $timestamp.total_millis() - packet_timestamp_ms + ); + $waiting_queue.remove(&seq_no); + $received += 1; + } + } + }}; +} + +fn main() { + utils::setup_logging("warn"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + opts.optopt( + "c", + "count", + "Amount of echo request packets to send (default: 4)", + "COUNT", + ); + opts.optopt( + "i", + "interval", + "Interval between successive packets sent (seconds) (default: 1)", + "INTERVAL", + ); + opts.optopt( + "", + "timeout", + "Maximum wait duration for an echo response packet (seconds) (default: 5)", + "TIMEOUT", + ); + free.push("ADDRESS"); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + let device_caps = device.capabilities(); + let remote_addr = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); + let count = matches + .opt_str("count") + .map(|s| usize::from_str(&s).unwrap()) + .unwrap_or(4); + let interval = matches + .opt_str("interval") + .map(|s| Duration::from_secs(u64::from_str(&s).unwrap())) + .unwrap_or_else(|| Duration::from_secs(1)); + let timeout = Duration::from_secs( + matches + .opt_str("timeout") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(5), + ); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) + .unwrap(); + iface + .routes_mut() + .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) + .unwrap(); + + // Create sockets + let icmp_rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 256]); + let icmp_tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 256]); + let icmp_socket = icmp::Socket::new(icmp_rx_buffer, icmp_tx_buffer); + let mut sockets = SocketSet::new(vec![]); + let icmp_handle = sockets.add(icmp_socket); + + let mut send_at = Instant::from_millis(0); + let mut seq_no = 0; + let mut received = 0; + let mut echo_payload = [0xffu8; 40]; + let mut waiting_queue = HashMap::new(); + let ident = 0x22b; + + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + let timestamp = Instant::now(); + let socket = sockets.get_mut::(icmp_handle); + if !socket.is_open() { + socket.bind(icmp::Endpoint::Ident(ident)).unwrap(); + send_at = timestamp; + } + + if socket.can_send() && seq_no < count as u16 && send_at <= timestamp { + NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis()); + + match remote_addr { + IpAddress::Ipv4(_) => { + let (icmp_repr, mut icmp_packet) = send_icmp_ping!( + Icmpv4Repr, + Icmpv4Packet, + ident, + seq_no, + echo_payload, + socket, + remote_addr + ); + icmp_repr.emit(&mut icmp_packet, &device_caps.checksum); + } + IpAddress::Ipv6(address) => { + let (icmp_repr, mut icmp_packet) = send_icmp_ping!( + Icmpv6Repr, + Icmpv6Packet, + ident, + seq_no, + echo_payload, + socket, + remote_addr + ); + icmp_repr.emit( + &iface.get_source_address_ipv6(&address), + &address, + &mut icmp_packet, + &device_caps.checksum, + ); + } + } + + waiting_queue.insert(seq_no, timestamp); + seq_no += 1; + send_at += interval; + } + + if socket.can_recv() { + let (payload, _) = socket.recv().unwrap(); + + match remote_addr { + IpAddress::Ipv4(_) => { + let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap(); + let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap(); + get_icmp_pong!( + Icmpv4Repr, + icmp_repr, + payload, + waiting_queue, + remote_addr, + timestamp, + received + ); + } + IpAddress::Ipv6(address) => { + let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap(); + let icmp_repr = Icmpv6Repr::parse( + &address, + &iface.get_source_address_ipv6(&address), + &icmp_packet, + &device_caps.checksum, + ) + .unwrap(); + get_icmp_pong!( + Icmpv6Repr, + icmp_repr, + payload, + waiting_queue, + remote_addr, + timestamp, + received + ); + } + } + } + + waiting_queue.retain(|seq, from| { + if timestamp - *from < timeout { + true + } else { + println!("From {remote_addr} icmp_seq={seq} timeout"); + false + } + }); + + if seq_no == count as u16 && waiting_queue.is_empty() { + break; + } + + let timestamp = Instant::now(); + match iface.poll_at(timestamp, &sockets) { + Some(poll_at) if timestamp < poll_at => { + let resume_at = cmp::min(poll_at, send_at); + phy_wait(fd, Some(resume_at - timestamp)).expect("wait error"); + } + Some(_) => (), + None => { + phy_wait(fd, Some(send_at - timestamp)).expect("wait error"); + } + } + } + + println!("--- {remote_addr} ping statistics ---"); + println!( + "{} packets transmitted, {} received, {:.0}% packet loss", + seq_no, + received, + 100.0 * (seq_no - received) as f64 / seq_no as f64 + ); +} diff --git a/vendor/smoltcp/examples/server.rs b/vendor/smoltcp/examples/server.rs new file mode 100644 index 00000000..df96ed06 --- /dev/null +++ b/vendor/smoltcp/examples/server.rs @@ -0,0 +1,209 @@ +mod utils; + +use log::debug; +use std::fmt::Write; +use std::os::unix::io::AsRawFd; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::socket::{tcp, udp}; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv6Address}; + +fn main() { + utils::setup_logging(""); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + + config.random_seed = rand::random(); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::new(192, 168, 69, 100)) + .unwrap(); + iface + .routes_mut() + .add_default_ipv6_route(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100)) + .unwrap(); + + // Create sockets + let udp_rx_buffer = udp::PacketBuffer::new( + vec![udp::PacketMetadata::EMPTY, udp::PacketMetadata::EMPTY], + vec![0; 65535], + ); + let udp_tx_buffer = udp::PacketBuffer::new( + vec![udp::PacketMetadata::EMPTY, udp::PacketMetadata::EMPTY], + vec![0; 65535], + ); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + + let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 64]); + let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 128]); + let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer); + + let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 64]); + let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 128]); + let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer); + + let tcp3_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp3_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp3_socket = tcp::Socket::new(tcp3_rx_buffer, tcp3_tx_buffer); + + let tcp4_rx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp4_tx_buffer = tcp::SocketBuffer::new(vec![0; 65535]); + let tcp4_socket = tcp::Socket::new(tcp4_rx_buffer, tcp4_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let udp_handle = sockets.add(udp_socket); + let tcp1_handle = sockets.add(tcp1_socket); + let tcp2_handle = sockets.add(tcp2_socket); + let tcp3_handle = sockets.add(tcp3_socket); + let tcp4_handle = sockets.add(tcp4_socket); + + let mut tcp_6970_active = false; + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + // udp:6969: respond "hello" + let socket = sockets.get_mut::(udp_handle); + if !socket.is_open() { + socket.bind(6969).unwrap() + } + + let client = match socket.recv() { + Ok((data, endpoint)) => { + debug!("udp:6969 recv data: {:?} from {}", data, endpoint); + let mut data = data.to_vec(); + data.reverse(); + Some((endpoint, data)) + } + Err(_) => None, + }; + if let Some((endpoint, data)) = client { + debug!("udp:6969 send data: {:?} to {}", data, endpoint,); + socket.send_slice(&data, endpoint).unwrap(); + } + + // tcp:6969: respond "hello" + let socket = sockets.get_mut::(tcp1_handle); + if !socket.is_open() { + socket.listen(6969).unwrap(); + } + + if socket.can_send() { + debug!("tcp:6969 send greeting"); + writeln!(socket, "hello").unwrap(); + debug!("tcp:6969 close"); + socket.close(); + } + + // tcp:6970: echo with reverse + let socket = sockets.get_mut::(tcp2_handle); + if !socket.is_open() { + socket.listen(6970).unwrap() + } + + if socket.is_active() && !tcp_6970_active { + debug!("tcp:6970 connected"); + } else if !socket.is_active() && tcp_6970_active { + debug!("tcp:6970 disconnected"); + } + tcp_6970_active = socket.is_active(); + + if socket.may_recv() { + let data = socket + .recv(|buffer| { + let recvd_len = buffer.len(); + let mut data = buffer.to_owned(); + if !data.is_empty() { + debug!("tcp:6970 recv data: {:?}", data); + data = data.split(|&b| b == b'\n').collect::>().concat(); + data.reverse(); + data.extend(b"\n"); + } + (recvd_len, data) + }) + .unwrap(); + if socket.can_send() && !data.is_empty() { + debug!("tcp:6970 send data: {:?}", data); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + debug!("tcp:6970 close"); + socket.close(); + } + + // tcp:6971: sinkhole + let socket = sockets.get_mut::(tcp3_handle); + if !socket.is_open() { + socket.listen(6971).unwrap(); + socket.set_keep_alive(Some(Duration::from_millis(1000))); + socket.set_timeout(Some(Duration::from_millis(2000))); + } + + if socket.may_recv() { + socket + .recv(|buffer| { + if !buffer.is_empty() { + debug!("tcp:6971 recv {:?} octets", buffer.len()); + } + (buffer.len(), ()) + }) + .unwrap(); + } else if socket.may_send() { + socket.close(); + } + + // tcp:6972: fountain + let socket = sockets.get_mut::(tcp4_handle); + if !socket.is_open() { + socket.listen(6972).unwrap() + } + + if socket.may_send() { + socket + .send(|data| { + if !data.is_empty() { + debug!("tcp:6972 send {:?} octets", data.len()); + for (i, b) in data.iter_mut().enumerate() { + *b = (i % 256) as u8; + } + } + (data.len(), ()) + }) + .unwrap(); + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/sixlowpan.rs b/vendor/smoltcp/examples/sixlowpan.rs new file mode 100644 index 00000000..47639da2 --- /dev/null +++ b/vendor/smoltcp/examples/sixlowpan.rs @@ -0,0 +1,177 @@ +//! 6lowpan example +//! +//! This example is designed to run using the Linux ieee802154/6lowpan support, +//! using mac802154_hwsim. +//! +//! mac802154_hwsim allows you to create multiple "virtual" radios and specify +//! which is in range with which. This is very useful for testing without +//! needing real hardware. By default it creates two interfaces `wpan0` and +//! `wpan1` that are in range with each other. You can customize this with +//! the `wpan-hwsim` tool. +//! +//! We'll configure Linux to speak 6lowpan on `wpan0`, and leave `wpan1` +//! unconfigured so smoltcp can use it with a raw socket. +//! +//! # Setup +//! +//! modprobe mac802154_hwsim +//! +//! ip link set wpan0 down +//! ip link set wpan1 down +//! iwpan dev wpan0 set pan_id 0xbeef +//! iwpan dev wpan1 set pan_id 0xbeef +//! ip link add link wpan0 name lowpan0 type lowpan +//! ip link set wpan0 up +//! ip link set wpan1 up +//! ip link set lowpan0 up +//! +//! # Running +//! +//! Run it with `sudo ./target/debug/examples/sixlowpan`. +//! +//! You can set wireshark to sniff on interface `wpan0` to see the packets. +//! +//! Ping it with `ping fe80::180b:4242:4242:4242%lowpan0`. +//! +//! Speak UDP with `nc -uv fe80::180b:4242:4242:4242%lowpan0 6969`. +//! +//! # Teardown +//! +//! rmmod mac802154_hwsim +//! + +mod utils; + +use log::debug; +use std::os::unix::io::AsRawFd; +use std::str; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, RawSocket, wait as phy_wait}; +use smoltcp::socket::tcp; +use smoltcp::socket::udp; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, Ieee802154Address, Ieee802154Pan, IpAddress, IpCidr}; + +fn main() { + utils::setup_logging(""); + + let (mut opts, mut free) = utils::create_options(); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + + let device = RawSocket::new("wpan1", Medium::Ieee802154).unwrap(); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => Config::new( + Ieee802154Address::Extended([0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42]).into(), + ), + }; + config.random_seed = rand::random(); + config.pan_id = Some(Ieee802154Pan(0xbeef)); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new( + IpAddress::v6(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242), + 64, + )) + .unwrap(); + }); + + // Create sockets + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1280]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1280]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let udp_handle = sockets.add(udp_socket); + let tcp_handle = sockets.add(tcp_socket); + + let socket = sockets.get_mut::(tcp_handle); + socket.listen(50000).unwrap(); + + let mut tcp_active = false; + + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + // udp:6969: respond "hello" + let socket = sockets.get_mut::(udp_handle); + if !socket.is_open() { + socket.bind(6969).unwrap() + } + + let mut buffer = vec![0; 1500]; + let client = match socket.recv() { + Ok((data, endpoint)) => { + debug!( + "udp:6969 recv data: {:?} from {}", + str::from_utf8(data).unwrap(), + endpoint + ); + buffer[..data.len()].copy_from_slice(data); + Some((data.len(), endpoint)) + } + Err(_) => None, + }; + if let Some((len, endpoint)) = client { + debug!( + "udp:6969 send data: {:?}", + str::from_utf8(&buffer[..len]).unwrap() + ); + socket.send_slice(&buffer[..len], endpoint).unwrap(); + } + + let socket = sockets.get_mut::(tcp_handle); + if socket.is_active() && !tcp_active { + debug!("connected"); + } else if !socket.is_active() && tcp_active { + debug!("disconnected"); + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + let data = socket + .recv(|data| { + let data = data.to_owned(); + if !data.is_empty() { + debug!( + "recv data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") + ); + } + (data.len(), data) + }) + .unwrap(); + + if socket.can_send() && !data.is_empty() { + debug!( + "send data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)") + ); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + debug!("close"); + socket.close(); + } + + phy_wait(fd, iface.poll_delay(timestamp, &sockets)).expect("wait error"); + } +} diff --git a/vendor/smoltcp/examples/sixlowpan_benchmark.rs b/vendor/smoltcp/examples/sixlowpan_benchmark.rs new file mode 100644 index 00000000..04a229e0 --- /dev/null +++ b/vendor/smoltcp/examples/sixlowpan_benchmark.rs @@ -0,0 +1,235 @@ +//! 6lowpan benchmark example +//! +//! This example runs a simple TCP throughput benchmark using the 6lowpan implementation in smoltcp +//! It is designed to run using the Linux ieee802154/6lowpan support, +//! using mac802154_hwsim. +//! +//! mac802154_hwsim allows you to create multiple "virtual" radios and specify +//! which is in range with which. This is very useful for testing without +//! needing real hardware. By default it creates two interfaces `wpan0` and +//! `wpan1` that are in range with each other. You can customize this with +//! the `wpan-hwsim` tool. +//! +//! We'll configure Linux to speak 6lowpan on `wpan0`, and leave `wpan1` +//! unconfigured so smoltcp can use it with a raw socket. +//! +//! +//! +//! +//! +//! # Setup +//! +//! modprobe mac802154_hwsim +//! +//! ip link set wpan0 down +//! ip link set wpan1 down +//! iwpan dev wpan0 set pan_id 0xbeef +//! iwpan dev wpan1 set pan_id 0xbeef +//! ip link add link wpan0 name lowpan0 type lowpan +//! ip link set wpan0 up +//! ip link set wpan1 up +//! ip link set lowpan0 up +//! +//! +//! # Running +//! +//! Compile with `cargo build --release --example sixlowpan_benchmark` +//! Run it with `sudo ./target/release/examples/sixlowpan_benchmark [reader|writer]`. +//! +//! # Teardown +//! +//! rmmod mac802154_hwsim +//! + +mod utils; + +use std::os::unix::io::AsRawFd; +use std::str; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, RawSocket, wait as phy_wait}; +use smoltcp::socket::tcp; +use smoltcp::wire::{EthernetAddress, Ieee802154Address, Ieee802154Pan, IpAddress, IpCidr}; + +//For benchmark +use smoltcp::time::{Duration, Instant}; +use std::cmp; +use std::io::{Read, Write}; +use std::net::SocketAddrV6; +use std::net::TcpStream; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; + +use std::fs; + +fn if_nametoindex(ifname: &str) -> u32 { + let contents = fs::read_to_string(format!("/sys/devices/virtual/net/{ifname}/ifindex")) + .expect("couldn't read interface from \"/sys/devices/virtual/net\"") + .replace('\n', ""); + contents.parse::().unwrap() +} + +const AMOUNT: usize = 100_000_000; + +enum Client { + Reader, + Writer, +} + +fn client(kind: Client) { + let port: u16 = match kind { + Client::Reader => 1234, + Client::Writer => 1235, + }; + + let scope_id = if_nametoindex("lowpan0"); + + let socket_addr = SocketAddrV6::new( + "fe80:0:0:0:180b:4242:4242:4242".parse().unwrap(), + port, + 0, + scope_id, + ); + + let mut stream = TcpStream::connect(socket_addr).expect("failed to connect TLKAGMKA"); + let mut buffer = vec![0; 1_000_000]; + + let start = Instant::now(); + + let mut processed = 0; + while processed < AMOUNT { + let length = cmp::min(buffer.len(), AMOUNT - processed); + let result = match kind { + Client::Reader => stream.read(&mut buffer[..length]), + Client::Writer => stream.write(&buffer[..length]), + }; + match result { + Ok(0) => break, + Ok(result) => { + // print!("(P:{})", result); + processed += result + } + Err(err) => panic!("cannot process: {err}"), + } + } + + let end = Instant::now(); + + let elapsed = (end - start).total_millis() as f64 / 1000.0; + + println!("throughput: {:.3} Gbps", AMOUNT as f64 / elapsed / 0.125e9); + + CLIENT_DONE.store(true, Ordering::SeqCst); +} + +static CLIENT_DONE: AtomicBool = AtomicBool::new(false); + +fn main() { + #[cfg(feature = "log")] + utils::setup_logging("info"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_middleware_options(&mut opts, &mut free); + free.push("MODE"); + + let mut matches = utils::parse_options(&opts, free); + + let device = RawSocket::new("wpan1", Medium::Ieee802154).unwrap(); + + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + let mode = match matches.free[0].as_ref() { + "reader" => Client::Reader, + "writer" => Client::Writer, + _ => panic!("invalid mode"), + }; + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => Config::new( + Ieee802154Address::Extended([0x1a, 0x0b, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42]).into(), + ), + }; + config.random_seed = rand::random(); + config.pan_id = Some(Ieee802154Pan(0xbeef)); + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new( + IpAddress::v6(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242), + 64, + )) + .unwrap(); + }); + + let tcp1_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp1_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp1_socket = tcp::Socket::new(tcp1_rx_buffer, tcp1_tx_buffer); + + let tcp2_rx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp2_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp2_socket = tcp::Socket::new(tcp2_rx_buffer, tcp2_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let tcp1_handle = sockets.add(tcp1_socket); + let tcp2_handle = sockets.add(tcp2_socket); + + let default_timeout = Some(Duration::from_millis(1000)); + + thread::spawn(move || client(mode)); + let mut processed = 0; + + while !CLIENT_DONE.load(Ordering::SeqCst) { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + + // tcp:1234: emit data + let socket = sockets.get_mut::(tcp1_handle); + if !socket.is_open() { + socket.listen(1234).unwrap(); + } + + if socket.can_send() && processed < AMOUNT { + let length = socket + .send(|buffer| { + let length = cmp::min(buffer.len(), AMOUNT - processed); + (length, length) + }) + .unwrap(); + processed += length; + } + + // tcp:1235: sink data + let socket = sockets.get_mut::(tcp2_handle); + if !socket.is_open() { + socket.listen(1235).unwrap(); + } + + if socket.can_recv() && processed < AMOUNT { + let length = socket + .recv(|buffer| { + let length = cmp::min(buffer.len(), AMOUNT - processed); + (length, length) + }) + .unwrap(); + processed += length; + } + + match iface.poll_at(timestamp, &sockets) { + Some(poll_at) if timestamp < poll_at => { + phy_wait(fd, Some(poll_at - timestamp)).expect("wait error"); + } + Some(_) => (), + None => { + phy_wait(fd, default_timeout).expect("wait error"); + } + } + } +} diff --git a/vendor/smoltcp/examples/slaac.rs b/vendor/smoltcp/examples/slaac.rs new file mode 100644 index 00000000..b10dce6b --- /dev/null +++ b/vendor/smoltcp/examples/slaac.rs @@ -0,0 +1,76 @@ +mod utils; + +use std::os::unix::io::AsRawFd; + +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::{Device, Medium, wait as phy_wait}; +use smoltcp::socket::udp; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv6Address}; + +const LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0x0, 0, 0, 0x01); + +fn main() { + utils::setup_logging("warn"); + + let (mut opts, mut free) = utils::create_options(); + utils::add_tuntap_options(&mut opts, &mut free); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_tuntap_options(&mut matches); + let fd = device.as_raw_fd(); + let mut device = + utils::parse_middleware_options(&mut matches, device, /*loopback=*/ false); + + // Create interface + let mut config = match device.capabilities().medium { + Medium::Ethernet => { + Config::new(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]).into()) + } + Medium::Ip => Config::new(smoltcp::wire::HardwareAddress::Ip), + Medium::Ieee802154 => todo!(), + }; + config.slaac = true; + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from(LOCAL_ADDR), 64)) + .unwrap(); + }); + + let mut sockets = SocketSet::new(vec![]); + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 4], vec![0; 1024]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 0]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let _udp_handle = sockets.add(udp_socket); + + let mut last_print = Instant::now(); + loop { + let timestamp = Instant::now(); + iface.poll(timestamp, &mut device, &mut sockets); + let mut delay = iface.poll_delay(timestamp, &sockets); + if delay.is_none() || delay.is_some_and(|d| d > Duration::from_millis(1000)) { + delay = Some(Duration::from_millis(1000)); + } + + phy_wait(fd, delay).expect("wait error"); + + let timestamp = Instant::now(); + if timestamp > last_print + Duration::from_secs(1) { + last_print = timestamp; + println!(); + println!("Addresses:"); + for addr in iface.ip_addrs() { + println!(" - {addr}"); + } + println!("Routes:"); + iface.routes_mut().update(|routes| { + for route in routes { + println!(" - {} via {}", route.cidr, route.via_router); + } + }); + } + } +} diff --git a/vendor/smoltcp/examples/tcpdump.rs b/vendor/smoltcp/examples/tcpdump.rs new file mode 100644 index 00000000..2baf376e --- /dev/null +++ b/vendor/smoltcp/examples/tcpdump.rs @@ -0,0 +1,21 @@ +use smoltcp::phy::wait as phy_wait; +use smoltcp::phy::{Device, RawSocket, RxToken}; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetFrame, PrettyPrinter}; +use std::env; +use std::os::unix::io::AsRawFd; + +fn main() { + let ifname = env::args().nth(1).unwrap(); + let mut socket = RawSocket::new(ifname.as_ref(), smoltcp::phy::Medium::Ethernet).unwrap(); + loop { + phy_wait(socket.as_raw_fd(), None).unwrap(); + let (rx_token, _) = socket.receive(Instant::now()).unwrap(); + rx_token.consume(|buffer| { + println!( + "{}", + PrettyPrinter::>::new("", &buffer) + ); + }) + } +} diff --git a/vendor/smoltcp/examples/utils.rs b/vendor/smoltcp/examples/utils.rs new file mode 100644 index 00000000..48fd8ec1 --- /dev/null +++ b/vendor/smoltcp/examples/utils.rs @@ -0,0 +1,218 @@ +#![allow(dead_code)] + +#[cfg(feature = "log")] +use env_logger::Builder; +use getopts::{Matches, Options}; +#[cfg(feature = "log")] +use log::{Level, LevelFilter, trace}; +use std::env; +use std::fs::File; +use std::io::{self, Write}; +use std::process; +use std::str::{self, FromStr}; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[cfg(feature = "phy-tuntap_interface")] +use smoltcp::phy::TunTapInterface; +use smoltcp::phy::{Device, FaultInjector, Medium, Tracer}; +use smoltcp::phy::{PcapMode, PcapWriter}; +use smoltcp::time::{Duration, Instant}; + +#[cfg(feature = "log")] +pub fn setup_logging_with_clock(filter: &str, since_startup: F) +where + F: Fn() -> Instant + Send + Sync + 'static, +{ + Builder::new() + .format(move |buf, record| { + let elapsed = since_startup(); + let timestamp = format!("[{elapsed}]"); + if record.target().starts_with("smoltcp::") { + writeln!( + buf, + "\x1b[0m{} ({}): {}\x1b[0m", + timestamp, + record.target().replace("smoltcp::", ""), + record.args() + ) + } else if record.level() == Level::Trace { + let message = format!("{}", record.args()); + writeln!( + buf, + "\x1b[37m{} {}\x1b[0m", + timestamp, + message.replace('\n', "\n ") + ) + } else { + writeln!( + buf, + "\x1b[32m{} ({}): {}\x1b[0m", + timestamp, + record.target(), + record.args() + ) + } + }) + .filter(None, LevelFilter::Trace) + .parse_filters(filter) + .parse_env("RUST_LOG") + .init(); +} + +#[cfg(feature = "log")] +pub fn setup_logging(filter: &str) { + setup_logging_with_clock(filter, Instant::now) +} + +pub fn create_options() -> (Options, Vec<&'static str>) { + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help menu"); + (opts, Vec::new()) +} + +pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches { + match options.parse(env::args().skip(1)) { + Err(err) => { + println!("{err}"); + process::exit(1) + } + Ok(matches) => { + if matches.opt_present("h") || matches.free.len() != free.len() { + let brief = format!( + "Usage: {} [OPTION]... {}", + env::args().next().unwrap(), + free.join(" ") + ); + print!("{}", options.usage(&brief)); + process::exit((matches.free.len() != free.len()) as _); + } + matches + } + } +} + +pub fn add_tuntap_options(opts: &mut Options, _free: &mut [&str]) { + opts.optopt("", "tun", "TUN interface to use", "tun0"); + opts.optopt("", "tap", "TAP interface to use", "tap0"); +} + +#[cfg(feature = "phy-tuntap_interface")] +pub fn parse_tuntap_options(matches: &mut Matches) -> TunTapInterface { + let tun = matches.opt_str("tun"); + let tap = matches.opt_str("tap"); + match (tun, tap) { + (Some(tun), None) => TunTapInterface::new(&tun, Medium::Ip).unwrap(), + (None, Some(tap)) => TunTapInterface::new(&tap, Medium::Ethernet).unwrap(), + _ => panic!("You must specify exactly one of --tun or --tap"), + } +} + +pub fn add_middleware_options(opts: &mut Options, _free: &mut [&str]) { + opts.optopt("", "pcap", "Write a packet capture file", "FILE"); + opts.optopt( + "", + "drop-chance", + "Chance of dropping a packet (%)", + "CHANCE", + ); + opts.optopt( + "", + "corrupt-chance", + "Chance of corrupting a packet (%)", + "CHANCE", + ); + opts.optopt( + "", + "size-limit", + "Drop packets larger than given size (octets)", + "SIZE", + ); + opts.optopt( + "", + "tx-rate-limit", + "Drop packets after transmit rate exceeds given limit \ + (packets per interval)", + "RATE", + ); + opts.optopt( + "", + "rx-rate-limit", + "Drop packets after transmit rate exceeds given limit \ + (packets per interval)", + "RATE", + ); + opts.optopt( + "", + "shaping-interval", + "Sets the interval for rate limiting (ms)", + "RATE", + ); +} + +pub fn parse_middleware_options( + matches: &mut Matches, + device: D, + loopback: bool, +) -> FaultInjector>>> +where + D: Device, +{ + let drop_chance = matches + .opt_str("drop-chance") + .map(|s| u8::from_str(&s).unwrap()) + .unwrap_or(0); + let corrupt_chance = matches + .opt_str("corrupt-chance") + .map(|s| u8::from_str(&s).unwrap()) + .unwrap_or(0); + let size_limit = matches + .opt_str("size-limit") + .map(|s| usize::from_str(&s).unwrap()) + .unwrap_or(0); + let tx_rate_limit = matches + .opt_str("tx-rate-limit") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(0); + let rx_rate_limit = matches + .opt_str("rx-rate-limit") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(0); + let shaping_interval = matches + .opt_str("shaping-interval") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(0); + + let pcap_writer: Box = match matches.opt_str("pcap") { + Some(pcap_filename) => Box::new(File::create(pcap_filename).expect("cannot open file")), + None => Box::new(io::sink()), + }; + + let seed = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos(); + + let device = PcapWriter::new( + device, + pcap_writer, + if loopback { + PcapMode::TxOnly + } else { + PcapMode::Both + }, + ); + + let device = Tracer::new(device, |_timestamp, _printer| { + #[cfg(feature = "log")] + trace!("{}", _printer); + }); + + let mut device = FaultInjector::new(device, seed); + device.set_drop_chance(drop_chance); + device.set_corrupt_chance(corrupt_chance); + device.set_max_packet_size(size_limit); + device.set_max_tx_rate(tx_rate_limit); + device.set_max_rx_rate(rx_rate_limit); + device.set_bucket_interval(Duration::from_millis(shaping_interval)); + device +} diff --git a/vendor/smoltcp/fuzz/.gitignore b/vendor/smoltcp/fuzz/.gitignore new file mode 100644 index 00000000..a0925114 --- /dev/null +++ b/vendor/smoltcp/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/vendor/smoltcp/fuzz/Cargo.toml b/vendor/smoltcp/fuzz/Cargo.toml new file mode 100644 index 00000000..f526d755 --- /dev/null +++ b/vendor/smoltcp/fuzz/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "smoltcp-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +arbitrary = { version = "1", features = ["derive"] } +getopts = "0.2" +smoltcp = { path = ".." } + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "packet_parser" +path = "fuzz_targets/packet_parser.rs" +test = false +doc = false + +[[bin]] +name = "tcp_headers" +path = "fuzz_targets/tcp_headers.rs" +test = false +doc = false + +[[bin]] +name = "dhcp_header" +path = "fuzz_targets/dhcp_header.rs" +test = false +doc = false + +[[bin]] +name = "ieee802154_header" +path = "fuzz_targets/ieee802154_header.rs" +test = false +doc = false + +[[bin]] +name = "sixlowpan_packet" +path = "fuzz_targets/sixlowpan_packet.rs" +test = false +doc = false diff --git a/vendor/smoltcp/fuzz/fuzz_targets/dhcp_header.rs b/vendor/smoltcp/fuzz/fuzz_targets/dhcp_header.rs new file mode 100644 index 00000000..f56efd09 --- /dev/null +++ b/vendor/smoltcp/fuzz/fuzz_targets/dhcp_header.rs @@ -0,0 +1,19 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use smoltcp::wire::{DhcpPacket, DhcpRepr}; + +fuzz_target!(|data: &[u8]| { + let _ = match DhcpPacket::new_checked(data) { + Ok(packet) => match DhcpRepr::parse(packet) { + Ok(dhcp_repr) => { + let mut dhcp_payload = vec![0; dhcp_repr.buffer_len()]; + match DhcpPacket::new_checked(&mut dhcp_payload[..]) { + Ok(mut dhcp_packet) => Some(dhcp_repr.emit(&mut dhcp_packet)), + Err(_) => None, + } + } + Err(_) => None, + }, + Err(_) => None, + }; +}); diff --git a/vendor/smoltcp/fuzz/fuzz_targets/ieee802154_header.rs b/vendor/smoltcp/fuzz/fuzz_targets/ieee802154_header.rs new file mode 100644 index 00000000..88f52f63 --- /dev/null +++ b/vendor/smoltcp/fuzz/fuzz_targets/ieee802154_header.rs @@ -0,0 +1,19 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use smoltcp::wire::{Ieee802154Frame, Ieee802154Repr}; + +fuzz_target!(|data: &[u8]| { + if let Ok(frame) = Ieee802154Frame::new_checked(data) { + if let Ok(repr) = Ieee802154Repr::parse(frame) { + // The buffer len returns only the length required for emitting the header + // and does not take into account the length of the payload. + let mut buffer = vec![0; repr.buffer_len()]; + + // NOTE: unchecked because the checked version checks if the addressing mode field + // is valid or not. The addressing mode field is required for calculating the length of + // the header, which is used in `check_len`. + let mut frame = Ieee802154Frame::new_unchecked(&mut buffer[..]); + repr.emit(&mut frame); + } + }; +}); diff --git a/vendor/smoltcp/fuzz/fuzz_targets/packet_parser.rs b/vendor/smoltcp/fuzz/fuzz_targets/packet_parser.rs new file mode 100644 index 00000000..e9e58bff --- /dev/null +++ b/vendor/smoltcp/fuzz/fuzz_targets/packet_parser.rs @@ -0,0 +1,10 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use smoltcp::wire::*; + +fuzz_target!(|data: &[u8]| { + format!( + "{}", + PrettyPrinter::>::new("", &data) + ); +}); diff --git a/vendor/smoltcp/fuzz/fuzz_targets/sixlowpan_packet.rs b/vendor/smoltcp/fuzz/fuzz_targets/sixlowpan_packet.rs new file mode 100644 index 00000000..e88285d2 --- /dev/null +++ b/vendor/smoltcp/fuzz/fuzz_targets/sixlowpan_packet.rs @@ -0,0 +1,243 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use smoltcp::{phy::ChecksumCapabilities, wire::*}; + +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, arbitrary::Arbitrary)] +pub enum AddressFuzzer { + Absent, + Short([u8; 2]), + Extended([u8; 8]), +} + +impl From for Ieee802154Address { + fn from(val: AddressFuzzer) -> Self { + match val { + AddressFuzzer::Absent => Ieee802154Address::Absent, + AddressFuzzer::Short(b) => Ieee802154Address::Short(b), + AddressFuzzer::Extended(b) => Ieee802154Address::Extended(b), + } + } +} + +#[derive(Debug, arbitrary::Arbitrary)] +struct SixlowpanPacketFuzzer<'a> { + data: &'a [u8], + ll_src_addr: Option, + ll_dst_addr: Option, +} + +fuzz_target!(|fuzz: SixlowpanPacketFuzzer| { + match SixlowpanPacket::dispatch(fuzz.data) { + Ok(SixlowpanPacket::FragmentHeader) => { + if let Ok(frame) = SixlowpanFragPacket::new_checked(fuzz.data) { + if let Ok(repr) = SixlowpanFragRepr::parse(&frame) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut frame = SixlowpanFragPacket::new_unchecked(&mut buffer[..]); + repr.emit(&mut frame); + } + } + } + Ok(SixlowpanPacket::IphcHeader) => { + if let Ok(frame) = SixlowpanIphcPacket::new_checked(fuzz.data) { + if let Ok(iphc_repr) = SixlowpanIphcRepr::parse( + &frame, + fuzz.ll_src_addr.map(Into::into), + fuzz.ll_dst_addr.map(Into::into), + &[], + ) { + let mut buffer = vec![0; iphc_repr.buffer_len()]; + let mut iphc_frame = SixlowpanIphcPacket::new_unchecked(&mut buffer[..]); + iphc_repr.emit(&mut iphc_frame); + + let payload = frame.payload(); + match iphc_repr.next_header { + SixlowpanNextHeader::Compressed => { + if let Ok(p) = SixlowpanNhcPacket::dispatch(payload) { + match p { + SixlowpanNhcPacket::ExtHeader => { + if let Ok(frame) = + SixlowpanExtHeaderPacket::new_checked(payload) + { + if let Ok(repr) = SixlowpanExtHeaderRepr::parse(&frame) + { + let mut buffer = vec![0; repr.buffer_len()]; + let mut ext_header_frame = + SixlowpanExtHeaderPacket::new_unchecked( + &mut buffer[..], + ); + repr.emit(&mut ext_header_frame); + } + } + } + SixlowpanNhcPacket::UdpHeader => { + if let Ok(frame) = + SixlowpanUdpNhcPacket::new_checked(payload) + { + if let Ok(repr) = SixlowpanUdpNhcRepr::parse( + &frame, + &iphc_repr.src_addr, + &iphc_repr.dst_addr, + &Default::default(), + ) { + let mut buffer = vec![ + 0; + repr.header_len() + + frame.payload().len() + ]; + let mut udp_packet = + SixlowpanUdpNhcPacket::new_unchecked( + &mut buffer[..], + ); + repr.emit( + &mut udp_packet, + &iphc_repr.src_addr, + &iphc_repr.dst_addr, + frame.payload().len(), + |b| b.copy_from_slice(frame.payload()), + &ChecksumCapabilities::ignored(), + ); + } + } + } + } + } + } + SixlowpanNextHeader::Uncompressed(proto) => match proto { + IpProtocol::HopByHop => { + if let Ok(frame) = Ipv6HopByHopHeader::new_checked(payload) { + if let Ok(repr) = Ipv6HopByHopRepr::parse(&frame) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut hop_by_hop_frame = + Ipv6HopByHopHeader::new_unchecked(&mut buffer[..]); + repr.emit(&mut hop_by_hop_frame); + } + } + } + IpProtocol::Icmp => { + if let Ok(frame) = Icmpv4Packet::new_checked(payload) { + if let Ok(repr) = + Icmpv4Repr::parse(&frame, &ChecksumCapabilities::default()) + { + let mut buffer = vec![0; repr.buffer_len()]; + let mut icmpv4_packet = + Icmpv4Packet::new_unchecked(&mut buffer[..]); + repr.emit( + &mut icmpv4_packet, + &ChecksumCapabilities::default(), + ); + } + } + } + IpProtocol::Igmp => { + if let Ok(frame) = IgmpPacket::new_checked(payload) { + if let Ok(repr) = IgmpRepr::parse(&frame) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut frame = IgmpPacket::new_unchecked(&mut buffer[..]); + repr.emit(&mut frame); + } + } + } + IpProtocol::Tcp => { + if let Ok(frame) = TcpPacket::new_checked(payload) { + if let Ok(repr) = TcpRepr::parse( + &frame, + &iphc_repr.src_addr.into_address(), + &iphc_repr.dst_addr.into_address(), + &ChecksumCapabilities::default(), + ) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut frame = TcpPacket::new_unchecked(&mut buffer[..]); + repr.emit( + &mut frame, + &iphc_repr.src_addr.into_address(), + &iphc_repr.dst_addr.into_address(), + &ChecksumCapabilities::default(), + ); + } + } + } + IpProtocol::Udp => { + if let Ok(frame) = UdpPacket::new_checked(payload) { + if let Ok(repr) = UdpRepr::parse( + &frame, + &iphc_repr.src_addr.into_address(), + &iphc_repr.dst_addr.into_address(), + &ChecksumCapabilities::default(), + ) { + let mut buffer = + vec![0; repr.header_len() + frame.payload().len()]; + let mut packet = UdpPacket::new_unchecked(&mut buffer[..]); + repr.emit( + &mut packet, + &iphc_repr.src_addr.into_address(), + &iphc_repr.dst_addr.into_address(), + frame.payload().len(), + |b| b.copy_from_slice(frame.payload()), + &ChecksumCapabilities::default(), + ); + } + } + } + IpProtocol::Ipv6Route => { + if let Ok(frame) = Ipv6RoutingHeader::new_checked(payload) { + if let Ok(repr) = Ipv6RoutingRepr::parse(&frame) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut packet = Ipv6RoutingHeader::new_unchecked(&mut buffer[..]); + repr.emit(&mut packet); + } + } + } + IpProtocol::Ipv6Frag => { + if let Ok(frame) = Ipv6FragmentHeader::new_checked(payload) { + if let Ok(repr) = Ipv6FragmentRepr::parse(&frame) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut frame = + Ipv6FragmentHeader::new_unchecked(&mut buffer[..]); + repr.emit(&mut frame); + } + } + } + IpProtocol::Icmpv6 => { + if let Ok(packet) = Icmpv6Packet::new_checked(payload) { + if let Ok(repr) = Icmpv6Repr::parse( + &iphc_repr.src_addr.into_address(), + &iphc_repr.dst_addr.into_address(), + &packet, + &ChecksumCapabilities::default(), + ) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut packet = + Icmpv6Packet::new_unchecked(&mut buffer[..]); + repr.emit( + &iphc_repr.src_addr.into_address(), + &iphc_repr.dst_addr.into_address(), + &mut packet, + &ChecksumCapabilities::default(), + ); + } + } + } + IpProtocol::Ipv6NoNxt => (), + IpProtocol::Ipv6Opts => { + if let Ok(packet) = Ipv6Option::new_checked(payload) { + if let Ok(repr) = Ipv6OptionRepr::parse(&packet) { + let mut buffer = vec![0; repr.buffer_len()]; + let mut packet = Ipv6Option::new_unchecked(&mut buffer[..]); + repr.emit(&mut packet); + } + } + } + IpProtocol::Unknown(_) => (), + }, + }; + + let mut buffer = vec![0; iphc_repr.buffer_len()]; + + let mut frame = SixlowpanIphcPacket::new_unchecked(&mut buffer[..]); + iphc_repr.emit(&mut frame); + } + }; + } + Err(_) => (), + } +}); diff --git a/vendor/smoltcp/fuzz/fuzz_targets/tcp_headers.rs b/vendor/smoltcp/fuzz/fuzz_targets/tcp_headers.rs new file mode 100644 index 00000000..0ff6b611 --- /dev/null +++ b/vendor/smoltcp/fuzz/fuzz_targets/tcp_headers.rs @@ -0,0 +1,213 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use smoltcp::iface::{InterfaceBuilder, NeighborCache}; +use smoltcp::phy::{Loopback, Medium}; +use smoltcp::socket::tcp; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{EthernetAddress, EthernetFrame, EthernetProtocol}; +use smoltcp::wire::{IpAddress, IpCidr, Ipv4Packet, Ipv6Packet, TcpPacket}; +use std::cmp; + +#[path = "../utils.rs"] +mod utils; + +mod mock { + use smoltcp::time::{Duration, Instant}; + use std::sync::Arc; + use std::sync::atomic::{AtomicU64, Ordering}; + + #[derive(Debug, Clone)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct Clock(Arc); + + impl Clock { + pub fn new() -> Clock { + Clock(Arc::new(AtomicU64::new(0))) + } + + pub fn advance(&self, duration: Duration) { + self.0.fetch_add(duration.total_millis(), Ordering::SeqCst); + } + + pub fn elapsed(&self) -> Instant { + Instant::from_millis(self.0.load(Ordering::SeqCst) as i64) + } + } +} + +struct TcpHeaderFuzzer([u8; 56], usize); + +impl TcpHeaderFuzzer { + // The fuzzer won't fuzz any packets with the SYN flag set in order to make sure the connection + // is established before the fuzzed headers arrive. + // + // It will also not fuzz the source and dest port so it reaches the open socket. + // + // Otherwise, it replaces the entire rest of the TCP header with the fuzzer's output. + pub fn new(data: &[u8]) -> TcpHeaderFuzzer { + let copy_len = cmp::min( + data.len(), + 56, /* max TCP header length without port numbers*/ + ); + + let mut fuzzer = TcpHeaderFuzzer([0; 56], copy_len); + fuzzer.0[..copy_len].copy_from_slice(&data[..copy_len]); + fuzzer + } +} + +impl smoltcp::phy::Fuzzer for TcpHeaderFuzzer { + fn fuzz_packet(&self, frame_data: &mut [u8]) { + if self.1 == 0 { + return; + } + + let tcp_packet_offset = { + let eth_frame = EthernetFrame::new_unchecked(&frame_data); + EthernetFrame::<&mut [u8]>::header_len() + + match eth_frame.ethertype() { + EthernetProtocol::Ipv4 => { + Ipv4Packet::new_unchecked(eth_frame.payload()).header_len() as usize + } + EthernetProtocol::Ipv6 => { + Ipv6Packet::new_unchecked(eth_frame.payload()).header_len() as usize + } + _ => return, + } + }; + + let tcp_is_syn = { + let tcp_packet = TcpPacket::new_checked(&frame_data[tcp_packet_offset..]).unwrap(); + tcp_packet.syn() + }; + + if tcp_is_syn { + return; + } + + if !frame_data.ends_with(b"abcdef") { + return; + } + + let tcp_header_len = { + let tcp_packet = &frame_data[tcp_packet_offset..]; + (tcp_packet[12] as usize >> 4) * 4 + }; + + let tcp_packet = &mut frame_data[tcp_packet_offset + 4..]; + + let replacement_data = &self.0[..self.1]; + let copy_len = cmp::min(replacement_data.len(), tcp_header_len); + assert!(copy_len < tcp_packet.len()); + tcp_packet[..copy_len].copy_from_slice(&replacement_data[..copy_len]); + } +} + +struct EmptyFuzzer(); + +impl smoltcp::phy::Fuzzer for EmptyFuzzer { + fn fuzz_packet(&self, _: &mut [u8]) {} +} + +fuzz_target!(|data: &[u8]| { + let clock = mock::Clock::new(); + + let device = { + let (mut opts, mut free) = utils::create_options(); + utils::add_middleware_options(&mut opts, &mut free); + + let mut matches = utils::parse_options(&opts, free); + let device = utils::parse_middleware_options( + &mut matches, + Loopback::new(Medium::Ethernet), + /*loopback=*/ true, + ); + + smoltcp::phy::FuzzInjector::new(device, EmptyFuzzer(), TcpHeaderFuzzer::new(data)) + }; + + let mut neighbor_cache_entries = [None; 8]; + let neighbor_cache = NeighborCache::new(&mut neighbor_cache_entries[..]); + + let ip_addrs = [IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)]; + let mut iface = InterfaceBuilder::new() + .ethernet_addr(EthernetAddress::default()) + .neighbor_cache(neighbor_cache) + .ip_addrs(ip_addrs) + .finalize(&mut device); + + let server_socket = { + // It is not strictly necessary to use a `static mut` and unsafe code here, but + // on embedded systems that smoltcp targets it is far better to allocate the data + // statically to verify that it fits into RAM rather than get undefined behavior + // when stack overflows. + static mut TCP_SERVER_RX_DATA: [u8; 1024] = [0; 1024]; + static mut TCP_SERVER_TX_DATA: [u8; 1024] = [0; 1024]; + let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_RX_DATA[..] }); + let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_SERVER_TX_DATA[..] }); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let client_socket = { + static mut TCP_CLIENT_RX_DATA: [u8; 1024] = [0; 1024]; + static mut TCP_CLIENT_TX_DATA: [u8; 1024] = [0; 1024]; + let tcp_rx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_RX_DATA[..] }); + let tcp_tx_buffer = tcp::SocketBuffer::new(unsafe { &mut TCP_CLIENT_TX_DATA[..] }); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let mut socket_set_entries: [_; 2] = Default::default(); + let mut socket_set = SocketSet::new(&mut socket_set_entries[..]); + let server_handle = socket_set.add(server_socket); + let client_handle = socket_set.add(client_socket); + + let mut did_listen = false; + let mut did_connect = false; + let mut done = false; + while !done && clock.elapsed() < Instant::from_millis(4_000) { + let _ = iface.poll(&mut socket_set, clock.elapsed()); + + { + let mut socket = socket_set.get::(server_handle); + if !socket.is_active() && !socket.is_listening() { + if !did_listen { + socket.listen(1234).unwrap(); + did_listen = true; + } + } + + if socket.can_recv() { + socket.close(); + done = true; + } + } + + { + let mut socket = socket_set.get::(client_handle); + if !socket.is_open() { + if !did_connect { + socket + .connect( + (IpAddress::v4(127, 0, 0, 1), 1234), + (IpAddress::Unspecified, 65000), + ) + .unwrap(); + did_connect = true; + } + } + + if socket.can_send() { + socket + .send_slice(b"0123456789abcdef0123456789abcdef0123456789abcdef") + .unwrap(); + socket.close(); + } + } + + match iface.poll_delay(&socket_set, clock.elapsed()) { + Some(Duration::ZERO) => {} + Some(delay) => clock.advance(delay), + None => clock.advance(Duration::from_millis(1)), + } + } +}); diff --git a/vendor/smoltcp/fuzz/utils.rs b/vendor/smoltcp/fuzz/utils.rs new file mode 100644 index 00000000..20644342 --- /dev/null +++ b/vendor/smoltcp/fuzz/utils.rs @@ -0,0 +1,157 @@ +// TODO: this is literally a copy of examples/utils.rs, but without an allow dead code attribute. +// The include logic does not allow having attributes in included files. + +use getopts::{Matches, Options}; +use std::env; +use std::fs::File; +use std::io; +use std::io::Write; +use std::process; +use std::str::{self, FromStr}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use smoltcp::phy::{Device, FaultInjector, Tracer}; +use smoltcp::phy::{PcapMode, PcapWriter}; +use smoltcp::time::Duration; + +pub fn create_options() -> (Options, Vec<&'static str>) { + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help menu"); + (opts, Vec::new()) +} + +pub fn parse_options(options: &Options, free: Vec<&str>) -> Matches { + match options.parse(env::args().skip(1)) { + Err(err) => { + println!("{}", err); + process::exit(1) + } + Ok(matches) => { + if matches.opt_present("h") || matches.free.len() != free.len() { + let brief = format!( + "Usage: {} [OPTION]... {}", + env::args().nth(0).unwrap(), + free.join(" ") + ); + print!("{}", options.usage(&brief)); + process::exit(if matches.free.len() != free.len() { + 1 + } else { + 0 + }) + } + matches + } + } +} + +pub fn add_middleware_options(opts: &mut Options, _free: &mut Vec<&str>) { + opts.optopt("", "pcap", "Write a packet capture file", "FILE"); + opts.optopt( + "", + "drop-chance", + "Chance of dropping a packet (%)", + "CHANCE", + ); + opts.optopt( + "", + "corrupt-chance", + "Chance of corrupting a packet (%)", + "CHANCE", + ); + opts.optopt( + "", + "size-limit", + "Drop packets larger than given size (octets)", + "SIZE", + ); + opts.optopt( + "", + "tx-rate-limit", + "Drop packets after transmit rate exceeds given limit \ + (packets per interval)", + "RATE", + ); + opts.optopt( + "", + "rx-rate-limit", + "Drop packets after transmit rate exceeds given limit \ + (packets per interval)", + "RATE", + ); + opts.optopt( + "", + "shaping-interval", + "Sets the interval for rate limiting (ms)", + "RATE", + ); +} + +pub fn parse_middleware_options( + matches: &mut Matches, + device: D, + loopback: bool, +) -> FaultInjector>>> +where + D: Device, +{ + let drop_chance = matches + .opt_str("drop-chance") + .map(|s| u8::from_str(&s).unwrap()) + .unwrap_or(0); + let corrupt_chance = matches + .opt_str("corrupt-chance") + .map(|s| u8::from_str(&s).unwrap()) + .unwrap_or(0); + let size_limit = matches + .opt_str("size-limit") + .map(|s| usize::from_str(&s).unwrap()) + .unwrap_or(0); + let tx_rate_limit = matches + .opt_str("tx-rate-limit") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(0); + let rx_rate_limit = matches + .opt_str("rx-rate-limit") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(0); + let shaping_interval = matches + .opt_str("shaping-interval") + .map(|s| u64::from_str(&s).unwrap()) + .unwrap_or(0); + + let pcap_writer: Box; + if let Some(pcap_filename) = matches.opt_str("pcap") { + pcap_writer = Box::new(File::create(pcap_filename).expect("cannot open file")) + } else { + pcap_writer = Box::new(io::sink()) + } + + let seed = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .subsec_nanos(); + + let device = PcapWriter::new( + device, + pcap_writer, + if loopback { + PcapMode::TxOnly + } else { + PcapMode::Both + }, + ); + + let device = Tracer::new(device, |_timestamp, _printer| { + #[cfg(feature = "log")] + trace!("{}", _printer); + }); + let mut device = FaultInjector::new(device, seed); + device.set_drop_chance(drop_chance); + device.set_corrupt_chance(corrupt_chance); + device.set_max_packet_size(size_limit); + device.set_max_tx_rate(tx_rate_limit); + device.set_max_rx_rate(rx_rate_limit); + device.set_bucket_interval(Duration::from_millis(shaping_interval)); + device +} diff --git a/vendor/smoltcp/gen_config.py b/vendor/smoltcp/gen_config.py new file mode 100644 index 00000000..73d454e1 --- /dev/null +++ b/vendor/smoltcp/gen_config.py @@ -0,0 +1,87 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min, max, pow2=None): + vals = set() + val = min + while val <= max: + vals.add(val) + if pow2 == True or (isinstance(pow2, int) and val >= pow2): + val *= 2 + else: + val += 1 + vals.add(default) + + features.append( + { + "name": name, + "default": default, + "vals": sorted(list(vals)), + } + ) + + +feature("iface_max_addr_count", default=2, min=1, max=8) +feature("iface_max_multicast_group_count", default=4, min=1, max=1024, pow2=8) +feature("iface_max_sixlowpan_address_context_count", default=4, min=1, max=1024, pow2=8) +feature("iface_neighbor_cache_count", default=8, min=1, max=1024, pow2=8) +feature("iface_max_route_count", default=2, min=0, max=1024, pow2=8) +feature("iface_max_prefix_count", default=1, min=1, max=8) +feature("fragmentation_buffer_size", default=1500, min=256, max=65536, pow2=True) +feature("assembler_max_segment_count", default=4, min=1, max=32, pow2=4) +feature("reassembly_buffer_size", default=1500, min=256, max=65536, pow2=True) +feature("reassembly_buffer_count", default=1, min=1, max=32, pow2=4) +feature("ipv6_hbh_max_options", default=4, min=1, max=32, pow2=4) +feature("dns_max_result_count", default=1, min=1, max=32, pow2=4) +feature("dns_max_server_count", default=1, min=1, max=32, pow2=4) +feature("dns_max_name_size", default=255, min=64, max=255, pow2=True) +feature("rpl_relations_buffer_count", default=16, min=1, max=128, pow2=True) +feature("rpl_parents_buffer_count", default=8, min=2, max=32, pow2=True) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"{name}-{val} = []" + if val == f["default"]: + things += " # Default" + things += "\n" + things += "\n" + +SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" +HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" +with open("Cargo.toml", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after +with open("Cargo.toml", "w") as f: + f.write(data) + + +# ========= Update build.rs + +things = "" +for f in features: + name = f["name"].upper() + things += f' ("{name}", {f["default"]}),\n' + +SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" +HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" +with open("build.rs", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after +with open("build.rs", "w") as f: + f.write(data) diff --git a/vendor/smoltcp/src/iface/fragmentation.rs b/vendor/smoltcp/src/iface/fragmentation.rs new file mode 100644 index 00000000..16c84f45 --- /dev/null +++ b/vendor/smoltcp/src/iface/fragmentation.rs @@ -0,0 +1,506 @@ +#![allow(unused)] + +use core::fmt; + +use managed::{ManagedMap, ManagedSlice}; + +use crate::config::{FRAGMENTATION_BUFFER_SIZE, REASSEMBLY_BUFFER_COUNT, REASSEMBLY_BUFFER_SIZE}; +use crate::storage::Assembler; +use crate::time::{Duration, Instant}; +use crate::wire::*; + +use core::result::Result; + +#[cfg(feature = "alloc")] +type Buffer = alloc::vec::Vec; +#[cfg(not(feature = "alloc"))] +type Buffer = [u8; REASSEMBLY_BUFFER_SIZE]; + +/// Problem when assembling: something was out of bounds. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssemblerError; + +impl fmt::Display for AssemblerError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "AssemblerError") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AssemblerError {} + +/// Packet assembler is full +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssemblerFullError; + +impl fmt::Display for AssemblerFullError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "AssemblerFullError") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for AssemblerFullError {} + +/// Holds different fragments of one packet, used for assembling fragmented packets. +/// +/// The buffer used for the `PacketAssembler` should either be dynamically sized (ex: Vec) +/// or should be statically allocated based upon the MTU of the type of packet being +/// assembled (ex: 1280 for a IPv6 frame). +#[derive(Debug)] +pub struct PacketAssembler { + key: Option, + buffer: Buffer, + + assembler: Assembler, + total_size: Option, + expires_at: Instant, +} + +impl PacketAssembler { + /// Create a new empty buffer for fragments. + pub const fn new() -> Self { + Self { + key: None, + + #[cfg(feature = "alloc")] + buffer: Buffer::new(), + #[cfg(not(feature = "alloc"))] + buffer: [0u8; REASSEMBLY_BUFFER_SIZE], + + assembler: Assembler::new(), + total_size: None, + expires_at: Instant::ZERO, + } + } + + pub(crate) fn reset(&mut self) { + self.key = None; + self.assembler.clear(); + self.total_size = None; + self.expires_at = Instant::ZERO; + } + + /// Set the total size of the packet assembler. + pub(crate) fn set_total_size(&mut self, size: usize) -> Result<(), AssemblerError> { + if let Some(old_size) = self.total_size { + if old_size != size { + return Err(AssemblerError); + } + } + + #[cfg(not(feature = "alloc"))] + if self.buffer.len() < size { + return Err(AssemblerError); + } + + #[cfg(feature = "alloc")] + if self.buffer.len() < size { + self.buffer.resize(size, 0); + } + + self.total_size = Some(size); + Ok(()) + } + + /// Return the instant when the assembler expires. + pub(crate) fn expires_at(&self) -> Instant { + self.expires_at + } + + pub(crate) fn add_with( + &mut self, + offset: usize, + f: impl Fn(&mut [u8]) -> Result, + ) -> Result<(), AssemblerError> { + if self.buffer.len() < offset { + return Err(AssemblerError); + } + + let len = f(&mut self.buffer[offset..])?; + assert!(offset + len <= self.buffer.len()); + + net_debug!( + "frag assembler: receiving {} octets at offset {}", + len, + offset + ); + + self.assembler.add(offset, len); + Ok(()) + } + + /// Add a fragment into the packet that is being reassembled. + /// + /// # Errors + /// + /// - Returns [`Error::PacketAssemblerBufferTooSmall`] when trying to add data into the buffer at a non-existing + /// place. + pub(crate) fn add(&mut self, data: &[u8], offset: usize) -> Result<(), AssemblerError> { + #[cfg(not(feature = "alloc"))] + if self.buffer.len() < offset + data.len() { + return Err(AssemblerError); + } + + #[cfg(feature = "alloc")] + if self.buffer.len() < offset + data.len() { + self.buffer.resize(offset + data.len(), 0); + } + + let len = data.len(); + self.buffer[offset..][..len].copy_from_slice(data); + + net_debug!( + "frag assembler: receiving {} octets at offset {}", + len, + offset + ); + + self.assembler.add(offset, data.len()); + Ok(()) + } + + /// Get an immutable slice of the underlying packet data, if reassembly complete. + /// This will mark the assembler as empty, so that it can be reused. + pub(crate) fn assemble(&mut self) -> Option<&'_ [u8]> { + if !self.is_complete() { + return None; + } + + // NOTE: we can unwrap because `is_complete` already checks this. + let total_size = self.total_size.unwrap(); + self.reset(); + Some(&self.buffer[..total_size]) + } + + /// Returns `true` when all fragments have been received, otherwise `false`. + pub(crate) fn is_complete(&self) -> bool { + self.total_size == Some(self.assembler.peek_front()) + } + + /// Returns `true` when the packet assembler is free to use. + fn is_free(&self) -> bool { + self.key.is_none() + } +} + +/// Set holding multiple [`PacketAssembler`]. +#[derive(Debug)] +pub struct PacketAssemblerSet { + assemblers: [PacketAssembler; REASSEMBLY_BUFFER_COUNT], +} + +impl PacketAssemblerSet { + const NEW_PA: PacketAssembler = PacketAssembler::new(); + + /// Create a new set of packet assemblers. + pub fn new() -> Self { + Self { + assemblers: [Self::NEW_PA; REASSEMBLY_BUFFER_COUNT], + } + } + + /// Get a [`PacketAssembler`] for a specific key. + /// + /// If it doesn't exist, it is created, with the `expires_at` timestamp. + /// + /// If the assembler set is full, in which case an error is returned. + pub(crate) fn get( + &mut self, + key: &K, + expires_at: Instant, + ) -> Result<&mut PacketAssembler, AssemblerFullError> { + let mut empty_slot = None; + for slot in &mut self.assemblers { + if slot.key.as_ref() == Some(key) { + return Ok(slot); + } + if slot.is_free() { + empty_slot = Some(slot) + } + } + + let slot = empty_slot.ok_or(AssemblerFullError)?; + slot.key = Some(*key); + slot.expires_at = expires_at; + Ok(slot) + } + + /// Remove all [`PacketAssembler`]s that are expired. + pub fn remove_expired(&mut self, timestamp: Instant) { + for frag in &mut self.assemblers { + if !frag.is_free() && frag.expires_at < timestamp { + frag.reset(); + } + } + } +} + +// Max len of non-fragmented packets after decompression (including ipv6 header and payload) +// TODO: lower. Should be (6lowpan mtu) - (min 6lowpan header size) + (max ipv6 header size) +pub(crate) const MAX_DECOMPRESSED_LEN: usize = 1500; + +#[cfg(feature = "_proto-fragmentation")] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum FragKey { + #[cfg(feature = "proto-ipv4-fragmentation")] + Ipv4(Ipv4FragKey), + #[cfg(feature = "proto-sixlowpan-fragmentation")] + Sixlowpan(SixlowpanFragKey), +} + +pub(crate) struct FragmentsBuffer { + #[cfg(feature = "proto-sixlowpan")] + pub decompress_buf: [u8; MAX_DECOMPRESSED_LEN], + + #[cfg(feature = "_proto-fragmentation")] + pub assembler: PacketAssemblerSet, + + #[cfg(feature = "_proto-fragmentation")] + pub reassembly_timeout: Duration, +} + +#[cfg(not(feature = "_proto-fragmentation"))] +pub(crate) struct Fragmenter {} + +#[cfg(not(feature = "_proto-fragmentation"))] +impl Fragmenter { + pub(crate) fn new() -> Self { + Self {} + } +} + +#[cfg(feature = "_proto-fragmentation")] +pub(crate) struct Fragmenter { + /// The buffer that holds the unfragmented 6LoWPAN packet. + pub buffer: [u8; FRAGMENTATION_BUFFER_SIZE], + /// The size of the packet without the IEEE802.15.4 header and the fragmentation headers. + pub packet_len: usize, + /// The amount of bytes that already have been transmitted. + pub sent_bytes: usize, + + #[cfg(feature = "proto-ipv4-fragmentation")] + pub ipv4: Ipv4Fragmenter, + #[cfg(feature = "proto-sixlowpan-fragmentation")] + pub sixlowpan: SixlowpanFragmenter, +} + +#[cfg(feature = "proto-ipv4-fragmentation")] +pub(crate) struct Ipv4Fragmenter { + /// The IPv4 representation. + pub repr: Ipv4Repr, + /// The destination hardware address. + #[cfg(feature = "medium-ethernet")] + pub dst_hardware_addr: EthernetAddress, + /// The offset of the next fragment. + pub frag_offset: u16, + /// The identifier of the stream. + pub ident: u16, +} + +#[cfg(feature = "proto-sixlowpan-fragmentation")] +pub(crate) struct SixlowpanFragmenter { + /// The datagram size that is used for the fragmentation headers. + pub datagram_size: u16, + /// The datagram tag that is used for the fragmentation headers. + pub datagram_tag: u16, + pub datagram_offset: usize, + + /// The size of the FRAG_N packets. + pub fragn_size: usize, + + /// The link layer IEEE802.15.4 source address. + pub ll_dst_addr: Ieee802154Address, + /// The link layer IEEE802.15.4 source address. + pub ll_src_addr: Ieee802154Address, +} + +#[cfg(feature = "_proto-fragmentation")] +impl Fragmenter { + pub(crate) fn new() -> Self { + Self { + buffer: [0u8; FRAGMENTATION_BUFFER_SIZE], + packet_len: 0, + sent_bytes: 0, + + #[cfg(feature = "proto-ipv4-fragmentation")] + ipv4: Ipv4Fragmenter { + repr: Ipv4Repr { + src_addr: Ipv4Address::new(0, 0, 0, 0), + dst_addr: Ipv4Address::new(0, 0, 0, 0), + next_header: IpProtocol::Unknown(0), + payload_len: 0, + hop_limit: 0, + }, + #[cfg(feature = "medium-ethernet")] + dst_hardware_addr: EthernetAddress::default(), + frag_offset: 0, + ident: 0, + }, + + #[cfg(feature = "proto-sixlowpan-fragmentation")] + sixlowpan: SixlowpanFragmenter { + datagram_size: 0, + datagram_tag: 0, + datagram_offset: 0, + fragn_size: 0, + ll_dst_addr: Ieee802154Address::Absent, + ll_src_addr: Ieee802154Address::Absent, + }, + } + } + + /// Return `true` when everything is transmitted. + #[inline] + pub(crate) fn finished(&self) -> bool { + self.packet_len == self.sent_bytes + } + + /// Returns `true` when there is nothing to transmit. + #[inline] + pub(crate) fn is_empty(&self) -> bool { + self.packet_len == 0 + } + + // Reset the buffer. + pub(crate) fn reset(&mut self) { + self.packet_len = 0; + self.sent_bytes = 0; + + #[cfg(feature = "proto-ipv4-fragmentation")] + { + self.ipv4.repr = Ipv4Repr { + src_addr: Ipv4Address::new(0, 0, 0, 0), + dst_addr: Ipv4Address::new(0, 0, 0, 0), + next_header: IpProtocol::Unknown(0), + payload_len: 0, + hop_limit: 0, + }; + #[cfg(feature = "medium-ethernet")] + { + self.ipv4.dst_hardware_addr = EthernetAddress::default(); + } + } + + #[cfg(feature = "proto-sixlowpan-fragmentation")] + { + self.sixlowpan.datagram_size = 0; + self.sixlowpan.datagram_tag = 0; + self.sixlowpan.fragn_size = 0; + self.sixlowpan.ll_dst_addr = Ieee802154Address::Absent; + self.sixlowpan.ll_src_addr = Ieee802154Address::Absent; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] + struct Key { + id: usize, + } + + #[test] + fn packet_assembler_overlap() { + let mut p_assembler = PacketAssembler::::new(); + + p_assembler.set_total_size(5).unwrap(); + + let data = b"Rust"; + p_assembler.add(&data[..], 0); + p_assembler.add(&data[..], 1); + + assert_eq!(p_assembler.assemble(), Some(&b"RRust"[..])) + } + + #[test] + fn packet_assembler_assemble() { + let mut p_assembler = PacketAssembler::::new(); + + let data = b"Hello World!"; + + p_assembler.set_total_size(data.len()).unwrap(); + + p_assembler.add(b"Hello ", 0).unwrap(); + assert_eq!(p_assembler.assemble(), None); + + p_assembler.add(b"World!", b"Hello ".len()).unwrap(); + + assert_eq!(p_assembler.assemble(), Some(&b"Hello World!"[..])); + } + + #[test] + fn packet_assembler_out_of_order_assemble() { + let mut p_assembler = PacketAssembler::::new(); + + let data = b"Hello World!"; + + p_assembler.set_total_size(data.len()).unwrap(); + + p_assembler.add(b"World!", b"Hello ".len()).unwrap(); + assert_eq!(p_assembler.assemble(), None); + + p_assembler.add(b"Hello ", 0).unwrap(); + + assert_eq!(p_assembler.assemble(), Some(&b"Hello World!"[..])); + } + + #[test] + fn packet_assembler_set() { + let key = Key { id: 1 }; + + let mut set = PacketAssemblerSet::new(); + + assert!(set.get(&key, Instant::ZERO).is_ok()); + } + + #[test] + fn packet_assembler_set_full() { + let mut set = PacketAssemblerSet::new(); + for i in 0..REASSEMBLY_BUFFER_COUNT { + set.get(&Key { id: i }, Instant::ZERO).unwrap(); + } + assert!(set.get(&Key { id: 4 }, Instant::ZERO).is_err()); + } + + #[test] + fn packet_assembler_set_assembling_many() { + let mut set = PacketAssemblerSet::new(); + + let key = Key { id: 0 }; + let assr = set.get(&key, Instant::ZERO).unwrap(); + assert_eq!(assr.assemble(), None); + assr.set_total_size(0).unwrap(); + assr.assemble().unwrap(); + + // Test that `.assemble()` effectively deletes it. + let assr = set.get(&key, Instant::ZERO).unwrap(); + assert_eq!(assr.assemble(), None); + assr.set_total_size(0).unwrap(); + assr.assemble().unwrap(); + + let key = Key { id: 1 }; + let assr = set.get(&key, Instant::ZERO).unwrap(); + assr.set_total_size(0).unwrap(); + assr.assemble().unwrap(); + + let key = Key { id: 2 }; + let assr = set.get(&key, Instant::ZERO).unwrap(); + assr.set_total_size(0).unwrap(); + assr.assemble().unwrap(); + + let key = Key { id: 2 }; + let assr = set.get(&key, Instant::ZERO).unwrap(); + assr.set_total_size(2).unwrap(); + assr.add(&[0x00], 0).unwrap(); + assert_eq!(assr.assemble(), None); + let assr = set.get(&key, Instant::ZERO).unwrap(); + assr.add(&[0x01], 1).unwrap(); + assert_eq!(assr.assemble(), Some(&[0x00, 0x01][..])); + } +} diff --git a/vendor/smoltcp/src/iface/interface/ethernet.rs b/vendor/smoltcp/src/iface/interface/ethernet.rs new file mode 100644 index 00000000..19d203b4 --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/ethernet.rs @@ -0,0 +1,71 @@ +use super::*; + +impl InterfaceInner { + pub(super) fn process_ethernet<'frame>( + &mut self, + sockets: &mut SocketSet, + meta: crate::phy::PacketMeta, + frame: &'frame [u8], + fragments: &'frame mut FragmentsBuffer, + ) -> Option> { + let eth_frame = check!(EthernetFrame::new_checked(frame)); + + // Ignore any packets not directed to our hardware address or any of the multicast groups. + if !eth_frame.dst_addr().is_broadcast() + && !eth_frame.dst_addr().is_multicast() + && HardwareAddress::Ethernet(eth_frame.dst_addr()) != self.hardware_addr + { + return None; + } + + match eth_frame.ethertype() { + #[cfg(feature = "proto-ipv4")] + EthernetProtocol::Arp => self.process_arp(self.now, ð_frame), + #[cfg(feature = "proto-ipv4")] + EthernetProtocol::Ipv4 => { + let ipv4_packet = check!(Ipv4Packet::new_checked(eth_frame.payload())); + + self.process_ipv4( + sockets, + meta, + eth_frame.src_addr().into(), + &ipv4_packet, + fragments, + ) + .map(EthernetPacket::Ip) + } + #[cfg(feature = "proto-ipv6")] + EthernetProtocol::Ipv6 => { + let ipv6_packet = check!(Ipv6Packet::new_checked(eth_frame.payload())); + self.process_ipv6(sockets, meta, eth_frame.src_addr().into(), &ipv6_packet) + .map(EthernetPacket::Ip) + } + // Drop all other traffic. + _ => None, + } + } + + pub(super) fn dispatch_ethernet( + &mut self, + tx_token: Tx, + buffer_len: usize, + f: F, + ) -> Result<(), DispatchError> + where + Tx: TxToken, + F: FnOnce(EthernetFrame<&mut [u8]>), + { + let tx_len = EthernetFrame::<&[u8]>::buffer_len(buffer_len); + tx_token.consume(tx_len, |tx_buffer| { + debug_assert!(tx_buffer.as_ref().len() == tx_len); + let mut frame = EthernetFrame::new_unchecked(tx_buffer); + + let src_addr = self.hardware_addr.ethernet_or_panic(); + frame.set_src_addr(src_addr); + + f(frame); + }); + + Ok(()) + } +} diff --git a/vendor/smoltcp/src/iface/interface/ieee802154.rs b/vendor/smoltcp/src/iface/interface/ieee802154.rs new file mode 100644 index 00000000..c053ec3d --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/ieee802154.rs @@ -0,0 +1,100 @@ +use super::*; + +impl InterfaceInner { + /// Return the next IEEE802.15.4 sequence number. + #[cfg(feature = "medium-ieee802154")] + pub(super) fn next_ieee802154_seq_number(&mut self) -> u8 { + let no = self.sequence_no; + self.sequence_no = self.sequence_no.wrapping_add(1); + no + } + + pub(super) fn process_ieee802154<'output, 'payload: 'output>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + sixlowpan_payload: &'payload [u8], + _fragments: &'output mut FragmentsBuffer, + ) -> Option> { + let ieee802154_frame = check!(Ieee802154Frame::new_checked(sixlowpan_payload)); + + if ieee802154_frame.frame_type() != Ieee802154FrameType::Data { + return None; + } + + let ieee802154_repr = check!(Ieee802154Repr::parse(&ieee802154_frame)); + + // Drop frames when the user has set a PAN id and the PAN id from frame is not equal to this + // When the user didn't set a PAN id (so it is None), then we accept all PAN id's. + // We always accept the broadcast PAN id. + if self.pan_id.is_some() + && ieee802154_repr.dst_pan_id != self.pan_id + && ieee802154_repr.dst_pan_id != Some(Ieee802154Pan::BROADCAST) + { + net_debug!( + "IEEE802.15.4: dropping {:?} because not our PAN id (or not broadcast)", + ieee802154_repr + ); + return None; + } + + match ieee802154_frame.payload() { + Some(payload) => { + self.process_sixlowpan(sockets, meta, &ieee802154_repr, payload, _fragments) + } + None => None, + } + } + + pub(super) fn dispatch_ieee802154( + &mut self, + ll_dst_a: Ieee802154Address, + tx_token: Tx, + meta: PacketMeta, + packet: Packet, + frag: &mut Fragmenter, + ) { + let ll_src_a = self.hardware_addr.ieee802154_or_panic(); + + // Create the IEEE802.15.4 header. + let ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(self.next_ieee802154_seq_number()), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: self.pan_id, + dst_addr: Some(ll_dst_a), + src_pan_id: self.pan_id, + src_addr: Some(ll_src_a), + }; + + self.dispatch_sixlowpan(tx_token, meta, packet, ieee_repr, frag); + } + + #[cfg(feature = "proto-sixlowpan-fragmentation")] + pub(super) fn dispatch_ieee802154_frag( + &mut self, + tx_token: Tx, + frag: &mut Fragmenter, + ) { + // Create the IEEE802.15.4 header. + let ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(self.next_ieee802154_seq_number()), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: self.pan_id, + dst_addr: Some(frag.sixlowpan.ll_dst_addr), + src_pan_id: self.pan_id, + src_addr: Some(frag.sixlowpan.ll_src_addr), + }; + + self.dispatch_sixlowpan_frag(tx_token, ieee_repr, frag); + } +} diff --git a/vendor/smoltcp/src/iface/interface/ipv4.rs b/vendor/smoltcp/src/iface/interface/ipv4.rs new file mode 100644 index 00000000..24143c1c --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/ipv4.rs @@ -0,0 +1,483 @@ +use super::*; + +impl Interface { + /// Process fragments that still need to be sent for IPv4 packets. + /// + /// This function returns a boolean value indicating whether any packets were + /// processed or emitted, and thus, whether the readiness of any socket might + /// have changed. + #[cfg(feature = "proto-ipv4-fragmentation")] + pub(super) fn ipv4_egress(&mut self, device: &mut (impl Device + ?Sized)) { + // Reset the buffer when we transmitted everything. + if self.fragmenter.finished() { + self.fragmenter.reset(); + } + + if self.fragmenter.is_empty() { + return; + } + + let pkt = &self.fragmenter; + if pkt.packet_len > pkt.sent_bytes { + if let Some(tx_token) = device.transmit(self.inner.now) { + self.inner + .dispatch_ipv4_frag(tx_token, &mut self.fragmenter); + } + } + } +} + +impl InterfaceInner { + /// Get the next IPv4 fragment identifier. + #[cfg(feature = "proto-ipv4-fragmentation")] + pub(super) fn next_ipv4_frag_ident(&mut self) -> u16 { + let ipv4_id = self.ipv4_id; + self.ipv4_id = self.ipv4_id.wrapping_add(1); + ipv4_id + } + + /// Get an IPv4 source address based on a destination address. + /// + /// This function tries to find the first IPv4 address from the interface + /// that is in the same subnet as the destination address. If no such + /// address is found, the first IPv4 address from the interface is returned. + #[allow(unused)] + pub(crate) fn get_source_address_ipv4(&self, dst_addr: &Ipv4Address) -> Option { + let mut first_ipv4 = None; + for cidr in self.ip_addrs.iter() { + #[allow(irrefutable_let_patterns)] // if only ipv4 is enabled + if let IpCidr::Ipv4(cidr) = cidr { + // Return immediately if we find an address in the same subnet + if cidr.contains_addr(dst_addr) { + return Some(cidr.address()); + } + + // Remember the first IPv4 address as fallback + if first_ipv4.is_none() { + first_ipv4 = Some(cidr.address()); + } + } + } + first_ipv4 + } + + /// Checks if an address is broadcast, taking into account ipv4 subnet-local + /// broadcast addresses. + pub(crate) fn is_broadcast_v4(&self, address: Ipv4Address) -> bool { + if address.is_broadcast() { + return true; + } + + self.ip_addrs + .iter() + .filter_map(|own_cidr| match own_cidr { + IpCidr::Ipv4(own_ip) => Some(own_ip.broadcast()?), + #[cfg(feature = "proto-ipv6")] + IpCidr::Ipv6(_) => None, + }) + .any(|broadcast_address| address == broadcast_address) + } + + /// Checks if an ipv4 address is unicast, taking into account subnet broadcast addresses + fn is_unicast_v4(&self, address: Ipv4Address) -> bool { + address.x_is_unicast() && !self.is_broadcast_v4(address) + } + + /// Get the first IPv4 address of the interface. + pub fn ipv4_addr(&self) -> Option { + self.ip_addrs.iter().find_map(|addr| match *addr { + IpCidr::Ipv4(cidr) => Some(cidr.address()), + #[allow(unreachable_patterns)] + _ => None, + }) + } + + pub(super) fn process_ipv4<'a>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + source_hardware_addr: HardwareAddress, + ipv4_packet: &Ipv4Packet<&'a [u8]>, + frag: &'a mut FragmentsBuffer, + ) -> Option> { + let mut ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum)); + if !self.is_unicast_v4(ipv4_repr.src_addr) && !ipv4_repr.src_addr.is_unspecified() { + // Discard packets with non-unicast source addresses but allow unspecified + net_debug!("non-unicast or unspecified source address"); + return None; + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + let ip_payload = { + if ipv4_packet.more_frags() || ipv4_packet.frag_offset() != 0 { + let key = FragKey::Ipv4(ipv4_packet.get_key()); + + let f = match frag.assembler.get(&key, self.now + frag.reassembly_timeout) { + Ok(f) => f, + Err(_) => { + net_debug!("No available packet assembler for fragmented packet"); + return None; + } + }; + + if !ipv4_packet.more_frags() { + // This is the last fragment, so we know the total size + check!(f.set_total_size( + ipv4_packet.total_len() as usize - ipv4_packet.header_len() as usize + + ipv4_packet.frag_offset() as usize, + )); + } + + if let Err(e) = f.add(ipv4_packet.payload(), ipv4_packet.frag_offset() as usize) { + net_debug!("fragmentation error: {:?}", e); + return None; + } + + let payload = f.assemble()?; + // Update the payload length, so that the raw sockets get the correct value. + ipv4_repr.payload_len = payload.len(); + payload + } else { + ipv4_packet.payload() + } + }; + + #[cfg(not(feature = "proto-ipv4-fragmentation"))] + let ip_payload = ipv4_packet.payload(); + + let ip_repr = IpRepr::Ipv4(ipv4_repr); + + #[cfg(feature = "socket-raw")] + let handled_by_raw_socket = self.raw_socket_filter(sockets, &ip_repr, ip_payload); + #[cfg(not(feature = "socket-raw"))] + let handled_by_raw_socket = false; + + #[cfg(feature = "socket-dhcpv4")] + { + use crate::socket::dhcpv4::Socket as Dhcpv4Socket; + + if ipv4_repr.next_header == IpProtocol::Udp + && matches!(self.caps.medium, Medium::Ethernet) + { + let udp_packet = check!(UdpPacket::new_checked(ip_payload)); + if let Some(dhcp_socket) = sockets + .items_mut() + .find_map(|i| Dhcpv4Socket::downcast_mut(&mut i.socket)) + { + // First check for source and dest ports, then do `UdpRepr::parse` if they match. + // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`) + if udp_packet.src_port() == dhcp_socket.server_port + && udp_packet.dst_port() == dhcp_socket.client_port + { + let udp_repr = check!(UdpRepr::parse( + &udp_packet, + &ipv4_repr.src_addr.into(), + &ipv4_repr.dst_addr.into(), + &self.caps.checksum + )); + dhcp_socket.process(self, &ipv4_repr, &udp_repr, udp_packet.payload()); + return None; + } + } + } + } + + if !self.has_ip_addr(ipv4_repr.dst_addr) + && !self.has_multicast_group(ipv4_repr.dst_addr) + && !self.is_broadcast_v4(ipv4_repr.dst_addr) + { + // Ignore IP packets not directed at us, or broadcast, or any of the multicast groups. + + if !ipv4_repr.dst_addr.x_is_unicast() { + net_trace!( + "Rejecting IPv4 packet; {} is not a unicast address", + ipv4_repr.dst_addr + ); + return None; + } + + if self + .routes + .lookup(&IpAddress::Ipv4(ipv4_repr.dst_addr), self.now) + .is_none_or(|router_addr| !self.has_ip_addr(router_addr)) + { + net_trace!("Rejecting IPv4 packet; no matching routes"); + + return None; + } + + net_trace!("Rejecting IPv4 packet; no assigned address"); + return None; + } + + #[cfg(feature = "medium-ethernet")] + if self.is_unicast_v4(ipv4_repr.dst_addr) { + self.neighbor_cache.reset_expiry_if_existing( + IpAddress::Ipv4(ipv4_repr.src_addr), + source_hardware_addr, + self.now, + ); + } + + match ipv4_repr.next_header { + IpProtocol::Icmp => self.process_icmpv4(sockets, ipv4_repr, ip_payload), + + #[cfg(feature = "multicast")] + IpProtocol::Igmp => self.process_igmp(ipv4_repr, ip_payload), + + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpProtocol::Udp => { + self.process_udp(sockets, meta, handled_by_raw_socket, ip_repr, ip_payload) + } + + #[cfg(feature = "socket-tcp")] + IpProtocol::Tcp => { + self.process_tcp(sockets, handled_by_raw_socket, ip_repr, ip_payload) + } + + _ if handled_by_raw_socket => None, + + _ => { + // Send back as much of the original payload as we can. + let payload_len = + icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU, ipv4_repr.buffer_len()); + let icmp_reply_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::ProtoUnreachable, + header: ipv4_repr, + data: &ip_payload[0..payload_len], + }; + self.icmpv4_reply(ipv4_repr, icmp_reply_repr) + } + } + } + + #[cfg(feature = "medium-ethernet")] + pub(super) fn process_arp<'frame>( + &mut self, + timestamp: Instant, + eth_frame: &EthernetFrame<&'frame [u8]>, + ) -> Option> { + let arp_packet = check!(ArpPacket::new_checked(eth_frame.payload())); + let arp_repr = check!(ArpRepr::parse(&arp_packet)); + + match arp_repr { + ArpRepr::EthernetIpv4 { + operation, + source_hardware_addr, + source_protocol_addr, + target_protocol_addr, + .. + } => { + // Only process ARP packets for us. + if !self.has_ip_addr(target_protocol_addr) { + return None; + } + + // Only process REQUEST and RESPONSE. + if let ArpOperation::Unknown(_) = operation { + net_debug!("arp: unknown operation code"); + return None; + } + + // Discard packets with non-unicast source addresses. + if !source_protocol_addr.x_is_unicast() || !source_hardware_addr.is_unicast() { + net_debug!("arp: non-unicast source address"); + return None; + } + + if !self.in_same_network(&IpAddress::Ipv4(source_protocol_addr)) { + net_debug!("arp: source IP address not in same network as us"); + return None; + } + + // Fill the ARP cache from any ARP packet aimed at us (both request or response). + // We fill from requests too because if someone is requesting our address they + // are probably going to talk to us, so we avoid having to request their address + // when we later reply to them. + self.neighbor_cache.fill( + source_protocol_addr.into(), + source_hardware_addr.into(), + timestamp, + ); + + if operation == ArpOperation::Request { + let src_hardware_addr = self.hardware_addr.ethernet_or_panic(); + + Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 { + operation: ArpOperation::Reply, + source_hardware_addr: src_hardware_addr, + source_protocol_addr: target_protocol_addr, + target_hardware_addr: source_hardware_addr, + target_protocol_addr: source_protocol_addr, + })) + } else { + None + } + } + } + } + + pub(super) fn process_icmpv4<'frame>( + &mut self, + _sockets: &mut SocketSet, + ip_repr: Ipv4Repr, + ip_payload: &'frame [u8], + ) -> Option> { + let icmp_packet = check!(Icmpv4Packet::new_checked(ip_payload)); + let icmp_repr = check!(Icmpv4Repr::parse(&icmp_packet, &self.caps.checksum)); + + #[cfg(feature = "socket-icmp")] + let mut handled_by_icmp_socket = false; + + #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))] + for icmp_socket in _sockets + .items_mut() + .filter_map(|i| icmp::Socket::downcast_mut(&mut i.socket)) + { + if icmp_socket.accepts_v4(self, &ip_repr, &icmp_repr) { + icmp_socket.process_v4(self, &ip_repr, &icmp_repr); + handled_by_icmp_socket = true; + } + } + + match icmp_repr { + // Respond to echo requests. + #[cfg(all(feature = "proto-ipv4", feature = "auto-icmp-echo-reply"))] + Icmpv4Repr::EchoRequest { + ident, + seq_no, + data, + } => { + let icmp_reply_repr = Icmpv4Repr::EchoReply { + ident, + seq_no, + data, + }; + self.icmpv4_reply(ip_repr, icmp_reply_repr) + } + + // Ignore any echo replies. + Icmpv4Repr::EchoReply { .. } => None, + + // Don't report an error if a packet with unknown type + // has been handled by an ICMP socket + #[cfg(feature = "socket-icmp")] + _ if handled_by_icmp_socket => None, + + // FIXME: do something correct here? + // By doing nothing, this arm handles the case when auto echo replies are disabled. + _ => None, + } + } + + pub(super) fn icmpv4_reply<'frame, 'icmp: 'frame>( + &self, + ipv4_repr: Ipv4Repr, + icmp_repr: Icmpv4Repr<'icmp>, + ) -> Option> { + if !self.is_unicast_v4(ipv4_repr.src_addr) { + // Do not send ICMP replies to non-unicast sources + None + } else if self.is_unicast_v4(ipv4_repr.dst_addr) { + // Reply as normal when src_addr and dst_addr are both unicast + let ipv4_reply_repr = Ipv4Repr { + src_addr: ipv4_repr.dst_addr, + dst_addr: ipv4_repr.src_addr, + next_header: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + hop_limit: 64, + }; + Some(Packet::new_ipv4( + ipv4_reply_repr, + IpPayload::Icmpv4(icmp_repr), + )) + } else if self.is_broadcast_v4(ipv4_repr.dst_addr) { + // Only reply to broadcasts for echo replies and not other ICMP messages + match icmp_repr { + Icmpv4Repr::EchoReply { .. } => match self.ipv4_addr() { + Some(src_addr) => { + let ipv4_reply_repr = Ipv4Repr { + src_addr, + dst_addr: ipv4_repr.src_addr, + next_header: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + hop_limit: 64, + }; + Some(Packet::new_ipv4( + ipv4_reply_repr, + IpPayload::Icmpv4(icmp_repr), + )) + } + None => None, + }, + _ => None, + } + } else { + None + } + } + + #[cfg(feature = "proto-ipv4-fragmentation")] + pub(super) fn dispatch_ipv4_frag(&mut self, tx_token: Tx, frag: &mut Fragmenter) { + let caps = self.caps.clone(); + + let max_fragment_size = caps.max_ipv4_fragment_size(frag.ipv4.repr.buffer_len()); + let payload_len = (frag.packet_len - frag.sent_bytes).min(max_fragment_size); + let ip_len = payload_len + frag.ipv4.repr.buffer_len(); + + let more_frags = (frag.packet_len - frag.sent_bytes) != payload_len; + frag.ipv4.repr.payload_len = payload_len; + frag.sent_bytes += payload_len; + + let mut tx_len = ip_len; + #[cfg(feature = "medium-ethernet")] + if matches!(caps.medium, Medium::Ethernet) { + tx_len += EthernetFrame::<&[u8]>::header_len(); + } + + // Emit function for the Ethernet header. + #[cfg(feature = "medium-ethernet")] + let emit_ethernet = |repr: &IpRepr, tx_buffer: &mut [u8]| { + let mut frame = EthernetFrame::new_unchecked(tx_buffer); + + let src_addr = self.hardware_addr.ethernet_or_panic(); + frame.set_src_addr(src_addr); + frame.set_dst_addr(frag.ipv4.dst_hardware_addr); + + match repr.version() { + #[cfg(feature = "proto-ipv4")] + IpVersion::Ipv4 => frame.set_ethertype(EthernetProtocol::Ipv4), + #[cfg(feature = "proto-ipv6")] + IpVersion::Ipv6 => frame.set_ethertype(EthernetProtocol::Ipv6), + } + }; + + tx_token.consume(tx_len, |mut tx_buffer| { + #[cfg(feature = "medium-ethernet")] + if matches!(self.caps.medium, Medium::Ethernet) { + emit_ethernet(&IpRepr::Ipv4(frag.ipv4.repr), tx_buffer); + tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..]; + } + + let mut packet = + Ipv4Packet::new_unchecked(&mut tx_buffer[..frag.ipv4.repr.buffer_len()]); + frag.ipv4.repr.emit(&mut packet, &caps.checksum); + packet.set_ident(frag.ipv4.ident); + packet.set_more_frags(more_frags); + packet.set_dont_frag(false); + packet.set_frag_offset(frag.ipv4.frag_offset); + + if caps.checksum.ipv4.tx() { + packet.fill_checksum(); + } + + tx_buffer[frag.ipv4.repr.buffer_len()..][..payload_len].copy_from_slice( + &frag.buffer[frag.ipv4.frag_offset as usize + frag.ipv4.repr.buffer_len()..] + [..payload_len], + ); + + // Update the frag offset for the next fragment. + frag.ipv4.frag_offset += payload_len as u16; + }) + } +} diff --git a/vendor/smoltcp/src/iface/interface/ipv6.rs b/vendor/smoltcp/src/iface/interface/ipv6.rs new file mode 100644 index 00000000..1bf6066b --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/ipv6.rs @@ -0,0 +1,741 @@ +use super::*; + +use crate::iface::Route; + +/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMP +/// parameter problem message needs to be transmitted to the source of the address. In other cases, +/// the processing of the IP packet can continue. +#[allow(clippy::large_enum_variant)] +enum HopByHopResponse<'frame> { + /// Continue processing the IPv6 packet. + Continue((IpProtocol, &'frame [u8])), + /// Discard the packet and maybe send back an ICMPv6 packet. + Discard(Option>), +} + +// We implement `Default` such that we can use the check! macro. +impl Default for HopByHopResponse<'_> { + fn default() -> Self { + Self::Discard(None) + } +} + +impl InterfaceInner { + /// Return the IPv6 address that is a candidate source address for the given destination + /// address, based on RFC 6724. + /// + /// # Panics + /// This function panics if the destination address is unspecified. + #[allow(unused)] + pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address { + assert!(!dst_addr.is_unspecified()); + + // See RFC 6724 Section 4: Candidate source address + fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool { + // For all multicast and link-local destination addresses, the candidate address MUST + // only be an address from the same link. + if dst_addr.is_link_local() && !src_addr.is_link_local() { + return false; + } + + if dst_addr.is_multicast() + && matches!(dst_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal) + && src_addr.is_multicast() + && !matches!(src_addr.x_multicast_scope(), Ipv6MulticastScope::LinkLocal) + { + return false; + } + + // Unspecified addresses and multicast address can not be in the candidate source address + // list. Except when the destination multicast address has a link-local scope, then the + // source address can also be link-local multicast. + if src_addr.is_unspecified() || src_addr.is_multicast() { + return false; + } + + true + } + + // See RFC 6724 Section 2.2: Common Prefix Length + fn common_prefix_length(dst_addr: &Ipv6Cidr, src_addr: &Ipv6Address) -> usize { + let addr = dst_addr.address(); + let mut bits = 0; + for (l, r) in addr.octets().iter().zip(src_addr.octets().iter()) { + if l == r { + bits += 8; + } else { + bits += (l ^ r).leading_zeros(); + break; + } + } + + bits = bits.min(dst_addr.prefix_len() as u32); + + bits as usize + } + + // If the destination address is a loopback address, or when there are no IPv6 addresses in + // the interface, then the loopback address is the only candidate source address. + if dst_addr.is_loopback() + || self + .ip_addrs + .iter() + .filter(|a| matches!(a, IpCidr::Ipv6(_))) + .count() + == 0 + { + return Ipv6Address::LOCALHOST; + } + + let mut candidate = self + .ip_addrs + .iter() + .find_map(|a| match a { + #[cfg(feature = "proto-ipv4")] + IpCidr::Ipv4(_) => None, + IpCidr::Ipv6(a) => Some(a), + }) + .unwrap(); // NOTE: we check above that there is at least one IPv6 address. + + for addr in self.ip_addrs.iter().filter_map(|a| match a { + #[cfg(feature = "proto-ipv4")] + IpCidr::Ipv4(_) => None, + #[cfg(feature = "proto-ipv6")] + IpCidr::Ipv6(a) => Some(a), + }) { + if !is_candidate_source_address(dst_addr, &addr.address()) { + continue; + } + + // Rule 1: prefer the address that is the same as the output destination address. + if candidate.address() != *dst_addr && addr.address() == *dst_addr { + candidate = addr; + } + + // Rule 2: prefer appropriate scope. + if (candidate.address().x_multicast_scope() as u8) + < (addr.address().x_multicast_scope() as u8) + { + if (candidate.address().x_multicast_scope() as u8) + < (dst_addr.x_multicast_scope() as u8) + { + candidate = addr; + } + } else if (addr.address().x_multicast_scope() as u8) + > (dst_addr.x_multicast_scope() as u8) + { + candidate = addr; + } + + // Rule 3: avoid deprecated addresses (TODO) + // Rule 4: prefer home addresses (TODO) + // Rule 5: prefer outgoing interfaces (TODO) + // Rule 5.5: prefer addresses in a prefix advertises by the next-hop (TODO). + // Rule 6: prefer matching label (TODO) + // Rule 7: prefer temporary addresses (TODO) + // Rule 8: use longest matching prefix + if common_prefix_length(candidate, dst_addr) < common_prefix_length(addr, dst_addr) { + candidate = addr; + } + } + + candidate.address() + } + + /// Determine if the given `Ipv6Address` is the solicited node + /// multicast address for a IPv6 addresses assigned to the interface. + /// See [RFC 4291 § 2.7.1] for more details. + /// + /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1 + pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool { + self.ip_addrs.iter().any(|cidr| { + match *cidr { + IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOCALHOST => { + // Take the lower order 24 bits of the IPv6 address and + // append those bits to FF02:0:0:0:0:1:FF00::/104. + addr.octets()[14..] == cidr.address().octets()[14..] + } + _ => false, + } + }) + } + + /// Get the first IPv6 address if present. + pub fn ipv6_addr(&self) -> Option { + self.ip_addrs.iter().find_map(|addr| match *addr { + IpCidr::Ipv6(cidr) => Some(cidr.address()), + #[allow(unreachable_patterns)] + _ => None, + }) + } + + /// Get the first link-local IPv6 address of the interface, if present. + fn link_local_ipv6_address(&self) -> Option { + self.ip_addrs.iter().find_map(|addr| match *addr { + #[cfg(feature = "proto-ipv4")] + IpCidr::Ipv4(_) => None, + #[cfg(feature = "proto-ipv6")] + IpCidr::Ipv6(cidr) => { + let addr = cidr.address(); + if addr.is_link_local() { + Some(addr) + } else { + None + } + } + }) + } + + pub(super) fn process_ipv6<'frame>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + source_hardware_addr: HardwareAddress, + ipv6_packet: &Ipv6Packet<&'frame [u8]>, + ) -> Option> { + let ipv6_repr = check!(Ipv6Repr::parse(ipv6_packet)); + + if !ipv6_repr.src_addr.x_is_unicast() { + // Discard packets with non-unicast source addresses. + net_debug!("non-unicast source address"); + return None; + } + + let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop { + match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) { + HopByHopResponse::Discard(e) => return e, + HopByHopResponse::Continue(next) => next, + } + } else { + (ipv6_repr.next_header, ipv6_packet.payload()) + }; + + if !self.has_ip_addr(ipv6_repr.dst_addr) + && !self.has_multicast_group(ipv6_repr.dst_addr) + && !ipv6_repr.dst_addr.is_loopback() + { + if !ipv6_repr.dst_addr.x_is_unicast() { + net_trace!( + "Rejecting IPv6 packet; {} is not a unicast address", + ipv6_repr.dst_addr + ); + return None; + } + + if self + .routes + .lookup(&IpAddress::Ipv6(ipv6_repr.dst_addr), self.now) + .is_none_or(|router_addr| !self.has_ip_addr(router_addr)) + { + net_trace!("Rejecting IPv6 packet; no matching routes"); + + return None; + } + + net_trace!("Rejecting IPv6 packet; no assigned address"); + return None; + } + + #[cfg(feature = "socket-raw")] + let handled_by_raw_socket = self.raw_socket_filter(sockets, &ipv6_repr.into(), ip_payload); + #[cfg(not(feature = "socket-raw"))] + let handled_by_raw_socket = false; + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + if ipv6_repr.dst_addr.x_is_unicast() { + self.neighbor_cache.reset_expiry_if_existing( + IpAddress::Ipv6(ipv6_repr.src_addr), + source_hardware_addr, + self.now, + ); + } + + self.process_nxt_hdr( + sockets, + meta, + ipv6_repr, + next_header, + handled_by_raw_socket, + ip_payload, + ) + } + + fn process_hopbyhop<'frame>( + &mut self, + ipv6_repr: Ipv6Repr, + ip_payload: &'frame [u8], + ) -> HopByHopResponse<'frame> { + let param_problem = || { + let payload_len = + icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len()); + self.icmpv6_reply( + ipv6_repr, + Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedOption, + pointer: ipv6_repr.buffer_len() as u32, + header: ipv6_repr, + data: &ip_payload[0..payload_len], + }, + ) + }; + + let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload)); + let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr)); + let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data)); + let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr)); + + for opt_repr in &hbh_repr.options { + match opt_repr { + Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) | Ipv6OptionRepr::RouterAlert(_) => { + } + #[cfg(feature = "proto-rpl")] + Ipv6OptionRepr::Rpl(_) => {} + + Ipv6OptionRepr::Unknown { type_, .. } => { + match Ipv6OptionFailureType::from(*type_) { + Ipv6OptionFailureType::Skip => (), + Ipv6OptionFailureType::Discard => { + return HopByHopResponse::Discard(None); + } + Ipv6OptionFailureType::DiscardSendAll => { + return HopByHopResponse::Discard(param_problem()); + } + Ipv6OptionFailureType::DiscardSendUnicast => { + if !ipv6_repr.dst_addr.is_multicast() { + return HopByHopResponse::Discard(param_problem()); + } else { + return HopByHopResponse::Discard(None); + } + } + } + } + } + } + + HopByHopResponse::Continue(( + ext_repr.next_header, + &ip_payload[ext_repr.header_len() + ext_repr.data.len()..], + )) + } + + /// Given the next header value forward the payload onto the correct process + /// function. + fn process_nxt_hdr<'frame>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + ipv6_repr: Ipv6Repr, + nxt_hdr: IpProtocol, + handled_by_raw_socket: bool, + ip_payload: &'frame [u8], + ) -> Option> { + match nxt_hdr { + IpProtocol::Icmpv6 => self.process_icmpv6(sockets, ipv6_repr, ip_payload), + + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpProtocol::Udp => self.process_udp( + sockets, + meta, + handled_by_raw_socket, + ipv6_repr.into(), + ip_payload, + ), + + #[cfg(feature = "socket-tcp")] + IpProtocol::Tcp => { + self.process_tcp(sockets, handled_by_raw_socket, ipv6_repr.into(), ip_payload) + } + + #[cfg(feature = "socket-raw")] + _ if handled_by_raw_socket => None, + + _ => { + // Send back as much of the original payload as we can. + let payload_len = + icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len()); + let icmp_reply_repr = Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedNxtHdr, + // The offending packet is after the IPv6 header. + pointer: ipv6_repr.buffer_len() as u32, + header: ipv6_repr, + data: &ip_payload[0..payload_len], + }; + self.icmpv6_reply(ipv6_repr, icmp_reply_repr) + } + } + } + + pub(super) fn process_icmpv6<'frame>( + &mut self, + _sockets: &mut SocketSet, + ip_repr: Ipv6Repr, + ip_payload: &'frame [u8], + ) -> Option> { + let icmp_packet = check!(Icmpv6Packet::new_checked(ip_payload)); + let icmp_repr = check!(Icmpv6Repr::parse( + &ip_repr.src_addr, + &ip_repr.dst_addr, + &icmp_packet, + &self.caps.checksum, + )); + + #[cfg(feature = "socket-icmp")] + let mut handled_by_icmp_socket = false; + + #[cfg(feature = "socket-icmp")] + { + use crate::socket::icmp::Socket as IcmpSocket; + for icmp_socket in _sockets + .items_mut() + .filter_map(|i| IcmpSocket::downcast_mut(&mut i.socket)) + { + if icmp_socket.accepts_v6(self, &ip_repr, &icmp_repr) { + icmp_socket.process_v6(self, &ip_repr, &icmp_repr); + handled_by_icmp_socket = true; + } + } + } + + match icmp_repr { + // Respond to echo requests. + #[cfg(feature = "auto-icmp-echo-reply")] + Icmpv6Repr::EchoRequest { + ident, + seq_no, + data, + } => { + let icmp_reply_repr = Icmpv6Repr::EchoReply { + ident, + seq_no, + data, + }; + self.icmpv6_reply(ip_repr, icmp_reply_repr) + } + + // Ignore any echo replies. + Icmpv6Repr::EchoReply { .. } => None, + + // Forward any NDISC packets to the ndisc packet handler + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit == 0xff => match self.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => self.process_ndisc(ip_repr, repr), + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => self.process_ndisc(ip_repr, repr), + #[cfg(feature = "medium-ip")] + Medium::Ip => None, + }, + #[cfg(feature = "multicast")] + Icmpv6Repr::Mld(repr) => match repr { + // [RFC 3810 § 6.2], reception checks + MldRepr::Query { .. } + if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() => + { + self.process_mldv2(ip_repr, repr) + } + _ => None, + }, + + // Don't report an error if a packet with unknown type + // has been handled by an ICMP socket + #[cfg(feature = "socket-icmp")] + _ if handled_by_icmp_socket => None, + + // FIXME: do something correct here? + // By doing nothing, this arm handles the case when auto echo replies are disabled. + _ => None, + } + } + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + pub(super) fn process_ndisc<'frame>( + &mut self, + ip_repr: Ipv6Repr, + repr: NdiscRepr<'frame>, + ) -> Option> { + match repr { + NdiscRepr::NeighborAdvert { + lladdr, + target_addr, + flags, + } => { + let ip_addr = ip_repr.src_addr.into(); + if let Some(lladdr) = lladdr { + let lladdr = check!(lladdr.parse(self.caps.medium)); + if !lladdr.is_unicast() || !target_addr.x_is_unicast() { + return None; + } + if flags.contains(NdiscNeighborFlags::OVERRIDE) + || !self.neighbor_cache.lookup(&ip_addr, self.now).found() + { + self.neighbor_cache.fill(ip_addr, lladdr, self.now) + } + } + None + } + NdiscRepr::NeighborSolicit { + target_addr, + lladdr, + .. + } => { + if let Some(lladdr) = lladdr { + let lladdr = check!(lladdr.parse(self.caps.medium)); + if !lladdr.is_unicast() || !target_addr.x_is_unicast() { + return None; + } + self.neighbor_cache + .fill(ip_repr.src_addr.into(), lladdr, self.now); + } + + if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) { + let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr, + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + lladdr: Some(self.hardware_addr.into()), + }); + let ip_repr = Ipv6Repr { + src_addr: target_addr, + dst_addr: ip_repr.src_addr, + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: advert.buffer_len(), + }; + Some(Packet::new_ipv6(ip_repr, IpPayload::Icmpv6(advert))) + } else { + None + } + } + #[cfg(feature = "proto-ipv6-slaac")] + NdiscRepr::RouterAdvert { + hop_limit: _, + flags: _, + router_lifetime, + reachable_time: _, + retrans_time: _, + lladdr: _, + mtu: _, + prefix_info, + } if self.slaac_enabled => { + if ip_repr.src_addr.is_link_local() + && (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES + || ip_repr.dst_addr.is_link_local()) + && ip_repr.hop_limit == 255 + { + self.slaac.process_advertisement( + &ip_repr.src_addr, + router_lifetime, + prefix_info, + self.now, + ) + } + None + } + _ => None, + } + } + + pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>( + &self, + ipv6_repr: Ipv6Repr, + icmp_repr: Icmpv6Repr<'icmp>, + ) -> Option> { + let src_addr = ipv6_repr.dst_addr; + let dst_addr = ipv6_repr.src_addr; + + let src_addr = if src_addr.x_is_unicast() { + src_addr + } else { + self.get_source_address_ipv6(&dst_addr) + }; + + let ipv6_reply_repr = Ipv6Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Icmpv6, + payload_len: icmp_repr.buffer_len(), + hop_limit: 64, + }; + Some(Packet::new_ipv6( + ipv6_reply_repr, + IpPayload::Icmpv6(icmp_repr), + )) + } + + pub(super) fn mldv2_report_packet<'any>( + &self, + records: &'any [MldAddressRecordRepr<'any>], + ) -> Option> { + // Per [RFC 3810 § 5.2.13], source addresses must be link-local, falling + // back to the unspecified address if we haven't acquired one. + // [RFC 3810 § 5.2.13]: https://tools.ietf.org/html/rfc3810#section-5.2.13 + let src_addr = self + .link_local_ipv6_address() + .unwrap_or(Ipv6Address::UNSPECIFIED); + + // Per [RFC 3810 § 5.2.14], all MLDv2 reports are sent to ff02::16. + // [RFC 3810 § 5.2.14]: https://tools.ietf.org/html/rfc3810#section-5.2.14 + let dst_addr = IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS; + + // Create a dummy IPv6 extension header so we can calculate the total length of the packet. + // The actual extension header will be created later by Packet::emit_payload(). + let dummy_ext_hdr = Ipv6ExtHeaderRepr { + next_header: IpProtocol::Unknown(0), + length: 0, + data: &[], + }; + + let mut hbh_repr = Ipv6HopByHopRepr::mldv2_router_alert(); + hbh_repr.push_padn_option(0); + + let mld_repr = MldRepr::ReportRecordReprs(records); + let records_len = records + .iter() + .map(MldAddressRecordRepr::buffer_len) + .sum::(); + + // All MLDv2 messages must be sent with an IPv6 Hop limit of 1. + Some(Packet::new_ipv6( + Ipv6Repr { + src_addr, + dst_addr, + next_header: IpProtocol::HopByHop, + payload_len: dummy_ext_hdr.header_len() + + hbh_repr.buffer_len() + + mld_repr.buffer_len() + + records_len, + hop_limit: 1, + }, + IpPayload::HopByHopIcmpv6(hbh_repr, Icmpv6Repr::Mld(mld_repr)), + )) + } +} + +impl Interface { + /// Synchronize the slaac address and router state with the interface state. + #[cfg(feature = "proto-ipv6-slaac")] + pub(super) fn sync_slaac_state(&mut self, timestamp: Instant) { + let required_addresses: Vec<_, IFACE_MAX_PREFIX_COUNT> = self + .inner + .slaac + .prefix() + .iter() + .filter_map(|(prefix, prefixinfo)| { + if prefixinfo.is_valid(timestamp) { + Ipv6Cidr::from_link_prefix(prefix, self.inner.hardware_addr()) + } else { + None + } + }) + .collect(); + let removed_addresses: Vec<_, IFACE_MAX_PREFIX_COUNT> = self + .inner + .slaac + .prefix() + .iter() + .filter_map(|(prefix, prefixinfo)| { + if !prefixinfo.is_valid(timestamp) { + Ipv6Cidr::from_link_prefix(prefix, self.inner.hardware_addr()) + } else { + None + } + }) + .collect(); + + self.update_ip_addrs(|addresses| { + for address in required_addresses { + if !addresses.contains(&IpCidr::Ipv6(address)) { + let _ = addresses.push(IpCidr::Ipv6(address)); + } + } + addresses.retain(|address| { + if let IpCidr::Ipv6(address) = address { + !removed_addresses.contains(address) + } else { + true + } + }); + }); + + { + let required_routes = self + .inner + .slaac + .routes() + .into_iter() + .filter(|required| required.is_valid(timestamp)); + + let removed_routes = self + .inner + .slaac + .routes() + .into_iter() + .filter(|r| !r.is_valid(timestamp)); + + self.inner.routes.update(|routes| { + routes.retain(|r| match (&r.cidr, &r.via_router) { + (IpCidr::Ipv6(cidr), IpAddress::Ipv6(via_router)) => !removed_routes + .clone() + .any(|f| f.same_route(cidr, via_router)), + _ => true, + }); + + for route in required_routes { + if routes.iter().all(|r| match (&r.cidr, &r.via_router) { + (IpCidr::Ipv6(cidr), IpAddress::Ipv6(via_router)) => { + !route.same_route(cidr, via_router) + } + _ => false, + }) { + let _ = routes.push(Route { + cidr: route.cidr.into(), + via_router: route.via_router.into(), + preferred_until: None, + expires_at: None, + }); + } + } + }); + } + self.inner.slaac_updated = timestamp; + self.inner.slaac.update_slaac_state(timestamp); + } + + /// Retrieve the timestamp at which the slaac state was last updated. + #[cfg(feature = "proto-ipv6-slaac")] + pub fn slaac_updated_at(&self) -> Instant { + self.inner.slaac_updated + } + + /// Emit a router solicitation when required by the interface's slaac state machine. + #[cfg(feature = "proto-ipv6-slaac")] + pub(super) fn ndisc_rs_egress(&mut self, device: &mut (impl Device + ?Sized)) { + if !self.inner.slaac.rs_required(self.inner.now) { + return; + } + let rs_repr = Icmpv6Repr::Ndisc(NdiscRepr::RouterSolicit { + lladdr: Some(self.hardware_addr().into()), + }); + let ipv6_repr = Ipv6Repr { + src_addr: self.inner.link_local_ipv6_address().unwrap(), + dst_addr: IPV6_LINK_LOCAL_ALL_ROUTERS, + next_header: IpProtocol::Icmpv6, + payload_len: rs_repr.buffer_len(), + hop_limit: 255, + }; + let packet = Packet::new_ipv6(ipv6_repr, IpPayload::Icmpv6(rs_repr)); + let Some(tx_token) = device.transmit(self.inner.now) else { + return; + }; + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip( + tx_token, + PacketMeta::default(), + packet, + &mut self.fragmenter, + ) + .unwrap(); + self.inner.slaac.rs_sent(self.inner.now); + } +} diff --git a/vendor/smoltcp/src/iface/interface/mod.rs b/vendor/smoltcp/src/iface/interface/mod.rs new file mode 100644 index 00000000..93a9b14e --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/mod.rs @@ -0,0 +1,1414 @@ +// Heads up! Before working on this file you should read the parts +// of RFC 1122 that discuss Ethernet, ARP and IP for any IPv4 work +// and RFCs 8200 and 4861 for any IPv6 and NDISC work. + +#[cfg(test)] +mod tests; + +#[cfg(feature = "medium-ethernet")] +mod ethernet; +#[cfg(feature = "medium-ieee802154")] +mod ieee802154; + +#[cfg(feature = "proto-ipv4")] +mod ipv4; +#[cfg(feature = "proto-ipv6")] +mod ipv6; +#[cfg(feature = "proto-sixlowpan")] +mod sixlowpan; + +#[cfg(feature = "multicast")] +pub(crate) mod multicast; +#[cfg(feature = "socket-tcp")] +mod tcp; +#[cfg(any(feature = "socket-udp", feature = "socket-dns"))] +mod udp; + +use super::packet::*; + +use core::result::Result; +use heapless::Vec; + +#[cfg(feature = "_proto-fragmentation")] +use super::fragmentation::FragKey; +#[cfg(any(feature = "proto-ipv4", feature = "proto-sixlowpan"))] +use super::fragmentation::PacketAssemblerSet; +use super::fragmentation::{Fragmenter, FragmentsBuffer}; + +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +use super::neighbor::{Answer as NeighborAnswer, Cache as NeighborCache}; +use super::socket_set::SocketSet; +use crate::config::{ + IFACE_MAX_ADDR_COUNT, IFACE_MAX_PREFIX_COUNT, IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT, +}; +use crate::iface::Routes; +#[cfg(feature = "proto-ipv6-slaac")] +use crate::iface::Slaac; +use crate::phy::PacketMeta; +use crate::phy::{ChecksumCapabilities, Device, DeviceCapabilities, Medium, RxToken, TxToken}; +use crate::rand::Rand; +use crate::socket::*; +use crate::time::{Duration, Instant}; + +use crate::wire::*; + +macro_rules! check { + ($e:expr) => { + match $e { + Ok(x) => x, + Err(_) => { + // concat!/stringify! doesn't work with defmt macros + #[cfg(not(feature = "defmt"))] + net_trace!(concat!("iface: malformed ", stringify!($e))); + #[cfg(feature = "defmt")] + net_trace!("iface: malformed"); + return Default::default(); + } + } + }; +} +use check; + +/// Result returned by [`Interface::poll`]. +/// +/// This contains information on whether socket states might have changed. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PollResult { + /// Socket state is guaranteed to not have changed. + None, + /// You should check the state of sockets again for received data or completion of operations. + SocketStateChanged, +} + +/// Result returned by [`Interface::poll_ingress_single`]. +/// +/// This contains information on whether a packet was processed or not, +/// and whether it might've affected socket states. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PollIngressSingleResult { + /// No packet was processed. You don't need to call [`Interface::poll_ingress_single`] + /// again, until more packets arrive. + /// + /// Socket state is guaranteed to not have changed. + None, + /// A packet was processed. + /// + /// There may be more packets in the device's RX queue, so you should call [`Interface::poll_ingress_single`] again. + /// + /// Socket state is guaranteed to not have changed. + PacketProcessed, + /// A packet was processed, which might have caused socket state to change. + /// + /// There may be more packets in the device's RX queue, so you should call [`Interface::poll_ingress_single`] again. + /// + /// You should check the state of sockets again for received data or completion of operations. + SocketStateChanged, +} + +/// A network interface. +/// +/// The network interface logically owns a number of other data structures; to avoid +/// a dependency on heap allocation, it instead owns a `BorrowMut<[T]>`, which can be +/// a `&mut [T]`, or `Vec` if a heap is available. +pub struct Interface { + pub(crate) inner: InterfaceInner, + fragments: FragmentsBuffer, + fragmenter: Fragmenter, +} + +/// The device independent part of an Ethernet network interface. +/// +/// Separating the device from the data required for processing and dispatching makes +/// it possible to borrow them independently. For example, the tx and rx tokens borrow +/// the `device` mutably until they're used, which makes it impossible to call other +/// methods on the `Interface` in this time (since its `device` field is borrowed +/// exclusively). However, it is still possible to call methods on its `inner` field. +pub struct InterfaceInner { + caps: DeviceCapabilities, + now: Instant, + rand: Rand, + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + neighbor_cache: NeighborCache, + hardware_addr: HardwareAddress, + #[cfg(feature = "medium-ieee802154")] + sequence_no: u8, + #[cfg(feature = "medium-ieee802154")] + pan_id: Option, + #[cfg(feature = "proto-ipv4-fragmentation")] + ipv4_id: u16, + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_address_context: + Vec, + #[cfg(feature = "proto-sixlowpan-fragmentation")] + tag: u16, + ip_addrs: Vec, + any_ip: bool, + #[cfg(feature = "proto-ipv6-slaac")] + slaac_enabled: bool, + #[cfg(feature = "proto-ipv6-slaac")] + slaac: Slaac, + #[cfg(feature = "proto-ipv6-slaac")] + slaac_updated: Instant, + routes: Routes, + #[cfg(feature = "multicast")] + multicast: multicast::State, +} + +/// Configuration structure used for creating a network interface. +#[non_exhaustive] +pub struct Config { + /// Random seed. + /// + /// It is strongly recommended that the random seed is different on each boot, + /// to avoid problems with TCP port/sequence collisions. + /// + /// The seed doesn't have to be cryptographically secure. + pub random_seed: u64, + + /// Set the Hardware address the interface will use. + /// + /// # Panics + /// Creating the interface panics if the address is not unicast. + pub hardware_addr: HardwareAddress, + + /// Set the IEEE802.15.4 PAN ID the interface will use. + /// + /// **NOTE**: we use the same PAN ID for destination and source. + #[cfg(feature = "medium-ieee802154")] + pub pan_id: Option, + + /// Enable stateless address autoconfiguration on the interface. + #[cfg(feature = "proto-ipv6")] + pub slaac: bool, +} + +impl Config { + pub fn new(hardware_addr: HardwareAddress) -> Self { + Config { + random_seed: 0, + hardware_addr, + #[cfg(feature = "medium-ieee802154")] + pan_id: None, + #[cfg(feature = "proto-ipv6")] + slaac: false, + } + } +} + +impl Interface { + /// Create a network interface using the previously provided configuration. + /// + /// # Panics + /// This function panics if the [`Config::hardware_address`] does not match + /// the medium of the device. + pub fn new(config: Config, device: &mut (impl Device + ?Sized), now: Instant) -> Self { + let caps = device.capabilities(); + assert_eq!( + config.hardware_addr.medium(), + caps.medium, + "The hardware address does not match the medium of the interface." + ); + + let mut rand = Rand::new(config.random_seed); + + #[cfg(feature = "medium-ieee802154")] + let mut sequence_no; + #[cfg(feature = "medium-ieee802154")] + loop { + sequence_no = (rand.rand_u32() & 0xff) as u8; + if sequence_no != 0 { + break; + } + } + + #[cfg(feature = "proto-sixlowpan")] + let mut tag; + + #[cfg(feature = "proto-sixlowpan")] + loop { + tag = rand.rand_u16(); + if tag != 0 { + break; + } + } + + #[cfg(feature = "proto-ipv4")] + let mut ipv4_id; + + #[cfg(feature = "proto-ipv4")] + loop { + ipv4_id = rand.rand_u16(); + if ipv4_id != 0 { + break; + } + } + + Interface { + fragments: FragmentsBuffer { + #[cfg(feature = "proto-sixlowpan")] + decompress_buf: [0u8; sixlowpan::MAX_DECOMPRESSED_LEN], + + #[cfg(feature = "_proto-fragmentation")] + assembler: PacketAssemblerSet::new(), + #[cfg(feature = "_proto-fragmentation")] + reassembly_timeout: Duration::from_secs(60), + }, + fragmenter: Fragmenter::new(), + inner: InterfaceInner { + now, + caps, + hardware_addr: config.hardware_addr, + ip_addrs: Vec::new(), + any_ip: false, + routes: Routes::new(), + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + neighbor_cache: NeighborCache::new(), + #[cfg(feature = "multicast")] + multicast: multicast::State::new(), + #[cfg(feature = "medium-ieee802154")] + sequence_no, + #[cfg(feature = "medium-ieee802154")] + pan_id: config.pan_id, + #[cfg(feature = "proto-sixlowpan-fragmentation")] + tag, + #[cfg(feature = "proto-ipv4-fragmentation")] + ipv4_id, + #[cfg(feature = "proto-sixlowpan")] + sixlowpan_address_context: Vec::new(), + #[cfg(feature = "proto-ipv6-slaac")] + slaac_enabled: config.slaac, + #[cfg(feature = "proto-ipv6-slaac")] + slaac: Slaac::new(), + #[cfg(feature = "proto-ipv6-slaac")] + slaac_updated: Instant::from_millis(0), + rand, + }, + } + } + + /// Get the socket context. + /// + /// The context is needed for some socket methods. + pub fn context(&mut self) -> &mut InterfaceInner { + &mut self.inner + } + + /// Get the HardwareAddress address of the interface. + /// + /// # Panics + /// This function panics if the medium is not Ethernet or Ieee802154. + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + pub fn hardware_addr(&self) -> HardwareAddress { + #[cfg(all(feature = "medium-ethernet", not(feature = "medium-ieee802154")))] + assert!(self.inner.caps.medium == Medium::Ethernet); + #[cfg(all(feature = "medium-ieee802154", not(feature = "medium-ethernet")))] + assert!(self.inner.caps.medium == Medium::Ieee802154); + + #[cfg(all(feature = "medium-ieee802154", feature = "medium-ethernet"))] + assert!( + self.inner.caps.medium == Medium::Ethernet + || self.inner.caps.medium == Medium::Ieee802154 + ); + + self.inner.hardware_addr + } + + /// Set the HardwareAddress address of the interface. + /// + /// # Panics + /// This function panics if the address is not unicast, and if the medium is not Ethernet or + /// Ieee802154. + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + pub fn set_hardware_addr(&mut self, addr: HardwareAddress) { + #[cfg(all(feature = "medium-ethernet", not(feature = "medium-ieee802154")))] + assert!(self.inner.caps.medium == Medium::Ethernet); + #[cfg(all(feature = "medium-ieee802154", not(feature = "medium-ethernet")))] + assert!(self.inner.caps.medium == Medium::Ieee802154); + + #[cfg(all(feature = "medium-ieee802154", feature = "medium-ethernet"))] + assert!( + self.inner.caps.medium == Medium::Ethernet + || self.inner.caps.medium == Medium::Ieee802154 + ); + + InterfaceInner::check_hardware_addr(&addr); + self.inner.hardware_addr = addr; + } + + /// Get the IP addresses of the interface. + pub fn ip_addrs(&self) -> &[IpCidr] { + self.inner.ip_addrs.as_ref() + } + + /// Get the first IPv4 address if present. + #[cfg(feature = "proto-ipv4")] + pub fn ipv4_addr(&self) -> Option { + self.inner.ipv4_addr() + } + + /// Get the first IPv6 address if present. + #[cfg(feature = "proto-ipv6")] + pub fn ipv6_addr(&self) -> Option { + self.inner.ipv6_addr() + } + + /// Get an address from the interface that could be used as source address. + /// For IPv4, this function tries to find a registered IPv4 address in the same + /// subnet as the destination, falling back to the first IPv4 address if none is + /// found. For IPv6, the selection is based on RFC6724. + pub fn get_source_address(&self, dst_addr: &IpAddress) -> Option { + self.inner.get_source_address(dst_addr) + } + + /// Get an IPv4 source address based on a destination address. This function tries + /// to find the first IPv4 address from the interface that is in the same subnet as + /// the destination address. If no such address is found, the first IPv4 address + /// from the interface is returned. + #[cfg(feature = "proto-ipv4")] + pub fn get_source_address_ipv4(&self, dst_addr: &Ipv4Address) -> Option { + self.inner.get_source_address_ipv4(dst_addr) + } + + /// Get an address from the interface that could be used as source address. The selection is + /// based on RFC6724. + #[cfg(feature = "proto-ipv6")] + pub fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address { + self.inner.get_source_address_ipv6(dst_addr) + } + + /// Update the IP addresses of the interface. + /// + /// # Panics + /// This function panics if any of the addresses are not unicast. + pub fn update_ip_addrs)>(&mut self, f: F) { + f(&mut self.inner.ip_addrs); + InterfaceInner::flush_neighbor_cache(&mut self.inner); + InterfaceInner::check_ip_addrs(&self.inner.ip_addrs); + + #[cfg(all( + feature = "proto-ipv6", + feature = "multicast", + feature = "medium-ethernet" + ))] + if self.inner.caps.medium == Medium::Ethernet { + self.update_solicited_node_groups(); + } + } + + /// Check whether the interface has the given IP address assigned. + pub fn has_ip_addr>(&self, addr: T) -> bool { + self.inner.has_ip_addr(addr) + } + + pub fn routes(&self) -> &Routes { + &self.inner.routes + } + + pub fn routes_mut(&mut self) -> &mut Routes { + &mut self.inner.routes + } + + /// Enable or disable the AnyIP capability. + /// + /// AnyIP allowins packets to be received + /// locally on IP addresses other than the interface's configured [ip_addrs]. + /// When AnyIP is enabled and a route prefix in [`routes`](Self::routes) specifies one of + /// the interface's [`ip_addrs`](Self::ip_addrs) as its gateway, the interface will accept + /// packets addressed to that prefix. + pub fn set_any_ip(&mut self, any_ip: bool) { + self.inner.any_ip = any_ip; + } + + /// Get whether AnyIP is enabled. + /// + /// See [`set_any_ip`](Self::set_any_ip) for details on AnyIP + pub fn any_ip(&self) -> bool { + self.inner.any_ip + } + + /// Get the packet reassembly timeout. + #[cfg(feature = "_proto-fragmentation")] + pub fn reassembly_timeout(&self) -> Duration { + self.fragments.reassembly_timeout + } + + /// Set the packet reassembly timeout. + #[cfg(feature = "_proto-fragmentation")] + pub fn set_reassembly_timeout(&mut self, timeout: Duration) { + if timeout > Duration::from_secs(60) { + net_debug!( + "RFC 4944 specifies that the reassembly timeout MUST be set to a maximum of 60 seconds" + ); + } + self.fragments.reassembly_timeout = timeout; + } + + /// Transmit packets queued in the sockets, and receive packets queued + /// in the device. + /// + /// This function returns a value indicating whether the state of any socket + /// might have changed. + /// + /// ## DoS warning + /// + /// This function processes all packets in the device's queue. This can + /// be an unbounded amount of work if packets arrive faster than they're + /// processed. + /// + /// If this is a concern for your application (i.e. your environment doesn't + /// have preemptive scheduling, or `poll()` is called from a main loop where + /// other important things are processed), you may use the lower-level methods + /// [`poll_egress()`](Self::poll_egress), [`poll_maintenance()`](Self::poll_maintenance) + /// and [`poll_ingress_single()`](Self::poll_ingress_single). + /// This allows you to insert yields or process other events between processing + /// individual ingress packets. + pub fn poll( + &mut self, + timestamp: Instant, + device: &mut (impl Device + ?Sized), + sockets: &mut SocketSet<'_>, + ) -> PollResult { + self.inner.now = timestamp; + + let mut res = PollResult::None; + + self.poll_maintenance(timestamp); + + // Process ingress while there's packets available. + loop { + match self.socket_ingress(device, sockets) { + PollIngressSingleResult::None => break, + PollIngressSingleResult::PacketProcessed => {} + PollIngressSingleResult::SocketStateChanged => res = PollResult::SocketStateChanged, + } + } + + // Process egress. + loop { + match self.poll_egress(timestamp, device, sockets) { + PollResult::None => break, + PollResult::SocketStateChanged => res = PollResult::SocketStateChanged, + } + } + + res + } + + /// Transmit packets queued in the sockets. + /// + /// This function returns a value indicating whether the state of any socket + /// might have changed. + /// + /// This is guaranteed to always perform a bounded amount of work. + pub fn poll_egress( + &mut self, + timestamp: Instant, + device: &mut (impl Device + ?Sized), + sockets: &mut SocketSet<'_>, + ) -> PollResult { + self.inner.now = timestamp; + + match self.inner.caps.medium { + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => { + #[cfg(feature = "proto-sixlowpan-fragmentation")] + self.sixlowpan_egress(device); + } + #[cfg(any(feature = "medium-ethernet", feature = "medium-ip"))] + _ => { + #[cfg(feature = "proto-ipv4-fragmentation")] + self.ipv4_egress(device); + } + } + + #[cfg(feature = "proto-ipv6-slaac")] + if self.inner.slaac_enabled { + self.ndisc_rs_egress(device); + } + + #[cfg(feature = "multicast")] + self.multicast_egress(device); + + self.socket_egress(device, sockets) + } + + /// Process one incoming packet queued in the device. + /// + /// Returns a value indicating: + /// - whether a packet was processed, in which case you have to call this method again in case there's more packets queued. + /// - whether the state of any socket might have changed. + /// + /// Since it processes at most one packet, this is guaranteed to always perform a bounded amount of work. + pub fn poll_ingress_single( + &mut self, + timestamp: Instant, + device: &mut (impl Device + ?Sized), + sockets: &mut SocketSet<'_>, + ) -> PollIngressSingleResult { + self.inner.now = timestamp; + + #[cfg(feature = "_proto-fragmentation")] + self.fragments.assembler.remove_expired(timestamp); + + self.socket_ingress(device, sockets) + } + + /// Maintain stateful processing on the device. + /// + /// This is guaranteed to always perform a bounded amount of work. + pub fn poll_maintenance(&mut self, timestamp: Instant) { + self.inner.now = timestamp; + + #[cfg(feature = "_proto-fragmentation")] + self.fragments.assembler.remove_expired(timestamp); + + #[cfg(feature = "proto-ipv6-slaac")] + if self.inner.slaac.sync_required(timestamp) { + self.sync_slaac_state(timestamp) + } + } + + /// Return a _soft deadline_ for calling [poll] the next time. + /// The [Instant] returned is the time at which you should call [poll] next. + /// It is harmless (but wastes energy) to call it before the [Instant], and + /// potentially harmful (impacting quality of service) to call it after the + /// [Instant] + /// + /// [poll]: #method.poll + /// [Instant]: struct.Instant.html + pub fn poll_at(&mut self, timestamp: Instant, sockets: &SocketSet<'_>) -> Option { + self.inner.now = timestamp; + + #[cfg(feature = "_proto-fragmentation")] + if !self.fragmenter.is_empty() { + return Some(Instant::from_millis(0)); + } + + #[allow(unused_mut)] + let mut res = sockets + .items() + .filter_map(|item| { + let socket_poll_at = item.socket.poll_at(&mut self.inner); + match item.meta.poll_at( + socket_poll_at, + |ip_addr| self.inner.has_neighbor(&ip_addr), + timestamp, + ) { + PollAt::Ingress => None, + PollAt::Time(instant) => Some(instant), + PollAt::Now => Some(Instant::from_millis(0)), + } + }) + .min(); + + #[cfg(feature = "proto-ipv6-slaac")] + if self.inner.slaac_enabled { + res = res.min(self.inner.slaac.poll_at(timestamp)); + } + + res + } + + /// Return an _advisory wait time_ for calling [poll] the next time. + /// The [Duration] returned is the time left to wait before calling [poll] next. + /// It is harmless (but wastes energy) to call it before the [Duration] has passed, + /// and potentially harmful (impacting quality of service) to call it after the + /// [Duration] has passed. + /// + /// [poll]: #method.poll + /// [Duration]: struct.Duration.html + pub fn poll_delay(&mut self, timestamp: Instant, sockets: &SocketSet<'_>) -> Option { + match self.poll_at(timestamp, sockets) { + Some(poll_at) if timestamp < poll_at => Some(poll_at - timestamp), + Some(_) => Some(Duration::from_millis(0)), + _ => None, + } + } + + fn socket_ingress( + &mut self, + device: &mut (impl Device + ?Sized), + sockets: &mut SocketSet<'_>, + ) -> PollIngressSingleResult { + let Some((rx_token, tx_token)) = device.receive(self.inner.now) else { + return PollIngressSingleResult::None; + }; + + let rx_meta = rx_token.meta(); + rx_token.consume(|frame| { + if frame.is_empty() { + return PollIngressSingleResult::PacketProcessed; + } + + match self.inner.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + if let Some(packet) = + self.inner + .process_ethernet(sockets, rx_meta, frame, &mut self.fragments) + { + if let Err(err) = + self.inner.dispatch(tx_token, packet, &mut self.fragmenter) + { + net_debug!("Failed to send response: {:?}", err); + } + } + } + #[cfg(feature = "medium-ip")] + Medium::Ip => { + if let Some(packet) = + self.inner + .process_ip(sockets, rx_meta, frame, &mut self.fragments) + { + if let Err(err) = self.inner.dispatch_ip( + tx_token, + PacketMeta::default(), + packet, + &mut self.fragmenter, + ) { + net_debug!("Failed to send response: {:?}", err); + } + } + } + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => { + if let Some(packet) = + self.inner + .process_ieee802154(sockets, rx_meta, frame, &mut self.fragments) + { + if let Err(err) = self.inner.dispatch_ip( + tx_token, + PacketMeta::default(), + packet, + &mut self.fragmenter, + ) { + net_debug!("Failed to send response: {:?}", err); + } + } + } + } + + // TODO: Propagate the PollIngressSingleResult from deeper. + // There's many received packets that we process but can't cause sockets + // to change state. For example IP fragments, multicast stuff, ICMP pings + // if they dont't match any raw socket... + // We should return `PacketProcessed` for these to save the user from + // doing useless socket polls. + PollIngressSingleResult::SocketStateChanged + }) + } + + fn socket_egress( + &mut self, + device: &mut (impl Device + ?Sized), + sockets: &mut SocketSet<'_>, + ) -> PollResult { + let _caps = device.capabilities(); + + enum EgressError { + Exhausted, + Dispatch, + } + + let mut result = PollResult::None; + for item in sockets.items_mut() { + if !item + .meta + .egress_permitted(self.inner.now, |ip_addr| self.inner.has_neighbor(&ip_addr)) + { + continue; + } + + let mut neighbor_addr = None; + let mut respond = |inner: &mut InterfaceInner, meta: PacketMeta, response: Packet| { + neighbor_addr = Some(response.ip_repr().dst_addr()); + let t = device.transmit(inner.now).ok_or_else(|| { + net_debug!("failed to transmit IP: device exhausted"); + EgressError::Exhausted + })?; + + inner + .dispatch_ip(t, meta, response, &mut self.fragmenter) + .map_err(|_| EgressError::Dispatch)?; + + result = PollResult::SocketStateChanged; + + Ok(()) + }; + + let result = match &mut item.socket { + #[cfg(feature = "socket-raw")] + Socket::Raw(socket) => socket.dispatch(&mut self.inner, |inner, (ip, raw)| { + respond( + inner, + PacketMeta::default(), + Packet::new(ip, IpPayload::Raw(raw)), + ) + }), + #[cfg(feature = "socket-icmp")] + Socket::Icmp(socket) => { + socket.dispatch(&mut self.inner, |inner, response| match response { + #[cfg(feature = "proto-ipv4")] + (IpRepr::Ipv4(ipv4_repr), IcmpRepr::Ipv4(icmpv4_repr)) => respond( + inner, + PacketMeta::default(), + Packet::new_ipv4(ipv4_repr, IpPayload::Icmpv4(icmpv4_repr)), + ), + #[cfg(feature = "proto-ipv6")] + (IpRepr::Ipv6(ipv6_repr), IcmpRepr::Ipv6(icmpv6_repr)) => respond( + inner, + PacketMeta::default(), + Packet::new_ipv6(ipv6_repr, IpPayload::Icmpv6(icmpv6_repr)), + ), + #[allow(unreachable_patterns)] + _ => unreachable!(), + }) + } + #[cfg(feature = "socket-udp")] + Socket::Udp(socket) => { + socket.dispatch(&mut self.inner, |inner, meta, (ip, udp, payload)| { + respond(inner, meta, Packet::new(ip, IpPayload::Udp(udp, payload))) + }) + } + #[cfg(feature = "socket-tcp")] + Socket::Tcp(socket) => socket.dispatch(&mut self.inner, |inner, (ip, tcp)| { + respond( + inner, + PacketMeta::default(), + Packet::new(ip, IpPayload::Tcp(tcp)), + ) + }), + #[cfg(feature = "socket-dhcpv4")] + Socket::Dhcpv4(socket) => { + socket.dispatch(&mut self.inner, |inner, (ip, udp, dhcp)| { + respond( + inner, + PacketMeta::default(), + Packet::new_ipv4(ip, IpPayload::Dhcpv4(udp, dhcp)), + ) + }) + } + #[cfg(feature = "socket-dns")] + Socket::Dns(socket) => socket.dispatch(&mut self.inner, |inner, (ip, udp, dns)| { + respond( + inner, + PacketMeta::default(), + Packet::new(ip, IpPayload::Udp(udp, dns)), + ) + }), + }; + + match result { + Err(EgressError::Exhausted) => break, // Device buffer full. + Err(EgressError::Dispatch) => { + // `NeighborCache` already takes care of rate limiting the neighbor discovery + // requests from the socket. However, without an additional rate limiting + // mechanism, we would spin on every socket that has yet to discover its + // neighbor. + item.meta.neighbor_missing( + self.inner.now, + neighbor_addr.expect("non-IP response packet"), + ); + } + Ok(()) => {} + } + } + result + } +} + +impl InterfaceInner { + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn now(&self) -> Instant { + self.now + } + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn hardware_addr(&self) -> HardwareAddress { + self.hardware_addr + } + + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn checksum_caps(&self) -> ChecksumCapabilities { + self.caps.checksum.clone() + } + + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn ip_mtu(&self) -> usize { + self.caps.ip_mtu() + } + + #[allow(unused)] // unused depending on which sockets are enabled, and in tests + pub(crate) fn rand(&mut self) -> &mut Rand { + &mut self.rand + } + + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn get_source_address(&self, dst_addr: &IpAddress) -> Option { + match dst_addr { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => self.get_source_address_ipv4(addr).map(|a| a.into()), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => Some(self.get_source_address_ipv6(addr).into()), + } + } + + #[cfg(test)] + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn set_now(&mut self, now: Instant) { + self.now = now + } + + #[cfg(test)] + #[allow(unused)] // unused depending on which sockets are enabled + pub(crate) fn set_ip_addrs(&mut self, addrs: Vec) { + self.ip_addrs = addrs; + } + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + fn check_hardware_addr(addr: &HardwareAddress) { + if !addr.is_unicast() { + panic!("Hardware address {addr} is not unicast") + } + } + + fn check_ip_addrs(addrs: &[IpCidr]) { + for cidr in addrs { + if !cidr.address().is_unicast() && !cidr.address().is_unspecified() { + panic!("IP address {} is not unicast", cidr.address()) + } + } + } + + /// Check whether the interface has the given IP address assigned. + /// + /// Always returns true if [`InterfaceInner::any_ip`]. + pub(crate) fn has_ip_addr>(&self, addr: T) -> bool { + // If any IP is set to true, we don't bother about checking the IP. + if self.any_ip { + return true; + } + + let addr = addr.into(); + self.ip_addrs.iter().any(|probe| probe.address() == addr) + } + + /// Check whether the interface listens to given destination multicast IP address. + fn has_multicast_group>(&self, addr: T) -> bool { + let addr = addr.into(); + + #[cfg(feature = "multicast")] + if self.multicast.has_multicast_group(addr) { + return true; + } + + match addr { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(key) => key == IPV4_MULTICAST_ALL_SYSTEMS, + #[cfg(feature = "proto-rpl")] + IpAddress::Ipv6(IPV6_LINK_LOCAL_ALL_RPL_NODES) => true, + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(key) => { + key == IPV6_LINK_LOCAL_ALL_NODES || self.has_solicited_node(key) + } + #[allow(unreachable_patterns)] + _ => false, + } + } + + #[cfg(feature = "medium-ip")] + fn process_ip<'frame>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + ip_payload: &'frame [u8], + frag: &'frame mut FragmentsBuffer, + ) -> Option> { + match IpVersion::of_packet(ip_payload) { + #[cfg(feature = "proto-ipv4")] + Ok(IpVersion::Ipv4) => { + let ipv4_packet = check!(Ipv4Packet::new_checked(ip_payload)); + self.process_ipv4(sockets, meta, HardwareAddress::Ip, &ipv4_packet, frag) + } + #[cfg(feature = "proto-ipv6")] + Ok(IpVersion::Ipv6) => { + let ipv6_packet = check!(Ipv6Packet::new_checked(ip_payload)); + self.process_ipv6(sockets, meta, HardwareAddress::Ip, &ipv6_packet) + } + // Drop all other traffic. + _ => None, + } + } + + #[cfg(feature = "socket-raw")] + fn raw_socket_filter( + &mut self, + sockets: &mut SocketSet, + ip_repr: &IpRepr, + ip_payload: &[u8], + ) -> bool { + let mut handled_by_raw_socket = false; + + // Pass every IP packet to all raw sockets we have registered. + for raw_socket in sockets + .items_mut() + .filter_map(|i| raw::Socket::downcast_mut(&mut i.socket)) + { + if raw_socket.accepts(ip_repr) { + raw_socket.process(self, ip_repr, ip_payload); + handled_by_raw_socket = true; + } + } + handled_by_raw_socket + } + + /// Checks if an address is broadcast, taking into account ipv4 subnet-local + /// broadcast addresses. + pub(crate) fn is_broadcast(&self, address: &IpAddress) -> bool { + match address { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(address) => self.is_broadcast_v4(*address), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(_) => false, + } + } + + #[cfg(feature = "medium-ethernet")] + fn dispatch( + &mut self, + tx_token: Tx, + packet: EthernetPacket, + frag: &mut Fragmenter, + ) -> Result<(), DispatchError> + where + Tx: TxToken, + { + match packet { + #[cfg(feature = "proto-ipv4")] + EthernetPacket::Arp(arp_repr) => { + let dst_hardware_addr = match arp_repr { + ArpRepr::EthernetIpv4 { + target_hardware_addr, + .. + } => target_hardware_addr, + }; + + self.dispatch_ethernet(tx_token, arp_repr.buffer_len(), |mut frame| { + frame.set_dst_addr(dst_hardware_addr); + frame.set_ethertype(EthernetProtocol::Arp); + + let mut packet = ArpPacket::new_unchecked(frame.payload_mut()); + arp_repr.emit(&mut packet); + }) + } + EthernetPacket::Ip(packet) => { + self.dispatch_ip(tx_token, PacketMeta::default(), packet, frag) + } + } + } + + fn in_same_network(&self, addr: &IpAddress) -> bool { + self.ip_addrs.iter().any(|cidr| cidr.contains_addr(addr)) + } + + fn route(&self, addr: &IpAddress, timestamp: Instant) -> Option { + // Send directly. + // note: no need to use `self.is_broadcast()` to check for subnet-local broadcast addrs + // here because `in_same_network` will already return true. + if self.in_same_network(addr) || addr.is_broadcast() { + return Some(*addr); + } + + // Route via a router. + self.routes.lookup(addr, timestamp) + } + + fn has_neighbor(&self, addr: &IpAddress) -> bool { + match self.route(addr, self.now) { + Some(_routed_addr) => match self.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => self.neighbor_cache.lookup(&_routed_addr, self.now).found(), + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => self.neighbor_cache.lookup(&_routed_addr, self.now).found(), + #[cfg(feature = "medium-ip")] + Medium::Ip => true, + }, + None => false, + } + } + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + fn lookup_hardware_addr( + &mut self, + tx_token: Tx, + dst_addr: &IpAddress, + fragmenter: &mut Fragmenter, + ) -> Result<(HardwareAddress, Tx), DispatchError> + where + Tx: TxToken, + { + if self.is_broadcast(dst_addr) { + let hardware_addr = match self.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress::BROADCAST), + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => HardwareAddress::Ieee802154(Ieee802154Address::BROADCAST), + #[cfg(feature = "medium-ip")] + Medium::Ip => unreachable!(), + }; + + return Ok((hardware_addr, tx_token)); + } + + if dst_addr.is_multicast() { + let hardware_addr = match *dst_addr { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => match self.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let b = addr.octets(); + HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[ + 0x01, + 0x00, + 0x5e, + b[1] & 0x7F, + b[2], + b[3], + ])) + } + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => unreachable!(), + #[cfg(feature = "medium-ip")] + Medium::Ip => unreachable!(), + }, + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => match self.caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let b = addr.octets(); + HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[ + 0x33, 0x33, b[12], b[13], b[14], b[15], + ])) + } + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => { + // Not sure if this is correct + HardwareAddress::Ieee802154(Ieee802154Address::BROADCAST) + } + #[cfg(feature = "medium-ip")] + Medium::Ip => unreachable!(), + }, + }; + + return Ok((hardware_addr, tx_token)); + } + + let dst_addr = self + .route(dst_addr, self.now) + .ok_or(DispatchError::NoRoute)?; + + match self.neighbor_cache.lookup(&dst_addr, self.now) { + NeighborAnswer::Found(hardware_addr) => return Ok((hardware_addr, tx_token)), + NeighborAnswer::RateLimited => return Err(DispatchError::NeighborPending), + _ => (), // XXX + } + + match dst_addr { + #[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4"))] + IpAddress::Ipv4(dst_addr) if matches!(self.caps.medium, Medium::Ethernet) => { + net_debug!( + "address {} not in neighbor cache, sending ARP request", + dst_addr + ); + let src_hardware_addr = self.hardware_addr.ethernet_or_panic(); + + let arp_repr = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Request, + source_hardware_addr: src_hardware_addr, + source_protocol_addr: self + .get_source_address_ipv4(&dst_addr) + .ok_or(DispatchError::NoRoute)?, + target_hardware_addr: EthernetAddress::BROADCAST, + target_protocol_addr: dst_addr, + }; + + if let Err(e) = + self.dispatch_ethernet(tx_token, arp_repr.buffer_len(), |mut frame| { + frame.set_dst_addr(EthernetAddress::BROADCAST); + frame.set_ethertype(EthernetProtocol::Arp); + + arp_repr.emit(&mut ArpPacket::new_unchecked(frame.payload_mut())) + }) + { + net_debug!("Failed to dispatch ARP request: {:?}", e); + return Err(DispatchError::NeighborPending); + } + } + + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(dst_addr) => { + net_debug!( + "address {} not in neighbor cache, sending Neighbor Solicitation", + dst_addr + ); + + let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit { + target_addr: dst_addr, + lladdr: Some(self.hardware_addr.into()), + }); + + let packet = Packet::new_ipv6( + Ipv6Repr { + src_addr: self.get_source_address_ipv6(&dst_addr), + dst_addr: dst_addr.solicited_node(), + next_header: IpProtocol::Icmpv6, + payload_len: solicit.buffer_len(), + hop_limit: 0xff, + }, + IpPayload::Icmpv6(solicit), + ); + + if let Err(e) = + self.dispatch_ip(tx_token, PacketMeta::default(), packet, fragmenter) + { + net_debug!("Failed to dispatch NDISC solicit: {:?}", e); + return Err(DispatchError::NeighborPending); + } + } + + #[allow(unreachable_patterns)] + _ => (), + } + + // The request got dispatched, limit the rate on the cache. + self.neighbor_cache.limit_rate(self.now); + Err(DispatchError::NeighborPending) + } + + fn flush_neighbor_cache(&mut self) { + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + self.neighbor_cache.flush() + } + + fn dispatch_ip( + &mut self, + // NOTE(unused_mut): tx_token isn't always mutated, depending on + // the feature set that is used. + #[allow(unused_mut)] mut tx_token: Tx, + meta: PacketMeta, + packet: Packet, + frag: &mut Fragmenter, + ) -> Result<(), DispatchError> { + let mut ip_repr = packet.ip_repr(); + assert!(!ip_repr.dst_addr().is_unspecified()); + + // Dispatch IEEE802.15.4: + + #[cfg(feature = "medium-ieee802154")] + if matches!(self.caps.medium, Medium::Ieee802154) { + let (addr, tx_token) = + self.lookup_hardware_addr(tx_token, &ip_repr.dst_addr(), frag)?; + let addr = addr.ieee802154_or_panic(); + + self.dispatch_ieee802154(addr, tx_token, meta, packet, frag); + return Ok(()); + } + + // Dispatch IP/Ethernet: + + let caps = self.caps.clone(); + + #[cfg(feature = "proto-ipv4-fragmentation")] + let ipv4_id = self.next_ipv4_frag_ident(); + + // First we calculate the total length that we will have to emit. + let mut total_len = ip_repr.buffer_len(); + + // Add the size of the Ethernet header if the medium is Ethernet. + #[cfg(feature = "medium-ethernet")] + if matches!(self.caps.medium, Medium::Ethernet) { + total_len = EthernetFrame::<&[u8]>::buffer_len(total_len); + } + + // If the medium is Ethernet, then we need to retrieve the destination hardware address. + #[cfg(feature = "medium-ethernet")] + let (dst_hardware_addr, mut tx_token) = match self.caps.medium { + Medium::Ethernet => { + match self.lookup_hardware_addr(tx_token, &ip_repr.dst_addr(), frag)? { + (HardwareAddress::Ethernet(addr), tx_token) => (addr, tx_token), + (_, _) => unreachable!(), + } + } + _ => (EthernetAddress([0; 6]), tx_token), + }; + + // Emit function for the Ethernet header. + #[cfg(feature = "medium-ethernet")] + let emit_ethernet = |repr: &IpRepr, tx_buffer: &mut [u8]| { + let mut frame = EthernetFrame::new_unchecked(tx_buffer); + + let src_addr = self.hardware_addr.ethernet_or_panic(); + frame.set_src_addr(src_addr); + frame.set_dst_addr(dst_hardware_addr); + + match repr.version() { + #[cfg(feature = "proto-ipv4")] + IpVersion::Ipv4 => frame.set_ethertype(EthernetProtocol::Ipv4), + #[cfg(feature = "proto-ipv6")] + IpVersion::Ipv6 => frame.set_ethertype(EthernetProtocol::Ipv6), + } + }; + + // Emit function for the IP header and payload. + let emit_ip = |repr: &IpRepr, tx_buffer: &mut [u8]| { + repr.emit(&mut *tx_buffer, &self.caps.checksum); + + let payload = &mut tx_buffer[repr.header_len()..]; + packet.emit_payload(repr, payload, &caps) + }; + + let total_ip_len = ip_repr.buffer_len(); + + match &mut ip_repr { + #[cfg(feature = "proto-ipv4")] + IpRepr::Ipv4(repr) => { + // If we have an IPv4 packet, then we need to check if we need to fragment it. + if total_ip_len > self.caps.ip_mtu() { + #[cfg(feature = "proto-ipv4-fragmentation")] + { + net_debug!("start fragmentation"); + + // Calculate how much we will send now (including the Ethernet header). + + let ip_header_len = repr.buffer_len(); + let first_frag_data_len = + self.caps.max_ipv4_fragment_size(repr.buffer_len()); + let first_frag_ip_len = first_frag_data_len + ip_header_len; + let mut tx_len = first_frag_ip_len; + #[cfg(feature = "medium-ethernet")] + if matches!(caps.medium, Medium::Ethernet) { + tx_len += EthernetFrame::<&[u8]>::header_len(); + } + + if frag.buffer.len() < total_ip_len { + net_debug!( + "Fragmentation buffer is too small, at least {} needed. Dropping", + total_ip_len + ); + return Ok(()); + } + + #[cfg(feature = "medium-ethernet")] + { + frag.ipv4.dst_hardware_addr = dst_hardware_addr; + } + + // Save the total packet len (without the Ethernet header, but with the first + // IP header). + frag.packet_len = total_ip_len; + + // Save the IP header for other fragments. + frag.ipv4.repr = *repr; + + // Modify the IP header + repr.payload_len = first_frag_data_len; + + // Save the number of bytes we will send now. + frag.sent_bytes = first_frag_ip_len; + + // Emit the IP header to the buffer. + emit_ip(&ip_repr, &mut frag.buffer); + + let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut frag.buffer[..]); + frag.ipv4.ident = ipv4_id; + ipv4_packet.set_ident(ipv4_id); + ipv4_packet.set_more_frags(true); + ipv4_packet.set_dont_frag(false); + ipv4_packet.set_frag_offset(0); + + if caps.checksum.ipv4.tx() { + ipv4_packet.fill_checksum(); + } + + // Transmit the first packet. + tx_token.consume(tx_len, |mut tx_buffer| { + #[cfg(feature = "medium-ethernet")] + if matches!(self.caps.medium, Medium::Ethernet) { + emit_ethernet(&ip_repr, tx_buffer); + tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..]; + } + + // Change the offset for the next packet. + frag.ipv4.frag_offset = (first_frag_ip_len - ip_header_len) as u16; + + // Copy the IP header and the payload. + tx_buffer[..first_frag_ip_len] + .copy_from_slice(&frag.buffer[..first_frag_ip_len]); + }); + + Ok(()) + } + + #[cfg(not(feature = "proto-ipv4-fragmentation"))] + { + net_debug!( + "Enable the `proto-ipv4-fragmentation` feature for fragmentation support." + ); + Ok(()) + } + } else { + tx_token.set_meta(meta); + + // No fragmentation is required. + tx_token.consume(total_len, |mut tx_buffer| { + #[cfg(feature = "medium-ethernet")] + if matches!(self.caps.medium, Medium::Ethernet) { + emit_ethernet(&ip_repr, tx_buffer); + tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..]; + } + + emit_ip(&ip_repr, tx_buffer); + }); + + Ok(()) + } + } + // We don't support IPv6 fragmentation yet. + #[cfg(feature = "proto-ipv6")] + IpRepr::Ipv6(_) => { + // Check if we need to fragment it. + if total_ip_len > self.caps.ip_mtu() { + net_debug!("IPv6 fragmentation support is unimplemented. Dropping."); + Ok(()) + } else { + tx_token.consume(total_len, |mut tx_buffer| { + #[cfg(feature = "medium-ethernet")] + if matches!(self.caps.medium, Medium::Ethernet) { + emit_ethernet(&ip_repr, tx_buffer); + tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..]; + } + + emit_ip(&ip_repr, tx_buffer); + }); + Ok(()) + } + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum DispatchError { + /// No route to dispatch this packet. Retrying won't help unless + /// configuration is changed. + NoRoute, + /// We do have a route to dispatch this packet, but we haven't discovered + /// the neighbor for it yet. Discovery has been initiated, dispatch + /// should be retried later. + NeighborPending, +} diff --git a/vendor/smoltcp/src/iface/interface/multicast.rs b/vendor/smoltcp/src/iface/interface/multicast.rs new file mode 100644 index 00000000..8025b2d1 --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/multicast.rs @@ -0,0 +1,571 @@ +use core::result::Result; +use heapless::{LinearMap, Vec}; + +use super::{Interface, InterfaceInner}; +#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] +use super::{IpPayload, Packet, check}; +use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT}; +use crate::phy::{Device, PacketMeta}; +use crate::wire::*; + +/// Error type for `join_multicast_group`, `leave_multicast_group`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MulticastError { + /// The table of joined multicast groups is already full. + GroupTableFull, + /// Cannot join/leave the given multicast group. + Unaddressable, +} + +#[cfg(feature = "proto-ipv4")] +pub(crate) enum IgmpReportState { + Inactive, + ToGeneralQuery { + version: IgmpVersion, + timeout: crate::time::Instant, + interval: crate::time::Duration, + next_index: usize, + }, + ToSpecificQuery { + version: IgmpVersion, + timeout: crate::time::Instant, + group: Ipv4Address, + }, +} + +#[cfg(feature = "proto-ipv6")] +pub(crate) enum MldReportState { + Inactive, + ToGeneralQuery { + timeout: crate::time::Instant, + }, + ToSpecificQuery { + group: Ipv6Address, + timeout: crate::time::Instant, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum GroupState { + /// Joining group, we have to send the join packet. + Joining, + /// We've already sent the join packet, we have nothing to do. + Joined, + /// We want to leave the group, we have to send a leave packet. + Leaving, +} + +pub(crate) struct State { + groups: LinearMap, + /// When to report for (all or) the next multicast group membership via IGMP + #[cfg(feature = "proto-ipv4")] + igmp_report_state: IgmpReportState, + #[cfg(feature = "proto-ipv6")] + mld_report_state: MldReportState, +} + +impl State { + pub(crate) fn new() -> Self { + Self { + groups: LinearMap::new(), + #[cfg(feature = "proto-ipv4")] + igmp_report_state: IgmpReportState::Inactive, + #[cfg(feature = "proto-ipv6")] + mld_report_state: MldReportState::Inactive, + } + } + + pub(crate) fn has_multicast_group>(&self, addr: T) -> bool { + // Return false if we don't have the multicast group, + // or we're leaving it. + match self.groups.get(&addr.into()) { + None => false, + Some(GroupState::Joining) => true, + Some(GroupState::Joined) => true, + Some(GroupState::Leaving) => false, + } + } +} + +impl core::fmt::Display for MulticastError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + MulticastError::GroupTableFull => write!(f, "GroupTableFull"), + MulticastError::Unaddressable => write!(f, "Unaddressable"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for MulticastError {} + +impl Interface { + /// Add an address to a list of subscribed multicast IP addresses. + pub fn join_multicast_group>( + &mut self, + addr: T, + ) -> Result<(), MulticastError> { + let addr = addr.into(); + if !addr.is_multicast() { + return Err(MulticastError::Unaddressable); + } + + if let Some(state) = self.inner.multicast.groups.get_mut(&addr) { + *state = match state { + GroupState::Joining => GroupState::Joining, + GroupState::Joined => GroupState::Joined, + GroupState::Leaving => GroupState::Joined, + }; + } else { + self.inner + .multicast + .groups + .insert(addr, GroupState::Joining) + .map_err(|_| MulticastError::GroupTableFull)?; + } + Ok(()) + } + + /// Remove an address from the subscribed multicast IP addresses. + pub fn leave_multicast_group>( + &mut self, + addr: T, + ) -> Result<(), MulticastError> { + let addr = addr.into(); + if !addr.is_multicast() { + return Err(MulticastError::Unaddressable); + } + + if let Some(state) = self.inner.multicast.groups.get_mut(&addr) { + let delete; + (*state, delete) = match state { + GroupState::Joining => (GroupState::Joined, true), + GroupState::Joined => (GroupState::Leaving, false), + GroupState::Leaving => (GroupState::Leaving, false), + }; + if delete { + self.inner.multicast.groups.remove(&addr); + } + } + Ok(()) + } + + /// Check whether the interface listens to given destination multicast IP address. + pub fn has_multicast_group>(&self, addr: T) -> bool { + self.inner.has_multicast_group(addr) + } + + #[cfg(feature = "proto-ipv6")] + pub(super) fn update_solicited_node_groups(&mut self) { + // Remove old solicited-node multicast addresses + let removals: Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT> = self + .inner + .multicast + .groups + .keys() + .cloned() + .filter(|a| matches!(a, IpAddress::Ipv6(a) if a.is_solicited_node_multicast() && !self.inner.has_solicited_node(*a))) + .collect(); + for removal in removals { + let _ = self.leave_multicast_group(removal); + } + + let cidrs: Vec = Vec::from_slice(self.ip_addrs()).unwrap(); + for cidr in cidrs { + if let IpCidr::Ipv6(cidr) = cidr { + let _ = self.join_multicast_group(cidr.address().solicited_node()); + } + } + } + + /// Do multicast egress. + /// + /// - Send join/leave packets according to the multicast group state. + /// - Depending on `igmp_report_state` and the therein contained + /// timeouts, send IGMP membership reports. + pub(crate) fn multicast_egress(&mut self, device: &mut (impl Device + ?Sized)) { + // Process multicast joins. + while let Some((&addr, _)) = self + .inner + .multicast + .groups + .iter() + .find(|&(_, &state)| state == GroupState::Joining) + { + match addr { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => { + if let Some(pkt) = self.inner.igmp_report_packet(IgmpVersion::Version2, addr) { + let Some(tx_token) = device.transmit(self.inner.now) else { + break; + }; + + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + } + } + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => { + if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new( + MldRecordType::ChangeToInclude, + addr, + )]) { + let Some(tx_token) = device.transmit(self.inner.now) else { + break; + }; + + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + } + } + } + + // NOTE(unwrap): this is always replacing an existing entry, so it can't fail due to the map being full. + self.inner + .multicast + .groups + .insert(addr, GroupState::Joined) + .unwrap(); + } + + // Process multicast leaves. + while let Some((&addr, _)) = self + .inner + .multicast + .groups + .iter() + .find(|&(_, &state)| state == GroupState::Leaving) + { + match addr { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => { + if let Some(pkt) = self.inner.igmp_leave_packet(addr) { + let Some(tx_token) = device.transmit(self.inner.now) else { + break; + }; + + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + } + } + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => { + if let Some(pkt) = self.inner.mldv2_report_packet(&[MldAddressRecordRepr::new( + MldRecordType::ChangeToExclude, + addr, + )]) { + let Some(tx_token) = device.transmit(self.inner.now) else { + break; + }; + + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + } + } + } + + self.inner.multicast.groups.remove(&addr); + } + + #[cfg(feature = "proto-ipv4")] + match self.inner.multicast.igmp_report_state { + IgmpReportState::ToSpecificQuery { + version, + timeout, + group, + } if self.inner.now >= timeout => { + if let Some(pkt) = self.inner.igmp_report_packet(version, group) { + // Send initial membership report + if let Some(tx_token) = device.transmit(self.inner.now) { + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter) + .unwrap(); + self.inner.multicast.igmp_report_state = IgmpReportState::Inactive; + } + } + } + IgmpReportState::ToGeneralQuery { + version, + timeout, + interval, + next_index, + } if self.inner.now >= timeout => { + let addr = self + .inner + .multicast + .groups + .iter() + .filter_map(|(addr, _)| match addr { + IpAddress::Ipv4(addr) => Some(*addr), + #[allow(unreachable_patterns)] + _ => None, + }) + .nth(next_index); + + match addr { + Some(addr) => { + if let Some(pkt) = self.inner.igmp_report_packet(version, addr) { + // Send initial membership report + if let Some(tx_token) = device.transmit(self.inner.now) { + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip( + tx_token, + PacketMeta::default(), + pkt, + &mut self.fragmenter, + ) + .unwrap(); + + let next_timeout = (timeout + interval).max(self.inner.now); + self.inner.multicast.igmp_report_state = + IgmpReportState::ToGeneralQuery { + version, + timeout: next_timeout, + interval, + next_index: next_index + 1, + }; + } + } + } + None => { + self.inner.multicast.igmp_report_state = IgmpReportState::Inactive; + } + } + } + _ => {} + } + #[cfg(feature = "proto-ipv6")] + match self.inner.multicast.mld_report_state { + MldReportState::ToGeneralQuery { timeout } if self.inner.now >= timeout => { + let records = self + .inner + .multicast + .groups + .iter() + .filter_map(|(addr, _)| match addr { + IpAddress::Ipv6(addr) => Some(MldAddressRecordRepr::new( + MldRecordType::ModeIsExclude, + *addr, + )), + #[allow(unreachable_patterns)] + _ => None, + }) + .collect::>(); + if let Some(pkt) = self.inner.mldv2_report_packet(&records) { + if let Some(tx_token) = device.transmit(self.inner.now) { + self.inner + .dispatch_ip( + tx_token, + PacketMeta::default(), + pkt, + &mut self.fragmenter, + ) + .unwrap(); + } + } + self.inner.multicast.mld_report_state = MldReportState::Inactive; + } + MldReportState::ToSpecificQuery { group, timeout } if self.inner.now >= timeout => { + let record = MldAddressRecordRepr::new(MldRecordType::ModeIsExclude, group); + if let Some(pkt) = self.inner.mldv2_report_packet(&[record]) { + if let Some(tx_token) = device.transmit(self.inner.now) { + // NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery. + self.inner + .dispatch_ip( + tx_token, + PacketMeta::default(), + pkt, + &mut self.fragmenter, + ) + .unwrap(); + } + } + self.inner.multicast.mld_report_state = MldReportState::Inactive; + } + _ => {} + } + } +} + +impl InterfaceInner { + /// Host duties of the **IGMPv2** protocol. + /// + /// Sets up `igmp_report_state` for responding to IGMP general/specific membership queries. + /// Membership must not be reported immediately in order to avoid flooding the network + /// after a query is broadcasted by a router; this is not currently done. + #[cfg(feature = "proto-ipv4")] + pub(super) fn process_igmp<'frame>( + &mut self, + ipv4_repr: Ipv4Repr, + ip_payload: &'frame [u8], + ) -> Option> { + use crate::time::Duration; + + let igmp_packet = check!(IgmpPacket::new_checked(ip_payload)); + let igmp_repr = check!(IgmpRepr::parse(&igmp_packet)); + + // FIXME: report membership after a delay + match igmp_repr { + IgmpRepr::MembershipQuery { + group_addr, + version, + max_resp_time, + } => { + // General query + if group_addr.is_unspecified() && ipv4_repr.dst_addr == IPV4_MULTICAST_ALL_SYSTEMS { + let ipv4_multicast_group_count = self + .multicast + .groups + .keys() + .filter(|a| matches!(a, IpAddress::Ipv4(_))) + .count(); + + // Are we member in any groups? + if ipv4_multicast_group_count != 0 { + let interval = match version { + IgmpVersion::Version1 => Duration::from_millis(100), + IgmpVersion::Version2 => { + // No dependence on a random generator + // (see [#24](https://github.com/m-labs/smoltcp/issues/24)) + // but at least spread reports evenly across max_resp_time. + let intervals = ipv4_multicast_group_count as u32 + 1; + max_resp_time / intervals + } + }; + self.multicast.igmp_report_state = IgmpReportState::ToGeneralQuery { + version, + timeout: self.now + interval, + interval, + next_index: 0, + }; + } + } else { + // Group-specific query + if self.has_multicast_group(group_addr) && ipv4_repr.dst_addr == group_addr { + // Don't respond immediately + let timeout = max_resp_time / 4; + self.multicast.igmp_report_state = IgmpReportState::ToSpecificQuery { + version, + timeout: self.now + timeout, + group: group_addr, + }; + } + } + } + // Ignore membership reports + IgmpRepr::MembershipReport { .. } => (), + // Ignore hosts leaving groups + IgmpRepr::LeaveGroup { .. } => (), + } + + None + } + + #[cfg(feature = "proto-ipv4")] + fn igmp_report_packet<'any>( + &self, + version: IgmpVersion, + group_addr: Ipv4Address, + ) -> Option> { + let iface_addr = self.ipv4_addr()?; + let igmp_repr = IgmpRepr::MembershipReport { + group_addr, + version, + }; + let pkt = Packet::new_ipv4( + Ipv4Repr { + src_addr: iface_addr, + // Send to the group being reported + dst_addr: group_addr, + next_header: IpProtocol::Igmp, + payload_len: igmp_repr.buffer_len(), + hop_limit: 1, + // [#183](https://github.com/m-labs/smoltcp/issues/183). + }, + IpPayload::Igmp(igmp_repr), + ); + Some(pkt) + } + + #[cfg(feature = "proto-ipv4")] + fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option> { + self.ipv4_addr().map(|iface_addr| { + let igmp_repr = IgmpRepr::LeaveGroup { group_addr }; + Packet::new_ipv4( + Ipv4Repr { + src_addr: iface_addr, + dst_addr: IPV4_MULTICAST_ALL_ROUTERS, + next_header: IpProtocol::Igmp, + payload_len: igmp_repr.buffer_len(), + hop_limit: 1, + }, + IpPayload::Igmp(igmp_repr), + ) + }) + } + + /// Host duties of the **MLDv2** protocol. + /// + /// Sets up `mld_report_state` for responding to MLD general/specific membership queries. + /// Membership must not be reported immediately in order to avoid flooding the network + /// after a query is broadcasted by a router; Currently the delay is fixed and not randomized. + #[cfg(feature = "proto-ipv6")] + pub(super) fn process_mldv2<'frame>( + &mut self, + ip_repr: Ipv6Repr, + repr: MldRepr<'frame>, + ) -> Option> { + match repr { + MldRepr::Query { + mcast_addr, + max_resp_code, + .. + } => { + // Do not respond immediately to the query, but wait a random time + let delay = if max_resp_code > 0 { + (self.rand.rand_u16() % max_resp_code).into() + } else { + 0 + }; + let delay = crate::time::Duration::from_millis(delay); + // General query + if mcast_addr.is_unspecified() + && (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES + || self.has_ip_addr(ip_repr.dst_addr)) + { + let ipv6_multicast_group_count = self + .multicast + .groups + .keys() + .filter(|a| matches!(a, IpAddress::Ipv6(_))) + .count(); + if ipv6_multicast_group_count != 0 { + self.multicast.mld_report_state = MldReportState::ToGeneralQuery { + timeout: self.now + delay, + }; + } + } + if self.has_multicast_group(mcast_addr) && ip_repr.dst_addr == mcast_addr { + self.multicast.mld_report_state = MldReportState::ToSpecificQuery { + group: mcast_addr, + timeout: self.now + delay, + }; + } + None + } + MldRepr::Report { .. } => None, + MldRepr::ReportRecordReprs { .. } => None, + } + } +} diff --git a/vendor/smoltcp/src/iface/interface/sixlowpan.rs b/vendor/smoltcp/src/iface/interface/sixlowpan.rs new file mode 100644 index 00000000..4106d357 --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/sixlowpan.rs @@ -0,0 +1,976 @@ +use super::*; +use crate::wire::Result; + +// Max len of non-fragmented packets after decompression (including ipv6 header and payload) +// TODO: lower. Should be (6lowpan mtu) - (min 6lowpan header size) + (max ipv6 header size) +pub(crate) const MAX_DECOMPRESSED_LEN: usize = 1500; + +impl Interface { + /// Process fragments that still need to be sent for 6LoWPAN packets. + #[cfg(feature = "proto-sixlowpan-fragmentation")] + pub(super) fn sixlowpan_egress(&mut self, device: &mut (impl Device + ?Sized)) { + // Reset the buffer when we transmitted everything. + if self.fragmenter.finished() { + self.fragmenter.reset(); + } + + if self.fragmenter.is_empty() { + return; + } + + let pkt = &self.fragmenter; + if pkt.packet_len > pkt.sent_bytes { + if let Some(tx_token) = device.transmit(self.inner.now) { + self.inner + .dispatch_ieee802154_frag(tx_token, &mut self.fragmenter); + } + } + } + + /// Get the 6LoWPAN address contexts. + pub fn sixlowpan_address_context(&self) -> &[SixlowpanAddressContext] { + &self.inner.sixlowpan_address_context[..] + } + + /// Get a mutable reference to the 6LoWPAN address contexts. + pub fn sixlowpan_address_context_mut( + &mut self, + ) -> &mut Vec { + &mut self.inner.sixlowpan_address_context + } +} + +impl InterfaceInner { + /// Get the next tag for a 6LoWPAN fragment. + #[cfg(feature = "proto-sixlowpan-fragmentation")] + fn get_sixlowpan_fragment_tag(&mut self) -> u16 { + let tag = self.tag; + self.tag = self.tag.wrapping_add(1); + tag + } + + pub(super) fn process_sixlowpan<'output, 'payload: 'output>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + ieee802154_repr: &Ieee802154Repr, + payload: &'payload [u8], + f: &'output mut FragmentsBuffer, + ) -> Option> { + let payload = match check!(SixlowpanPacket::dispatch(payload)) { + #[cfg(not(feature = "proto-sixlowpan-fragmentation"))] + SixlowpanPacket::FragmentHeader => { + net_debug!( + "Fragmentation is not supported, \ + use the `proto-sixlowpan-fragmentation` feature to add support." + ); + return None; + } + #[cfg(feature = "proto-sixlowpan-fragmentation")] + SixlowpanPacket::FragmentHeader => { + self.process_sixlowpan_fragment(ieee802154_repr, payload, f)? + } + SixlowpanPacket::IphcHeader => { + match Self::sixlowpan_to_ipv6( + &self.sixlowpan_address_context, + ieee802154_repr, + payload, + None, + &mut f.decompress_buf, + ) { + Ok(len) => &f.decompress_buf[..len], + Err(e) => { + net_debug!("sixlowpan decompress failed: {:?}", e); + return None; + } + } + } + }; + + self.process_ipv6( + sockets, + meta, + match ieee802154_repr.src_addr { + Some(s) => HardwareAddress::Ieee802154(s), + None => HardwareAddress::Ieee802154(Ieee802154Address::Absent), + }, + &check!(Ipv6Packet::new_checked(payload)), + ) + } + + #[cfg(feature = "proto-sixlowpan-fragmentation")] + fn process_sixlowpan_fragment<'output, 'payload: 'output>( + &mut self, + ieee802154_repr: &Ieee802154Repr, + payload: &'payload [u8], + f: &'output mut FragmentsBuffer, + ) -> Option<&'output [u8]> { + use crate::iface::fragmentation::{AssemblerError, AssemblerFullError}; + + // We have a fragment header, which means we cannot process the 6LoWPAN packet, + // unless we have a complete one after processing this fragment. + let frag = check!(SixlowpanFragPacket::new_checked(payload)); + + // From RFC 4944 § 5.3: "The value of datagram_size SHALL be 40 octets more than the value + // of Payload Length in the IPv6 header of the packet." + // We should check that this is true, otherwise `buffer.split_at_mut(40)` will panic, since + // we assume that the decompressed packet is at least 40 bytes. + if frag.datagram_size() < 40 { + net_debug!("6LoWPAN: fragment size too small"); + return None; + } + + // The key specifies to which 6LoWPAN fragment it belongs too. + // It is based on the link layer addresses, the tag and the size. + let key = FragKey::Sixlowpan(frag.get_key(ieee802154_repr)); + + // The offset of this fragment in increments of 8 octets. + let offset = frag.datagram_offset() as usize * 8; + + // We reserve a spot in the packet assembler set and add the required + // information to the packet assembler. + // This information is the total size of the packet when it is fully assmbled. + // We also pass the header size, since this is needed when other fragments + // (other than the first one) are added. + let frag_slot = match f.assembler.get(&key, self.now + f.reassembly_timeout) { + Ok(frag) => frag, + Err(AssemblerFullError) => { + net_debug!("No available packet assembler for fragmented packet"); + return None; + } + }; + + if frag.is_first_fragment() { + // The first fragment contains the total size of the IPv6 packet. + // However, we received a packet that is compressed following the 6LoWPAN + // standard. This means we need to convert the IPv6 packet size to a 6LoWPAN + // packet size. The packet size can be different because of first the + // compression of the IP header and when UDP is used (because the UDP header + // can also be compressed). Other headers are not compressed by 6LoWPAN. + + // First segment tells us the total size. + let total_size = frag.datagram_size() as usize; + if frag_slot.set_total_size(total_size).is_err() { + net_debug!("No available packet assembler for fragmented packet"); + return None; + } + + // Decompress headers+payload into the assembler. + if let Err(e) = frag_slot.add_with(0, |buffer| { + Self::sixlowpan_to_ipv6( + &self.sixlowpan_address_context, + ieee802154_repr, + frag.payload(), + Some(total_size), + buffer, + ) + .map_err(|_| AssemblerError) + }) { + net_debug!("fragmentation error: {:?}", e); + return None; + } + } else { + // Add the fragment to the packet assembler. + if let Err(e) = frag_slot.add(frag.payload(), offset) { + net_debug!("fragmentation error: {:?}", e); + return None; + } + } + + match frag_slot.assemble() { + Some(payload) => { + net_trace!("6LoWPAN: fragmented packet now complete"); + Some(payload) + } + None => None, + } + } + + /// Decompress a 6LoWPAN packet into an IPv6 packet. + /// + /// The return value is the length of the decompressed packet, but not including the total + /// length of the payload of the UDP packet. This value is then used by the assembler to know + /// how far in the assembler buffer the packet is. + /// + /// **NOTE**: when decompressing a fragmented packet, the `total_len` parameter should be + /// passed. This is the total length of the IPv6 packet, including the IPv6 header. It is used + /// for calculating the length field in the UDP header. + fn sixlowpan_to_ipv6( + address_context: &[SixlowpanAddressContext], + ieee802154_repr: &Ieee802154Repr, + iphc_payload: &[u8], + total_len: Option, + buffer: &mut [u8], + ) -> Result { + let iphc = SixlowpanIphcPacket::new_checked(iphc_payload)?; + let iphc_repr = SixlowpanIphcRepr::parse( + &iphc, + ieee802154_repr.src_addr, + ieee802154_repr.dst_addr, + address_context, + )?; + + // The first thing we have to decompress is the IPv6 header. However, at this point we + // don't know the total size of the packet, neither the next header, since that can be a + // compressed header. However, we know that the IPv6 header is 40 bytes, so we can reserve + // this space in the buffer such that we can decompress the IPv6 header into it at a later + // point. + let (ipv6_buffer, mut buffer) = buffer.split_at_mut(40); + let mut ipv6_header = Ipv6Packet::new_unchecked(ipv6_buffer); + + // If the total length is given, we are dealing with a fragmented packet. The total + // length is then used to calculate the length field for the UDP header. If the total + // length is not given, we are not working with a fragmented packet, and we need to + // calculate the length of the payload ourselves. + let mut payload_len = 40; + let mut decompressed_len = 40; + + let mut next_header = Some(iphc_repr.next_header); + let mut data = iphc.payload(); + + while let Some(nh) = next_header { + match nh { + SixlowpanNextHeader::Compressed => match SixlowpanNhcPacket::dispatch(data)? { + SixlowpanNhcPacket::ExtHeader => { + (buffer, data) = decompress_ext_hdr( + data, + &mut next_header, + buffer, + &mut payload_len, + &mut decompressed_len, + )?; + } + SixlowpanNhcPacket::UdpHeader => { + decompress_udp( + data, + &iphc_repr, + buffer, + total_len, + &mut payload_len, + &mut decompressed_len, + )?; + + break; + } + }, + SixlowpanNextHeader::Uncompressed(proto) => { + // We have a 6LoWPAN uncompressed header. + match proto { + IpProtocol::Tcp | IpProtocol::Udp | IpProtocol::Icmpv6 => { + // There can be no protocol after this one, so we can just copy the + // rest of the data buffer. There is also no length field in the UDP + // header that we need to correct as this header was not changed by the + // 6LoWPAN compressor. + if data.len() > buffer.len() { + return Err(Error); + } + buffer[..data.len()].copy_from_slice(data); + payload_len += data.len(); + decompressed_len += data.len(); + break; + } + proto => { + net_debug!("Unsupported uncompressed next header: {:?}", proto); + return Err(Error); + } + } + } + } + } + + let ipv6_repr = Ipv6Repr { + src_addr: iphc_repr.src_addr, + dst_addr: iphc_repr.dst_addr, + next_header: decompress_next_header(iphc_repr.next_header, iphc.payload())?, + payload_len: total_len.unwrap_or(payload_len) - 40, + hop_limit: iphc_repr.hop_limit, + }; + ipv6_repr.emit(&mut ipv6_header); + + Ok(decompressed_len) + } + + pub(super) fn dispatch_sixlowpan( + &mut self, + mut tx_token: Tx, + meta: PacketMeta, + packet: Packet, + ieee_repr: Ieee802154Repr, + frag: &mut Fragmenter, + ) { + let packet = match packet { + #[cfg(feature = "proto-ipv4")] + Packet::Ipv4(_) => unreachable!(), + Packet::Ipv6(packet) => packet, + }; + + // First we calculate the size we are going to need. If the size is bigger than the MTU, + // then we use fragmentation. + let (total_size, compressed_size, uncompressed_size) = + Self::compressed_packet_size(&packet, &ieee_repr); + + let ieee_len = ieee_repr.buffer_len(); + + // TODO(thvdveld): use the MTU of the device. + if total_size + ieee_len > 125 { + #[cfg(feature = "proto-sixlowpan-fragmentation")] + { + // The packet does not fit in one Ieee802154 frame, so we need fragmentation. + // We do this by emitting everything in the `frag.buffer` from the interface. + // After emitting everything into that buffer, we send the first fragment heere. + // When `poll` is called again, we check if frag was fully sent, otherwise we + // call `dispatch_ieee802154_frag`, which will transmit the other fragments. + + // `dispatch_ieee802154_frag` requires some information about the total packet size, + // the link local source and destination address... + + let pkt = frag; + if pkt.buffer.len() < total_size { + net_debug!( + "dispatch_ieee802154: dropping, \ + fragmentation buffer is too small, at least {} needed", + total_size + ); + return; + } + + let payload_length = packet.header.payload_len; + + Self::ipv6_to_sixlowpan( + &self.checksum_caps(), + packet, + &ieee_repr, + &mut pkt.buffer[..], + ); + + pkt.sixlowpan.ll_dst_addr = ieee_repr.dst_addr.unwrap(); + pkt.sixlowpan.ll_src_addr = ieee_repr.src_addr.unwrap(); + pkt.packet_len = total_size; + + // The datagram size that we need to set in the first fragment header is equal to the + // IPv6 payload length + 40. + pkt.sixlowpan.datagram_size = (payload_length + 40) as u16; + + let tag = self.get_sixlowpan_fragment_tag(); + // We save the tag for the other fragments that will be created when calling `poll` + // multiple times. + pkt.sixlowpan.datagram_tag = tag; + + let frag1 = SixlowpanFragRepr::FirstFragment { + size: pkt.sixlowpan.datagram_size, + tag, + }; + let fragn = SixlowpanFragRepr::Fragment { + size: pkt.sixlowpan.datagram_size, + tag, + offset: 0, + }; + + // We calculate how much data we can send in the first fragment and the other + // fragments. The eventual IPv6 sizes of these fragments need to be a multiple of eight + // (except for the last fragment) since the offset field in the fragment is an offset + // in multiples of 8 octets. This is explained in [RFC 4944 § 5.3]. + // + // [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3 + + let header_diff = uncompressed_size - compressed_size; + let frag1_size = + (125 - ieee_len - frag1.buffer_len() + header_diff) / 8 * 8 - header_diff; + + pkt.sixlowpan.fragn_size = (125 - ieee_len - fragn.buffer_len()) / 8 * 8; + pkt.sent_bytes = frag1_size; + pkt.sixlowpan.datagram_offset = frag1_size + header_diff; + + tx_token.set_meta(meta); + tx_token.consume(ieee_len + frag1.buffer_len() + frag1_size, |mut tx_buf| { + // Add the IEEE header. + let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]); + ieee_repr.emit(&mut ieee_packet); + tx_buf = &mut tx_buf[ieee_len..]; + + // Add the first fragment header + let mut frag1_packet = SixlowpanFragPacket::new_unchecked(&mut tx_buf); + frag1.emit(&mut frag1_packet); + tx_buf = &mut tx_buf[frag1.buffer_len()..]; + + // Add the buffer part. + tx_buf[..frag1_size].copy_from_slice(&pkt.buffer[..frag1_size]); + }); + } + + #[cfg(not(feature = "proto-sixlowpan-fragmentation"))] + { + net_debug!( + "Enable the `proto-sixlowpan-fragmentation` feature for fragmentation support." + ); + return; + } + } else { + tx_token.set_meta(meta); + + // We don't need fragmentation, so we emit everything to the TX token. + tx_token.consume(total_size + ieee_len, |mut tx_buf| { + let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]); + ieee_repr.emit(&mut ieee_packet); + tx_buf = &mut tx_buf[ieee_len..]; + + Self::ipv6_to_sixlowpan(&self.checksum_caps(), packet, &ieee_repr, tx_buf); + }); + } + } + + fn ipv6_to_sixlowpan( + checksum_caps: &ChecksumCapabilities, + mut packet: PacketV6, + ieee_repr: &Ieee802154Repr, + mut buffer: &mut [u8], + ) { + let last_header = packet.payload.as_sixlowpan_next_header(); + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-hbh")] + let next_header = if packet.hop_by_hop.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + let iphc_repr = SixlowpanIphcRepr { + src_addr: packet.header.src_addr, + ll_src_addr: ieee_repr.src_addr, + dst_addr: packet.header.dst_addr, + ll_dst_addr: ieee_repr.dst_addr, + next_header, + hop_limit: packet.header.hop_limit, + ecn: None, + dscp: None, + flow_label: None, + }; + + iphc_repr.emit(&mut SixlowpanIphcPacket::new_unchecked( + &mut buffer[..iphc_repr.buffer_len()], + )); + buffer = &mut buffer[iphc_repr.buffer_len()..]; + + // Emit the Hop-by-Hop header + #[cfg(feature = "proto-ipv6-hbh")] + if let Some(hbh) = packet.hop_by_hop { + #[allow(unused)] + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + last_header + }; + + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::HopByHopHeader, + next_header, + length: hbh.options.iter().map(|o| o.buffer_len()).sum::() as u8, + }; + ext_hdr.emit(&mut SixlowpanExtHeaderPacket::new_unchecked( + &mut buffer[..ext_hdr.buffer_len()], + )); + buffer = &mut buffer[ext_hdr.buffer_len()..]; + + for opt in &hbh.options { + opt.emit(&mut Ipv6Option::new_unchecked( + &mut buffer[..opt.buffer_len()], + )); + + buffer = &mut buffer[opt.buffer_len()..]; + } + } + + // Emit the Routing header + #[cfg(feature = "proto-ipv6-routing")] + if let Some(routing) = &packet.routing { + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::RoutingHeader, + next_header, + length: routing.buffer_len() as u8, + }; + ext_hdr.emit(&mut SixlowpanExtHeaderPacket::new_unchecked( + &mut buffer[..ext_hdr.buffer_len()], + )); + buffer = &mut buffer[ext_hdr.buffer_len()..]; + + routing.emit(&mut Ipv6RoutingHeader::new_unchecked( + &mut buffer[..routing.buffer_len()], + )); + buffer = &mut buffer[routing.buffer_len()..]; + } + + match &mut packet.payload { + IpPayload::Icmpv6(icmp_repr) => { + icmp_repr.emit( + &packet.header.src_addr, + &packet.header.dst_addr, + &mut Icmpv6Packet::new_unchecked(&mut buffer[..icmp_repr.buffer_len()]), + checksum_caps, + ); + } + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpPayload::Udp(udp_repr, payload) => { + let udp_repr = SixlowpanUdpNhcRepr(*udp_repr); + udp_repr.emit( + &mut SixlowpanUdpNhcPacket::new_unchecked( + &mut buffer[..udp_repr.header_len() + payload.len()], + ), + &iphc_repr.src_addr, + &iphc_repr.dst_addr, + payload.len(), + |buf| buf.copy_from_slice(payload), + checksum_caps, + ); + } + #[cfg(feature = "socket-tcp")] + IpPayload::Tcp(tcp_repr) => { + tcp_repr.emit( + &mut TcpPacket::new_unchecked(&mut buffer[..tcp_repr.buffer_len()]), + &packet.header.src_addr.into(), + &packet.header.dst_addr.into(), + checksum_caps, + ); + } + #[cfg(feature = "socket-raw")] + IpPayload::Raw(_raw) => todo!(), + + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + + /// Calculates three sizes: + /// - total size: the size of a compressed IPv6 packet + /// - compressed header size: the size of the compressed headers + /// - uncompressed header size: the size of the headers that are not compressed + /// + /// They are returned as a tuple in the same order. + fn compressed_packet_size( + packet: &PacketV6, + ieee_repr: &Ieee802154Repr, + ) -> (usize, usize, usize) { + let last_header = packet.payload.as_sixlowpan_next_header(); + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-hbh")] + let next_header = if packet.hop_by_hop.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + next_header + }; + + let iphc = SixlowpanIphcRepr { + src_addr: packet.header.src_addr, + ll_src_addr: ieee_repr.src_addr, + dst_addr: packet.header.dst_addr, + ll_dst_addr: ieee_repr.dst_addr, + next_header, + hop_limit: packet.header.hop_limit, + ecn: None, + dscp: None, + flow_label: None, + }; + + let mut total_size = iphc.buffer_len(); + let mut compressed_hdr_size = iphc.buffer_len(); + let mut uncompressed_hdr_size = packet.header.buffer_len(); + + // Add the hop-by-hop to the sizes. + #[cfg(feature = "proto-ipv6-hbh")] + if let Some(hbh) = &packet.hop_by_hop { + #[allow(unused)] + let next_header = last_header; + + #[cfg(feature = "proto-ipv6-routing")] + let next_header = if packet.routing.is_some() { + SixlowpanNextHeader::Compressed + } else { + last_header + }; + + let options_size = hbh.options.iter().map(|o| o.buffer_len()).sum::(); + + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::HopByHopHeader, + next_header, + length: hbh.buffer_len() as u8 + options_size as u8, + }; + + total_size += ext_hdr.buffer_len() + options_size; + compressed_hdr_size += ext_hdr.buffer_len() + options_size; + uncompressed_hdr_size += hbh.buffer_len() + options_size; + } + + // Add the routing header to the sizes. + #[cfg(feature = "proto-ipv6-routing")] + if let Some(routing) = &packet.routing { + let ext_hdr = SixlowpanExtHeaderRepr { + ext_header_id: SixlowpanExtHeaderId::RoutingHeader, + next_header, + length: routing.buffer_len() as u8, + }; + total_size += ext_hdr.buffer_len() + routing.buffer_len(); + compressed_hdr_size += ext_hdr.buffer_len() + routing.buffer_len(); + uncompressed_hdr_size += routing.buffer_len(); + } + + match packet.payload { + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpPayload::Udp(udp_hdr, payload) => { + uncompressed_hdr_size += udp_hdr.header_len(); + + let udp_hdr = SixlowpanUdpNhcRepr(udp_hdr); + compressed_hdr_size += udp_hdr.header_len(); + + total_size += udp_hdr.header_len() + payload.len(); + } + _ => { + total_size += packet.header.payload_len; + } + } + + (total_size, compressed_hdr_size, uncompressed_hdr_size) + } + + #[cfg(feature = "proto-sixlowpan-fragmentation")] + pub(super) fn dispatch_sixlowpan_frag( + &mut self, + tx_token: Tx, + ieee_repr: Ieee802154Repr, + frag: &mut Fragmenter, + ) { + // Create the FRAG_N header. + let fragn = SixlowpanFragRepr::Fragment { + size: frag.sixlowpan.datagram_size, + tag: frag.sixlowpan.datagram_tag, + offset: (frag.sixlowpan.datagram_offset / 8) as u8, + }; + + let ieee_len = ieee_repr.buffer_len(); + let frag_size = (frag.packet_len - frag.sent_bytes).min(frag.sixlowpan.fragn_size); + + tx_token.consume( + ieee_repr.buffer_len() + fragn.buffer_len() + frag_size, + |mut tx_buf| { + let mut ieee_packet = Ieee802154Frame::new_unchecked(&mut tx_buf[..ieee_len]); + ieee_repr.emit(&mut ieee_packet); + tx_buf = &mut tx_buf[ieee_len..]; + + let mut frag_packet = + SixlowpanFragPacket::new_unchecked(&mut tx_buf[..fragn.buffer_len()]); + fragn.emit(&mut frag_packet); + tx_buf = &mut tx_buf[fragn.buffer_len()..]; + + // Add the buffer part + tx_buf[..frag_size].copy_from_slice(&frag.buffer[frag.sent_bytes..][..frag_size]); + + frag.sent_bytes += frag_size; + frag.sixlowpan.datagram_offset += frag_size; + }, + ); + } +} + +/// Convert a 6LoWPAN next header to an IPv6 next header. +#[inline] +fn decompress_next_header(next_header: SixlowpanNextHeader, payload: &[u8]) -> Result { + match next_header { + SixlowpanNextHeader::Compressed => match SixlowpanNhcPacket::dispatch(payload)? { + SixlowpanNhcPacket::ExtHeader => { + let ext_hdr = SixlowpanExtHeaderPacket::new_checked(payload)?; + Ok(ext_hdr.extension_header_id().into()) + } + SixlowpanNhcPacket::UdpHeader => Ok(IpProtocol::Udp), + }, + SixlowpanNextHeader::Uncompressed(proto) => Ok(proto), + } +} + +// NOTE: we always inline this function into the sixlowpan_to_ipv6 function, since it is only used there. +#[inline(always)] +fn decompress_ext_hdr<'d>( + mut data: &'d [u8], + next_header: &mut Option, + mut buffer: &'d mut [u8], + payload_len: &mut usize, + decompressed_len: &mut usize, +) -> Result<(&'d mut [u8], &'d [u8])> { + let ext_hdr = SixlowpanExtHeaderPacket::new_checked(data)?; + let ext_repr = SixlowpanExtHeaderRepr::parse(&ext_hdr)?; + let nh = decompress_next_header( + ext_repr.next_header, + &data[ext_repr.length as usize + ext_repr.buffer_len()..], + )?; + *next_header = Some(ext_repr.next_header); + let ipv6_ext_hdr = Ipv6ExtHeaderRepr { + next_header: nh, + length: ext_repr.length / 8, + data: ext_hdr.payload(), + }; + if ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len() > buffer.len() { + return Err(Error); + } + ipv6_ext_hdr.emit(&mut Ipv6ExtHeader::new_unchecked( + &mut buffer[..ipv6_ext_hdr.header_len()], + )); + buffer[ipv6_ext_hdr.header_len()..][..ipv6_ext_hdr.data.len()] + .copy_from_slice(ipv6_ext_hdr.data); + buffer = &mut buffer[ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len()..]; + *payload_len += ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len(); + *decompressed_len += ipv6_ext_hdr.header_len() + ipv6_ext_hdr.data.len(); + data = &data[ext_repr.buffer_len() + ext_repr.length as usize..]; + Ok((buffer, data)) +} + +// NOTE: we always inline this function into the sixlowpan_to_ipv6 function, since it is only used there. +#[inline(always)] +fn decompress_udp( + data: &[u8], + iphc_repr: &SixlowpanIphcRepr, + buffer: &mut [u8], + total_len: Option, + payload_len: &mut usize, + decompressed_len: &mut usize, +) -> Result<()> { + let udp_packet = SixlowpanUdpNhcPacket::new_checked(data)?; + let payload = udp_packet.payload(); + let udp_repr = SixlowpanUdpNhcRepr::parse( + &udp_packet, + &iphc_repr.src_addr, + &iphc_repr.dst_addr, + &ChecksumCapabilities::ignored(), + )?; + if udp_repr.header_len() + payload.len() > buffer.len() { + return Err(Error); + } + let udp_payload_len = if let Some(total_len) = total_len { + total_len - *payload_len - 8 + } else { + payload.len() + }; + *payload_len += udp_payload_len + 8; + *decompressed_len += udp_repr.0.header_len() + payload.len(); + let mut udp = UdpPacket::new_unchecked(&mut buffer[..payload.len() + 8]); + udp_repr.0.emit_header(&mut udp, udp_payload_len); + buffer[8..][..payload.len()].copy_from_slice(payload); + Ok(()) +} + +#[cfg(test)] +#[cfg(all(feature = "proto-rpl", feature = "proto-ipv6-hbh"))] +mod tests { + use super::*; + + static SIXLOWPAN_COMPRESSED_RPL_DAO: [u8; 99] = [ + 0x61, 0xdc, 0x45, 0xcd, 0xab, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x7e, 0xf7, 0x00, 0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00, + 0x1e, 0x08, 0x00, 0x9b, 0x02, 0x3e, 0x63, 0x1e, 0x40, 0x00, 0xf1, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x05, 0x12, 0x00, + 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + ]; + + static SIXLOWPAN_UNCOMPRESSED_RPL_DAO: [u8; 114] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x40, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x3a, 0x00, 0x63, 0x04, 0x00, + 0x1e, 0x08, 0x00, 0x9b, 0x02, 0x3e, 0x63, 0x1e, 0x40, 0x00, 0xf1, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x05, 0x12, 0x00, + 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x03, 0x00, 0x03, + 0x00, 0x03, 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + ]; + + #[test] + fn test_sixlowpan_decompress_hop_by_hop_with_icmpv6() { + let address_context = [SixlowpanAddressContext([ + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ])]; + + let ieee_frame = Ieee802154Frame::new_checked(&SIXLOWPAN_COMPRESSED_RPL_DAO).unwrap(); + let ieee_repr = Ieee802154Repr::parse(&ieee_frame).unwrap(); + + let mut buffer = [0u8; 256]; + let len = InterfaceInner::sixlowpan_to_ipv6( + &address_context, + &ieee_repr, + ieee_frame.payload().unwrap(), + None, + &mut buffer[..], + ) + .unwrap(); + + assert_eq!(&buffer[..len], &SIXLOWPAN_UNCOMPRESSED_RPL_DAO); + } + + #[test] + fn test_sixlowpan_compress_hop_by_hop_with_icmpv6() { + let ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: true, + sequence_number: Some(69), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2006, + dst_pan_id: Some(Ieee802154Pan(43981)), + dst_addr: Some(Ieee802154Address::Extended([0, 1, 0, 1, 0, 1, 0, 1])), + src_pan_id: None, + src_addr: Some(Ieee802154Address::Extended([0, 3, 0, 3, 0, 3, 0, 3])), + }; + + let mut ip_packet = PacketV6 { + header: Ipv6Repr { + src_addr: crate::wire::ipv6_from_octets([ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3, + ]), + dst_addr: crate::wire::ipv6_from_octets([ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ]), + next_header: IpProtocol::Icmpv6, + payload_len: 66, + hop_limit: 64, + }, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: None, + #[cfg(feature = "proto-ipv6-fragmentation")] + fragment: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload: IpPayload::Icmpv6(Icmpv6Repr::Rpl(RplRepr::DestinationAdvertisementObject { + rpl_instance_id: RplInstanceId::Global(30), + expect_ack: false, + sequence: 241, + dodag_id: Some(crate::wire::ipv6_from_octets([ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ])), + options: &[], + })), + }; + + let (total_size, _, _) = InterfaceInner::compressed_packet_size(&mut ip_packet, &ieee_repr); + let mut buffer = vec![0u8; total_size]; + + InterfaceInner::ipv6_to_sixlowpan( + &ChecksumCapabilities::default(), + ip_packet, + &ieee_repr, + &mut buffer[..total_size], + ); + + let result = [ + 0x7e, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, 0x0, 0x3, 0x0, + 0x3, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + 0xe0, 0x3a, 0x6, 0x63, 0x4, 0x0, 0x1e, 0x3, 0x0, 0x9b, 0x2, 0x3e, 0x63, 0x1e, 0x40, + 0x0, 0xf1, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, + 0x1, 0x5, 0x12, 0x0, 0x80, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, + 0x0, 0x3, 0x0, 0x3, 0x6, 0x14, 0x0, 0x0, 0x0, 0x1e, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + ]; + + assert_eq!(&result, &result); + } + + #[test] + fn test_sixlowpan_compress_hop_by_hop_with_udp() { + let ieee_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: true, + sequence_number: Some(69), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2006, + dst_pan_id: Some(Ieee802154Pan(43981)), + dst_addr: Some(Ieee802154Address::Extended([0, 1, 0, 1, 0, 1, 0, 1])), + src_pan_id: None, + src_addr: Some(Ieee802154Address::Extended([0, 3, 0, 3, 0, 3, 0, 3])), + }; + + let addr = crate::wire::ipv6_from_octets([253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3]); + let parent_address = + crate::wire::ipv6_from_octets([253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1]); + + let mut hbh_options = heapless::Vec::new(); + hbh_options + .push(Ipv6OptionRepr::Rpl(RplHopByHopRepr { + down: false, + rank_error: false, + forwarding_error: false, + instance_id: RplInstanceId::from(0x1e), + sender_rank: 0x300, + })) + .unwrap(); + + let mut ip_packet = PacketV6 { + header: Ipv6Repr { + src_addr: addr, + dst_addr: parent_address, + next_header: IpProtocol::Icmpv6, + payload_len: 66, + hop_limit: 64, + }, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: Some(Ipv6HopByHopRepr { + options: hbh_options, + }), + #[cfg(feature = "proto-ipv6-fragmentation")] + fragment: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload: IpPayload::Icmpv6(Icmpv6Repr::Rpl(RplRepr::DestinationAdvertisementObject { + rpl_instance_id: RplInstanceId::Global(30), + expect_ack: false, + sequence: 241, + dodag_id: Some(crate::wire::ipv6_from_octets([ + 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ])), + options: &[ + 5, 18, 0, 128, 253, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 3, 0, 3, 0, 3, 6, 20, 0, 0, + 0, 30, 253, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 1, 0, 1, + ], + })), + }; + + let (total_size, _, _) = InterfaceInner::compressed_packet_size(&mut ip_packet, &ieee_repr); + let mut buffer = vec![0u8; total_size]; + + InterfaceInner::ipv6_to_sixlowpan( + &ChecksumCapabilities::default(), + ip_packet, + &ieee_repr, + &mut buffer[..total_size], + ); + + let result = [ + 0x7e, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, 0x0, 0x3, 0x0, + 0x3, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + 0xe0, 0x3a, 0x6, 0x63, 0x4, 0x0, 0x1e, 0x3, 0x0, 0x9b, 0x2, 0x3e, 0x63, 0x1e, 0x40, + 0x0, 0xf1, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, + 0x1, 0x5, 0x12, 0x0, 0x80, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3, 0x0, 0x3, + 0x0, 0x3, 0x0, 0x3, 0x6, 0x14, 0x0, 0x0, 0x0, 0x1e, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x2, 0x1, 0x0, 0x1, 0x0, 0x1, 0x0, 0x1, + ]; + + assert_eq!(&buffer[..total_size], &result); + } +} diff --git a/vendor/smoltcp/src/iface/interface/tcp.rs b/vendor/smoltcp/src/iface/interface/tcp.rs new file mode 100644 index 00000000..140bdcda --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/tcp.rs @@ -0,0 +1,48 @@ +use super::*; + +use crate::socket::tcp::Socket; + +impl InterfaceInner { + pub(crate) fn process_tcp<'frame>( + &mut self, + sockets: &mut SocketSet, + handled_by_raw_socket: bool, + ip_repr: IpRepr, + ip_payload: &'frame [u8], + ) -> Option> { + let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr()); + let tcp_packet = check!(TcpPacket::new_checked(ip_payload)); + let tcp_repr = check!(TcpRepr::parse( + &tcp_packet, + &src_addr, + &dst_addr, + &self.caps.checksum + )); + + for tcp_socket in sockets + .items_mut() + .filter_map(|i| Socket::downcast_mut(&mut i.socket)) + { + if tcp_socket.accepts(self, &ip_repr, &tcp_repr) { + return tcp_socket + .process(self, &ip_repr, &tcp_repr) + .map(|(ip, tcp)| Packet::new(ip, IpPayload::Tcp(tcp))); + } + } + + if tcp_repr.control == TcpControl::Rst + || ip_repr.dst_addr().is_unspecified() + || ip_repr.src_addr().is_unspecified() + || handled_by_raw_socket + { + // Never reply to a TCP RST packet with another TCP RST packet. + // Never send a TCP RST packet with unspecified addresses. + // Never send a TCP RST when packet has been handled by raw socket. + None + } else { + // The packet wasn't handled by a socket, send a TCP RST packet. + let (ip, tcp) = tcp::Socket::rst_reply(&ip_repr, &tcp_repr); + Some(Packet::new(ip, IpPayload::Tcp(tcp))) + } + } +} diff --git a/vendor/smoltcp/src/iface/interface/tests/ipv4.rs b/vendor/smoltcp/src/iface/interface/tests/ipv4.rs new file mode 100644 index 00000000..778c7124 --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/tests/ipv4.rs @@ -0,0 +1,1547 @@ +use super::*; + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_any_ip_accept_arp(#[case] medium: Medium) { + let mut buffer = [0u8; 64]; + #[allow(non_snake_case)] + fn ETHERNET_FRAME_ARP(buffer: &mut [u8]) -> &[u8] { + let ethernet_repr = EthernetRepr { + src_addr: EthernetAddress::from_bytes(&[0x02, 0x02, 0x02, 0x02, 0x02, 0x03]), + dst_addr: EthernetAddress::from_bytes(&[0x02, 0x02, 0x02, 0x02, 0x02, 0x02]), + ethertype: EthernetProtocol::Arp, + }; + let frame_repr = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Request, + source_hardware_addr: EthernetAddress::from_bytes(&[ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, + ]), + source_protocol_addr: crate::wire::ipv4_from_octets([192, 168, 1, 2]), + target_hardware_addr: EthernetAddress::from_bytes(&[ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + ]), + target_protocol_addr: crate::wire::ipv4_from_octets([192, 168, 1, 3]), + }; + let mut frame = EthernetFrame::new_unchecked(&mut buffer[..]); + ethernet_repr.emit(&mut frame); + + let mut frame = ArpPacket::new_unchecked(&mut buffer[ethernet_repr.buffer_len()..]); + frame_repr.emit(&mut frame); + + &buffer[..ethernet_repr.buffer_len() + frame_repr.buffer_len()] + } + + let (mut iface, mut sockets, _) = setup(medium); + + assert!( + iface + .inner + .process_ethernet( + &mut sockets, + PacketMeta::default(), + ETHERNET_FRAME_ARP(buffer.as_mut()), + &mut iface.fragments, + ) + .is_none() + ); + + // Accept any IP address + iface.set_any_ip(true); + + assert!( + iface + .inner + .process_ethernet( + &mut sockets, + PacketMeta::default(), + ETHERNET_FRAME_ARP(buffer.as_mut()), + &mut iface.fragments, + ) + .is_some() + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_no_icmp_no_unicast(#[case] medium: Medium) { + let (mut iface, mut sockets, _) = setup(medium); + + // Unknown Ipv4 Protocol + // + // Because the destination is the broadcast address + // this should not trigger and Destination Unreachable + // response. See RFC 1122 § 3.2.2. + let repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Unknown(0x0c), + payload_len: 0, + hop_limit: 0x40, + }); + + let mut bytes = vec![0u8; 54]; + repr.emit(&mut bytes, &ChecksumCapabilities::default()); + let frame = Ipv4Packet::new_unchecked(&bytes[..]); + + // Ensure that the unknown protocol frame does not trigger an + // ICMP error response when the destination address is a + // broadcast address + + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frame, + &mut iface.fragments + ), + None + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_icmp_error_no_payload(#[case] medium: Medium) { + static NO_BYTES: [u8; 0] = []; + let (mut iface, mut sockets, _device) = setup(medium); + + // Unknown Ipv4 Protocol with no payload + let repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + next_header: IpProtocol::Unknown(0x0c), + payload_len: 0, + hop_limit: 0x40, + }); + + let mut bytes = vec![0u8; 34]; + repr.emit(&mut bytes, &ChecksumCapabilities::default()); + let frame = Ipv4Packet::new_unchecked(&bytes[..]); + + // The expected Destination Unreachable response due to the + // unknown protocol + let icmp_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::ProtoUnreachable, + header: Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + next_header: IpProtocol::Unknown(12), + payload_len: 0, + hop_limit: 64, + }, + data: &NO_BYTES, + }; + + let expected_repr = Packet::new_ipv4( + Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + next_header: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + hop_limit: 64, + }, + IpPayload::Icmpv4(icmp_repr), + ); + + // Ensure that the unknown protocol triggers an error response. + // And we correctly handle no payload. + + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frame, + &mut iface.fragments + ), + Some(expected_repr) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_local_subnet_broadcasts(#[case] medium: Medium) { + let (mut iface, _, _device) = setup(medium); + iface.update_ip_addrs(|addrs| { + addrs.iter_mut().next().map(|addr| { + *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 23), 24)); + }); + }); + + assert!( + iface + .inner + .is_broadcast_v4(Ipv4Address::new(255, 255, 255, 255)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(255, 255, 255, 254)) + ); + assert!( + iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 168, 1, 255)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 168, 1, 254)) + ); + + iface.update_ip_addrs(|addrs| { + addrs.iter_mut().next().map(|addr| { + *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(192, 168, 23, 24), 16)); + }); + }); + assert!( + iface + .inner + .is_broadcast_v4(Ipv4Address::new(255, 255, 255, 255)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(255, 255, 255, 254)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 168, 23, 255)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 168, 23, 254)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 168, 255, 254)) + ); + assert!( + iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 168, 255, 255)) + ); + + iface.update_ip_addrs(|addrs| { + addrs.iter_mut().next().map(|addr| { + *addr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(192, 168, 23, 24), 8)); + }); + }); + assert!( + iface + .inner + .is_broadcast_v4(Ipv4Address::new(255, 255, 255, 255)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(255, 255, 255, 254)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 23, 1, 255)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 23, 1, 254)) + ); + assert!( + !iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 255, 255, 254)) + ); + assert!( + iface + .inner + .is_broadcast_v4(Ipv4Address::new(192, 255, 255, 255)) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "medium-ip", feature = "socket-udp"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "medium-ethernet", feature = "socket-udp"))] +fn test_icmp_error_port_unreachable(#[case] medium: Medium) { + static UDP_PAYLOAD: [u8; 12] = [ + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x6c, 0x64, 0x21, + ]; + let (mut iface, mut sockets, _device) = setup(medium); + + let mut udp_bytes_unicast = vec![0u8; 20]; + let mut udp_bytes_broadcast = vec![0u8; 20]; + let mut packet_unicast = UdpPacket::new_unchecked(&mut udp_bytes_unicast); + let mut packet_broadcast = UdpPacket::new_unchecked(&mut udp_bytes_broadcast); + + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 64, + }); + + // Emit the representations to a packet + udp_repr.emit( + &mut packet_unicast, + &ip_repr.src_addr(), + &ip_repr.dst_addr(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(&UDP_PAYLOAD), + &ChecksumCapabilities::default(), + ); + + let data = packet_unicast.into_inner(); + + // The expected Destination Unreachable ICMPv4 error response due + // to no sockets listening on the destination port. + let icmp_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::PortUnreachable, + header: Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 64, + }, + data, + }; + let expected_repr = Packet::new_ipv4( + Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + next_header: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + hop_limit: 64, + }, + IpPayload::Icmpv4(icmp_repr), + ); + + // Ensure that the unknown protocol triggers an error response. + // And we correctly handle no payload. + assert_eq!( + iface + .inner + .process_udp(&mut sockets, PacketMeta::default(), false, ip_repr, data), + Some(expected_repr) + ); + + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 64, + }); + + // Emit the representations to a packet + udp_repr.emit( + &mut packet_broadcast, + &ip_repr.src_addr(), + &IpAddress::Ipv4(Ipv4Address::BROADCAST), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(&UDP_PAYLOAD), + &ChecksumCapabilities::default(), + ); + + // Ensure that the port unreachable error does not trigger an + // ICMP error response when the destination address is a + // broadcast address and no socket is bound to the port. + assert_eq!( + iface.inner.process_udp( + &mut sockets, + PacketMeta::default(), + false, + ip_repr, + packet_broadcast.into_inner(), + ), + None + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "medium-ip", feature = "auto-icmp-echo-reply"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "medium-ethernet", feature = "auto-icmp-echo-reply"))] +fn test_handle_ipv4_broadcast(#[case] medium: Medium) { + use crate::wire::{Icmpv4Packet, Icmpv4Repr}; + + let (mut iface, mut sockets, _device) = setup(medium); + + let our_ipv4_addr = iface.ipv4_addr().unwrap(); + let src_ipv4_addr = Ipv4Address::new(127, 0, 0, 2); + + // ICMPv4 echo request + let icmpv4_data: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + let icmpv4_repr = Icmpv4Repr::EchoRequest { + ident: 0x1234, + seq_no: 0xabcd, + data: &icmpv4_data, + }; + + // Send to IPv4 broadcast address + let ipv4_repr = Ipv4Repr { + src_addr: src_ipv4_addr, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Icmp, + hop_limit: 64, + payload_len: icmpv4_repr.buffer_len(), + }; + + // Emit to ip frame + let mut bytes = vec![0u8; ipv4_repr.buffer_len() + icmpv4_repr.buffer_len()]; + let frame = { + ipv4_repr.emit( + &mut Ipv4Packet::new_unchecked(&mut bytes[..]), + &ChecksumCapabilities::default(), + ); + icmpv4_repr.emit( + &mut Icmpv4Packet::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]), + &ChecksumCapabilities::default(), + ); + Ipv4Packet::new_unchecked(&bytes[..]) + }; + + // Expected ICMPv4 echo reply + let expected_icmpv4_repr = Icmpv4Repr::EchoReply { + ident: 0x1234, + seq_no: 0xabcd, + data: &icmpv4_data, + }; + let expected_ipv4_repr = Ipv4Repr { + src_addr: our_ipv4_addr, + dst_addr: src_ipv4_addr, + next_header: IpProtocol::Icmp, + hop_limit: 64, + payload_len: expected_icmpv4_repr.buffer_len(), + }; + let expected_packet = + Packet::new_ipv4(expected_ipv4_repr, IpPayload::Icmpv4(expected_icmpv4_repr)); + + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frame, + &mut iface.fragments + ), + Some(expected_packet) + ); +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_handle_valid_arp_request(#[case] medium: Medium) { + let (mut iface, mut sockets, _device) = setup(medium); + + let mut eth_bytes = vec![0u8; 42]; + + let local_ip_addr = Ipv4Address::new(0x7f, 0x00, 0x00, 0x01); + let remote_ip_addr = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02); + let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + + let repr = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Request, + source_hardware_addr: remote_hw_addr, + source_protocol_addr: remote_ip_addr, + target_hardware_addr: EthernetAddress::default(), + target_protocol_addr: local_ip_addr, + }; + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress::BROADCAST); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Arp); + let mut packet = ArpPacket::new_unchecked(frame.payload_mut()); + repr.emit(&mut packet); + + // Ensure an ARP Request for us triggers an ARP Reply + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 { + operation: ArpOperation::Reply, + source_hardware_addr: local_hw_addr, + source_protocol_addr: local_ip_addr, + target_hardware_addr: remote_hw_addr, + target_protocol_addr: remote_ip_addr + })) + ); + + // Ensure the address of the requester was entered in the cache + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv4(remote_ip_addr), + &mut iface.fragmenter, + ), + Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken)) + ); +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_handle_other_arp_request(#[case] medium: Medium) { + let (mut iface, mut sockets, _device) = setup(medium); + + let mut eth_bytes = vec![0u8; 42]; + + let remote_ip_addr = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + + let repr = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Request, + source_hardware_addr: remote_hw_addr, + source_protocol_addr: remote_ip_addr, + target_hardware_addr: EthernetAddress::default(), + target_protocol_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x03), + }; + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress::BROADCAST); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Arp); + let mut packet = ArpPacket::new_unchecked(frame.payload_mut()); + repr.emit(&mut packet); + + // Ensure an ARP Request for someone else does not trigger an ARP Reply + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + None + ); + + // Ensure the address of the requester was NOT entered in the cache + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv4(remote_ip_addr), + &mut iface.fragmenter, + ), + Err(DispatchError::NeighborPending) + ); +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_arp_flush_after_update_ip(#[case] medium: Medium) { + let (mut iface, mut sockets, _device) = setup(medium); + + let mut eth_bytes = vec![0u8; 42]; + + let local_ip_addr = Ipv4Address::new(0x7f, 0x00, 0x00, 0x01); + let remote_ip_addr = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02); + let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + + let repr = ArpRepr::EthernetIpv4 { + operation: ArpOperation::Request, + source_hardware_addr: remote_hw_addr, + source_protocol_addr: remote_ip_addr, + target_hardware_addr: EthernetAddress::default(), + target_protocol_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + }; + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress::BROADCAST); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Arp); + { + let mut packet = ArpPacket::new_unchecked(frame.payload_mut()); + repr.emit(&mut packet); + } + + // Ensure an ARP Request for us triggers an ARP Reply + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 { + operation: ArpOperation::Reply, + source_hardware_addr: local_hw_addr, + source_protocol_addr: local_ip_addr, + target_hardware_addr: remote_hw_addr, + target_protocol_addr: remote_ip_addr + })) + ); + + // Ensure the address of the requester was entered in the cache + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv4(remote_ip_addr), + &mut iface.fragmenter, + ), + Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken)) + ); + + // Update IP addrs to trigger ARP cache flush + let local_ip_addr_new = Ipv4Address::new(0x7f, 0x00, 0x00, 0x01); + iface.update_ip_addrs(|addrs| { + addrs.iter_mut().next().map(|addr| { + *addr = IpCidr::Ipv4(Ipv4Cidr::new(local_ip_addr_new, 24)); + }); + }); + + // ARP cache flush after address change + assert!(!iface.inner.has_neighbor(&IpAddress::Ipv4(remote_ip_addr))); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all( + feature = "socket-icmp", + feature = "medium-ip", + feature = "auto-icmp-echo-reply", +))] +#[case(Medium::Ethernet)] +#[cfg(all( + feature = "socket-icmp", + feature = "medium-ethernet", + feature = "auto-icmp-echo-reply", +))] +fn test_icmpv4_socket(#[case] medium: Medium) { + use crate::wire::Icmpv4Packet; + + let (mut iface, mut sockets, _device) = setup(medium); + + let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]); + let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 24]); + + let icmpv4_socket = icmp::Socket::new(rx_buffer, tx_buffer); + + let socket_handle = sockets.add(icmpv4_socket); + + let ident = 0x1234; + let seq_no = 0x5432; + let echo_data = &[0xff; 16]; + + let socket = sockets.get_mut::(socket_handle); + // Bind to the ID 0x1234 + assert_eq!(socket.bind(icmp::Endpoint::Ident(ident)), Ok(())); + + // Ensure the ident we bound to and the ident of the packet are the same. + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]); + let echo_repr = Icmpv4Repr::EchoRequest { + ident, + seq_no, + data: echo_data, + }; + echo_repr.emit(&mut packet, &ChecksumCapabilities::default()); + let icmp_data = &*packet.into_inner(); + + let ipv4_repr = Ipv4Repr { + src_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x02), + dst_addr: Ipv4Address::new(0x7f, 0x00, 0x00, 0x01), + next_header: IpProtocol::Icmp, + payload_len: 24, + hop_limit: 64, + }; + + // Open a socket and ensure the packet is handled due to the listening + // socket. + assert!(!sockets.get_mut::(socket_handle).can_recv()); + + // Confirm we still get EchoReply from `smoltcp` even with the ICMP socket listening + let echo_reply = Icmpv4Repr::EchoReply { + ident, + seq_no, + data: echo_data, + }; + let ipv4_reply = Ipv4Repr { + src_addr: ipv4_repr.dst_addr, + dst_addr: ipv4_repr.src_addr, + ..ipv4_repr + }; + assert_eq!( + iface + .inner + .process_icmpv4(&mut sockets, ipv4_repr, icmp_data), + Some(Packet::new_ipv4(ipv4_reply, IpPayload::Icmpv4(echo_reply))) + ); + + let socket = sockets.get_mut::(socket_handle); + assert!(socket.can_recv()); + assert_eq!( + socket.recv(), + Ok(( + icmp_data, + IpAddress::Ipv4(Ipv4Address::new(0x7f, 0x00, 0x00, 0x02)) + )) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "multicast", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "multicast", feature = "medium-ethernet"))] +fn test_handle_igmp(#[case] medium: Medium) { + fn recv_igmp( + device: &mut crate::tests::TestingDevice, + timestamp: Instant, + ) -> Vec<(Ipv4Repr, IgmpRepr)> { + let caps = device.capabilities(); + let checksum_caps = &caps.checksum; + recv_all(device, timestamp) + .iter() + .filter_map(|frame| { + let ipv4_packet = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let eth_frame = EthernetFrame::new_checked(frame).ok()?; + Ipv4Packet::new_checked(eth_frame.payload()).ok()? + } + #[cfg(feature = "medium-ip")] + Medium::Ip => Ipv4Packet::new_checked(&frame[..]).ok()?, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + let ipv4_repr = Ipv4Repr::parse(&ipv4_packet, checksum_caps).ok()?; + let ip_payload = ipv4_packet.payload(); + let igmp_packet = IgmpPacket::new_checked(ip_payload).ok()?; + let igmp_repr = IgmpRepr::parse(&igmp_packet).ok()?; + Some((ipv4_repr, igmp_repr)) + }) + .collect::>() + } + + let groups = [ + Ipv4Address::new(224, 0, 0, 22), + Ipv4Address::new(224, 0, 0, 56), + ]; + + let (mut iface, mut sockets, mut device) = setup(medium); + + // Join multicast groups + let timestamp = Instant::ZERO; + for group in &groups { + iface.join_multicast_group(*group).unwrap(); + } + iface.poll(timestamp, &mut device, &mut sockets); + + let reports = recv_igmp(&mut device, timestamp); + assert_eq!(reports.len(), 2); + for (i, group_addr) in groups.iter().enumerate() { + assert_eq!(reports[i].0.next_header, IpProtocol::Igmp); + assert_eq!(reports[i].0.dst_addr, *group_addr); + assert_eq!( + reports[i].1, + IgmpRepr::MembershipReport { + group_addr: *group_addr, + version: IgmpVersion::Version2, + } + ); + } + + // General query + const GENERAL_QUERY_BYTES: &[u8] = &[ + 0x46, 0xc0, 0x00, 0x24, 0xed, 0xb4, 0x00, 0x00, 0x01, 0x02, 0x47, 0x43, 0xac, 0x16, 0x63, + 0x04, 0xe0, 0x00, 0x00, 0x01, 0x94, 0x04, 0x00, 0x00, 0x11, 0x64, 0xec, 0x8f, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ]; + device.rx_queue.push_back(GENERAL_QUERY_BYTES.to_vec()); + + // Trigger processing until all packets received through the + // loopback have been processed, including responses to + // GENERAL_QUERY_BYTES. Therefore `recv_all()` would return 0 + // pkts that could be checked. + iface.socket_ingress(&mut device, &mut sockets); + + // Leave multicast groups + let timestamp = Instant::ZERO; + for group in &groups { + iface.leave_multicast_group(*group).unwrap(); + } + iface.poll(timestamp, &mut device, &mut sockets); + + let leaves = recv_igmp(&mut device, timestamp); + assert_eq!(leaves.len(), 2); + for (i, group_addr) in groups.iter().cloned().enumerate() { + assert_eq!(leaves[i].0.next_header, IpProtocol::Igmp); + assert_eq!(leaves[i].0.dst_addr, IPV4_MULTICAST_ALL_ROUTERS); + assert_eq!(leaves[i].1, IgmpRepr::LeaveGroup { group_addr }); + } +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "proto-ipv4-fragmentation", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "proto-ipv4-fragmentation", feature = "medium-ethernet"))] +fn test_packet_len(#[case] medium: Medium) { + use crate::config::FRAGMENTATION_BUFFER_SIZE; + + let (mut iface, _, _) = setup(medium); + + struct TestTxToken { + max_transmission_unit: usize, + } + + impl TxToken for TestTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + net_debug!("TxToken get len: {}", len); + assert!(len <= self.max_transmission_unit); + let mut junk = [0; 1536]; + f(&mut junk[..len]) + } + } + + iface.inner.neighbor_cache.fill( + IpAddress::Ipv4(Ipv4Address::new(127, 0, 0, 1)), + HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + ])), + Instant::ZERO, + ); + + for ip_packet_len in [ + 100, + iface.inner.ip_mtu(), + iface.inner.ip_mtu() + 1, + FRAGMENTATION_BUFFER_SIZE, + ] { + net_debug!("ip_packet_len: {}", ip_packet_len); + + let mut ip_repr = Ipv4Repr { + src_addr: Ipv4Address::new(127, 0, 0, 1), + dst_addr: Ipv4Address::new(127, 0, 0, 1), + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }; + let udp_repr = UdpRepr { + src_port: 12345, + dst_port: 54321, + }; + + let ip_packet_payload_len = ip_packet_len - ip_repr.buffer_len(); + let udp_packet_payload_len = ip_packet_payload_len - udp_repr.header_len(); + ip_repr.payload_len = ip_packet_payload_len; + + let udp_packet_payload = vec![1; udp_packet_payload_len]; + let ip_payload = IpPayload::Udp(udp_repr, &udp_packet_payload); + let ip_packet = Packet::new_ipv4(ip_repr, ip_payload); + + assert_eq!( + iface.inner.dispatch_ip( + TestTxToken { + max_transmission_unit: iface.inner.caps.max_transmission_unit + }, + PacketMeta::default(), + ip_packet, + &mut iface.fragmenter, + ), + Ok(()) + ); + } +} + +/// Check no reply is emitted when using a raw socket +#[cfg(feature = "socket-raw")] +fn check_no_reply_raw_socket(medium: Medium, frame: &crate::wire::ipv4::Packet<&[u8]>) { + let (mut iface, mut sockets, _) = setup(medium); + + let packets = 1; + let rx_buffer = + raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]); + let tx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packets], + vec![0; 48 * packets], + ); + let raw_socket = raw::Socket::new(Some(IpVersion::Ipv4), None, rx_buffer, tx_buffer); + sockets.add(raw_socket); + + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + frame, + &mut iface.fragments + ), + None + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-raw", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "socket-raw", feature = "medium-ethernet"))] +/// Test no reply to received UDP when using raw socket which accepts all protocols +fn test_raw_socket_no_reply_udp(#[case] medium: Medium) { + use crate::wire::{UdpPacket, UdpRepr}; + + let src_addr = Ipv4Address::new(127, 0, 0, 2); + let dst_addr = Ipv4Address::new(127, 0, 0, 1); + + const PAYLOAD_LEN: usize = 10; + + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + let ipv4_repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Udp, + hop_limit: 64, + payload_len: udp_repr.header_len() + PAYLOAD_LEN, + }; + + // Emit to frame + let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + PAYLOAD_LEN]; + let frame = { + ipv4_repr.emit( + &mut Ipv4Packet::new_unchecked(&mut bytes), + &ChecksumCapabilities::default(), + ); + udp_repr.emit( + &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]), + &src_addr.into(), + &dst_addr.into(), + PAYLOAD_LEN, + |buf| fill_slice(buf, 0x2a), + &ChecksumCapabilities::default(), + ); + Ipv4Packet::new_unchecked(&bytes[..]) + }; + + check_no_reply_raw_socket(medium, &frame); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-raw", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "socket-raw", feature = "medium-ethernet"))] +/// Test no reply to received TCP when using raw socket which accepts all protocols +fn test_raw_socket_no_reply_tcp(#[case] medium: Medium) { + use crate::wire::{TcpPacket, TcpRepr}; + + let src_addr = Ipv4Address::new(127, 0, 0, 2); + let dst_addr = Ipv4Address::new(127, 0, 0, 1); + + const PAYLOAD_LEN: usize = 10; + const PAYLOAD: [u8; PAYLOAD_LEN] = [0x2a; PAYLOAD_LEN]; + + let tcp_repr = TcpRepr { + src_port: 67, + dst_port: 68, + control: TcpControl::Syn, + seq_number: TcpSeqNumber(1), + ack_number: None, + window_len: 10, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &PAYLOAD, + }; + let ipv4_repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Tcp, + hop_limit: 64, + payload_len: tcp_repr.header_len() + PAYLOAD_LEN, + }; + + // Emit to frame + let mut bytes = vec![0u8; ipv4_repr.buffer_len() + tcp_repr.header_len() + PAYLOAD_LEN]; + let frame = { + ipv4_repr.emit( + &mut Ipv4Packet::new_unchecked(&mut bytes), + &ChecksumCapabilities::default(), + ); + tcp_repr.emit( + &mut TcpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]), + &src_addr.into(), + &dst_addr.into(), + &ChecksumCapabilities::default(), + ); + Ipv4Packet::new_unchecked(&bytes[..]) + }; + + check_no_reply_raw_socket(medium, &frame); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-raw", feature = "socket-udp", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all( + feature = "socket-raw", + feature = "socket-udp", + feature = "medium-ethernet" +))] +fn test_raw_socket_with_udp_socket(#[case] medium: Medium) { + use crate::socket::udp; + use crate::wire::{IpEndpoint, IpVersion, UdpPacket, UdpRepr}; + + static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f]; + + let (mut iface, mut sockets, _) = setup(medium); + + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let udp_socket_handle = sockets.add(udp_socket); + + // Bind the socket to port 68 + let socket = sockets.get_mut::(udp_socket_handle); + assert_eq!(socket.bind(68), Ok(())); + assert!(!socket.can_recv()); + assert!(socket.can_send()); + + let packets = 1; + let raw_rx_buffer = + raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 48 * 1]); + let raw_tx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packets], + vec![0; 48 * packets], + ); + let raw_socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Udp), + raw_rx_buffer, + raw_tx_buffer, + ); + sockets.add(raw_socket); + + let src_addr = Ipv4Address::new(127, 0, 0, 2); + let dst_addr = Ipv4Address::new(127, 0, 0, 1); + + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + let mut bytes = vec![0xff; udp_repr.header_len() + UDP_PAYLOAD.len()]; + let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); + udp_repr.emit( + &mut packet, + &src_addr.into(), + &dst_addr.into(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(&UDP_PAYLOAD), + &ChecksumCapabilities::default(), + ); + let ipv4_repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Udp, + hop_limit: 64, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + }; + + // Emit to frame + let mut bytes = vec![0u8; ipv4_repr.buffer_len() + udp_repr.header_len() + UDP_PAYLOAD.len()]; + let frame = { + ipv4_repr.emit( + &mut Ipv4Packet::new_unchecked(&mut bytes), + &ChecksumCapabilities::default(), + ); + udp_repr.emit( + &mut UdpPacket::new_unchecked(&mut bytes[ipv4_repr.buffer_len()..]), + &src_addr.into(), + &dst_addr.into(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(&UDP_PAYLOAD), + &ChecksumCapabilities::default(), + ); + Ipv4Packet::new_unchecked(&bytes[..]) + }; + + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frame, + &mut iface.fragments + ), + None + ); + + // Make sure the UDP socket can still receive in presence of a Raw socket that handles UDP + let socket = sockets.get_mut::(udp_socket_handle); + assert!(socket.can_recv()); + assert_eq!( + socket.recv(), + Ok(( + &UDP_PAYLOAD[..], + udp::UdpMetadata { + local_address: Some(dst_addr.into()), + ..IpEndpoint::new(src_addr.into(), 67).into() + } + )) + ); +} + +#[cfg(feature = "proto-ipv4-fragmentation")] +use crate::phy::IPV4_FRAGMENT_PAYLOAD_ALIGNMENT; +#[rstest] +#[case(Medium::Ip)] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ip" +))] +#[case(Medium::Ethernet)] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ethernet" +))] +fn test_raw_socket_tx_fragmentation(#[case] medium: Medium) { + use std::panic::AssertUnwindSafe; + + let (mut iface, mut sockets, device) = setup(medium); + let mtu = device.capabilities().max_transmission_unit; + let unaligned_length = mtu - IPV4_HEADER_LEN; + // This check ensures a valid test in which we actually do adjust for alignment. + let mtu = if unaligned_length % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT == 0 { + mtu + IPV4_FRAGMENT_PAYLOAD_ALIGNMENT / 2 + } else { + mtu + }; + + let packets = 5; + let rx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packets], + vec![0; mtu * packets], + ); + let tx_buffer = raw::PacketBuffer::new( + vec![raw::PacketMetadata::EMPTY; packets], + vec![0; mtu * packets], + ); + let socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Udp), + rx_buffer, + tx_buffer, + ); + let _handle = sockets.add(socket); + + let tx_packet_sizes = vec![ + mtu * 3 / 4, // Smaller than MTU + mtu * 5 / 4, // Larger than MTU, requires fragmentation + mtu * 9 / 4, // Much larger, requires two fragments + ]; + + // Define test token for capturing the fragments. + struct TestFragmentTxToken {} + + impl TxToken for TestFragmentTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // Buffer is something arbitrarily large. + // We cannot capture the dynamic packet_size calculation here. + let mut buffer = [0; 2048]; + let result = f(&mut buffer[..len]); + // Verify the payload size is aligned. + let payload_size = len - IPV4_HEADER_LEN; + assert!(payload_size % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT == 0); + result + } + } + + for packet_size in tx_packet_sizes { + let payload_len = packet_size - IPV4_HEADER_LEN; + let payload = vec![0u8; payload_len]; + + let ip_repr = Ipv4Repr { + src_addr: Ipv4Address::new(192, 168, 1, 3), + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Unknown(92), + hop_limit: 64, + payload_len, + }; + let ip_payload = IpPayload::Raw(&payload); + let packet = Packet::new_ipv4(ip_repr, ip_payload); + + // This should not panic for any payload size + let result = std::panic::catch_unwind(AssertUnwindSafe(|| { + if packet_size > mtu && medium == Medium::Ip { + iface.inner.dispatch_ip( + TestFragmentTxToken {}, + PacketMeta::default(), + packet, + &mut iface.fragmenter, + ) + } else { + iface.inner.dispatch_ip( + MockTxToken {}, + PacketMeta::default(), + packet, + &mut iface.fragmenter, + ) + } + })); + + // All transmissions should succeed without panicking + assert!(result.is_ok(), "Failed for packet size: {}", packet_size,); + + // Perform payload size checks if fragmentation is required. + // It is sufficient to test only the simpler IP test case. + if packet_size <= mtu || medium != Medium::Ip { + continue; + } + + // Verify that the fragment offset is correct. + let unaligned_length = mtu - IPV4_HEADER_LEN; + let remainder = unaligned_length % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT; + let expected_fragment_offset = mtu - IPV4_HEADER_LEN - remainder; + let frag_offset = iface.fragmenter.ipv4.frag_offset; + assert_eq!(frag_offset as usize, expected_fragment_offset); + + // Check subsequent fragment sizes if applicable. + if packet_size / mtu == 2 { + // Two fragments are left. The intermediate fragment must be aligned. + iface + .inner + .dispatch_ipv4_frag(TestFragmentTxToken {}, &mut iface.fragmenter); + } + // Process the final fragment. It is the remainder of the data and does not have to be aligned. + iface + .inner + .dispatch_ipv4_frag(MockTxToken {}, &mut iface.fragmenter); + + // The fragment offset should be the complete payload length once transmission is complete. + let frag_offset = iface.fragmenter.ipv4.frag_offset; + assert_eq!(frag_offset as usize, payload_len); + } +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ip" +))] +#[case(Medium::Ethernet)] +#[cfg(all( + feature = "socket-raw", + feature = "proto-ipv4-fragmentation", + feature = "medium-ethernet" +))] +fn test_raw_socket_rx_fragmentation(#[case] medium: Medium) { + use crate::wire::{IpProtocol, IpVersion, Ipv4Address, Ipv4Packet, Ipv4Repr}; + + let (mut iface, mut sockets, _device) = setup(medium); + + // Raw socket bound to IPv4 and a custom protocol. + let packets = 1; + let rx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 64]); + let tx_buffer = raw::PacketBuffer::new(vec![raw::PacketMetadata::EMPTY; packets], vec![0; 64]); + let raw_socket = raw::Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Unknown(99)), + rx_buffer, + tx_buffer, + ); + let handle = sockets.add(raw_socket); + + // Build two IPv4 fragments that together form one packet. + let src_addr = Ipv4Address::new(127, 0, 0, 2); + let dst_addr = Ipv4Address::new(127, 0, 0, 1); + let proto = IpProtocol::Unknown(99); + let ident: u16 = 0x1234; + + let total_payload_len = 30usize; + let first_payload_len = 24usize; // must be a multiple of 8 + let last_payload_len = total_payload_len - first_payload_len; + + // Helper to build one fragment as on-the-wire bytes + let build_fragment = |payload_len: usize, + more_frags: bool, + frag_offset_octets: u16, + payload_byte: u8| + -> Vec { + let repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: proto, + hop_limit: 64, + payload_len, + }; + let header_len = repr.buffer_len(); + let mut bytes = vec![0u8; header_len + payload_len]; + { + let mut pkt = Ipv4Packet::new_unchecked(&mut bytes[..]); + repr.emit(&mut pkt, &ChecksumCapabilities::default()); + pkt.set_ident(ident); + pkt.set_dont_frag(false); + pkt.set_more_frags(more_frags); + pkt.set_frag_offset(frag_offset_octets); + // Recompute checksum after changing fragmentation fields. + pkt.fill_checksum(); + } + // Fill payload with a simple pattern for validation + for b in &mut bytes[header_len..] { + *b = payload_byte; + } + bytes + }; + + let frag1_bytes = build_fragment(first_payload_len, true, 0, 0xAA); + let frag2_bytes = build_fragment(last_payload_len, false, first_payload_len as u16, 0xBB); + + let frag1 = Ipv4Packet::new_unchecked(&frag1_bytes[..]); + let frag2 = Ipv4Packet::new_unchecked(&frag2_bytes[..]); + + // First fragment alone should not be delivered to the raw socket. + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frag1, + &mut iface.fragments + ), + None + ); + { + let socket = sockets.get_mut::(handle); + assert!(!socket.can_recv()); + } + + // After the last fragment, the reassembled packet should be delivered. + assert_eq!( + iface.inner.process_ipv4( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &frag2, + &mut iface.fragments + ), + None + ); + + // Validate the raw socket received one defragmented packet with correct payload. + let socket = sockets.get_mut::(handle); + assert!(socket.can_recv()); + let data = socket.recv().expect("raw socket should have a packet"); + let packet = Ipv4Packet::new_unchecked(data); + let repr = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(repr.src_addr, src_addr); + assert_eq!(repr.dst_addr, dst_addr); + assert_eq!(repr.next_header, proto); + assert_eq!(repr.payload_len, total_payload_len); + + let payload = packet.payload(); + assert_eq!(payload.len(), total_payload_len); + assert!(payload[..first_payload_len].iter().all(|&b| b == 0xAA)); + assert!(payload[first_payload_len..].iter().all(|&b| b == 0xBB)); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-udp", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))] +fn test_icmp_reply_size(#[case] medium: Medium) { + use crate::wire::IPV4_MIN_MTU as MIN_MTU; + const MAX_PAYLOAD_LEN: usize = 528; + + let (mut iface, mut sockets, _device) = setup(medium); + + let src_addr = Ipv4Address::new(192, 168, 1, 1); + let dst_addr = Ipv4Address::new(192, 168, 1, 2); + + // UDP packet that if not tructated will cause a icmp port unreachable reply + // to exceed the minimum mtu bytes in length. + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN]; + let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); + udp_repr.emit( + &mut packet, + &src_addr.into(), + &dst_addr.into(), + MAX_PAYLOAD_LEN, + |buf| fill_slice(buf, 0x2a), + &ChecksumCapabilities::default(), + ); + + let ip_repr = Ipv4Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Udp, + hop_limit: 64, + payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN, + }; + let payload = packet.into_inner(); + + let expected_icmp_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::PortUnreachable, + header: ip_repr, + data: &payload[..MAX_PAYLOAD_LEN], + }; + + let expected_ip_repr = Ipv4Repr { + src_addr: dst_addr, + dst_addr: src_addr, + next_header: IpProtocol::Icmp, + hop_limit: 64, + payload_len: expected_icmp_repr.buffer_len(), + }; + + assert_eq!( + expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(), + MIN_MTU + ); + + assert_eq!( + iface.inner.process_udp( + &mut sockets, + PacketMeta::default(), + false, + ip_repr.into(), + payload, + ), + Some(Packet::new_ipv4( + expected_ip_repr, + IpPayload::Icmpv4(expected_icmp_repr) + )) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn get_source_address(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + + const OWN_UNIQUE_LOCAL_ADDR1: Ipv4Address = Ipv4Address::new(172, 18, 1, 2); + const OWN_UNIQUE_LOCAL_ADDR2: Ipv4Address = Ipv4Address::new(172, 24, 24, 14); + + // List of addresses of the interface: + // 172.18.1.2/24 + // 172.24.24.14/24 + iface.update_ip_addrs(|addrs| { + addrs.clear(); + + addrs + .push(IpCidr::Ipv4(Ipv4Cidr::new(OWN_UNIQUE_LOCAL_ADDR1, 24))) + .unwrap(); + addrs + .push(IpCidr::Ipv4(Ipv4Cidr::new(OWN_UNIQUE_LOCAL_ADDR2, 24))) + .unwrap(); + }); + + // List of addresses we test: + // 172.18.1.254 -> 172.18.1.2 + // 172.24.24.12 -> 172.24.24.14 + // 172.24.23.254 -> 172.18.1.2 + const UNIQUE_LOCAL_ADDR1: Ipv4Address = Ipv4Address::new(172, 18, 1, 254); + const UNIQUE_LOCAL_ADDR2: Ipv4Address = Ipv4Address::new(172, 24, 24, 12); + const UNIQUE_LOCAL_ADDR3: Ipv4Address = Ipv4Address::new(172, 24, 23, 254); + + assert_eq!( + iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR1), + Some(OWN_UNIQUE_LOCAL_ADDR1) + ); + + assert_eq!( + iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR2), + Some(OWN_UNIQUE_LOCAL_ADDR2) + ); + assert_eq!( + iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR3), + Some(OWN_UNIQUE_LOCAL_ADDR1) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn get_source_address_empty_interface(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + + iface.update_ip_addrs(|ips| ips.clear()); + + // List of addresses we test: + // 172.18.1.254 -> None + // 172.24.24.12 -> None + // 172.24.23.254 -> None + const UNIQUE_LOCAL_ADDR1: Ipv4Address = Ipv4Address::new(172, 18, 1, 254); + const UNIQUE_LOCAL_ADDR2: Ipv4Address = Ipv4Address::new(172, 24, 24, 12); + const UNIQUE_LOCAL_ADDR3: Ipv4Address = Ipv4Address::new(172, 24, 23, 254); + + assert_eq!( + iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR1), + None + ); + assert_eq!( + iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR2), + None + ); + assert_eq!( + iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR3), + None + ); +} + +use crate::wire::ipv4::HEADER_LEN; +#[rstest] +#[cfg(all(feature = "medium-ip", feature = "proto-ipv4-fragmentation",))] +fn test_ipv4_fragment_size() { + let (_, _, device) = setup(Medium::Ip); + let caps = device.capabilities(); + for i in 0..IPV4_FRAGMENT_PAYLOAD_ALIGNMENT { + assert!(caps.max_ipv4_fragment_size(HEADER_LEN + i) % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT == 0); + } +} diff --git a/vendor/smoltcp/src/iface/interface/tests/ipv6.rs b/vendor/smoltcp/src/iface/interface/tests/ipv6.rs new file mode 100644 index 00000000..427cb6ec --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/tests/ipv6.rs @@ -0,0 +1,1868 @@ +use super::*; + +fn parse_ipv6(data: &[u8]) -> crate::wire::Result> { + let ipv6_header = Ipv6Packet::new_checked(data)?; + let ipv6 = Ipv6Repr::parse(&ipv6_header)?; + + match ipv6.next_header { + IpProtocol::HopByHop => todo!(), + IpProtocol::Icmp => todo!(), + IpProtocol::Igmp => todo!(), + IpProtocol::Tcp => todo!(), + IpProtocol::Udp => todo!(), + IpProtocol::Ipv6Route => todo!(), + IpProtocol::Ipv6Frag => todo!(), + IpProtocol::IpSecEsp => todo!(), + IpProtocol::IpSecAh => todo!(), + IpProtocol::Icmpv6 => { + let icmp = Icmpv6Repr::parse( + &ipv6.src_addr, + &ipv6.dst_addr, + &Icmpv6Packet::new_checked(ipv6_header.payload())?, + &Default::default(), + )?; + Ok(Packet::new_ipv6(ipv6, IpPayload::Icmpv6(icmp))) + } + IpProtocol::Ipv6NoNxt => todo!(), + IpProtocol::Ipv6Opts => todo!(), + IpProtocol::Unknown(_) => todo!(), + } +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn any_ip(#[case] medium: Medium) { + // An empty echo request with destination address fdbe::3, which is not part of the interface + // address list. + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x8, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x3, 0x80, 0x0, 0x84, 0x3a, 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0003), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 8, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoRequest { + ident: 0, + seq_no: 0, + data: b"", + }) + )) + ); + + let (mut iface, mut sockets, _device) = setup(medium); + + // Add a route to the interface, otherwise, we don't know if the packet is routed localy. + iface.routes_mut().update(|routes| { + routes + .push(crate::iface::Route { + cidr: IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0), + 64, + )), + via_router: IpAddress::Ipv6(Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001)), + preferred_until: None, + expires_at: None, + }) + .unwrap(); + }); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + None + ); + + // Accept any IP: + iface.set_any_ip(true); + assert!( + iface + .inner + .process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ) + .is_some() + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn multicast_source_address(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, + ]; + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn hop_by_hop_skip_with_icmp(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (skipped) + // - ICMP echo request + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0xf, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 42, + seq_no: 420, + data: b"Lorem Ipsum", + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn hop_by_hop_discard_with_icmp(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (discard) + // - ICMP echo request + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x1, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +fn hop_by_hop_discard_param_problem(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (discard + ParamProblem) + // - ICMP echo request + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Icmpv6, + payload_len: 75, + hop_limit: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedOption, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1), + next_header: IpProtocol::HopByHop, + payload_len: 27, + hop_limit: 64, + }, + data: &[ + 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, + 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +fn hop_by_hop_discard_with_multicast(#[case] medium: Medium) { + // The following contains: + // - IPv6 header + // - Hop-by-hop, with options: + // - PADN (skipped) + // - Unknown option (discard (0b11) + ParamProblem) + // - ICMP echo request + // + // In this case, even if the destination address is a multicast address, an ICMPv6 ParamProblem + // should be transmitted. + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, + 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Icmpv6, + payload_len: 75, + hop_limit: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedOption, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1), + next_header: IpProtocol::HopByHop, + payload_len: 27, + hop_limit: 64, + }, + data: &[ + 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, + 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn imcp_empty_echo_request(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x8, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x84, 0x3c, 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 8, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoRequest { + ident: 0, + seq_no: 0, + data: b"", + }) + )) + ); + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 8, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 0, + seq_no: 0, + data: b"", + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn icmp_echo_request(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, + 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoRequest { + ident: 42, + seq_no: 420, + data: b"Lorem Ipsum", + }) + )) + ); + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 42, + seq_no: 420, + data: b"Lorem Ipsum", + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn icmp_echo_reply_as_input(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x13, 0x3a, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x81, 0x0, 0x2d, 0x56, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x6f, 0x72, 0x65, + 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 19, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 0, + seq_no: 0, + data: b"Lorem Ipsum", + }) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 48, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedNxtHdr, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 64, + next_header: IpProtocol::Unknown(0x0c), + payload_len: 0, + }, + data: &[], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn unknown_proto(#[case] medium: Medium) { + // Since the destination address is multicast, we should answer with an ICMPv6 message. + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 48, + }, + IpPayload::Icmpv6(Icmpv6Repr::ParamProblem { + reason: Icmpv6ParamProblem::UnrecognizedNxtHdr, + pointer: 40, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 64, + next_header: IpProtocol::Unknown(0x0c), + payload_len: 0, + }, + data: &[], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); +} + +#[rstest] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn ndisc_neighbor_advertisement_ethernet(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x9f, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 255, + next_header: IpProtocol::Icmpv6, + payload_len: 32, + }, + IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x0002), + lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 1])), + })) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); + + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002)), + iface.inner.now, + ), + NeighborAnswer::Found(HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[ + 0, 0, 0, 0, 0, 1 + ]))), + ); +} + +#[rstest] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn ndisc_neighbor_advertisement_ethernet_multicast_addr(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0xa0, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x1, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 255, + next_header: IpProtocol::Icmpv6, + payload_len: 32, + }, + IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x0002), + lladdr: Some(RawHardwareAddress::from_bytes(&[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + ])), + })) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); + + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002)), + iface.inner.now, + ), + NeighborAnswer::NotFound, + ); +} + +#[rstest] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn ndisc_neighbor_advertisement_ieee802154(#[case] medium: Medium) { + let data = [ + 0x60, 0x0, 0x0, 0x0, 0x0, 0x28, 0x3a, 0xff, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x1, 0x88, 0x0, 0x3b, 0x96, 0x40, 0x0, 0x0, 0x0, 0xfe, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + assert_eq!( + parse_ipv6(&data), + Ok(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002), + dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0001), + hop_limit: 255, + next_header: IpProtocol::Icmpv6, + payload_len: 40, + }, + IpPayload::Icmpv6(Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x0002), + lladdr: Some(RawHardwareAddress::from_bytes(&[0, 0, 0, 0, 0, 0, 0, 1])), + })) + )) + ); + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ipv6( + &mut sockets, + PacketMeta::default(), + HardwareAddress::default(), + &Ipv6Packet::new_checked(&data[..]).unwrap() + ), + response + ); + + assert_eq!( + iface.inner.neighbor_cache.lookup( + &IpAddress::Ipv6(Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002)), + iface.inner.now, + ), + NeighborAnswer::Found(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes( + &[0, 0, 0, 0, 0, 0, 0, 1] + ))), + ); +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_handle_valid_ndisc_request(#[case] medium: Medium) { + let (mut iface, mut sockets, _device) = setup(medium); + + let mut eth_bytes = vec![0u8; 86]; + + let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1); + let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2); + let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + + let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit { + target_addr: local_ip_addr, + lladdr: Some(remote_hw_addr.into()), + }); + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: remote_ip_addr, + dst_addr: local_ip_addr.solicited_node(), + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: solicit.buffer_len(), + }); + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00])); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + solicit.emit( + &remote_ip_addr, + &local_ip_addr.solicited_node(), + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert { + flags: NdiscNeighborFlags::SOLICITED, + target_addr: local_ip_addr, + lladdr: Some(local_hw_addr.into()), + }); + + let ipv6_expected = Ipv6Repr { + src_addr: local_ip_addr, + dst_addr: remote_ip_addr, + next_header: IpProtocol::Icmpv6, + hop_limit: 0xff, + payload_len: icmpv6_expected.buffer_len(), + }; + + // Ensure an Neighbor Solicitation triggers a Neighbor Advertisement + assert_eq!( + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments + ), + Some(EthernetPacket::Ip(Packet::new_ipv6( + ipv6_expected, + IpPayload::Icmpv6(icmpv6_expected) + ))) + ); + + // Ensure the address of the requester was entered in the cache + assert_eq!( + iface.inner.lookup_hardware_addr( + MockTxToken, + &IpAddress::Ipv6(remote_ip_addr), + &mut iface.fragmenter, + ), + Ok((HardwareAddress::Ethernet(remote_hw_addr), MockTxToken)) + ); +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(feature = "proto-ipv6-slaac")] +fn test_router_advertisement(#[case] medium: Medium) { + fn recv_icmpv6( + device: &mut crate::tests::TestingDevice, + timestamp: Instant, + ) -> std::vec::Vec>> { + let caps = device.capabilities(); + recv_all(device, timestamp) + .iter() + .filter_map(|frame| { + let ipv6_packet = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let eth_frame = EthernetFrame::new_checked(frame).ok()?; + Ipv6Packet::new_checked(eth_frame.payload()).ok()? + } + #[cfg(feature = "medium-ip")] + Medium::Ip => Ipv6Packet::new_checked(&frame[..]).ok()?, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + let buf = ipv6_packet.into_inner().to_vec(); + Some(Ipv6Packet::new_unchecked(buf)) + }) + .collect::>() + } + let prefix_addr = Ipv6Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 0); + + let mut device = crate::tests::TestingDevice::new(medium); + let caps = device.capabilities(); + let checksum_caps = &caps.checksum; + + let mut eth_bytes = vec![0u8; 102]; + + // Create mac addresses with derived link local addresses + let local_hw_addr = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + let ll_prefix = Ipv6Cidr::new(Ipv6Cidr::LINK_LOCAL_PREFIX.address(), 64); + let local_ip_addr = + Ipv6Cidr::from_link_prefix(&ll_prefix, HardwareAddress::Ethernet(local_hw_addr)).unwrap(); + let remote_ip_addr = + Ipv6Cidr::from_link_prefix(&ll_prefix, HardwareAddress::Ethernet(remote_hw_addr)).unwrap(); + + // Create config with slaac enabled + let mut config = Config::new(match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => HardwareAddress::Ethernet(local_hw_addr), + _ => panic!("Not supported"), + }); + config.slaac = true; + + // Set up interface with link local address + let mut iface = Interface::new(config, &mut device, Instant::ZERO); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.push(IpCidr::Ipv6(local_ip_addr)).unwrap(); + }); + + let mut sockets = SocketSet::new(vec![]); + iface.poll(Instant::ZERO, &mut device, &mut sockets); + + let transmitted: std::vec::Vec>> = + recv_icmpv6(&mut device, Instant::ZERO) + .into_iter() + .filter(|packet| { + // Filter for router solicitations + packet.dst_addr() == IPV6_LINK_LOCAL_ALL_ROUTERS + }) + .collect(); + + assert_eq!(transmitted.len(), 1); + + for ipv6_packet in transmitted.into_iter() { + let buf = ipv6_packet.into_inner(); + let ipv6_packet = Ipv6Packet::new_unchecked(buf.as_slice()); + let ipv6_repr = Ipv6Repr::parse(&ipv6_packet).unwrap(); + if ipv6_repr.dst_addr == IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS { + continue; // Skip MLD reports + } + let icmpv6_packet = Icmpv6Packet::new_checked(ipv6_packet.payload()).unwrap(); + let icmp_repr = Icmpv6Repr::parse( + &ipv6_repr.src_addr, + &ipv6_repr.dst_addr, + &icmpv6_packet, + checksum_caps, + ) + .unwrap(); + + assert_eq!( + icmp_repr, + Icmpv6Repr::Ndisc(NdiscRepr::RouterSolicit { + lladdr: Some(local_hw_addr.into()), + }) + ); + + assert_eq!(ipv6_repr.dst_addr, IPV6_LINK_LOCAL_ALL_ROUTERS); + println!("repr {:?}", icmp_repr); + } + + // Craft the router advertisement + let mut prefix_information = NdiscPrefixInformation { + prefix: prefix_addr, + prefix_len: 64, + flags: NdiscPrefixInfoFlags::ADDRCONF, + valid_lifetime: Duration::from_secs(600), + preferred_lifetime: Duration::from_secs(300), + }; + let mut advertisement = NdiscRepr::RouterAdvert { + hop_limit: 255, + flags: NdiscRouterFlags::empty(), + router_lifetime: Duration::from_secs(600), + reachable_time: Duration::from_secs(0), + retrans_time: Duration::from_secs(0), + lladdr: None, + mtu: None, + prefix_info: Some(prefix_information), + }; + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: remote_ip_addr.address(), + dst_addr: local_ip_addr.address(), + next_header: IpProtocol::Icmpv6, + hop_limit: 255, + payload_len: advertisement.buffer_len(), + }); + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(local_hw_addr); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + Icmpv6Repr::Ndisc(advertisement).emit( + &remote_ip_addr.address(), + &local_ip_addr.address(), + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments, + ); + + iface.poll(Instant::ZERO, &mut device, &mut sockets); + + // Expect to have these two addresses after the router advertisement + let expected_addrs = [ + IpCidr::Ipv6(local_ip_addr), + IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0x2001, 0xdb8, 0x3, 0x0, 0x2, 0x2ff, 0xfe02, 0x202), + 64, + )), + ]; + for (generated, expected) in iface.ip_addrs().iter().zip(expected_addrs.iter()) { + assert_eq!(generated, expected); + } + // Verify the pushed route matches expected + iface.routes_mut().update(|route| { + assert_eq!(route.len(), 1); + assert_eq!( + route[0].cidr, + IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), 0) + ); + assert_eq!( + route[0].via_router, + IpAddress::Ipv6(remote_ip_addr.address()) + ); + assert_eq!(route[0].preferred_until, None); + assert_eq!(route[0].expires_at, None); + }); + + // Craft a router advertisement with zero lifetime for the prefix + // to remove the prefix, but retain the route + prefix_information.valid_lifetime = Duration::ZERO; + prefix_information.preferred_lifetime = Duration::ZERO; + if let NdiscRepr::RouterAdvert { + ref mut prefix_info, + .. + } = advertisement + { + *prefix_info = Some(prefix_information); + } + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(local_hw_addr); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + Icmpv6Repr::Ndisc(advertisement).emit( + &remote_ip_addr.address(), + &local_ip_addr.address(), + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments, + ); + + let now = Instant::from_secs(10); + + iface.poll(now, &mut device, &mut sockets); + assert_eq!(iface.ip_addrs().len(), 1); + iface.routes_mut().update(|route| { + assert_eq!(route.len(), 1); + }); + + // Craft router advertisement with zero router lifetime + // to remove the route + if let NdiscRepr::RouterAdvert { + ref mut prefix_info, + ref mut router_lifetime, + .. + } = advertisement + { + *prefix_info = None; + *router_lifetime = Duration::ZERO; + } + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(local_hw_addr); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + Icmpv6Repr::Ndisc(advertisement).emit( + &remote_ip_addr.address(), + &local_ip_addr.address(), + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments, + ); + + let now = Instant::from_secs(20); + iface.poll(now, &mut device, &mut sockets); + iface.routes_mut().update(|route| { + assert_eq!(route.len(), 0); + }); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn test_solicited_node_addrs(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let mut new_addrs = heapless::Vec::::new(); + new_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 1, 2, 0, 2), 64)) + .unwrap(); + new_addrs + .push(IpCidr::new( + IpAddress::v6(0xfe80, 0, 0, 0, 3, 4, 0, 0xffff), + 64, + )) + .unwrap(); + iface.update_ip_addrs(|addrs| { + new_addrs.extend(addrs.to_vec()); + *addrs = new_addrs; + }); + assert!( + iface + .inner + .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)) + ); + assert!( + iface + .inner + .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)) + ); + assert!( + !iface + .inner + .has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0003)) + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(all(feature = "socket-udp", feature = "medium-ip"))] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "socket-udp", feature = "medium-ethernet"))] +#[case(Medium::Ieee802154)] +#[cfg(all(feature = "socket-udp", feature = "medium-ieee802154"))] +fn test_icmp_reply_size(#[case] medium: Medium) { + use crate::wire::IPV6_MIN_MTU as MIN_MTU; + use crate::wire::Icmpv6DstUnreachable; + const MAX_PAYLOAD_LEN: usize = 1192; + + let (mut iface, mut sockets, _device) = setup(medium); + + let src_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + let dst_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + + // UDP packet that if not tructated will cause a icmp port unreachable reply + // to exceed the minimum mtu bytes in length. + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + let mut bytes = vec![0xff; udp_repr.header_len() + MAX_PAYLOAD_LEN]; + let mut packet = UdpPacket::new_unchecked(&mut bytes[..]); + udp_repr.emit( + &mut packet, + &src_addr.into(), + &dst_addr.into(), + MAX_PAYLOAD_LEN, + |buf| fill_slice(buf, 0x2a), + &ChecksumCapabilities::default(), + ); + + let ip_repr = Ipv6Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Udp, + hop_limit: 64, + payload_len: udp_repr.header_len() + MAX_PAYLOAD_LEN, + }; + let payload = packet.into_inner(); + + let expected_icmp_repr = Icmpv6Repr::DstUnreachable { + reason: Icmpv6DstUnreachable::PortUnreachable, + header: ip_repr, + data: &payload[..MAX_PAYLOAD_LEN], + }; + + let expected_ip_repr = Ipv6Repr { + src_addr: dst_addr, + dst_addr: src_addr, + next_header: IpProtocol::Icmpv6, + hop_limit: 64, + payload_len: expected_icmp_repr.buffer_len(), + }; + + assert_eq!( + expected_ip_repr.buffer_len() + expected_icmp_repr.buffer_len(), + MIN_MTU + ); + + assert_eq!( + iface.inner.process_udp( + &mut sockets, + PacketMeta::default(), + false, + ip_repr.into(), + payload, + ), + Some(Packet::new_ipv6( + expected_ip_repr, + IpPayload::Icmpv6(expected_icmp_repr) + )) + ); +} + +#[cfg(feature = "medium-ip")] +#[test] +fn get_source_address() { + let (mut iface, _, _) = setup(Medium::Ip); + + const OWN_LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const OWN_UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 2); + const OWN_UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 2); + const OWN_GLOBAL_UNICAST_ADDR1: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 1); + + // List of addresses of the interface: + // fe80::1/64 + // fd00::201:1:1:1:2/64 + // fd01::201:1:1:1:2/64 + // 2001:db8:3::1/64 + iface.update_ip_addrs(|addrs| { + addrs.clear(); + + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_LINK_LOCAL_ADDR, 64))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR1, 64))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_UNIQUE_LOCAL_ADDR2, 64))) + .unwrap(); + addrs + .push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_GLOBAL_UNICAST_ADDR1, 64))) + .unwrap(); + }); + + // List of addresses we test: + // ::1 -> ::1 + // fe80::42 -> fe80::1 + // fd00::201:1:1:1:1 -> fd00::201:1:1:1:2 + // fd01::201:1:1:1:1 -> fd01::201:1:1:1:2 + // fd02::201:1:1:1:1 -> fd00::201:1:1:1:2 (because first added in the list) + // fd01::201:1:1:1:3 -> fd01::201:1:1:1:2 (because in same subnet) + // ff02::1 -> fe80::1 (same scope) + // 2001:db8:3::2 -> 2001:db8:3::1 + // 2001:db9:3::2 -> 2001:db8:3::1 + const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42); + const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR4: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 3); + const GLOBAL_UNICAST_ADDR1: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2); + const GLOBAL_UNICAST_ADDR2: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db9, 0x0003, 0, 0, 0, 0, 2); + + assert_eq!( + iface.inner.get_source_address_ipv6(&Ipv6Address::LOCALHOST), + Ipv6Address::LOCALHOST + ); + + assert_eq!( + iface.inner.get_source_address_ipv6(&LINK_LOCAL_ADDR), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + OWN_UNIQUE_LOCAL_ADDR1 + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + OWN_UNIQUE_LOCAL_ADDR2 + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + OWN_UNIQUE_LOCAL_ADDR1 + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR4), + OWN_UNIQUE_LOCAL_ADDR2 + ); + assert_eq!( + iface + .inner + .get_source_address_ipv6(&IPV6_LINK_LOCAL_ALL_NODES), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + OWN_GLOBAL_UNICAST_ADDR1 + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + OWN_GLOBAL_UNICAST_ADDR1 + ); + + assert_eq!( + iface.get_source_address_ipv6(&LINK_LOCAL_ADDR), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + OWN_UNIQUE_LOCAL_ADDR1 + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + OWN_UNIQUE_LOCAL_ADDR2 + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + OWN_UNIQUE_LOCAL_ADDR1 + ); + assert_eq!( + iface.get_source_address_ipv6(&IPV6_LINK_LOCAL_ALL_NODES), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + OWN_GLOBAL_UNICAST_ADDR1 + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + OWN_GLOBAL_UNICAST_ADDR1 + ); +} + +#[cfg(feature = "medium-ip")] +#[test] +fn get_source_address_only_link_local() { + let (mut iface, _, _) = setup(Medium::Ip); + + // List of addresses in the interface: + // fe80::1/64 + const OWN_LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + iface.update_ip_addrs(|ips| { + ips.clear(); + ips.push(IpCidr::Ipv6(Ipv6Cidr::new(OWN_LINK_LOCAL_ADDR, 64))) + .unwrap(); + }); + + // List of addresses we test: + // ::1 -> ::1 + // fe80::42 -> fe80::1 + // fd00::201:1:1:1:1 -> fe80::1 + // fd01::201:1:1:1:1 -> fe80::1 + // fd02::201:1:1:1:1 -> fe80::1 + // ff02::1 -> fe80::1 + // 2001:db8:3::2 -> fe80::1 + // 2001:db9:3::2 -> fe80::1 + const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42); + const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1); + const GLOBAL_UNICAST_ADDR1: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2); + const GLOBAL_UNICAST_ADDR2: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db9, 0x0003, 0, 0, 0, 0, 2); + + assert_eq!( + iface.inner.get_source_address_ipv6(&Ipv6Address::LOCALHOST), + Ipv6Address::LOCALHOST + ); + + assert_eq!( + iface.inner.get_source_address_ipv6(&LINK_LOCAL_ADDR), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface + .inner + .get_source_address_ipv6(&IPV6_LINK_LOCAL_ALL_NODES), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + OWN_LINK_LOCAL_ADDR + ); + + assert_eq!( + iface.get_source_address_ipv6(&LINK_LOCAL_ADDR), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&IPV6_LINK_LOCAL_ALL_NODES), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + OWN_LINK_LOCAL_ADDR + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + OWN_LINK_LOCAL_ADDR + ); +} + +#[cfg(feature = "medium-ip")] +#[test] +fn get_source_address_empty_interface() { + let (mut iface, _, _) = setup(Medium::Ip); + + iface.update_ip_addrs(|ips| ips.clear()); + + // List of addresses we test: + // ::1 -> ::1 + // fe80::42 -> ::1 + // fd00::201:1:1:1:1 -> ::1 + // fd01::201:1:1:1:1 -> ::1 + // fd02::201:1:1:1:1 -> ::1 + // ff02::1 -> ::1 + // 2001:db8:3::2 -> ::1 + // 2001:db9:3::2 -> ::1 + const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42); + const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1); + const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1); + const GLOBAL_UNICAST_ADDR1: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2); + const GLOBAL_UNICAST_ADDR2: Ipv6Address = + Ipv6Address::new(0x2001, 0x0db9, 0x0003, 0, 0, 0, 0, 2); + + assert_eq!( + iface.inner.get_source_address_ipv6(&Ipv6Address::LOCALHOST), + Ipv6Address::LOCALHOST + ); + + assert_eq!( + iface.inner.get_source_address_ipv6(&LINK_LOCAL_ADDR), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface + .inner + .get_source_address_ipv6(&IPV6_LINK_LOCAL_ALL_NODES), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.inner.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + Ipv6Address::LOCALHOST + ); + + assert_eq!( + iface.get_source_address_ipv6(&LINK_LOCAL_ADDR), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR1), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR2), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.get_source_address_ipv6(&IPV6_LINK_LOCAL_ALL_NODES), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR1), + Ipv6Address::LOCALHOST + ); + assert_eq!( + iface.get_source_address_ipv6(&GLOBAL_UNICAST_ADDR2), + Ipv6Address::LOCALHOST + ); +} + +#[rstest] +#[case(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +fn test_join_ipv6_multicast_group(#[case] medium: Medium) { + fn recv_icmpv6( + device: &mut crate::tests::TestingDevice, + timestamp: Instant, + ) -> std::vec::Vec>> { + let caps = device.capabilities(); + recv_all(device, timestamp) + .iter() + .filter_map(|frame| { + let ipv6_packet = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let eth_frame = EthernetFrame::new_checked(frame).ok()?; + Ipv6Packet::new_checked(eth_frame.payload()).ok()? + } + #[cfg(feature = "medium-ip")] + Medium::Ip => Ipv6Packet::new_checked(&frame[..]).ok()?, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + let buf = ipv6_packet.into_inner().to_vec(); + Some(Ipv6Packet::new_unchecked(buf)) + }) + .collect::>() + } + + let (mut iface, mut sockets, mut device) = setup(medium); + + let groups = [ + Ipv6Address::new(0xff05, 0, 0, 0, 0, 0, 0, 0x00fb), + Ipv6Address::new(0xff0e, 0, 0, 0, 0, 0, 0, 0x0017), + ]; + + let timestamp = Instant::from_millis(0); + + // Drain the unsolicited node multicast report from the device + iface.poll(timestamp, &mut device, &mut sockets); + let _ = recv_icmpv6(&mut device, timestamp); + + for &group in &groups { + iface.join_multicast_group(group).unwrap(); + assert!(iface.has_multicast_group(group)); + } + assert!(iface.has_multicast_group(IPV6_LINK_LOCAL_ALL_NODES)); + iface.poll(timestamp, &mut device, &mut sockets); + assert!(iface.has_multicast_group(IPV6_LINK_LOCAL_ALL_NODES)); + + let reports = recv_icmpv6(&mut device, timestamp); + assert_eq!(reports.len(), 2); + + let caps = device.capabilities(); + let checksum_caps = &caps.checksum; + for (&group_addr, ipv6_packet) in groups.iter().zip(reports) { + let buf = ipv6_packet.into_inner(); + let ipv6_packet = Ipv6Packet::new_unchecked(buf.as_slice()); + + let _ipv6_repr = Ipv6Repr::parse(&ipv6_packet).unwrap(); + let ip_payload = ipv6_packet.payload(); + + // The first 2 octets of this payload hold the next-header indicator and the + // Hop-by-Hop header length (in 8-octet words, minus 1). The remaining 6 octets + // hold the Hop-by-Hop PadN and Router Alert options. + let hbh_header = Ipv6HopByHopHeader::new_checked(&ip_payload[..8]).unwrap(); + let hbh_repr = Ipv6HopByHopRepr::parse(&hbh_header).unwrap(); + + assert_eq!(hbh_repr.options.len(), 3); + assert_eq!( + hbh_repr.options[0], + Ipv6OptionRepr::Unknown { + type_: Ipv6OptionType::Unknown(IpProtocol::Icmpv6.into()), + length: 0, + data: &[], + } + ); + assert_eq!( + hbh_repr.options[1], + Ipv6OptionRepr::RouterAlert(Ipv6OptionRouterAlert::MulticastListenerDiscovery) + ); + assert_eq!(hbh_repr.options[2], Ipv6OptionRepr::PadN(0)); + + let icmpv6_packet = + Icmpv6Packet::new_checked(&ip_payload[hbh_repr.buffer_len()..]).unwrap(); + let icmpv6_repr = Icmpv6Repr::parse( + &ipv6_packet.src_addr(), + &ipv6_packet.dst_addr(), + &icmpv6_packet, + checksum_caps, + ) + .unwrap(); + + let record_data = match icmpv6_repr { + Icmpv6Repr::Mld(MldRepr::Report { + nr_mcast_addr_rcrds, + data, + }) => { + assert_eq!(nr_mcast_addr_rcrds, 1); + data + } + other => panic!("unexpected icmpv6_repr: {:?}", other), + }; + + let record = MldAddressRecord::new_checked(record_data).unwrap(); + let record_repr = MldAddressRecordRepr::parse(&record).unwrap(); + + assert_eq!( + record_repr, + MldAddressRecordRepr { + num_srcs: 0, + mcast_addr: group_addr, + record_type: MldRecordType::ChangeToInclude, + aux_data_len: 0, + payload: &[], + } + ); + + if !group_addr.is_solicited_node_multicast() { + iface.leave_multicast_group(group_addr).unwrap(); + assert!(!iface.has_multicast_group(group_addr)); + iface.poll(timestamp, &mut device, &mut sockets); + assert!(!iface.has_multicast_group(group_addr)); + } + } +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "multicast", feature = "medium-ethernet"))] +fn test_handle_valid_multicast_query(#[case] medium: Medium) { + fn recv_icmpv6( + device: &mut crate::tests::TestingDevice, + timestamp: Instant, + ) -> std::vec::Vec>> { + let caps = device.capabilities(); + recv_all(device, timestamp) + .iter() + .filter_map(|frame| { + let ipv6_packet = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + let eth_frame = EthernetFrame::new_checked(frame).ok()?; + Ipv6Packet::new_checked(eth_frame.payload()).ok()? + } + #[cfg(feature = "medium-ip")] + Medium::Ip => Ipv6Packet::new_checked(&frame[..]).ok()?, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + let buf = ipv6_packet.into_inner().to_vec(); + Some(Ipv6Packet::new_unchecked(buf)) + }) + .collect::>() + } + + let (mut iface, mut sockets, mut device) = setup(medium); + + let mut timestamp = Instant::ZERO; + + let mut eth_bytes = vec![0u8; 86]; + + let local_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + let remote_ip_addr = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 100); + let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]); + let query_ip_addr = Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1234); + + iface.join_multicast_group(query_ip_addr).unwrap(); + + iface.poll(timestamp, &mut device, &mut sockets); + // flush multicast reports from the join_multicast_group calls + recv_icmpv6(&mut device, timestamp); + + let queries = [ + // General query, expect both multicast addresses back + ( + Ipv6Address::UNSPECIFIED, + IPV6_LINK_LOCAL_ALL_NODES, + vec![local_ip_addr.solicited_node(), query_ip_addr], + ), + // Address specific query, expect only the queried address back + (query_ip_addr, query_ip_addr, vec![query_ip_addr]), + ]; + + for (mcast_query, address, _results) in queries.iter() { + let query = Icmpv6Repr::Mld(MldRepr::Query { + max_resp_code: 1000, + mcast_addr: *mcast_query, + s_flag: false, + qrv: 1, + qqic: 60, + num_srcs: 0, + data: &[0, 0, 0, 0], + }); + + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: remote_ip_addr, + dst_addr: *address, + next_header: IpProtocol::Icmpv6, + hop_limit: 1, + payload_len: query.buffer_len(), + }); + + let mut frame = EthernetFrame::new_unchecked(&mut eth_bytes); + frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00])); + frame.set_src_addr(remote_hw_addr); + frame.set_ethertype(EthernetProtocol::Ipv6); + ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default()); + query.emit( + &remote_ip_addr, + address, + &mut Icmpv6Packet::new_unchecked(&mut frame.payload_mut()[ip_repr.header_len()..]), + &ChecksumCapabilities::default(), + ); + + iface.inner.process_ethernet( + &mut sockets, + PacketMeta::default(), + frame.into_inner(), + &mut iface.fragments, + ); + + timestamp += crate::time::Duration::from_millis(1000); + iface.poll(timestamp, &mut device, &mut sockets); + } + + let reports = recv_icmpv6(&mut device, timestamp); + assert_eq!(reports.len(), queries.len()); + + let caps = device.capabilities(); + let checksum_caps = &caps.checksum; + for ((_mcast_query, _address, results), ipv6_packet) in queries.iter().zip(reports) { + let buf = ipv6_packet.into_inner(); + let ipv6_packet = Ipv6Packet::new_unchecked(buf.as_slice()); + + let ipv6_repr = Ipv6Repr::parse(&ipv6_packet).unwrap(); + let ip_payload = ipv6_packet.payload(); + assert_eq!(ipv6_repr.dst_addr, IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS); + + // The first 2 octets of this payload hold the next-header indicator and the + // Hop-by-Hop header length (in 8-octet words, minus 1). The remaining 6 octets + // hold the Hop-by-Hop PadN and Router Alert options. + let hbh_header = Ipv6HopByHopHeader::new_checked(&ip_payload[..8]).unwrap(); + let hbh_repr = Ipv6HopByHopRepr::parse(&hbh_header).unwrap(); + + assert_eq!(hbh_repr.options.len(), 3); + assert_eq!( + hbh_repr.options[0], + Ipv6OptionRepr::Unknown { + type_: Ipv6OptionType::Unknown(IpProtocol::Icmpv6.into()), + length: 0, + data: &[], + } + ); + assert_eq!( + hbh_repr.options[1], + Ipv6OptionRepr::RouterAlert(Ipv6OptionRouterAlert::MulticastListenerDiscovery) + ); + assert_eq!(hbh_repr.options[2], Ipv6OptionRepr::PadN(0)); + + let icmpv6_packet = + Icmpv6Packet::new_checked(&ip_payload[hbh_repr.buffer_len()..]).unwrap(); + let icmpv6_repr = Icmpv6Repr::parse( + &ipv6_packet.src_addr(), + &ipv6_packet.dst_addr(), + &icmpv6_packet, + checksum_caps, + ) + .unwrap(); + + let record_data = match icmpv6_repr { + Icmpv6Repr::Mld(MldRepr::Report { + nr_mcast_addr_rcrds, + data, + }) => { + assert_eq!(nr_mcast_addr_rcrds, results.len() as u16); + data + } + other => panic!("unexpected icmpv6_repr: {:?}", other), + }; + + let mut record_reprs = Vec::new(); + let mut payload = record_data; + + // FIXME: parsing multiple address records should be done by the MLD code + while !payload.is_empty() { + let record = MldAddressRecord::new_checked(payload).unwrap(); + let mut record_repr = MldAddressRecordRepr::parse(&record).unwrap(); + payload = record_repr.payload; + record_repr.payload = &[]; + record_reprs.push(record_repr); + } + + let expected_records = results + .iter() + .map(|addr| MldAddressRecordRepr { + num_srcs: 0, + mcast_addr: *addr, + record_type: MldRecordType::ModeIsExclude, + aux_data_len: 0, + payload: &[], + }) + .collect::>(); + + assert_eq!(record_reprs, expected_records); + } +} + +#[rstest] +#[case(Medium::Ethernet)] +#[cfg(all(feature = "multicast", feature = "medium-ethernet"))] +fn test_solicited_node_multicast_autojoin(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + + let addr1 = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + let addr2 = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.clear(); + ip_addrs.push(IpCidr::new(addr1.into(), 64)).unwrap(); + }); + assert!(iface.has_multicast_group(addr1.solicited_node())); + assert!(!iface.has_multicast_group(addr2.solicited_node())); + + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.clear(); + ip_addrs.push(IpCidr::new(addr2.into(), 64)).unwrap(); + }); + assert!(!iface.has_multicast_group(addr1.solicited_node())); + assert!(iface.has_multicast_group(addr2.solicited_node())); + + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.clear(); + ip_addrs.push(IpCidr::new(addr1.into(), 64)).unwrap(); + ip_addrs.push(IpCidr::new(addr2.into(), 64)).unwrap(); + }); + assert!(iface.has_multicast_group(addr1.solicited_node())); + assert!(iface.has_multicast_group(addr2.solicited_node())); + + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.clear(); + }); + assert!(!iface.has_multicast_group(addr1.solicited_node())); + assert!(!iface.has_multicast_group(addr2.solicited_node())); +} diff --git a/vendor/smoltcp/src/iface/interface/tests/mod.rs b/vendor/smoltcp/src/iface/interface/tests/mod.rs new file mode 100644 index 00000000..907b22bb --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/tests/mod.rs @@ -0,0 +1,247 @@ +#[cfg(feature = "proto-ipv4")] +mod ipv4; +#[cfg(feature = "proto-ipv6")] +mod ipv6; +#[cfg(feature = "proto-sixlowpan")] +mod sixlowpan; + +#[allow(unused)] +use std::vec::Vec; + +use crate::tests::setup; + +use rstest::*; + +use super::*; + +use crate::iface::Interface; +use crate::phy::ChecksumCapabilities; +#[cfg(feature = "alloc")] +use crate::phy::Loopback; +use crate::time::Instant; + +#[allow(unused)] +fn fill_slice(s: &mut [u8], val: u8) { + for x in s.iter_mut() { + *x = val + } +} + +#[allow(unused)] +fn recv_all(device: &mut crate::tests::TestingDevice, timestamp: Instant) -> Vec> { + let mut pkts = Vec::new(); + while let Some(pkt) = device.tx_queue.pop_front() { + pkts.push(pkt) + } + pkts +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct MockTxToken; + +impl TxToken for MockTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut junk = [0; 1536]; + f(&mut junk[..len]) + } +} + +#[test] +#[should_panic(expected = "The hardware address does not match the medium of the interface.")] +#[cfg(all(feature = "medium-ip", feature = "medium-ethernet", feature = "alloc"))] +fn test_new_panic() { + let mut device = Loopback::new(Medium::Ethernet); + let config = Config::new(HardwareAddress::Ip); + Interface::new(config, &mut device, Instant::ZERO); +} + +#[cfg(feature = "socket-udp")] +#[rstest] +#[case::ip(Medium::Ip)] +#[cfg(feature = "medium-ip")] +#[case::ethernet(Medium::Ethernet)] +#[cfg(feature = "medium-ethernet")] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn test_handle_udp_broadcast(#[case] medium: Medium) { + use crate::socket::udp; + use crate::wire::IpEndpoint; + + static UDP_PAYLOAD: [u8; 5] = [0x48, 0x65, 0x6c, 0x6c, 0x6f]; + + let (mut iface, mut sockets, _device) = setup(medium); + + let rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]); + let tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 15]); + + let udp_socket = udp::Socket::new(rx_buffer, tx_buffer); + + let mut udp_bytes = vec![0u8; 13]; + let mut packet = UdpPacket::new_unchecked(&mut udp_bytes); + + let socket_handle = sockets.add(udp_socket); + + #[cfg(feature = "proto-ipv6")] + let src_ip = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] + let src_ip = Ipv4Address::new(0x7f, 0x00, 0x00, 0x02); + + let udp_repr = UdpRepr { + src_port: 67, + dst_port: 68, + }; + + #[cfg(feature = "proto-ipv6")] + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: src_ip, + dst_addr: IPV6_LINK_LOCAL_ALL_NODES, + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 0x40, + }); + #[cfg(all(not(feature = "proto-ipv6"), feature = "proto-ipv4"))] + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: src_ip, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: udp_repr.header_len() + UDP_PAYLOAD.len(), + hop_limit: 0x40, + }); + let dst_addr = ip_repr.dst_addr(); + + // Bind the socket to port 68 + let socket = sockets.get_mut::(socket_handle); + assert_eq!(socket.bind(68), Ok(())); + assert!(!socket.can_recv()); + assert!(socket.can_send()); + + udp_repr.emit( + &mut packet, + &ip_repr.src_addr(), + &ip_repr.dst_addr(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(&UDP_PAYLOAD), + &ChecksumCapabilities::default(), + ); + + // Packet should be handled by bound UDP socket + assert_eq!( + iface.inner.process_udp( + &mut sockets, + PacketMeta::default(), + false, + ip_repr, + packet.into_inner(), + ), + None + ); + + // Make sure the payload to the UDP packet processed by process_udp is + // appended to the bound sockets rx_buffer + let socket = sockets.get_mut::(socket_handle); + assert!(socket.can_recv()); + assert_eq!( + socket.recv(), + Ok(( + &UDP_PAYLOAD[..], + udp::UdpMetadata { + local_address: Some(dst_addr), + ..IpEndpoint::new(src_ip.into(), 67).into() + } + )) + ); +} + +#[test] +#[cfg(all(feature = "medium-ip", feature = "socket-tcp", feature = "proto-ipv6"))] +pub fn tcp_not_accepted() { + let (mut iface, mut sockets, _) = setup(Medium::Ip); + let tcp = TcpRepr { + src_port: 4242, + dst_port: 4243, + control: TcpControl::Syn, + seq_number: TcpSeqNumber(-10001), + ack_number: None, + window_len: 256, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &[], + }; + + let mut tcp_bytes = vec![0u8; tcp.buffer_len()]; + + tcp.emit( + &mut TcpPacket::new_unchecked(&mut tcp_bytes), + &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(), + &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1).into(), + &ChecksumCapabilities::default(), + ); + + assert_eq!( + iface.inner.process_tcp( + &mut sockets, + false, + IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + next_header: IpProtocol::Tcp, + payload_len: tcp.buffer_len(), + hop_limit: 64, + }), + &tcp_bytes, + ), + Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Tcp, + payload_len: tcp.buffer_len(), + hop_limit: 64, + }, + IpPayload::Tcp(TcpRepr { + src_port: 4243, + dst_port: 4242, + control: TcpControl::Rst, + seq_number: TcpSeqNumber(0), + ack_number: Some(TcpSeqNumber(-10000)), + window_len: 0, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &[], + }) + )) + ); + // Unspecified destination address. + tcp.emit( + &mut TcpPacket::new_unchecked(&mut tcp_bytes), + &Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2).into(), + &Ipv6Address::UNSPECIFIED.into(), + &ChecksumCapabilities::default(), + ); + + assert_eq!( + iface.inner.process_tcp( + &mut sockets, + false, + IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + dst_addr: Ipv6Address::UNSPECIFIED, + next_header: IpProtocol::Tcp, + payload_len: tcp.buffer_len(), + hop_limit: 64, + }), + &tcp_bytes, + ), + None, + ); +} diff --git a/vendor/smoltcp/src/iface/interface/tests/sixlowpan.rs b/vendor/smoltcp/src/iface/interface/tests/sixlowpan.rs new file mode 100644 index 00000000..56e1fb8c --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/tests/sixlowpan.rs @@ -0,0 +1,427 @@ +use super::*; + +#[rstest] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn ieee802154_wrong_pan_id(#[case] medium: Medium) { + let data = [ + 0x41, 0xcc, 0x3b, 0xff, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a, + 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26, + ]; + + let response = None; + + let (mut iface, mut sockets, _device) = setup(medium); + + assert_eq!( + iface.inner.process_ieee802154( + &mut sockets, + PacketMeta::default(), + &data[..], + &mut iface.fragments + ), + response, + ); +} + +#[rstest] +#[case::ieee802154(Medium::Ieee802154)] +#[cfg(feature = "medium-ieee802154")] +fn icmp_echo_request(#[case] medium: Medium) { + let data = [ + 0x41, 0xcc, 0x3b, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0x62, 0x3a, + 0xa6, 0x34, 0x57, 0x29, 0x1c, 0x26, 0x6a, 0x33, 0x0a, 0x62, 0x17, 0x3a, 0x80, 0x00, 0xb0, + 0xe3, 0x00, 0x04, 0x00, 0x01, 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, + ]; + + let response = Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0x241c, 0x2957, 0x34a6, 0x3a62), + hop_limit: 64, + next_header: IpProtocol::Icmpv6, + payload_len: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 4, + seq_no: 1, + data: &[ + 0x82, 0xf2, 0x82, 0x64, 0x00, 0x00, 0x00, 0x00, 0x66, 0x23, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + ], + }), + )); + + let (mut iface, mut sockets, _device) = setup(medium); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0, 0, 0, 0x180b, 0x4242, 0x4242, 0x4242), + 10, + ))) + .unwrap(); + }); + + assert_eq!( + iface.inner.process_ieee802154( + &mut sockets, + PacketMeta::default(), + &data[..], + &mut iface.fragments + ), + response, + ); +} + +#[test] +#[cfg(feature = "proto-sixlowpan-fragmentation")] +fn test_echo_request_sixlowpan_128_bytes() { + use crate::phy::Checksum; + + let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76), + 10, + ))) + .unwrap(); + }); + // TODO: modify the example, such that we can also test if the checksum is correctly + // computed. + iface.inner.caps.checksum.icmpv6 = Checksum::None; + + assert_eq!(iface.inner.caps.medium, Medium::Ieee802154); + let now = iface.inner.now(); + + iface.inner.neighbor_cache.fill( + Ipv6Address::new(0xfe80, 0, 0, 0, 0x0200, 0, 0, 0).into(), + HardwareAddress::Ieee802154(Ieee802154Address::default()), + now, + ); + + let mut ieee802154_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(5), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: Some(Ieee802154Pan(0xbeef)), + dst_addr: Some(Ieee802154Address::Extended([ + 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76, + ])), + src_pan_id: Some(Ieee802154Pan(0xbeef)), + src_addr: Some(Ieee802154Address::Extended([ + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, + ])), + }; + + // NOTE: this data is retrieved from tests with Contiki-NG + + let request_first_part_packet = SixlowpanFragPacket::new_checked(&[ + 0xc0, 0xb0, 0x00, 0x8e, 0x6a, 0x33, 0x05, 0x25, 0x2c, 0x3a, 0x80, 0x00, 0xe0, 0x71, 0x00, + 0x27, 0x00, 0x02, 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + ]) + .unwrap(); + + let request_first_part_iphc_packet = + SixlowpanIphcPacket::new_checked(request_first_part_packet.payload()).unwrap(); + + let request_first_part_iphc_repr = SixlowpanIphcRepr::parse( + &request_first_part_iphc_packet, + ieee802154_repr.src_addr, + ieee802154_repr.dst_addr, + &iface.inner.sixlowpan_address_context, + ) + .unwrap(); + + assert_eq!( + request_first_part_iphc_repr.src_addr, + Ipv6Address::new(0xfe80, 0, 0, 0, 0x4042, 0x4242, 0x4242, 0x0b1a), + ); + assert_eq!( + request_first_part_iphc_repr.dst_addr, + Ipv6Address::new(0xfe80, 0, 0, 0, 0x92fc, 0x48c2, 0xa441, 0xfc76), + ); + + let request_second_part = [ + 0xe0, 0xb0, 0x00, 0x8e, 0x10, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + ]; + + assert_eq!( + iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + &request_first_part_packet.into_inner()[..], + &mut iface.fragments + ), + None + ); + + ieee802154_repr.sequence_number = Some(6); + + // data that was generated when using `ping -s 128` + let data = &[ + 0xa2, 0xc2, 0x2d, 0x63, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x5e, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, + 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + ]; + + let result = iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + &request_second_part, + &mut iface.fragments, + ); + + assert_eq!( + result, + Some(Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0x92fc, 0x48c2, 0xa441, 0xfc76), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0x4042, 0x4242, 0x4242, 0x0b1a), + next_header: IpProtocol::Icmpv6, + payload_len: 136, + hop_limit: 64, + }, + IpPayload::Icmpv6(Icmpv6Repr::EchoReply { + ident: 39, + seq_no: 2, + data, + }) + )) + ); + + iface.inner.neighbor_cache.fill( + IpAddress::Ipv6(Ipv6Address::new( + 0xfe80, 0, 0, 0, 0x4042, 0x4242, 0x4242, 0x0b1a, + )), + HardwareAddress::Ieee802154(Ieee802154Address::default()), + Instant::now(), + ); + + let tx_token = device.transmit(Instant::now()).unwrap(); + iface.inner.dispatch_ieee802154( + Ieee802154Address::default(), + tx_token, + PacketMeta::default(), + result.unwrap(), + &mut iface.fragmenter, + ); + + assert_eq!( + device.tx_queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb0, 0x5, 0x4e, 0x7a, 0x11, 0x3a, 0x92, 0xfc, 0x48, 0xc2, + 0xa4, 0x41, 0xfc, 0x76, 0x40, 0x42, 0x42, 0x42, 0x42, 0x42, 0xb, 0x1a, 0x81, 0x0, 0x0, + 0x0, 0x0, 0x27, 0x0, 0x2, 0xa2, 0xc2, 0x2d, 0x63, 0x0, 0x0, 0x0, 0x0, 0xd9, 0x5e, 0xc, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, + ] + ); + + iface.poll(Instant::now(), &mut device, &mut sockets); + + assert_eq!( + device.tx_queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb0, 0x5, 0x4e, 0xf, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, + 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + ] + ); +} + +#[test] +#[cfg(feature = "proto-sixlowpan-fragmentation")] +fn test_sixlowpan_udp_with_fragmentation() { + use crate::phy::Checksum; + use crate::socket::udp; + + let mut ieee802154_repr = Ieee802154Repr { + frame_type: Ieee802154FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: false, + sequence_number: Some(5), + pan_id_compression: true, + frame_version: Ieee802154FrameVersion::Ieee802154_2003, + dst_pan_id: Some(Ieee802154Pan(0xbeef)), + dst_addr: Some(Ieee802154Address::Extended([ + 0x90, 0xfc, 0x48, 0xc2, 0xa4, 0x41, 0xfc, 0x76, + ])), + src_pan_id: Some(Ieee802154Pan(0xbeef)), + src_addr: Some(Ieee802154Address::Extended([ + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, + ])), + }; + + let (mut iface, mut sockets, mut device) = setup(Medium::Ieee802154); + iface.update_ip_addrs(|ips| { + ips.push(IpCidr::Ipv6(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0x0, 0x0, 0x0, 0x92fc, 0x48c2, 0xa441, 0xfc76), + 10, + ))) + .unwrap(); + }); + iface.inner.caps.checksum.udp = Checksum::None; + + let udp_rx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]); + let udp_tx_buffer = udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY], vec![0; 1024 * 4]); + let udp_socket = udp::Socket::new(udp_rx_buffer, udp_tx_buffer); + let udp_socket_handle = sockets.add(udp_socket); + + { + let socket = sockets.get_mut::(udp_socket_handle); + assert_eq!(socket.bind(6969), Ok(())); + assert!(!socket.can_recv()); + assert!(socket.can_send()); + } + + let udp_first_part = &[ + 0xc0, 0xbc, 0x00, 0x92, 0x6e, 0x33, 0x07, 0xe7, 0xdc, 0xf0, 0xd3, 0xc9, 0x1b, 0x39, 0xbf, + 0xa0, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, + 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, + 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x49, 0x6e, + 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, 0x20, 0x74, 0x6f, 0x72, + 0x74, 0x6f, 0x72, 0x2e, 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e, + ]; + + assert_eq!( + iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + udp_first_part, + &mut iface.fragments + ), + None + ); + + ieee802154_repr.sequence_number = Some(6); + + let udp_second_part = &[ + 0xe0, 0xbc, 0x00, 0x92, 0x11, 0x64, 0x69, 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73, + 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76, 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76, + 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c, 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e, + ]; + + assert_eq!( + iface.inner.process_sixlowpan( + &mut sockets, + PacketMeta::default(), + &ieee802154_repr, + udp_second_part, + &mut iface.fragments + ), + None + ); + + let socket = sockets.get_mut::(udp_socket_handle); + + let udp_data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. \ +In at rhoncus tortor. Cras blandit tellus diam, varius vestibulum nibh commodo nec."; + assert_eq!( + socket.recv(), + Ok(( + &udp_data[..], + udp::UdpMetadata { + local_address: Some( + Ipv6Address::new(0xfe80, 0, 0, 0, 0x92fc, 0x48c2, 0xa441, 0xfc76).into() + ), + ..IpEndpoint { + addr: IpAddress::Ipv6(Ipv6Address::new( + 0xfe80, 0, 0, 0, 0x4042, 0x4242, 0x4242, 0x0b1a + )), + port: 54217, + } + .into() + } + )) + ); + + let tx_token = device.transmit(Instant::now()).unwrap(); + iface.inner.dispatch_ieee802154( + Ieee802154Address::default(), + tx_token, + PacketMeta::default(), + Packet::new_ipv6( + Ipv6Repr { + src_addr: Ipv6Address::UNSPECIFIED, + dst_addr: Ipv6Address::UNSPECIFIED, + next_header: IpProtocol::Udp, + payload_len: udp_data.len(), + hop_limit: 64, + }, + IpPayload::Udp( + UdpRepr { + src_port: 1234, + dst_port: 1234, + }, + udp_data, + ), + ), + &mut iface.fragmenter, + ); + + iface.poll(Instant::now(), &mut device, &mut sockets); + + assert_eq!( + device.tx_queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xc0, 0xb4, 0x5, 0x4e, 0x7e, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x4, 0xd2, 0x4, 0xd2, 0x0, 0x0, + 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, + 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, + 0x20, 0x49, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x72, 0x68, 0x6f, 0x6e, 0x63, 0x75, 0x73, + 0x20, 0x74, + ], + ); + + assert_eq!( + device.tx_queue.pop_front().unwrap(), + &[ + 0x41, 0xcc, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0xe0, 0xb4, 0x5, 0x4e, 0xf, 0x6f, 0x72, 0x74, 0x6f, 0x72, 0x2e, + 0x20, 0x43, 0x72, 0x61, 0x73, 0x20, 0x62, 0x6c, 0x61, 0x6e, 0x64, 0x69, 0x74, 0x20, + 0x74, 0x65, 0x6c, 0x6c, 0x75, 0x73, 0x20, 0x64, 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x76, + 0x61, 0x72, 0x69, 0x75, 0x73, 0x20, 0x76, 0x65, 0x73, 0x74, 0x69, 0x62, 0x75, 0x6c, + 0x75, 0x6d, 0x20, 0x6e, 0x69, 0x62, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, + 0x6f, 0x20, 0x6e, 0x65, 0x63, 0x2e, + ] + ); +} diff --git a/vendor/smoltcp/src/iface/interface/udp.rs b/vendor/smoltcp/src/iface/interface/udp.rs new file mode 100644 index 00000000..a85c83a2 --- /dev/null +++ b/vendor/smoltcp/src/iface/interface/udp.rs @@ -0,0 +1,107 @@ +use super::*; + +#[cfg(feature = "socket-dns")] +use crate::socket::dns::Socket as DnsSocket; + +#[cfg(feature = "socket-udp")] +use crate::socket::udp::Socket as UdpSocket; + +impl InterfaceInner { + pub(super) fn process_udp<'frame>( + &mut self, + sockets: &mut SocketSet, + meta: PacketMeta, + handled_by_raw_socket: bool, + ip_repr: IpRepr, + ip_payload: &'frame [u8], + ) -> Option> { + let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr()); + let udp_packet = check!(UdpPacket::new_checked(ip_payload)); + let udp_repr = check!(UdpRepr::parse( + &udp_packet, + &src_addr, + &dst_addr, + &self.caps.checksum + )); + + #[cfg(feature = "socket-udp")] + { + // Find the best matching socket based on priority score + // We need to do this in two passes because we can't hold mutable references + // to multiple sockets at once. + + // First pass: find the best match + let mut best_match: Option<(usize, u8)> = None; + + for (idx, item) in sockets.items().enumerate() { + if let Some(udp_socket) = UdpSocket::downcast(&item.socket) { + let score = udp_socket.accepts(self, &ip_repr, &udp_repr); + if score > 0 { + match best_match { + Some((_, best_score)) if score > best_score => { + best_match = Some((idx, score)); + } + None => { + best_match = Some((idx, score)); + } + _ => {} + } + } + } + } + + // Second pass: process the packet with the best matching socket + if let Some((best_idx, _)) = best_match { + for (idx, item) in sockets.items_mut().enumerate() { + if idx == best_idx { + if let Some(udp_socket) = UdpSocket::downcast_mut(&mut item.socket) { + udp_socket.process(self, meta, &ip_repr, &udp_repr, udp_packet.payload()); + return None; + } + } + } + } + } + + #[cfg(feature = "socket-dns")] + for dns_socket in sockets + .items_mut() + .filter_map(|i| DnsSocket::downcast_mut(&mut i.socket)) + { + if dns_socket.accepts(&ip_repr, &udp_repr) { + dns_socket.process(self, &ip_repr, &udp_repr, udp_packet.payload()); + return None; + } + } + + // The packet wasn't handled by a socket, send an ICMP port unreachable packet. + match ip_repr { + #[cfg(feature = "proto-ipv4")] + IpRepr::Ipv4(_) if handled_by_raw_socket => None, + #[cfg(feature = "proto-ipv6")] + IpRepr::Ipv6(_) if handled_by_raw_socket => None, + #[cfg(feature = "proto-ipv4")] + IpRepr::Ipv4(ipv4_repr) => { + let payload_len = + icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU, ipv4_repr.buffer_len()); + let icmpv4_reply_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::PortUnreachable, + header: ipv4_repr, + data: &ip_payload[0..payload_len], + }; + self.icmpv4_reply(ipv4_repr, icmpv4_reply_repr) + } + #[cfg(feature = "proto-ipv6")] + IpRepr::Ipv6(ipv6_repr) => { + let payload_len = + icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len()); + let icmpv6_reply_repr = Icmpv6Repr::DstUnreachable { + reason: Icmpv6DstUnreachable::PortUnreachable, + header: ipv6_repr, + data: &ip_payload[0..payload_len], + }; + self.icmpv6_reply(ipv6_repr, icmpv6_reply_repr) + } + } + } +} diff --git a/vendor/smoltcp/src/iface/mod.rs b/vendor/smoltcp/src/iface/mod.rs new file mode 100644 index 00000000..16e3be29 --- /dev/null +++ b/vendor/smoltcp/src/iface/mod.rs @@ -0,0 +1,30 @@ +/*! Network interface logic. + +The `iface` module deals with the *network interfaces*. It filters incoming frames, +provides lookup and caching of hardware addresses, and handles management packets. +*/ + +mod fragmentation; +mod interface; +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +mod neighbor; +mod route; +#[cfg(feature = "proto-rpl")] +mod rpl; +#[cfg(feature = "proto-ipv6-slaac")] +mod slaac; +mod socket_meta; +mod socket_set; + +mod packet; + +#[cfg(feature = "multicast")] +pub use self::interface::multicast::MulticastError; +pub use self::interface::{ + Config, Interface, InterfaceInner as Context, PollIngressSingleResult, PollResult, +}; + +pub use self::route::{Route, RouteTableFull, Routes}; +#[cfg(feature = "proto-ipv6-slaac")] +pub use self::slaac::Slaac; +pub use self::socket_set::{SocketHandle, SocketSet, SocketStorage}; diff --git a/vendor/smoltcp/src/iface/neighbor.rs b/vendor/smoltcp/src/iface/neighbor.rs new file mode 100644 index 00000000..3c462398 --- /dev/null +++ b/vendor/smoltcp/src/iface/neighbor.rs @@ -0,0 +1,348 @@ +// Heads up! Before working on this file you should read, at least, +// the parts of RFC 1122 that discuss ARP. + +use heapless::LinearMap; + +use crate::config::IFACE_NEIGHBOR_CACHE_COUNT; +use crate::time::{Duration, Instant}; +use crate::wire::{HardwareAddress, IpAddress}; + +/// A cached neighbor. +/// +/// A neighbor mapping translates from a protocol address to a hardware address, +/// and contains the timestamp past which the mapping should be discarded. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Neighbor { + hardware_addr: HardwareAddress, + expires_at: Instant, +} + +/// An answer to a neighbor cache lookup. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum Answer { + /// The neighbor address is in the cache and not expired. + Found(HardwareAddress), + /// The neighbor address is not in the cache, or has expired. + NotFound, + /// The neighbor address is not in the cache, or has expired, + /// and a lookup has been made recently. + RateLimited, +} + +impl Answer { + /// Returns whether a valid address was found. + pub(crate) fn found(&self) -> bool { + match self { + Answer::Found(_) => true, + _ => false, + } + } +} + +/// A neighbor cache backed by a map. +#[derive(Debug)] +pub struct Cache { + storage: LinearMap, + silent_until: Instant, +} + +impl Cache { + /// Minimum delay between discovery requests, in milliseconds. + pub(crate) const SILENT_TIME: Duration = Duration::from_millis(1_000); + + /// Neighbor entry lifetime, in milliseconds. + pub(crate) const ENTRY_LIFETIME: Duration = Duration::from_millis(60_000); + + /// Create a cache. + pub fn new() -> Self { + Self { + storage: LinearMap::new(), + silent_until: Instant::from_millis(0), + } + } + + pub fn reset_expiry_if_existing( + &mut self, + protocol_addr: IpAddress, + source_hardware_addr: HardwareAddress, + timestamp: Instant, + ) { + if let Some(Neighbor { + expires_at, + hardware_addr, + }) = self.storage.get_mut(&protocol_addr) + { + if source_hardware_addr == *hardware_addr { + *expires_at = timestamp + Self::ENTRY_LIFETIME; + } + } + } + + pub fn fill( + &mut self, + protocol_addr: IpAddress, + hardware_addr: HardwareAddress, + timestamp: Instant, + ) { + debug_assert!(protocol_addr.is_unicast()); + debug_assert!(hardware_addr.is_unicast()); + + let expires_at = timestamp + Self::ENTRY_LIFETIME; + self.fill_with_expiration(protocol_addr, hardware_addr, expires_at); + } + + pub fn fill_with_expiration( + &mut self, + protocol_addr: IpAddress, + hardware_addr: HardwareAddress, + expires_at: Instant, + ) { + debug_assert!(protocol_addr.is_unicast()); + debug_assert!(hardware_addr.is_unicast()); + + let neighbor = Neighbor { + expires_at, + hardware_addr, + }; + match self.storage.insert(protocol_addr, neighbor) { + Ok(Some(old_neighbor)) => { + if old_neighbor.hardware_addr != hardware_addr { + net_trace!( + "replaced {} => {} (was {})", + protocol_addr, + hardware_addr, + old_neighbor.hardware_addr + ); + } + } + Ok(None) => { + net_trace!("filled {} => {} (was empty)", protocol_addr, hardware_addr); + } + Err((protocol_addr, neighbor)) => { + // If we're going down this branch, it means the cache is full, and we need to evict an entry. + let old_protocol_addr = *self + .storage + .iter() + .min_by_key(|(_, neighbor)| neighbor.expires_at) + .expect("empty neighbor cache storage") + .0; + + let _old_neighbor = self.storage.remove(&old_protocol_addr).unwrap(); + match self.storage.insert(protocol_addr, neighbor) { + Ok(None) => { + net_trace!( + "filled {} => {} (evicted {} => {})", + protocol_addr, + hardware_addr, + old_protocol_addr, + _old_neighbor.hardware_addr + ); + } + // We've covered everything else above. + _ => unreachable!(), + } + } + } + } + + pub(crate) fn lookup(&self, protocol_addr: &IpAddress, timestamp: Instant) -> Answer { + assert!(protocol_addr.is_unicast()); + + if let Some(&Neighbor { + expires_at, + hardware_addr, + }) = self.storage.get(protocol_addr) + { + if timestamp < expires_at { + return Answer::Found(hardware_addr); + } + } + + if timestamp < self.silent_until { + Answer::RateLimited + } else { + Answer::NotFound + } + } + + pub(crate) fn limit_rate(&mut self, timestamp: Instant) { + self.silent_until = timestamp + Self::SILENT_TIME; + } + + pub(crate) fn flush(&mut self) { + self.storage.clear() + } +} + +#[cfg(feature = "medium-ethernet")] +#[cfg(test)] +mod test { + use super::*; + #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))] + use crate::wire::ipv4::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4}; + #[cfg(feature = "proto-ipv6")] + use crate::wire::ipv6::test::{MOCK_IP_ADDR_1, MOCK_IP_ADDR_2, MOCK_IP_ADDR_3, MOCK_IP_ADDR_4}; + + use crate::wire::EthernetAddress; + + const HADDR_A: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 1])); + const HADDR_B: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 2])); + const HADDR_C: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 3])); + const HADDR_D: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 4])); + + #[test] + fn test_fill() { + let mut cache = Cache::new(); + + assert!( + !cache + .lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)) + .found() + ); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0)) + .found() + ); + + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)), + Answer::Found(HADDR_A) + ); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0)) + .found() + ); + assert!( + !cache + .lookup( + &MOCK_IP_ADDR_1.into(), + Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2 + ) + .found(), + ); + + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0)); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0)) + .found() + ); + } + + #[test] + fn test_expire() { + let mut cache = Cache::new(); + + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)), + Answer::Found(HADDR_A) + ); + assert!( + !cache + .lookup( + &MOCK_IP_ADDR_1.into(), + Instant::from_millis(0) + Cache::ENTRY_LIFETIME * 2 + ) + .found(), + ); + } + + #[test] + fn test_replace() { + let mut cache = Cache::new(); + + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)), + Answer::Found(HADDR_A) + ); + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_B, Instant::from_millis(0)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)), + Answer::Found(HADDR_B) + ); + } + + #[test] + fn test_evict() { + let mut cache = Cache::new(); + + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(100)); + cache.fill(MOCK_IP_ADDR_2.into(), HADDR_B, Instant::from_millis(50)); + cache.fill(MOCK_IP_ADDR_3.into(), HADDR_C, Instant::from_millis(200)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(1000)), + Answer::Found(HADDR_B) + ); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_4.into(), Instant::from_millis(1000)) + .found() + ); + + cache.fill(MOCK_IP_ADDR_4.into(), HADDR_D, Instant::from_millis(300)); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(1000)) + .found() + ); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_4.into(), Instant::from_millis(1000)), + Answer::Found(HADDR_D) + ); + } + + #[test] + fn test_hush() { + let mut cache = Cache::new(); + + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)), + Answer::NotFound + ); + + cache.limit_rate(Instant::from_millis(0)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(100)), + Answer::RateLimited + ); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(2000)), + Answer::NotFound + ); + } + + #[test] + fn test_flush() { + let mut cache = Cache::new(); + + cache.fill(MOCK_IP_ADDR_1.into(), HADDR_A, Instant::from_millis(0)); + assert_eq!( + cache.lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)), + Answer::Found(HADDR_A) + ); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_2.into(), Instant::from_millis(0)) + .found() + ); + + cache.flush(); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)) + .found() + ); + assert!( + !cache + .lookup(&MOCK_IP_ADDR_1.into(), Instant::from_millis(0)) + .found() + ); + } +} diff --git a/vendor/smoltcp/src/iface/packet.rs b/vendor/smoltcp/src/iface/packet.rs new file mode 100644 index 00000000..b1500e1c --- /dev/null +++ b/vendor/smoltcp/src/iface/packet.rs @@ -0,0 +1,264 @@ +use crate::phy::DeviceCapabilities; +use crate::wire::*; + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "medium-ethernet")] +pub(crate) enum EthernetPacket<'a> { + #[cfg(feature = "proto-ipv4")] + Arp(ArpRepr), + Ip(Packet<'a>), +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum Packet<'p> { + #[cfg(feature = "proto-ipv4")] + Ipv4(PacketV4<'p>), + #[cfg(feature = "proto-ipv6")] + Ipv6(PacketV6<'p>), +} + +impl<'p> Packet<'p> { + pub(crate) fn new(ip_repr: IpRepr, payload: IpPayload<'p>) -> Self { + match ip_repr { + #[cfg(feature = "proto-ipv4")] + IpRepr::Ipv4(header) => Self::new_ipv4(header, payload), + #[cfg(feature = "proto-ipv6")] + IpRepr::Ipv6(header) => Self::new_ipv6(header, payload), + } + } + + #[cfg(feature = "proto-ipv4")] + pub(crate) fn new_ipv4(ip_repr: Ipv4Repr, payload: IpPayload<'p>) -> Self { + Self::Ipv4(PacketV4 { + header: ip_repr, + payload, + }) + } + + #[cfg(feature = "proto-ipv6")] + pub(crate) fn new_ipv6(ip_repr: Ipv6Repr, payload: IpPayload<'p>) -> Self { + Self::Ipv6(PacketV6 { + header: ip_repr, + #[cfg(feature = "proto-ipv6-hbh")] + hop_by_hop: None, + #[cfg(feature = "proto-ipv6-fragmentation")] + fragment: None, + #[cfg(feature = "proto-ipv6-routing")] + routing: None, + payload, + }) + } + + pub(crate) fn ip_repr(&self) -> IpRepr { + match self { + #[cfg(feature = "proto-ipv4")] + Packet::Ipv4(p) => IpRepr::Ipv4(p.header), + #[cfg(feature = "proto-ipv6")] + Packet::Ipv6(p) => IpRepr::Ipv6(p.header), + } + } + + pub(crate) fn payload(&self) -> &IpPayload<'p> { + match self { + #[cfg(feature = "proto-ipv4")] + Packet::Ipv4(p) => &p.payload, + #[cfg(feature = "proto-ipv6")] + Packet::Ipv6(p) => &p.payload, + } + } + + pub(crate) fn emit_payload( + &self, + _ip_repr: &IpRepr, + payload: &mut [u8], + caps: &DeviceCapabilities, + ) { + match self.payload() { + #[cfg(feature = "proto-ipv4")] + IpPayload::Icmpv4(icmpv4_repr) => { + icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &caps.checksum) + } + #[cfg(all(feature = "proto-ipv4", feature = "multicast"))] + IpPayload::Igmp(igmp_repr) => igmp_repr.emit(&mut IgmpPacket::new_unchecked(payload)), + #[cfg(feature = "proto-ipv6")] + IpPayload::Icmpv6(icmpv6_repr) => { + let ipv6_repr = match _ip_repr { + #[cfg(feature = "proto-ipv4")] + IpRepr::Ipv4(_) => unreachable!(), + IpRepr::Ipv6(repr) => repr, + }; + + icmpv6_repr.emit( + &ipv6_repr.src_addr, + &ipv6_repr.dst_addr, + &mut Icmpv6Packet::new_unchecked(payload), + &caps.checksum, + ) + } + #[cfg(feature = "proto-ipv6")] + IpPayload::HopByHopIcmpv6(hbh_repr, icmpv6_repr) => { + let ipv6_repr = match _ip_repr { + #[cfg(feature = "proto-ipv4")] + IpRepr::Ipv4(_) => unreachable!(), + IpRepr::Ipv6(repr) => repr, + }; + + let ipv6_ext_hdr = Ipv6ExtHeaderRepr { + next_header: IpProtocol::Icmpv6, + length: 0, + data: &[], + }; + ipv6_ext_hdr.emit(&mut Ipv6ExtHeader::new_unchecked( + &mut payload[..ipv6_ext_hdr.header_len()], + )); + + let hbh_start = ipv6_ext_hdr.header_len(); + let hbh_end = hbh_start + hbh_repr.buffer_len(); + hbh_repr.emit(&mut Ipv6HopByHopHeader::new_unchecked( + &mut payload[hbh_start..hbh_end], + )); + + icmpv6_repr.emit( + &ipv6_repr.src_addr, + &ipv6_repr.dst_addr, + &mut Icmpv6Packet::new_unchecked(&mut payload[hbh_end..]), + &caps.checksum, + ); + } + + #[cfg(feature = "socket-raw")] + IpPayload::Raw(raw_packet) => { + let len = raw_packet.len(); + payload[..len].copy_from_slice(raw_packet) + } + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + IpPayload::Udp(udp_repr, inner_payload) => udp_repr.emit( + &mut UdpPacket::new_unchecked(payload), + &_ip_repr.src_addr(), + &_ip_repr.dst_addr(), + inner_payload.len(), + |buf| buf.copy_from_slice(inner_payload), + &caps.checksum, + ), + #[cfg(feature = "socket-tcp")] + &IpPayload::Tcp(mut tcp_repr) => { + // This is a terrible hack to make TCP performance more acceptable on systems + // where the TCP buffers are significantly larger than network buffers, + // e.g. a 64 kB TCP receive buffer (and so, when empty, a 64k window) + // together with four 1500 B Ethernet receive buffers. If left untreated, + // this would result in our peer pushing our window and sever packet loss. + // + // I'm really not happy about this "solution" but I don't know what else to do. + if let Some(max_burst_size) = caps.max_burst_size { + let mut max_segment_size = caps.max_transmission_unit; + max_segment_size -= _ip_repr.header_len(); + max_segment_size -= tcp_repr.header_len(); + + let max_window_size = max_burst_size * max_segment_size; + if tcp_repr.window_len as usize > max_window_size { + tcp_repr.window_len = max_window_size as u16; + } + } + + tcp_repr.emit( + &mut TcpPacket::new_unchecked(payload), + &_ip_repr.src_addr(), + &_ip_repr.dst_addr(), + &caps.checksum, + ); + } + #[cfg(feature = "socket-dhcpv4")] + IpPayload::Dhcpv4(udp_repr, dhcp_repr) => udp_repr.emit( + &mut UdpPacket::new_unchecked(payload), + &_ip_repr.src_addr(), + &_ip_repr.dst_addr(), + dhcp_repr.buffer_len(), + |buf| dhcp_repr.emit(&mut DhcpPacket::new_unchecked(buf)).unwrap(), + &caps.checksum, + ), + } + } +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "proto-ipv4")] +pub(crate) struct PacketV4<'p> { + header: Ipv4Repr, + payload: IpPayload<'p>, +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "proto-ipv6")] +pub(crate) struct PacketV6<'p> { + pub(crate) header: Ipv6Repr, + #[cfg(feature = "proto-ipv6-hbh")] + pub(crate) hop_by_hop: Option>, + #[cfg(feature = "proto-ipv6-fragmentation")] + pub(crate) fragment: Option, + #[cfg(feature = "proto-ipv6-routing")] + pub(crate) routing: Option>, + pub(crate) payload: IpPayload<'p>, +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum IpPayload<'p> { + #[cfg(feature = "proto-ipv4")] + Icmpv4(Icmpv4Repr<'p>), + #[cfg(all(feature = "proto-ipv4", feature = "multicast"))] + Igmp(IgmpRepr), + #[cfg(feature = "proto-ipv6")] + Icmpv6(Icmpv6Repr<'p>), + #[cfg(feature = "proto-ipv6")] + HopByHopIcmpv6(Ipv6HopByHopRepr<'p>, Icmpv6Repr<'p>), + #[cfg(feature = "socket-raw")] + Raw(&'p [u8]), + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + Udp(UdpRepr, &'p [u8]), + #[cfg(feature = "socket-tcp")] + Tcp(TcpRepr<'p>), + #[cfg(feature = "socket-dhcpv4")] + Dhcpv4(UdpRepr, DhcpRepr<'p>), +} + +impl<'p> IpPayload<'p> { + #[cfg(feature = "proto-sixlowpan")] + pub(crate) fn as_sixlowpan_next_header(&self) -> SixlowpanNextHeader { + match self { + #[cfg(feature = "proto-ipv4")] + Self::Icmpv4(_) => unreachable!(), + #[cfg(feature = "socket-dhcpv4")] + Self::Dhcpv4(..) => unreachable!(), + #[cfg(feature = "proto-ipv6")] + Self::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6), + #[cfg(feature = "proto-ipv6")] + Self::HopByHopIcmpv6(_, _) => unreachable!(), + #[cfg(all(feature = "proto-ipv4", feature = "multicast"))] + Self::Igmp(_) => unreachable!(), + #[cfg(feature = "socket-tcp")] + Self::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp), + #[cfg(any(feature = "socket-udp", feature = "socket-dns"))] + Self::Udp(..) => SixlowpanNextHeader::Compressed, + #[cfg(feature = "socket-raw")] + Self::Raw(_) => todo!(), + } + } +} + +#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] +pub(crate) fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize { + // Send back as much of the original payload as will fit within + // the minimum MTU required by IPv4. See RFC 1812 § 4.3.2.3 for + // more details. + // + // Since the entire network layer packet must fit within the minimum + // MTU supported, the payload must not exceed the following: + // + // - IP Header Size * 2 - ICMPv4 DstUnreachable hdr size + len.min(mtu - header_len * 2 - 8) +} diff --git a/vendor/smoltcp/src/iface/route.rs b/vendor/smoltcp/src/iface/route.rs new file mode 100644 index 00000000..e3bef806 --- /dev/null +++ b/vendor/smoltcp/src/iface/route.rs @@ -0,0 +1,340 @@ +use heapless::Vec; + +use crate::config::IFACE_MAX_ROUTE_COUNT; +use crate::time::Instant; +use crate::wire::{IpAddress, IpCidr}; +#[cfg(feature = "proto-ipv4")] +use crate::wire::{Ipv4Address, Ipv4Cidr}; +#[cfg(feature = "proto-ipv6")] +use crate::wire::{Ipv6Address, Ipv6Cidr}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RouteTableFull; + +impl core::fmt::Display for RouteTableFull { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Route table full") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RouteTableFull {} + +/// A prefix of addresses that should be routed via a router +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Route { + pub cidr: IpCidr, + pub via_router: IpAddress, + /// `None` means "forever". + pub preferred_until: Option, + /// `None` means "forever". + pub expires_at: Option, +} + +#[cfg(feature = "proto-ipv4")] +const IPV4_DEFAULT: IpCidr = IpCidr::Ipv4(Ipv4Cidr::new(Ipv4Address::new(0, 0, 0, 0), 0)); +#[cfg(feature = "proto-ipv6")] +const IPV6_DEFAULT: IpCidr = + IpCidr::Ipv6(Ipv6Cidr::new(Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0), 0)); + +impl Route { + /// Returns a route to 0.0.0.0/0 via the `gateway`, with no expiry. + #[cfg(feature = "proto-ipv4")] + pub fn new_ipv4_gateway(gateway: Ipv4Address) -> Route { + Route { + cidr: IPV4_DEFAULT, + via_router: gateway.into(), + preferred_until: None, + expires_at: None, + } + } + + /// Returns a route to ::/0 via the `gateway`, with no expiry. + #[cfg(feature = "proto-ipv6")] + pub fn new_ipv6_gateway(gateway: Ipv6Address) -> Route { + Route { + cidr: IPV6_DEFAULT, + via_router: gateway.into(), + preferred_until: None, + expires_at: None, + } + } + + /// Returns `true` if the route is a default route for IPv6. + #[cfg(feature = "proto-ipv6")] + pub fn is_ipv6_gateway(&self) -> bool { + self.cidr == IPV6_DEFAULT + } + + /// Returns `true` if the route is a default route for IPv4. + #[cfg(feature = "proto-ipv4")] + pub fn is_ipv4_gateway(&self) -> bool { + self.cidr == IPV4_DEFAULT + } +} + +/// A routing table. +#[derive(Debug)] +pub struct Routes { + storage: Vec, +} + +impl Routes { + /// Creates a new empty routing table. + pub fn new() -> Self { + Self { + storage: Vec::new(), + } + } + + /// Update the routes of this node. + pub fn update)>(&mut self, f: F) { + f(&mut self.storage); + } + + /// Add a default ipv4 gateway (ie. "ip route add 0.0.0.0/0 via `gateway`"). + /// + /// On success, returns the previous default route, if any. + #[cfg(feature = "proto-ipv4")] + pub fn add_default_ipv4_route( + &mut self, + gateway: Ipv4Address, + ) -> Result, RouteTableFull> { + let old = self.remove_default_ipv4_route(); + self.storage + .push(Route::new_ipv4_gateway(gateway)) + .map_err(|_| RouteTableFull)?; + Ok(old) + } + + /// Add a default ipv6 gateway (ie. "ip -6 route add ::/0 via `gateway`"). + /// + /// On success, returns the previous default route, if any. + #[cfg(feature = "proto-ipv6")] + pub fn add_default_ipv6_route( + &mut self, + gateway: Ipv6Address, + ) -> Result, RouteTableFull> { + let old = self.remove_default_ipv6_route(); + self.storage + .push(Route::new_ipv6_gateway(gateway)) + .map_err(|_| RouteTableFull)?; + Ok(old) + } + + /// Returns the ipv4 default route if there is one in the route table. + #[cfg(feature = "proto-ipv4")] + pub fn get_default_ipv4_route(&self) -> Option { + self.storage.iter().find(|r| r.is_ipv4_gateway()).copied() + } + + /// Returns the ipv6 default route if there is one in the route table. + #[cfg(feature = "proto-ipv6")] + pub fn get_default_ipv6_route(&self) -> Option { + self.storage.iter().find(|r| r.is_ipv6_gateway()).copied() + } + + /// Remove the default ipv4 gateway + /// + /// On success, returns the previous default route, if any. + #[cfg(feature = "proto-ipv4")] + pub fn remove_default_ipv4_route(&mut self) -> Option { + if let Some((i, _)) = self + .storage + .iter() + .enumerate() + .find(|(_, r)| r.is_ipv4_gateway()) + { + Some(self.storage.remove(i)) + } else { + None + } + } + + /// Remove the default ipv6 gateway + /// + /// On success, returns the previous default route, if any. + #[cfg(feature = "proto-ipv6")] + pub fn remove_default_ipv6_route(&mut self) -> Option { + if let Some((i, _)) = self + .storage + .iter() + .enumerate() + .find(|(_, r)| r.is_ipv6_gateway()) + { + Some(self.storage.remove(i)) + } else { + None + } + } + + pub(crate) fn lookup(&self, addr: &IpAddress, timestamp: Instant) -> Option { + assert!(addr.is_unicast()); + + self.storage + .iter() + // Keep only matching routes + .filter(|route| { + if let Some(expires_at) = route.expires_at { + if timestamp > expires_at { + return false; + } + } + route.cidr.contains_addr(addr) + }) + // pick the most specific one (highest prefix_len) + .max_by_key(|route| route.cidr.prefix_len()) + .map(|route| route.via_router) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[cfg(feature = "proto-ipv6")] + mod mock { + use super::super::*; + pub const ADDR_1A: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 1); + pub const ADDR_1B: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 13); + pub const ADDR_1C: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 42); + pub fn cidr_1() -> Ipv6Cidr { + Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 2, 0, 0, 0, 0), 64) + } + + pub const ADDR_2A: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 1); + pub const ADDR_2B: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 21); + pub fn cidr_2() -> Ipv6Cidr { + Ipv6Cidr::new(Ipv6Address::new(0xfe80, 0, 0, 0x3364, 0, 0, 0, 0), 64) + } + } + + #[cfg(all(feature = "proto-ipv4", not(feature = "proto-ipv6")))] + mod mock { + use super::super::*; + pub const ADDR_1A: Ipv4Address = Ipv4Address::new(192, 0, 2, 1); + pub const ADDR_1B: Ipv4Address = Ipv4Address::new(192, 0, 2, 13); + pub const ADDR_1C: Ipv4Address = Ipv4Address::new(192, 0, 2, 42); + pub fn cidr_1() -> Ipv4Cidr { + Ipv4Cidr::new(Ipv4Address::new(192, 0, 2, 0), 24) + } + + pub const ADDR_2A: Ipv4Address = Ipv4Address::new(198, 51, 100, 1); + pub const ADDR_2B: Ipv4Address = Ipv4Address::new(198, 51, 100, 21); + pub fn cidr_2() -> Ipv4Cidr { + Ipv4Cidr::new(Ipv4Address::new(198, 51, 100, 0), 24) + } + } + + use self::mock::*; + + #[test] + fn test_fill() { + let mut routes = Routes::new(); + + assert_eq!( + routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), + None + ); + assert_eq!( + routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), + None + ); + assert_eq!( + routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), + None + ); + assert_eq!( + routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), + None + ); + assert_eq!( + routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), + None + ); + + let route = Route { + cidr: cidr_1().into(), + via_router: ADDR_1A.into(), + preferred_until: None, + expires_at: None, + }; + routes.update(|storage| { + storage.push(route).unwrap(); + }); + + assert_eq!( + routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), + None + ); + assert_eq!( + routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), + None + ); + + let route2 = Route { + cidr: cidr_2().into(), + via_router: ADDR_2A.into(), + preferred_until: Some(Instant::from_millis(10)), + expires_at: Some(Instant::from_millis(10)), + }; + routes.update(|storage| { + storage.push(route2).unwrap(); + }); + + assert_eq!( + routes.lookup(&ADDR_1A.into(), Instant::from_millis(0)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_1B.into(), Instant::from_millis(0)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_1C.into(), Instant::from_millis(0)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_2A.into(), Instant::from_millis(0)), + Some(ADDR_2A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_2B.into(), Instant::from_millis(0)), + Some(ADDR_2A.into()) + ); + + assert_eq!( + routes.lookup(&ADDR_1A.into(), Instant::from_millis(10)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_1B.into(), Instant::from_millis(10)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_1C.into(), Instant::from_millis(10)), + Some(ADDR_1A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_2A.into(), Instant::from_millis(10)), + Some(ADDR_2A.into()) + ); + assert_eq!( + routes.lookup(&ADDR_2B.into(), Instant::from_millis(10)), + Some(ADDR_2A.into()) + ); + } +} diff --git a/vendor/smoltcp/src/iface/rpl/consts.rs b/vendor/smoltcp/src/iface/rpl/consts.rs new file mode 100644 index 00000000..70a66138 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/consts.rs @@ -0,0 +1,8 @@ +pub const SEQUENCE_WINDOW: u8 = 16; + +pub const DEFAULT_MIN_HOP_RANK_INCREASE: u16 = 256; + +pub const DEFAULT_DIO_INTERVAL_MIN: u32 = 12; +pub const DEFAULT_DIO_REDUNDANCY_CONSTANT: usize = 10; +/// This is 20 in the standard, but in Contiki they use: +pub const DEFAULT_DIO_INTERVAL_DOUBLINGS: u32 = 8; diff --git a/vendor/smoltcp/src/iface/rpl/lollipop.rs b/vendor/smoltcp/src/iface/rpl/lollipop.rs new file mode 100644 index 00000000..4785c772 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/lollipop.rs @@ -0,0 +1,189 @@ +//! Implementation of sequence counters defined in [RFC 6550 § 7.2]. Values from 128 and greater +//! are used as a linear sequence to indicate a restart and bootstrap the counter. Values less than +//! or equal to 127 are used as a circular sequence number space of size 128. When operating in the +//! circular region, if sequence numbers are detected to be too far apart, then they are not +//! comparable. +//! +//! [RFC 6550 § 7.2]: https://datatracker.ietf.org/doc/html/rfc6550#section-7.2 + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SequenceCounter(u8); + +impl Default for SequenceCounter { + fn default() -> Self { + // RFC6550 7.2 recommends 240 (256 - SEQUENCE_WINDOW) as the initialization value of the + // counter. + Self(240) + } +} + +impl SequenceCounter { + /// Create a new sequence counter. + /// + /// Use `Self::default()` when a new sequence counter needs to be created with a value that is + /// recommended in RFC6550 7.2, being 240. + pub fn new(value: u8) -> Self { + Self(value) + } + + /// Return the value of the sequence counter. + pub fn value(&self) -> u8 { + self.0 + } + + /// Increment the sequence counter. + /// + /// When the sequence counter is greater than or equal to 128, the maximum value is 255. + /// When the sequence counter is less than 128, the maximum value is 127. + /// + /// When an increment of the sequence counter would cause the counter to increment beyond its + /// maximum value, the counter MUST wrap back to zero. + pub fn increment(&mut self) { + let max = if self.0 >= 128 { 255 } else { 127 }; + + self.0 = match self.0.checked_add(1) { + Some(val) if val <= max => val, + _ => 0, + }; + } +} + +impl PartialEq for SequenceCounter { + fn eq(&self, other: &Self) -> bool { + let a = self.value() as usize; + let b = other.value() as usize; + + if ((128..=255).contains(&a) && (0..=127).contains(&b)) + || ((128..=255).contains(&b) && (0..=127).contains(&a)) + { + false + } else { + let result = if a > b { a - b } else { b - a }; + + if result <= super::consts::SEQUENCE_WINDOW as usize { + // RFC1982 + a == b + } else { + // This case is actually not comparable. + false + } + } + } +} + +impl PartialOrd for SequenceCounter { + fn partial_cmp(&self, other: &Self) -> Option { + use super::consts::SEQUENCE_WINDOW; + use core::cmp::Ordering; + + let a = self.value() as usize; + let b = other.value() as usize; + + if (128..256).contains(&a) && (0..128).contains(&b) { + if 256 + b - a <= SEQUENCE_WINDOW as usize { + Some(Ordering::Less) + } else { + Some(Ordering::Greater) + } + } else if (128..256).contains(&b) && (0..128).contains(&a) { + if 256 + a - b <= SEQUENCE_WINDOW as usize { + Some(Ordering::Greater) + } else { + Some(Ordering::Less) + } + } else if ((0..128).contains(&a) && (0..128).contains(&b)) + || ((128..256).contains(&a) && (128..256).contains(&b)) + { + let result = if a > b { a - b } else { b - a }; + + if result <= SEQUENCE_WINDOW as usize { + // RFC1982 + a.partial_cmp(&b) + } else { + // This case is not comparable. + None + } + } else { + unreachable!(); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sequence_counter_increment() { + let mut seq = SequenceCounter::new(253); + seq.increment(); + assert_eq!(seq.value(), 254); + seq.increment(); + assert_eq!(seq.value(), 255); + seq.increment(); + assert_eq!(seq.value(), 0); + + let mut seq = SequenceCounter::new(126); + seq.increment(); + assert_eq!(seq.value(), 127); + seq.increment(); + assert_eq!(seq.value(), 0); + } + + #[test] + fn sequence_counter_comparison() { + use core::cmp::Ordering; + + assert!(SequenceCounter::new(240) != SequenceCounter::new(1)); + assert!(SequenceCounter::new(1) != SequenceCounter::new(240)); + assert!(SequenceCounter::new(1) != SequenceCounter::new(240)); + assert!(SequenceCounter::new(240) == SequenceCounter::new(240)); + assert!(SequenceCounter::new(240 - 17) != SequenceCounter::new(240)); + + assert_eq!( + SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(5)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(250).partial_cmp(&SequenceCounter::new(5)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(5).partial_cmp(&SequenceCounter::new(250)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(127).partial_cmp(&SequenceCounter::new(129)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(121)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(121).partial_cmp(&SequenceCounter::new(120)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(241)), + Some(Ordering::Less) + ); + assert_eq!( + SequenceCounter::new(241).partial_cmp(&SequenceCounter::new(240)), + Some(Ordering::Greater) + ); + assert_eq!( + SequenceCounter::new(120).partial_cmp(&SequenceCounter::new(120)), + Some(Ordering::Equal) + ); + assert_eq!( + SequenceCounter::new(240).partial_cmp(&SequenceCounter::new(240)), + Some(Ordering::Equal) + ); + assert_eq!( + SequenceCounter::new(130).partial_cmp(&SequenceCounter::new(241)), + None + ); + } +} diff --git a/vendor/smoltcp/src/iface/rpl/mod.rs b/vendor/smoltcp/src/iface/rpl/mod.rs new file mode 100644 index 00000000..69aa9ae7 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/mod.rs @@ -0,0 +1,9 @@ +#![allow(unused)] + +mod consts; +mod lollipop; +mod of0; +mod parents; +mod rank; +mod relations; +mod trickle; diff --git a/vendor/smoltcp/src/iface/rpl/of0.rs b/vendor/smoltcp/src/iface/rpl/of0.rs new file mode 100644 index 00000000..299938f8 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/of0.rs @@ -0,0 +1,128 @@ +use super::parents::*; +use super::rank::Rank; + +pub struct ObjectiveFunction0; + +pub(crate) trait ObjectiveFunction { + const OCP: u16; + + /// Return the new calculated Rank, based on information from the parent. + fn rank(current_rank: Rank, parent_rank: Rank) -> Rank; + + /// Return the preferred parent from a given parent set. + fn preferred_parent(parent_set: &ParentSet) -> Option<&Parent>; +} + +impl ObjectiveFunction0 { + const OCP: u16 = 0; + + const RANK_STRETCH: u16 = 0; + const RANK_FACTOR: u16 = 1; + const RANK_STEP: u16 = 3; + + fn rank_increase(parent_rank: Rank) -> u16 { + (Self::RANK_FACTOR * Self::RANK_STEP + Self::RANK_STRETCH) + * parent_rank.min_hop_rank_increase + } +} + +impl ObjectiveFunction for ObjectiveFunction0 { + const OCP: u16 = 0; + + fn rank(_: Rank, parent_rank: Rank) -> Rank { + assert_ne!(parent_rank, Rank::INFINITE); + + Rank::new( + parent_rank.value + Self::rank_increase(parent_rank), + parent_rank.min_hop_rank_increase, + ) + } + + fn preferred_parent(parent_set: &ParentSet) -> Option<&Parent> { + let mut pref_parent: Option<&Parent> = None; + + for (_, parent) in parent_set.parents() { + if pref_parent.is_none() || parent.rank() < pref_parent.unwrap().rank() { + pref_parent = Some(parent); + } + } + + pref_parent + } +} + +#[cfg(test)] +mod tests { + use crate::iface::rpl::consts::DEFAULT_MIN_HOP_RANK_INCREASE; + + use super::*; + + #[test] + fn rank_increase() { + // 256 (root) + 3 * 256 + assert_eq!( + ObjectiveFunction0::rank(Rank::INFINITE, Rank::ROOT), + Rank::new(256 + 3 * 256, DEFAULT_MIN_HOP_RANK_INCREASE) + ); + + // 1024 + 3 * 256 + assert_eq!( + ObjectiveFunction0::rank( + Rank::INFINITE, + Rank::new(1024, DEFAULT_MIN_HOP_RANK_INCREASE) + ), + Rank::new(1024 + 3 * 256, DEFAULT_MIN_HOP_RANK_INCREASE) + ); + } + + #[test] + #[should_panic] + fn rank_increase_infinite() { + assert_eq!( + ObjectiveFunction0::rank(Rank::INFINITE, Rank::INFINITE), + Rank::INFINITE + ); + } + + #[test] + fn empty_set() { + assert_eq!( + ObjectiveFunction0::preferred_parent(&ParentSet::default()), + None + ); + } + + #[test] + fn non_empty_set() { + use crate::wire::Ipv6Address; + + let mut parents = ParentSet::default(); + + parents.add( + Ipv6Address::UNSPECIFIED, + Parent::new(0, Rank::ROOT, Default::default(), Ipv6Address::UNSPECIFIED), + ); + + let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 1); + + parents.add( + address, + Parent::new( + 0, + Rank::new(1024, DEFAULT_MIN_HOP_RANK_INCREASE), + Default::default(), + Ipv6Address::UNSPECIFIED, + ), + ); + + assert_eq!( + ObjectiveFunction0::preferred_parent(&parents), + Some(&Parent::new( + 0, + Rank::ROOT, + Default::default(), + Ipv6Address::UNSPECIFIED, + )) + ); + } +} diff --git a/vendor/smoltcp/src/iface/rpl/parents.rs b/vendor/smoltcp/src/iface/rpl/parents.rs new file mode 100644 index 00000000..7d4d60e2 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/parents.rs @@ -0,0 +1,173 @@ +use crate::wire::Ipv6Address; + +use super::{lollipop::SequenceCounter, rank::Rank}; +use crate::config::RPL_PARENTS_BUFFER_COUNT; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct Parent { + rank: Rank, + preference: u8, + version_number: SequenceCounter, + dodag_id: Ipv6Address, +} + +impl Parent { + /// Create a new parent. + pub(crate) fn new( + preference: u8, + rank: Rank, + version_number: SequenceCounter, + dodag_id: Ipv6Address, + ) -> Self { + Self { + rank, + preference, + version_number, + dodag_id, + } + } + + /// Return the Rank of the parent. + pub(crate) fn rank(&self) -> &Rank { + &self.rank + } +} + +#[derive(Debug, Default)] +pub(crate) struct ParentSet { + parents: heapless::LinearMap, +} + +impl ParentSet { + /// Add a new parent to the parent set. The Rank of the new parent should be lower than the + /// Rank of the node that holds this parent set. + pub(crate) fn add(&mut self, address: Ipv6Address, parent: Parent) { + if let Some(p) = self.parents.get_mut(&address) { + *p = parent; + } else if let Err(p) = self.parents.insert(address, parent) { + if let Some((w_a, w_p)) = self.worst_parent() { + if w_p.rank.dag_rank() > parent.rank.dag_rank() { + self.parents.remove(&w_a.clone()).unwrap(); + self.parents.insert(address, parent).unwrap(); + } else { + net_debug!("could not add {} to parent set, buffer is full", address); + } + } else { + unreachable!() + } + } + } + + /// Find a parent based on its address. + pub(crate) fn find(&self, address: &Ipv6Address) -> Option<&Parent> { + self.parents.get(address) + } + + /// Find a mutable parent based on its address. + pub(crate) fn find_mut(&mut self, address: &Ipv6Address) -> Option<&mut Parent> { + self.parents.get_mut(address) + } + + /// Return a slice to the parent set. + pub(crate) fn parents(&self) -> impl Iterator { + self.parents.iter() + } + + /// Find the worst parent that is currently in the parent set. + fn worst_parent(&self) -> Option<(&Ipv6Address, &Parent)> { + self.parents.iter().max_by_key(|(k, v)| v.rank.dag_rank()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add_parent() { + let mut set = ParentSet::default(); + set.add( + Ipv6Address::UNSPECIFIED, + Parent::new(0, Rank::ROOT, Default::default(), Ipv6Address::UNSPECIFIED), + ); + + assert_eq!( + set.find(&Ipv6Address::UNSPECIFIED), + Some(&Parent::new( + 0, + Rank::ROOT, + Default::default(), + Ipv6Address::UNSPECIFIED + )) + ); + } + + #[test] + fn add_more_parents() { + use super::super::consts::DEFAULT_MIN_HOP_RANK_INCREASE; + let mut set = ParentSet::default(); + + let mut last_address = Ipv6Address::UNSPECIFIED; + for i in 0..RPL_PARENTS_BUFFER_COUNT { + let i = i as u16; + let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, i as _); + last_address = address; + + set.add( + address, + Parent::new( + 0, + Rank::new(256 * i, DEFAULT_MIN_HOP_RANK_INCREASE), + Default::default(), + address, + ), + ); + + assert_eq!( + set.find(&address), + Some(&Parent::new( + 0, + Rank::new(256 * i, DEFAULT_MIN_HOP_RANK_INCREASE), + Default::default(), + address, + )) + ); + } + + // This one is not added to the set, because its Rank is worse than any other parent in the + // set. + let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 8); + set.add( + address, + Parent::new( + 0, + Rank::new(256 * 8, DEFAULT_MIN_HOP_RANK_INCREASE), + Default::default(), + address, + ), + ); + assert_eq!(set.find(&address), None); + + /// This Parent has a better rank than the last one in the set. + let address = Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 9); + set.add( + address, + Parent::new( + 0, + Rank::new(0, DEFAULT_MIN_HOP_RANK_INCREASE), + Default::default(), + address, + ), + ); + assert_eq!( + set.find(&address), + Some(&Parent::new( + 0, + Rank::new(0, DEFAULT_MIN_HOP_RANK_INCREASE), + Default::default(), + address + )) + ); + assert_eq!(set.find(&last_address), None); + } +} diff --git a/vendor/smoltcp/src/iface/rpl/rank.rs b/vendor/smoltcp/src/iface/rpl/rank.rs new file mode 100644 index 00000000..02a5ecf5 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/rank.rs @@ -0,0 +1,104 @@ +//! Implementation of the Rank comparison in RPL. +//! +//! A Rank can be thought of as a fixed-point number, where the position of the radix point between +//! the integer part and the fractional part is determined by `MinHopRankIncrease`. +//! `MinHopRankIncrease` is the minimum increase in Rank between a node and any of its DODAG +//! parents. +//! This value is provisined by the DODAG root. +//! +//! When Rank is compared, the integer portion of the Rank is to be used. +//! +//! Meaning of the comparison: +//! - **Rank M is less than Rank N**: the position of M is closer to the DODAG root than the position +//! of N. Node M may safely be a DODAG parent for node N. +//! - **Ranks are equal**: the positions of both nodes within the DODAG and with respect to the DODAG +//! are similar or identical. Routing through a node with equal Rank may cause a routing loop. +//! - **Rank M is greater than Rank N**: the position of node M is farther from the DODAG root +//! than the position of N. Node M may in fact be in the sub-DODAG of node N. If node N selects +//! node M as a DODAG parent, there is a risk of creating a loop. + +use super::consts::DEFAULT_MIN_HOP_RANK_INCREASE; + +/// The Rank is the expression of the relative position within a DODAG Version with regard to +/// neighbors, and it is not necessarily a good indication or a proper expression of a distance or +/// a path cost to the root. +#[derive(Debug, Clone, Copy, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Rank { + pub(super) value: u16, + pub(super) min_hop_rank_increase: u16, +} + +impl core::fmt::Display for Rank { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Rank({})", self.dag_rank()) + } +} + +impl Rank { + pub const INFINITE: Self = Rank::new(0xffff, DEFAULT_MIN_HOP_RANK_INCREASE); + + /// The ROOT_RANK is the smallest rank possible. + /// DAG_RANK(ROOT_RANK) should be 1. See RFC6550 § 17. + pub const ROOT: Self = Rank::new(DEFAULT_MIN_HOP_RANK_INCREASE, DEFAULT_MIN_HOP_RANK_INCREASE); + + /// Create a new Rank from some value and a `MinHopRankIncrease`. + /// The `MinHopRankIncrease` is used for calculating the integer part for comparing to other + /// Ranks. + pub const fn new(value: u16, min_hop_rank_increase: u16) -> Self { + assert!(min_hop_rank_increase > 0); + + Self { + value, + min_hop_rank_increase, + } + } + + /// Return the integer part of the Rank. + pub fn dag_rank(&self) -> u16 { + self.value / self.min_hop_rank_increase + } + + /// Return the raw Rank value. + pub fn raw_value(&self) -> u16 { + self.value + } +} + +impl PartialEq for Rank { + fn eq(&self, other: &Self) -> bool { + self.dag_rank() == other.dag_rank() + } +} + +impl PartialOrd for Rank { + fn partial_cmp(&self, other: &Self) -> Option { + self.dag_rank().partial_cmp(&other.dag_rank()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn calculate_rank() { + let r = Rank::new(27, 16); + assert_eq!(r.dag_rank(), 1) + } + + #[test] + fn comparison() { + let r1 = Rank::ROOT; + let r2 = Rank::new(16, 16); + assert!(r1 == r2); + + let r1 = Rank::new(16, 16); + let r2 = Rank::new(32, 16); + assert!(r1 < r2); + + let r1 = Rank::ROOT; + let r2 = Rank::INFINITE; + assert!(r1 < r2); + } +} diff --git a/vendor/smoltcp/src/iface/rpl/relations.rs b/vendor/smoltcp/src/iface/rpl/relations.rs new file mode 100644 index 00000000..c2972f2e --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/relations.rs @@ -0,0 +1,158 @@ +use crate::time::Instant; +use crate::wire::Ipv6Address; + +use crate::config::RPL_RELATIONS_BUFFER_COUNT; + +#[derive(Debug)] +pub struct Relation { + destination: Ipv6Address, + next_hop: Ipv6Address, + expiration: Instant, +} + +#[derive(Default, Debug)] +pub struct Relations { + relations: heapless::Vec, +} + +impl Relations { + /// Add a new relation to the buffer. If there was already a relation in the buffer, then + /// update it. + pub fn add_relation( + &mut self, + destination: Ipv6Address, + next_hop: Ipv6Address, + expiration: Instant, + ) { + if let Some(r) = self + .relations + .iter_mut() + .find(|r| r.destination == destination) + { + r.next_hop = next_hop; + r.expiration = expiration; + } else { + let relation = Relation { + destination, + next_hop, + expiration, + }; + + if let Err(e) = self.relations.push(relation) { + net_debug!("Unable to add relation, buffer is full"); + } + } + } + + /// Remove all relation entries for a specific destination. + pub fn remove_relation(&mut self, destination: Ipv6Address) { + self.relations.retain(|r| r.destination != destination) + } + + /// Return the next hop for a specific IPv6 address, if there is one. + pub fn find_next_hop(&mut self, destination: Ipv6Address) -> Option { + self.relations.iter().find_map(|r| { + if r.destination == destination { + Some(r.next_hop) + } else { + None + } + }) + } + + /// Purge expired relations. + pub fn purge(&mut self, now: Instant) { + self.relations.retain(|r| r.expiration > now) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::time::Duration; + + fn addresses(count: usize) -> Vec { + (0..count) + .map(|i| Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, i as _)) + .collect() + } + + #[test] + fn add_relation() { + let addrs = addresses(2); + + let mut relations = Relations::default(); + relations.add_relation(addrs[0], addrs[1], Instant::now()); + assert_eq!(relations.relations.len(), 1); + } + + #[test] + fn add_relations_full_buffer() { + let addrs = addresses(crate::config::RPL_RELATIONS_BUFFER_COUNT + 1); + + // Try to add RPL_RELATIONS_BUFFER_COUNT + 1 to the buffer. + // The size of the buffer should still be RPL_RELATIONS_BUFFER_COUNT. + let mut relations = Relations::default(); + for a in addrs { + relations.add_relation(a, a, Instant::now()); + } + + assert_eq!(relations.relations.len(), RPL_RELATIONS_BUFFER_COUNT); + } + + #[test] + fn update_relation() { + let addrs = addresses(3); + + let mut relations = Relations::default(); + relations.add_relation(addrs[0], addrs[1], Instant::now()); + assert_eq!(relations.relations.len(), 1); + + relations.add_relation(addrs[0], addrs[2], Instant::now()); + assert_eq!(relations.relations.len(), 1); + + assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[2])); + } + + #[test] + fn find_next_hop() { + let addrs = addresses(3); + + let mut relations = Relations::default(); + relations.add_relation(addrs[0], addrs[1], Instant::now()); + assert_eq!(relations.relations.len(), 1); + assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[1])); + + relations.add_relation(addrs[0], addrs[2], Instant::now()); + assert_eq!(relations.relations.len(), 1); + assert_eq!(relations.find_next_hop(addrs[0]), Some(addrs[2])); + + // Find the next hop of a destination not in the buffer. + assert_eq!(relations.find_next_hop(addrs[1]), None); + } + + #[test] + fn remove_relation() { + let addrs = addresses(2); + + let mut relations = Relations::default(); + relations.add_relation(addrs[0], addrs[1], Instant::now()); + assert_eq!(relations.relations.len(), 1); + + relations.remove_relation(addrs[0]); + assert!(relations.relations.is_empty()); + } + + #[test] + fn purge_relation() { + let addrs = addresses(2); + + let mut relations = Relations::default(); + relations.add_relation(addrs[0], addrs[1], Instant::now() - Duration::from_secs(1)); + + assert_eq!(relations.relations.len(), 1); + + relations.purge(Instant::now()); + assert!(relations.relations.is_empty()); + } +} diff --git a/vendor/smoltcp/src/iface/rpl/trickle.rs b/vendor/smoltcp/src/iface/rpl/trickle.rs new file mode 100644 index 00000000..a5b3b979 --- /dev/null +++ b/vendor/smoltcp/src/iface/rpl/trickle.rs @@ -0,0 +1,266 @@ +//! Implementation of the Trickle timer defined in [RFC 6206]. The algorithm allows node in a lossy +//! shared medium to exchange information in a highly robust, energy efficient, simple, and +//! scalable manner. Dynamically adjusting transmission windows allows Trickle to spread new +//! information fast while sending only a few messages per hour when information does not change. +//! +//! **NOTE**: the constants used for the default Trickle timer are the ones from the [Enhanced +//! Trickle]. +//! +//! [RFC 6206]: https://datatracker.ietf.org/doc/html/rfc6206 +//! [Enhanced Trickle]: https://d1wqtxts1xzle7.cloudfront.net/71402623/E-Trickle_Enhanced_Trickle_Algorithm_for20211005-2078-1ckh34a.pdf?1633439582=&response-content-disposition=inline%3B+filename%3DE_Trickle_Enhanced_Trickle_Algorithm_for.pdf&Expires=1681472005&Signature=cC7l-Pyr5r64XBNCDeSJ2ha6oqWUtO6A-KlDOyC0UVaHxDV3h3FuVHRtcNp3O9BUfRK8jeuWCYGBkCZgQT4Zgb6XwgVB-3z4TF9o3qBRMteRyYO5vjVkpPBeN7mz4Tl746SsSCHDm2NMtr7UVtLYamriU3D0rryoqLqJXmnkNoJpn~~wJe2H5PmPgIwixTwSvDkfFLSVoESaYS9ZWHZwbW-7G7OxIw8oSYhx9xMBnzkpdmT7sJNmvDzTUhoOjYrHTRM23cLVS9~oOSpT7hKtKD4h5CSmrNK4st07KnT9~tUqEcvGO3aXdd4quRZeKUcCkCbTLvhOEYg9~QqgD8xwhA__&Key-Pair-Id=APKAJLOHF5GGSLRBV4ZA + +use crate::{ + rand::Rand, + time::{Duration, Instant}, +}; + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct TrickleTimer { + i_min: u32, + i_max: u32, + k: usize, + + i: Duration, + t: Duration, + t_exp: Instant, + i_exp: Instant, + counter: usize, +} + +impl TrickleTimer { + /// Creat a new Trickle timer using the default values. + /// + /// **NOTE**: the standard defines I as a random value between [Imin, Imax]. However, this + /// could result in a t value that is very close to Imax. Therefore, sending DIO messages will + /// be sporadic, which is not ideal when a network is started. It might take a long time before + /// the network is actually stable. Therefore, we don't draw a random numberm but just use Imin + /// for I. This only affects the start of the RPL tree and speeds up building it. Also, we + /// don't use the default values from the standard, but the values from the _Enhanced Trickle + /// Algorithm for Low-Power and Lossy Networks_ from Baraq Ghaleb et al. This is also what the + /// Contiki Trickle timer does. + pub(crate) fn default(now: Instant, rand: &mut Rand) -> Self { + use super::consts::{ + DEFAULT_DIO_INTERVAL_DOUBLINGS, DEFAULT_DIO_INTERVAL_MIN, + DEFAULT_DIO_REDUNDANCY_CONSTANT, + }; + + Self::new( + DEFAULT_DIO_INTERVAL_MIN, + DEFAULT_DIO_INTERVAL_MIN + DEFAULT_DIO_INTERVAL_DOUBLINGS, + DEFAULT_DIO_REDUNDANCY_CONSTANT, + now, + rand, + ) + } + + /// Create a new Trickle timer. + pub(crate) fn new(i_min: u32, i_max: u32, k: usize, now: Instant, rand: &mut Rand) -> Self { + let mut timer = Self { + i_min, + i_max, + k, + i: Duration::ZERO, + t: Duration::ZERO, + t_exp: Instant::ZERO, + i_exp: Instant::ZERO, + counter: 0, + }; + + timer.i = Duration::from_millis(2u32.pow(timer.i_min) as u64); + timer.i_exp = now + timer.i; + timer.counter = 0; + + timer.set_t(now, rand); + + timer + } + + /// Poll the Trickle timer. Returns `true` when the Trickle timer signals that a message can be + /// transmitted. This happens when the Trickle timer expires. + pub(crate) fn poll(&mut self, now: Instant, rand: &mut Rand) -> bool { + let can_transmit = self.can_transmit() && self.t_expired(now); + + if can_transmit { + self.set_t(now, rand); + } + + if self.i_expired(now) { + self.expire(now, rand); + } + + can_transmit + } + + /// Returns the Instant at which the Trickle timer should be polled again. Polling the Trickle + /// timer before this Instant is not harmfull, however, polling after it is not correct. + pub(crate) fn poll_at(&self) -> Instant { + self.t_exp.min(self.i_exp) + } + + /// Signal the Trickle timer that a consistency has been heard, and thus increasing it's + /// counter. + pub(crate) fn hear_consistent(&mut self) { + self.counter += 1; + } + + /// Signal the Trickle timer that an inconsistency has been heard. This resets the Trickle + /// timer when the current interval is not the smallest possible. + pub(crate) fn hear_inconsistency(&mut self, now: Instant, rand: &mut Rand) { + let i = Duration::from_millis(2u32.pow(self.i_min) as u64); + if self.i > i { + self.reset(i, now, rand); + } + } + + /// Check if the Trickle timer can transmit or not. Returns `false` when the consistency + /// counter is bigger or equal to the default consistency constant. + pub(crate) fn can_transmit(&self) -> bool { + self.k != 0 && self.counter < self.k + } + + /// Reset the Trickle timer when the interval has expired. + fn expire(&mut self, now: Instant, rand: &mut Rand) { + let max_interval = Duration::from_millis(2u32.pow(self.i_max) as u64); + let i = if self.i >= max_interval { + max_interval + } else { + self.i + self.i + }; + + self.reset(i, now, rand); + } + + pub(crate) fn reset(&mut self, i: Duration, now: Instant, rand: &mut Rand) { + self.i = i; + self.i_exp = now + self.i; + self.counter = 0; + self.set_t(now, rand); + } + + pub(crate) const fn max_expiration(&self) -> Duration { + Duration::from_millis(2u32.pow(self.i_max) as u64) + } + + pub(crate) const fn min_expiration(&self) -> Duration { + Duration::from_millis(2u32.pow(self.i_min) as u64) + } + + fn set_t(&mut self, now: Instant, rand: &mut Rand) { + let t = Duration::from_micros( + self.i.total_micros() / 2 + + (rand.rand_u32() as u64 + % (self.i.total_micros() - self.i.total_micros() / 2 + 1)), + ); + + self.t = t; + self.t_exp = now + t; + } + + fn t_expired(&self, now: Instant) -> bool { + now >= self.t_exp + } + + fn i_expired(&self, now: Instant) -> bool { + now >= self.i_exp + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn trickle_timer_intervals() { + let mut rand = Rand::new(1234); + let mut now = Instant::ZERO; + let mut trickle = TrickleTimer::default(now, &mut rand); + + let mut previous_i = trickle.i; + + while now <= Instant::from_secs(100_000) { + trickle.poll(now, &mut rand); + + if now < Instant::ZERO + trickle.max_expiration() { + // t should always be inbetween I/2 and I. + assert!(trickle.i / 2 < trickle.t); + assert!(trickle.i > trickle.t); + } + + if previous_i != trickle.i { + // When a new Interval is selected, this should be double the previous one. + assert_eq!(previous_i * 2, trickle.i); + assert_eq!(trickle.counter, 0); + previous_i = trickle.i; + } + + now += Duration::from_millis(100); + } + } + + #[test] + fn trickle_timer_hear_inconsistency() { + let mut rand = Rand::new(1234); + let mut now = Instant::ZERO; + let mut trickle = TrickleTimer::default(now, &mut rand); + + trickle.counter = 1; + + while now <= Instant::from_secs(10_000) { + trickle.poll(now, &mut rand); + + if now < trickle.i_exp && now < Instant::ZERO + trickle.min_expiration() { + assert_eq!(trickle.counter, 1); + } else { + // The first interval expired, so the counter is reset. + assert_eq!(trickle.counter, 0); + } + + if now == Instant::from_secs(10) { + // We set the counter to 1 such that we can test the `hear_inconsistency`. + trickle.counter = 1; + + assert_eq!(trickle.counter, 1); + + trickle.hear_inconsistency(now, &mut rand); + + assert_eq!(trickle.counter, 0); + assert_eq!(trickle.i, trickle.min_expiration()); + } + + now += Duration::from_millis(100); + } + } + + #[test] + fn trickle_timer_hear_consistency() { + let mut rand = Rand::new(1234); + let mut now = Instant::ZERO; + let mut trickle = TrickleTimer::default(now, &mut rand); + + trickle.counter = 1; + + let mut transmit_counter = 0; + + while now <= Instant::from_secs(10_000) { + trickle.hear_consistent(); + + if trickle.poll(now, &mut rand) { + transmit_counter += 1; + } + + if now == Instant::from_secs(10_000) { + use super::super::consts::{ + DEFAULT_DIO_INTERVAL_DOUBLINGS, DEFAULT_DIO_REDUNDANCY_CONSTANT, + }; + assert!(!trickle.poll(now, &mut rand)); + assert!(trickle.counter > DEFAULT_DIO_REDUNDANCY_CONSTANT); + // We should never have transmitted since the counter was higher than the default + // redundancy constant. + assert_eq!(transmit_counter, 0); + } + + now += Duration::from_millis(100); + } + } +} diff --git a/vendor/smoltcp/src/iface/slaac.rs b/vendor/smoltcp/src/iface/slaac.rs new file mode 100644 index 00000000..898f3331 --- /dev/null +++ b/vendor/smoltcp/src/iface/slaac.rs @@ -0,0 +1,492 @@ +#![deny(missing_docs)] +use heapless::{LinearMap, Vec}; + +use crate::config::{IFACE_MAX_PREFIX_COUNT, IFACE_MAX_ROUTE_COUNT}; +use crate::time::{Duration, Instant}; +use crate::wire::NdiscPrefixInfoFlags; +use crate::wire::{Ipv6Address, Ipv6Cidr, NdiscPrefixInformation, ipv6::AddressExt}; + +const MAX_RTR_SOLICITATIONS: u8 = 3; +const RTR_SOLICITATION_INTERVAL: Duration = Duration::from_secs(4); +const IPV6_DEFAULT: Ipv6Cidr = Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0); + +/// Router solicitation state machine +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Phase { + Start, + Discovering, + Maintaining, + None, +} + +/// A prefix of addresses received via router advertisements +#[derive(Debug, Clone, Copy)] +pub(crate) struct Route { + /// IPv6 cidr to route + pub cidr: Ipv6Cidr, + /// Router, origin of the advertisement + pub via_router: Ipv6Address, + /// Valid lifetime of the route + pub valid_until: Instant, +} + +/// Info associated with a prefix +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PrefixInfo { + preferred_until: Instant, + valid_until: Instant, +} + +impl PrefixInfo { + fn new(preferred_until: Instant, valid_until: Instant) -> Self { + Self { + preferred_until, + valid_until, + } + } + + /// Derive the prefix information from the neighbor discovery option. + pub(crate) fn from_prefix(prefix: &NdiscPrefixInformation, now: Instant) -> Self { + let preferred_until = now + prefix.preferred_lifetime; + let valid_until = now + prefix.valid_lifetime; + + Self::new(preferred_until, valid_until) + } + + /// Get whether the prefix is still valid. + pub(crate) fn is_valid(&self, now: Instant) -> bool { + self.valid_until > now + } +} + +impl Route { + /// Compare this route based on the prefix and the next hop router. + pub fn same_route(&self, cidr: &Ipv6Cidr, via_router: &Ipv6Address) -> bool { + self.cidr == *cidr && self.via_router == *via_router + } + + /// Get whether the route is still valid. + pub fn is_valid(&self, now: Instant) -> bool { + self.valid_until > now + } +} + +/// SLAAC runtime state +/// +/// Tracks router solicitations and collects information from all received +/// router advertisements. +/// +/// State must be synchronized with the IP addresses and routes in the `Interface`. +#[derive(Debug)] +pub struct Slaac { + /// Set of prefixes received. + prefix: LinearMap, + /// Set of routes received. + routes: Vec, + /// Router discovery phase. + phase: Phase, + /// Signal for address and route updates. + sync_required: bool, + /// Time to next router solicitation. + retry_rs_at: Instant, + /// Number of solicitations emitted. + num_solicitations: u8, +} + +impl Slaac { + pub(super) fn new() -> Self { + Self { + prefix: LinearMap::new(), + routes: Vec::new(), + phase: Phase::Start, + sync_required: false, + retry_rs_at: Instant::from_millis(0), + num_solicitations: MAX_RTR_SOLICITATIONS, + } + } + + /// Get whether router advertisement information is updated. + /// + /// This flags whether new prefixes or routes have been received, or current prefixes and + /// routes have expired. + pub(crate) fn has_ra_update(&self) -> bool { + self.sync_required + } + + /// Get a reference to the map of prefixes stored. + pub(crate) fn prefix(&self) -> &LinearMap { + &self.prefix + } + + /// Get a reference to the set of routes stored. + pub(crate) fn routes(&self) -> &Vec { + &self.routes + } + + fn add_prefix(&mut self, cidr: &Ipv6Cidr, prefix: &NdiscPrefixInformation, now: Instant) { + if cidr.address().is_link_local() { + return; + } + let prefix_info = PrefixInfo::from_prefix(prefix, now); + if let Ok(old_info) = self.prefix.insert(*cidr, prefix_info) { + if old_info.is_none() { + self.sync_required = true; + } + } + } + + fn expire_prefix(&mut self, cidr: &Ipv6Cidr) { + if let Some(info) = self.prefix.get_mut(cidr) { + info.valid_until = Instant::from_millis(0); + info.preferred_until = Instant::from_millis(0); + self.sync_required = true; + } + } + + fn add_route(&mut self, cidr: &Ipv6Cidr, router: &Ipv6Address, valid_until: Instant) { + if let Some(route) = self.routes.iter_mut().find(|r| r.same_route(cidr, router)) { + route.valid_until = valid_until; + } else { + let _ = self.routes.push(Route { + cidr: *cidr, + via_router: *router, + valid_until, + }); + self.sync_required = true; + } + } + + fn expire_route(&mut self, cidr: &Ipv6Cidr, via_router: &Ipv6Address) { + for route in self.routes.iter_mut() { + if route.same_route(cidr, via_router) { + route.valid_until = Instant::from_millis(0); + self.sync_required = true; + } + } + } + + fn process_prefix(&mut self, prefix: NdiscPrefixInformation, now: Instant) { + if !prefix.flags.contains(NdiscPrefixInfoFlags::ADDRCONF) { + return; + } + + let cidr = Ipv6Cidr::new(prefix.prefix, prefix.prefix_len); + + if prefix.valid_lifetime > Duration::ZERO { + self.add_prefix(&cidr, &prefix, now); + } else { + self.expire_prefix(&cidr); + } + } + + /// Process a router advertisement's information. + pub(super) fn process_advertisement( + &mut self, + source: &Ipv6Address, + router_lifetime: Duration, // default route lifetime + prefix: Option, // prefix info + now: Instant, + ) { + if let Some(prefix) = prefix { + if prefix.is_valid_prefix_info() { + self.process_prefix(prefix, now) + } + } + + if router_lifetime > Duration::ZERO { + self.add_route(&IPV6_DEFAULT, source, now + router_lifetime); + } else { + self.expire_route(&IPV6_DEFAULT, source); + } + + // Advertisement might be unsolicited + if self.phase == Phase::Discovering { + self.phase = Phase::Maintaining; + } + } + + fn prefix_expire_sync_required(&self, now: Instant) -> bool { + self.prefix.values().any(|info| !info.is_valid(now)) + } + + fn route_expire_sync_required(&self, now: Instant) -> bool { + self.routes.iter().any(|r| !r.is_valid(now)) + } + + /// Get whether a route and prefix information must be synchronized with the interface. + pub(crate) fn sync_required(&self, now: Instant) -> bool { + self.has_ra_update() + || self.prefix_expire_sync_required(now) + || self.route_expire_sync_required(now) + } + + /// Remove expired routes and prefixes. + pub(crate) fn update_slaac_state(&mut self, now: Instant) { + let removals: Vec = self + .prefix + .iter() + .filter_map(|(cidr, info)| { + if info.is_valid(now) { + None + } else { + Some(*cidr) + } + }) + .collect(); + for cidr in removals.iter() { + self.prefix.remove(cidr); + } + self.routes.retain(|r| r.is_valid(now)); + self.sync_required = false; + } + + /// Get whether a router solicitation must be emitted. + pub(crate) fn rs_required(&self, now: Instant) -> bool { + match self.phase { + Phase::Start | Phase::Discovering + if self.retry_rs_at <= now && self.num_solicitations > 0 => + { + true + } + _ => false, + } + } + + /// Update router solicitation tracking state + /// + /// Must be called after sending a router solicitation on the interface. + pub(crate) fn rs_sent(&mut self, now: Instant) { + match self.phase { + Phase::Start | Phase::Discovering if self.retry_rs_at <= now => { + if self.num_solicitations == 0 { + self.phase = Phase::None; + } else { + self.num_solicitations -= 1; + self.phase = Phase::Discovering; + self.retry_rs_at = now + RTR_SOLICITATION_INTERVAL; + } + } + _ => (), + } + } + + /// Get the next time the SLAAC state must be polled for updates. + pub(crate) fn poll_at(&self, now: Instant) -> Option { + match self.phase { + Phase::Discovering | Phase::Start => Some(self.retry_rs_at), + Phase::Maintaining => { + let prefix_at = self.prefix.values().filter_map(|prefix_info| { + if prefix_info.is_valid(now) { + Some(prefix_info.valid_until) + } else { + None + } + }); + let routes_at = self.routes.iter().filter_map(|r| { + if r.is_valid(now) { + Some(r.valid_until) + } else { + None + } + }); + prefix_at.chain(routes_at).min() + } + _ => None, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + mod mock { + use super::super::*; + pub const SOURCE: Ipv6Address = Ipv6Address::new(0xfe80, 0xdb8, 0, 0, 0, 0, 0, 0); + pub const PREFIX: NdiscPrefixInformation = NdiscPrefixInformation { + prefix_len: 64, + flags: NdiscPrefixInfoFlags::ADDRCONF, + valid_lifetime: Duration::from_secs(700), + preferred_lifetime: Duration::from_secs(300), + prefix: Ipv6Address::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), + }; + pub const VALID: Duration = Duration::from_secs(600); + + pub const ROUTE: Route = Route { + cidr: Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0), + via_router: SOURCE, + valid_until: Instant::from_millis_const(100000), + }; + } + use mock::*; + + #[test] + fn test_route() { + assert!(ROUTE.same_route(&Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0), &SOURCE)); + assert!(!ROUTE.same_route(&Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 64), &SOURCE)); + assert!(!ROUTE.same_route( + &Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0), + &Ipv6Address::UNSPECIFIED + )); + assert!(!ROUTE.same_route(&Ipv6Cidr::new(SOURCE, 0), &Ipv6Address::UNSPECIFIED)); + assert!(!ROUTE.same_route(&Ipv6Cidr::new(SOURCE, 64), &Ipv6Address::UNSPECIFIED)); + } + + #[test] + fn test_route_valid() { + assert!(ROUTE.is_valid(Instant::ZERO)); + assert!(!ROUTE.is_valid(Instant::from_secs(200))); + } + + #[test] + fn test_solicitation() { + let mut slaac = Slaac::new(); + let now = Instant::from_millis(1); + assert!(slaac.rs_required(now)); + + slaac.rs_sent(now); + assert_eq!(slaac.num_solicitations, 2); + assert!(!slaac.rs_required(now)); + + let next_poll = slaac.poll_at(now).unwrap(); + assert_eq!(next_poll, now + RTR_SOLICITATION_INTERVAL); + + let now = next_poll; + assert!(slaac.rs_required(now)); + + slaac.num_solicitations = 0; + assert!(!slaac.rs_required(now)); + slaac.rs_sent(now); + assert_eq!(slaac.phase, Phase::None); + assert!(slaac.poll_at(now).is_none()); + } + + #[test] + fn test_ra_state() { + let mut slaac = Slaac::new(); + assert_eq!(slaac.phase, Phase::Start); + let now = Instant::from_millis(1); + assert!(!slaac.has_ra_update()); + + // Unsolicited advertisement + slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now); + assert_eq!(slaac.phase, Phase::Start); + assert!(slaac.has_ra_update()); + + let now = Instant::from_secs(300); + slaac.rs_sent(now); + assert_eq!(slaac.phase, Phase::Discovering); + + // Solicited advertisement + slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now); + slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now); + assert_eq!(slaac.phase, Phase::Maintaining); + let poll_at = slaac.poll_at(now).unwrap(); + assert_eq!(poll_at, now + VALID); + + for (prefix, info) in slaac.prefix() { + assert_eq!(prefix.address(), PREFIX.prefix); + assert_eq!(prefix.prefix_len(), PREFIX.prefix_len); + assert_eq!(info.valid_until, now + PREFIX.valid_lifetime); + assert_eq!(info.preferred_until, now + PREFIX.preferred_lifetime); + assert!(info.is_valid(now)); + } + + for route in slaac.routes() { + assert_eq!(route.cidr, Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 0)); + assert_eq!(route.via_router, SOURCE); + assert_eq!(route.valid_until, now + VALID); + assert!(route.is_valid(now)); + } + assert_eq!(slaac.prefix().len(), 1); + assert_eq!(slaac.routes().len(), 1); + assert!(slaac.sync_required(now)); + + slaac.update_slaac_state(now); + assert!(!slaac.sync_required(now)); + + // Skip time until the route expires + let now = poll_at; + assert!(slaac.sync_required(now)); + for (_prefix, info) in slaac.prefix() { + assert!(info.is_valid(now)); + } + for route in slaac.routes() { + assert!(!route.is_valid(now)); + } + + slaac.update_slaac_state(now); + assert!(!slaac.sync_required(now)); + assert_eq!(slaac.routes().len(), 0); + + // Skip time until the prefix expires + let poll_at = slaac.poll_at(now).unwrap(); + let now = poll_at; + assert!(slaac.sync_required(now)); + for (_prefix, info) in slaac.prefix() { + assert!(!info.is_valid(now)); + } + // Should already return None + assert!(slaac.poll_at(now).is_none()); + slaac.update_slaac_state(now); + assert!(!slaac.sync_required(now)); + assert_eq!(slaac.routes().len(), 0); + assert_eq!(slaac.prefix().len(), 0); + + // No state remaining, nothing to wait on + assert!(slaac.poll_at(now).is_none()); + } + + #[test] + fn test_ra_expire() { + let mut slaac = Slaac::new(); + let now = Instant::from_millis(1); + slaac.rs_sent(now); + slaac.process_advertisement(&SOURCE, VALID, Some(PREFIX), now); + + let now = Instant::from_secs(300); + + assert!(slaac.sync_required(now)); + for (_prefix, info) in slaac.prefix() { + assert!(info.is_valid(now)); + } + for route in slaac.routes() { + assert!(route.is_valid(now)); + } + slaac.update_slaac_state(now); + + let mut expire_prefix = PREFIX; + expire_prefix.preferred_lifetime = Duration::ZERO; + expire_prefix.valid_lifetime = Duration::ZERO; + + // Invalidate the prefix, but not the route + slaac.process_advertisement(&SOURCE, VALID, Some(expire_prefix), now); + + assert!(slaac.sync_required(now)); + for (_prefix, info) in slaac.prefix() { + assert!(!info.is_valid(now)); + } + for route in slaac.routes() { + assert!(route.is_valid(now)); + } + slaac.update_slaac_state(now); + assert_eq!(slaac.prefix().len(), 0); + assert_eq!(slaac.routes().len(), 1); + + assert!(!slaac.sync_required(now)); + // Invalidate also the route + slaac.process_advertisement(&SOURCE, Duration::ZERO, Some(expire_prefix), now); + assert!(slaac.sync_required(now)); + for route in slaac.routes() { + assert!(!route.is_valid(now)); + } + assert!(slaac.poll_at(now).is_none()); + + slaac.update_slaac_state(now); + assert_eq!(slaac.prefix().len(), 0); + assert_eq!(slaac.routes().len(), 0); + assert!(!slaac.sync_required(now)); + // No state remaining, nothing to wait on + assert!(slaac.poll_at(now).is_none()); + } +} diff --git a/vendor/smoltcp/src/iface/socket_meta.rs b/vendor/smoltcp/src/iface/socket_meta.rs new file mode 100644 index 00000000..1503d692 --- /dev/null +++ b/vendor/smoltcp/src/iface/socket_meta.rs @@ -0,0 +1,230 @@ +use super::SocketHandle; +use crate::{ + socket::PollAt, + time::{Duration, Instant}, + wire::IpAddress, +}; + +/// Neighbor dependency. +/// +/// This enum tracks whether the socket should be polled based on the neighbor +/// it is going to send packets to. +#[derive(Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum NeighborState { + /// Socket can be polled immediately. + #[default] + Active, + /// Socket should not be polled until either `silent_until` passes or + /// `neighbor` appears in the neighbor cache. + Waiting { + neighbor: IpAddress, + silent_until: Instant, + }, +} + +/// Network socket metadata. +/// +/// This includes things that only external (to the socket, that is) code +/// is interested in, but which are more conveniently stored inside the socket +/// itself. +#[derive(Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct Meta { + /// Handle of this socket within its enclosing `SocketSet`. + /// Mainly useful for debug output. + pub(crate) handle: SocketHandle, + /// See [NeighborState](struct.NeighborState.html). + neighbor_state: NeighborState, +} + +impl Meta { + /// Minimum delay between neighbor discovery requests for this particular + /// socket, in milliseconds. + /// + /// See also `iface::NeighborCache::SILENT_TIME`. + pub(crate) const DISCOVERY_SILENT_TIME: Duration = Duration::from_millis(1_000); + + pub(crate) fn poll_at( + &self, + socket_poll_at: PollAt, + has_neighbor: F, + timestamp: Instant, + ) -> PollAt + where + F: Fn(IpAddress) -> bool, + { + match self.neighbor_state { + NeighborState::Active => socket_poll_at, + NeighborState::Waiting { neighbor, .. } if has_neighbor(neighbor) => socket_poll_at, + NeighborState::Waiting { silent_until, .. } if timestamp >= silent_until => { + socket_poll_at + } + NeighborState::Waiting { silent_until, .. } => PollAt::Time(silent_until), + } + } + + pub(crate) fn egress_permitted(&mut self, timestamp: Instant, has_neighbor: F) -> bool + where + F: Fn(IpAddress) -> bool, + { + match self.neighbor_state { + NeighborState::Active => true, + NeighborState::Waiting { + neighbor, + silent_until, + } => { + if has_neighbor(neighbor) { + net_trace!( + "{}: neighbor {} discovered, unsilencing", + self.handle, + neighbor + ); + self.neighbor_state = NeighborState::Active; + true + } else if timestamp >= silent_until { + net_trace!( + "{}: neighbor {} silence timer expired, rediscovering", + self.handle, + neighbor + ); + true + } else { + false + } + } + } + } + + pub(crate) fn neighbor_missing(&mut self, timestamp: Instant, neighbor: IpAddress) { + net_trace!( + "{}: neighbor {} missing, silencing until t+{}", + self.handle, + neighbor, + Self::DISCOVERY_SILENT_TIME + ); + self.neighbor_state = NeighborState::Waiting { + neighbor, + silent_until: timestamp + Self::DISCOVERY_SILENT_TIME, + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "proto-ipv4")] + const NEIGHBOR: IpAddress = IpAddress::v4(192, 168, 1, 1); + #[cfg(not(feature = "proto-ipv4"))] + const NEIGHBOR: IpAddress = IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1); + + fn meta() -> Meta { + Meta { + handle: SocketHandle::default(), + neighbor_state: NeighborState::Active, + } + } + + #[test] + fn poll_at_active_passes_through() { + let m = meta(); + let t = Instant::from_millis(1000); + + assert_eq!(m.poll_at(PollAt::Ingress, |_| false, t), PollAt::Ingress); + assert_eq!(m.poll_at(PollAt::Now, |_| false, t), PollAt::Now); + let future = Instant::from_millis(2000); + assert_eq!( + m.poll_at(PollAt::Time(future), |_| false, t), + PollAt::Time(future), + ); + } + + #[test] + fn poll_at_waiting_neighbor_found() { + let mut m = meta(); + m.neighbor_missing(Instant::from_millis(1000), NEIGHBOR); + + assert_eq!( + m.poll_at(PollAt::Now, |_| true, Instant::from_millis(1000)), + PollAt::Now, + ); + assert_eq!( + m.poll_at(PollAt::Ingress, |_| true, Instant::from_millis(1000)), + PollAt::Ingress, + ); + } + + #[test] + fn poll_at_waiting_before_silent_until() { + let mut m = meta(); + let t0 = Instant::from_millis(1000); + m.neighbor_missing(t0, NEIGHBOR); + let silent_until = t0 + Meta::DISCOVERY_SILENT_TIME; + + let t_before = Instant::from_millis(1500); + assert!(t_before < silent_until); + + assert_eq!( + m.poll_at(PollAt::Ingress, |_| false, t_before), + PollAt::Time(silent_until), + ); + assert_eq!( + m.poll_at(PollAt::Now, |_| false, t_before), + PollAt::Time(silent_until), + ); + } + + #[test] + fn poll_at_waiting_after_silent_until_returns_socket_poll_at() { + let mut m = meta(); + let t0 = Instant::from_millis(1000); + m.neighbor_missing(t0, NEIGHBOR); + let silent_until = t0 + Meta::DISCOVERY_SILENT_TIME; + + let t_after = Instant::from_millis(2500); + assert!(t_after >= silent_until); + + assert_eq!( + m.poll_at(PollAt::Ingress, |_| false, t_after), + PollAt::Ingress, + ); + assert_eq!(m.poll_at(PollAt::Now, |_| false, t_after), PollAt::Now); + let future = Instant::from_millis(5000); + assert_eq!( + m.poll_at(PollAt::Time(future), |_| false, t_after), + PollAt::Time(future), + ); + } + + #[test] + fn poll_at_waiting_at_exact_silent_until() { + let mut m = meta(); + let t0 = Instant::from_millis(1000); + m.neighbor_missing(t0, NEIGHBOR); + let silent_until = t0 + Meta::DISCOVERY_SILENT_TIME; + + assert_eq!( + m.poll_at(PollAt::Ingress, |_| false, silent_until), + PollAt::Ingress, + ); + } + + #[test] + fn egress_permitted_consistent_with_poll_at() { + let mut m = meta(); + let t0 = Instant::from_millis(1000); + m.neighbor_missing(t0, NEIGHBOR); + let silent_until = t0 + Meta::DISCOVERY_SILENT_TIME; + + let t_before = Instant::from_millis(1500); + assert!(!m.egress_permitted(t_before, |_| false)); + assert_eq!( + m.poll_at(PollAt::Ingress, |_| false, t_before), + PollAt::Time(silent_until), + ); + + let t_after = Instant::from_millis(2500); + assert!(m.egress_permitted(t_after, |_| false)); + } +} diff --git a/vendor/smoltcp/src/iface/socket_set.rs b/vendor/smoltcp/src/iface/socket_set.rs new file mode 100644 index 00000000..be55fef5 --- /dev/null +++ b/vendor/smoltcp/src/iface/socket_set.rs @@ -0,0 +1,151 @@ +use core::fmt; +use managed::ManagedSlice; + +use super::socket_meta::Meta; +use crate::socket::{AnySocket, Socket}; + +/// Opaque struct with space for storing one socket. +/// +/// This is public so you can use it to allocate space for storing +/// sockets when creating an Interface. +#[derive(Debug, Default)] +pub struct SocketStorage<'a> { + inner: Option>, +} + +impl<'a> SocketStorage<'a> { + pub const EMPTY: Self = Self { inner: None }; +} + +/// An item of a socket set. +#[derive(Debug)] +pub(crate) struct Item<'a> { + pub(crate) meta: Meta, + pub(crate) socket: Socket<'a>, +} + +/// A handle, identifying a socket in an Interface. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SocketHandle(usize); + +impl fmt::Display for SocketHandle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "#{}", self.0) + } +} + +/// An extensible set of sockets. +/// +/// The lifetime `'a` is used when storing a `Socket<'a>`. If you're using +/// owned buffers for your sockets (passed in as `Vec`s) you can use +/// `SocketSet<'static>`. +#[derive(Debug)] +pub struct SocketSet<'a> { + sockets: ManagedSlice<'a, SocketStorage<'a>>, +} + +impl<'a> SocketSet<'a> { + /// Create a socket set using the provided storage. + pub fn new(sockets: SocketsT) -> SocketSet<'a> + where + SocketsT: Into>>, + { + let sockets = sockets.into(); + SocketSet { sockets } + } + + /// Add a socket to the set, and return its handle. + /// + /// # Panics + /// This function panics if the storage is fixed-size (not a `Vec`) and is full. + pub fn add>(&mut self, socket: T) -> SocketHandle { + fn put<'a>(index: usize, slot: &mut SocketStorage<'a>, socket: Socket<'a>) -> SocketHandle { + net_trace!("[{}]: adding", index); + let handle = SocketHandle(index); + let mut meta = Meta::default(); + meta.handle = handle; + *slot = SocketStorage { + inner: Some(Item { meta, socket }), + }; + handle + } + + let socket = socket.upcast(); + + for (index, slot) in self.sockets.iter_mut().enumerate() { + if slot.inner.is_none() { + return put(index, slot, socket); + } + } + + match &mut self.sockets { + ManagedSlice::Borrowed(_) => panic!("adding a socket to a full SocketSet"), + #[cfg(feature = "alloc")] + ManagedSlice::Owned(sockets) => { + sockets.push(SocketStorage { inner: None }); + let index = sockets.len() - 1; + put(index, &mut sockets[index], socket) + } + } + } + + /// Get a socket from the set by its handle, as mutable. + /// + /// # Panics + /// This function may panic if the handle does not belong to this socket set + /// or the socket has the wrong type. + pub fn get>(&self, handle: SocketHandle) -> &T { + match self.sockets[handle.0].inner.as_ref() { + Some(item) => { + T::downcast(&item.socket).expect("handle refers to a socket of a wrong type") + } + None => panic!("handle does not refer to a valid socket"), + } + } + + /// Get a mutable socket from the set by its handle, as mutable. + /// + /// # Panics + /// This function may panic if the handle does not belong to this socket set + /// or the socket has the wrong type. + pub fn get_mut>(&mut self, handle: SocketHandle) -> &mut T { + match self.sockets[handle.0].inner.as_mut() { + Some(item) => T::downcast_mut(&mut item.socket) + .expect("handle refers to a socket of a wrong type"), + None => panic!("handle does not refer to a valid socket"), + } + } + + /// Remove a socket from the set, without changing its state. + /// + /// # Panics + /// This function may panic if the handle does not belong to this socket set. + pub fn remove(&mut self, handle: SocketHandle) -> Socket<'a> { + net_trace!("[{}]: removing", handle.0); + match self.sockets[handle.0].inner.take() { + Some(item) => item.socket, + None => panic!("handle does not refer to a valid socket"), + } + } + + /// Get an iterator to the inner sockets. + pub fn iter(&self) -> impl Iterator)> { + self.items().map(|i| (i.meta.handle, &i.socket)) + } + + /// Get a mutable iterator to the inner sockets. + pub fn iter_mut(&mut self) -> impl Iterator)> { + self.items_mut().map(|i| (i.meta.handle, &mut i.socket)) + } + + /// Iterate every socket in this set. + pub(crate) fn items(&self) -> impl Iterator> + '_ { + self.sockets.iter().filter_map(|x| x.inner.as_ref()) + } + + /// Iterate every socket in this set. + pub(crate) fn items_mut(&mut self) -> impl Iterator> + '_ { + self.sockets.iter_mut().filter_map(|x| x.inner.as_mut()) + } +} diff --git a/vendor/smoltcp/src/lib.rs b/vendor/smoltcp/src/lib.rs new file mode 100644 index 00000000..38a699ba --- /dev/null +++ b/vendor/smoltcp/src/lib.rs @@ -0,0 +1,195 @@ +#![cfg_attr(not(any(test, feature = "std")), no_std)] +#![deny(unsafe_code)] + +//! The _smoltcp_ library is built in a layered structure, with the layers corresponding +//! to the levels of API abstraction. Only the highest layers would be used by a typical +//! application; however, the goal of _smoltcp_ is not just to provide a simple interface +//! for writing applications but also to be a toolbox of networking primitives, so +//! every layer is fully exposed and documented. +//! +//! When discussing networking stacks and layering, often the [OSI model][osi] is invoked. +//! _smoltcp_ makes no effort to conform to the OSI model as it is not applicable to TCP/IP. +//! +//! # The socket layer +//! The socket layer APIs are provided in the module [socket](socket/index.html); currently, +//! raw, ICMP, TCP, and UDP sockets are provided. The socket API provides the usual primitives, +//! but necessarily differs in many from the [Berkeley socket API][berk], as the latter was +//! not designed to be used without heap allocation. +//! +//! The socket layer provides the buffering, packet construction and validation, and (for +//! stateful sockets) the state machines, but it is interface-agnostic. An application must +//! use sockets together with a network interface. +//! +//! # The interface layer +//! The interface layer APIs are provided in the module [iface](iface/index.html); currently, +//! Ethernet interface is provided. +//! +//! The interface layer handles the control messages, physical addressing and neighbor discovery. +//! It routes packets to and from sockets. +//! +//! # The physical layer +//! The physical layer APIs are provided in the module [phy](phy/index.html); currently, +//! raw socket and TAP interface are provided. In addition, two _middleware_ interfaces +//! are provided: the _tracer device_, which prints a human-readable representation of packets, +//! and the _fault injector device_, which randomly introduces errors into the transmitted +//! and received packet sequences. +//! +//! The physical layer handles interaction with a platform-specific network device. +//! +//! # The wire layers +//! Unlike the higher layers, the wire layer APIs will not be used by a typical application. +//! They however are the bedrock of _smoltcp_, and everything else is built on top of them. +//! +//! The wire layer APIs are designed by the principle "make illegal states ir-representable". +//! If a wire layer object can be constructed, then it can also be parsed from or emitted to +//! a lower level. +//! +//! The wire layer APIs also provide _tcpdump_-like pretty printing. +//! +//! ## The representation layer +//! The representation layer APIs are provided in the module [wire]. +//! +//! The representation layer exists to reduce the state space of raw packets. Raw packets +//! may be nonsensical in a multitude of ways: invalid checksums, impossible combinations of flags, +//! pointers to fields out of bounds, meaningless options... Representations shed all that, +//! as well as any features not supported by _smoltcp_. +//! +//! ## The packet layer +//! The packet layer APIs are also provided in the module [wire]. +//! +//! The packet layer exists to provide a more structured way to work with packets than +//! treating them as sequences of octets. It makes no judgement as to content of the packets, +//! except where necessary to provide safe access to fields, and strives to implement every +//! feature ever defined, to ensure that, when the representation layer is unable to make sense +//! of a packet, it is still logged correctly and in full. +//! +//! # Minimum Supported Rust Version (MSRV) +//! +//! This crate is guaranteed to compile on stable Rust 1.86 and up with any valid set of features. +//! It *might* compile on older versions but that may change in any new patch release. +//! +//! The exception is when using the `defmt` feature, in which case `defmt`'s MSRV applies, which +//! is higher. +//! +//! [wire]: wire/index.html +//! [osi]: https://en.wikipedia.org/wiki/OSI_model +//! [berk]: https://en.wikipedia.org/wiki/Berkeley_sockets + +/* XXX compiler bug +#![cfg(not(any(feature = "socket-raw", + feature = "socket-udp", + feature = "socket-tcp")))] +compile_error!("at least one socket needs to be enabled"); */ + +#![allow(clippy::match_like_matches_macro)] +#![allow(clippy::redundant_field_names)] +#![allow(clippy::identity_op)] +#![allow(clippy::option_map_unit_fn)] +#![allow(clippy::unit_arg)] +#![allow(clippy::new_without_default)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(not(any( + feature = "proto-ipv4", + feature = "proto-ipv6", + feature = "proto-sixlowpan" +)))] +compile_error!( + "You must enable at least one of the following features: proto-ipv4, proto-ipv6, proto-sixlowpan" +); + +#[cfg(all( + feature = "socket", + not(any( + feature = "socket-raw", + feature = "socket-udp", + feature = "socket-tcp", + feature = "socket-icmp", + feature = "socket-dhcpv4", + feature = "socket-dns", + )) +))] +compile_error!( + "If you enable the socket feature, you must enable at least one of the following features: socket-raw, socket-udp, socket-tcp, socket-icmp, socket-dhcpv4, socket-dns" +); + +#[cfg(all( + feature = "socket", + not(any( + feature = "medium-ethernet", + feature = "medium-ip", + feature = "medium-ieee802154", + )) +))] +compile_error!( + "If you enable the socket feature, you must enable at least one of the following features: medium-ip, medium-ethernet, medium-ieee802154" +); + +#[cfg(all( + feature = "proto-ipv6-slaac", + not(any(feature = "medium-ethernet", feature = "medium-ieee802154",)) +))] +compile_error!( + "If you enable the `proto-ipv6-slaac` feature, you must enable at least one of the following features: medium-ethernet, medium-ieee802154" +); + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You must enable at most one of the following features: defmt, log"); + +#[macro_use] +mod macros; +mod parsers; +mod rand; + +#[cfg(test)] +pub mod config { + #![allow(unused)] + pub const ASSEMBLER_MAX_SEGMENT_COUNT: usize = 4; + pub const DNS_MAX_NAME_SIZE: usize = 255; + pub const DNS_MAX_RESULT_COUNT: usize = 1; + pub const DNS_MAX_SERVER_COUNT: usize = 1; + pub const FRAGMENTATION_BUFFER_SIZE: usize = 4096; + pub const IFACE_MAX_ADDR_COUNT: usize = 8; + pub const IFACE_MAX_MULTICAST_GROUP_COUNT: usize = 4; + pub const IFACE_MAX_ROUTE_COUNT: usize = 4; + pub const IFACE_MAX_PREFIX_COUNT: usize = 1; + pub const IFACE_MAX_SIXLOWPAN_ADDRESS_CONTEXT_COUNT: usize = 4; + pub const IFACE_NEIGHBOR_CACHE_COUNT: usize = 3; + pub const REASSEMBLY_BUFFER_COUNT: usize = 4; + pub const REASSEMBLY_BUFFER_SIZE: usize = 1500; + pub const RPL_RELATIONS_BUFFER_COUNT: usize = 16; + pub const RPL_PARENTS_BUFFER_COUNT: usize = 8; + pub const IPV6_HBH_MAX_OPTIONS: usize = 4; +} + +#[cfg(not(test))] +pub mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +#[cfg(any( + feature = "medium-ethernet", + feature = "medium-ip", + feature = "medium-ieee802154" +))] +pub mod iface; + +pub mod phy; +#[cfg(feature = "socket")] +pub mod socket; +pub mod storage; +pub mod time; +pub mod wire; + +#[cfg(all( + test, + any( + feature = "medium-ethernet", + feature = "medium-ip", + feature = "medium-ieee802154" + ) +))] +mod tests; diff --git a/vendor/smoltcp/src/macros.rs b/vendor/smoltcp/src/macros.rs new file mode 100644 index 00000000..3da0123b --- /dev/null +++ b/vendor/smoltcp/src/macros.rs @@ -0,0 +1,169 @@ +#[cfg(not(test))] +#[cfg(feature = "log")] +macro_rules! net_log { + (trace, $($arg:expr),*) => { log::trace!($($arg),*) }; + (debug, $($arg:expr),*) => { log::debug!($($arg),*) }; +} + +#[cfg(test)] +#[cfg(feature = "log")] +macro_rules! net_log { + (trace, $($arg:expr),*) => { println!($($arg),*) }; + (debug, $($arg:expr),*) => { println!($($arg),*) }; +} + +#[cfg(feature = "defmt")] +macro_rules! net_log { + (trace, $($arg:expr),*) => { defmt::trace!($($arg),*) }; + (debug, $($arg:expr),*) => { defmt::debug!($($arg),*) }; +} + +#[cfg(not(any(feature = "log", feature = "defmt")))] +macro_rules! net_log { + ($level:ident, $($arg:expr),*) => {{ $( let _ = $arg; )* }} +} + +macro_rules! net_trace { + ($($arg:expr),*) => (net_log!(trace, $($arg),*)); +} + +macro_rules! net_debug { + ($($arg:expr),*) => (net_log!(debug, $($arg),*)); +} + +macro_rules! enum_with_unknown { + ( + $( #[$enum_attr:meta] )* + pub enum $name:ident($ty:ty) { + $( + $( #[$variant_attr:meta] )* + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + $( #[$enum_attr] )* + pub enum $name { + $( + $( #[$variant_attr] )* + $variant + ),*, + Unknown($ty) + } + + impl ::core::convert::From<$ty> for $name { + fn from(value: $ty) -> Self { + match value { + $( $value => $name::$variant ),*, + other => $name::Unknown(other) + } + } + } + + impl ::core::convert::From<$name> for $ty { + fn from(value: $name) -> Self { + match value { + $( $name::$variant => $value ),*, + $name::Unknown(other) => other + } + } + } + } +} + +#[cfg(feature = "proto-rpl")] +macro_rules! get { + ($buffer:expr, into: $into:ty, fun: $fun:ident, field: $field:expr $(,)?) => { + { + <$into>::$fun(&$buffer.as_ref()[$field]) + } + }; + + ($buffer:expr, into: $into:ty, field: $field:expr $(,)?) => { + get!($buffer, into: $into, field: $field, shift: 0, mask: 0b1111_1111) + }; + + ($buffer:expr, into: $into:ty, field: $field:expr, mask: $bit_mask:expr $(,)?) => { + get!($buffer, into: $into, field: $field, shift: 0, mask: $bit_mask) + }; + + ($buffer:expr, into: $into:ty, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => { + { + <$into>::from((&$buffer.as_ref()[$field] >> $bit_shift) & $bit_mask) + } + }; + + ($buffer:expr, field: $field:expr $(,)?) => { + get!($buffer, field: $field, shift: 0, mask: 0b1111_1111) + }; + + ($buffer:expr, field: $field:expr, mask: $bit_mask:expr $(,)?) => { + get!($buffer, field: $field, shift: 0, mask: $bit_mask) + }; + + ($buffer:expr, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) + => + { + { + (&$buffer.as_ref()[$field] >> $bit_shift) & $bit_mask + } + }; + + ($buffer:expr, u16, field: $field:expr $(,)?) => { + { + NetworkEndian::read_u16(&$buffer.as_ref()[$field]) + } + }; + + ($buffer:expr, bool, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => { + { + (($buffer.as_ref()[$field] >> $bit_shift) & $bit_mask) == 0b1 + } + }; + + ($buffer:expr, u32, field: $field:expr $(,)?) => { + { + NetworkEndian::read_u32(&$buffer.as_ref()[$field]) + } + }; +} + +#[cfg(feature = "proto-rpl")] +macro_rules! set { + ($buffer:expr, address: $address:ident, field: $field:expr $(,)?) => {{ + $buffer.as_mut()[$field].copy_from_slice(&$address.octets()); + }}; + + ($buffer:expr, $value:ident, field: $field:expr $(,)?) => { + set!($buffer, $value, field: $field, shift: 0, mask: 0b1111_1111) + }; + + ($buffer:expr, $value:ident, field: $field:expr, mask: $bit_mask:expr $(,)?) => { + set!($buffer, $value, field: $field, shift: 0, mask: $bit_mask) + }; + + ($buffer:expr, $value:ident, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {{ + let raw = + ($buffer.as_ref()[$field] & !($bit_mask << $bit_shift)) | ($value << $bit_shift); + $buffer.as_mut()[$field] = raw; + }}; + + ($buffer:expr, $value:ident, bool, field: $field:expr, mask: $bit_mask:expr $(,)?) => { + set!($buffer, $value, bool, field: $field, shift: 0, mask: $bit_mask); + }; + + ($buffer:expr, $value:ident, bool, field: $field:expr, shift: $bit_shift:expr, mask: $bit_mask:expr $(,)?) => {{ + let raw = ($buffer.as_ref()[$field] & !($bit_mask << $bit_shift)) + | (if $value { 0b1 } else { 0b0 } << $bit_shift); + $buffer.as_mut()[$field] = raw; + }}; + + ($buffer:expr, $value:ident, u16, field: $field:expr $(,)?) => {{ + NetworkEndian::write_u16(&mut $buffer.as_mut()[$field], $value); + }}; + + ($buffer:expr, $value:ident, u32, field: $field:expr $(,)?) => {{ + NetworkEndian::write_u32(&mut $buffer.as_mut()[$field], $value); + }}; +} diff --git a/vendor/smoltcp/src/parsers.rs b/vendor/smoltcp/src/parsers.rs new file mode 100644 index 00000000..3c561370 --- /dev/null +++ b/vendor/smoltcp/src/parsers.rs @@ -0,0 +1,652 @@ +#![cfg_attr( + not(all(feature = "proto-ipv6", feature = "proto-ipv4")), + allow(dead_code) +)] + +use core::result; +use core::str::FromStr; + +#[cfg(feature = "medium-ethernet")] +use crate::wire::EthernetAddress; +use crate::wire::{IpAddress, IpCidr, IpEndpoint}; +#[cfg(feature = "proto-ipv4")] +use crate::wire::{Ipv4Address, Ipv4Cidr}; +#[cfg(feature = "proto-ipv6")] +use crate::wire::{Ipv6Address, Ipv6Cidr}; + +type Result = result::Result; + +struct Parser<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> Parser<'a> { + fn new(data: &'a str) -> Parser<'a> { + Parser { + data: data.as_bytes(), + pos: 0, + } + } + + fn lookahead_char(&self, ch: u8) -> bool { + if self.pos < self.data.len() { + self.data[self.pos] == ch + } else { + false + } + } + + fn advance(&mut self) -> Result { + match self.data.get(self.pos) { + Some(&chr) => { + self.pos += 1; + Ok(chr) + } + None => Err(()), + } + } + + fn try_do(&mut self, f: F) -> Option + where + F: FnOnce(&mut Parser<'a>) -> Result, + { + let pos = self.pos; + match f(self) { + Ok(res) => Some(res), + Err(()) => { + self.pos = pos; + None + } + } + } + + fn accept_eof(&mut self) -> Result<()> { + if self.data.len() == self.pos { + Ok(()) + } else { + Err(()) + } + } + + fn until_eof(&mut self, f: F) -> Result + where + F: FnOnce(&mut Parser<'a>) -> Result, + { + let res = f(self)?; + self.accept_eof()?; + Ok(res) + } + + fn accept_char(&mut self, chr: u8) -> Result<()> { + if self.advance()? == chr { + Ok(()) + } else { + Err(()) + } + } + + fn accept_str(&mut self, string: &[u8]) -> Result<()> { + for byte in string.iter() { + self.accept_char(*byte)?; + } + Ok(()) + } + + fn accept_digit(&mut self, hex: bool) -> Result { + let digit = self.advance()?; + if digit.is_ascii_digit() { + Ok(digit - b'0') + } else if hex && (b'a'..=b'f').contains(&digit) { + Ok(digit - b'a' + 10) + } else if hex && (b'A'..=b'F').contains(&digit) { + Ok(digit - b'A' + 10) + } else { + Err(()) + } + } + + fn accept_number(&mut self, max_digits: usize, max_value: u32, hex: bool) -> Result { + let mut value = self.accept_digit(hex)? as u32; + for _ in 1..max_digits { + match self.try_do(|p| p.accept_digit(hex)) { + Some(digit) => { + value *= if hex { 16 } else { 10 }; + value += digit as u32; + } + None => break, + } + } + if value < max_value { + Ok(value) + } else { + Err(()) + } + } + + #[cfg(feature = "medium-ethernet")] + fn accept_mac_joined_with(&mut self, separator: u8) -> Result { + let mut octets = [0u8; 6]; + for (n, octet) in octets.iter_mut().enumerate() { + *octet = self.accept_number(2, 0x100, true)? as u8; + if n != 5 { + self.accept_char(separator)?; + } + } + Ok(EthernetAddress(octets)) + } + + #[cfg(feature = "medium-ethernet")] + fn accept_mac(&mut self) -> Result { + if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b'-')) { + return Ok(mac); + } + if let Some(mac) = self.try_do(|p| p.accept_mac_joined_with(b':')) { + return Ok(mac); + } + Err(()) + } + + #[cfg(feature = "proto-ipv6")] + fn accept_ipv4_mapped_ipv6_part(&mut self, parts: &mut [u16], idx: &mut usize) -> Result<()> { + let octets = self.accept_ipv4_octets()?; + + parts[*idx] = ((octets[0] as u16) << 8) | (octets[1] as u16); + *idx += 1; + parts[*idx] = ((octets[2] as u16) << 8) | (octets[3] as u16); + *idx += 1; + + Ok(()) + } + + #[cfg(feature = "proto-ipv6")] + fn accept_ipv6_part( + &mut self, + (head, tail): (&mut [u16; 8], &mut [u16; 6]), + (head_idx, tail_idx): (&mut usize, &mut usize), + mut use_tail: bool, + ) -> Result<()> { + let double_colon = match self.try_do(|p| p.accept_str(b"::")) { + Some(_) if !use_tail && *head_idx < 7 => { + // Found a double colon. Start filling out the + // tail and set the double colon flag in case + // this is the last character we can parse. + use_tail = true; + true + } + Some(_) => { + // This is a bad address. Only one double colon is + // allowed and an address is only 128 bits. + return Err(()); + } + None => { + if *head_idx != 0 || use_tail && *tail_idx != 0 { + // If this is not the first number or the position following + // a double colon, we expect there to be a single colon. + self.accept_char(b':')?; + } + false + } + }; + + match self.try_do(|p| p.accept_number(4, 0x10000, true)) { + Some(part) if !use_tail && *head_idx < 8 => { + // Valid u16 to be added to the address + head[*head_idx] = part as u16; + *head_idx += 1; + + if *head_idx == 6 && head[0..*head_idx] == [0, 0, 0, 0, 0, 0xffff] { + self.try_do(|p| { + p.accept_char(b':')?; + p.accept_ipv4_mapped_ipv6_part(head, head_idx) + }); + } + Ok(()) + } + Some(part) if *tail_idx < 6 => { + // Valid u16 to be added to the address + tail[*tail_idx] = part as u16; + *tail_idx += 1; + + if *tail_idx == 1 && tail[0] == 0xffff && head[0..8] == [0, 0, 0, 0, 0, 0, 0, 0] { + self.try_do(|p| { + p.accept_char(b':')?; + p.accept_ipv4_mapped_ipv6_part(tail, tail_idx) + }); + } + Ok(()) + } + Some(_) => { + // Tail or head section is too long + Err(()) + } + None if double_colon => { + // The address ends with "::". E.g. 1234:: or :: + Ok(()) + } + None => { + // Invalid address + Err(()) + } + }?; + + if *head_idx + *tail_idx > 8 { + // The head and tail indexes add up to a bad address length. + Err(()) + } else if !self.lookahead_char(b':') { + if *head_idx < 8 && !use_tail { + // There was no double colon found, and the head is too short + return Err(()); + } + Ok(()) + } else { + // Continue recursing + self.accept_ipv6_part((head, tail), (head_idx, tail_idx), use_tail) + } + } + + #[cfg(feature = "proto-ipv6")] + fn accept_ipv6(&mut self) -> Result { + // IPv6 addresses may contain a "::" to indicate a series of + // 16 bit sections that evaluate to 0. E.g. + // + // fe80:0000:0000:0000:0000:0000:0000:0001 + // + // May be written as + // + // fe80::1 + // + // As a result, we need to find the first section of colon + // delimited u16's before a possible "::", then the + // possible second section after the "::", and finally + // combine the second optional section to the end of the + // final address. + // + // See https://tools.ietf.org/html/rfc4291#section-2.2 + // for details. + let (mut addr, mut tail) = ([0u16; 8], [0u16; 6]); + let (mut head_idx, mut tail_idx) = (0, 0); + + self.accept_ipv6_part( + (&mut addr, &mut tail), + (&mut head_idx, &mut tail_idx), + false, + )?; + + // We need to copy the tail portion (the portion following the "::") to the + // end of the address. + addr[8 - tail_idx..].copy_from_slice(&tail[..tail_idx]); + + Ok(Ipv6Address::from(addr)) + } + + fn accept_ipv4_octets(&mut self) -> Result<[u8; 4]> { + let mut octets = [0u8; 4]; + for (n, octet) in octets.iter_mut().enumerate() { + *octet = self.accept_number(3, 0x100, false)? as u8; + if n != 3 { + self.accept_char(b'.')?; + } + } + Ok(octets) + } + + #[cfg(feature = "proto-ipv4")] + fn accept_ipv4(&mut self) -> Result { + let octets = self.accept_ipv4_octets()?; + Ok(Ipv4Address::new( + octets[0], + octets[1], + octets[2], + octets[3], + )) + } + + fn accept_ip(&mut self) -> Result { + #[cfg(feature = "proto-ipv4")] + #[allow(clippy::single_match)] + match self.try_do(|p| p.accept_ipv4()) { + Some(ipv4) => return Ok(IpAddress::Ipv4(ipv4)), + None => (), + } + + #[cfg(feature = "proto-ipv6")] + #[allow(clippy::single_match)] + match self.try_do(|p| p.accept_ipv6()) { + Some(ipv6) => return Ok(IpAddress::Ipv6(ipv6)), + None => (), + } + + Err(()) + } + + #[cfg(feature = "proto-ipv4")] + fn accept_ipv4_endpoint(&mut self) -> Result { + let ip = self.accept_ipv4()?; + + let port = if self.accept_eof().is_ok() { + 0 + } else { + self.accept_char(b':')?; + self.accept_number(5, 65535, false)? + }; + + Ok(IpEndpoint { + addr: IpAddress::Ipv4(ip), + port: port as u16, + }) + } + + #[cfg(feature = "proto-ipv6")] + fn accept_ipv6_endpoint(&mut self) -> Result { + if self.lookahead_char(b'[') { + self.accept_char(b'[')?; + let ip = self.accept_ipv6()?; + self.accept_char(b']')?; + self.accept_char(b':')?; + let port = self.accept_number(5, 65535, false)?; + + Ok(IpEndpoint { + addr: IpAddress::Ipv6(ip), + port: port as u16, + }) + } else { + let ip = self.accept_ipv6()?; + Ok(IpEndpoint { + addr: IpAddress::Ipv6(ip), + port: 0, + }) + } + } + + fn accept_ip_endpoint(&mut self) -> Result { + #[cfg(feature = "proto-ipv4")] + #[allow(clippy::single_match)] + match self.try_do(|p| p.accept_ipv4_endpoint()) { + Some(ipv4) => return Ok(ipv4), + None => (), + } + + #[cfg(feature = "proto-ipv6")] + #[allow(clippy::single_match)] + match self.try_do(|p| p.accept_ipv6_endpoint()) { + Some(ipv6) => return Ok(ipv6), + None => (), + } + + Err(()) + } +} + +#[cfg(feature = "medium-ethernet")] +impl FromStr for EthernetAddress { + type Err = (); + + /// Parse a string representation of an Ethernet address. + fn from_str(s: &str) -> Result { + Parser::new(s).until_eof(|p| p.accept_mac()) + } +} + +impl FromStr for IpAddress { + type Err = (); + + /// Parse a string representation of an IP address. + fn from_str(s: &str) -> Result { + Parser::new(s).until_eof(|p| p.accept_ip()) + } +} + +#[cfg(feature = "proto-ipv4")] +impl FromStr for Ipv4Cidr { + type Err = (); + + /// Parse a string representation of an IPv4 CIDR. + fn from_str(s: &str) -> Result { + Parser::new(s).until_eof(|p| { + let ip = p.accept_ipv4()?; + p.accept_char(b'/')?; + let prefix_len = p.accept_number(2, 33, false)? as u8; + Ok(Ipv4Cidr::new(ip, prefix_len)) + }) + } +} + +#[cfg(feature = "proto-ipv6")] +impl FromStr for Ipv6Cidr { + type Err = (); + + /// Parse a string representation of an IPv6 CIDR. + fn from_str(s: &str) -> Result { + // https://tools.ietf.org/html/rfc4291#section-2.3 + Parser::new(s).until_eof(|p| { + let ip = p.accept_ipv6()?; + p.accept_char(b'/')?; + let prefix_len = p.accept_number(3, 129, false)? as u8; + Ok(Ipv6Cidr::new(ip, prefix_len)) + }) + } +} + +impl FromStr for IpCidr { + type Err = (); + + /// Parse a string representation of an IP CIDR. + fn from_str(s: &str) -> Result { + #[cfg(feature = "proto-ipv4")] + #[allow(clippy::single_match)] + match Ipv4Cidr::from_str(s) { + Ok(cidr) => return Ok(IpCidr::Ipv4(cidr)), + Err(_) => (), + } + + #[cfg(feature = "proto-ipv6")] + #[allow(clippy::single_match)] + match Ipv6Cidr::from_str(s) { + Ok(cidr) => return Ok(IpCidr::Ipv6(cidr)), + Err(_) => (), + } + + Err(()) + } +} + +impl FromStr for IpEndpoint { + type Err = (); + + fn from_str(s: &str) -> Result { + Parser::new(s).until_eof(|p| p.accept_ip_endpoint()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + macro_rules! check_cidr_test_array { + ($tests:expr, $from_str:path, $variant:path) => { + for &(s, cidr) in &$tests { + assert_eq!($from_str(s), cidr); + assert_eq!(IpCidr::from_str(s), cidr.map($variant)); + + if let Ok(cidr) = cidr { + assert_eq!($from_str(&format!("{}", cidr)), Ok(cidr)); + assert_eq!(IpCidr::from_str(&format!("{}", cidr)), Ok($variant(cidr))); + } + } + }; + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))] + fn test_mac() { + assert_eq!(EthernetAddress::from_str(""), Err(())); + assert_eq!( + EthernetAddress::from_str("02:00:00:00:00:00"), + Ok(EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x00])) + ); + assert_eq!( + EthernetAddress::from_str("01:23:45:67:89:ab"), + Ok(EthernetAddress([0x01, 0x23, 0x45, 0x67, 0x89, 0xab])) + ); + assert_eq!( + EthernetAddress::from_str("cd:ef:10:00:00:00"), + Ok(EthernetAddress([0xcd, 0xef, 0x10, 0x00, 0x00, 0x00])) + ); + assert_eq!( + EthernetAddress::from_str("00:00:00:ab:cd:ef"), + Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef])) + ); + assert_eq!( + EthernetAddress::from_str("00-00-00-ab-cd-ef"), + Ok(EthernetAddress([0x00, 0x00, 0x00, 0xab, 0xcd, 0xef])) + ); + assert_eq!( + EthernetAddress::from_str("AB-CD-EF-00-00-00"), + Ok(EthernetAddress([0xab, 0xcd, 0xef, 0x00, 0x00, 0x00])) + ); + assert_eq!(EthernetAddress::from_str("100:00:00:00:00:00"), Err(())); + assert_eq!(EthernetAddress::from_str("002:00:00:00:00:00"), Err(())); + assert_eq!(EthernetAddress::from_str("02:00:00:00:00:000"), Err(())); + assert_eq!(EthernetAddress::from_str("02:00:00:00:00:0x"), Err(())); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_ip_ipv4() { + assert_eq!(IpAddress::from_str(""), Err(())); + assert_eq!( + IpAddress::from_str("1.2.3.4"), + Ok(IpAddress::Ipv4(Ipv4Address::new(1, 2, 3, 4))) + ); + assert_eq!(IpAddress::from_str("x"), Err(())); + } + + #[test] + #[cfg(feature = "proto-ipv6")] + fn test_ip_ipv6() { + assert_eq!(IpAddress::from_str(""), Err(())); + assert_eq!( + IpAddress::from_str("fe80::1"), + Ok(IpAddress::Ipv6(Ipv6Address::new( + 0xfe80, 0, 0, 0, 0, 0, 0, 1 + ))) + ); + assert_eq!(IpAddress::from_str("x"), Err(())); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_cidr_ipv4() { + let tests = [ + ( + "127.0.0.1/8", + Ok(Ipv4Cidr::new(Ipv4Address::new(127, 0, 0, 1), 8u8)), + ), + ( + "192.168.1.1/24", + Ok(Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 1), 24u8)), + ), + ( + "8.8.8.8/32", + Ok(Ipv4Cidr::new(Ipv4Address::new(8, 8, 8, 8), 32u8)), + ), + ( + "8.8.8.8/0", + Ok(Ipv4Cidr::new(Ipv4Address::new(8, 8, 8, 8), 0u8)), + ), + ("", Err(())), + ("1", Err(())), + ("127.0.0.1", Err(())), + ("127.0.0.1/", Err(())), + ("127.0.0.1/33", Err(())), + ("127.0.0.1/111", Err(())), + ("/32", Err(())), + ]; + + check_cidr_test_array!(tests, Ipv4Cidr::from_str, IpCidr::Ipv4); + } + + #[test] + #[cfg(feature = "proto-ipv6")] + fn test_cidr_ipv6() { + let tests = [ + ( + "fe80::1/64", + Ok(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + 64u8, + )), + ), + ( + "fe80::/64", + Ok(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), + 64u8, + )), + ), + ("::1/128", Ok(Ipv6Cidr::new(Ipv6Address::LOCALHOST, 128u8))), + ("::/128", Ok(Ipv6Cidr::new(Ipv6Address::UNSPECIFIED, 128u8))), + ( + "fe80:0:0:0:0:0:0:1/64", + Ok(Ipv6Cidr::new( + Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + 64u8, + )), + ), + ("fe80:0:0:0:0:0:0:1|64", Err(())), + ("fe80::|64", Err(())), + ("fe80::1::/64", Err(())), + ]; + check_cidr_test_array!(tests, Ipv6Cidr::from_str, IpCidr::Ipv6); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_endpoint_ipv4() { + assert_eq!(IpEndpoint::from_str(""), Err(())); + assert_eq!(IpEndpoint::from_str("x"), Err(())); + assert_eq!( + IpEndpoint::from_str("127.0.0.1"), + Ok(IpEndpoint { + addr: IpAddress::v4(127, 0, 0, 1), + port: 0 + }) + ); + assert_eq!( + IpEndpoint::from_str("127.0.0.1:12345"), + Ok(IpEndpoint { + addr: IpAddress::v4(127, 0, 0, 1), + port: 12345 + }) + ); + } + + #[test] + #[cfg(feature = "proto-ipv6")] + fn test_endpoint_ipv6() { + assert_eq!(IpEndpoint::from_str(""), Err(())); + assert_eq!(IpEndpoint::from_str("x"), Err(())); + assert_eq!( + IpEndpoint::from_str("fe80::1"), + Ok(IpEndpoint { + addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), + port: 0 + }) + ); + assert_eq!( + IpEndpoint::from_str("[fe80::1]:12345"), + Ok(IpEndpoint { + addr: IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), + port: 12345 + }) + ); + assert_eq!( + IpEndpoint::from_str("[::]:12345"), + Ok(IpEndpoint { + addr: IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 0), + port: 12345 + }) + ); + } +} diff --git a/vendor/smoltcp/src/phy/fault_injector.rs b/vendor/smoltcp/src/phy/fault_injector.rs new file mode 100644 index 00000000..1f30db54 --- /dev/null +++ b/vendor/smoltcp/src/phy/fault_injector.rs @@ -0,0 +1,332 @@ +use crate::phy::{self, Device, DeviceCapabilities}; +use crate::time::{Duration, Instant}; + +use super::PacketMeta; + +// We use our own RNG to stay compatible with #![no_std]. +// The use of the RNG below has a slight bias, but it doesn't matter. +fn xorshift32(state: &mut u32) -> u32 { + let mut x = *state; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *state = x; + x +} + +// This could be fixed once associated consts are stable. +const MTU: usize = 1536; + +#[derive(Debug, Default, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct Config { + corrupt_pct: u8, + drop_pct: u8, + max_size: usize, + max_tx_rate: u64, + max_rx_rate: u64, + interval: Duration, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct State { + rng_seed: u32, + refilled_at: Instant, + tx_bucket: u64, + rx_bucket: u64, +} + +impl State { + fn maybe(&mut self, pct: u8) -> bool { + xorshift32(&mut self.rng_seed) % 100 < pct as u32 + } + + fn corrupt>(&mut self, mut buffer: T) { + let buffer = buffer.as_mut(); + // We introduce a single bitflip, as the most likely, and the hardest to detect, error. + let index = (xorshift32(&mut self.rng_seed) as usize) % buffer.len(); + let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8; + buffer[index] ^= bit; + } + + fn refill(&mut self, config: &Config, timestamp: Instant) { + if timestamp - self.refilled_at > config.interval { + self.tx_bucket = config.max_tx_rate; + self.rx_bucket = config.max_rx_rate; + self.refilled_at = timestamp; + } + } + + fn maybe_transmit(&mut self, config: &Config, timestamp: Instant) -> bool { + if config.max_tx_rate == 0 { + return true; + } + + self.refill(config, timestamp); + if self.tx_bucket > 0 { + self.tx_bucket -= 1; + true + } else { + false + } + } + + fn maybe_receive(&mut self, config: &Config, timestamp: Instant) -> bool { + if config.max_rx_rate == 0 { + return true; + } + + self.refill(config, timestamp); + if self.rx_bucket > 0 { + self.rx_bucket -= 1; + true + } else { + false + } + } +} + +/// A fault injector device. +/// +/// A fault injector is a device that alters packets traversing through it to simulate +/// adverse network conditions (such as random packet loss or corruption), or software +/// or hardware limitations (such as a limited number or size of usable network buffers). +#[derive(Debug)] +pub struct FaultInjector { + inner: D, + state: State, + config: Config, + rx_buf: [u8; MTU], +} + +impl FaultInjector { + /// Create a fault injector device, using the given random number generator seed. + pub fn new(inner: D, seed: u32) -> FaultInjector { + FaultInjector { + inner, + state: State { + rng_seed: seed, + refilled_at: Instant::from_millis(0), + tx_bucket: 0, + rx_bucket: 0, + }, + config: Config::default(), + rx_buf: [0u8; MTU], + } + } + + /// Return the underlying device, consuming the fault injector. + pub fn into_inner(self) -> D { + self.inner + } + + /// Return the probability of corrupting a packet, in percents. + pub fn corrupt_chance(&self) -> u8 { + self.config.corrupt_pct + } + + /// Return the probability of dropping a packet, in percents. + pub fn drop_chance(&self) -> u8 { + self.config.drop_pct + } + + /// Return the maximum packet size, in octets. + pub fn max_packet_size(&self) -> usize { + self.config.max_size + } + + /// Return the maximum packet transmission rate, in packets per second. + pub fn max_tx_rate(&self) -> u64 { + self.config.max_tx_rate + } + + /// Return the maximum packet reception rate, in packets per second. + pub fn max_rx_rate(&self) -> u64 { + self.config.max_rx_rate + } + + /// Return the interval for packet rate limiting, in milliseconds. + pub fn bucket_interval(&self) -> Duration { + self.config.interval + } + + /// Set the probability of corrupting a packet, in percents. + /// + /// # Panics + /// This function panics if the probability is not between 0% and 100%. + pub fn set_corrupt_chance(&mut self, pct: u8) { + if pct > 100 { + panic!("percentage out of range") + } + self.config.corrupt_pct = pct + } + + /// Set the probability of dropping a packet, in percents. + /// + /// # Panics + /// This function panics if the probability is not between 0% and 100%. + pub fn set_drop_chance(&mut self, pct: u8) { + if pct > 100 { + panic!("percentage out of range") + } + self.config.drop_pct = pct + } + + /// Set the maximum packet size, in octets. + pub fn set_max_packet_size(&mut self, size: usize) { + self.config.max_size = size + } + + /// Set the maximum packet transmission rate, in packets per interval. + pub fn set_max_tx_rate(&mut self, rate: u64) { + self.config.max_tx_rate = rate + } + + /// Set the maximum packet reception rate, in packets per interval. + pub fn set_max_rx_rate(&mut self, rate: u64) { + self.config.max_rx_rate = rate + } + + /// Set the interval for packet rate limiting, in milliseconds. + pub fn set_bucket_interval(&mut self, interval: Duration) { + self.state.refilled_at = Instant::from_millis(0); + self.config.interval = interval + } +} + +impl Device for FaultInjector { + type RxToken<'a> + = RxToken<'a> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, D::TxToken<'a>> + where + Self: 'a; + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = self.inner.capabilities(); + if caps.max_transmission_unit > MTU { + caps.max_transmission_unit = MTU; + } + caps + } + + fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let (rx_token, tx_token) = self.inner.receive(timestamp)?; + let rx_meta = as phy::RxToken>::meta(&rx_token); + + let len = super::RxToken::consume(rx_token, |buffer| { + if (self.config.max_size > 0 && buffer.len() > self.config.max_size) + || buffer.len() > self.rx_buf.len() + { + net_trace!("rx: dropping a packet that is too large"); + return None; + } + self.rx_buf[..buffer.len()].copy_from_slice(buffer); + Some(buffer.len()) + })?; + + let buf = &mut self.rx_buf[..len]; + + if self.state.maybe(self.config.drop_pct) { + net_trace!("rx: randomly dropping a packet"); + return None; + } + + if !self.state.maybe_receive(&self.config, timestamp) { + net_trace!("rx: dropping a packet because of rate limiting"); + return None; + } + + if self.state.maybe(self.config.corrupt_pct) { + net_trace!("rx: randomly corrupting a packet"); + self.state.corrupt(&mut buf[..]); + } + + let rx = RxToken { buf, meta: rx_meta }; + let tx = TxToken { + state: &mut self.state, + config: self.config, + token: tx_token, + junk: [0; MTU], + timestamp, + }; + Some((rx, tx)) + } + + fn transmit(&mut self, timestamp: Instant) -> Option> { + self.inner.transmit(timestamp).map(|token| TxToken { + state: &mut self.state, + config: self.config, + token, + junk: [0; MTU], + timestamp, + }) + } +} + +#[doc(hidden)] +pub struct RxToken<'a> { + buf: &'a mut [u8], + meta: PacketMeta, +} + +impl<'a> phy::RxToken for RxToken<'a> { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(self.buf) + } + + fn meta(&self) -> phy::PacketMeta { + self.meta + } +} + +#[doc(hidden)] +pub struct TxToken<'a, Tx: phy::TxToken> { + state: &'a mut State, + config: Config, + token: Tx, + junk: [u8; MTU], + timestamp: Instant, +} + +impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let drop = if self.state.maybe(self.config.drop_pct) { + net_trace!("tx: randomly dropping a packet"); + true + } else if self.config.max_size > 0 && len > self.config.max_size { + net_trace!("tx: dropping a packet that is too large"); + true + } else if !self.state.maybe_transmit(&self.config, self.timestamp) { + net_trace!("tx: dropping a packet because of rate limiting"); + true + } else { + false + }; + + if drop { + return f(&mut self.junk[..len]); + } + + self.token.consume(len, |buf| { + if self.state.maybe(self.config.corrupt_pct) { + net_trace!("tx: corrupting a packet"); + self.state.corrupt(&mut *buf); + } + f(buf) + }) + } + + fn set_meta(&mut self, meta: PacketMeta) { + self.token.set_meta(meta); + } +} diff --git a/vendor/smoltcp/src/phy/fuzz_injector.rs b/vendor/smoltcp/src/phy/fuzz_injector.rs new file mode 100644 index 00000000..5598488a --- /dev/null +++ b/vendor/smoltcp/src/phy/fuzz_injector.rs @@ -0,0 +1,133 @@ +use crate::phy::{self, Device, DeviceCapabilities}; +use crate::time::Instant; +use alloc::vec::Vec; + +// This could be fixed once associated consts are stable. +const MTU: usize = 1536; + +/// Represents a fuzzer. It is expected to replace bytes in the packet with fuzzed data. +pub trait Fuzzer { + /// Modify a single packet with fuzzed data. + fn fuzz_packet(&self, packet_data: &mut [u8]); +} + +/// A fuzz injector device. +/// +/// A fuzz injector is a device that alters packets traversing through it according to the +/// directions of a guided fuzzer. It is designed to support fuzzing internal state machines inside +/// smoltcp, and is not for production use. +#[allow(unused)] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FuzzInjector { + inner: D, + fuzz_tx: FTx, + fuzz_rx: FRx, +} + +#[allow(unused)] +impl FuzzInjector { + /// Create a fuzz injector device. + pub fn new(inner: D, fuzz_tx: FTx, fuzz_rx: FRx) -> FuzzInjector { + FuzzInjector { + inner, + fuzz_tx, + fuzz_rx, + } + } + + /// Return the underlying device, consuming the fuzz injector. + pub fn into_inner(self) -> D { + self.inner + } +} + +impl Device for FuzzInjector +where + FTx: Fuzzer, + FRx: Fuzzer, +{ + type RxToken<'a> + = RxToken<'a, D::RxToken<'a>, FRx> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, D::TxToken<'a>, FTx> + where + Self: 'a; + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = self.inner.capabilities(); + if caps.max_transmission_unit > MTU { + caps.max_transmission_unit = MTU; + } + caps + } + + fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.inner.receive(timestamp).map(|(rx_token, tx_token)| { + let rx = RxToken { + fuzzer: &mut self.fuzz_rx, + token: rx_token, + }; + let tx = TxToken { + fuzzer: &mut self.fuzz_tx, + token: tx_token, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, timestamp: Instant) -> Option> { + self.inner.transmit(timestamp).map(|token| TxToken { + fuzzer: &mut self.fuzz_tx, + token: token, + }) + } +} + +#[doc(hidden)] +pub struct RxToken<'a, Rx: phy::RxToken, F: Fuzzer + 'a> { + fuzzer: &'a F, + token: Rx, +} + +impl<'a, Rx: phy::RxToken, FRx: Fuzzer> phy::RxToken for RxToken<'a, Rx, FRx> { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + self.token.consume(|buffer| { + let mut new_buffer: Vec = buffer.to_vec(); + self.fuzzer.fuzz_packet(&mut new_buffer); + f(&mut new_buffer) + }) + } + + fn meta(&self) -> phy::PacketMeta { + self.token.meta() + } +} + +#[doc(hidden)] +pub struct TxToken<'a, Tx: phy::TxToken, F: Fuzzer + 'a> { + fuzzer: &'a F, + token: Tx, +} + +impl<'a, Tx: phy::TxToken, FTx: Fuzzer> phy::TxToken for TxToken<'a, Tx, FTx> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.token.consume(len, |buf| { + let result = f(buf); + self.fuzzer.fuzz_packet(buf); + result + }) + } + + fn set_meta(&mut self, meta: phy::PacketMeta) { + self.token.set_meta(meta) + } +} diff --git a/vendor/smoltcp/src/phy/loopback.rs b/vendor/smoltcp/src/phy/loopback.rs new file mode 100644 index 00000000..fd3aea7d --- /dev/null +++ b/vendor/smoltcp/src/phy/loopback.rs @@ -0,0 +1,89 @@ +use alloc::collections::VecDeque; +use alloc::vec; +use alloc::vec::Vec; + +use crate::phy::{self, ChecksumCapabilities, Device, DeviceCapabilities, Medium}; +use crate::time::Instant; + +/// A loopback device. +#[derive(Debug)] +pub struct Loopback { + pub(crate) queue: VecDeque>, + medium: Medium, +} + +#[allow(clippy::new_without_default)] +impl Loopback { + /// Creates a loopback device. + /// + /// Every packet transmitted through this device will be received through it + /// in FIFO order. + pub fn new(medium: Medium) -> Loopback { + Loopback { + queue: VecDeque::new(), + medium, + } + } +} + +impl Device for Loopback { + type RxToken<'a> = RxToken; + type TxToken<'a> = TxToken<'a>; + + fn capabilities(&self) -> DeviceCapabilities { + DeviceCapabilities { + max_transmission_unit: 65535, + medium: self.medium, + checksum: ChecksumCapabilities::ignored(), + ..DeviceCapabilities::default() + } + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.queue.pop_front().map(move |buffer| { + let rx = RxToken { buffer }; + let tx = TxToken { + queue: &mut self.queue, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + queue: &mut self.queue, + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(&self.buffer) + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct TxToken<'a> { + queue: &'a mut VecDeque>, +} + +impl<'a> phy::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + self.queue.push_back(buffer); + result + } +} diff --git a/vendor/smoltcp/src/phy/mod.rs b/vendor/smoltcp/src/phy/mod.rs new file mode 100644 index 00000000..1d3de66e --- /dev/null +++ b/vendor/smoltcp/src/phy/mod.rs @@ -0,0 +1,411 @@ +/*! Access to networking hardware. + +The `phy` module deals with the *network devices*. It provides a trait +for transmitting and receiving frames, [Device](trait.Device.html) +and implementations of it: + + * the [_loopback_](struct.Loopback.html), for zero dependency testing; + * _middleware_ [Tracer](struct.Tracer.html) and + [FaultInjector](struct.FaultInjector.html), to facilitate debugging; + * _adapters_ [RawSocket](struct.RawSocket.html) and + [TunTapInterface](struct.TunTapInterface.html), to transmit and receive frames + on the host OS. +*/ +#![cfg_attr( + feature = "medium-ethernet", + doc = r##" +# Examples + +An implementation of the [Device](trait.Device.html) trait for a simple hardware +Ethernet controller could look as follows: + +```rust +use smoltcp::phy::{self, DeviceCapabilities, Device, Medium}; +use smoltcp::time::Instant; + +struct StmPhy { + rx_buffer: [u8; 1536], + tx_buffer: [u8; 1536], +} + +impl<'a> StmPhy { + fn new() -> StmPhy { + StmPhy { + rx_buffer: [0; 1536], + tx_buffer: [0; 1536], + } + } +} + +impl phy::Device for StmPhy { + type RxToken<'a> = StmPhyRxToken<'a> where Self: 'a; + type TxToken<'a> = StmPhyTxToken<'a> where Self: 'a; + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + Some((StmPhyRxToken(&mut self.rx_buffer[..]), + StmPhyTxToken(&mut self.tx_buffer[..]))) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(StmPhyTxToken(&mut self.tx_buffer[..])) + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1536; + caps.max_burst_size = Some(1); + caps.medium = Medium::Ethernet; + caps + } +} + +struct StmPhyRxToken<'a>(&'a mut [u8]); + +impl<'a> phy::RxToken for StmPhyRxToken<'a> { + fn consume(self, f: F) -> R + where F: FnOnce(& [u8]) -> R + { + // TODO: receive packet into buffer + let result = f(&self.0); + println!("rx called"); + result + } +} + +struct StmPhyTxToken<'a>(&'a mut [u8]); + +impl<'a> phy::TxToken for StmPhyTxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where F: FnOnce(&mut [u8]) -> R + { + let result = f(&mut self.0[..len]); + println!("tx called {}", len); + // TODO: send packet out + result + } +} +``` +"## +)] + +use crate::time::Instant; + +#[cfg(all( + any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"), + unix +))] +mod sys; + +mod fault_injector; +#[cfg(feature = "alloc")] +mod fuzz_injector; +#[cfg(feature = "alloc")] +mod loopback; +mod pcap_writer; +#[cfg(all(feature = "phy-raw_socket", unix))] +mod raw_socket; +mod tracer; +#[cfg(all( + feature = "phy-tuntap_interface", + any(target_os = "linux", target_os = "android") +))] +mod tuntap_interface; + +#[cfg(all( + any(feature = "phy-raw_socket", feature = "phy-tuntap_interface"), + unix +))] +pub use self::sys::wait; + +pub use self::fault_injector::FaultInjector; +#[cfg(feature = "alloc")] +pub use self::fuzz_injector::{FuzzInjector, Fuzzer}; +#[cfg(feature = "alloc")] +pub use self::loopback::Loopback; +pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter}; +#[cfg(all(feature = "phy-raw_socket", unix))] +pub use self::raw_socket::RawSocket; +pub use self::tracer::{Tracer, TracerDirection, TracerPacket}; +#[cfg(all( + feature = "phy-tuntap_interface", + any(target_os = "linux", target_os = "android") +))] +pub use self::tuntap_interface::TunTapInterface; + +/// The IPV4 payload fragment size must be an increment of this value. +#[cfg(feature = "proto-ipv4-fragmentation")] +pub const IPV4_FRAGMENT_PAYLOAD_ALIGNMENT: usize = 8; + +/// Metadata associated to a packet. +/// +/// The packet metadata is a set of attributes associated to network packets +/// as they travel up or down the stack. The metadata is get/set by the +/// [`Device`] implementations or by the user when sending/receiving packets from a +/// socket. +/// +/// Metadata fields are enabled via Cargo features. If no field is enabled, this +/// struct becomes zero-sized, which allows the compiler to optimize it out as if +/// the packet metadata mechanism didn't exist at all. +/// +/// Currently only UDP sockets allow setting/retrieving packet metadata. The metadata +/// for packets emitted with other sockets will be all default values. +/// +/// This struct is marked as `#[non_exhaustive]`. This means it is not possible to +/// create it directly by specifying all fields. You have to instead create it with +/// default values and then set the fields you want. This makes adding metadata +/// fields a non-breaking change. +/// +/// ```rust +/// let mut meta = smoltcp::phy::PacketMeta::default(); +/// #[cfg(feature = "packetmeta-id")] +/// { +/// meta.id = 15; +/// } +/// ``` +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)] +#[non_exhaustive] +pub struct PacketMeta { + #[cfg(feature = "packetmeta-id")] + pub id: u32, +} + +/// A description of checksum behavior for a particular protocol. +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Checksum { + /// Verify checksum when receiving and compute checksum when sending. + #[default] + Both, + /// Verify checksum when receiving. + Rx, + /// Compute checksum before sending. + Tx, + /// Ignore checksum completely. + None, +} + +impl Checksum { + /// Returns whether checksum should be verified when receiving. + pub fn rx(&self) -> bool { + match *self { + Checksum::Both | Checksum::Rx => true, + _ => false, + } + } + + /// Returns whether checksum should be verified when sending. + pub fn tx(&self) -> bool { + match *self { + Checksum::Both | Checksum::Tx => true, + _ => false, + } + } +} + +/// A description of checksum behavior for every supported protocol. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ChecksumCapabilities { + pub ipv4: Checksum, + pub udp: Checksum, + pub tcp: Checksum, + #[cfg(feature = "proto-ipv4")] + pub icmpv4: Checksum, + #[cfg(feature = "proto-ipv6")] + pub icmpv6: Checksum, +} + +impl ChecksumCapabilities { + /// Checksum behavior that results in not computing or verifying checksums + /// for any of the supported protocols. + pub fn ignored() -> Self { + ChecksumCapabilities { + ipv4: Checksum::None, + udp: Checksum::None, + tcp: Checksum::None, + #[cfg(feature = "proto-ipv4")] + icmpv4: Checksum::None, + #[cfg(feature = "proto-ipv6")] + icmpv6: Checksum::None, + } + } +} + +/// A description of device capabilities. +/// +/// Higher-level protocols may achieve higher throughput or lower latency if they consider +/// the bandwidth or packet size limitations. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct DeviceCapabilities { + /// Medium of the device. + /// + /// This indicates what kind of packet the sent/received bytes are, and determines + /// some behaviors of Interface. For example, ARP/NDISC address resolution is only done + /// for Ethernet mediums. + pub medium: Medium, + + /// Maximum transmission unit. + /// + /// The network device is unable to send or receive frames larger than the value returned + /// by this function. + /// + /// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but + /// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14. + /// + /// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet + /// devices. This is a common source of confusion. + /// + /// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets. + pub max_transmission_unit: usize, + + /// Maximum burst size, in terms of MTU. + /// + /// The network device is unable to send or receive bursts large than the value returned + /// by this function. + /// + /// If `None`, there is no fixed limit on burst size, e.g. if network buffers are + /// dynamically allocated. + pub max_burst_size: Option, + + /// Checksum behavior. + /// + /// If the network device is capable of verifying or computing checksums for some protocols, + /// it can request that the stack not do so in software to improve performance. + pub checksum: ChecksumCapabilities, +} + +impl DeviceCapabilities { + pub fn ip_mtu(&self) -> usize { + match self.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + self.max_transmission_unit - crate::wire::EthernetFrame::<&[u8]>::header_len() + } + #[cfg(feature = "medium-ip")] + Medium::Ip => self.max_transmission_unit, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => self.max_transmission_unit, // TODO(thvdveld): what is the MTU for Medium::IEEE802 + } + } + + /// Special case method to determine the maximum payload size that is based on the MTU and also aligned per spec. + #[cfg(feature = "proto-ipv4-fragmentation")] + pub fn max_ipv4_fragment_size(&self, ip_header_len: usize) -> usize { + let payload_mtu = self.ip_mtu() - ip_header_len; + payload_mtu - (payload_mtu % IPV4_FRAGMENT_PAYLOAD_ALIGNMENT) + } +} + +/// Type of medium of a device. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Medium { + /// Ethernet medium. Devices of this type send and receive Ethernet frames, + /// and interfaces using it must do neighbor discovery via ARP or NDISC. + /// + /// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode. + #[cfg(feature = "medium-ethernet")] + Ethernet, + + /// IP medium. Devices of this type send and receive IP frames, without an + /// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done. + /// + /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode. + #[cfg(feature = "medium-ip")] + Ip, + + #[cfg(feature = "medium-ieee802154")] + Ieee802154, +} + +impl Default for Medium { + fn default() -> Medium { + #[cfg(feature = "medium-ethernet")] + return Medium::Ethernet; + #[cfg(all(feature = "medium-ip", not(feature = "medium-ethernet")))] + return Medium::Ip; + #[cfg(all( + feature = "medium-ieee802154", + not(feature = "medium-ip"), + not(feature = "medium-ethernet") + ))] + return Medium::Ieee802154; + #[cfg(all( + not(feature = "medium-ip"), + not(feature = "medium-ethernet"), + not(feature = "medium-ieee802154") + ))] + return panic!("No medium enabled"); + } +} + +/// An interface for sending and receiving raw network frames. +/// +/// The interface is based on _tokens_, which are types that allow to receive/transmit a +/// single packet. The `receive` and `transmit` functions only construct such tokens, the +/// real sending/receiving operation are performed when the tokens are consumed. +pub trait Device { + type RxToken<'a>: RxToken + where + Self: 'a; + type TxToken<'a>: TxToken + where + Self: 'a; + + /// Construct a token pair consisting of one receive token and one transmit token. + /// + /// The additional transmit token makes it possible to generate a reply packet based + /// on the contents of the received packet. For example, this makes it possible to + /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes + /// need to be sent back, without heap allocation. + /// + /// The timestamp must be a number of milliseconds, monotonically increasing since an + /// arbitrary moment in time, such as system startup. + fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; + + /// Construct a transmit token. + /// + /// The timestamp must be a number of milliseconds, monotonically increasing since an + /// arbitrary moment in time, such as system startup. + fn transmit(&mut self, timestamp: Instant) -> Option>; + + /// Get a description of device capabilities. + fn capabilities(&self) -> DeviceCapabilities; +} + +/// A token to receive a single network packet. +pub trait RxToken { + /// Consumes the token to receive a single network packet. + /// + /// This method receives a packet and then calls the given closure `f` with the raw + /// packet bytes as argument. + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R; + + /// The Packet ID associated with the frame received by this [`RxToken`] + fn meta(&self) -> PacketMeta { + PacketMeta::default() + } +} + +/// A token to transmit a single network packet. +pub trait TxToken { + /// Consumes the token to send a single network packet. + /// + /// This method constructs a transmit buffer of size `len` and calls the passed + /// closure `f` with a mutable reference to that buffer. The closure should construct + /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure + /// returns, the transmit buffer is sent out. + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; + + /// The Packet ID to be associated with the frame to be transmitted by this [`TxToken`]. + #[allow(unused_variables)] + fn set_meta(&mut self, meta: PacketMeta) {} +} diff --git a/vendor/smoltcp/src/phy/pcap_writer.rs b/vendor/smoltcp/src/phy/pcap_writer.rs new file mode 100644 index 00000000..a1014fc3 --- /dev/null +++ b/vendor/smoltcp/src/phy/pcap_writer.rs @@ -0,0 +1,266 @@ +use byteorder::{ByteOrder, NativeEndian}; +use core::cell::RefCell; +use phy::Medium; +#[cfg(feature = "std")] +use std::io::Write; + +use crate::phy::{self, Device, DeviceCapabilities}; +use crate::time::Instant; + +enum_with_unknown! { + /// Captured packet header type. + pub enum PcapLinkType(u32) { + /// Ethernet frames + Ethernet = 1, + /// IPv4 or IPv6 packets (depending on the version field) + Ip = 101, + /// IEEE 802.15.4 packets without FCS. + Ieee802154WithoutFcs = 230, + } +} + +/// Packet capture mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PcapMode { + /// Capture both received and transmitted packets. + Both, + /// Capture only received packets. + RxOnly, + /// Capture only transmitted packets. + TxOnly, +} + +/// A packet capture sink. +pub trait PcapSink { + /// Write data into the sink. + fn write(&mut self, data: &[u8]); + + /// Flush data written into the sync. + fn flush(&mut self) {} + + /// Write an `u16` into the sink, in native byte order. + fn write_u16(&mut self, value: u16) { + let mut bytes = [0u8; 2]; + NativeEndian::write_u16(&mut bytes, value); + self.write(&bytes[..]) + } + + /// Write an `u32` into the sink, in native byte order. + fn write_u32(&mut self, value: u32) { + let mut bytes = [0u8; 4]; + NativeEndian::write_u32(&mut bytes, value); + self.write(&bytes[..]) + } + + /// Write the libpcap global header into the sink. + /// + /// This method may be overridden e.g. if special synchronization is necessary. + fn global_header(&mut self, link_type: PcapLinkType) { + self.write_u32(0xa1b2c3d4); // magic number + self.write_u16(2); // major version + self.write_u16(4); // minor version + self.write_u32(0); // timezone (= UTC) + self.write_u32(0); // accuracy (not used) + self.write_u32(65535); // maximum packet length + self.write_u32(link_type.into()); // link-layer header type + } + + /// Write the libpcap packet header into the sink. + /// + /// See also the note for [global_header](#method.global_header). + /// + /// # Panics + /// This function panics if `length` is greater than 65535. + fn packet_header(&mut self, timestamp: Instant, length: usize) { + assert!(length <= 65535); + + self.write_u32(timestamp.secs() as u32); // timestamp seconds + self.write_u32(timestamp.micros() as u32); // timestamp microseconds + self.write_u32(length as u32); // captured length + self.write_u32(length as u32); // original length + } + + /// Write the libpcap packet header followed by packet data into the sink. + /// + /// See also the note for [global_header](#method.global_header). + fn packet(&mut self, timestamp: Instant, packet: &[u8]) { + self.packet_header(timestamp, packet.len()); + self.write(packet); + self.flush(); + } +} + +#[cfg(feature = "std")] +impl PcapSink for T { + fn write(&mut self, data: &[u8]) { + T::write_all(self, data).expect("cannot write") + } + + fn flush(&mut self) { + T::flush(self).expect("cannot flush") + } +} + +/// A packet capture writer device. +/// +/// Every packet transmitted or received through this device is timestamped +/// and written (in the [libpcap] format) using the provided [sink]. +/// Note that writes are fine-grained, and buffering is recommended. +/// +/// [libpcap]: https://wiki.wireshark.org/Development/LibpcapFileFormat +/// [sink]: trait.PcapSink.html +#[derive(Debug)] +pub struct PcapWriter +where + D: Device, + S: PcapSink, +{ + lower: D, + sink: RefCell, + mode: PcapMode, +} + +impl PcapWriter { + /// Creates a packet capture writer. + pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter { + let medium = lower.capabilities().medium; + let link_type = match medium { + #[cfg(feature = "medium-ip")] + Medium::Ip => PcapLinkType::Ip, + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => PcapLinkType::Ethernet, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => PcapLinkType::Ieee802154WithoutFcs, + }; + sink.global_header(link_type); + PcapWriter { + lower, + sink: RefCell::new(sink), + mode, + } + } + + /// Get a reference to the underlying device. + /// + /// Even if the device offers reading through a standard reference, it is inadvisable to + /// directly read from the device as doing so will circumvent the packet capture. + pub fn get_ref(&self) -> &D { + &self.lower + } + + /// Get a mutable reference to the underlying device. + /// + /// It is inadvisable to directly read from the device as doing so will circumvent the packet capture. + pub fn get_mut(&mut self) -> &mut D { + &mut self.lower + } +} + +impl Device for PcapWriter +where + S: PcapSink, +{ + type RxToken<'a> + = RxToken<'a, D::RxToken<'a>, S> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, D::TxToken<'a>, S> + where + Self: 'a; + + fn capabilities(&self) -> DeviceCapabilities { + self.lower.capabilities() + } + + fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let sink = &self.sink; + let mode = self.mode; + self.lower + .receive(timestamp) + .map(move |(rx_token, tx_token)| { + let rx = RxToken { + token: rx_token, + sink, + mode, + timestamp, + }; + let tx = TxToken { + token: tx_token, + sink, + mode, + timestamp, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, timestamp: Instant) -> Option> { + let sink = &self.sink; + let mode = self.mode; + self.lower.transmit(timestamp).map(move |token| TxToken { + token, + sink, + mode, + timestamp, + }) + } +} + +#[doc(hidden)] +pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> { + token: Rx, + sink: &'a RefCell, + mode: PcapMode, + timestamp: Instant, +} + +impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> { + fn consume R>(self, f: F) -> R { + self.token.consume(|buffer| { + match self.mode { + PcapMode::Both | PcapMode::RxOnly => self + .sink + .borrow_mut() + .packet(self.timestamp, buffer.as_ref()), + PcapMode::TxOnly => (), + } + f(buffer) + }) + } + + fn meta(&self) -> phy::PacketMeta { + self.token.meta() + } +} + +#[doc(hidden)] +pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> { + token: Tx, + sink: &'a RefCell, + mode: PcapMode, + timestamp: Instant, +} + +impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.token.consume(len, |buffer| { + let result = f(buffer); + match self.mode { + PcapMode::Both | PcapMode::TxOnly => { + self.sink.borrow_mut().packet(self.timestamp, buffer) + } + PcapMode::RxOnly => (), + }; + result + }) + } + + fn set_meta(&mut self, meta: phy::PacketMeta) { + self.token.set_meta(meta) + } +} diff --git a/vendor/smoltcp/src/phy/raw_socket.rs b/vendor/smoltcp/src/phy/raw_socket.rs new file mode 100644 index 00000000..54552899 --- /dev/null +++ b/vendor/smoltcp/src/phy/raw_socket.rs @@ -0,0 +1,139 @@ +use std::cell::RefCell; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::Rc; +use std::vec::Vec; + +use crate::phy::{self, Device, DeviceCapabilities, Medium, sys}; +use crate::time::Instant; + +/// A socket that captures or transmits the complete frame. +#[derive(Debug)] +pub struct RawSocket { + medium: Medium, + lower: Rc>, + mtu: usize, +} + +impl AsRawFd for RawSocket { + fn as_raw_fd(&self) -> RawFd { + self.lower.borrow().as_raw_fd() + } +} + +impl RawSocket { + /// Creates a raw socket, bound to the interface called `name`. + /// + /// This requires superuser privileges or a corresponding capability bit + /// set on the executable. + pub fn new(name: &str, medium: Medium) -> io::Result { + let mut lower = sys::RawSocketDesc::new(name, medium)?; + lower.bind_interface()?; + + let mut mtu = lower.interface_mtu()?; + + #[cfg(feature = "medium-ieee802154")] + if medium == Medium::Ieee802154 { + // SIOCGIFMTU returns 127 - (ACK_PSDU - FCS - 1) - FCS. + // 127 - (5 - 2 - 1) - 2 = 123 + // For IEEE802154, we want to add (ACK_PSDU - FCS - 1), since that is what SIOCGIFMTU + // uses as the size of the link layer header. + // + // https://github.com/torvalds/linux/blob/7475e51b87969e01a6812eac713a1c8310372e8a/net/mac802154/iface.c#L541 + mtu += 2; + } + + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) + // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. + mtu += crate::wire::EthernetFrame::<&[u8]>::header_len() + } + + Ok(RawSocket { + medium, + lower: Rc::new(RefCell::new(lower)), + mtu, + }) + } +} + +impl Device for RawSocket { + type RxToken<'a> + = RxToken + where + Self: 'a; + type TxToken<'a> + = TxToken + where + Self: 'a; + + fn capabilities(&self) -> DeviceCapabilities { + DeviceCapabilities { + max_transmission_unit: self.mtu, + medium: self.medium, + ..DeviceCapabilities::default() + } + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut lower = self.lower.borrow_mut(); + let mut buffer = vec![0; self.mtu]; + match lower.recv(&mut buffer[..]) { + Ok(size) => { + buffer.resize(size, 0); + let rx = RxToken { buffer }; + let tx = TxToken { + lower: self.lower.clone(), + }; + Some((rx, tx)) + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, + Err(err) => panic!("{}", err), + } + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + lower: self.lower.clone(), + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(&self.buffer[..]) + } +} + +#[doc(hidden)] +pub struct TxToken { + lower: Rc>, +} + +impl phy::TxToken for TxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut lower = self.lower.borrow_mut(); + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + match lower.send(&buffer[..]) { + Ok(_) => {} + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + net_debug!("phy: tx failed due to WouldBlock") + } + Err(err) => panic!("{}", err), + } + result + } +} diff --git a/vendor/smoltcp/src/phy/sys/bpf.rs b/vendor/smoltcp/src/phy/sys/bpf.rs new file mode 100644 index 00000000..1a74606b --- /dev/null +++ b/vendor/smoltcp/src/phy/sys/bpf.rs @@ -0,0 +1,206 @@ +use std::io; +use std::mem; +use std::os::unix::io::{AsRawFd, RawFd}; + +use libc; + +use super::{ifreq, ifreq_for}; +use crate::phy::Medium; +use crate::wire::ETHERNET_HEADER_LEN; + +/// set interface +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd" +))] +const BIOCSETIF: libc::c_ulong = 0x8020426c; +/// get buffer length +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd" +))] +const BIOCGBLEN: libc::c_ulong = 0x40044266; +/// set immediate/nonblocking read +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd" +))] +const BIOCIMMEDIATE: libc::c_ulong = 0x80044270; +/// set bpf_hdr struct size +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "netbsd"))] +const SIZEOF_BPF_HDR: usize = 18; +/// set bpf_hdr struct size +#[cfg(any(target_os = "openbsd", target_os = "freebsd"))] +const SIZEOF_BPF_HDR: usize = 24; +/// The actual header length may be larger than the bpf_hdr struct due to aligning +/// see +/// and +/// and +/// for FreeBSD, core::mem::size_of::() = 32 when run on a FreeBSD system. +#[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd" +))] +const BPF_HDRLEN: usize = (((SIZEOF_BPF_HDR + ETHERNET_HEADER_LEN) + mem::align_of::() - 1) + & !(mem::align_of::() - 1)) + - ETHERNET_HEADER_LEN; + +macro_rules! try_ioctl { + ($fd:expr,$cmd:expr,$req:expr) => { + unsafe { + if libc::ioctl($fd, $cmd, $req) == -1 { + return Err(io::Error::last_os_error()); + } + } + }; +} + +#[derive(Debug)] +pub struct BpfDevice { + fd: libc::c_int, + ifreq: ifreq, +} + +impl AsRawFd for BpfDevice { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +fn open_device() -> io::Result { + unsafe { + for i in 0..256 { + let dev = format!("/dev/bpf{}\0", i); + match libc::open( + dev.as_ptr() as *const libc::c_char, + libc::O_RDWR | libc::O_NONBLOCK, + ) { + -1 => continue, + fd => return Ok(fd), + }; + } + } + // at this point, all 256 BPF devices were busy and we weren't able to open any + Err(io::Error::last_os_error()) +} + +impl BpfDevice { + pub fn new(name: &str, _medium: Medium) -> io::Result { + Ok(BpfDevice { + fd: open_device()?, + ifreq: ifreq_for(name), + }) + } + + pub fn bind_interface(&mut self) -> io::Result<()> { + try_ioctl!(self.fd, BIOCSETIF, &mut self.ifreq); + + Ok(()) + } + + /// This in fact does not return the interface's mtu, + /// but it returns the size of the buffer that the app needs to allocate + /// for the BPF device + /// + /// The `SIOGIFMTU` cant be called on a BPF descriptor. There is a workaround + /// to get the actual interface mtu, but this should work better + /// + /// To get the interface MTU, you would need to create a raw socket first, + /// and then call `SIOGIFMTU` for the same interface your BPF device is "bound" to. + /// This MTU that you would get would not include the length of `struct bpf_hdr` + /// which gets prepended to every packet by BPF, + /// and your packet will be truncated if it has the length of the MTU. + /// + /// The buffer size for BPF is usually 4096 bytes, MTU is typically 1500 bytes. + /// You could do something like `mtu += BPF_HDRLEN`, + /// but you must change the buffer size the BPF device expects using `BIOCSBLEN` accordingly, + /// and you must set it before setting the interface with the `BIOCSETIF` ioctl. + /// + /// The reason I said this should work better is because you might see some unexpected behavior, + /// truncated/unaligned packets, I/O errors on read() + /// if you change the buffer size to the actual MTU of the interface. + pub fn interface_mtu(&mut self) -> io::Result { + let mut bufsize: libc::c_int = 1; + try_ioctl!(self.fd, BIOCIMMEDIATE, &mut bufsize as *mut libc::c_int); + try_ioctl!(self.fd, BIOCGBLEN, &mut bufsize as *mut libc::c_int); + + Ok(bufsize as usize) + } + + pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { + unsafe { + let len = libc::read( + self.fd, + buffer.as_mut_ptr() as *mut libc::c_void, + buffer.len(), + ); + + if len == -1 || len < BPF_HDRLEN as isize { + return Err(io::Error::last_os_error()); + } + + let len = len as usize; + + libc::memmove( + buffer.as_mut_ptr() as *mut libc::c_void, + &buffer[BPF_HDRLEN] as *const u8 as *const libc::c_void, + len - BPF_HDRLEN, + ); + + Ok(len) + } + } + + pub fn send(&mut self, buffer: &[u8]) -> io::Result { + unsafe { + let len = libc::write( + self.fd, + buffer.as_ptr() as *const libc::c_void, + buffer.len(), + ); + + if len == -1 { + panic!("{:?}", io::Error::last_os_error()) + } + + Ok(len as usize) + } + } +} + +impl Drop for BpfDevice { + fn drop(&mut self) { + unsafe { + libc::close(self.fd); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(any(target_os = "macos", target_os = "netbsd"))] + fn test_aligned_bpf_hdr_len() { + assert_eq!(18, BPF_HDRLEN); + } + + #[test] + #[cfg(target_os = "openbsd")] + fn test_aligned_bpf_hdr_len() { + assert_eq!(26, BPF_HDRLEN); + } +} diff --git a/vendor/smoltcp/src/phy/sys/linux.rs b/vendor/smoltcp/src/phy/sys/linux.rs new file mode 100644 index 00000000..6c3388e3 --- /dev/null +++ b/vendor/smoltcp/src/phy/sys/linux.rs @@ -0,0 +1,26 @@ +#![allow(unused)] + +pub const SIOCGIFMTU: libc::c_ulong = 0x8921; +pub const SIOCGIFINDEX: libc::c_ulong = 0x8933; +pub const ETH_P_ALL: libc::c_short = 0x0003; +pub const ETH_P_IEEE802154: libc::c_short = 0x00F6; + +// Constant definition as per +// https://github.com/golang/sys/blob/master/unix/zerrors_linux_.go +pub const TUNSETIFF: libc::c_ulong = if cfg!(any( + target_arch = "mips", + all(target_arch = "mips", target_endian = "little"), + target_arch = "mips64", + all(target_arch = "mips64", target_endian = "little"), + target_arch = "powerpc", + target_arch = "powerpc64", + all(target_arch = "powerpc64", target_endian = "little"), + target_arch = "sparc64" +)) { + 0x800454CA +} else { + 0x400454CA +}; +pub const IFF_TUN: libc::c_int = 0x0001; +pub const IFF_TAP: libc::c_int = 0x0002; +pub const IFF_NO_PI: libc::c_int = 0x1000; diff --git a/vendor/smoltcp/src/phy/sys/mod.rs b/vendor/smoltcp/src/phy/sys/mod.rs new file mode 100644 index 00000000..3f42301c --- /dev/null +++ b/vendor/smoltcp/src/phy/sys/mod.rs @@ -0,0 +1,136 @@ +#![allow(unsafe_code)] + +use crate::time::Duration; +use std::os::unix::io::RawFd; +use std::{io, mem, ptr}; + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[path = "linux.rs"] +mod imp; + +#[cfg(all( + feature = "phy-raw_socket", + not(any(target_os = "linux", target_os = "android")), + unix +))] +pub mod bpf; +#[cfg(all( + feature = "phy-raw_socket", + any(target_os = "linux", target_os = "android") +))] +pub mod raw_socket; +#[cfg(all( + feature = "phy-tuntap_interface", + any(target_os = "linux", target_os = "android") +))] +pub mod tuntap_interface; + +#[cfg(all( + feature = "phy-raw_socket", + not(any(target_os = "linux", target_os = "android")), + unix +))] +pub use self::bpf::BpfDevice as RawSocketDesc; +#[cfg(all( + feature = "phy-raw_socket", + any(target_os = "linux", target_os = "android") +))] +pub use self::raw_socket::RawSocketDesc; +#[cfg(all( + feature = "phy-tuntap_interface", + any(target_os = "linux", target_os = "android") +))] +pub use self::tuntap_interface::TunTapInterfaceDesc; + +/// Wait until given file descriptor becomes readable, but no longer than given timeout. +pub fn wait(fd: RawFd, duration: Option) -> io::Result<()> { + unsafe { + let mut readfds = { + let mut readfds = mem::MaybeUninit::::uninit(); + libc::FD_ZERO(readfds.as_mut_ptr()); + libc::FD_SET(fd, readfds.as_mut_ptr()); + readfds.assume_init() + }; + + let mut writefds = { + let mut writefds = mem::MaybeUninit::::uninit(); + libc::FD_ZERO(writefds.as_mut_ptr()); + writefds.assume_init() + }; + + let mut exceptfds = { + let mut exceptfds = mem::MaybeUninit::::uninit(); + libc::FD_ZERO(exceptfds.as_mut_ptr()); + exceptfds.assume_init() + }; + + let mut timeout = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + let timeout_ptr = if let Some(duration) = duration { + timeout.tv_sec = duration.secs() as libc::time_t; + timeout.tv_usec = (duration.millis() * 1_000) as libc::suseconds_t; + &mut timeout as *mut _ + } else { + ptr::null_mut() + }; + + let res = libc::select( + fd + 1, + &mut readfds, + &mut writefds, + &mut exceptfds, + timeout_ptr, + ); + if res == -1 { + return Err(io::Error::last_os_error()); + } + Ok(()) + } +} + +#[cfg(all( + any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"), + unix +))] +#[repr(C)] +#[derive(Debug)] +struct ifreq { + ifr_name: [libc::c_char; libc::IF_NAMESIZE], + ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */ +} + +#[cfg(all( + any(feature = "phy-tuntap_interface", feature = "phy-raw_socket"), + unix +))] +fn ifreq_for(name: &str) -> ifreq { + let mut ifreq = ifreq { + ifr_name: [0; libc::IF_NAMESIZE], + ifr_data: 0, + }; + for (i, byte) in name.as_bytes().iter().enumerate() { + ifreq.ifr_name[i] = *byte as libc::c_char + } + ifreq +} + +#[cfg(all( + any(target_os = "linux", target_os = "android"), + any(feature = "phy-tuntap_interface", feature = "phy-raw_socket") +))] +fn ifreq_ioctl( + lower: libc::c_int, + ifreq: &mut ifreq, + cmd: libc::c_ulong, +) -> io::Result { + unsafe { + let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq); + if res == -1 { + return Err(io::Error::last_os_error()); + } + } + + Ok(ifreq.ifr_data) +} diff --git a/vendor/smoltcp/src/phy/sys/raw_socket.rs b/vendor/smoltcp/src/phy/sys/raw_socket.rs new file mode 100644 index 00000000..f37fe960 --- /dev/null +++ b/vendor/smoltcp/src/phy/sys/raw_socket.rs @@ -0,0 +1,115 @@ +use super::*; +use crate::phy::Medium; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::{io, mem}; + +#[derive(Debug)] +pub struct RawSocketDesc { + protocol: libc::c_short, + lower: libc::c_int, + ifreq: ifreq, +} + +impl AsRawFd for RawSocketDesc { + fn as_raw_fd(&self) -> RawFd { + self.lower + } +} + +impl RawSocketDesc { + pub fn new(name: &str, medium: Medium) -> io::Result { + let protocol = match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => imp::ETH_P_ALL, + #[cfg(feature = "medium-ip")] + Medium::Ip => imp::ETH_P_ALL, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => imp::ETH_P_IEEE802154, + }; + + let lower = unsafe { + let lower = libc::socket( + libc::AF_PACKET, + libc::SOCK_RAW | libc::SOCK_NONBLOCK, + protocol.to_be() as i32, + ); + if lower == -1 { + return Err(io::Error::last_os_error()); + } + lower + }; + + Ok(RawSocketDesc { + protocol, + lower, + ifreq: ifreq_for(name), + }) + } + + pub fn interface_mtu(&mut self) -> io::Result { + ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFMTU).map(|mtu| mtu as usize) + } + + pub fn bind_interface(&mut self) -> io::Result<()> { + let sockaddr = libc::sockaddr_ll { + sll_family: libc::AF_PACKET as u16, + sll_protocol: self.protocol.to_be() as u16, + sll_ifindex: ifreq_ioctl(self.lower, &mut self.ifreq, imp::SIOCGIFINDEX)?, + sll_hatype: 1, + sll_pkttype: 0, + sll_halen: 6, + sll_addr: [0; 8], + }; + + unsafe { + let res = libc::bind( + self.lower, + &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr, + mem::size_of::() as libc::socklen_t, + ); + if res == -1 { + return Err(io::Error::last_os_error()); + } + } + + Ok(()) + } + + pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { + unsafe { + let len = libc::recv( + self.lower, + buffer.as_mut_ptr() as *mut libc::c_void, + buffer.len(), + 0, + ); + if len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(len as usize) + } + } + + pub fn send(&mut self, buffer: &[u8]) -> io::Result { + unsafe { + let len = libc::send( + self.lower, + buffer.as_ptr() as *const libc::c_void, + buffer.len(), + 0, + ); + if len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(len as usize) + } + } +} + +impl Drop for RawSocketDesc { + fn drop(&mut self) { + unsafe { + libc::close(self.lower); + } + } +} diff --git a/vendor/smoltcp/src/phy/sys/tuntap_interface.rs b/vendor/smoltcp/src/phy/sys/tuntap_interface.rs new file mode 100644 index 00000000..7c1748c7 --- /dev/null +++ b/vendor/smoltcp/src/phy/sys/tuntap_interface.rs @@ -0,0 +1,127 @@ +use super::*; +use crate::phy::Medium; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; + +#[derive(Debug)] +pub struct TunTapInterfaceDesc { + lower: libc::c_int, + mtu: usize, +} + +impl AsRawFd for TunTapInterfaceDesc { + fn as_raw_fd(&self) -> RawFd { + self.lower + } +} + +impl TunTapInterfaceDesc { + pub fn new(name: &str, medium: Medium) -> io::Result { + let lower = unsafe { + let lower = libc::open(c"/dev/net/tun".as_ptr(), libc::O_RDWR | libc::O_NONBLOCK); + if lower == -1 { + return Err(io::Error::last_os_error()); + } + lower + }; + + let mut ifreq = ifreq_for(name); + Self::attach_interface_ifreq(lower, medium, &mut ifreq)?; + let mtu = Self::mtu_ifreq(medium, &mut ifreq)?; + + Ok(TunTapInterfaceDesc { lower, mtu }) + } + + pub fn from_fd(fd: RawFd, mtu: usize) -> io::Result { + Ok(TunTapInterfaceDesc { lower: fd, mtu }) + } + + fn attach_interface_ifreq( + lower: libc::c_int, + medium: Medium, + ifr: &mut ifreq, + ) -> io::Result<()> { + let mode = match medium { + #[cfg(feature = "medium-ip")] + Medium::Ip => imp::IFF_TUN, + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => imp::IFF_TAP, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + ifr.ifr_data = mode | imp::IFF_NO_PI; + ifreq_ioctl(lower, ifr, imp::TUNSETIFF).map(|_| ()) + } + + fn mtu_ifreq(medium: Medium, ifr: &mut ifreq) -> io::Result { + let lower = unsafe { + let lower = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP); + if lower == -1 { + return Err(io::Error::last_os_error()); + } + lower + }; + + let ip_mtu = ifreq_ioctl(lower, ifr, imp::SIOCGIFMTU).map(|mtu| mtu as usize); + + unsafe { + libc::close(lower); + } + + // Propagate error after close, to ensure we always close. + let ip_mtu = ip_mtu?; + + // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.) + // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it. + let mtu = match medium { + #[cfg(feature = "medium-ip")] + Medium::Ip => ip_mtu, + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => ip_mtu + crate::wire::EthernetFrame::<&[u8]>::header_len(), + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => todo!(), + }; + + Ok(mtu) + } + + pub fn interface_mtu(&self) -> io::Result { + Ok(self.mtu) + } + + pub fn recv(&mut self, buffer: &mut [u8]) -> io::Result { + unsafe { + let len = libc::read( + self.lower, + buffer.as_mut_ptr() as *mut libc::c_void, + buffer.len(), + ); + if len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(len as usize) + } + } + + pub fn send(&mut self, buffer: &[u8]) -> io::Result { + unsafe { + let len = libc::write( + self.lower, + buffer.as_ptr() as *const libc::c_void, + buffer.len(), + ); + if len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(len as usize) + } + } +} + +impl Drop for TunTapInterfaceDesc { + fn drop(&mut self) { + unsafe { + libc::close(self.lower); + } + } +} diff --git a/vendor/smoltcp/src/phy/tracer.rs b/vendor/smoltcp/src/phy/tracer.rs new file mode 100644 index 00000000..4fa9417a --- /dev/null +++ b/vendor/smoltcp/src/phy/tracer.rs @@ -0,0 +1,355 @@ +use core::fmt; + +use crate::phy::{self, Device, DeviceCapabilities, Medium}; +use crate::time::Instant; +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +/// A tracer device. +/// +/// A tracer is a device that pretty prints all packets traversing it +/// using the provided writer function, and then passes them to another +/// device. +pub struct Tracer { + inner: D, + writer: fn(Instant, TracerPacket), +} + +impl Tracer { + /// Create a tracer device. + pub fn new(inner: D, writer: fn(timestamp: Instant, packet: TracerPacket)) -> Tracer { + Tracer { inner, writer } + } + + /// Get a reference to the underlying device. + /// + /// Even if the device offers reading through a standard reference, it is inadvisable to + /// directly read from the device as doing so will circumvent the tracing. + pub fn get_ref(&self) -> &D { + &self.inner + } + + /// Get a mutable reference to the underlying device. + /// + /// It is inadvisable to directly read from the device as doing so will circumvent the tracing. + pub fn get_mut(&mut self) -> &mut D { + &mut self.inner + } + + /// Return the underlying device, consuming the tracer. + pub fn into_inner(self) -> D { + self.inner + } +} + +impl Device for Tracer { + type RxToken<'a> + = RxToken> + where + Self: 'a; + type TxToken<'a> + = TxToken> + where + Self: 'a; + + fn capabilities(&self) -> DeviceCapabilities { + self.inner.capabilities() + } + + fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let medium = self.inner.capabilities().medium; + self.inner.receive(timestamp).map(|(rx_token, tx_token)| { + let rx = RxToken { + token: rx_token, + writer: self.writer, + medium, + timestamp, + }; + let tx = TxToken { + token: tx_token, + writer: self.writer, + medium, + timestamp, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, timestamp: Instant) -> Option> { + let medium = self.inner.capabilities().medium; + self.inner.transmit(timestamp).map(|tx_token| TxToken { + token: tx_token, + medium, + writer: self.writer, + timestamp, + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + token: Rx, + writer: fn(Instant, TracerPacket), + medium: Medium, + timestamp: Instant, +} + +impl phy::RxToken for RxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + self.token.consume(|buffer| { + (self.writer)( + self.timestamp, + TracerPacket { + buffer, + medium: self.medium, + direction: TracerDirection::RX, + }, + ); + f(buffer) + }) + } + + fn meta(&self) -> phy::PacketMeta { + self.token.meta() + } +} + +#[doc(hidden)] +pub struct TxToken { + token: Tx, + writer: fn(Instant, TracerPacket), + medium: Medium, + timestamp: Instant, +} + +impl phy::TxToken for TxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.token.consume(len, |buffer| { + let result = f(buffer); + (self.writer)( + self.timestamp, + TracerPacket { + buffer, + medium: self.medium, + direction: TracerDirection::TX, + }, + ); + result + }) + } + + fn set_meta(&mut self, meta: phy::PacketMeta) { + self.token.set_meta(meta) + } +} + +/// Packet which is being traced by [Tracer](struct.Tracer.html) device. +#[derive(Debug, Clone, Copy)] +pub struct TracerPacket<'a> { + /// Packet buffer + pub buffer: &'a [u8], + /// Packet medium + pub medium: Medium, + /// Direction in which packet is being traced + pub direction: TracerDirection, +} + +/// Direction on which packet is being traced +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum TracerDirection { + /// Packet is received by Smoltcp interface + RX, + /// Packet is transmitted by Smoltcp interface + TX, +} + +impl<'a> fmt::Display for TracerPacket<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let prefix = match self.direction { + TracerDirection::RX => "<- ", + TracerDirection::TX => "-> ", + }; + + let mut indent = PrettyIndent::new(prefix); + match self.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print( + &self.buffer, + f, + &mut indent, + ), + #[cfg(feature = "medium-ip")] + Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) { + #[cfg(feature = "proto-ipv4")] + Ok(crate::wire::IpVersion::Ipv4) => { + crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print( + &self.buffer, + f, + &mut indent, + ) + } + #[cfg(feature = "proto-ipv6")] + Ok(crate::wire::IpVersion::Ipv6) => { + crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print( + &self.buffer, + f, + &mut indent, + ) + } + _ => f.write_str("unrecognized IP version"), + }, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => Ok(()), // XXX + } + } +} + +#[cfg(test)] +mod tests { + use core::cell::RefCell; + use std::collections::VecDeque; + + use super::*; + + use crate::phy::ChecksumCapabilities; + use crate::{ + phy::{Device, Loopback, RxToken, TxToken}, + time::Instant, + }; + + #[cfg(any( + feature = "medium-ethernet", + feature = "medium-ip", + feature = "medium-ieee802154" + ))] + #[test] + fn test_tracer() { + type TracerEvent = (Instant, Vec, Medium, TracerDirection); + thread_local! { + static TRACE_EVENTS: RefCell> = const { RefCell::new(VecDeque::new()) }; + } + TRACE_EVENTS.replace(VecDeque::new()); + + let medium = Medium::default(); + + let loopback_device = Loopback::new(medium); + let mut tracer_device = Tracer::new(loopback_device, |instant, packet| { + TRACE_EVENTS.with_borrow_mut(|events| { + events.push_back(( + instant, + packet.buffer.to_owned(), + packet.medium, + packet.direction, + )) + }); + }); + + let expected_payload = [1, 2, 3, 4, 5, 6, 7, 8]; + + let tx_instant = Instant::from_secs(1); + let tx_token = tracer_device.transmit(tx_instant).unwrap(); + + tx_token.consume(expected_payload.len(), |buf| { + buf.copy_from_slice(&expected_payload) + }); + let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front()); + assert_eq!( + last_event, + Some(( + tx_instant, + expected_payload.into(), + medium, + TracerDirection::TX + )) + ); + let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front()); + assert_eq!(last_event, None); + + let rx_instant = Instant::from_secs(2); + let (rx_token, _) = tracer_device.receive(rx_instant).unwrap(); + let mut rx_pkt = [0; 8]; + rx_token.consume(|buf| rx_pkt.copy_from_slice(buf)); + + assert_eq!(rx_pkt, expected_payload); + + let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front()); + assert_eq!( + last_event, + Some(( + rx_instant, + expected_payload.into(), + medium, + TracerDirection::RX + )) + ); + let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front()); + assert_eq!(last_event, None); + } + + #[cfg(feature = "medium-ethernet")] + #[test] + fn test_tracer_packet_display_ether() { + use crate::wire::{EthernetAddress, EthernetProtocol, EthernetRepr}; + + let repr = EthernetRepr { + src_addr: EthernetAddress([0, 1, 2, 3, 4, 5]), + dst_addr: EthernetAddress([5, 4, 3, 2, 1, 0]), + ethertype: EthernetProtocol::Unknown(0), + }; + let mut buffer = vec![0_u8; repr.buffer_len()]; + { + use crate::wire::EthernetFrame; + + let mut frame = EthernetFrame::new_unchecked(&mut buffer); + repr.emit(&mut frame); + } + + let pkt = TracerPacket { + buffer: &buffer, + medium: Medium::Ethernet, + direction: TracerDirection::RX, + }; + + let pkt_pretty = pkt.to_string(); + assert_eq!( + pkt_pretty, + "<- EthernetII src=00-01-02-03-04-05 dst=05-04-03-02-01-00 type=0x0000" + ); + } + + #[cfg(all(feature = "medium-ip", feature = "proto-ipv4"))] + #[test] + fn test_tracer_packet_display_ip() { + use crate::wire::{IpProtocol, Ipv4Address, Ipv4Repr}; + + let repr = Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 0, 1), + dst_addr: Ipv4Address::new(10, 0, 0, 2), + next_header: IpProtocol::Unknown(255), + payload_len: 0, + hop_limit: 64, + }; + + let mut buffer = vec![0_u8; repr.buffer_len()]; + { + use crate::wire::Ipv4Packet; + + let mut packet = Ipv4Packet::new_unchecked(&mut buffer); + repr.emit(&mut packet, &ChecksumCapabilities::default()); + } + + let pkt = TracerPacket { + buffer: &buffer, + medium: Medium::Ip, + direction: TracerDirection::TX, + }; + + let pkt_pretty = pkt.to_string(); + assert_eq!(pkt_pretty, "-> IPv4 src=10.0.0.1 dst=10.0.0.2 proto=0xff"); + } +} diff --git a/vendor/smoltcp/src/phy/tuntap_interface.rs b/vendor/smoltcp/src/phy/tuntap_interface.rs new file mode 100644 index 00000000..29821bf4 --- /dev/null +++ b/vendor/smoltcp/src/phy/tuntap_interface.rs @@ -0,0 +1,126 @@ +use std::cell::RefCell; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::rc::Rc; +use std::vec::Vec; + +use crate::phy::{self, Device, DeviceCapabilities, Medium, sys}; +use crate::time::Instant; + +/// A virtual TUN (IP) or TAP (Ethernet) interface. +#[derive(Debug)] +pub struct TunTapInterface { + lower: Rc>, + mtu: usize, + medium: Medium, +} + +impl AsRawFd for TunTapInterface { + fn as_raw_fd(&self) -> RawFd { + self.lower.borrow().as_raw_fd() + } +} + +impl TunTapInterface { + /// Attaches to a TUN/TAP interface called `name`, or creates it if it does not exist. + /// + /// If `name` is a persistent interface configured with UID of the current user, + /// no special privileges are needed. Otherwise, this requires superuser privileges + /// or a corresponding capability set on the executable. + pub fn new(name: &str, medium: Medium) -> io::Result { + let lower = sys::TunTapInterfaceDesc::new(name, medium)?; + let mtu = lower.interface_mtu()?; + Ok(TunTapInterface { + lower: Rc::new(RefCell::new(lower)), + mtu, + medium, + }) + } + + /// Attaches to a TUN/TAP interface specified by file descriptor `fd`. + /// + /// On platforms like Android, a file descriptor to a tun interface is exposed. + /// On these platforms, a TunTapInterface cannot be instantiated with a name. + pub fn from_fd(fd: RawFd, medium: Medium, mtu: usize) -> io::Result { + let lower = sys::TunTapInterfaceDesc::from_fd(fd, mtu)?; + Ok(TunTapInterface { + lower: Rc::new(RefCell::new(lower)), + mtu, + medium, + }) + } +} + +impl Device for TunTapInterface { + type RxToken<'a> = RxToken; + type TxToken<'a> = TxToken; + + fn capabilities(&self) -> DeviceCapabilities { + DeviceCapabilities { + max_transmission_unit: self.mtu, + medium: self.medium, + ..DeviceCapabilities::default() + } + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut lower = self.lower.borrow_mut(); + let mut buffer = vec![0; self.mtu]; + match lower.recv(&mut buffer[..]) { + Ok(size) => { + buffer.resize(size, 0); + let rx = RxToken { buffer }; + let tx = TxToken { + lower: self.lower.clone(), + }; + Some((rx, tx)) + } + Err(err) if err.kind() == io::ErrorKind::WouldBlock => None, + Err(err) => panic!("{}", err), + } + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + lower: self.lower.clone(), + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(&self.buffer[..]) + } +} + +#[doc(hidden)] +pub struct TxToken { + lower: Rc>, +} + +impl phy::TxToken for TxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut lower = self.lower.borrow_mut(); + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + match lower.send(&buffer[..]) { + Ok(_) => {} + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + net_debug!("phy: tx failed due to WouldBlock") + } + Err(err) => panic!("{}", err), + } + result + } +} diff --git a/vendor/smoltcp/src/rand.rs b/vendor/smoltcp/src/rand.rs new file mode 100644 index 00000000..15d88f77 --- /dev/null +++ b/vendor/smoltcp/src/rand.rs @@ -0,0 +1,40 @@ +#![allow(unsafe_code)] +#![allow(unused)] + +#[derive(Debug)] +pub(crate) struct Rand { + state: u64, +} + +impl Rand { + pub(crate) const fn new(seed: u64) -> Self { + Self { state: seed } + } + + pub(crate) fn rand_u32(&mut self) -> u32 { + // sPCG32 from https://www.pcg-random.org/paper.html + // see also https://nullprogram.com/blog/2017/09/21/ + const M: u64 = 0xbb2efcec3c39611d; + const A: u64 = 0x7590ef39; + + let s = self.state.wrapping_mul(M).wrapping_add(A); + self.state = s; + + let shift = 29 - (s >> 61); + (s >> shift) as u32 + } + + pub(crate) fn rand_u16(&mut self) -> u16 { + let n = self.rand_u32(); + (n ^ (n >> 16)) as u16 + } + + pub(crate) fn rand_source_port(&mut self) -> u16 { + loop { + let res = self.rand_u16(); + if res > 1024 { + return res; + } + } + } +} diff --git a/vendor/smoltcp/src/socket/dhcpv4.rs b/vendor/smoltcp/src/socket/dhcpv4.rs new file mode 100644 index 00000000..3dc4cf00 --- /dev/null +++ b/vendor/smoltcp/src/socket/dhcpv4.rs @@ -0,0 +1,1431 @@ +#[cfg(feature = "async")] +use core::task::Waker; + +use crate::iface::Context; +use crate::time::{Duration, Instant}; +use crate::wire::dhcpv4::field as dhcpv4_field; +use crate::wire::{ + DHCP_CLIENT_PORT, DHCP_MAX_DNS_SERVER_COUNT, DHCP_SERVER_PORT, DhcpMessageType, DhcpPacket, + DhcpRepr, IpAddress, IpProtocol, Ipv4Address, Ipv4AddressExt, Ipv4Cidr, Ipv4Repr, + UDP_HEADER_LEN, UdpRepr, +}; +use crate::wire::{DhcpOption, HardwareAddress}; +use heapless::Vec; + +#[cfg(feature = "async")] +use super::WakerRegistration; + +use super::PollAt; + +const DEFAULT_LEASE_DURATION: Duration = Duration::from_secs(120); + +const DEFAULT_PARAMETER_REQUEST_LIST: &[u8] = &[ + dhcpv4_field::OPT_SUBNET_MASK, + dhcpv4_field::OPT_ROUTER, + dhcpv4_field::OPT_DOMAIN_NAME_SERVER, +]; + +/// IPv4 configuration data provided by the DHCP server. +#[derive(Debug, Eq, PartialEq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config<'a> { + /// Information on how to reach the DHCP server that responded with DHCP + /// configuration. + pub server: ServerInfo, + /// IP address + pub address: Ipv4Cidr, + /// Router address, also known as default gateway. Does not necessarily + /// match the DHCP server's address. + pub router: Option, + /// DNS servers + pub dns_servers: Vec, + /// Received DHCP packet + pub packet: Option>, +} + +/// Information on how to reach a DHCP server. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ServerInfo { + /// IP address to use as destination in outgoing packets + pub address: Ipv4Address, + /// Server identifier to use in outgoing packets. Usually equal to server_address, + /// but may differ in some situations (eg DHCP relays) + pub identifier: Ipv4Address, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DiscoverState { + /// When to send next request + retry_at: Instant, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RequestState { + /// When to send next request + retry_at: Instant, + /// How many retries have been done + retry: u16, + /// Server we're trying to request from + server: ServerInfo, + /// IP address that we're trying to request. + requested_ip: Ipv4Address, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RenewState { + /// Active network config + config: Config<'static>, + + /// Renew timer. When reached, we will start attempting + /// to renew this lease with the DHCP server. + /// + /// Must be less or equal than `rebind_at`. + renew_at: Instant, + + /// Rebind timer. When reached, we will start broadcasting to renew + /// this lease with any DHCP server. + /// + /// Must be greater than or equal to `renew_at`, and less than or + /// equal to `expires_at`. + rebind_at: Instant, + + /// Whether the T2 time has elapsed + rebinding: bool, + + /// Expiration timer. When reached, this lease is no longer valid, so it must be + /// thrown away and the ethernet interface deconfigured. + expires_at: Instant, +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum ClientState { + /// Discovering the DHCP server + Discovering(DiscoverState), + /// Requesting an address + Requesting(RequestState), + /// Having an address, refresh it periodically. + Renewing(RenewState), +} + +/// Timeout and retry configuration. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct RetryConfig { + pub discover_timeout: Duration, + /// The REQUEST timeout doubles every 2 tries. + pub initial_request_timeout: Duration, + pub request_retries: u16, + pub min_renew_timeout: Duration, + /// An upper bound on how long to wait between retrying a renew or rebind. + /// + /// Set this to [`Duration::MAX`] if you don't want to impose an upper bound. + pub max_renew_timeout: Duration, +} + +impl Default for RetryConfig { + fn default() -> Self { + Self { + discover_timeout: Duration::from_secs(10), + initial_request_timeout: Duration::from_secs(5), + request_retries: 5, + min_renew_timeout: Duration::from_secs(60), + max_renew_timeout: Duration::MAX, + } + } +} + +/// Return value for the `Dhcpv4Socket::poll` function +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event<'a> { + /// Configuration has been lost (for example, the lease has expired) + Deconfigured, + /// Configuration has been newly acquired, or modified. + Configured(Config<'a>), +} + +#[derive(Debug)] +pub struct Socket<'a> { + /// State of the DHCP client. + state: ClientState, + /// Set to true on config/state change, cleared back to false by the `config` function. + config_changed: bool, + /// xid of the last sent message. + transaction_id: u32, + + /// Max lease duration. If set, it sets a maximum cap to the server-provided lease duration. + /// Useful to react faster to IP configuration changes and to test whether renews work correctly. + max_lease_duration: Option, + + retry_config: RetryConfig, + + /// Ignore NAKs. + ignore_naks: bool, + + /// Server port config + pub(crate) server_port: u16, + + /// Client port config + pub(crate) client_port: u16, + + /// A buffer contains options additional to be added to outgoing DHCP + /// packets. + outgoing_options: &'a [DhcpOption<'a>], + /// A buffer containing all requested parameters. + parameter_request_list: Option<&'a [u8]>, + + /// Incoming DHCP packets are copied into this buffer, overwriting the previous. + receive_packet_buffer: Option<&'a mut [u8]>, + + /// Waker registration + #[cfg(feature = "async")] + waker: WakerRegistration, +} + +/// DHCP client socket. +/// +/// The socket acquires an IP address configuration through DHCP autonomously. +/// You must query the configuration with `.poll()` after every call to `Interface::poll()`, +/// and apply the configuration to the `Interface`. +impl<'a> Socket<'a> { + /// Create a DHCPv4 socket + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Socket { + state: ClientState::Discovering(DiscoverState { + retry_at: Instant::from_millis(0), + }), + config_changed: true, + transaction_id: 1, + max_lease_duration: None, + retry_config: RetryConfig::default(), + ignore_naks: false, + outgoing_options: &[], + parameter_request_list: None, + receive_packet_buffer: None, + #[cfg(feature = "async")] + waker: WakerRegistration::new(), + server_port: DHCP_SERVER_PORT, + client_port: DHCP_CLIENT_PORT, + } + } + + /// Set the retry/timeouts configuration. + pub fn set_retry_config(&mut self, config: RetryConfig) { + self.retry_config = config; + } + + /// Gets the current retry/timeouts configuration + pub fn get_retry_config(&self) -> RetryConfig { + self.retry_config + } + + /// Set the outgoing options. + pub fn set_outgoing_options(&mut self, options: &'a [DhcpOption<'a>]) { + self.outgoing_options = options; + } + + /// Set the buffer into which incoming DHCP packets are copied into. + pub fn set_receive_packet_buffer(&mut self, buffer: &'a mut [u8]) { + self.receive_packet_buffer = Some(buffer); + } + + /// Set the parameter request list. + /// + /// This should contain at least `OPT_SUBNET_MASK` (`1`), `OPT_ROUTER` + /// (`3`), and `OPT_DOMAIN_NAME_SERVER` (`6`). + pub fn set_parameter_request_list(&mut self, parameter_request_list: &'a [u8]) { + self.parameter_request_list = Some(parameter_request_list); + } + + /// Get the configured max lease duration. + /// + /// See also [`Self::set_max_lease_duration()`] + pub fn max_lease_duration(&self) -> Option { + self.max_lease_duration + } + + /// Set the max lease duration. + /// + /// When set, the lease duration will be capped at the configured duration if the + /// DHCP server gives us a longer lease. This is generally not recommended, but + /// can be useful for debugging or reacting faster to network configuration changes. + /// + /// If None, no max is applied (the lease duration from the DHCP server is used.) + pub fn set_max_lease_duration(&mut self, max_lease_duration: Option) { + self.max_lease_duration = max_lease_duration; + } + + /// Get whether to ignore NAKs. + /// + /// See also [`Self::set_ignore_naks()`] + pub fn ignore_naks(&self) -> bool { + self.ignore_naks + } + + /// Set whether to ignore NAKs. + /// + /// This is not compliant with the DHCP RFCs, since theoretically + /// we must stop using the assigned IP when receiving a NAK. This + /// can increase reliability on broken networks with buggy routers + /// or rogue DHCP servers, however. + pub fn set_ignore_naks(&mut self, ignore_naks: bool) { + self.ignore_naks = ignore_naks; + } + + /// Set the server/client port + /// + /// Allows you to specify the ports used by DHCP. + /// This is meant to support esoteric usecases allowed by the dhclient program. + pub fn set_ports(&mut self, server_port: u16, client_port: u16) { + self.server_port = server_port; + self.client_port = client_port; + } + + pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt { + let t = match &self.state { + ClientState::Discovering(state) => state.retry_at, + ClientState::Requesting(state) => state.retry_at, + ClientState::Renewing(state) => if state.rebinding { + state.rebind_at + } else { + state.renew_at.min(state.rebind_at) + } + .min(state.expires_at), + }; + PollAt::Time(t) + } + + pub(crate) fn process( + &mut self, + cx: &mut Context, + ip_repr: &Ipv4Repr, + repr: &UdpRepr, + payload: &[u8], + ) { + let src_ip = ip_repr.src_addr; + + // This is enforced in interface.rs. + assert!(repr.src_port == self.server_port && repr.dst_port == self.client_port); + + let dhcp_packet = match DhcpPacket::new_checked(payload) { + Ok(dhcp_packet) => dhcp_packet, + Err(e) => { + net_debug!("DHCP invalid pkt from {}: {:?}", src_ip, e); + return; + } + }; + let dhcp_repr = match DhcpRepr::parse(&dhcp_packet) { + Ok(dhcp_repr) => dhcp_repr, + Err(e) => { + net_debug!("DHCP error parsing pkt from {}: {:?}", src_ip, e); + return; + } + }; + + let HardwareAddress::Ethernet(ethernet_addr) = cx.hardware_addr() else { + panic!("using DHCPv4 socket with a non-ethernet hardware address."); + }; + + if dhcp_repr.client_hardware_address != ethernet_addr { + return; + } + if dhcp_repr.transaction_id != self.transaction_id { + return; + } + let server_identifier = match dhcp_repr.server_identifier { + Some(server_identifier) => server_identifier, + None => { + net_debug!( + "DHCP ignoring {:?} because missing server_identifier", + dhcp_repr.message_type + ); + return; + } + }; + + net_debug!( + "DHCP recv {:?} from {}: {:?}", + dhcp_repr.message_type, + src_ip, + dhcp_repr + ); + + // Copy over the payload into the receive packet buffer. + if let Some(buffer) = self.receive_packet_buffer.as_mut() { + if let Some(buffer) = buffer.get_mut(..payload.len()) { + buffer.copy_from_slice(payload); + } + } + + match (&mut self.state, dhcp_repr.message_type) { + (ClientState::Discovering(_state), DhcpMessageType::Offer) => { + if !dhcp_repr.your_ip.x_is_unicast() { + net_debug!("DHCP ignoring OFFER because your_ip is not unicast"); + return; + } + + self.state = ClientState::Requesting(RequestState { + retry_at: cx.now(), + retry: 0, + server: ServerInfo { + address: src_ip, + identifier: server_identifier, + }, + requested_ip: dhcp_repr.your_ip, // use the offered ip + }); + } + (ClientState::Requesting(state), DhcpMessageType::Ack) => { + if let Some((config, renew_at, rebind_at, expires_at)) = + Self::parse_ack(cx.now(), &dhcp_repr, self.max_lease_duration, state.server) + { + self.state = ClientState::Renewing(RenewState { + config, + renew_at, + rebind_at, + expires_at, + rebinding: false, + }); + self.config_changed(); + } + } + (ClientState::Requesting(_), DhcpMessageType::Nak) => { + if !self.ignore_naks { + self.reset(); + } + } + (ClientState::Renewing(state), DhcpMessageType::Ack) => { + if let Some((config, renew_at, rebind_at, expires_at)) = Self::parse_ack( + cx.now(), + &dhcp_repr, + self.max_lease_duration, + state.config.server, + ) { + state.renew_at = renew_at; + state.rebind_at = rebind_at; + state.rebinding = false; + state.expires_at = expires_at; + // The `receive_packet_buffer` field isn't populated until + // the client asks for the state, but receiving any packet + // will change it, so we indicate that the config has + // changed every time if the receive packet buffer is set, + // but we only write changes to the rest of the config now. + let config_changed = + state.config != config || self.receive_packet_buffer.is_some(); + if state.config != config { + state.config = config; + } + if config_changed { + self.config_changed(); + } + } + } + (ClientState::Renewing(_), DhcpMessageType::Nak) => { + if !self.ignore_naks { + self.reset(); + } + } + _ => { + net_debug!( + "DHCP ignoring {:?}: unexpected in current state", + dhcp_repr.message_type + ); + } + } + } + + fn parse_ack( + now: Instant, + dhcp_repr: &DhcpRepr, + max_lease_duration: Option, + server: ServerInfo, + ) -> Option<(Config<'static>, Instant, Instant, Instant)> { + let subnet_mask = match dhcp_repr.subnet_mask { + Some(subnet_mask) => subnet_mask, + None => { + net_debug!("DHCP ignoring ACK because missing subnet_mask"); + return None; + } + }; + + let prefix_len = match IpAddress::Ipv4(subnet_mask).prefix_len() { + Some(prefix_len) => prefix_len, + None => { + net_debug!("DHCP ignoring ACK because subnet_mask is not a valid mask"); + return None; + } + }; + + if !dhcp_repr.your_ip.x_is_unicast() { + net_debug!("DHCP ignoring ACK because your_ip is not unicast"); + return None; + } + + let mut lease_duration = dhcp_repr + .lease_duration + .map(|d| Duration::from_secs(d as _)) + .unwrap_or(DEFAULT_LEASE_DURATION); + if let Some(max_lease_duration) = max_lease_duration { + lease_duration = lease_duration.min(max_lease_duration); + } + + // Cleanup the DNS servers list, keeping only unicasts/ + // TP-Link TD-W8970 sends 0.0.0.0 as second DNS server if there's only one configured :( + let mut dns_servers = Vec::new(); + + dhcp_repr + .dns_servers + .iter() + .flatten() + .filter(|s| s.x_is_unicast()) + .for_each(|a| { + // This will never produce an error, as both the arrays and `dns_servers` + // have length DHCP_MAX_DNS_SERVER_COUNT + dns_servers.push(*a).ok(); + }); + + let config = Config { + server, + address: Ipv4Cidr::new(dhcp_repr.your_ip, prefix_len), + router: dhcp_repr.router, + dns_servers, + packet: None, + }; + + // Set renew and rebind times as per RFC 2131: + // Times T1 and T2 are configurable by the server through + // options. T1 defaults to (0.5 * duration_of_lease). T2 + // defaults to (0.875 * duration_of_lease). + // When receiving T1 and T2, they must be in the order: + // T1 < T2 < lease_duration + let (renew_duration, rebind_duration) = match ( + dhcp_repr + .renew_duration + .map(|d| Duration::from_secs(d as u64)), + dhcp_repr + .rebind_duration + .map(|d| Duration::from_secs(d as u64)), + ) { + (Some(renew_duration), Some(rebind_duration)) + if renew_duration < rebind_duration && rebind_duration < lease_duration => + { + (renew_duration, rebind_duration) + } + // RFC 2131 does not say what to do if only one value is + // provided, so: + + // If only T1 is provided, set T2 to be 0.75 through the gap + // between T1 and the duration of the lease. If T1 is set to + // the default (0.5 * duration_of_lease), then T2 will also + // be set to the default (0.875 * duration_of_lease). + (Some(renew_duration), None) if renew_duration < lease_duration => ( + renew_duration, + renew_duration + (lease_duration - renew_duration) * 3 / 4, + ), + + // If only T2 is provided, then T1 will be set to be + // whichever is smaller of the default (0.5 * + // duration_of_lease) or T2. + (None, Some(rebind_duration)) if rebind_duration < lease_duration => { + ((lease_duration / 2).min(rebind_duration), rebind_duration) + } + + // Use the defaults if the following order is not met: + // T1 < T2 < lease_duration + (_, _) => { + net_debug!("using default T1 and T2 values since the provided values are invalid"); + (lease_duration / 2, lease_duration * 7 / 8) + } + }; + let renew_at = now + renew_duration; + let rebind_at = now + rebind_duration; + let expires_at = now + lease_duration; + + Some((config, renew_at, rebind_at, expires_at)) + } + + #[cfg(not(test))] + fn random_transaction_id(cx: &mut Context) -> u32 { + cx.rand().rand_u32() + } + + #[cfg(test)] + fn random_transaction_id(_cx: &mut Context) -> u32 { + 0x12345678 + } + + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, (Ipv4Repr, UdpRepr, DhcpRepr)) -> Result<(), E>, + { + // note: Dhcpv4Socket is only usable in ethernet mediums, so the + // unwrap can never fail. + let HardwareAddress::Ethernet(ethernet_addr) = cx.hardware_addr() else { + panic!("using DHCPv4 socket with a non-ethernet hardware address."); + }; + + // Worst case biggest IPv4 header length. + // 0x0f * 4 = 60 bytes. + const MAX_IPV4_HEADER_LEN: usize = 60; + + let mut dhcp_repr = DhcpRepr { + message_type: DhcpMessageType::Discover, + transaction_id: self.transaction_id, + secs: 0, + client_hardware_address: ethernet_addr, + client_ip: Ipv4Address::UNSPECIFIED, + your_ip: Ipv4Address::UNSPECIFIED, + server_ip: Ipv4Address::UNSPECIFIED, + router: None, + subnet_mask: None, + relay_agent_ip: Ipv4Address::UNSPECIFIED, + broadcast: false, + requested_ip: None, + client_identifier: Some(ethernet_addr), + server_identifier: None, + parameter_request_list: Some( + self.parameter_request_list + .unwrap_or(DEFAULT_PARAMETER_REQUEST_LIST), + ), + max_size: Some((cx.ip_mtu() - MAX_IPV4_HEADER_LEN - UDP_HEADER_LEN) as u16), + lease_duration: None, + renew_duration: None, + rebind_duration: None, + dns_servers: None, + additional_options: self.outgoing_options, + }; + + let udp_repr = UdpRepr { + src_port: self.client_port, + dst_port: self.server_port, + }; + + let mut ipv4_repr = Ipv4Repr { + src_addr: Ipv4Address::UNSPECIFIED, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: 0, // filled right before emit + hop_limit: 64, + }; + + match &mut self.state { + ClientState::Discovering(state) => { + if cx.now() < state.retry_at { + return Ok(()); + } + + let next_transaction_id = Self::random_transaction_id(cx); + dhcp_repr.transaction_id = next_transaction_id; + + // send packet + net_debug!( + "DHCP send DISCOVER to {}: {:?}", + ipv4_repr.dst_addr, + dhcp_repr + ); + ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); + emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?; + + // Update state AFTER the packet has been successfully sent. + state.retry_at = cx.now() + self.retry_config.discover_timeout; + self.transaction_id = next_transaction_id; + Ok(()) + } + ClientState::Requesting(state) => { + if cx.now() < state.retry_at { + return Ok(()); + } + + if state.retry >= self.retry_config.request_retries { + net_debug!("DHCP request retries exceeded, restarting discovery"); + self.reset(); + return Ok(()); + } + + dhcp_repr.message_type = DhcpMessageType::Request; + dhcp_repr.requested_ip = Some(state.requested_ip); + dhcp_repr.server_identifier = Some(state.server.identifier); + + net_debug!( + "DHCP send request to {}: {:?}", + ipv4_repr.dst_addr, + dhcp_repr + ); + ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); + emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?; + + // Exponential backoff: Double every 2 retries. + state.retry_at = cx.now() + + (self.retry_config.initial_request_timeout << (state.retry as u32 / 2)); + state.retry += 1; + + Ok(()) + } + ClientState::Renewing(state) => { + let now = cx.now(); + if state.expires_at <= now { + net_debug!("DHCP lease expired"); + self.reset(); + // return Ok so we get polled again + return Ok(()); + } + + if now < state.renew_at || state.rebinding && now < state.rebind_at { + return Ok(()); + } + + state.rebinding |= now >= state.rebind_at; + + ipv4_repr.src_addr = state.config.address.address(); + // Renewing is unicast to the original server, rebinding is broadcast + if !state.rebinding { + ipv4_repr.dst_addr = state.config.server.address; + } + dhcp_repr.message_type = DhcpMessageType::Request; + dhcp_repr.client_ip = state.config.address.address(); + + let next_transaction_id = Self::random_transaction_id(cx); + dhcp_repr.transaction_id = next_transaction_id; + + net_debug!("DHCP send renew to {}: {:?}", ipv4_repr.dst_addr, dhcp_repr); + ipv4_repr.payload_len = udp_repr.header_len() + dhcp_repr.buffer_len(); + emit(cx, (ipv4_repr, udp_repr, dhcp_repr))?; + + // In both RENEWING and REBINDING states, if the client receives no + // response to its DHCPREQUEST message, the client SHOULD wait one-half + // of the remaining time until T2 (in RENEWING state) and one-half of + // the remaining lease time (in REBINDING state), down to a minimum of + // 60 seconds, before retransmitting the DHCPREQUEST message. + if state.rebinding { + state.rebind_at = now + + self + .retry_config + .min_renew_timeout + .max((state.expires_at - now) / 2) + .min(self.retry_config.max_renew_timeout); + } else { + state.renew_at = now + + self + .retry_config + .min_renew_timeout + .max((state.rebind_at - now) / 2) + .min(state.rebind_at - now) + .min(self.retry_config.max_renew_timeout); + } + + self.transaction_id = next_transaction_id; + Ok(()) + } + } + } + + /// Reset state and restart discovery phase. + /// + /// Use this to speed up acquisition of an address in a new + /// network if a link was down and it is now back up. + pub fn reset(&mut self) { + net_trace!("DHCP reset"); + if let ClientState::Renewing(_) = &self.state { + self.config_changed(); + } + self.state = ClientState::Discovering(DiscoverState { + retry_at: Instant::from_millis(0), + }); + } + + /// Query the socket for configuration changes. + /// + /// The socket has an internal "configuration changed" flag. If + /// set, this function returns the configuration and resets the flag. + pub fn poll(&mut self) -> Option> { + if !self.config_changed { + None + } else if let ClientState::Renewing(state) = &self.state { + self.config_changed = false; + Some(Event::Configured(Config { + server: state.config.server, + address: state.config.address, + router: state.config.router, + dns_servers: state.config.dns_servers.clone(), + packet: self + .receive_packet_buffer + .as_deref() + .map(DhcpPacket::new_unchecked), + })) + } else { + self.config_changed = false; + Some(Event::Deconfigured) + } + } + + /// This function _must_ be called when the configuration provided to the + /// interface, by this DHCP socket, changes. It will update the `config_changed` field + /// so that a subsequent call to `poll` will yield an event, and wake a possible waker. + pub(crate) fn config_changed(&mut self) { + self.config_changed = true; + #[cfg(feature = "async")] + self.waker.wake(); + } + + /// Register a waker. + /// + /// The waker is woken on state changes that might affect the return value + /// of `poll` method calls, which indicates a new state in the DHCP configuration + /// provided by this DHCP socket. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + #[cfg(feature = "async")] + pub fn register_waker(&mut self, waker: &Waker) { + self.waker.register(waker) + } +} + +#[cfg(test)] +mod test { + + use std::ops::{Deref, DerefMut}; + + use super::*; + use crate::wire::EthernetAddress; + + // =========================================================================================// + // Helper functions + + struct TestSocket { + socket: Socket<'static>, + cx: Context, + } + + impl Deref for TestSocket { + type Target = Socket<'static>; + fn deref(&self) -> &Self::Target { + &self.socket + } + } + + impl DerefMut for TestSocket { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.socket + } + } + + fn send( + s: &mut TestSocket, + timestamp: Instant, + (ip_repr, udp_repr, dhcp_repr): (Ipv4Repr, UdpRepr, DhcpRepr), + ) { + s.cx.set_now(timestamp); + + net_trace!("send: {:?}", ip_repr); + net_trace!(" {:?}", udp_repr); + net_trace!(" {:?}", dhcp_repr); + + let mut payload = vec![0; dhcp_repr.buffer_len()]; + dhcp_repr + .emit(&mut DhcpPacket::new_unchecked(&mut payload)) + .unwrap(); + + s.socket.process(&mut s.cx, &ip_repr, &udp_repr, &payload) + } + + fn recv(s: &mut TestSocket, timestamp: Instant, reprs: &[(Ipv4Repr, UdpRepr, DhcpRepr)]) { + s.cx.set_now(timestamp); + + let mut i = 0; + + while s.socket.poll_at(&mut s.cx) <= PollAt::Time(timestamp) { + let _ = s + .socket + .dispatch(&mut s.cx, |_, (mut ip_repr, udp_repr, dhcp_repr)| { + assert_eq!(ip_repr.next_header, IpProtocol::Udp); + assert_eq!( + ip_repr.payload_len, + udp_repr.header_len() + dhcp_repr.buffer_len() + ); + + // We validated the payload len, change it to 0 to make equality testing easier + ip_repr.payload_len = 0; + + net_trace!("recv: {:?}", ip_repr); + net_trace!(" {:?}", udp_repr); + net_trace!(" {:?}", dhcp_repr); + + let got_repr = (ip_repr, udp_repr, dhcp_repr); + match reprs.get(i) { + Some(want_repr) => assert_eq!(want_repr, &got_repr), + None => panic!("Too many reprs emitted"), + } + i += 1; + Ok::<_, ()>(()) + }); + } + + assert_eq!(i, reprs.len()); + } + + macro_rules! send { + ($socket:ident, $repr:expr) => + (send!($socket, time 0, $repr)); + ($socket:ident, time $time:expr, $repr:expr) => + (send(&mut $socket, Instant::from_millis($time), $repr)); + } + + macro_rules! recv { + ($socket:ident, $reprs:expr) => ({ + recv!($socket, time 0, $reprs); + }); + ($socket:ident, time $time:expr, $reprs:expr) => ({ + recv(&mut $socket, Instant::from_millis($time), &$reprs); + }); + } + + // =========================================================================================// + // Constants + + const TXID: u32 = 0x12345678; + + const MY_IP: Ipv4Address = Ipv4Address::new(192, 168, 1, 42); + const SERVER_IP: Ipv4Address = Ipv4Address::new(192, 168, 1, 1); + const DNS_IP_1: Ipv4Address = Ipv4Address::new(1, 1, 1, 1); + const DNS_IP_2: Ipv4Address = Ipv4Address::new(1, 1, 1, 2); + const DNS_IP_3: Ipv4Address = Ipv4Address::new(1, 1, 1, 3); + const DNS_IPS: &[Ipv4Address] = &[DNS_IP_1, DNS_IP_2, DNS_IP_3]; + + const MASK_24: Ipv4Address = Ipv4Address::new(255, 255, 255, 0); + + const MY_MAC: EthernetAddress = EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02]); + + const IP_BROADCAST: Ipv4Repr = Ipv4Repr { + src_addr: Ipv4Address::UNSPECIFIED, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }; + + const IP_BROADCAST_ADDRESSED: Ipv4Repr = Ipv4Repr { + src_addr: MY_IP, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }; + + const IP_SERVER_BROADCAST: Ipv4Repr = Ipv4Repr { + src_addr: SERVER_IP, + dst_addr: Ipv4Address::BROADCAST, + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }; + + const IP_RECV: Ipv4Repr = Ipv4Repr { + src_addr: SERVER_IP, + dst_addr: MY_IP, + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }; + + const IP_SEND: Ipv4Repr = Ipv4Repr { + src_addr: MY_IP, + dst_addr: SERVER_IP, + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }; + + const UDP_SEND: UdpRepr = UdpRepr { + src_port: DHCP_CLIENT_PORT, + dst_port: DHCP_SERVER_PORT, + }; + const UDP_RECV: UdpRepr = UdpRepr { + src_port: DHCP_SERVER_PORT, + dst_port: DHCP_CLIENT_PORT, + }; + + const DIFFERENT_CLIENT_PORT: u16 = 6800; + const DIFFERENT_SERVER_PORT: u16 = 6700; + + const UDP_SEND_DIFFERENT_PORT: UdpRepr = UdpRepr { + src_port: DIFFERENT_CLIENT_PORT, + dst_port: DIFFERENT_SERVER_PORT, + }; + const UDP_RECV_DIFFERENT_PORT: UdpRepr = UdpRepr { + src_port: DIFFERENT_SERVER_PORT, + dst_port: DIFFERENT_CLIENT_PORT, + }; + + const DHCP_DEFAULT: DhcpRepr = DhcpRepr { + message_type: DhcpMessageType::Unknown(99), + transaction_id: TXID, + secs: 0, + client_hardware_address: MY_MAC, + client_ip: Ipv4Address::UNSPECIFIED, + your_ip: Ipv4Address::UNSPECIFIED, + server_ip: Ipv4Address::UNSPECIFIED, + router: None, + subnet_mask: None, + relay_agent_ip: Ipv4Address::UNSPECIFIED, + broadcast: false, + requested_ip: None, + client_identifier: None, + server_identifier: None, + parameter_request_list: None, + dns_servers: None, + max_size: None, + renew_duration: None, + rebind_duration: None, + lease_duration: None, + additional_options: &[], + }; + + const DHCP_DISCOVER: DhcpRepr = DhcpRepr { + message_type: DhcpMessageType::Discover, + client_identifier: Some(MY_MAC), + parameter_request_list: Some(&[1, 3, 6]), + max_size: Some(1432), + ..DHCP_DEFAULT + }; + + fn dhcp_offer() -> DhcpRepr<'static> { + DhcpRepr { + message_type: DhcpMessageType::Offer, + server_ip: SERVER_IP, + server_identifier: Some(SERVER_IP), + + your_ip: MY_IP, + router: Some(SERVER_IP), + subnet_mask: Some(MASK_24), + dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()), + lease_duration: Some(1000), + + ..DHCP_DEFAULT + } + } + + const DHCP_REQUEST: DhcpRepr = DhcpRepr { + message_type: DhcpMessageType::Request, + client_identifier: Some(MY_MAC), + server_identifier: Some(SERVER_IP), + max_size: Some(1432), + + requested_ip: Some(MY_IP), + parameter_request_list: Some(&[1, 3, 6]), + ..DHCP_DEFAULT + }; + + fn dhcp_ack() -> DhcpRepr<'static> { + DhcpRepr { + message_type: DhcpMessageType::Ack, + server_ip: SERVER_IP, + server_identifier: Some(SERVER_IP), + + your_ip: MY_IP, + router: Some(SERVER_IP), + subnet_mask: Some(MASK_24), + dns_servers: Some(Vec::from_slice(DNS_IPS).unwrap()), + lease_duration: Some(1000), + + ..DHCP_DEFAULT + } + } + + const DHCP_NAK: DhcpRepr = DhcpRepr { + message_type: DhcpMessageType::Nak, + server_ip: SERVER_IP, + server_identifier: Some(SERVER_IP), + ..DHCP_DEFAULT + }; + + const DHCP_RENEW: DhcpRepr = DhcpRepr { + message_type: DhcpMessageType::Request, + client_identifier: Some(MY_MAC), + // NO server_identifier in renew requests, only in first one! + client_ip: MY_IP, + max_size: Some(1432), + + requested_ip: None, + parameter_request_list: Some(&[1, 3, 6]), + ..DHCP_DEFAULT + }; + + const DHCP_REBIND: DhcpRepr = DhcpRepr { + message_type: DhcpMessageType::Request, + client_identifier: Some(MY_MAC), + // NO server_identifier in renew requests, only in first one! + client_ip: MY_IP, + max_size: Some(1432), + + requested_ip: None, + parameter_request_list: Some(&[1, 3, 6]), + ..DHCP_DEFAULT + }; + + // =========================================================================================// + // Tests + + use crate::phy::Medium; + use crate::tests::setup; + use rstest::*; + + fn socket(medium: Medium) -> TestSocket { + let (iface, _, _) = setup(medium); + let mut s = Socket::new(); + assert_eq!(s.poll(), Some(Event::Deconfigured)); + TestSocket { + socket: s, + cx: iface.inner, + } + } + + fn socket_different_port(medium: Medium) -> TestSocket { + let (iface, _, _) = setup(medium); + let mut s = Socket::new(); + s.set_ports(DIFFERENT_SERVER_PORT, DIFFERENT_CLIENT_PORT); + + assert_eq!(s.poll(), Some(Event::Deconfigured)); + TestSocket { + socket: s, + cx: iface.inner, + } + } + + fn socket_bound(medium: Medium) -> TestSocket { + let mut s = socket(medium); + s.state = ClientState::Renewing(RenewState { + config: Config { + server: ServerInfo { + address: SERVER_IP, + identifier: SERVER_IP, + }, + address: Ipv4Cidr::new(MY_IP, 24), + dns_servers: Vec::from_slice(DNS_IPS).unwrap(), + router: Some(SERVER_IP), + packet: None, + }, + renew_at: Instant::from_secs(500), + rebind_at: Instant::from_secs(875), + rebinding: false, + expires_at: Instant::from_secs(1000), + }); + + s + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_bind(#[case] medium: Medium) { + let mut s = socket(medium); + + recv!(s, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + assert_eq!(s.poll(), None); + send!(s, (IP_RECV, UDP_RECV, dhcp_offer())); + assert_eq!(s.poll(), None); + recv!(s, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + assert_eq!(s.poll(), None); + send!(s, (IP_RECV, UDP_RECV, dhcp_ack())); + + assert_eq!( + s.poll(), + Some(Event::Configured(Config { + server: ServerInfo { + address: SERVER_IP, + identifier: SERVER_IP, + }, + address: Ipv4Cidr::new(MY_IP, 24), + dns_servers: Vec::from_slice(DNS_IPS).unwrap(), + router: Some(SERVER_IP), + packet: None, + })) + ); + + match &s.state { + ClientState::Renewing(r) => { + assert_eq!(r.renew_at, Instant::from_secs(500)); + assert_eq!(r.rebind_at, Instant::from_secs(875)); + assert_eq!(r.expires_at, Instant::from_secs(1000)); + } + _ => panic!("Invalid state"), + } + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_bind_different_ports(#[case] medium: Medium) { + let mut s = socket_different_port(medium); + + recv!(s, [(IP_BROADCAST, UDP_SEND_DIFFERENT_PORT, DHCP_DISCOVER)]); + assert_eq!(s.poll(), None); + send!(s, (IP_RECV, UDP_RECV_DIFFERENT_PORT, dhcp_offer())); + assert_eq!(s.poll(), None); + recv!(s, [(IP_BROADCAST, UDP_SEND_DIFFERENT_PORT, DHCP_REQUEST)]); + assert_eq!(s.poll(), None); + send!(s, (IP_RECV, UDP_RECV_DIFFERENT_PORT, dhcp_ack())); + + assert_eq!( + s.poll(), + Some(Event::Configured(Config { + server: ServerInfo { + address: SERVER_IP, + identifier: SERVER_IP, + }, + address: Ipv4Cidr::new(MY_IP, 24), + dns_servers: Vec::from_slice(DNS_IPS).unwrap(), + router: Some(SERVER_IP), + packet: None, + })) + ); + + match &s.state { + ClientState::Renewing(r) => { + assert_eq!(r.renew_at, Instant::from_secs(500)); + assert_eq!(r.rebind_at, Instant::from_secs(875)); + assert_eq!(r.expires_at, Instant::from_secs(1000)); + } + _ => panic!("Invalid state"), + } + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_discover_retransmit(#[case] medium: Medium) { + let mut s = socket(medium); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + recv!(s, time 1_000, []); + recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + recv!(s, time 11_000, []); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + + // check after retransmits it still works + send!(s, time 20_000, (IP_RECV, UDP_RECV, dhcp_offer())); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_request_retransmit(#[case] medium: Medium) { + let mut s = socket(medium); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + send!(s, time 0, (IP_RECV, UDP_RECV, dhcp_offer())); + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 1_000, []); + recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 6_000, []); + recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 15_000, []); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + + // check after retransmits it still works + send!(s, time 20_000, (IP_RECV, UDP_RECV, dhcp_ack())); + + match &s.state { + ClientState::Renewing(r) => { + assert_eq!(r.renew_at, Instant::from_secs(20 + 500)); + assert_eq!(r.expires_at, Instant::from_secs(20 + 1000)); + } + _ => panic!("Invalid state"), + } + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_request_timeout(#[case] medium: Medium) { + let mut s = socket(medium); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + send!(s, time 0, (IP_RECV, UDP_RECV, dhcp_offer())); + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 5_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 10_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 20_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + recv!(s, time 30_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + + // After 5 tries and 70 seconds, it gives up. + // 5 + 5 + 10 + 10 + 20 = 70 + recv!(s, time 70_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + + // check it still works + send!(s, time 60_000, (IP_RECV, UDP_RECV, dhcp_offer())); + recv!(s, time 60_000, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_request_nak(#[case] medium: Medium) { + let mut s = socket(medium); + + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + send!(s, time 0, (IP_RECV, UDP_RECV, dhcp_offer())); + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_REQUEST)]); + send!(s, time 0, (IP_SERVER_BROADCAST, UDP_RECV, DHCP_NAK)); + recv!(s, time 0, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_renew(#[case] medium: Medium) { + let mut s = socket_bound(medium); + + recv!(s, []); + assert_eq!(s.poll(), None); + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + assert_eq!(s.poll(), None); + + match &s.state { + ClientState::Renewing(r) => { + // the expiration still hasn't been bumped, because + // we haven't received the ACK yet + assert_eq!(r.expires_at, Instant::from_secs(1000)); + } + _ => panic!("Invalid state"), + } + + send!(s, time 500_000, (IP_RECV, UDP_RECV, dhcp_ack())); + assert_eq!(s.poll(), None); + + match &s.state { + ClientState::Renewing(r) => { + // NOW the expiration gets bumped + assert_eq!(r.renew_at, Instant::from_secs(500 + 500)); + assert_eq!(r.expires_at, Instant::from_secs(500 + 1000)); + } + _ => panic!("Invalid state"), + } + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_renew_rebind_retransmit(#[case] medium: Medium) { + let mut s = socket_bound(medium); + + recv!(s, []); + // First renew attempt at T1 + recv!(s, time 499_000, []); + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt at half way to T2 + recv!(s, time 687_000, []); + recv!(s, time 687_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt at half way again to T2 + recv!(s, time 781_000, []); + recv!(s, time 781_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt 60s later (minimum interval) + recv!(s, time 841_000, []); + recv!(s, time 841_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // No more renews due to minimum interval + recv!(s, time 874_000, []); + // First rebind attempt + recv!(s, time 875_000, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + // Next rebind attempt half way to expiry + recv!(s, time 937_000, []); + recv!(s, time 937_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + // Next rebind attempt 60s later (minimum interval) + recv!(s, time 997_000, []); + recv!(s, time 997_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + + // check it still works + send!(s, time 999_000, (IP_RECV, UDP_RECV, dhcp_ack())); + match &s.state { + ClientState::Renewing(r) => { + // NOW the expiration gets bumped + assert_eq!(r.renew_at, Instant::from_secs(999 + 500)); + assert_eq!(r.expires_at, Instant::from_secs(999 + 1000)); + } + _ => panic!("Invalid state"), + } + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_renew_rebind_timeout(#[case] medium: Medium) { + let mut s = socket_bound(medium); + + recv!(s, []); + // First renew attempt at T1 + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt at half way to T2 + recv!(s, time 687_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt at half way again to T2 + recv!(s, time 781_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt 60s later (minimum interval) + recv!(s, time 841_250, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // TODO uncomment below part of test + // // First rebind attempt + // recv!(s, time 875_000, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + // // Next rebind attempt half way to expiry + // recv!(s, time 937_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + // // Next rebind attempt 60s later (minimum interval) + // recv!(s, time 997_500, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + // No more rebinds due to minimum interval + recv!(s, time 1_000_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + match &s.state { + ClientState::Discovering(_) => {} + _ => panic!("Invalid state"), + } + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_min_max_renew_timeout(#[case] medium: Medium) { + let mut s = socket_bound(medium); + // Set a minimum of 45s and a maximum of 120s + let config = RetryConfig { + max_renew_timeout: Duration::from_secs(120), + min_renew_timeout: Duration::from_secs(45), + ..s.get_retry_config() + }; + s.set_retry_config(config); + recv!(s, []); + // First renew attempt at T1 + recv!(s, time 499_999, []); + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt 120s after T1 because we hit the max + recv!(s, time 619_999, []); + recv!(s, time 620_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt 120s after previous because we hit the max again + recv!(s, time 739_999, []); + recv!(s, time 740_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt half way to T2 + recv!(s, time 807_499, []); + recv!(s, time 807_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next renew attempt 45s after previous because we hit the min + recv!(s, time 852_499, []); + recv!(s, time 852_500, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + // Next is a rebind, because the min puts us after T2 + recv!(s, time 874_999, []); + recv!(s, time 875_000, [(IP_BROADCAST_ADDRESSED, UDP_SEND, DHCP_REBIND)]); + } + + #[rstest] + #[case::ip(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_renew_nak(#[case] medium: Medium) { + let mut s = socket_bound(medium); + + recv!(s, time 500_000, [(IP_SEND, UDP_SEND, DHCP_RENEW)]); + send!(s, time 500_000, (IP_SERVER_BROADCAST, UDP_RECV, DHCP_NAK)); + recv!(s, time 500_000, [(IP_BROADCAST, UDP_SEND, DHCP_DISCOVER)]); + } +} diff --git a/vendor/smoltcp/src/socket/dns.rs b/vendor/smoltcp/src/socket/dns.rs new file mode 100644 index 00000000..69b33e33 --- /dev/null +++ b/vendor/smoltcp/src/socket/dns.rs @@ -0,0 +1,713 @@ +use core::cmp::min; +#[cfg(feature = "async")] +use core::task::Waker; + +use heapless::Vec; +use managed::ManagedSlice; + +use crate::config::{DNS_MAX_NAME_SIZE, DNS_MAX_RESULT_COUNT, DNS_MAX_SERVER_COUNT}; +use crate::socket::{Context, PollAt}; +use crate::time::{Duration, Instant}; +use crate::wire::dns::{Flags, Opcode, Packet, Question, Rcode, Record, RecordData, Repr, Type}; +use crate::wire::{self, IpAddress, IpProtocol, IpRepr, UdpRepr}; + +#[cfg(feature = "async")] +use super::WakerRegistration; + +const DNS_PORT: u16 = 53; +const MDNS_DNS_PORT: u16 = 5353; +const RETRANSMIT_DELAY: Duration = Duration::from_millis(1_000); +const MAX_RETRANSMIT_DELAY: Duration = Duration::from_millis(10_000); +const RETRANSMIT_TIMEOUT: Duration = Duration::from_millis(10_000); // Should generally be 2-10 secs + +#[cfg(feature = "proto-ipv6")] +#[allow(unused)] +const MDNS_IPV6_ADDR: IpAddress = IpAddress::Ipv6(crate::wire::Ipv6Address::new( + 0xff02, 0, 0, 0, 0, 0, 0, 0xfb, +)); + +#[cfg(feature = "proto-ipv4")] +#[allow(unused)] +const MDNS_IPV4_ADDR: IpAddress = IpAddress::Ipv4(crate::wire::Ipv4Address::new(224, 0, 0, 251)); + +/// Error returned by [`Socket::start_query`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum StartQueryError { + NoFreeSlot, + InvalidName, + NameTooLong, +} + +impl core::fmt::Display for StartQueryError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + StartQueryError::NoFreeSlot => write!(f, "No free slot"), + StartQueryError::InvalidName => write!(f, "Invalid name"), + StartQueryError::NameTooLong => write!(f, "Name too long"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for StartQueryError {} + +/// Error returned by [`Socket::get_query_result`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum GetQueryResultError { + /// Query is not done yet. + Pending, + /// Query failed. + Failed, +} + +impl core::fmt::Display for GetQueryResultError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + GetQueryResultError::Pending => write!(f, "Query is not done yet"), + GetQueryResultError::Failed => write!(f, "Query failed"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for GetQueryResultError {} + +/// State for an in-progress DNS query. +/// +/// The only reason this struct is public is to allow the socket state +/// to be allocated externally. +#[derive(Debug)] +pub struct DnsQuery { + state: State, + + #[cfg(feature = "async")] + waker: WakerRegistration, +} + +impl DnsQuery { + fn set_state(&mut self, state: State) { + self.state = state; + #[cfg(feature = "async")] + self.waker.wake(); + } +} + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +enum State { + Pending(PendingQuery), + Completed(CompletedQuery), + Failure, +} + +#[derive(Debug)] +struct PendingQuery { + name: Vec, + type_: Type, + + port: u16, // UDP port (src for request, dst for response) + txid: u16, // transaction ID + + timeout_at: Option, + retransmit_at: Instant, + delay: Duration, + + server_idx: usize, + mdns: MulticastDns, +} + +#[derive(Debug)] +pub enum MulticastDns { + Disabled, + #[cfg(feature = "socket-mdns")] + Enabled, +} + +#[derive(Debug)] +struct CompletedQuery { + addresses: Vec, +} + +/// A handle to an in-progress DNS query. +#[derive(Clone, Copy)] +pub struct QueryHandle(usize); + +/// A Domain Name System socket. +/// +/// A UDP socket is bound to a specific endpoint, and owns transmit and receive +/// packet buffers. +#[derive(Debug)] +pub struct Socket<'a> { + servers: Vec, + queries: ManagedSlice<'a, Option>, + + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + hop_limit: Option, +} + +impl<'a> Socket<'a> { + /// Create a DNS socket. + /// + /// Truncates the server list if `servers.len() > MAX_SERVER_COUNT` + pub fn new(servers: &[IpAddress], queries: Q) -> Socket<'a> + where + Q: Into>>, + { + let truncated_servers = &servers[..min(servers.len(), DNS_MAX_SERVER_COUNT)]; + + Socket { + servers: Vec::from_slice(truncated_servers).unwrap(), + queries: queries.into(), + hop_limit: None, + } + } + + /// Update the list of DNS servers, will replace all existing servers + /// + /// Truncates the server list if `servers.len() > MAX_SERVER_COUNT` + pub fn update_servers(&mut self, servers: &[IpAddress]) { + if servers.len() > DNS_MAX_SERVER_COUNT { + net_trace!("Max DNS Servers exceeded. Increase MAX_SERVER_COUNT"); + self.servers = Vec::from_slice(&servers[..DNS_MAX_SERVER_COUNT]).unwrap(); + } else { + self.servers = Vec::from_slice(servers).unwrap(); + } + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_hop_limit](#method.set_hop_limit) method + pub fn hop_limit(&self) -> Option { + self.hop_limit + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set hop limit value uses the default [IANA recommended] + /// value (64). + /// + /// # Panics + /// + /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_hop_limit(&mut self, hop_limit: Option) { + // A host MUST NOT send a datagram with a hop limit value of 0 + if let Some(0) = hop_limit { + panic!("the time-to-live value of a packet must not be zero") + } + + self.hop_limit = hop_limit + } + + fn find_free_query(&mut self) -> Option { + for (i, q) in self.queries.iter().enumerate() { + if q.is_none() { + return Some(QueryHandle(i)); + } + } + + match &mut self.queries { + ManagedSlice::Borrowed(_) => None, + #[cfg(feature = "alloc")] + ManagedSlice::Owned(queries) => { + queries.push(None); + let index = queries.len() - 1; + Some(QueryHandle(index)) + } + } + } + + /// Start a query. + /// + /// `name` is specified in human-friendly format, such as `"rust-lang.org"`. + /// It accepts names both with and without trailing dot, and they're treated + /// the same (there's no support for DNS search path). + pub fn start_query( + &mut self, + cx: &mut Context, + name: &str, + query_type: Type, + ) -> Result { + let mut name = name.as_bytes(); + + if name.is_empty() { + net_trace!("invalid name: zero length"); + return Err(StartQueryError::InvalidName); + } + + // Remove trailing dot, if any + if name[name.len() - 1] == b'.' { + name = &name[..name.len() - 1]; + } + + let mut raw_name: Vec = Vec::new(); + + let mut mdns = MulticastDns::Disabled; + #[cfg(feature = "socket-mdns")] + if name.split(|&c| c == b'.').next_back().unwrap() == b"local" { + net_trace!("Starting a mDNS query"); + mdns = MulticastDns::Enabled; + } + + for s in name.split(|&c| c == b'.') { + if s.len() > 63 { + net_trace!("invalid name: too long label"); + return Err(StartQueryError::InvalidName); + } + if s.is_empty() { + net_trace!("invalid name: zero length label"); + return Err(StartQueryError::InvalidName); + } + + // Push label + raw_name + .push(s.len() as u8) + .map_err(|_| StartQueryError::NameTooLong)?; + raw_name + .extend_from_slice(s) + .map_err(|_| StartQueryError::NameTooLong)?; + } + + // Push terminator. + raw_name + .push(0x00) + .map_err(|_| StartQueryError::NameTooLong)?; + + self.start_query_raw(cx, &raw_name, query_type, mdns) + } + + /// Start a query with a raw (wire-format) DNS name. + /// `b"\x09rust-lang\x03org\x00"` + /// + /// You probably want to use [`start_query`] instead. + pub fn start_query_raw( + &mut self, + cx: &mut Context, + raw_name: &[u8], + query_type: Type, + mdns: MulticastDns, + ) -> Result { + let handle = self.find_free_query().ok_or(StartQueryError::NoFreeSlot)?; + + self.queries[handle.0] = Some(DnsQuery { + state: State::Pending(PendingQuery { + name: Vec::from_slice(raw_name).map_err(|_| StartQueryError::NameTooLong)?, + type_: query_type, + txid: cx.rand().rand_u16(), + port: cx.rand().rand_source_port(), + delay: RETRANSMIT_DELAY, + timeout_at: None, + retransmit_at: Instant::ZERO, + server_idx: 0, + mdns, + }), + #[cfg(feature = "async")] + waker: WakerRegistration::new(), + }); + Ok(handle) + } + + /// Get the result of a query. + /// + /// If the query is completed, the query slot is automatically freed. + /// + /// # Panics + /// Panics if the QueryHandle corresponds to a free slot. + pub fn get_query_result( + &mut self, + handle: QueryHandle, + ) -> Result, GetQueryResultError> { + let slot = &mut self.queries[handle.0]; + let q = slot.as_mut().unwrap(); + match &mut q.state { + // Query is not done yet. + State::Pending(_) => Err(GetQueryResultError::Pending), + // Query is done + State::Completed(q) => { + let res = q.addresses.clone(); + *slot = None; // Free up the slot for recycling. + Ok(res) + } + State::Failure => { + *slot = None; // Free up the slot for recycling. + Err(GetQueryResultError::Failed) + } + } + } + + /// Cancels a query, freeing the slot. + /// + /// # Panics + /// + /// Panics if the QueryHandle corresponds to an already free slot. + pub fn cancel_query(&mut self, handle: QueryHandle) { + let slot = &mut self.queries[handle.0]; + if slot.is_none() { + panic!("Canceling query in a free slot.") + } + *slot = None; // Free up the slot for recycling. + } + + /// Assign a waker to a query slot + /// + /// The waker will be woken when the query completes, either successfully or failed. + /// + /// # Panics + /// + /// Panics if the QueryHandle corresponds to an already free slot. + #[cfg(feature = "async")] + pub fn register_query_waker(&mut self, handle: QueryHandle, waker: &Waker) { + self.queries[handle.0] + .as_mut() + .unwrap() + .waker + .register(waker); + } + + pub(crate) fn accepts(&self, ip_repr: &IpRepr, udp_repr: &UdpRepr) -> bool { + (udp_repr.src_port == DNS_PORT + && self + .servers + .iter() + .any(|server| *server == ip_repr.src_addr())) + || (udp_repr.src_port == MDNS_DNS_PORT) + } + + pub(crate) fn process( + &mut self, + _cx: &mut Context, + ip_repr: &IpRepr, + udp_repr: &UdpRepr, + payload: &[u8], + ) { + debug_assert!(self.accepts(ip_repr, udp_repr)); + + let size = payload.len(); + + net_trace!( + "receiving {} octets from {:?}:{}", + size, + ip_repr.src_addr(), + udp_repr.dst_port + ); + + let p = match Packet::new_checked(payload) { + Ok(x) => x, + Err(_) => { + net_trace!("dns packet malformed"); + return; + } + }; + if p.opcode() != Opcode::Query { + net_trace!("unwanted opcode {:?}", p.opcode()); + return; + } + + if !p.flags().contains(Flags::RESPONSE) { + net_trace!("packet doesn't have response bit set"); + return; + } + + if p.question_count() != 1 { + net_trace!("bad question count {:?}", p.question_count()); + return; + } + + // Find pending query + for q in self.queries.iter_mut().flatten() { + if let State::Pending(pq) = &mut q.state { + if udp_repr.dst_port != pq.port || p.transaction_id() != pq.txid { + continue; + } + + if p.rcode() == Rcode::NXDomain { + net_trace!("rcode NXDomain"); + q.set_state(State::Failure); + continue; + } + + let payload = p.payload(); + let (mut payload, question) = match Question::parse(payload) { + Ok(x) => x, + Err(_) => { + net_trace!("question malformed"); + return; + } + }; + + if question.type_ != pq.type_ { + net_trace!("question type mismatch"); + return; + } + + match eq_names(p.parse_name(question.name), p.parse_name(&pq.name)) { + Ok(true) => {} + Ok(false) => { + net_trace!("question name mismatch"); + return; + } + Err(_) => { + net_trace!("dns question name malformed"); + return; + } + } + + let mut addresses = Vec::new(); + + for _ in 0..p.answer_record_count() { + let (payload2, r) = match Record::parse(payload) { + Ok(x) => x, + Err(_) => { + net_trace!("dns answer record malformed"); + return; + } + }; + payload = payload2; + + match eq_names(p.parse_name(r.name), p.parse_name(&pq.name)) { + Ok(true) => {} + Ok(false) => { + net_trace!("answer name mismatch: {:?}", r); + continue; + } + Err(_) => { + net_trace!("dns answer record name malformed"); + return; + } + } + + match r.data { + #[cfg(feature = "proto-ipv4")] + RecordData::A(addr) => { + net_trace!("A: {:?}", addr); + if addresses.push(addr.into()).is_err() { + net_trace!("too many addresses in response, ignoring {:?}", addr); + } + } + #[cfg(feature = "proto-ipv6")] + RecordData::Aaaa(addr) => { + net_trace!("AAAA: {:?}", addr); + if addresses.push(addr.into()).is_err() { + net_trace!("too many addresses in response, ignoring {:?}", addr); + } + } + RecordData::Cname(name) => { + net_trace!("CNAME: {:?}", name); + + // When faced with a CNAME, recursive resolvers are supposed to + // resolve the CNAME and append the results for it. + // + // We update the query with the new name, so that we pick up the A/AAAA + // records for the CNAME when we parse them later. + // I believe it's mandatory the CNAME results MUST come *after* in the + // packet, so it's enough to do one linear pass over it. + if copy_name(&mut pq.name, p.parse_name(name)).is_err() { + net_trace!("dns answer cname malformed"); + return; + } + } + RecordData::Other(type_, data) => { + net_trace!("unknown: {:?} {:?}", type_, data) + } + } + } + + q.set_state(if addresses.is_empty() { + State::Failure + } else { + State::Completed(CompletedQuery { addresses }) + }); + + // If we get here, packet matched the current query, stop processing. + return; + } + } + + // If we get here, packet matched with no query. + net_trace!("no query matched"); + } + + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, (IpRepr, UdpRepr, &[u8])) -> Result<(), E>, + { + let hop_limit = self.hop_limit.unwrap_or(64); + + for q in self.queries.iter_mut().flatten() { + if let State::Pending(pq) = &mut q.state { + // As per RFC 6762 any DNS query ending in .local. MUST be sent as mdns + // so we internally overwrite the servers for any of those queries + // in this function. + let servers = match pq.mdns { + #[cfg(feature = "socket-mdns")] + MulticastDns::Enabled => &[ + #[cfg(feature = "proto-ipv6")] + MDNS_IPV6_ADDR, + #[cfg(feature = "proto-ipv4")] + MDNS_IPV4_ADDR, + ], + MulticastDns::Disabled => self.servers.as_slice(), + }; + + let timeout = if let Some(timeout) = pq.timeout_at { + timeout + } else { + let v = cx.now() + RETRANSMIT_TIMEOUT; + pq.timeout_at = Some(v); + v + }; + + // Check timeout + if timeout < cx.now() { + // DNS timeout + pq.timeout_at = Some(cx.now() + RETRANSMIT_TIMEOUT); + pq.retransmit_at = Instant::ZERO; + pq.delay = RETRANSMIT_DELAY; + + // Try next server. We check below whether we've tried all servers. + pq.server_idx += 1; + } + // Check if we've run out of servers to try. + if pq.server_idx >= servers.len() { + net_trace!("already tried all servers."); + q.set_state(State::Failure); + continue; + } + + // Check so the IP address is valid + if servers[pq.server_idx].is_unspecified() { + net_trace!("invalid unspecified DNS server addr."); + q.set_state(State::Failure); + continue; + } + + if pq.retransmit_at > cx.now() { + // query is waiting for retransmit + continue; + } + + let repr = Repr { + transaction_id: pq.txid, + flags: Flags::RECURSION_DESIRED, + opcode: Opcode::Query, + question: Question { + name: &pq.name, + type_: pq.type_, + }, + }; + + let mut payload = [0u8; 512]; + let payload = &mut payload[..repr.buffer_len()]; + repr.emit(&mut Packet::new_unchecked(payload)); + + let dst_port = match pq.mdns { + #[cfg(feature = "socket-mdns")] + MulticastDns::Enabled => MDNS_DNS_PORT, + MulticastDns::Disabled => DNS_PORT, + }; + + let udp_repr = UdpRepr { + src_port: pq.port, + dst_port, + }; + + let dst_addr = servers[pq.server_idx]; + let src_addr = match cx.get_source_address(&dst_addr) { + Some(src_addr) => src_addr, + None => { + net_trace!("no source address for destination {}", dst_addr); + q.set_state(State::Failure); + continue; + } + }; + + let ip_repr = IpRepr::new( + src_addr, + dst_addr, + IpProtocol::Udp, + udp_repr.header_len() + payload.len(), + hop_limit, + ); + + net_trace!( + "sending {} octets to {} from port {}", + payload.len(), + ip_repr.dst_addr(), + udp_repr.src_port + ); + + emit(cx, (ip_repr, udp_repr, payload))?; + + pq.retransmit_at = cx.now() + pq.delay; + pq.delay = MAX_RETRANSMIT_DELAY.min(pq.delay * 2); + + return Ok(()); + } + } + + // Nothing to dispatch + Ok(()) + } + + pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt { + self.queries + .iter() + .flatten() + .filter_map(|q| match &q.state { + State::Pending(pq) => Some(PollAt::Time(pq.retransmit_at)), + State::Completed(_) => None, + State::Failure => None, + }) + .min() + .unwrap_or(PollAt::Ingress) + } +} + +fn eq_names<'a>( + mut a: impl Iterator>, + mut b: impl Iterator>, +) -> wire::Result { + loop { + match (a.next(), b.next()) { + // Handle errors + (Some(Err(e)), _) => return Err(e), + (_, Some(Err(e))) => return Err(e), + + // Both finished -> equal + (None, None) => return Ok(true), + + // One finished before the other -> not equal + (None, _) => return Ok(false), + (_, None) => return Ok(false), + + // Got two labels, check if they're equal + (Some(Ok(la)), Some(Ok(lb))) => { + if la != lb { + return Ok(false); + } + } + } + } +} + +fn copy_name<'a, const N: usize>( + dest: &mut Vec, + name: impl Iterator>, +) -> Result<(), wire::Error> { + dest.truncate(0); + + for label in name { + let label = label?; + dest.push(label.len() as u8).map_err(|_| wire::Error)?; + dest.extend_from_slice(label).map_err(|_| wire::Error)?; + } + + // Write terminator 0x00 + dest.push(0).map_err(|_| wire::Error)?; + + Ok(()) +} diff --git a/vendor/smoltcp/src/socket/icmp.rs b/vendor/smoltcp/src/socket/icmp.rs new file mode 100644 index 00000000..1f87801b --- /dev/null +++ b/vendor/smoltcp/src/socket/icmp.rs @@ -0,0 +1,1276 @@ +use core::cmp; +#[cfg(feature = "async")] +use core::task::Waker; + +use crate::phy::ChecksumCapabilities; +#[cfg(feature = "async")] +use crate::socket::WakerRegistration; +use crate::socket::{Context, PollAt}; + +use crate::storage::Empty; +use crate::wire::IcmpRepr; +#[cfg(feature = "proto-ipv4")] +use crate::wire::{Icmpv4Packet, Icmpv4Repr, Ipv4Repr}; +#[cfg(feature = "proto-ipv6")] +use crate::wire::{Icmpv6Packet, Icmpv6Repr, Ipv6Repr}; +use crate::wire::{IpAddress, IpListenEndpoint, IpProtocol, IpRepr}; +use crate::wire::{TcpPacket, TcpRepr}; +use crate::wire::{UdpPacket, UdpRepr}; + +/// Error returned by [`Socket::bind`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + InvalidState, + Unaddressable, +} + +impl core::fmt::Display for BindError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + BindError::InvalidState => write!(f, "invalid state"), + BindError::Unaddressable => write!(f, "unaddressable"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for BindError {} + +/// Error returned by [`Socket::send`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + Unaddressable, + BufferFull, +} + +impl core::fmt::Display for SendError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + SendError::Unaddressable => write!(f, "unaddressable"), + SendError::BufferFull => write!(f, "buffer full"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SendError {} + +/// Error returned by [`Socket::recv`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + Exhausted, + Truncated, +} + +impl core::fmt::Display for RecvError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + RecvError::Exhausted => write!(f, "exhausted"), + RecvError::Truncated => write!(f, "truncated"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RecvError {} + +/// Type of endpoint to bind the ICMP socket to. See [IcmpSocket::bind] for +/// more details. +/// +/// [IcmpSocket::bind]: struct.IcmpSocket.html#method.bind +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Endpoint { + #[default] + Unspecified, + Ident(u16), + Tcp(IpListenEndpoint), + Udp(IpListenEndpoint), +} + +impl Endpoint { + pub fn is_specified(&self) -> bool { + match *self { + Endpoint::Unspecified => false, + Endpoint::Ident(_) => true, + Endpoint::Tcp(endpoint) => endpoint.port != 0, + Endpoint::Udp(endpoint) => endpoint.port != 0, + } + } +} + +/// An ICMP packet metadata. +pub type PacketMetadata = crate::storage::PacketMetadata; + +/// An ICMP packet ring buffer. +pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, IpAddress>; + +/// A ICMP socket +/// +/// An ICMP socket is bound to a specific [IcmpEndpoint] which may +/// be a specific UDP port to listen for ICMP error messages related +/// to the port or a specific ICMP identifier value. See [bind] for +/// more details. +/// +/// [IcmpEndpoint]: enum.IcmpEndpoint.html +/// [bind]: #method.bind +#[derive(Debug)] +pub struct Socket<'a> { + rx_buffer: PacketBuffer<'a>, + tx_buffer: PacketBuffer<'a>, + /// The endpoint this socket is communicating with + endpoint: Endpoint, + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + hop_limit: Option, + #[cfg(feature = "async")] + rx_waker: WakerRegistration, + #[cfg(feature = "async")] + tx_waker: WakerRegistration, +} + +impl<'a> Socket<'a> { + /// Create an ICMP socket with the given buffers. + pub fn new(rx_buffer: PacketBuffer<'a>, tx_buffer: PacketBuffer<'a>) -> Socket<'a> { + Socket { + rx_buffer, + tx_buffer, + endpoint: Default::default(), + hop_limit: None, + #[cfg(feature = "async")] + rx_waker: WakerRegistration::new(), + #[cfg(feature = "async")] + tx_waker: WakerRegistration::new(), + } + } + + /// Register a waker for receive operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `recv` method calls, such as receiving data, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_recv_waker(&mut self, waker: &Waker) { + self.rx_waker.register(waker) + } + + /// Register a waker for send operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `send` method calls, such as space becoming available in the transmit + /// buffer, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_send_waker(&mut self, waker: &Waker) { + self.tx_waker.register(waker) + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_hop_limit](#method.set_hop_limit) method + pub fn hop_limit(&self) -> Option { + self.hop_limit + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set hop limit value uses the default [IANA recommended] + /// value (64). + /// + /// # Panics + /// + /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_hop_limit(&mut self, hop_limit: Option) { + // A host MUST NOT send a datagram with a hop limit value of 0 + if let Some(0) = hop_limit { + panic!("the time-to-live value of a packet must not be zero") + } + + self.hop_limit = hop_limit + } + + /// Bind the socket to the given endpoint. + /// + /// This function returns `Err(Error::Illegal)` if the socket was open + /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)` + /// if `endpoint` is unspecified (see [is_specified]). + /// + /// # Examples + /// + /// ## Bind to ICMP Error messages associated with a specific UDP port: + /// + /// To [recv] ICMP error messages that are associated with a specific local + /// UDP port, the socket may be bound to a given port using [IcmpEndpoint::Udp]. + /// This may be useful for applications using UDP attempting to detect and/or + /// diagnose connection problems. + /// + /// ``` + /// use smoltcp::wire::IpListenEndpoint; + /// use smoltcp::socket::icmp; + /// # let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]); + /// # let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]); + /// + /// let mut icmp_socket = // ... + /// # icmp::Socket::new(rx_buffer, tx_buffer); + /// + /// // Bind to ICMP error responses for UDP packets sent from port 53. + /// let endpoint = IpListenEndpoint::from(53); + /// icmp_socket.bind(icmp::Endpoint::Udp(endpoint)).unwrap(); + /// ``` + /// + /// ## Bind to a specific ICMP identifier: + /// + /// To [send] and [recv] ICMP packets that are not associated with a specific UDP + /// port, the socket may be bound to a specific ICMP identifier using + /// [IcmpEndpoint::Ident]. This is useful for sending and receiving Echo Request/Reply + /// messages. + /// + /// ``` + /// use smoltcp::wire::IpListenEndpoint; + /// use smoltcp::socket::icmp; + /// # let rx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]); + /// # let tx_buffer = icmp::PacketBuffer::new(vec![icmp::PacketMetadata::EMPTY], vec![0; 20]); + /// + /// let mut icmp_socket = // ... + /// # icmp::Socket::new(rx_buffer, tx_buffer); + /// + /// // Bind to ICMP messages with the ICMP identifier 0x1234 + /// icmp_socket.bind(icmp::Endpoint::Ident(0x1234)).unwrap(); + /// ``` + /// + /// [is_specified]: enum.IcmpEndpoint.html#method.is_specified + /// [IcmpEndpoint::Ident]: enum.IcmpEndpoint.html#variant.Ident + /// [IcmpEndpoint::Udp]: enum.IcmpEndpoint.html#variant.Udp + /// [send]: #method.send + /// [recv]: #method.recv + pub fn bind>(&mut self, endpoint: T) -> Result<(), BindError> { + let endpoint = endpoint.into(); + if !endpoint.is_specified() { + return Err(BindError::Unaddressable); + } + + if self.is_open() { + return Err(BindError::InvalidState); + } + + self.endpoint = endpoint; + + #[cfg(feature = "async")] + { + self.rx_waker.wake(); + self.tx_waker.wake(); + } + + Ok(()) + } + + /// Check whether the transmit buffer is full. + #[inline] + pub fn can_send(&self) -> bool { + !self.tx_buffer.is_full() + } + + /// Check whether the receive buffer is not empty. + #[inline] + pub fn can_recv(&self) -> bool { + !self.rx_buffer.is_empty() + } + + /// Return the maximum number packets the socket can receive. + #[inline] + pub fn packet_recv_capacity(&self) -> usize { + self.rx_buffer.packet_capacity() + } + + /// Return the maximum number packets the socket can transmit. + #[inline] + pub fn packet_send_capacity(&self) -> usize { + self.tx_buffer.packet_capacity() + } + + /// Return the maximum number of bytes inside the recv buffer. + #[inline] + pub fn payload_recv_capacity(&self) -> usize { + self.rx_buffer.payload_capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + #[inline] + pub fn payload_send_capacity(&self) -> usize { + self.tx_buffer.payload_capacity() + } + + /// Check whether the socket is open. + #[inline] + pub fn is_open(&self) -> bool { + self.endpoint != Endpoint::Unspecified + } + + /// Enqueue a packet to be sent to a given remote address, and return a pointer + /// to its payload. + /// + /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full, + /// `Err(Error::Truncated)` if the requested size is larger than the packet buffer + /// size, and `Err(Error::Unaddressable)` if the remote address is unspecified. + pub fn send(&mut self, size: usize, endpoint: IpAddress) -> Result<&mut [u8], SendError> { + if endpoint.is_unspecified() { + return Err(SendError::Unaddressable); + } + + let packet_buf = self + .tx_buffer + .enqueue(size, endpoint) + .map_err(|_| SendError::BufferFull)?; + + net_trace!("icmp:{}: buffer to send {} octets", endpoint, size); + Ok(packet_buf) + } + + /// Enqueue a packet to be send to a given remote address and pass the buffer + /// to the provided closure. The closure then returns the size of the data written + /// into the buffer. + /// + /// Also see [send](#method.send). + pub fn send_with( + &mut self, + max_size: usize, + endpoint: IpAddress, + f: F, + ) -> Result + where + F: FnOnce(&mut [u8]) -> usize, + { + if endpoint.is_unspecified() { + return Err(SendError::Unaddressable); + } + + let size = self + .tx_buffer + .enqueue_with_infallible(max_size, endpoint, f) + .map_err(|_| SendError::BufferFull)?; + + net_trace!("icmp:{}: buffer to send {} octets", endpoint, size); + Ok(size) + } + + /// Enqueue a packet to be sent to a given remote address, and fill it from a slice. + /// + /// See also [send](#method.send). + pub fn send_slice(&mut self, data: &[u8], endpoint: IpAddress) -> Result<(), SendError> { + let packet_buf = self.send(data.len(), endpoint)?; + packet_buf.copy_from_slice(data); + Ok(()) + } + + /// Dequeue a packet received from a remote endpoint, and return the `IpAddress` as well + /// as a pointer to the payload. + /// + /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty. + pub fn recv(&mut self) -> Result<(&[u8], IpAddress), RecvError> { + let (endpoint, packet_buf) = self.rx_buffer.dequeue().map_err(|_| RecvError::Exhausted)?; + + net_trace!( + "icmp:{}: receive {} buffered octets", + endpoint, + packet_buf.len() + ); + Ok((packet_buf, endpoint)) + } + + /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice, + /// and return the amount of octets copied as well as the `IpAddress` + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// the packet is dropped and a `RecvError::Truncated` error is returned. + /// + /// See also [recv](#method.recv). + pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<(usize, IpAddress), RecvError> { + let (buffer, endpoint) = self.recv()?; + + if data.len() < buffer.len() { + return Err(RecvError::Truncated); + } + + let length = cmp::min(data.len(), buffer.len()); + data[..length].copy_from_slice(&buffer[..length]); + Ok((length, endpoint)) + } + + /// Return the amount of octets queued in the transmit buffer. + pub fn send_queue(&self) -> usize { + self.tx_buffer.payload_bytes_count() + } + + /// Return the amount of octets queued in the receive buffer. + pub fn recv_queue(&self) -> usize { + self.rx_buffer.payload_bytes_count() + } + + /// Fitler determining whether the socket accepts a given ICMPv4 packet. + /// Accepted packets are enqueued into the socket's receive buffer. + #[cfg(feature = "proto-ipv4")] + #[inline] + pub(crate) fn accepts_v4( + &self, + cx: &mut Context, + ip_repr: &Ipv4Repr, + icmp_repr: &Icmpv4Repr, + ) -> bool { + match (&self.endpoint, icmp_repr) { + // If we are bound to ICMP errors associated to a UDP port, only + // accept Destination Unreachable or Time Exceeded messages with + // the data containing a UDP packet send from the local port we + // are bound to. + ( + &Endpoint::Udp(endpoint), + &Icmpv4Repr::DstUnreachable { data, header, .. } + | &Icmpv4Repr::TimeExceeded { data, header, .. }, + ) if endpoint.addr.is_none() || endpoint.addr == Some(ip_repr.dst_addr.into()) => { + let packet = UdpPacket::new_unchecked(data); + match UdpRepr::parse( + &packet, + &header.src_addr.into(), + &header.dst_addr.into(), + &cx.checksum_caps(), + ) { + Ok(repr) => endpoint.port == repr.src_port, + Err(_) => false, + } + } + // If we are bound to ICMP errors associated to a TCP port, only + // accept Destination Unreachable or Time Exceeded messages with + // the data containing a UDP packet send from the local port we + // are bound to. + ( + &Endpoint::Tcp(endpoint), + &Icmpv4Repr::DstUnreachable { data, header, .. } + | &Icmpv4Repr::TimeExceeded { data, header, .. }, + ) if endpoint.addr.is_none() || endpoint.addr == Some(ip_repr.dst_addr.into()) => { + let packet = TcpPacket::new_unchecked(data); + match TcpRepr::parse( + &packet, + &header.src_addr.into(), + &header.dst_addr.into(), + &cx.checksum_caps(), + ) { + Ok(repr) => endpoint.port == repr.src_port, + Err(_) => false, + } + } + // If we are bound to a specific ICMP identifier value, only accept an + // Echo Request/Reply with the identifier field matching the endpoint + // port. + (&Endpoint::Ident(bound_ident), &Icmpv4Repr::EchoRequest { ident, .. }) + | (&Endpoint::Ident(bound_ident), &Icmpv4Repr::EchoReply { ident, .. }) => { + ident == bound_ident + } + _ => false, + } + } + + /// Fitler determining whether the socket accepts a given ICMPv6 packet. + /// Accepted packets are enqueued into the socket's receive buffer. + #[cfg(feature = "proto-ipv6")] + #[inline] + pub(crate) fn accepts_v6( + &self, + cx: &mut Context, + ip_repr: &Ipv6Repr, + icmp_repr: &Icmpv6Repr, + ) -> bool { + match (&self.endpoint, icmp_repr) { + // If we are bound to ICMP errors associated to a UDP port, only + // accept Destination Unreachable or Time Exceeded messages with + // the data containing a UDP packet send from the local port we + // are bound to. + ( + &Endpoint::Udp(endpoint), + &Icmpv6Repr::DstUnreachable { data, header, .. } + | &Icmpv6Repr::TimeExceeded { data, header, .. }, + ) if endpoint.addr.is_none() || endpoint.addr == Some(ip_repr.dst_addr.into()) => { + let packet = UdpPacket::new_unchecked(data); + match UdpRepr::parse( + &packet, + &header.src_addr.into(), + &header.dst_addr.into(), + &cx.checksum_caps(), + ) { + Ok(repr) => endpoint.port == repr.src_port, + Err(_) => false, + } + } + // If we are bound to ICMP errors associated to a TCP port, only + // accept Destination Unreachable or Time Exceeded messages with + // the data containing a UDP packet send from the local port we + // are bound to. + ( + &Endpoint::Tcp(endpoint), + &Icmpv6Repr::DstUnreachable { data, header, .. } + | &Icmpv6Repr::TimeExceeded { data, header, .. }, + ) if endpoint.addr.is_none() || endpoint.addr == Some(ip_repr.dst_addr.into()) => { + let packet = TcpPacket::new_unchecked(data); + match TcpRepr::parse( + &packet, + &header.src_addr.into(), + &header.dst_addr.into(), + &cx.checksum_caps(), + ) { + Ok(repr) => endpoint.port == repr.src_port, + Err(_) => false, + } + } + // If we are bound to a specific ICMP identifier value, only accept an + // Echo Request/Reply with the identifier field matching the endpoint + // port. + ( + &Endpoint::Ident(bound_ident), + &Icmpv6Repr::EchoRequest { ident, .. } | &Icmpv6Repr::EchoReply { ident, .. }, + ) => ident == bound_ident, + _ => false, + } + } + + #[cfg(feature = "proto-ipv4")] + pub(crate) fn process_v4( + &mut self, + _cx: &mut Context, + ip_repr: &Ipv4Repr, + icmp_repr: &Icmpv4Repr, + ) { + net_trace!("icmp: receiving {} octets", icmp_repr.buffer_len()); + + match self + .rx_buffer + .enqueue(icmp_repr.buffer_len(), ip_repr.src_addr.into()) + { + Ok(packet_buf) => { + icmp_repr.emit( + &mut Icmpv4Packet::new_unchecked(packet_buf), + &ChecksumCapabilities::default(), + ); + } + Err(_) => net_trace!("icmp: buffer full, dropped incoming packet"), + } + + #[cfg(feature = "async")] + self.rx_waker.wake(); + } + + #[cfg(feature = "proto-ipv6")] + pub(crate) fn process_v6( + &mut self, + _cx: &mut Context, + ip_repr: &Ipv6Repr, + icmp_repr: &Icmpv6Repr, + ) { + net_trace!("icmp: receiving {} octets", icmp_repr.buffer_len()); + + match self + .rx_buffer + .enqueue(icmp_repr.buffer_len(), ip_repr.src_addr.into()) + { + Ok(packet_buf) => icmp_repr.emit( + &ip_repr.src_addr, + &ip_repr.dst_addr, + &mut Icmpv6Packet::new_unchecked(packet_buf), + &ChecksumCapabilities::default(), + ), + Err(_) => net_trace!("icmp: buffer full, dropped incoming packet"), + } + + #[cfg(feature = "async")] + self.rx_waker.wake(); + } + + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, (IpRepr, IcmpRepr)) -> Result<(), E>, + { + let hop_limit = self.hop_limit.unwrap_or(64); + let res = self.tx_buffer.dequeue_with(|remote_endpoint, packet_buf| { + net_trace!( + "icmp:{}: sending {} octets", + remote_endpoint, + packet_buf.len() + ); + match *remote_endpoint { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(dst_addr) => { + let src_addr = match cx.get_source_address_ipv4(&dst_addr) { + Some(addr) => addr, + None => { + net_trace!( + "icmp:{}: not find suitable source address, dropping", + remote_endpoint + ); + return Ok(()); + } + }; + let packet = Icmpv4Packet::new_unchecked(&*packet_buf); + let repr = match Icmpv4Repr::parse(&packet, &ChecksumCapabilities::ignored()) { + Ok(x) => x, + Err(_) => { + net_trace!( + "icmp:{}: malformed packet in queue, dropping", + remote_endpoint + ); + return Ok(()); + } + }; + let ip_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Icmp, + payload_len: repr.buffer_len(), + hop_limit, + }); + emit(cx, (ip_repr, IcmpRepr::Ipv4(repr))) + } + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(dst_addr) => { + let src_addr = cx.get_source_address_ipv6(&dst_addr); + + let packet = Icmpv6Packet::new_unchecked(&*packet_buf); + let repr = match Icmpv6Repr::parse( + &src_addr, + &dst_addr, + &packet, + &ChecksumCapabilities::ignored(), + ) { + Ok(x) => x, + Err(_) => { + net_trace!( + "icmp:{}: malformed packet in queue, dropping", + remote_endpoint + ); + return Ok(()); + } + }; + let ip_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr, + dst_addr, + next_header: IpProtocol::Icmpv6, + payload_len: repr.buffer_len(), + hop_limit, + }); + emit(cx, (ip_repr, IcmpRepr::Ipv6(repr))) + } + } + }); + match res { + Err(Empty) => Ok(()), + Ok(Err(e)) => Err(e), + Ok(Ok(())) => { + #[cfg(feature = "async")] + self.tx_waker.wake(); + Ok(()) + } + } + } + + pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt { + if self.tx_buffer.is_empty() { + PollAt::Ingress + } else { + PollAt::Now + } + } +} + +#[cfg(test)] +mod tests_common { + pub use super::*; + pub use crate::wire::IpAddress; + + pub fn buffer(packets: usize) -> PacketBuffer<'static> { + PacketBuffer::new(vec![PacketMetadata::EMPTY; packets], vec![0; 66 * packets]) + } + + pub fn socket( + rx_buffer: PacketBuffer<'static>, + tx_buffer: PacketBuffer<'static>, + ) -> Socket<'static> { + Socket::new(rx_buffer, tx_buffer) + } + + pub const LOCAL_PORT: u16 = 53; + + pub static UDP_REPR: UdpRepr = UdpRepr { + src_port: 53, + dst_port: 9090, + }; + + pub static UDP_PAYLOAD: &[u8] = &[0xff; 10]; +} + +#[cfg(all(test, feature = "proto-ipv4"))] +mod test_ipv4 { + use crate::phy::Medium; + use crate::tests::setup; + use rstest::*; + + use super::tests_common::*; + use crate::wire::{Icmpv4DstUnreachable, IpEndpoint, Ipv4Address}; + + const REMOTE_IPV4: Ipv4Address = Ipv4Address::new(192, 168, 1, 2); + const LOCAL_IPV4: Ipv4Address = Ipv4Address::new(192, 168, 1, 1); + const LOCAL_END_V4: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv4(LOCAL_IPV4), + port: LOCAL_PORT, + }; + + static ECHOV4_REPR: Icmpv4Repr = Icmpv4Repr::EchoRequest { + ident: 0x1234, + seq_no: 0x5678, + data: &[0xff; 16], + }; + + static LOCAL_IPV4_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { + src_addr: LOCAL_IPV4, + dst_addr: REMOTE_IPV4, + next_header: IpProtocol::Icmp, + payload_len: 24, + hop_limit: 0x40, + }); + + static REMOTE_IPV4_REPR: Ipv4Repr = Ipv4Repr { + src_addr: REMOTE_IPV4, + dst_addr: LOCAL_IPV4, + next_header: IpProtocol::Icmp, + payload_len: 24, + hop_limit: 0x40, + }; + + #[test] + fn test_send_unaddressable() { + let mut socket = socket(buffer(0), buffer(1)); + assert_eq!( + socket.send_slice(b"abcdef", IpAddress::Ipv4(Ipv4Address::new(0, 0, 0, 0))), + Err(SendError::Unaddressable) + ); + assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV4.into()), Ok(())); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_send_dispatch(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(0), buffer(1)); + let checksum = ChecksumCapabilities::default(); + + assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(())); + + // This buffer is too long + assert_eq!( + socket.send_slice(&[0xff; 67], REMOTE_IPV4.into()), + Err(SendError::BufferFull) + ); + assert!(socket.can_send()); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new_unchecked(&mut bytes); + ECHOV4_REPR.emit(&mut packet, &checksum); + + assert_eq!( + socket.send_slice(&*packet.into_inner(), REMOTE_IPV4.into()), + Ok(()) + ); + assert_eq!( + socket.send_slice(b"123456", REMOTE_IPV4.into()), + Err(SendError::BufferFull) + ); + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(cx, |_, (ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IPV4_REPR); + assert_eq!(icmp_repr, ECHOV4_REPR.into()); + Err(()) + }), + Err(()) + ); + // buffer is not taken off of the tx queue due to the error + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(cx, |_, (ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IPV4_REPR); + assert_eq!(icmp_repr, ECHOV4_REPR.into()); + Ok::<_, ()>(()) + }), + Ok(()) + ); + // buffer is taken off of the queue this time + assert!(socket.can_send()); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_set_hop_limit_v4(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut s = socket(buffer(0), buffer(1)); + let checksum = ChecksumCapabilities::default(); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new_unchecked(&mut bytes); + ECHOV4_REPR.emit(&mut packet, &checksum); + + s.set_hop_limit(Some(0x2a)); + + assert_eq!( + s.send_slice(&*packet.into_inner(), REMOTE_IPV4.into()), + Ok(()) + ); + assert_eq!( + s.dispatch(cx, |_, (ip_repr, _)| { + assert_eq!( + ip_repr, + IpRepr::Ipv4(Ipv4Repr { + src_addr: LOCAL_IPV4, + dst_addr: REMOTE_IPV4, + next_header: IpProtocol::Icmp, + payload_len: ECHOV4_REPR.buffer_len(), + hop_limit: 0x2a, + }) + ); + Ok::<_, ()>(()) + }), + Ok(()) + ); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_recv_process(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + assert!(!socket.can_recv()); + assert_eq!(socket.recv(), Err(RecvError::Exhausted)); + + let checksum = ChecksumCapabilities::default(); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]); + ECHOV4_REPR.emit(&mut packet, &checksum); + let data = &*packet.into_inner(); + + assert!(socket.accepts_v4(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR)); + socket.process_v4(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR); + assert!(socket.can_recv()); + + assert!(socket.accepts_v4(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR)); + socket.process_v4(cx, &REMOTE_IPV4_REPR, &ECHOV4_REPR); + + assert_eq!(socket.recv(), Ok((data, REMOTE_IPV4.into()))); + assert!(!socket.can_recv()); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_accept_bad_id(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + let checksum = ChecksumCapabilities::default(); + let mut bytes = [0xff; 20]; + let mut packet = Icmpv4Packet::new_unchecked(&mut bytes); + let icmp_repr = Icmpv4Repr::EchoRequest { + ident: 0x4321, + seq_no: 0x5678, + data: &[0xff; 16], + }; + icmp_repr.emit(&mut packet, &checksum); + + // Ensure that a packet with an identifier that isn't the bound + // ID is not accepted + assert!(!socket.accepts_v4(cx, &REMOTE_IPV4_REPR, &icmp_repr)); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_accepts_udp(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V4.into())), Ok(())); + + let checksum = ChecksumCapabilities::default(); + + let mut bytes = [0xff; 18]; + let mut packet = UdpPacket::new_unchecked(&mut bytes); + UDP_REPR.emit( + &mut packet, + &REMOTE_IPV4.into(), + &LOCAL_IPV4.into(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(UDP_PAYLOAD), + &checksum, + ); + + let data = &*packet.into_inner(); + + let icmp_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::PortUnreachable, + header: Ipv4Repr { + src_addr: LOCAL_IPV4, + dst_addr: REMOTE_IPV4, + next_header: IpProtocol::Icmp, + payload_len: 12, + hop_limit: 0x40, + }, + data, + }; + let ip_repr = Ipv4Repr { + src_addr: REMOTE_IPV4, + dst_addr: LOCAL_IPV4, + next_header: IpProtocol::Icmp, + payload_len: icmp_repr.buffer_len(), + hop_limit: 0x40, + }; + + assert!(!socket.can_recv()); + + // Ensure we can accept ICMP error response to the bound + // UDP port + assert!(socket.accepts_v4(cx, &ip_repr, &icmp_repr)); + socket.process_v4(cx, &ip_repr, &icmp_repr); + assert!(socket.can_recv()); + + let mut bytes = [0x00; 46]; + let mut packet = Icmpv4Packet::new_unchecked(&mut bytes[..]); + icmp_repr.emit(&mut packet, &checksum); + assert_eq!( + socket.recv(), + Ok((&*packet.into_inner(), REMOTE_IPV4.into())) + ); + assert!(!socket.can_recv()); + } +} + +#[cfg(all(test, feature = "proto-ipv6"))] +mod test_ipv6 { + use crate::phy::Medium; + use crate::tests::setup; + use rstest::*; + + use super::tests_common::*; + + use crate::wire::{Icmpv6DstUnreachable, IpEndpoint, Ipv6Address}; + + const REMOTE_IPV6: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + const LOCAL_IPV6: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const LOCAL_END_V6: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv6(LOCAL_IPV6), + port: LOCAL_PORT, + }; + static ECHOV6_REPR: Icmpv6Repr = Icmpv6Repr::EchoRequest { + ident: 0x1234, + seq_no: 0x5678, + data: &[0xff; 16], + }; + + static LOCAL_IPV6_REPR: Ipv6Repr = Ipv6Repr { + src_addr: LOCAL_IPV6, + dst_addr: REMOTE_IPV6, + next_header: IpProtocol::Icmpv6, + payload_len: 24, + hop_limit: 0x40, + }; + + static REMOTE_IPV6_REPR: Ipv6Repr = Ipv6Repr { + src_addr: REMOTE_IPV6, + dst_addr: LOCAL_IPV6, + next_header: IpProtocol::Icmpv6, + payload_len: 24, + hop_limit: 0x40, + }; + + #[test] + fn test_send_unaddressable() { + let mut socket = socket(buffer(0), buffer(1)); + assert_eq!( + socket.send_slice(b"abcdef", IpAddress::Ipv6(Ipv6Address::UNSPECIFIED)), + Err(SendError::Unaddressable) + ); + assert_eq!(socket.send_slice(b"abcdef", REMOTE_IPV6.into()), Ok(())); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_send_dispatch(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(0), buffer(1)); + let checksum = ChecksumCapabilities::default(); + + assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(())); + + // This buffer is too long + assert_eq!( + socket.send_slice(&[0xff; 67], REMOTE_IPV6.into()), + Err(SendError::BufferFull) + ); + assert!(socket.can_send()); + + let mut bytes = vec![0xff; 24]; + let mut packet = Icmpv6Packet::new_unchecked(&mut bytes); + ECHOV6_REPR.emit(&LOCAL_IPV6, &REMOTE_IPV6, &mut packet, &checksum); + + assert_eq!( + socket.send_slice(&*packet.into_inner(), REMOTE_IPV6.into()), + Ok(()) + ); + assert_eq!( + socket.send_slice(b"123456", REMOTE_IPV6.into()), + Err(SendError::BufferFull) + ); + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(cx, |_, (ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IPV6_REPR.into()); + assert_eq!(icmp_repr, ECHOV6_REPR.into()); + Err(()) + }), + Err(()) + ); + // buffer is not taken off of the tx queue due to the error + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(cx, |_, (ip_repr, icmp_repr)| { + assert_eq!(ip_repr, LOCAL_IPV6_REPR.into()); + assert_eq!(icmp_repr, ECHOV6_REPR.into()); + Ok::<_, ()>(()) + }), + Ok(()) + ); + // buffer is taken off of the queue this time + assert!(socket.can_send()); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_set_hop_limit(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut s = socket(buffer(0), buffer(1)); + let checksum = ChecksumCapabilities::default(); + + let mut bytes = vec![0xff; 24]; + let mut packet = Icmpv6Packet::new_unchecked(&mut bytes); + ECHOV6_REPR.emit(&LOCAL_IPV6, &REMOTE_IPV6, &mut packet, &checksum); + + s.set_hop_limit(Some(0x2a)); + + assert_eq!( + s.send_slice(&*packet.into_inner(), REMOTE_IPV6.into()), + Ok(()) + ); + assert_eq!( + s.dispatch(cx, |_, (ip_repr, _)| { + assert_eq!( + ip_repr, + IpRepr::Ipv6(Ipv6Repr { + src_addr: LOCAL_IPV6, + dst_addr: REMOTE_IPV6, + next_header: IpProtocol::Icmpv6, + payload_len: ECHOV6_REPR.buffer_len(), + hop_limit: 0x2a, + }) + ); + Ok::<_, ()>(()) + }), + Ok(()) + ); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_recv_process(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + assert!(!socket.can_recv()); + assert_eq!(socket.recv(), Err(RecvError::Exhausted)); + + let checksum = ChecksumCapabilities::default(); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv6Packet::new_unchecked(&mut bytes[..]); + ECHOV6_REPR.emit(&LOCAL_IPV6, &REMOTE_IPV6, &mut packet, &checksum); + let data = &*packet.into_inner(); + + assert!(socket.accepts_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR)); + socket.process_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR); + assert!(socket.can_recv()); + + assert!(socket.accepts_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR)); + socket.process_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR); + + assert_eq!(socket.recv(), Ok((data, REMOTE_IPV6.into()))); + assert!(!socket.can_recv()); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_truncated_recv_slice(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + let checksum = ChecksumCapabilities::default(); + + let mut bytes = [0xff; 24]; + let mut packet = Icmpv6Packet::new_unchecked(&mut bytes[..]); + ECHOV6_REPR.emit(&LOCAL_IPV6, &REMOTE_IPV6, &mut packet, &checksum); + + assert!(socket.accepts_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR)); + socket.process_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR); + assert!(socket.can_recv()); + + assert!(socket.accepts_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR)); + socket.process_v6(cx, &REMOTE_IPV6_REPR, &ECHOV6_REPR); + + let mut buffer = [0u8; 1]; + assert_eq!( + socket.recv_slice(&mut buffer[..]), + Err(RecvError::Truncated) + ); + assert!(!socket.can_recv()); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_accept_bad_id(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Ident(0x1234)), Ok(())); + + let checksum = ChecksumCapabilities::default(); + let mut bytes = [0xff; 20]; + let mut packet = Icmpv6Packet::new_unchecked(&mut bytes); + let icmp_repr = Icmpv6Repr::EchoRequest { + ident: 0x4321, + seq_no: 0x5678, + data: &[0xff; 16], + }; + icmp_repr.emit(&LOCAL_IPV6, &REMOTE_IPV6, &mut packet, &checksum); + + // Ensure that a packet with an identifier that isn't the bound + // ID is not accepted + assert!(!socket.accepts_v6(cx, &REMOTE_IPV6_REPR, &icmp_repr)); + } + + #[rstest] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + fn test_accepts_udp(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(1)); + assert_eq!(socket.bind(Endpoint::Udp(LOCAL_END_V6.into())), Ok(())); + + let checksum = ChecksumCapabilities::default(); + + let mut bytes = [0xff; 18]; + let mut packet = UdpPacket::new_unchecked(&mut bytes); + UDP_REPR.emit( + &mut packet, + &REMOTE_IPV6.into(), + &LOCAL_IPV6.into(), + UDP_PAYLOAD.len(), + |buf| buf.copy_from_slice(UDP_PAYLOAD), + &checksum, + ); + + let data = &*packet.into_inner(); + + let icmp_repr = Icmpv6Repr::DstUnreachable { + reason: Icmpv6DstUnreachable::PortUnreachable, + header: Ipv6Repr { + src_addr: LOCAL_IPV6, + dst_addr: REMOTE_IPV6, + next_header: IpProtocol::Icmpv6, + payload_len: 12, + hop_limit: 0x40, + }, + data, + }; + let ip_repr = Ipv6Repr { + src_addr: REMOTE_IPV6, + dst_addr: LOCAL_IPV6, + next_header: IpProtocol::Icmpv6, + payload_len: icmp_repr.buffer_len(), + hop_limit: 0x40, + }; + + assert!(!socket.can_recv()); + + // Ensure we can accept ICMP error response to the bound + // UDP port + assert!(socket.accepts_v6(cx, &ip_repr, &icmp_repr)); + socket.process_v6(cx, &ip_repr, &icmp_repr); + assert!(socket.can_recv()); + + let mut bytes = [0x00; 66]; + let mut packet = Icmpv6Packet::new_unchecked(&mut bytes[..]); + icmp_repr.emit(&LOCAL_IPV6, &REMOTE_IPV6, &mut packet, &checksum); + assert_eq!( + socket.recv(), + Ok((&*packet.into_inner(), REMOTE_IPV6.into())) + ); + assert!(!socket.can_recv()); + } +} diff --git a/vendor/smoltcp/src/socket/mod.rs b/vendor/smoltcp/src/socket/mod.rs new file mode 100644 index 00000000..6df3e173 --- /dev/null +++ b/vendor/smoltcp/src/socket/mod.rs @@ -0,0 +1,165 @@ +/*! Communication between endpoints. + +The `socket` module deals with *network endpoints* and *buffering*. +It provides interfaces for accessing buffers of data, and protocol state machines +for filling and emptying these buffers. + +The programming interface implemented here differs greatly from the common Berkeley socket +interface. Specifically, in the Berkeley interface the buffering is implicit: +the operating system decides on the good size for a buffer and manages it. +The interface implemented by this module uses explicit buffering: you decide on the good +size for a buffer, allocate it, and let the networking stack use it. +*/ + +use crate::iface::Context; +use crate::time::Instant; + +#[cfg(feature = "socket-dhcpv4")] +pub mod dhcpv4; +#[cfg(feature = "socket-dns")] +pub mod dns; +#[cfg(feature = "socket-icmp")] +pub mod icmp; +#[cfg(feature = "socket-raw")] +pub mod raw; +#[cfg(feature = "socket-tcp")] +pub mod tcp; +#[cfg(feature = "socket-udp")] +pub mod udp; + +#[cfg(feature = "async")] +mod waker; + +#[cfg(feature = "async")] +pub(crate) use self::waker::WakerRegistration; + +/// Gives an indication on the next time the socket should be polled. +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum PollAt { + /// The socket needs to be polled immediately. + Now, + /// The socket needs to be polled at given [Instant][struct.Instant]. + Time(Instant), + /// The socket does not need to be polled unless there are external changes. + Ingress, +} + +/// A network socket. +/// +/// This enumeration abstracts the various types of sockets based on the IP protocol. +/// To downcast a `Socket` value to a concrete socket, use the [AnySocket] trait, +/// e.g. to get `udp::Socket`, call `udp::Socket::downcast(socket)`. +/// +/// It is usually more convenient to use [SocketSet::get] instead. +/// +/// [AnySocket]: trait.AnySocket.html +/// [SocketSet::get]: struct.SocketSet.html#method.get +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum Socket<'a> { + #[cfg(feature = "socket-raw")] + Raw(raw::Socket<'a>), + #[cfg(feature = "socket-icmp")] + Icmp(icmp::Socket<'a>), + #[cfg(feature = "socket-udp")] + Udp(udp::Socket<'a>), + #[cfg(feature = "socket-tcp")] + Tcp(tcp::Socket<'a>), + #[cfg(feature = "socket-dhcpv4")] + Dhcpv4(dhcpv4::Socket<'a>), + #[cfg(feature = "socket-dns")] + Dns(dns::Socket<'a>), +} + +impl<'a> Socket<'a> { + pub(crate) fn poll_at(&self, cx: &mut Context) -> PollAt { + match self { + #[cfg(feature = "socket-raw")] + Socket::Raw(s) => s.poll_at(cx), + #[cfg(feature = "socket-icmp")] + Socket::Icmp(s) => s.poll_at(cx), + #[cfg(feature = "socket-udp")] + Socket::Udp(s) => s.poll_at(cx), + #[cfg(feature = "socket-tcp")] + Socket::Tcp(s) => s.poll_at(cx), + #[cfg(feature = "socket-dhcpv4")] + Socket::Dhcpv4(s) => s.poll_at(cx), + #[cfg(feature = "socket-dns")] + Socket::Dns(s) => s.poll_at(cx), + } + } +} + +/// A conversion trait for network sockets. +pub trait AnySocket<'a> { + fn upcast(self) -> Socket<'a>; + fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self> + where + Self: Sized; + fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> + where + Self: Sized; +} + +impl<'a> AnySocket<'a> for Socket<'a> { + #[inline] + fn upcast(self) -> Socket<'a> { + self + } + + #[inline] + fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self> + where + Self: Sized, + { + Some(socket) + } + + #[inline] + fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> + where + Self: Sized, + { + Some(socket) + } +} + +macro_rules! from_socket { + ($socket:ty, $variant:ident) => { + impl<'a> AnySocket<'a> for $socket { + fn upcast(self) -> Socket<'a> { + Socket::$variant(self) + } + + fn downcast<'c>(socket: &'c Socket<'a>) -> Option<&'c Self> { + #[allow(unreachable_patterns)] + match socket { + Socket::$variant(socket) => Some(socket), + _ => None, + } + } + + fn downcast_mut<'c>(socket: &'c mut Socket<'a>) -> Option<&'c mut Self> { + #[allow(unreachable_patterns)] + match socket { + Socket::$variant(socket) => Some(socket), + _ => None, + } + } + } + }; +} + +#[cfg(feature = "socket-raw")] +from_socket!(raw::Socket<'a>, Raw); +#[cfg(feature = "socket-icmp")] +from_socket!(icmp::Socket<'a>, Icmp); +#[cfg(feature = "socket-udp")] +from_socket!(udp::Socket<'a>, Udp); +#[cfg(feature = "socket-tcp")] +from_socket!(tcp::Socket<'a>, Tcp); +#[cfg(feature = "socket-dhcpv4")] +from_socket!(dhcpv4::Socket<'a>, Dhcpv4); +#[cfg(feature = "socket-dns")] +from_socket!(dns::Socket<'a>, Dns); diff --git a/vendor/smoltcp/src/socket/raw.rs b/vendor/smoltcp/src/socket/raw.rs new file mode 100644 index 00000000..a366b1b2 --- /dev/null +++ b/vendor/smoltcp/src/socket/raw.rs @@ -0,0 +1,949 @@ +use core::cmp::min; +#[cfg(feature = "async")] +use core::task::Waker; + +use crate::iface::Context; +use crate::socket::PollAt; +#[cfg(feature = "async")] +use crate::socket::WakerRegistration; + +use crate::storage::Empty; +use crate::wire::{IpProtocol, IpRepr, IpVersion}; +#[cfg(feature = "proto-ipv4")] +use crate::wire::{Ipv4Packet, Ipv4Repr}; +#[cfg(feature = "proto-ipv6")] +use crate::wire::{Ipv6Packet, Ipv6Repr}; + +/// Error returned by [`Socket::bind`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + InvalidState, + Unaddressable, +} + +impl core::fmt::Display for BindError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + BindError::InvalidState => write!(f, "invalid state"), + BindError::Unaddressable => write!(f, "unaddressable"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for BindError {} + +/// Error returned by [`Socket::send`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + BufferFull, +} + +impl core::fmt::Display for SendError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + SendError::BufferFull => write!(f, "buffer full"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SendError {} + +/// Error returned by [`Socket::recv`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + Exhausted, + Truncated, +} + +impl core::fmt::Display for RecvError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + RecvError::Exhausted => write!(f, "exhausted"), + RecvError::Truncated => write!(f, "truncated"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RecvError {} + +/// A UDP packet metadata. +pub type PacketMetadata = crate::storage::PacketMetadata<()>; + +/// A UDP packet ring buffer. +pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, ()>; + +/// A raw IP socket. +/// +/// A raw socket may be bound to a specific IP protocol, and owns +/// transmit and receive packet buffers. +#[derive(Debug)] +pub struct Socket<'a> { + ip_version: Option, + ip_protocol: Option, + rx_buffer: PacketBuffer<'a>, + tx_buffer: PacketBuffer<'a>, + #[cfg(feature = "async")] + rx_waker: WakerRegistration, + #[cfg(feature = "async")] + tx_waker: WakerRegistration, +} + +impl<'a> Socket<'a> { + /// Create a raw IP socket bound to the given IP version and datagram protocol, + /// with the given buffers. + pub fn new( + ip_version: Option, + ip_protocol: Option, + rx_buffer: PacketBuffer<'a>, + tx_buffer: PacketBuffer<'a>, + ) -> Socket<'a> { + Socket { + ip_version, + ip_protocol, + rx_buffer, + tx_buffer, + #[cfg(feature = "async")] + rx_waker: WakerRegistration::new(), + #[cfg(feature = "async")] + tx_waker: WakerRegistration::new(), + } + } + + /// Register a waker for receive operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `recv` method calls, such as receiving data, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_recv_waker(&mut self, waker: &Waker) { + self.rx_waker.register(waker) + } + + /// Register a waker for send operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `send` method calls, such as space becoming available in the transmit + /// buffer, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_send_waker(&mut self, waker: &Waker) { + self.tx_waker.register(waker) + } + + /// Return the IP version the socket is bound to. + #[inline] + pub fn ip_version(&self) -> Option { + self.ip_version + } + + /// Return the IP protocol the socket is bound to. + #[inline] + pub fn ip_protocol(&self) -> Option { + self.ip_protocol + } + + /// Check whether the transmit buffer is full. + #[inline] + pub fn can_send(&self) -> bool { + !self.tx_buffer.is_full() + } + + /// Check whether the receive buffer is not empty. + #[inline] + pub fn can_recv(&self) -> bool { + !self.rx_buffer.is_empty() + } + + /// Return the maximum number packets the socket can receive. + #[inline] + pub fn packet_recv_capacity(&self) -> usize { + self.rx_buffer.packet_capacity() + } + + /// Return the maximum number packets the socket can transmit. + #[inline] + pub fn packet_send_capacity(&self) -> usize { + self.tx_buffer.packet_capacity() + } + + /// Return the maximum number of bytes inside the recv buffer. + #[inline] + pub fn payload_recv_capacity(&self) -> usize { + self.rx_buffer.payload_capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + #[inline] + pub fn payload_send_capacity(&self) -> usize { + self.tx_buffer.payload_capacity() + } + + /// Enqueue a packet to send, and return a pointer to its payload. + /// + /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full, + /// and `Err(Error::Truncated)` if there is not enough transmit buffer capacity + /// to ever send this packet. + /// + /// If the buffer is filled in a way that does not match the socket's + /// IP version or protocol, the packet will be silently dropped. + /// + /// **Note:** The IP header is parsed and re-serialized, and may not match + /// the header actually transmitted bit for bit. + pub fn send(&mut self, size: usize) -> Result<&mut [u8], SendError> { + let packet_buf = self + .tx_buffer + .enqueue(size, ()) + .map_err(|_| SendError::BufferFull)?; + + net_trace!( + "raw:{:?}:{:?}: buffer to send {} octets", + self.ip_version, + self.ip_protocol, + packet_buf.len() + ); + Ok(packet_buf) + } + + /// Enqueue a packet to be send and pass the buffer to the provided closure. + /// The closure then returns the size of the data written into the buffer. + /// + /// Also see [send](#method.send). + pub fn send_with(&mut self, max_size: usize, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> usize, + { + let size = self + .tx_buffer + .enqueue_with_infallible(max_size, (), f) + .map_err(|_| SendError::BufferFull)?; + + net_trace!( + "raw:{:?}:{:?}: buffer to send {} octets", + self.ip_version, + self.ip_protocol, + size + ); + + Ok(size) + } + + /// Enqueue a packet to send, and fill it from a slice. + /// + /// See also [send](#method.send). + pub fn send_slice(&mut self, data: &[u8]) -> Result<(), SendError> { + self.send(data.len())?.copy_from_slice(data); + Ok(()) + } + + /// Dequeue a packet, and return a pointer to the payload. + /// + /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty. + /// + /// **Note:** The IP header is parsed and re-serialized, and may not match + /// the header actually received bit for bit. + pub fn recv(&mut self) -> Result<&[u8], RecvError> { + let ((), packet_buf) = self.rx_buffer.dequeue().map_err(|_| RecvError::Exhausted)?; + + net_trace!( + "raw:{:?}:{:?}: receive {} buffered octets", + self.ip_version, + self.ip_protocol, + packet_buf.len() + ); + Ok(packet_buf) + } + + /// Dequeue a packet, and copy the payload into the given slice. + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// the packet is dropped and a `RecvError::Truncated` error is returned. + /// + /// See also [recv](#method.recv). + pub fn recv_slice(&mut self, data: &mut [u8]) -> Result { + let buffer = self.recv()?; + if data.len() < buffer.len() { + return Err(RecvError::Truncated); + } + + let length = min(data.len(), buffer.len()); + data[..length].copy_from_slice(&buffer[..length]); + Ok(length) + } + + /// Peek at a packet in the receive buffer and return a pointer to the + /// payload without removing the packet from the receive buffer. + /// This function otherwise behaves identically to [recv](#method.recv). + /// + /// It returns `Err(Error::Exhausted)` if the receive buffer is empty. + pub fn peek(&mut self) -> Result<&[u8], RecvError> { + let ((), packet_buf) = self.rx_buffer.peek().map_err(|_| RecvError::Exhausted)?; + + net_trace!( + "raw:{:?}:{:?}: receive {} buffered octets", + self.ip_version, + self.ip_protocol, + packet_buf.len() + ); + + Ok(packet_buf) + } + + /// Peek at a packet in the receive buffer, copy the payload into the given slice, + /// and return the amount of octets copied without removing the packet from the receive buffer. + /// This function otherwise behaves identically to [recv_slice](#method.recv_slice). + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// no data is copied into the provided buffer and a `RecvError::Truncated` error is returned. + /// + /// See also [peek](#method.peek). + pub fn peek_slice(&mut self, data: &mut [u8]) -> Result { + let buffer = self.peek()?; + if data.len() < buffer.len() { + return Err(RecvError::Truncated); + } + + let length = min(data.len(), buffer.len()); + data[..length].copy_from_slice(&buffer[..length]); + Ok(length) + } + + /// Return the amount of octets queued in the transmit buffer. + pub fn send_queue(&self) -> usize { + self.tx_buffer.payload_bytes_count() + } + + /// Return the amount of octets queued in the receive buffer. + pub fn recv_queue(&self) -> usize { + self.rx_buffer.payload_bytes_count() + } + + pub(crate) fn accepts(&self, ip_repr: &IpRepr) -> bool { + if self + .ip_version + .is_some_and(|version| version != ip_repr.version()) + { + return false; + } + + if self + .ip_protocol + .is_some_and(|next_header| next_header != ip_repr.next_header()) + { + return false; + } + + true + } + + pub(crate) fn process(&mut self, cx: &mut Context, ip_repr: &IpRepr, payload: &[u8]) { + debug_assert!(self.accepts(ip_repr)); + + let header_len = ip_repr.header_len(); + let total_len = header_len + payload.len(); + + net_trace!( + "raw:{:?}:{:?}: receiving {} octets", + self.ip_version, + self.ip_protocol, + total_len + ); + + match self.rx_buffer.enqueue(total_len, ()) { + Ok(buf) => { + ip_repr.emit(&mut buf[..header_len], &cx.checksum_caps()); + buf[header_len..].copy_from_slice(payload); + } + Err(_) => net_trace!( + "raw:{:?}:{:?}: buffer full, dropped incoming packet", + self.ip_version, + self.ip_protocol + ), + } + + #[cfg(feature = "async")] + self.rx_waker.wake(); + } + + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, (IpRepr, &[u8])) -> Result<(), E>, + { + let ip_protocol = self.ip_protocol; + let ip_version = self.ip_version; + let _checksum_caps = &cx.checksum_caps(); + let res = self.tx_buffer.dequeue_with(|&mut (), buffer| { + match IpVersion::of_packet(buffer) { + #[cfg(feature = "proto-ipv4")] + Ok(IpVersion::Ipv4) => { + let mut packet = match Ipv4Packet::new_checked(buffer) { + Ok(x) => x, + Err(_) => { + net_trace!("raw: malformed ipv6 packet in queue, dropping."); + return Ok(()); + } + }; + if ip_protocol.is_some_and(|next_header| next_header != packet.next_header()) { + net_trace!("raw: sent packet with wrong ip protocol, dropping."); + return Ok(()); + } + if _checksum_caps.ipv4.tx() { + packet.fill_checksum(); + } else { + // make sure we get a consistently zeroed checksum, + // since implementations might rely on it + packet.set_checksum(0); + } + + let packet = Ipv4Packet::new_unchecked(&*packet.into_inner()); + let ipv4_repr = match Ipv4Repr::parse(&packet, _checksum_caps) { + Ok(x) => x, + Err(_) => { + net_trace!("raw: malformed ipv4 packet in queue, dropping."); + return Ok(()); + } + }; + net_trace!("raw:{:?}:{:?}: sending", ip_version, ip_protocol); + emit(cx, (IpRepr::Ipv4(ipv4_repr), packet.payload())) + } + #[cfg(feature = "proto-ipv6")] + Ok(IpVersion::Ipv6) => { + let packet = match Ipv6Packet::new_checked(buffer) { + Ok(x) => x, + Err(_) => { + net_trace!("raw: malformed ipv6 packet in queue, dropping."); + return Ok(()); + } + }; + if ip_protocol.is_some_and(|next_header| next_header != packet.next_header()) { + net_trace!("raw: sent ipv6 packet with wrong ip protocol, dropping."); + return Ok(()); + } + let packet = Ipv6Packet::new_unchecked(&*packet.into_inner()); + let ipv6_repr = match Ipv6Repr::parse(&packet) { + Ok(x) => x, + Err(_) => { + net_trace!("raw: malformed ipv6 packet in queue, dropping."); + return Ok(()); + } + }; + + net_trace!("raw:{:?}:{:?}: sending", ip_version, ip_protocol); + emit(cx, (IpRepr::Ipv6(ipv6_repr), packet.payload())) + } + Err(_) => { + net_trace!("raw: sent packet with invalid IP version, dropping."); + Ok(()) + } + } + }); + match res { + Err(Empty) => Ok(()), + Ok(Err(e)) => Err(e), + Ok(Ok(())) => { + #[cfg(feature = "async")] + self.tx_waker.wake(); + Ok(()) + } + } + } + + pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt { + if self.tx_buffer.is_empty() { + PollAt::Ingress + } else { + PollAt::Now + } + } +} + +#[cfg(test)] +mod test { + use crate::phy::Medium; + use crate::tests::setup; + use rstest::*; + + use super::*; + use crate::wire::IpRepr; + #[cfg(feature = "proto-ipv4")] + use crate::wire::{Ipv4Address, Ipv4Repr}; + #[cfg(feature = "proto-ipv6")] + use crate::wire::{Ipv6Address, Ipv6Repr}; + + fn buffer(packets: usize) -> PacketBuffer<'static> { + PacketBuffer::new(vec![PacketMetadata::EMPTY; packets], vec![0; 48 * packets]) + } + + #[cfg(feature = "proto-ipv4")] + mod ipv4_locals { + use super::*; + + pub fn socket( + rx_buffer: PacketBuffer<'static>, + tx_buffer: PacketBuffer<'static>, + ) -> Socket<'static> { + Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Unknown(IP_PROTO)), + rx_buffer, + tx_buffer, + ) + } + + pub const IP_PROTO: u8 = 63; + pub const HEADER_REPR: IpRepr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 0, 1), + dst_addr: Ipv4Address::new(10, 0, 0, 2), + next_header: IpProtocol::Unknown(IP_PROTO), + payload_len: 4, + hop_limit: 64, + }); + pub const PACKET_BYTES: [u8; 24] = [ + 0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x3f, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x01, 0x0a, 0x00, 0x00, 0x02, 0xaa, 0x00, 0x00, 0xff, + ]; + pub const PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + } + + #[cfg(feature = "proto-ipv6")] + mod ipv6_locals { + use super::*; + + pub fn socket( + rx_buffer: PacketBuffer<'static>, + tx_buffer: PacketBuffer<'static>, + ) -> Socket<'static> { + Socket::new( + Some(IpVersion::Ipv6), + Some(IpProtocol::Unknown(IP_PROTO)), + rx_buffer, + tx_buffer, + ) + } + + pub const IP_PROTO: u8 = 63; + pub const HEADER_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Unknown(IP_PROTO), + payload_len: 4, + hop_limit: 64, + }); + + pub const PACKET_BYTES: [u8; 44] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x04, 0x3f, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0x00, + 0x00, 0xff, + ]; + + pub const PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + } + + macro_rules! reusable_ip_specific_tests { + ($module:ident, $socket:path, $hdr:path, $packet:path, $payload:path) => { + mod $module { + use super::*; + + #[test] + fn test_send_truncated() { + let mut socket = $socket(buffer(0), buffer(1)); + assert_eq!(socket.send_slice(&[0; 56][..]), Err(SendError::BufferFull)); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_send_dispatch(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let mut cx = iface.context(); + let mut socket = $socket(buffer(0), buffer(1)); + + assert!(socket.can_send()); + assert_eq!( + socket.dispatch(&mut cx, |_, _| unreachable!()), + Ok::<_, ()>(()) + ); + + assert_eq!(socket.send_slice(&$packet[..]), Ok(())); + assert_eq!(socket.send_slice(b""), Err(SendError::BufferFull)); + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(&mut cx, |_, (ip_repr, ip_payload)| { + assert_eq!(ip_repr, $hdr); + assert_eq!(ip_payload, &$payload); + Err(()) + }), + Err(()) + ); + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(&mut cx, |_, (ip_repr, ip_payload)| { + assert_eq!(ip_repr, $hdr); + assert_eq!(ip_payload, &$payload); + Ok::<_, ()>(()) + }), + Ok(()) + ); + assert!(socket.can_send()); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_recv_truncated_slice(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let mut cx = iface.context(); + let mut socket = $socket(buffer(1), buffer(0)); + + assert!(socket.accepts(&$hdr)); + socket.process(&mut cx, &$hdr, &$payload); + + let mut slice = [0; 4]; + assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated)); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_recv_truncated_packet(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let mut cx = iface.context(); + let mut socket = $socket(buffer(1), buffer(0)); + + let mut buffer = vec![0; 128]; + buffer[..$packet.len()].copy_from_slice(&$packet[..]); + + assert!(socket.accepts(&$hdr)); + socket.process(&mut cx, &$hdr, &buffer); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_peek_truncated_slice(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let mut cx = iface.context(); + let mut socket = $socket(buffer(1), buffer(0)); + + assert!(socket.accepts(&$hdr)); + socket.process(&mut cx, &$hdr, &$payload); + + let mut slice = [0; 4]; + assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Truncated)); + assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated)); + assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Exhausted)); + } + } + }; + } + + #[cfg(feature = "proto-ipv4")] + reusable_ip_specific_tests!( + ipv4, + ipv4_locals::socket, + ipv4_locals::HEADER_REPR, + ipv4_locals::PACKET_BYTES, + ipv4_locals::PACKET_PAYLOAD + ); + + #[cfg(feature = "proto-ipv6")] + reusable_ip_specific_tests!( + ipv6, + ipv6_locals::socket, + ipv6_locals::HEADER_REPR, + ipv6_locals::PACKET_BYTES, + ipv6_locals::PACKET_PAYLOAD + ); + + #[rstest] + #[case::ip(Medium::Ip)] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_send_illegal(#[case] medium: Medium) { + #[cfg(feature = "proto-ipv4")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = ipv4_locals::socket(buffer(0), buffer(2)); + + let mut wrong_version = ipv4_locals::PACKET_BYTES; + Ipv4Packet::new_unchecked(&mut wrong_version).set_version(6); + + assert_eq!(socket.send_slice(&wrong_version[..]), Ok(())); + assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(())); + + let mut wrong_protocol = ipv4_locals::PACKET_BYTES; + Ipv4Packet::new_unchecked(&mut wrong_protocol).set_next_header(IpProtocol::Tcp); + + assert_eq!(socket.send_slice(&wrong_protocol[..]), Ok(())); + assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(())); + } + #[cfg(feature = "proto-ipv6")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = ipv6_locals::socket(buffer(0), buffer(2)); + + let mut wrong_version = ipv6_locals::PACKET_BYTES; + Ipv6Packet::new_unchecked(&mut wrong_version[..]).set_version(4); + + assert_eq!(socket.send_slice(&wrong_version[..]), Ok(())); + assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(())); + + let mut wrong_protocol = ipv6_locals::PACKET_BYTES; + Ipv6Packet::new_unchecked(&mut wrong_protocol[..]).set_next_header(IpProtocol::Tcp); + + assert_eq!(socket.send_slice(&wrong_protocol[..]), Ok(())); + assert_eq!(socket.dispatch(cx, |_, _| unreachable!()), Ok::<_, ()>(())); + } + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_recv_process(#[case] medium: Medium) { + #[cfg(feature = "proto-ipv4")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = ipv4_locals::socket(buffer(1), buffer(0)); + assert!(!socket.can_recv()); + + let mut cksumd_packet = ipv4_locals::PACKET_BYTES; + Ipv4Packet::new_unchecked(&mut cksumd_packet).fill_checksum(); + + assert_eq!(socket.recv(), Err(RecvError::Exhausted)); + assert!(socket.accepts(&ipv4_locals::HEADER_REPR)); + socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD); + assert!(socket.can_recv()); + + assert!(socket.accepts(&ipv4_locals::HEADER_REPR)); + socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD); + assert_eq!(socket.recv(), Ok(&cksumd_packet[..])); + assert!(!socket.can_recv()); + } + #[cfg(feature = "proto-ipv6")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = ipv6_locals::socket(buffer(1), buffer(0)); + assert!(!socket.can_recv()); + + assert_eq!(socket.recv(), Err(RecvError::Exhausted)); + assert!(socket.accepts(&ipv6_locals::HEADER_REPR)); + socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD); + assert!(socket.can_recv()); + + assert!(socket.accepts(&ipv6_locals::HEADER_REPR)); + socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD); + assert_eq!(socket.recv(), Ok(&ipv6_locals::PACKET_BYTES[..])); + assert!(!socket.can_recv()); + } + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_peek_process(#[case] medium: Medium) { + #[cfg(feature = "proto-ipv4")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = ipv4_locals::socket(buffer(1), buffer(0)); + + let mut cksumd_packet = ipv4_locals::PACKET_BYTES; + Ipv4Packet::new_unchecked(&mut cksumd_packet).fill_checksum(); + + assert_eq!(socket.peek(), Err(RecvError::Exhausted)); + assert!(socket.accepts(&ipv4_locals::HEADER_REPR)); + socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD); + + assert!(socket.accepts(&ipv4_locals::HEADER_REPR)); + socket.process(cx, &ipv4_locals::HEADER_REPR, &ipv4_locals::PACKET_PAYLOAD); + assert_eq!(socket.peek(), Ok(&cksumd_packet[..])); + assert_eq!(socket.recv(), Ok(&cksumd_packet[..])); + assert_eq!(socket.peek(), Err(RecvError::Exhausted)); + } + #[cfg(feature = "proto-ipv6")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = ipv6_locals::socket(buffer(1), buffer(0)); + + assert_eq!(socket.peek(), Err(RecvError::Exhausted)); + assert!(socket.accepts(&ipv6_locals::HEADER_REPR)); + socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD); + + assert!(socket.accepts(&ipv6_locals::HEADER_REPR)); + socket.process(cx, &ipv6_locals::HEADER_REPR, &ipv6_locals::PACKET_PAYLOAD); + assert_eq!(socket.peek(), Ok(&ipv6_locals::PACKET_BYTES[..])); + assert_eq!(socket.recv(), Ok(&ipv6_locals::PACKET_BYTES[..])); + assert_eq!(socket.peek(), Err(RecvError::Exhausted)); + } + } + + #[test] + fn test_doesnt_accept_wrong_proto() { + #[cfg(feature = "proto-ipv4")] + { + let socket = Socket::new( + Some(IpVersion::Ipv4), + Some(IpProtocol::Unknown(ipv4_locals::IP_PROTO + 1)), + buffer(1), + buffer(1), + ); + assert!(!socket.accepts(&ipv4_locals::HEADER_REPR)); + #[cfg(feature = "proto-ipv6")] + assert!(!socket.accepts(&ipv6_locals::HEADER_REPR)); + } + #[cfg(feature = "proto-ipv6")] + { + let socket = Socket::new( + Some(IpVersion::Ipv6), + Some(IpProtocol::Unknown(ipv6_locals::IP_PROTO + 1)), + buffer(1), + buffer(1), + ); + assert!(!socket.accepts(&ipv6_locals::HEADER_REPR)); + #[cfg(feature = "proto-ipv4")] + assert!(!socket.accepts(&ipv4_locals::HEADER_REPR)); + } + } + + fn check_dispatch(socket: &mut Socket<'_>, cx: &mut Context) { + // Check dispatch returns Ok(()) and calls the emit closure + let mut emitted = false; + assert_eq!( + socket.dispatch(cx, |_, _| { + emitted = true; + Ok(()) + }), + Ok::<_, ()>(()) + ); + assert!(emitted); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_unfiltered_sends_all(#[case] medium: Medium) { + // Test a single unfiltered socket can send packets with different IP versions and next + // headers + let mut socket = Socket::new(None, None, buffer(0), buffer(2)); + #[cfg(feature = "proto-ipv4")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut udp_packet = ipv4_locals::PACKET_BYTES; + Ipv4Packet::new_unchecked(&mut udp_packet).set_next_header(IpProtocol::Udp); + + assert_eq!(socket.send_slice(&udp_packet), Ok(())); + check_dispatch(&mut socket, cx); + + let mut tcp_packet = ipv4_locals::PACKET_BYTES; + Ipv4Packet::new_unchecked(&mut tcp_packet).set_next_header(IpProtocol::Tcp); + + assert_eq!(socket.send_slice(&tcp_packet[..]), Ok(())); + check_dispatch(&mut socket, cx); + } + #[cfg(feature = "proto-ipv6")] + { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut udp_packet = ipv6_locals::PACKET_BYTES; + Ipv6Packet::new_unchecked(&mut udp_packet).set_next_header(IpProtocol::Udp); + + assert_eq!(socket.send_slice(&ipv6_locals::PACKET_BYTES), Ok(())); + check_dispatch(&mut socket, cx); + + let mut tcp_packet = ipv6_locals::PACKET_BYTES; + Ipv6Packet::new_unchecked(&mut tcp_packet).set_next_header(IpProtocol::Tcp); + + assert_eq!(socket.send_slice(&tcp_packet[..]), Ok(())); + check_dispatch(&mut socket, cx); + } + } + + #[rstest] + #[case::proto(IpProtocol::Icmp)] + #[case::proto(IpProtocol::Tcp)] + #[case::proto(IpProtocol::Udp)] + fn test_unfiltered_accepts_all(#[case] proto: IpProtocol) { + // Test an unfiltered socket can accept packets with different IP versions and next headers + let socket = Socket::new(None, None, buffer(0), buffer(0)); + #[cfg(feature = "proto-ipv4")] + { + let header_repr = IpRepr::Ipv4(Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 0, 1), + dst_addr: Ipv4Address::new(10, 0, 0, 2), + next_header: proto, + payload_len: 4, + hop_limit: 64, + }); + assert!(socket.accepts(&header_repr)); + } + #[cfg(feature = "proto-ipv6")] + { + let header_repr = IpRepr::Ipv6(Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + next_header: proto, + payload_len: 4, + hop_limit: 64, + }); + assert!(socket.accepts(&header_repr)); + } + } +} diff --git a/vendor/smoltcp/src/socket/tcp.rs b/vendor/smoltcp/src/socket/tcp.rs new file mode 100644 index 00000000..bb4a4617 --- /dev/null +++ b/vendor/smoltcp/src/socket/tcp.rs @@ -0,0 +1,9002 @@ +// Heads up! Before working on this file you should read, at least, RFC 793 and +// the parts of RFC 1122 that discuss TCP, as well as RFC 7323 for some of the TCP options. +// Consult RFC 7414 when implementing a new feature. + +use core::fmt::Display; +#[cfg(feature = "async")] +use core::task::Waker; +use core::{fmt, mem}; + +#[cfg(feature = "async")] +use crate::socket::WakerRegistration; +use crate::socket::{Context, PollAt}; +use crate::storage::{Assembler, RingBuffer}; +use crate::time::{Duration, Instant}; +use crate::wire::{ + IpAddress, IpEndpoint, IpListenEndpoint, IpProtocol, IpRepr, TCP_HEADER_LEN, TcpControl, + TcpRepr, TcpSeqNumber, TcpTimestampGenerator, TcpTimestampRepr, +}; + +mod congestion; + +macro_rules! tcp_trace { + ($($arg:expr),*) => (net_log!(trace, $($arg),*)); +} + +/// Error returned by [`Socket::listen`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ListenError { + InvalidState, + Unaddressable, +} + +impl Display for ListenError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ListenError::InvalidState => write!(f, "invalid state"), + ListenError::Unaddressable => write!(f, "unaddressable destination"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ListenError {} + +/// Error returned by [`Socket::connect`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConnectError { + InvalidState, + Unaddressable, +} + +impl Display for ConnectError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ConnectError::InvalidState => write!(f, "invalid state"), + ConnectError::Unaddressable => write!(f, "unaddressable destination"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ConnectError {} + +/// Error returned by [`Socket::send`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + InvalidState, +} + +impl Display for SendError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SendError::InvalidState => write!(f, "invalid state"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SendError {} + +/// Error returned by [`Socket::recv`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + InvalidState, + Finished, +} + +impl Display for RecvError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + RecvError::InvalidState => write!(f, "invalid state"), + RecvError::Finished => write!(f, "operation finished"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RecvError {} + +/// A TCP socket ring buffer. +pub type SocketBuffer<'a> = RingBuffer<'a, u8>; + +/// The state of a TCP socket, according to [RFC 793]. +/// +/// [RFC 793]: https://tools.ietf.org/html/rfc793 +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum State { + Closed, + Listen, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + Closing, + LastAck, + TimeWait, +} + +impl fmt::Display for State { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + State::Closed => write!(f, "CLOSED"), + State::Listen => write!(f, "LISTEN"), + State::SynSent => write!(f, "SYN-SENT"), + State::SynReceived => write!(f, "SYN-RECEIVED"), + State::Established => write!(f, "ESTABLISHED"), + State::FinWait1 => write!(f, "FIN-WAIT-1"), + State::FinWait2 => write!(f, "FIN-WAIT-2"), + State::CloseWait => write!(f, "CLOSE-WAIT"), + State::Closing => write!(f, "CLOSING"), + State::LastAck => write!(f, "LAST-ACK"), + State::TimeWait => write!(f, "TIME-WAIT"), + } + } +} + +/// RFC 6298: (2.1) Until a round-trip time (RTT) measurement has been made for a +/// segment sent between the sender and receiver, the sender SHOULD +/// set RTO <- 1 second, +const RTTE_INITIAL_RTO: u32 = 1000; + +// Minimum "safety margin" for the RTO that kicks in when the +// variance gets very low. +const RTTE_MIN_MARGIN: u32 = 5; + +/// K, according to RFC 6298 +const RTTE_K: u32 = 4; + +// RFC 6298 (2.4): Whenever RTO is computed, if it is less than 1 second, then the +// RTO SHOULD be rounded up to 1 second. +const RTTE_MIN_RTO: u32 = 1000; + +// RFC 6298 (2.5) A maximum value MAY be placed on RTO provided it is at least 60 +// seconds +const RTTE_MAX_RTO: u32 = 60_000; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct RttEstimator { + /// true if we have made at least one rtt measurement. + have_measurement: bool, + // Using u32 instead of Duration to save space (Duration is i64) + /// Smoothed RTT + srtt: u32, + /// RTT variance. + rttvar: u32, + /// Retransmission Time-Out + rto: u32, + timestamp: Option<(Instant, TcpSeqNumber)>, + max_seq_sent: Option, + rto_count: u8, +} + +impl Default for RttEstimator { + fn default() -> Self { + Self { + have_measurement: false, + srtt: 0, // ignored, will be overwritten on first measurement. + rttvar: 0, // ignored, will be overwritten on first measurement. + rto: RTTE_INITIAL_RTO, + timestamp: None, + max_seq_sent: None, + rto_count: 0, + } + } +} + +impl RttEstimator { + fn retransmission_timeout(&self) -> Duration { + Duration::from_millis(self.rto as _) + } + + fn sample(&mut self, new_rtt: u32) { + if self.have_measurement { + // RFC 6298 (2.3) When a subsequent RTT measurement R' is made, a host MUST set (...) + let diff = (self.srtt as i32 - new_rtt as i32).unsigned_abs(); + self.rttvar = (self.rttvar * 3 + diff).div_ceil(4); + self.srtt = (self.srtt * 7 + new_rtt).div_ceil(8); + } else { + // RFC 6298 (2.2) When the first RTT measurement R is made, the host MUST set (...) + self.have_measurement = true; + self.srtt = new_rtt; + self.rttvar = new_rtt / 2; + } + + // RFC 6298 (2.2), (2.3) + let margin = RTTE_MIN_MARGIN.max(self.rttvar * RTTE_K); + self.rto = (self.srtt + margin).clamp(RTTE_MIN_RTO, RTTE_MAX_RTO); + + self.rto_count = 0; + + tcp_trace!( + "rtte: sample={:?} srtt={:?} rttvar={:?} rto={:?}", + new_rtt, + self.srtt, + self.rttvar, + self.rto + ); + } + + fn on_send(&mut self, timestamp: Instant, seq: TcpSeqNumber) { + if self + .max_seq_sent + .map(|max_seq_sent| seq > max_seq_sent) + .unwrap_or(true) + { + self.max_seq_sent = Some(seq); + if self.timestamp.is_none() { + self.timestamp = Some((timestamp, seq)); + tcp_trace!("rtte: sampling at seq={:?}", seq); + } + } + } + + fn on_ack(&mut self, timestamp: Instant, seq: TcpSeqNumber) { + if let Some((sent_timestamp, sent_seq)) = self.timestamp { + if seq >= sent_seq { + self.sample((timestamp - sent_timestamp).total_millis() as u32); + self.timestamp = None; + } + } + } + + fn on_retransmit(&mut self) { + if self.timestamp.is_some() { + tcp_trace!("rtte: abort sampling due to retransmit"); + } + self.timestamp = None; + + // RFC 6298 (5.5) The host MUST set RTO <- RTO * 2 ("back off the timer"). The + // maximum value discussed in (2.5) above may be used to provide + // an upper bound to this doubling operation. + self.rto = (self.rto * 2).min(RTTE_MAX_RTO); + tcp_trace!("rtte: doubling rto to {:?}", self.rto); + + // RFC 6298: a TCP implementation MAY clear SRTT and RTTVAR after + // backing off the timer multiple times as it is likely that the current + // SRTT and RTTVAR are bogus in this situation. Once SRTT and RTTVAR + // are cleared, they should be initialized with the next RTT sample + // taken per (2.2) rather than using (2.3). + self.rto_count += 1; + if self.rto_count >= 3 { + self.rto_count = 0; + self.have_measurement = false; + tcp_trace!("rtte: too many retransmissions, clearing srtt, rttvar."); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Timer { + Idle { + keep_alive_at: Option, + }, + Retransmit { + expires_at: Instant, + }, + FastRetransmit, + ZeroWindowProbe { + expires_at: Instant, + delay: Duration, + }, + Close { + expires_at: Instant, + }, +} + +const ACK_DELAY_DEFAULT: Duration = Duration::from_millis(10); +const CLOSE_DELAY: Duration = Duration::from_millis(10_000); + +impl Timer { + fn new() -> Timer { + Timer::Idle { + keep_alive_at: None, + } + } + + fn should_keep_alive(&self, timestamp: Instant) -> bool { + match *self { + Timer::Idle { + keep_alive_at: Some(keep_alive_at), + } if timestamp >= keep_alive_at => true, + _ => false, + } + } + + fn should_retransmit(&self, timestamp: Instant) -> bool { + match *self { + Timer::Retransmit { expires_at } if timestamp >= expires_at => true, + Timer::FastRetransmit => true, + _ => false, + } + } + + fn should_close(&self, timestamp: Instant) -> bool { + match *self { + Timer::Close { expires_at } if timestamp >= expires_at => true, + _ => false, + } + } + + fn should_zero_window_probe(&self, timestamp: Instant) -> bool { + match *self { + Timer::ZeroWindowProbe { expires_at, .. } if timestamp >= expires_at => true, + _ => false, + } + } + + fn poll_at(&self) -> PollAt { + match *self { + Timer::Idle { + keep_alive_at: Some(keep_alive_at), + } => PollAt::Time(keep_alive_at), + Timer::Idle { + keep_alive_at: None, + } => PollAt::Ingress, + Timer::ZeroWindowProbe { expires_at, .. } => PollAt::Time(expires_at), + Timer::Retransmit { expires_at, .. } => PollAt::Time(expires_at), + Timer::FastRetransmit => PollAt::Now, + Timer::Close { expires_at } => PollAt::Time(expires_at), + } + } + + fn set_for_idle(&mut self, timestamp: Instant, interval: Option) { + *self = Timer::Idle { + keep_alive_at: interval.map(|interval| timestamp + interval), + } + } + + fn set_keep_alive(&mut self) { + if let Timer::Idle { keep_alive_at } = self { + if keep_alive_at.is_none() { + *keep_alive_at = Some(Instant::from_millis(0)) + } + } + } + + fn rewind_keep_alive(&mut self, timestamp: Instant, interval: Option) { + if let Timer::Idle { keep_alive_at } = self { + *keep_alive_at = interval.map(|interval| timestamp + interval) + } + } + + fn set_for_retransmit(&mut self, timestamp: Instant, delay: Duration) { + match *self { + Timer::Idle { .. } + | Timer::FastRetransmit + | Timer::Retransmit { .. } + | Timer::ZeroWindowProbe { .. } => { + *self = Timer::Retransmit { + expires_at: timestamp + delay, + } + } + Timer::Close { .. } => (), + } + } + + fn set_for_fast_retransmit(&mut self) { + *self = Timer::FastRetransmit + } + + fn set_for_close(&mut self, timestamp: Instant) { + *self = Timer::Close { + expires_at: timestamp + CLOSE_DELAY, + } + } + + fn set_for_zero_window_probe(&mut self, timestamp: Instant, delay: Duration) { + *self = Timer::ZeroWindowProbe { + expires_at: timestamp + delay, + delay, + } + } + + fn rewind_zero_window_probe(&mut self, timestamp: Instant) { + if let Timer::ZeroWindowProbe { mut delay, .. } = *self { + delay = (delay * 2).min(Duration::from_millis(RTTE_MAX_RTO as _)); + *self = Timer::ZeroWindowProbe { + expires_at: timestamp + delay, + delay, + } + } + } + + fn is_idle(&self) -> bool { + matches!(self, Timer::Idle { .. }) + } + + fn is_zero_window_probe(&self) -> bool { + matches!(self, Timer::ZeroWindowProbe { .. }) + } + + fn is_retransmit(&self) -> bool { + matches!(self, Timer::Retransmit { .. } | Timer::FastRetransmit) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum AckDelayTimer { + Idle, + Waiting(Instant), + Immediate, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct Tuple { + local: IpEndpoint, + remote: IpEndpoint, +} + +impl Display for Tuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.local, self.remote) + } +} + +/// A congestion control algorithm. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CongestionControl { + None, + + #[cfg(feature = "socket-tcp-reno")] + Reno, + + #[cfg(feature = "socket-tcp-cubic")] + Cubic, +} + +/// A Transmission Control Protocol socket. +/// +/// A TCP socket may passively listen for connections or actively connect to another endpoint. +/// Note that, for listening sockets, there is no "backlog"; to be able to simultaneously +/// accept several connections, as many sockets must be allocated, or any new connection +/// attempts will be reset. +#[derive(Debug)] +pub struct Socket<'a> { + state: State, + timer: Timer, + rtte: RttEstimator, + assembler: Assembler, + rx_buffer: SocketBuffer<'a>, + rx_fin_received: bool, + tx_buffer: SocketBuffer<'a>, + /// Interval after which, if no inbound packets are received, the connection is aborted. + timeout: Option, + /// Interval at which keep-alive packets will be sent. + keep_alive: Option, + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + hop_limit: Option, + /// Address passed to listen(). Listen address is set when listen() is called and + /// used every time the socket is reset back to the LISTEN state. + listen_endpoint: IpListenEndpoint, + /// Current 4-tuple (local and remote endpoints). + tuple: Option, + /// The sequence number corresponding to the beginning of the transmit buffer. + /// I.e. an ACK(local_seq_no+n) packet removes n bytes from the transmit buffer. + local_seq_no: TcpSeqNumber, + /// The sequence number corresponding to the beginning of the receive buffer. + /// I.e. userspace reading n bytes adds n to remote_seq_no. + remote_seq_no: TcpSeqNumber, + /// The last sequence number sent. + /// I.e. in an idle socket, local_seq_no+tx_buffer.len(). + remote_last_seq: TcpSeqNumber, + /// The last acknowledgement number sent. + /// I.e. in an idle socket, remote_seq_no+rx_buffer.len(). + remote_last_ack: Option, + /// The last window length sent. + remote_last_win: u16, + /// The sending window scaling factor advertised to remotes which support RFC 1323. + /// It is zero if the window <= 64KiB and/or the remote does not support it. + remote_win_shift: u8, + /// The remote window size, relative to local_seq_no + /// I.e. we're allowed to send octets until local_seq_no+remote_win_len + remote_win_len: usize, + /// The receive window scaling factor for remotes which support RFC 1323, None if unsupported. + remote_win_scale: Option, + /// Whether or not the remote supports selective ACK as described in RFC 2018. + remote_has_sack: bool, + /// The maximum number of data octets that the remote side may receive. + remote_mss: usize, + /// The timestamp of the last packet received. + remote_last_ts: Option, + /// The sequence number of the last packet received, used for sACK + local_rx_last_seq: Option, + /// The ACK number of the last packet received. + local_rx_last_ack: Option, + /// The number of packets received directly after + /// each other which have the same ACK number. + local_rx_dup_acks: u8, + + /// Duration for Delayed ACK. If None no ACKs will be delayed. + ack_delay: Option, + /// Delayed ack timer. If set, packets containing exclusively + /// ACK or window updates (ie, no data) won't be sent until expiry. + ack_delay_timer: AckDelayTimer, + + /// Used for rate-limiting: No more challenge ACKs will be sent until this instant. + challenge_ack_timer: Instant, + + /// Nagle's Algorithm enabled. + nagle: bool, + + /// The congestion control algorithm. + congestion_controller: congestion::AnyController, + + /// tsval generator - if some, tcp timestamp is enabled + tsval_generator: Option, + + /// 0 if not seen or timestamp not enabled + last_remote_tsval: u32, + + #[cfg(feature = "async")] + rx_waker: WakerRegistration, + #[cfg(feature = "async")] + tx_waker: WakerRegistration, + + /// If this is set, we will not send a SYN|ACK until this is unset. + #[cfg(feature = "socket-tcp-pause-synack")] + synack_paused: bool, +} + +const DEFAULT_MSS: usize = 536; + +impl<'a> Socket<'a> { + #[allow(unused_comparisons)] // small usize platforms always pass rx_capacity check + /// Create a socket using the given buffers. + pub fn new(rx_buffer: T, tx_buffer: T) -> Socket<'a> + where + T: Into>, + { + let (rx_buffer, tx_buffer) = (rx_buffer.into(), tx_buffer.into()); + let rx_capacity = rx_buffer.capacity(); + + // From RFC 1323: + // [...] the above constraints imply that 2 * the max window size must be less + // than 2**31 [...] Thus, the shift count must be limited to 14 (which allows + // windows of 2**30 = 1 Gbyte). + #[cfg(not(target_pointer_width = "16"))] // Prevent overflow + if rx_capacity > (1 << 30) { + panic!("receiving buffer too large, cannot exceed 1 GiB") + } + let rx_cap_log2 = mem::size_of::() * 8 - rx_capacity.leading_zeros() as usize; + + Socket { + state: State::Closed, + timer: Timer::new(), + rtte: RttEstimator::default(), + assembler: Assembler::new(), + tx_buffer, + rx_buffer, + rx_fin_received: false, + timeout: None, + keep_alive: None, + hop_limit: None, + listen_endpoint: IpListenEndpoint::default(), + tuple: None, + local_seq_no: TcpSeqNumber::default(), + remote_seq_no: TcpSeqNumber::default(), + remote_last_seq: TcpSeqNumber::default(), + remote_last_ack: None, + remote_last_win: 0, + remote_win_len: 0, + remote_win_shift: rx_cap_log2.saturating_sub(16) as u8, + remote_win_scale: None, + remote_has_sack: false, + remote_mss: DEFAULT_MSS, + remote_last_ts: None, + local_rx_last_ack: None, + local_rx_last_seq: None, + local_rx_dup_acks: 0, + ack_delay: Some(ACK_DELAY_DEFAULT), + ack_delay_timer: AckDelayTimer::Idle, + challenge_ack_timer: Instant::from_secs(0), + nagle: true, + tsval_generator: None, + last_remote_tsval: 0, + congestion_controller: congestion::AnyController::new(), + + #[cfg(feature = "async")] + rx_waker: WakerRegistration::new(), + #[cfg(feature = "async")] + tx_waker: WakerRegistration::new(), + + #[cfg(feature = "socket-tcp-pause-synack")] + synack_paused: false, + } + } + + /// Enable or disable TCP Timestamp. + pub fn set_tsval_generator(&mut self, generator: Option) { + self.tsval_generator = generator; + } + + /// Return whether TCP Timestamp is enabled. + pub fn timestamp_enabled(&self) -> bool { + self.tsval_generator.is_some() + } + + /// Set an algorithm for congestion control. + /// + /// `CongestionControl::None` indicates that no congestion control is applied. + /// Options `CongestionControl::Cubic` and `CongestionControl::Reno` are also available. + /// To use Reno and Cubic, please enable the `socket-tcp-reno` and `socket-tcp-cubic` features + /// in the `smoltcp` crate, respectively. + /// + /// `CongestionControl::Reno` is a classic congestion control algorithm valued for its simplicity. + /// Despite having a lower algorithmic complexity than `Cubic`, + /// it is less efficient in terms of bandwidth usage. + /// + /// `CongestionControl::Cubic` represents a modern congestion control algorithm designed to + /// be more efficient and fair compared to `CongestionControl::Reno`. + /// It is the default choice for Linux, Windows, and macOS. + /// `CongestionControl::Cubic` relies on double precision (`f64`) floating point operations, which may cause issues in some contexts: + /// * Small embedded processors (such as Cortex-M0, Cortex-M1, and Cortex-M3) do not have an FPU, and floating point operations consume significant amounts of CPU time and Flash space. + /// * Interrupt handlers should almost always avoid floating-point operations. + /// * Kernel-mode code on desktop processors usually avoids FPU operations to reduce the penalty of saving and restoring FPU registers. + /// + /// In all these cases, `CongestionControl::Reno` is a better choice of congestion control algorithm. + pub fn set_congestion_control(&mut self, congestion_control: CongestionControl) { + use congestion::*; + + self.congestion_controller = match congestion_control { + CongestionControl::None => AnyController::None(no_control::NoControl), + + #[cfg(feature = "socket-tcp-reno")] + CongestionControl::Reno => AnyController::Reno(reno::Reno::new()), + + #[cfg(feature = "socket-tcp-cubic")] + CongestionControl::Cubic => AnyController::Cubic(cubic::Cubic::new()), + } + } + + /// Return the current congestion control algorithm. + pub fn congestion_control(&self) -> CongestionControl { + use congestion::*; + + match self.congestion_controller { + AnyController::None(_) => CongestionControl::None, + + #[cfg(feature = "socket-tcp-reno")] + AnyController::Reno(_) => CongestionControl::Reno, + + #[cfg(feature = "socket-tcp-cubic")] + AnyController::Cubic(_) => CongestionControl::Cubic, + } + } + + /// Register a waker for receive operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `recv` method calls, such as receiving data, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_recv_waker(&mut self, waker: &Waker) { + self.rx_waker.register(waker) + } + + /// Register a waker for send operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `send` method calls, such as space becoming available in the transmit + /// buffer, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_send_waker(&mut self, waker: &Waker) { + self.tx_waker.register(waker) + } + + /// Return the timeout duration. + /// + /// See also the [set_timeout](#method.set_timeout) method. + pub fn timeout(&self) -> Option { + self.timeout + } + + /// Return the ACK delay duration. + /// + /// See also the [set_ack_delay](#method.set_ack_delay) method. + pub fn ack_delay(&self) -> Option { + self.ack_delay + } + + /// Return whether Nagle's Algorithm is enabled. + /// + /// See also the [set_nagle_enabled](#method.set_nagle_enabled) method. + pub fn nagle_enabled(&self) -> bool { + self.nagle + } + + /// Pause sending of SYN|ACK packets. + /// + /// When this flag is set, the socket will get stuck in `SynReceived` state without sending + /// any SYN|ACK packets back, until this flag is unset. This is useful for certain niche TCP + /// proxy usecases. + #[cfg(feature = "socket-tcp-pause-synack")] + pub fn pause_synack(&mut self, pause: bool) { + self.synack_paused = pause; + } + + /// Return the current window field value, including scaling according to RFC 1323. + /// + /// Used in internal calculations as well as packet generation. + #[inline] + fn scaled_window(&self) -> u16 { + u16::try_from(self.rx_buffer.window() >> self.remote_win_shift).unwrap_or(u16::MAX) + } + + /// Return the last window field value, including scaling according to RFC 1323. + /// + /// Used in internal calculations as well as packet generation. + /// + /// Unlike `remote_last_win`, we take into account new packets received (but not acknowledged) + /// since the last window update and adjust the window length accordingly. This ensures a fair + /// comparison between the last window length and the new window length we're going to + /// advertise. + #[inline] + fn last_scaled_window(&self) -> Option { + let last_ack = self.remote_last_ack?; + let next_ack = self.remote_seq_no + self.rx_buffer.len(); + + let last_win = (self.remote_last_win as usize) << self.remote_win_shift; + let last_win_adjusted = last_ack + last_win - next_ack; + + Some(u16::try_from(last_win_adjusted >> self.remote_win_shift).unwrap_or(u16::MAX)) + } + + /// Set the timeout duration. + /// + /// A socket with a timeout duration set will abort the connection if either of the following + /// occurs: + /// + /// * After a [connect](#method.connect) call, the remote endpoint does not respond within + /// the specified duration; + /// * After establishing a connection, there is data in the transmit buffer and the remote + /// endpoint exceeds the specified duration between any two packets it sends; + /// * After enabling [keep-alive](#method.set_keep_alive), the remote endpoint exceeds + /// the specified duration between any two packets it sends. + pub fn set_timeout(&mut self, duration: Option) { + self.timeout = duration + } + + /// Set the ACK delay duration. + /// + /// By default, the ACK delay is set to 10ms. + pub fn set_ack_delay(&mut self, duration: Option) { + self.ack_delay = duration + } + + /// Enable or disable Nagle's Algorithm. + /// + /// Also known as "tinygram prevention". By default, it is enabled. + /// Disabling it is equivalent to Linux's TCP_NODELAY flag. + /// + /// When enabled, Nagle's Algorithm prevents sending segments smaller than MSS if + /// there is data in flight (sent but not acknowledged). In other words, it ensures + /// at most only one segment smaller than MSS is in flight at a time. + /// + /// It ensures better network utilization by preventing sending many very small packets, + /// at the cost of increased latency in some situations, particularly when the remote peer + /// has ACK delay enabled. + pub fn set_nagle_enabled(&mut self, enabled: bool) { + self.nagle = enabled + } + + /// Return the keep-alive interval. + /// + /// See also the [set_keep_alive](#method.set_keep_alive) method. + pub fn keep_alive(&self) -> Option { + self.keep_alive + } + + /// Set the keep-alive interval. + /// + /// An idle socket with a keep-alive interval set will transmit a "keep-alive ACK" packet + /// every time it receives no communication during that interval. As a result, three things + /// may happen: + /// + /// * The remote endpoint is fine and answers with an ACK packet. + /// * The remote endpoint has rebooted and answers with an RST packet. + /// * The remote endpoint has crashed and does not answer. + /// + /// The keep-alive functionality together with the timeout functionality allows to react + /// to these error conditions. + pub fn set_keep_alive(&mut self, interval: Option) { + self.keep_alive = interval; + if self.keep_alive.is_some() { + // If the connection is idle and we've just set the option, it would not take effect + // until the next packet, unless we wind up the timer explicitly. + self.timer.set_keep_alive(); + } + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_hop_limit](#method.set_hop_limit) method + pub fn hop_limit(&self) -> Option { + self.hop_limit + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set hop limit value uses the default [IANA recommended] + /// value (64). + /// + /// # Panics + /// + /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_hop_limit(&mut self, hop_limit: Option) { + // A host MUST NOT send a datagram with a hop limit value of 0 + if let Some(0) = hop_limit { + panic!("the time-to-live value of a packet must not be zero") + } + + self.hop_limit = hop_limit + } + + /// Return the listen endpoint + #[inline] + pub fn listen_endpoint(&self) -> IpListenEndpoint { + self.listen_endpoint + } + + /// Return the local endpoint, or None if not connected. + #[inline] + pub fn local_endpoint(&self) -> Option { + Some(self.tuple?.local) + } + + /// Return the remote endpoint, or None if not connected. + #[inline] + pub fn remote_endpoint(&self) -> Option { + Some(self.tuple?.remote) + } + + /// Return the connection state, in terms of the TCP state machine. + #[inline] + pub fn state(&self) -> State { + self.state + } + + fn reset(&mut self) { + let rx_cap_log2 = + mem::size_of::() * 8 - self.rx_buffer.capacity().leading_zeros() as usize; + + self.state = State::Closed; + self.timer = Timer::new(); + self.rtte = RttEstimator::default(); + self.assembler = Assembler::new(); + self.tx_buffer.clear(); + self.rx_buffer.clear(); + self.rx_fin_received = false; + self.listen_endpoint = IpListenEndpoint::default(); + self.tuple = None; + self.local_seq_no = TcpSeqNumber::default(); + self.remote_seq_no = TcpSeqNumber::default(); + self.remote_last_seq = TcpSeqNumber::default(); + self.remote_last_ack = None; + self.remote_last_win = 0; + self.remote_win_len = 0; + self.remote_win_scale = None; + self.remote_win_shift = rx_cap_log2.saturating_sub(16) as u8; + self.remote_mss = DEFAULT_MSS; + self.remote_last_ts = None; + self.ack_delay_timer = AckDelayTimer::Idle; + self.challenge_ack_timer = Instant::from_secs(0); + + #[cfg(feature = "async")] + { + self.rx_waker.wake(); + self.tx_waker.wake(); + } + } + + /// Start listening on the given endpoint. + /// + /// This function returns `Err(Error::InvalidState)` if the socket was already open + /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)` + /// if the port in the given endpoint is zero. + pub fn listen(&mut self, local_endpoint: T) -> Result<(), ListenError> + where + T: Into, + { + let local_endpoint = local_endpoint.into(); + if local_endpoint.port == 0 { + return Err(ListenError::Unaddressable); + } + + if self.is_open() { + // If we were already listening to same endpoint there is nothing to do; exit early. + // + // In the past listening on an socket that was already listening was an error, + // however this makes writing an acceptor loop with multiple sockets impossible. + // Without this early exit, if you tried to listen on a socket that's already listening you'll + // immediately get an error. The only way around this is to abort the socket first + // before listening again, but this means that incoming connections can actually + // get aborted between the abort() and the next listen(). + if matches!(self.state, State::Listen) && self.listen_endpoint == local_endpoint { + return Ok(()); + } else { + return Err(ListenError::InvalidState); + } + } + + self.reset(); + self.listen_endpoint = local_endpoint; + self.tuple = None; + self.set_state(State::Listen); + Ok(()) + } + + /// Connect to a given endpoint. + /// + /// The local port must be provided explicitly. Assuming `fn get_ephemeral_port() -> u16` + /// allocates a port between 49152 and 65535, a connection may be established as follows: + /// + /// ```no_run + /// # #[cfg(all( + /// # feature = "medium-ethernet", + /// # feature = "proto-ipv4", + /// # ))] + /// # { + /// # use smoltcp::socket::tcp::{Socket, SocketBuffer}; + /// # use smoltcp::iface::Interface; + /// # use smoltcp::wire::IpAddress; + /// # + /// # fn get_ephemeral_port() -> u16 { + /// # 49152 + /// # } + /// # + /// # let mut socket = Socket::new( + /// # SocketBuffer::new(vec![0; 1200]), + /// # SocketBuffer::new(vec![0; 1200]) + /// # ); + /// # + /// # let mut iface: Interface = todo!(); + /// # + /// socket.connect( + /// iface.context(), + /// (IpAddress::v4(10, 0, 0, 1), 80), + /// get_ephemeral_port() + /// ).unwrap(); + /// # } + /// ``` + /// + /// The local address may optionally be provided. + /// + /// This function returns an error if the socket was open; see [is_open](#method.is_open). + /// It also returns an error if the local or remote port is zero, or if the remote address + /// is unspecified. + pub fn connect( + &mut self, + cx: &mut Context, + remote_endpoint: T, + local_endpoint: U, + ) -> Result<(), ConnectError> + where + T: Into, + U: Into, + { + let remote_endpoint: IpEndpoint = remote_endpoint.into(); + let local_endpoint: IpListenEndpoint = local_endpoint.into(); + + if self.is_open() { + return Err(ConnectError::InvalidState); + } + if remote_endpoint.port == 0 || remote_endpoint.addr.is_unspecified() { + return Err(ConnectError::Unaddressable); + } + if local_endpoint.port == 0 { + return Err(ConnectError::Unaddressable); + } + + // If local address is not provided, choose it automatically. + let local_endpoint = IpEndpoint { + addr: match local_endpoint.addr { + Some(addr) => { + if addr.is_unspecified() { + return Err(ConnectError::Unaddressable); + } + addr + } + None => cx + .get_source_address(&remote_endpoint.addr) + .ok_or(ConnectError::Unaddressable)?, + }, + port: local_endpoint.port, + }; + + if local_endpoint.addr.version() != remote_endpoint.addr.version() { + return Err(ConnectError::Unaddressable); + } + + self.reset(); + self.tuple = Some(Tuple { + local: local_endpoint, + remote: remote_endpoint, + }); + self.set_state(State::SynSent); + + let seq = Self::random_seq_no(cx); + self.local_seq_no = seq; + self.remote_last_seq = seq; + Ok(()) + } + + #[cfg(test)] + fn random_seq_no(_cx: &mut Context) -> TcpSeqNumber { + TcpSeqNumber(10000) + } + + #[cfg(not(test))] + fn random_seq_no(cx: &mut Context) -> TcpSeqNumber { + TcpSeqNumber(cx.rand().rand_u32() as i32) + } + + /// Close the transmit half of the full-duplex connection. + /// + /// Note that there is no corresponding function for the receive half of the full-duplex + /// connection; only the remote end can close it. If you no longer wish to receive any + /// data and would like to reuse the socket right away, use [abort](#method.abort). + pub fn close(&mut self) { + match self.state { + // In the LISTEN state there is no established connection. + State::Listen => self.set_state(State::Closed), + // In the SYN-SENT state the remote endpoint is not yet synchronized and, upon + // receiving an RST, will abort the connection. + State::SynSent => self.set_state(State::Closed), + // In the SYN-RECEIVED, ESTABLISHED and CLOSE-WAIT states the transmit half + // of the connection is open, and needs to be explicitly closed with a FIN. + State::SynReceived | State::Established => self.set_state(State::FinWait1), + State::CloseWait => self.set_state(State::LastAck), + // In the FIN-WAIT-1, FIN-WAIT-2, CLOSING, LAST-ACK, TIME-WAIT and CLOSED states, + // the transmit half of the connection is already closed, and no further + // action is needed. + State::FinWait1 + | State::FinWait2 + | State::Closing + | State::TimeWait + | State::LastAck + | State::Closed => (), + } + } + + /// Aborts the connection, if any. + /// + /// This function instantly closes the socket. One reset packet will be sent to the remote + /// endpoint. + /// + /// In terms of the TCP state machine, the socket may be in any state and is moved to + /// the `CLOSED` state. + pub fn abort(&mut self) { + self.set_state(State::Closed); + } + + /// Return whether the socket is passively listening for incoming connections. + /// + /// In terms of the TCP state machine, the socket must be in the `LISTEN` state. + #[inline] + pub fn is_listening(&self) -> bool { + match self.state { + State::Listen => true, + _ => false, + } + } + + /// Return whether the socket is open. + /// + /// This function returns true if the socket will process incoming or dispatch outgoing + /// packets. Note that this does not mean that it is possible to send or receive data through + /// the socket; for that, use [can_send](#method.can_send) or [can_recv](#method.can_recv). + /// + /// In terms of the TCP state machine, the socket must not be in the `CLOSED` + /// or `TIME-WAIT` states. + #[inline] + pub fn is_open(&self) -> bool { + match self.state { + State::Closed => false, + State::TimeWait => false, + _ => true, + } + } + + /// Return whether a connection is active. + /// + /// This function returns true if the socket is actively exchanging packets with + /// a remote endpoint. Note that this does not mean that it is possible to send or receive + /// data through the socket; for that, use [can_send](#method.can_send) or + /// [can_recv](#method.can_recv). + /// + /// If a connection is established, [abort](#method.close) will send a reset to + /// the remote endpoint. + /// + /// In terms of the TCP state machine, the socket must not be in the `CLOSED`, `TIME-WAIT`, + /// or `LISTEN` state. + #[inline] + pub fn is_active(&self) -> bool { + match self.state { + State::Closed => false, + State::TimeWait => false, + State::Listen => false, + _ => true, + } + } + + /// Return whether the transmit half of the full-duplex connection is open. + /// + /// This function returns true if it's possible to send data and have it arrive + /// to the remote endpoint. However, it does not make any guarantees about the state + /// of the transmit buffer, and even if it returns true, [send](#method.send) may + /// not be able to enqueue any octets. + /// + /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or + /// `CLOSE-WAIT` state. + #[inline] + pub fn may_send(&self) -> bool { + match self.state { + State::Established => true, + // In CLOSE-WAIT, the remote endpoint has closed our receive half of the connection + // but we still can transmit indefinitely. + State::CloseWait => true, + _ => false, + } + } + + /// Return whether the receive half of the full-duplex connection is open. + /// + /// This function returns true if it's possible to receive data from the remote endpoint. + /// It will return true while there is data in the receive buffer, and if there isn't, + /// as long as the remote endpoint has not closed the connection. + /// + /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED`, + /// `FIN-WAIT-1`, or `FIN-WAIT-2` state, or have data in the receive buffer instead. + #[inline] + pub fn may_recv(&self) -> bool { + match self.state { + State::Established => true, + // In FIN-WAIT-1/2, we have closed our transmit half of the connection but + // we still can receive indefinitely. + State::FinWait1 | State::FinWait2 => true, + // If we have something in the receive buffer, we can receive that. + _ if self.can_recv() => true, + _ => false, + } + } + + /// Check whether the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + #[inline] + pub fn can_send(&self) -> bool { + if !self.may_send() { + return false; + } + + !self.tx_buffer.is_full() + } + + /// Return the maximum number of bytes inside the recv buffer. + #[inline] + pub fn recv_capacity(&self) -> usize { + self.rx_buffer.capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + #[inline] + pub fn send_capacity(&self) -> usize { + self.tx_buffer.capacity() + } + + /// Check whether the receive buffer is not empty. + #[inline] + pub fn can_recv(&self) -> bool { + !self.rx_buffer.is_empty() + } + + fn send_impl<'b, F, R>(&'b mut self, f: F) -> Result + where + F: FnOnce(&'b mut SocketBuffer<'a>) -> (usize, R), + { + if !self.may_send() { + return Err(SendError::InvalidState); + } + + let old_length = self.tx_buffer.len(); + let (size, result) = f(&mut self.tx_buffer); + if size > 0 { + // The connection might have been idle for a long time, and so remote_last_ts + // would be far in the past. Unless we clear it here, we'll abort the connection + // down over in dispatch() by erroneously detecting it as timed out. + if old_length == 0 { + self.remote_last_ts = None + } + + // if remote win is zero and we go from having no data to some data pending to + // send, start the zero window probe timer. + if self.remote_win_len == 0 && self.timer.is_idle() { + let delay = self.rtte.retransmission_timeout(); + tcp_trace!("starting zero-window-probe timer for t+{}", delay); + + // We don't have access to the current time here, so use Instant::ZERO instead. + // this will cause the first ZWP to be sent immediately, but that's okay. + self.timer.set_for_zero_window_probe(Instant::ZERO, delay); + } + + #[cfg(any(test, feature = "verbose"))] + tcp_trace!( + "tx buffer: enqueueing {} octets (now {})", + size, + old_length + size + ); + } + Ok(result) + } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// This function returns `Err(Error::Illegal)` if the transmit half of + /// the connection is not open; see [may_send](#method.may_send). + pub fn send<'b, F, R>(&'b mut self, f: F) -> Result + where + F: FnOnce(&'b mut [u8]) -> (usize, R), + { + self.send_impl(|tx_buffer| tx_buffer.enqueue_many_with(f)) + } + + /// Enqueue a sequence of octets to be sent, and fill it from a slice. + /// + /// This function returns the amount of octets actually enqueued, which is limited + /// by the amount of free space in the transmit buffer; down to zero. + /// + /// See also [send](#method.send). + pub fn send_slice(&mut self, data: &[u8]) -> Result { + self.send_impl(|tx_buffer| { + let size = tx_buffer.enqueue_slice(data); + (size, size) + }) + } + + fn recv_error_check(&mut self) -> Result<(), RecvError> { + // We may have received some data inside the initial SYN, but until the connection + // is fully open we must not dequeue any data, as it may be overwritten by e.g. + // another (stale) SYN. (We do not support TCP Fast Open.) + if !self.may_recv() { + if self.rx_fin_received { + return Err(RecvError::Finished); + } + return Err(RecvError::InvalidState); + } + + Ok(()) + } + + fn recv_impl<'b, F, R>(&'b mut self, f: F) -> Result + where + F: FnOnce(&'b mut SocketBuffer<'a>) -> (usize, R), + { + self.recv_error_check()?; + + let _old_length = self.rx_buffer.len(); + let (size, result) = f(&mut self.rx_buffer); + self.remote_seq_no += size; + if size > 0 { + #[cfg(any(test, feature = "verbose"))] + tcp_trace!( + "rx buffer: dequeueing {} octets (now {})", + size, + _old_length - size + ); + } + Ok(result) + } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// This function errors if the receive half of the connection is not open. + /// + /// If the receive half has been gracefully closed (with a FIN packet), `Err(Error::Finished)` + /// is returned. In this case, the previously received data is guaranteed to be complete. + /// + /// In all other cases, `Err(Error::Illegal)` is returned and previously received data (if any) + /// may be incomplete (truncated). + pub fn recv<'b, F, R>(&'b mut self, f: F) -> Result + where + F: FnOnce(&'b mut [u8]) -> (usize, R), + { + self.recv_impl(|rx_buffer| rx_buffer.dequeue_many_with(f)) + } + + /// Dequeue a sequence of received octets, and fill a slice from it. + /// + /// This function returns the amount of octets actually dequeued, which is limited + /// by the amount of occupied space in the receive buffer; down to zero. + /// + /// See also [recv](#method.recv). + pub fn recv_slice(&mut self, data: &mut [u8]) -> Result { + self.recv_impl(|rx_buffer| { + let size = rx_buffer.dequeue_slice(data); + (size, size) + }) + } + + /// Peek at a sequence of received octets without removing them from + /// the receive buffer, and return a pointer to it. + /// + /// This function otherwise behaves identically to [recv](#method.recv). + pub fn peek(&mut self, size: usize) -> Result<&[u8], RecvError> { + self.recv_error_check()?; + + let buffer = self.rx_buffer.get_allocated(0, size); + if !buffer.is_empty() { + #[cfg(any(test, feature = "verbose"))] + tcp_trace!("rx buffer: peeking at {} octets", buffer.len()); + } + Ok(buffer) + } + + /// Peek at a sequence of received octets without removing them from + /// the receive buffer, and fill a slice from it. + /// + /// This function otherwise behaves identically to [recv_slice](#method.recv_slice). + pub fn peek_slice(&mut self, data: &mut [u8]) -> Result { + Ok(self.rx_buffer.read_allocated(0, data)) + } + + /// Return the amount of octets queued in the transmit buffer. + /// + /// Note that the Berkeley sockets interface does not have an equivalent of this API. + pub fn send_queue(&self) -> usize { + self.tx_buffer.len() + } + + /// Return the amount of octets queued in the receive buffer. This value can be larger than + /// the slice read by the next `recv` or `peek` call because it includes all queued octets, + /// and not only the octets that may be returned as a contiguous slice. + /// + /// Note that the Berkeley sockets interface does not have an equivalent of this API. + pub fn recv_queue(&self) -> usize { + self.rx_buffer.len() + } + + fn set_state(&mut self, state: State) { + if self.state != state { + tcp_trace!("state={}=>{}", self.state, state); + } + + self.state = state; + + #[cfg(feature = "async")] + { + // Wake all tasks waiting. Even if we haven't received/sent data, this + // is needed because return values of functions may change depending on the state. + // For example, a pending read has to fail with an error if the socket is closed. + self.rx_waker.wake(); + self.tx_waker.wake(); + } + } + + pub(crate) fn reply(ip_repr: &IpRepr, repr: &TcpRepr) -> (IpRepr, TcpRepr<'static>) { + let reply_repr = TcpRepr { + src_port: repr.dst_port, + dst_port: repr.src_port, + control: TcpControl::None, + seq_number: TcpSeqNumber(0), + ack_number: None, + window_len: 0, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &[], + }; + let ip_reply_repr = IpRepr::new( + ip_repr.dst_addr(), + ip_repr.src_addr(), + IpProtocol::Tcp, + reply_repr.buffer_len(), + 64, + ); + (ip_reply_repr, reply_repr) + } + + pub(crate) fn rst_reply(ip_repr: &IpRepr, repr: &TcpRepr) -> (IpRepr, TcpRepr<'static>) { + debug_assert!(repr.control != TcpControl::Rst); + + let (ip_reply_repr, mut reply_repr) = Self::reply(ip_repr, repr); + + // See https://www.snellman.net/blog/archive/2016-02-01-tcp-rst/ for explanation + // of why we sometimes send an RST and sometimes an RST|ACK + reply_repr.control = TcpControl::Rst; + reply_repr.seq_number = repr.ack_number.unwrap_or_default(); + if repr.control == TcpControl::Syn && repr.ack_number.is_none() { + reply_repr.ack_number = Some(repr.seq_number + repr.segment_len()); + } + + (ip_reply_repr, reply_repr) + } + + fn ack_reply(&mut self, ip_repr: &IpRepr, repr: &TcpRepr) -> (IpRepr, TcpRepr<'static>) { + let (mut ip_reply_repr, mut reply_repr) = Self::reply(ip_repr, repr); + reply_repr.timestamp = repr + .timestamp + .and_then(|tcp_ts| tcp_ts.generate_reply(self.tsval_generator)); + + // From RFC 793: + // [...] an empty acknowledgment segment containing the current send-sequence number + // and an acknowledgment indicating the next sequence number expected + // to be received. + reply_repr.seq_number = self.remote_last_seq; + reply_repr.ack_number = Some(self.remote_seq_no + self.rx_buffer.len()); + self.remote_last_ack = reply_repr.ack_number; + + // From RFC 1323: + // The window field [...] of every outgoing segment, with the exception of SYN + // segments, is right-shifted by [advertised scale value] bits[...] + reply_repr.window_len = self.scaled_window(); + self.remote_last_win = reply_repr.window_len; + + // If the remote supports selective acknowledgement, add the option to the outgoing + // segment. + if self.remote_has_sack { + net_debug!("sending sACK option with current assembler ranges"); + + // RFC 2018: The first SACK block (i.e., the one immediately following the kind and + // length fields in the option) MUST specify the contiguous block of data containing + // the segment which triggered this ACK, unless that segment advanced the + // Acknowledgment Number field in the header. + reply_repr.sack_ranges[0] = None; + + let ack = reply_repr.ack_number.unwrap_or(TcpSeqNumber(0)); + + if let Some(last_seg_seq) = self.local_rx_last_seq { + reply_repr.sack_ranges[0] = self + .assembler + .iter_data() + .map(|(left, right)| (ack + left, ack + right)) + .find(|&(left, right)| left <= last_seg_seq && right >= last_seg_seq) + .map(|(left, right)| (left.0 as u32, right.0 as u32)); + } + + if reply_repr.sack_ranges[0].is_none() { + // The matching segment was removed from the assembler, meaning the acknowledgement + // number has advanced, or there was no previous sACK. + // + // While the RFC says we SHOULD keep a list of reported sACK ranges, and iterate + // through those, that is currently infeasible. Instead, we offer the range with + // the lowest sequence number (if one exists) to hint at what segments would + // most quickly advance the acknowledgement number. + reply_repr.sack_ranges[0] = self + .assembler + .iter_data() + .map(|(left, right)| (ack + left, ack + right)) + .next() + .map(|(left, right)| (left.0 as u32, right.0 as u32)); + } + } + + // Since the sACK option may have changed the length of the payload, update that. + ip_reply_repr.set_payload_len(reply_repr.buffer_len()); + (ip_reply_repr, reply_repr) + } + + fn challenge_ack_reply( + &mut self, + cx: &mut Context, + ip_repr: &IpRepr, + repr: &TcpRepr, + ) -> Option<(IpRepr, TcpRepr<'static>)> { + if cx.now() < self.challenge_ack_timer { + return None; + } + + // Rate-limit to 1 per second max. + self.challenge_ack_timer = cx.now() + Duration::from_secs(1); + + Some(self.ack_reply(ip_repr, repr)) + } + + pub(crate) fn accepts(&self, _cx: &mut Context, ip_repr: &IpRepr, repr: &TcpRepr) -> bool { + if self.state == State::Closed { + return false; + } + + // If we're still listening for SYNs and the packet has an ACK or a RST, + // it cannot be destined to this socket, but another one may well listen + // on the same local endpoint. + if self.state == State::Listen + && (repr.ack_number.is_some() || repr.control == TcpControl::Rst) + { + return false; + } + + if let Some(tuple) = &self.tuple { + // Reject packets not matching the 4-tuple + ip_repr.dst_addr() == tuple.local.addr + && repr.dst_port == tuple.local.port + && ip_repr.src_addr() == tuple.remote.addr + && repr.src_port == tuple.remote.port + } else { + // We're listening, reject packets not matching the listen endpoint. + let addr_ok = match self.listen_endpoint.addr { + Some(addr) => ip_repr.dst_addr() == addr, + None => true, + }; + addr_ok && repr.dst_port != 0 && repr.dst_port == self.listen_endpoint.port + } + } + + pub(crate) fn process( + &mut self, + cx: &mut Context, + ip_repr: &IpRepr, + repr: &TcpRepr, + ) -> Option<(IpRepr, TcpRepr<'static>)> { + debug_assert!(self.accepts(cx, ip_repr, repr)); + + // Consider how much the sequence number space differs from the transmit buffer space. + let (sent_syn, sent_fin) = match self.state { + // In SYN-SENT or SYN-RECEIVED, we've just sent a SYN. + State::SynSent | State::SynReceived => (true, false), + // In FIN-WAIT-1, LAST-ACK, or CLOSING, we've just sent a FIN. + State::FinWait1 | State::LastAck | State::Closing => (false, true), + // In all other states we've already got acknowledgements for + // all of the control flags we sent. + _ => (false, false), + }; + let control_len = (sent_syn as usize) + (sent_fin as usize); + + // Reject unacceptable acknowledgements. + match (self.state, repr.control, repr.ack_number) { + // An RST received in response to initial SYN is acceptable if it acknowledges + // the initial SYN. + (State::SynSent, TcpControl::Rst, None) => { + net_debug!("unacceptable RST (expecting RST|ACK) in response to initial SYN"); + return None; + } + (State::SynSent, TcpControl::Rst, Some(ack_number)) => { + if ack_number != self.local_seq_no + 1 { + net_debug!("unacceptable RST|ACK in response to initial SYN"); + return None; + } + } + // Any other RST need only have a valid sequence number. + (_, TcpControl::Rst, _) => (), + // The initial SYN cannot contain an acknowledgement. + (State::Listen, _, None) => (), + // This case is handled in `accepts()`. + (State::Listen, _, Some(_)) => unreachable!(), + // SYN|ACK in the SYN-SENT state must have the exact ACK number. + (State::SynSent, TcpControl::Syn, Some(ack_number)) => { + if ack_number != self.local_seq_no + 1 { + net_debug!("unacceptable SYN|ACK in response to initial SYN"); + return Some(Self::rst_reply(ip_repr, repr)); + } + } + // TCP simultaneous open. + // This is required by RFC 9293, which states "A TCP implementation MUST support + // simultaneous open attempts (MUST-10)." + (State::SynSent, TcpControl::Syn, None) => (), + // ACKs in the SYN-SENT state are invalid. + (State::SynSent, TcpControl::None, Some(ack_number)) => { + // If the sequence number matches, ignore it instead of RSTing. + // I'm not sure why, I think it may be a workaround for broken TCP + // servers, or a defense against reordering. Either way, if Linux + // does it, we do too. + if ack_number == self.local_seq_no + 1 { + net_debug!( + "expecting a SYN|ACK, received an ACK with the right ack_number, ignoring." + ); + return None; + } + + net_debug!( + "expecting a SYN|ACK, received an ACK with the wrong ack_number, sending RST." + ); + return Some(Self::rst_reply(ip_repr, repr)); + } + // Anything else in the SYN-SENT state is invalid. + (State::SynSent, _, _) => { + net_debug!("expecting a SYN|ACK"); + return None; + } + // Every packet after the initial SYN must be an acknowledgement. + (_, _, None) => { + net_debug!("expecting an ACK"); + return None; + } + // ACK in the SYN-RECEIVED state must have the exact ACK number, or we RST it. + (State::SynReceived, _, Some(ack_number)) => { + if ack_number != self.local_seq_no + 1 { + net_debug!("unacceptable ACK in response to SYN|ACK"); + return Some(Self::rst_reply(ip_repr, repr)); + } + } + // Every acknowledgement must be for transmitted but unacknowledged data. + (_, _, Some(ack_number)) => { + let unacknowledged = self.tx_buffer.len() + control_len; + + // Acceptable ACK range (both inclusive) + let mut ack_min = self.local_seq_no; + let ack_max = self.local_seq_no + unacknowledged; + + // If we have sent a SYN, it MUST be acknowledged. + if sent_syn { + ack_min += 1; + } + + if ack_number < ack_min { + net_debug!( + "duplicate ACK ({} not in {}...{})", + ack_number, + ack_min, + ack_max + ); + return None; + } + + if ack_number > ack_max { + net_debug!( + "unacceptable ACK ({} not in {}...{})", + ack_number, + ack_min, + ack_max + ); + return self.challenge_ack_reply(cx, ip_repr, repr); + } + } + } + + let window_start = self.remote_seq_no + self.rx_buffer.len(); + let window_end = if let Some(last_ack) = self.remote_last_ack { + last_ack + ((self.remote_last_win as usize) << self.remote_win_shift) + } else { + window_start + }; + let segment_start = repr.seq_number; + let segment_end = repr.seq_number + repr.payload.len(); + + let (payload, payload_offset) = match self.state { + // In LISTEN and SYN-SENT states, we have not yet synchronized with the remote end. + State::Listen | State::SynSent => (&[][..], 0), + _ => { + // https://www.rfc-editor.org/rfc/rfc9293.html#name-segment-acceptability-tests + let segment_in_window = match ( + segment_start == segment_end, + window_start == window_end, + ) { + (true, _) if segment_end == window_start - 1 => { + net_debug!( + "received a keep-alive or window probe packet, will send an ACK" + ); + false + } + (true, true) => { + if window_start == segment_start { + true + } else { + net_debug!( + "zero-length segment not inside zero-length window, will send an ACK." + ); + false + } + } + (true, false) => { + if window_start <= segment_start && segment_start < window_end { + true + } else { + net_debug!("zero-length segment not inside window, will send an ACK."); + false + } + } + (false, true) => { + net_debug!( + "non-zero-length segment with zero receive window, will only send an ACK" + ); + false + } + (false, false) => { + if (window_start <= segment_start && segment_start < window_end) + || (window_start < segment_end && segment_end <= window_end) + { + true + } else { + net_debug!( + "segment not in receive window ({}..{} not intersecting {}..{}), will send challenge ACK", + segment_start, + segment_end, + window_start, + window_end + ); + false + } + } + }; + + if segment_in_window { + let overlap_start = window_start.max(segment_start); + let overlap_end = window_end.min(segment_end); + + // the checks done above imply this. + debug_assert!(overlap_start <= overlap_end); + + self.local_rx_last_seq = Some(repr.seq_number); + + ( + &repr.payload[overlap_start - segment_start..overlap_end - segment_start], + overlap_start - window_start, + ) + } else { + // If we're in the TIME-WAIT state, restart the TIME-WAIT timeout, since + // the remote end may not have realized we've closed the connection. + if self.state == State::TimeWait { + self.timer.set_for_close(cx.now()); + } + + return self.challenge_ack_reply(cx, ip_repr, repr); + } + } + }; + + // Compute the amount of acknowledged octets, removing the SYN and FIN bits + // from the sequence space. + let mut ack_len = 0; + let mut ack_of_fin = false; + let mut ack_all = false; + if repr.control != TcpControl::Rst { + if let Some(ack_number) = repr.ack_number { + // Sequence number corresponding to the first byte in `tx_buffer`. + // This normally equals `local_seq_no`, but is 1 higher if we have sent a SYN, + // as the SYN occupies 1 sequence number "before" the data. + let tx_buffer_start_seq = self.local_seq_no + (sent_syn as usize); + + if ack_number >= tx_buffer_start_seq { + ack_len = ack_number - tx_buffer_start_seq; + + // We could've sent data before the FIN, so only remove FIN from the sequence + // space if all of that data is acknowledged. + if sent_fin && self.tx_buffer.len() + 1 == ack_len { + ack_len -= 1; + tcp_trace!("received ACK of FIN"); + ack_of_fin = true; + } + + ack_all = self.remote_last_seq <= ack_number; + } + + self.rtte.on_ack(cx.now(), ack_number); + self.congestion_controller + .inner_mut() + .on_ack(cx.now(), ack_len, &self.rtte); + } + } + + // Disregard control flags we don't care about or shouldn't act on yet. + let mut control = repr.control; + control = control.quash_psh(); + + // If a FIN is received at the end of the current segment, but + // we have a hole in the assembler before the current segment, disregard this FIN. + if control == TcpControl::Fin && window_start < segment_start { + tcp_trace!( + "ignoring FIN because we don't have full data yet. window_start={} segment_start={}", + window_start, + segment_start + ); + control = TcpControl::None; + } + + // Validate and update the state. + match (self.state, control) { + // RSTs are not accepted in the LISTEN state. + (State::Listen, TcpControl::Rst) => return None, + + // RSTs in SYN-RECEIVED flip the socket back to the LISTEN state. + // Here we need to additionally check `listen_endpoint`, because we want to make sure + // that SYN-RECEIVED was actually converted from the LISTEN state (another possible + // reason is TCP simultaneous open). + (State::SynReceived, TcpControl::Rst) if self.listen_endpoint.port != 0 => { + tcp_trace!("received RST"); + self.tuple = None; + self.set_state(State::Listen); + return None; + } + + // RSTs in any other state close the socket. + (_, TcpControl::Rst) => { + tcp_trace!("received RST"); + self.set_state(State::Closed); + self.tuple = None; + return None; + } + + // SYN packets in the LISTEN state change it to SYN-RECEIVED. + (State::Listen, TcpControl::Syn) => { + tcp_trace!("received SYN"); + if let Some(max_seg_size) = repr.max_seg_size { + if max_seg_size == 0 { + tcp_trace!("received SYNACK with zero MSS, ignoring"); + return None; + } + self.congestion_controller + .inner_mut() + .set_mss(max_seg_size as usize); + self.remote_mss = max_seg_size as usize + } + + self.tuple = Some(Tuple { + local: IpEndpoint::new(ip_repr.dst_addr(), repr.dst_port), + remote: IpEndpoint::new(ip_repr.src_addr(), repr.src_port), + }); + self.local_seq_no = Self::random_seq_no(cx); + self.remote_seq_no = repr.seq_number + 1; + self.remote_last_seq = self.local_seq_no; + self.remote_has_sack = repr.sack_permitted; + self.remote_win_scale = repr.window_scale; + // Remote doesn't support window scaling, don't do it. + if self.remote_win_scale.is_none() { + self.remote_win_shift = 0; + } + // Remote doesn't support timestamping, don't do it. + if repr.timestamp.is_none() { + self.tsval_generator = None; + } + self.set_state(State::SynReceived); + self.timer.set_for_idle(cx.now(), self.keep_alive); + } + + // ACK packets in the SYN-RECEIVED state change it to ESTABLISHED. + (State::SynReceived, TcpControl::None) => { + self.set_state(State::Established); + } + + // FIN packets in the SYN-RECEIVED state change it to CLOSE-WAIT. + // It's not obvious from RFC 793 that this is permitted, but + // 7th and 8th steps in the "SEGMENT ARRIVES" event describe this behavior. + (State::SynReceived, TcpControl::Fin) => { + self.remote_seq_no += 1; + self.rx_fin_received = true; + self.set_state(State::CloseWait); + } + + // SYN|ACK packets in the SYN-SENT state change it to ESTABLISHED. + // SYN packets in the SYN-SENT state change it to SYN-RECEIVED. + (State::SynSent, TcpControl::Syn) => { + if repr.ack_number.is_some() { + tcp_trace!("received SYN|ACK"); + } else { + tcp_trace!("received SYN"); + } + if let Some(max_seg_size) = repr.max_seg_size { + if max_seg_size == 0 { + tcp_trace!("received SYNACK with zero MSS, ignoring"); + return None; + } + self.remote_mss = max_seg_size as usize; + self.congestion_controller + .inner_mut() + .set_mss(self.remote_mss); + } + + self.remote_seq_no = repr.seq_number + 1; + self.remote_last_seq = self.local_seq_no + 1; + self.remote_last_ack = Some(repr.seq_number); + self.remote_has_sack = repr.sack_permitted; + self.remote_win_scale = repr.window_scale; + // Remote doesn't support window scaling, don't do it. + if self.remote_win_scale.is_none() { + self.remote_win_shift = 0; + } + // Remote doesn't support timestamping, don't do it. + if repr.timestamp.is_none() { + self.tsval_generator = None; + } + + if repr.ack_number.is_some() { + self.set_state(State::Established); + } else { + self.set_state(State::SynReceived); + } + } + + (State::Established, TcpControl::None) => {} + + // FIN packets in ESTABLISHED state indicate the remote side has closed. + (State::Established, TcpControl::Fin) => { + self.remote_seq_no += 1; + self.rx_fin_received = true; + self.set_state(State::CloseWait); + } + + // ACK packets in FIN-WAIT-1 state change it to FIN-WAIT-2, if we've already + // sent everything in the transmit buffer. If not, they reset the retransmit timer. + (State::FinWait1, TcpControl::None) => { + if ack_of_fin { + self.set_state(State::FinWait2); + } + } + + // FIN packets in FIN-WAIT-1 state change it to CLOSING, or to TIME-WAIT + // if they also acknowledge our FIN. + (State::FinWait1, TcpControl::Fin) => { + self.remote_seq_no += 1; + self.rx_fin_received = true; + if ack_of_fin { + self.set_state(State::TimeWait); + self.timer.set_for_close(cx.now()); + } else { + self.set_state(State::Closing); + } + } + + (State::FinWait2, TcpControl::None) => {} + + // FIN packets in FIN-WAIT-2 state change it to TIME-WAIT. + (State::FinWait2, TcpControl::Fin) => { + self.remote_seq_no += 1; + self.rx_fin_received = true; + self.set_state(State::TimeWait); + self.timer.set_for_close(cx.now()); + } + + // ACK packets in CLOSING state change it to TIME-WAIT. + (State::Closing, TcpControl::None) => { + if ack_of_fin { + self.set_state(State::TimeWait); + self.timer.set_for_close(cx.now()); + } + } + + (State::CloseWait, TcpControl::None) => {} + + // ACK packets in LAST-ACK state change it to CLOSED. + (State::LastAck, TcpControl::None) => { + if ack_of_fin { + // Clear the remote endpoint, or we'll send an RST there. + self.set_state(State::Closed); + self.tuple = None; + } else if ack_len == 0 { + // Duplicate ACK; our FIN has not been acknowledged. + // Per RFC 9293 (3.10.7.4), send a challenge ACK. + return self.challenge_ack_reply(cx, ip_repr, repr); + } + // Partial ACK: fall through to advance SND.UNA normally. + } + + _ => { + net_debug!("unexpected packet {}", repr); + return None; + } + } + + // Update remote state. + self.remote_last_ts = Some(cx.now()); + + // RFC 1323: The window field (SEG.WND) in the header of every incoming segment, with the + // exception of SYN segments, is left-shifted by Snd.Wind.Scale bits before updating SND.WND. + let scale = match repr.control { + TcpControl::Syn => 0, + _ => self.remote_win_scale.unwrap_or(0), + }; + let new_remote_win_len = (repr.window_len as usize) << (scale as usize); + let is_window_update = new_remote_win_len != self.remote_win_len; + self.remote_win_len = new_remote_win_len; + + self.congestion_controller + .inner_mut() + .set_remote_window(new_remote_win_len); + + if ack_len > 0 { + // Dequeue acknowledged octets. + debug_assert!(self.tx_buffer.len() >= ack_len); + tcp_trace!( + "tx buffer: dequeueing {} octets (now {})", + ack_len, + self.tx_buffer.len() - ack_len + ); + self.tx_buffer.dequeue_allocated(ack_len); + + // There's new room available in tx_buffer, wake the waiting task if any. + #[cfg(feature = "async")] + self.tx_waker.wake(); + } + + if let Some(ack_number) = repr.ack_number { + // TODO: When flow control is implemented, + // refractor the following block within that implementation + + // Detect and react to duplicate ACKs by: + // 1. Check if duplicate ACK and change self.local_rx_dup_acks accordingly + // 2. If exactly 3 duplicate ACKs received, set for fast retransmit + // 3. Update the last received ACK (self.local_rx_last_ack) + match self.local_rx_last_ack { + // Duplicate ACK if payload empty and ACK doesn't move send window -> + // Increment duplicate ACK count and set for retransmit if we just received + // the third duplicate ACK + Some(last_rx_ack) + if repr.payload.is_empty() + && last_rx_ack == ack_number + && ack_number < self.remote_last_seq + && !is_window_update => + { + // Increment duplicate ACK count + self.local_rx_dup_acks = self.local_rx_dup_acks.saturating_add(1); + + // Inform congestion controller of duplicate ACK + self.congestion_controller + .inner_mut() + .on_duplicate_ack(cx.now()); + + net_debug!( + "received duplicate ACK for seq {} (duplicate nr {}{})", + ack_number, + self.local_rx_dup_acks, + if self.local_rx_dup_acks == u8::MAX { + "+" + } else { + "" + } + ); + + if self.local_rx_dup_acks == 3 { + self.timer.set_for_fast_retransmit(); + net_debug!("started fast retransmit"); + } + } + // No duplicate ACK -> Reset state and update last received ACK + _ => { + if self.local_rx_dup_acks > 0 { + self.local_rx_dup_acks = 0; + net_debug!("reset duplicate ACK count"); + } + self.local_rx_last_ack = Some(ack_number); + } + }; + // We've processed everything in the incoming segment, so advance the local + // sequence number past it. + self.local_seq_no = ack_number; + // During retransmission, if an earlier segment got lost but later was + // successfully received, self.local_seq_no can move past self.remote_last_seq. + // Do not attempt to retransmit the latter segments; not only this is pointless + // in theory but also impossible in practice, since they have been already + // deallocated from the buffer. + if self.remote_last_seq < self.local_seq_no { + self.remote_last_seq = self.local_seq_no + } + } + + // update last remote tsval + if let Some(timestamp) = repr.timestamp { + self.last_remote_tsval = timestamp.tsval; + } + + // update timers. + match self.timer { + Timer::Retransmit { .. } | Timer::FastRetransmit => { + if ack_all { + // RFC 6298: (5.2) ACK of all outstanding data turn off the retransmit timer. + self.timer.set_for_idle(cx.now(), self.keep_alive); + } else if ack_len > 0 { + // (5.3) ACK of new data in ESTABLISHED state restart the retransmit timer. + let rto = self.rtte.retransmission_timeout(); + self.timer.set_for_retransmit(cx.now(), rto); + } + } + Timer::Idle { .. } => { + // any packet on idle refresh the keepalive timer. + self.timer.set_for_idle(cx.now(), self.keep_alive); + } + _ => {} + } + + // start/stop the Zero Window Probe timer. + if self.remote_win_len == 0 + && !self.tx_buffer.is_empty() + && (self.timer.is_idle() || ack_len > 0) + { + let delay = self.rtte.retransmission_timeout(); + tcp_trace!("starting zero-window-probe timer for t+{}", delay); + self.timer.set_for_zero_window_probe(cx.now(), delay); + } + if self.remote_win_len != 0 && self.timer.is_zero_window_probe() { + tcp_trace!("stopping zero-window-probe timer"); + self.timer.set_for_idle(cx.now(), self.keep_alive); + } + + let payload_len = payload.len(); + if payload_len == 0 { + return None; + } + + let assembler_was_empty = self.assembler.is_empty(); + + // Try adding payload octets to the assembler. + let Ok(contig_len) = self + .assembler + .add_then_remove_front(payload_offset, payload_len) + else { + net_debug!( + "assembler: too many holes to add {} octets at offset {}", + payload_len, + payload_offset + ); + return None; + }; + + // Place payload octets into the buffer. + tcp_trace!( + "rx buffer: receiving {} octets at offset {}", + payload_len, + payload_offset + ); + let len_written = self.rx_buffer.write_unallocated(payload_offset, payload); + debug_assert!(len_written == payload_len); + + if contig_len != 0 { + // Enqueue the contiguous data octets in front of the buffer. + tcp_trace!( + "rx buffer: enqueueing {} octets (now {})", + contig_len, + self.rx_buffer.len() + contig_len + ); + self.rx_buffer.enqueue_unallocated(contig_len); + + // There's new data in rx_buffer, notify waiting task if any. + #[cfg(feature = "async")] + self.rx_waker.wake(); + } + + if !self.assembler.is_empty() { + // Print the ranges recorded in the assembler. + tcp_trace!("assembler: {}", self.assembler); + } + + // Handle delayed acks + if let Some(ack_delay) = self.ack_delay { + if self.ack_to_transmit() { + self.ack_delay_timer = match self.ack_delay_timer { + AckDelayTimer::Idle => { + tcp_trace!("starting delayed ack timer"); + AckDelayTimer::Waiting(cx.now() + ack_delay) + } + AckDelayTimer::Waiting(_) if self.immediate_ack_to_transmit() => { + tcp_trace!("delayed ack timer already started, forcing expiry"); + AckDelayTimer::Immediate + } + timer @ AckDelayTimer::Waiting(_) => { + tcp_trace!("waiting until delayed ack timer expires"); + timer + } + AckDelayTimer::Immediate => { + tcp_trace!("delayed ack timer already force-expired"); + AckDelayTimer::Immediate + } + }; + } + } + + // Per RFC 5681, we should send an immediate ACK when either: + // 1) an out-of-order segment is received, or + // 2) a segment arrives that fills in all or part of a gap in sequence space. + if !self.assembler.is_empty() || !assembler_was_empty { + // Note that we change the transmitter state here. + // This is fine because smoltcp assumes that it can always transmit zero or one + // packets for every packet it receives. + tcp_trace!("ACKing incoming segment"); + Some(self.ack_reply(ip_repr, repr)) + } else { + None + } + } + + fn timed_out(&self, timestamp: Instant) -> bool { + match (self.remote_last_ts, self.timeout) { + (Some(remote_last_ts), Some(timeout)) => timestamp >= remote_last_ts + timeout, + (_, _) => false, + } + } + + fn seq_to_transmit(&self, cx: &mut Context) -> bool { + let ip_header_len = match self.tuple.unwrap().local.addr { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(_) => crate::wire::IPV4_HEADER_LEN, + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(_) => crate::wire::IPV6_HEADER_LEN, + }; + + // Max segment size we're able to send due to MTU limitations. + let local_mss = cx.ip_mtu() - ip_header_len - TCP_HEADER_LEN; + + // The effective max segment size, taking into account our and remote's limits. + let effective_mss = local_mss.min(self.remote_mss); + + // Have we sent data that hasn't been ACKed yet? + let data_in_flight = self.remote_last_seq != self.local_seq_no; + + // If we want to send a SYN and we haven't done so, do it! + if matches!(self.state, State::SynSent | State::SynReceived) && !data_in_flight { + return true; + } + + // max sequence number we can send. + let max_send_seq = + self.local_seq_no + core::cmp::min(self.remote_win_len, self.tx_buffer.len()); + + // Max amount of octets we can send. + let max_send = if max_send_seq >= self.remote_last_seq { + max_send_seq - self.remote_last_seq + } else { + 0 + }; + + // Compare max_send with the congestion window. + let max_send = max_send.min(self.congestion_controller.inner().window()); + + // Can we send at least 1 octet? + let mut can_send = max_send != 0; + // Can we send at least 1 full segment? + let can_send_full = max_send >= effective_mss; + + // Do we have to send a FIN? + let want_fin = match self.state { + State::FinWait1 => true, + State::Closing => true, + State::LastAck => true, + _ => false, + }; + + // If we're applying the Nagle algorithm we don't want to send more + // until one of: + // * There's no data in flight + // * We can send a full packet + // * We have all the data we'll ever send (we're closing send) + if self.nagle && data_in_flight && !can_send_full && !want_fin { + can_send = false; + } + + // Can we actually send the FIN? We can send it if: + // 1. We have unsent data that fits in the remote window. + // 2. We have no unsent data. + // This condition matches only if #2, because #1 is already covered by can_data and we're ORing them. + let can_fin = want_fin && self.remote_last_seq == self.local_seq_no + self.tx_buffer.len(); + + can_send || can_fin + } + + fn delayed_ack_expired(&self, timestamp: Instant) -> bool { + match self.ack_delay_timer { + AckDelayTimer::Idle => true, + AckDelayTimer::Waiting(t) => t <= timestamp, + AckDelayTimer::Immediate => true, + } + } + + fn ack_to_transmit(&self) -> bool { + if let Some(remote_last_ack) = self.remote_last_ack { + remote_last_ack < self.remote_seq_no + self.rx_buffer.len() + } else { + false + } + } + + /// Return whether to send ACK immediately due to the amount of unacknowledged data. + /// + /// RFC 9293 states "An ACK SHOULD be generated for at least every second full-sized segment or + /// 2*RMSS bytes of new data (where RMSS is the MSS specified by the TCP endpoint receiving the + /// segments to be acknowledged, or the default value if not specified) (SHLD-19)." + /// + /// Note that the RFC above only says "at least 2*RMSS bytes", which is not a hard requirement. + /// In practice, we follow the Linux kernel's empirical value of sending an ACK for every RMSS + /// byte of new data. For details, see + /// . + fn immediate_ack_to_transmit(&self) -> bool { + if let Some(remote_last_ack) = self.remote_last_ack { + remote_last_ack + self.remote_mss < self.remote_seq_no + self.rx_buffer.len() + } else { + false + } + } + + /// Return whether we should send ACK immediately due to significant window updates. + /// + /// ACKs with significant window updates should be sent immediately to let the sender know that + /// more data can be sent. According to the Linux kernel implementation, "significant" means + /// doubling the receive window. The Linux kernel implementation can be found at + /// . + fn window_to_update(&self) -> bool { + match self.state { + State::SynSent + | State::SynReceived + | State::Established + | State::FinWait1 + | State::FinWait2 => { + let new_win = self.scaled_window(); + if let Some(last_win) = self.last_scaled_window() { + new_win > 0 && new_win / 2 >= last_win + } else { + false + } + } + _ => false, + } + } + + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, (IpRepr, TcpRepr)) -> Result<(), E>, + { + if self.tuple.is_none() { + return Ok(()); + } + + // NOTE(unwrap): we check tuple is not None above. + let tuple = self.tuple.unwrap(); + + // Check if the interface still has our source IP address. + // If not (e.g. the interface's IP changed), reset the socket. + // We use reset() instead of set_state(Closed) to avoid sending + // an RST packet with the now-invalid source IP. + if !cx.has_ip_addr(tuple.local.addr) { + net_debug!("source IP address no longer available, closing socket"); + self.reset(); + return Ok(()); + } + + if self.remote_last_ts.is_none() { + // We get here in exactly two cases: + // 1) This socket just transitioned into SYN-SENT. + // 2) This socket had an empty transmit buffer and some data was added there. + // Both are similar in that the socket has been quiet for an indefinite + // period of time, it isn't anymore, and the local endpoint is talking. + // So, we start counting the timeout not from the last received packet + // but from the first transmitted one. + self.remote_last_ts = Some(cx.now()); + } + + self.congestion_controller + .inner_mut() + .pre_transmit(cx.now()); + + // Check if any state needs to be changed because of a timer. + if self.timed_out(cx.now()) { + // If a timeout expires, we should abort the connection. + net_debug!("timeout exceeded"); + self.set_state(State::Closed); + } else if !self.seq_to_transmit(cx) && self.timer.should_retransmit(cx.now()) { + // If a retransmit timer expired, we should resend data starting at the last ACK. + net_debug!("retransmitting"); + + // Rewind "last sequence number sent", as if we never + // had sent them. This will cause all data in the queue + // to be sent again. + self.remote_last_seq = self.local_seq_no; + + // Clear the `should_retransmit` state. If we can't retransmit right + // now for whatever reason (like zero window), this avoids an + // infinite polling loop where `poll_at` returns `Now` but `dispatch` + // can't actually do anything. + self.timer.set_for_idle(cx.now(), self.keep_alive); + + // Inform RTTE, so that it can avoid bogus measurements. + self.rtte.on_retransmit(); + + // Inform the congestion controller that we're retransmitting. + self.congestion_controller + .inner_mut() + .on_retransmit(cx.now()); + } + + #[cfg(feature = "socket-tcp-pause-synack")] + if matches!(self.state, State::SynReceived) && self.synack_paused { + return Ok(()); + } + + // Decide whether we're sending a packet. + if self.seq_to_transmit(cx) { + // If we have data to transmit and it fits into partner's window, do it. + tcp_trace!("outgoing segment will send data or flags"); + } else if self.ack_to_transmit() && self.delayed_ack_expired(cx.now()) { + // If we have data to acknowledge, do it. + tcp_trace!("outgoing segment will acknowledge"); + } else if self.window_to_update() { + // If we have window length increase to advertise, do it. + tcp_trace!("outgoing segment will update window"); + } else if self.state == State::Closed { + // If we need to abort the connection, do it. + tcp_trace!("outgoing segment will abort connection"); + } else if self.timer.should_keep_alive(cx.now()) { + // If we need to transmit a keep-alive packet, do it. + tcp_trace!("keep-alive timer expired"); + } else if self.timer.should_zero_window_probe(cx.now()) { + tcp_trace!("sending zero-window probe"); + } else if self.timer.should_close(cx.now()) { + // If we have spent enough time in the TIME-WAIT state, close the socket. + tcp_trace!("TIME-WAIT timer expired"); + self.reset(); + return Ok(()); + } else { + return Ok(()); + } + + // Construct the lowered IP representation. + // We might need this to calculate the MSS, so do it early. + let mut ip_repr = IpRepr::new( + tuple.local.addr, + tuple.remote.addr, + IpProtocol::Tcp, + 0, + self.hop_limit.unwrap_or(64), + ); + + // Construct the basic TCP representation, an empty ACK packet. + // We'll adjust this to be more specific as needed. + let mut repr = TcpRepr { + src_port: tuple.local.port, + dst_port: tuple.remote.port, + control: TcpControl::None, + seq_number: self.remote_last_seq, + ack_number: Some(self.remote_seq_no + self.rx_buffer.len()), + window_len: self.scaled_window(), + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: TcpTimestampRepr::generate_reply_with_tsval( + self.tsval_generator, + self.last_remote_tsval, + ), + payload: &[], + }; + + let mut is_zero_window_probe = false; + + match self.state { + // We transmit an RST in the CLOSED state. If we ended up in the CLOSED state + // with a specified endpoint, it means that the socket was aborted. + State::Closed => { + repr.control = TcpControl::Rst; + } + + // We never transmit anything in the LISTEN state. + State::Listen => return Ok(()), + + // We transmit a SYN in the SYN-SENT state. + // We transmit a SYN|ACK in the SYN-RECEIVED state. + State::SynSent | State::SynReceived => { + repr.control = TcpControl::Syn; + repr.seq_number = self.local_seq_no; + // window len must NOT be scaled in SYNs. + repr.window_len = u16::try_from(self.rx_buffer.window()).unwrap_or(u16::MAX); + if self.state == State::SynSent { + repr.ack_number = None; + repr.window_scale = Some(self.remote_win_shift); + repr.sack_permitted = true; + } else { + repr.sack_permitted = self.remote_has_sack; + repr.window_scale = self.remote_win_scale.map(|_| self.remote_win_shift); + } + } + + // We transmit data in all states where we may have data in the buffer, + // or the transmit half of the connection is still open. + State::Established + | State::FinWait1 + | State::Closing + | State::CloseWait + | State::LastAck => { + // Extract as much data as the remote side can receive in this packet + // from the transmit buffer. + + // Right edge of window, ie the max sequence number we're allowed to send. + let win_right_edge = self.local_seq_no + self.remote_win_len; + + // Max amount of octets we're allowed to send according to the remote window. + let mut win_limit = if win_right_edge >= self.remote_last_seq { + win_right_edge - self.remote_last_seq + } else { + // This can happen if we've sent some data and later the remote side + // has shrunk its window so that data is no longer inside the window. + // This should be very rare and is strongly discouraged by the RFCs, + // but it does happen in practice. + // http://www.tcpipguide.com/free/t_TCPWindowManagementIssues.htm + 0 + }; + + // To send a zero-window-probe, force the window limit to at least 1 byte. + if win_limit == 0 && self.timer.should_zero_window_probe(cx.now()) { + win_limit = 1; + is_zero_window_probe = true; + } + + // Maximum size we're allowed to send. This can be limited by 3 factors: + // 1. remote window + // 2. MSS the remote is willing to accept, probably determined by their MTU + // 3. MSS we can send, determined by our MTU. + let size = win_limit + .min(self.remote_mss) + .min(cx.ip_mtu() - ip_repr.header_len() - TCP_HEADER_LEN); + + let offset = self.remote_last_seq - self.local_seq_no; + repr.payload = self.tx_buffer.get_allocated(offset, size); + + // If we've sent everything we had in the buffer, follow it with the PSH or FIN + // flags, depending on whether the transmit half of the connection is open. + if offset + repr.payload.len() == self.tx_buffer.len() { + match self.state { + State::FinWait1 | State::LastAck | State::Closing => { + repr.control = TcpControl::Fin + } + State::Established | State::CloseWait if !repr.payload.is_empty() => { + repr.control = TcpControl::Psh + } + _ => (), + } + } + } + + // In FIN-WAIT-2 and TIME-WAIT states we may only transmit ACKs for incoming data or FIN + State::FinWait2 | State::TimeWait => {} + } + + // There might be more than one reason to send a packet. E.g. the keep-alive timer + // has expired, and we also have data in transmit buffer. Since any packet that occupies + // sequence space will elicit an ACK, we only need to send an explicit packet if we + // couldn't fill the sequence space with anything. + let is_keep_alive; + if self.timer.should_keep_alive(cx.now()) && repr.is_empty() { + repr.seq_number = repr.seq_number - 1; + repr.payload = b"\x00"; // RFC 1122 says we should do this + is_keep_alive = true; + } else { + is_keep_alive = false; + } + + // Trace a summary of what will be sent. + if is_keep_alive { + tcp_trace!("sending a keep-alive"); + } else if !repr.payload.is_empty() { + tcp_trace!( + "tx buffer: sending {} octets at offset {}", + repr.payload.len(), + self.remote_last_seq - self.local_seq_no + ); + } + if repr.control != TcpControl::None || repr.payload.is_empty() { + let flags = match (repr.control, repr.ack_number) { + (TcpControl::Syn, None) => "SYN", + (TcpControl::Syn, Some(_)) => "SYN|ACK", + (TcpControl::Fin, Some(_)) => "FIN|ACK", + (TcpControl::Rst, Some(_)) => "RST|ACK", + (TcpControl::Psh, Some(_)) => "PSH|ACK", + (TcpControl::None, Some(_)) => "ACK", + _ => "", + }; + tcp_trace!("sending {}", flags); + } + + if repr.control == TcpControl::Syn { + // Fill the MSS option. See RFC 6691 for an explanation of this calculation. + let max_segment_size = cx.ip_mtu() - ip_repr.header_len() - TCP_HEADER_LEN; + repr.max_seg_size = Some(max_segment_size as u16); + } + + // Actually send the packet. If this succeeds, it means the packet is in + // the device buffer, and its transmission is imminent. If not, we might have + // a number of problems, e.g. we need neighbor discovery. + // + // Bailing out if the packet isn't placed in the device buffer allows us + // to not waste time waiting for the retransmit timer on packets that we know + // for sure will not be successfully transmitted. + ip_repr.set_payload_len(repr.buffer_len()); + emit(cx, (ip_repr, repr))?; + + // We've sent something, whether useful data or a keep-alive packet, so rewind + // the keep-alive timer. + self.timer.rewind_keep_alive(cx.now(), self.keep_alive); + + // Reset delayed-ack timer + match self.ack_delay_timer { + AckDelayTimer::Idle => {} + AckDelayTimer::Waiting(_) => { + tcp_trace!("stop delayed ack timer") + } + AckDelayTimer::Immediate => { + tcp_trace!("stop delayed ack timer (was force-expired)") + } + } + self.ack_delay_timer = AckDelayTimer::Idle; + + // Leave the rest of the state intact if sending a zero-window probe. + if is_zero_window_probe { + self.timer.rewind_zero_window_probe(cx.now()); + return Ok(()); + } + + // Leave the rest of the state intact if sending a keep-alive packet, since those + // carry a fake segment. + if is_keep_alive { + return Ok(()); + } + + // We've sent a packet successfully, so we can update the internal state now. + self.remote_last_seq = repr.seq_number + repr.segment_len(); + self.remote_last_ack = repr.ack_number; + self.remote_last_win = repr.window_len; + + if repr.segment_len() > 0 { + self.rtte + .on_send(cx.now(), repr.seq_number + repr.segment_len()); + self.congestion_controller + .inner_mut() + .post_transmit(cx.now(), repr.segment_len()); + } + + if repr.segment_len() > 0 && !self.timer.is_retransmit() { + // RFC 6298 (5.1) Every time a packet containing data is sent (including a + // retransmission), if the timer is not running, start it running + // so that it will expire after RTO seconds. + let rto = self.rtte.retransmission_timeout(); + self.timer.set_for_retransmit(cx.now(), rto); + } + + if self.state == State::Closed { + // When aborting a connection, forget about it after sending a single RST packet. + self.tuple = None; + #[cfg(feature = "async")] + { + // Wake tx now so that async users can wait for the RST to be sent + self.tx_waker.wake(); + } + } + + Ok(()) + } + + #[allow(clippy::if_same_then_else)] + pub(crate) fn poll_at(&self, cx: &mut Context) -> PollAt { + // The logic here mirrors the beginning of dispatch() closely. + if self.tuple.is_none() { + // No one to talk to, nothing to transmit. + PollAt::Ingress + } else if self.remote_last_ts.is_none() { + // Socket stopped being quiet recently, we need to acquire a timestamp. + PollAt::Now + } else if self.state == State::Closed { + // Socket was aborted, we have an RST packet to transmit. + PollAt::Now + } else if self.seq_to_transmit(cx) { + // We have a data or flag packet to transmit. + PollAt::Now + } else if self.window_to_update() { + // The receive window has been raised significantly. + PollAt::Now + } else { + let want_ack = self.ack_to_transmit(); + + let delayed_ack_poll_at = match (want_ack, self.ack_delay_timer) { + (false, _) => PollAt::Ingress, + (true, AckDelayTimer::Idle) => PollAt::Now, + (true, AckDelayTimer::Waiting(t)) => PollAt::Time(t), + (true, AckDelayTimer::Immediate) => PollAt::Now, + }; + + let timeout_poll_at = match (self.remote_last_ts, self.timeout) { + // If we're transmitting or retransmitting data, we need to poll at the moment + // when the timeout would expire. + (Some(remote_last_ts), Some(timeout)) => PollAt::Time(remote_last_ts + timeout), + // Otherwise we have no timeout. + (_, _) => PollAt::Ingress, + }; + + // We wait for the earliest of our timers to fire. + *[self.timer.poll_at(), timeout_poll_at, delayed_ack_poll_at] + .iter() + .min() + .unwrap_or(&PollAt::Ingress) + } + } +} + +impl<'a> fmt::Write for Socket<'a> { + fn write_str(&mut self, slice: &str) -> fmt::Result { + let slice = slice.as_bytes(); + if self.send_slice(slice) == Ok(slice.len()) { + Ok(()) + } else { + Err(fmt::Error) + } + } +} + +// TODO: TCP should work for all features. For now, we only test with the IP feature. We could do +// it for other features as well with rstest, however, this means we have to modify a lot of the +// tests in here, which I didn't had the time for at the moment. +#[cfg(all(test, feature = "medium-ip"))] +mod test { + use super::*; + use crate::config::IFACE_MAX_ADDR_COUNT; + use crate::wire::{IpCidr, IpRepr}; + use std::ops::{Deref, DerefMut}; + use std::vec::Vec; + + // =========================================================================================// + // Constants + // =========================================================================================// + + const LOCAL_PORT: u16 = 80; + const REMOTE_PORT: u16 = 49500; + const LISTEN_END: IpListenEndpoint = IpListenEndpoint { + addr: None, + port: LOCAL_PORT, + }; + const TUPLE: Tuple = Tuple { + local: LOCAL_END, + remote: REMOTE_END, + }; + const LOCAL_SEQ: TcpSeqNumber = TcpSeqNumber(10000); + const REMOTE_SEQ: TcpSeqNumber = TcpSeqNumber(-10001); + + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + use crate::wire::Ipv4Address as IpvXAddress; + use crate::wire::Ipv4Repr as IpvXRepr; + use IpRepr::Ipv4 as IpReprIpvX; + + const LOCAL_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 1); + const REMOTE_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 2); + const OTHER_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 3); + + const BASE_MSS: u16 = 1460; + + const LOCAL_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv4(LOCAL_ADDR), + port: LOCAL_PORT, + }; + const REMOTE_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv4(REMOTE_ADDR), + port: REMOTE_PORT, + }; + } else { + use crate::wire::Ipv6Address as IpvXAddress; + use crate::wire::Ipv6Repr as IpvXRepr; + use IpRepr::Ipv6 as IpReprIpvX; + + const LOCAL_ADDR: IpvXAddress = IpvXAddress::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const REMOTE_ADDR: IpvXAddress = IpvXAddress::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + const OTHER_ADDR: IpvXAddress = IpvXAddress::new(0xfe80, 0, 0, 0, 0, 0, 0, 3); + + const BASE_MSS: u16 = 1440; + + const LOCAL_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv6(LOCAL_ADDR), + port: LOCAL_PORT, + }; + const REMOTE_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv6(REMOTE_ADDR), + port: REMOTE_PORT, + }; + } + } + + const SEND_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Tcp, + payload_len: 20, + hop_limit: 64, + }); + const SEND_TEMPL: TcpRepr<'static> = TcpRepr { + src_port: REMOTE_PORT, + dst_port: LOCAL_PORT, + control: TcpControl::None, + seq_number: TcpSeqNumber(0), + ack_number: Some(TcpSeqNumber(0)), + window_len: 256, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &[], + }; + const _RECV_IP_TEMPL: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Tcp, + payload_len: 20, + hop_limit: 64, + }); + const RECV_TEMPL: TcpRepr<'static> = TcpRepr { + src_port: LOCAL_PORT, + dst_port: REMOTE_PORT, + control: TcpControl::None, + seq_number: TcpSeqNumber(0), + ack_number: Some(TcpSeqNumber(0)), + window_len: 64, + window_scale: None, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &[], + }; + + // =========================================================================================// + // Helper functions + // =========================================================================================// + + struct TestSocket { + socket: Socket<'static>, + cx: Context, + } + + impl Deref for TestSocket { + type Target = Socket<'static>; + fn deref(&self) -> &Self::Target { + &self.socket + } + } + + impl DerefMut for TestSocket { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.socket + } + } + + #[track_caller] + fn send( + socket: &mut TestSocket, + timestamp: Instant, + repr: &TcpRepr, + ) -> Option> { + socket.cx.set_now(timestamp); + + let ip_repr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + payload_len: repr.buffer_len(), + hop_limit: 64, + }); + net_trace!("send: {}", repr); + + assert!(socket.socket.accepts(&mut socket.cx, &ip_repr, repr)); + + match socket.socket.process(&mut socket.cx, &ip_repr, repr) { + Some((_ip_repr, repr)) => { + net_trace!("recv: {}", repr); + Some(repr) + } + None => None, + } + } + + #[track_caller] + fn recv(socket: &mut TestSocket, timestamp: Instant, mut f: F) + where + F: FnMut(Result), + { + socket.cx.set_now(timestamp); + + let mut sent = 0; + let result = socket + .socket + .dispatch(&mut socket.cx, |_, (ip_repr, tcp_repr)| { + assert_eq!(ip_repr.next_header(), IpProtocol::Tcp); + assert_eq!(ip_repr.src_addr(), LOCAL_ADDR.into()); + assert_eq!(ip_repr.dst_addr(), REMOTE_ADDR.into()); + assert_eq!(ip_repr.payload_len(), tcp_repr.buffer_len()); + + net_trace!("recv: {}", tcp_repr); + sent += 1; + Ok(f(Ok(tcp_repr))) + }); + match result { + Ok(()) => assert_eq!(sent, 1, "Exactly one packet should be sent"), + Err(e) => f(Err(e)), + } + } + + #[track_caller] + fn recv_nothing(socket: &mut TestSocket, timestamp: Instant) { + socket.cx.set_now(timestamp); + + let mut fail = false; + let result: Result<(), ()> = socket.socket.dispatch(&mut socket.cx, |_, _| { + fail = true; + Ok(()) + }); + if fail { + panic!("Should not send a packet") + } + + assert_eq!(result, Ok(())) + } + + #[collapse_debuginfo(yes)] + macro_rules! send { + ($socket:ident, $repr:expr) => + (send!($socket, time 0, $repr)); + ($socket:ident, $repr:expr, $result:expr) => + (send!($socket, time 0, $repr, $result)); + ($socket:ident, time $time:expr, $repr:expr) => + (send!($socket, time $time, $repr, None)); + ($socket:ident, time $time:expr, $repr:expr, $result:expr) => + (assert_eq!(send(&mut $socket, Instant::from_millis($time), &$repr), $result)); + } + + #[collapse_debuginfo(yes)] + macro_rules! recv { + ($socket:ident, [$( $repr:expr ),*]) => ({ + $( recv!($socket, Ok($repr)); )* + recv_nothing!($socket) + }); + ($socket:ident, time $time:expr, [$( $repr:expr ),*]) => ({ + $( recv!($socket, time $time, Ok($repr)); )* + recv_nothing!($socket, time $time) + }); + ($socket:ident, $result:expr) => + (recv!($socket, time 0, $result)); + ($socket:ident, time $time:expr, $result:expr) => + (recv(&mut $socket, Instant::from_millis($time), |result| { + // Most of the time we don't care about the PSH flag. + let result = result.map(|mut repr| { + repr.control = repr.control.quash_psh(); + repr + }); + assert_eq!(result, $result) + })); + ($socket:ident, time $time:expr, $result:expr, exact) => + (recv(&mut $socket, Instant::from_millis($time), |repr| assert_eq!(repr, $result))); + } + + #[collapse_debuginfo(yes)] + macro_rules! recv_nothing { + ($socket:ident) => (recv_nothing!($socket, time 0)); + ($socket:ident, time $time:expr) => (recv_nothing(&mut $socket, Instant::from_millis($time))); + } + + #[collapse_debuginfo(yes)] + macro_rules! sanity { + ($socket1:expr, $socket2:expr) => {{ + let (s1, s2) = ($socket1, $socket2); + assert_eq!(s1.state, s2.state, "state"); + assert_eq!(s1.tuple, s2.tuple, "tuple"); + assert_eq!(s1.local_seq_no, s2.local_seq_no, "local_seq_no"); + assert_eq!(s1.remote_seq_no, s2.remote_seq_no, "remote_seq_no"); + assert_eq!(s1.remote_last_seq, s2.remote_last_seq, "remote_last_seq"); + assert_eq!(s1.remote_last_ack, s2.remote_last_ack, "remote_last_ack"); + assert_eq!(s1.remote_last_win, s2.remote_last_win, "remote_last_win"); + assert_eq!(s1.remote_win_len, s2.remote_win_len, "remote_win_len"); + assert_eq!(s1.timer, s2.timer, "timer"); + }}; + } + + fn socket() -> TestSocket { + socket_with_buffer_sizes(64, 64) + } + + fn socket_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket { + let (iface, _, _) = crate::tests::setup(crate::phy::Medium::Ip); + + let rx_buffer = SocketBuffer::new(vec![0; rx_len]); + let tx_buffer = SocketBuffer::new(vec![0; tx_len]); + let mut socket = Socket::new(rx_buffer, tx_buffer); + socket.set_ack_delay(None); + TestSocket { + socket, + cx: iface.inner, + } + } + + fn socket_syn_received_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket { + let mut s = socket_with_buffer_sizes(tx_len, rx_len); + s.state = State::SynReceived; + s.tuple = Some(TUPLE); + s.local_seq_no = LOCAL_SEQ; + s.remote_seq_no = REMOTE_SEQ + 1; + s.remote_last_seq = LOCAL_SEQ; + s.remote_win_len = 256; + s + } + + fn socket_syn_received() -> TestSocket { + socket_syn_received_with_buffer_sizes(64, 64) + } + + fn socket_syn_sent_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket { + let mut s = socket_with_buffer_sizes(tx_len, rx_len); + s.state = State::SynSent; + s.tuple = Some(TUPLE); + s.local_seq_no = LOCAL_SEQ; + s.remote_last_seq = LOCAL_SEQ; + s + } + + fn socket_syn_sent() -> TestSocket { + socket_syn_sent_with_buffer_sizes(64, 64) + } + + fn socket_established_with_buffer_sizes(tx_len: usize, rx_len: usize) -> TestSocket { + let mut s = socket_syn_received_with_buffer_sizes(tx_len, rx_len); + s.state = State::Established; + s.local_seq_no = LOCAL_SEQ + 1; + s.remote_last_seq = LOCAL_SEQ + 1; + s.remote_last_ack = Some(REMOTE_SEQ + 1); + s.remote_last_win = s.scaled_window(); + s + } + + fn socket_established() -> TestSocket { + socket_established_with_buffer_sizes(64, 64) + } + + fn socket_fin_wait_1() -> TestSocket { + let mut s = socket_established(); + s.state = State::FinWait1; + s + } + + fn socket_fin_wait_2() -> TestSocket { + let mut s = socket_fin_wait_1(); + s.state = State::FinWait2; + s.local_seq_no = LOCAL_SEQ + 1 + 1; + s.remote_last_seq = LOCAL_SEQ + 1 + 1; + s + } + + fn socket_closing() -> TestSocket { + let mut s = socket_fin_wait_1(); + s.state = State::Closing; + s.remote_last_seq = LOCAL_SEQ + 1 + 1; + s.remote_seq_no = REMOTE_SEQ + 1 + 1; + s.timer = Timer::Retransmit { + expires_at: Instant::from_millis_const(1000), + }; + s + } + + fn socket_time_wait(from_closing: bool) -> TestSocket { + let mut s = socket_fin_wait_2(); + s.state = State::TimeWait; + s.remote_seq_no = REMOTE_SEQ + 1 + 1; + if from_closing { + s.remote_last_ack = Some(REMOTE_SEQ + 1 + 1); + } + s.timer = Timer::Close { + expires_at: Instant::from_secs(1) + CLOSE_DELAY, + }; + s + } + + fn socket_close_wait() -> TestSocket { + let mut s = socket_established(); + s.state = State::CloseWait; + s.remote_seq_no = REMOTE_SEQ + 1 + 1; + s.remote_last_ack = Some(REMOTE_SEQ + 1 + 1); + s + } + + fn socket_last_ack() -> TestSocket { + let mut s = socket_close_wait(); + s.state = State::LastAck; + s + } + + fn socket_recved() -> TestSocket { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 58, + ..RECV_TEMPL + }] + ); + s + } + + // =========================================================================================// + // Tests for the CLOSED state. + // =========================================================================================// + #[test] + fn test_closed_reject() { + let mut s = socket(); + assert_eq!(s.state, State::Closed); + + let tcp_repr = TcpRepr { + control: TcpControl::Syn, + ..SEND_TEMPL + }; + assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr)); + } + + #[test] + fn test_closed_reject_after_listen() { + let mut s = socket(); + s.listen(LOCAL_END).unwrap(); + s.close(); + + let tcp_repr = TcpRepr { + control: TcpControl::Syn, + ..SEND_TEMPL + }; + assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr)); + } + + #[test] + fn test_closed_close() { + let mut s = socket(); + s.close(); + assert_eq!(s.state, State::Closed); + } + + // =========================================================================================// + // Tests for the LISTEN state. + // =========================================================================================// + fn socket_listen() -> TestSocket { + let mut s = socket(); + s.state = State::Listen; + s.listen_endpoint = LISTEN_END; + s + } + + #[test] + fn test_listen_sack_option() { + let mut s = socket_listen(); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + sack_permitted: false, + ..SEND_TEMPL + } + ); + assert!(!s.remote_has_sack); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + + let mut s = socket_listen(); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + sack_permitted: true, + ..SEND_TEMPL + } + ); + assert!(s.remote_has_sack); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_listen_syn_win_scale_buffers() { + for (buffer_size, shift_amt) in &[ + (64, 0), + (128, 0), + (1024, 0), + (65535, 0), + (65536, 1), + (65537, 1), + (131071, 1), + (131072, 2), + (524287, 3), + (524288, 4), + (655350, 4), + (1048576, 5), + ] { + let mut s = socket_with_buffer_sizes(64, *buffer_size); + s.state = State::Listen; + s.listen_endpoint = LISTEN_END; + assert_eq!(s.remote_win_shift, *shift_amt); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + window_scale: Some(0), + ..SEND_TEMPL + } + ); + assert_eq!(s.remote_win_shift, *shift_amt); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + window_scale: Some(*shift_amt), + window_len: u16::try_from(*buffer_size).unwrap_or(u16::MAX), + ..RECV_TEMPL + }] + ); + } + } + + #[test] + fn test_listen_sanity() { + let mut s = socket(); + s.listen(LOCAL_PORT).unwrap(); + sanity!(s, socket_listen()); + } + + #[test] + fn test_listen_validation() { + let mut s = socket(); + assert_eq!(s.listen(0), Err(ListenError::Unaddressable)); + } + + #[test] + fn test_listen_twice() { + let mut s = socket(); + assert_eq!(s.listen(80), Ok(())); + // multiple calls to listen are okay if its the same local endpoint and the state is still in listening + assert_eq!(s.listen(80), Ok(())); + s.set_state(State::SynReceived); // state change, simulate incoming connection + assert_eq!(s.listen(80), Err(ListenError::InvalidState)); + } + + #[test] + fn test_listen_syn() { + let mut s = socket_listen(); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + ..SEND_TEMPL + } + ); + sanity!(s, socket_syn_received()); + } + + #[test] + fn test_listen_syn_reject_ack() { + let mut s = socket_listen(); + + let tcp_repr = TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ), + ..SEND_TEMPL + }; + assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr)); + + assert_eq!(s.state, State::Listen); + } + + #[test] + fn test_listen_rst() { + let mut s = socket_listen(); + let tcp_repr = TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ, + ack_number: None, + ..SEND_TEMPL + }; + assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr)); + assert_eq!(s.state, State::Listen); + } + + #[test] + fn test_listen_close() { + let mut s = socket_listen(); + s.close(); + assert_eq!(s.state, State::Closed); + } + + // =========================================================================================// + // Tests for the SYN-RECEIVED state. + // =========================================================================================// + + #[test] + fn test_syn_received_ack() { + let mut s = socket_syn_received(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Established); + sanity!(s, socket_established()); + } + + #[cfg(feature = "socket-tcp-pause-synack")] + #[test] + fn test_syn_paused_ack() { + let mut s = socket_syn_received(); + + s.pause_synack(true); + recv_nothing!(s); + assert_eq!(s.state, State::SynReceived); + + s.pause_synack(false); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Established); + } + + #[test] + fn test_syn_received_ack_too_low() { + let mut s = socket_syn_received(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ), // wrong + ..SEND_TEMPL + }, + Some(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ, + ack_number: None, + window_len: 0, + ..RECV_TEMPL + }) + ); + assert_eq!(s.state, State::SynReceived); + } + + #[test] + fn test_syn_received_ack_too_high() { + let mut s = socket_syn_received(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 2), // wrong + ..SEND_TEMPL + }, + Some(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 2, + ack_number: None, + window_len: 0, + ..RECV_TEMPL + }) + ); + assert_eq!(s.state, State::SynReceived); + } + + #[test] + fn test_syn_received_fin() { + let mut s = socket_syn_received(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6 + 1), + window_len: 58, + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::CloseWait); + + let mut s2 = socket_close_wait(); + s2.remote_last_ack = Some(REMOTE_SEQ + 1 + 6 + 1); + s2.remote_last_win = 58; + sanity!(s, s2); + } + + #[test] + fn test_syn_received_rst() { + let mut s = socket_syn_received(); + s.listen_endpoint = LISTEN_END; + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Listen); + assert_eq!(s.listen_endpoint, LISTEN_END); + assert_eq!(s.tuple, None); + } + + #[test] + fn test_syn_received_no_window_scaling() { + let mut s = socket_listen(); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::SynReceived); + assert_eq!(s.tuple, Some(TUPLE)); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + window_scale: None, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_scale: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.remote_win_shift, 0); + assert_eq!(s.remote_win_scale, None); + } + + #[test] + fn test_syn_received_window_scaling() { + for scale in 0..14 { + let mut s = socket_listen(); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + window_scale: Some(scale), + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::SynReceived); + assert_eq!(s.tuple, Some(TUPLE)); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_scale: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.remote_win_scale, Some(scale)); + } + } + + #[test] + fn test_syn_received_close() { + let mut s = socket_syn_received(); + s.close(); + assert_eq!(s.state, State::FinWait1); + } + + // =========================================================================================// + // Tests for the SYN-SENT state. + // =========================================================================================// + + #[test] + fn test_connect_validation() { + let mut s = socket(); + assert_eq!( + s.socket + .connect(&mut s.cx, REMOTE_END, (IpvXAddress::UNSPECIFIED, 0)), + Err(ConnectError::Unaddressable) + ); + assert_eq!( + s.socket + .connect(&mut s.cx, REMOTE_END, (IpvXAddress::UNSPECIFIED, 1024)), + Err(ConnectError::Unaddressable) + ); + assert_eq!( + s.socket + .connect(&mut s.cx, (IpvXAddress::UNSPECIFIED, 0), LOCAL_END), + Err(ConnectError::Unaddressable) + ); + s.socket + .connect(&mut s.cx, REMOTE_END, LOCAL_END) + .expect("Connect failed with valid parameters"); + assert_eq!(s.tuple, Some(TUPLE)); + } + + #[test] + fn test_connect() { + let mut s = socket(); + s.local_seq_no = LOCAL_SEQ; + s.socket + .connect(&mut s.cx, REMOTE_END, LOCAL_END.port) + .unwrap(); + assert_eq!(s.tuple, Some(TUPLE)); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + ..SEND_TEMPL + } + ); + assert_eq!(s.tuple, Some(TUPLE)); + } + + #[test] + fn test_connect_unspecified_local() { + let mut s = socket(); + assert_eq!(s.socket.connect(&mut s.cx, REMOTE_END, 80), Ok(())); + } + + #[test] + fn test_connect_specified_local() { + let mut s = socket(); + assert_eq!( + s.socket.connect(&mut s.cx, REMOTE_END, (REMOTE_ADDR, 80)), + Ok(()) + ); + } + + #[test] + fn test_connect_twice() { + let mut s = socket(); + assert_eq!(s.socket.connect(&mut s.cx, REMOTE_END, 80), Ok(())); + assert_eq!( + s.socket.connect(&mut s.cx, REMOTE_END, 80), + Err(ConnectError::InvalidState) + ); + } + + #[test] + fn test_syn_sent_sanity() { + let mut s = socket(); + s.local_seq_no = LOCAL_SEQ; + s.socket.connect(&mut s.cx, REMOTE_END, LOCAL_END).unwrap(); + sanity!(s, socket_syn_sent()); + } + + #[test] + fn test_syn_sent_syn_ack() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + recv_nothing!(s, time 1000); + assert_eq!(s.state, State::Established); + sanity!(s, socket_established()); + } + + #[test] + fn test_syn_sent_syn_received_ack() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + + // A SYN packet changes the SYN-SENT state to SYN-RECEIVED. + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::SynReceived); + + // The socket will then send a SYN|ACK packet. + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + ..RECV_TEMPL + }] + ); + recv_nothing!(s); + + // The socket may retransmit the SYN|ACK packet. + recv!( + s, + time 1001, + Ok(TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + ..RECV_TEMPL + }) + ); + + // An ACK packet changes the SYN-RECEIVED state to ESTABLISHED. + send!( + s, + TcpRepr { + control: TcpControl::None, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Established); + sanity!(s, socket_established()); + } + + #[test] + fn test_syn_sent_syn_ack_not_incremented() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ), // WRONG + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + ..SEND_TEMPL + }, + Some(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ, + ack_number: None, + window_len: 0, + ..RECV_TEMPL + }) + ); + assert_eq!(s.state, State::SynSent); + } + + #[test] + fn test_syn_sent_syn_received_rst() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + + // A SYN packet changes the SYN-SENT state to SYN-RECEIVED. + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::SynReceived); + + // A RST packet changes the SYN-RECEIVED state to CLOSED. + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_syn_sent_rst() { + let mut s = socket_syn_sent(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_syn_sent_rst_no_ack() { + let mut s = socket_syn_sent(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ, + ack_number: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::SynSent); + } + + #[test] + fn test_syn_sent_rst_bad_ack() { + let mut s = socket_syn_sent(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ, + ack_number: Some(TcpSeqNumber(1234)), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::SynSent); + } + + #[test] + fn test_syn_sent_bad_ack() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::None, // Unexpected + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), // Correct + ..SEND_TEMPL + } + ); + + // It should trigger no response and change no state + recv!(s, []); + assert_eq!(s.state, State::SynSent); + } + + #[test] + fn test_syn_sent_bad_ack_seq_1() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::None, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ), // WRONG + ..SEND_TEMPL + }, + Some(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ, // matching the ack_number of the unexpected ack + ack_number: None, + window_len: 0, + ..RECV_TEMPL + }) + ); + + // It should trigger a RST, and change no state + assert_eq!(s.state, State::SynSent); + } + + #[test] + fn test_syn_sent_bad_ack_seq_2() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::None, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 123456), // WRONG + ..SEND_TEMPL + }, + Some(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 123456, // matching the ack_number of the unexpected ack + ack_number: None, + window_len: 0, + ..RECV_TEMPL + }) + ); + + // It should trigger a RST, and change no state + assert_eq!(s.state, State::SynSent); + } + + #[test] + fn test_syn_sent_close() { + let mut s = socket(); + s.close(); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_syn_sent_sack_option() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + sack_permitted: true, + ..SEND_TEMPL + } + ); + assert!(s.remote_has_sack); + + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + sack_permitted: false, + ..SEND_TEMPL + } + ); + assert!(!s.remote_has_sack); + } + + #[test] + fn test_syn_sent_win_scale_buffers() { + for (buffer_size, shift_amt) in &[ + (64, 0), + (128, 0), + (1024, 0), + (65535, 0), + (65536, 1), + (65537, 1), + (131071, 1), + (131072, 2), + (524287, 3), + (524288, 4), + (655350, 4), + (1048576, 5), + ] { + let mut s = socket_with_buffer_sizes(64, *buffer_size); + s.local_seq_no = LOCAL_SEQ; + assert_eq!(s.remote_win_shift, *shift_amt); + s.socket.connect(&mut s.cx, REMOTE_END, LOCAL_END).unwrap(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(*shift_amt), + window_len: u16::try_from(*buffer_size).unwrap_or(u16::MAX), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + } + } + + #[test] + fn test_syn_sent_syn_ack_no_window_scaling() { + let mut s = socket_syn_sent_with_buffer_sizes(1048576, 1048576); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + // scaling does NOT apply to the window value in SYN packets + window_len: 65535, + window_scale: Some(5), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + assert_eq!(s.remote_win_shift, 5); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: None, + window_len: 42, + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Established); + assert_eq!(s.remote_win_shift, 0); + assert_eq!(s.remote_win_scale, None); + assert_eq!(s.remote_win_len, 42); + } + + #[test] + fn test_syn_sent_syn_ack_window_scaling() { + let mut s = socket_syn_sent(); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(7), + window_len: 42, + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Established); + assert_eq!(s.remote_win_scale, Some(7)); + // scaling does NOT apply to the window value in SYN packets + assert_eq!(s.remote_win_len, 42); + } + + // =========================================================================================// + // Tests for the ESTABLISHED state. + // =========================================================================================// + + #[test] + fn test_established_recv() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 58, + ..RECV_TEMPL + }] + ); + assert_eq!(s.rx_buffer.dequeue_many(6), &b"abcdef"[..]); + } + + #[test] + fn test_peek_slice() { + const BUF_SIZE: usize = 10; + + let send_buf = b"0123456"; + + let mut s = socket_established_with_buffer_sizes(BUF_SIZE, BUF_SIZE); + + // Populate the recv buffer + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &send_buf[..], + ..SEND_TEMPL + } + ); + + // Peek into the recv buffer + let mut peeked_buf = [0u8; BUF_SIZE]; + let actually_peeked = s.peek_slice(&mut peeked_buf[..]).unwrap(); + let mut recv_buf = [0u8; BUF_SIZE]; + let actually_recvd = s.recv_slice(&mut recv_buf[..]).unwrap(); + assert_eq!( + &mut peeked_buf[..actually_peeked], + &mut recv_buf[..actually_recvd] + ); + } + + #[test] + fn test_peek_slice_buffer_wrap() { + const BUF_SIZE: usize = 10; + + let send_buf = b"0123456789"; + + let mut s = socket_established_with_buffer_sizes(BUF_SIZE, BUF_SIZE); + + let _ = s.rx_buffer.enqueue_slice(&send_buf[..8]); + let _ = s.rx_buffer.dequeue_many(6); + let _ = s.rx_buffer.enqueue_slice(&send_buf[..5]); + + let mut peeked_buf = [0u8; BUF_SIZE]; + let actually_peeked = s.peek_slice(&mut peeked_buf[..]).unwrap(); + let mut recv_buf = [0u8; BUF_SIZE]; + let actually_recvd = s.recv_slice(&mut recv_buf[..]).unwrap(); + assert_eq!( + &mut peeked_buf[..actually_peeked], + &mut recv_buf[..actually_recvd] + ); + } + + fn setup_rfc2018_cases() -> (TestSocket, Vec) { + // This is a utility function used by the tests for RFC 2018 cases. It configures a socket + // in a particular way suitable for those cases. + // + // RFC 2018: Assume the left window edge is 5000 and that the data transmitter sends [...] + // segments, each containing 500 data bytes. + let mut s = socket_established_with_buffer_sizes(4000, 4000); + s.remote_has_sack = true; + + // create a segment that is 500 bytes long + let mut segment: Vec = Vec::with_capacity(500); + + // move the last ack to 5000 by sending ten of them + for _ in 0..50 { + segment.extend_from_slice(b"abcdefghij") + } + for offset in (0..5000).step_by(500) { + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + offset, + ack_number: Some(LOCAL_SEQ + 1), + payload: &segment, + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + offset + 500), + window_len: 3500, + ..RECV_TEMPL + }] + ); + s.recv(|data| { + assert_eq!(data.len(), 500); + assert_eq!(data, segment.as_slice()); + (500, ()) + }) + .unwrap(); + } + assert_eq!(s.remote_last_win, 3500); + (s, segment) + } + + #[test] + fn test_established_rfc2018_cases() { + // This test case verifies the exact scenarios described on pages 8-9 of RFC 2018. Please + // ensure its behavior does not deviate from those scenarios. + + let (mut s, segment) = setup_rfc2018_cases(); + // RFC 2018: + // + // Case 2: The first segment is dropped but the remaining 7 are received. + // + // Upon receiving each of the last seven packets, the data receiver will return a TCP ACK + // segment that acknowledges sequence number 5000 and contains a SACK option specifying one + // block of queued data: + // + // Triggering ACK Left Edge Right Edge + // Segment + // + // 5000 (lost) + // 5500 5000 5500 6000 + // 6000 5000 5500 6500 + // 6500 5000 5500 7000 + // 7000 5000 5500 7500 + // 7500 5000 5500 8000 + // 8000 5000 5500 8500 + // 8500 5000 5500 9000 + // + for offset in (500..3500).step_by(500) { + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + offset + 5000, + ack_number: Some(LOCAL_SEQ + 1), + payload: &segment, + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 5000), + window_len: 4000, + sack_ranges: [ + Some(( + REMOTE_SEQ.0 as u32 + 1 + 5500, + REMOTE_SEQ.0 as u32 + 1 + 5500 + offset as u32 + )), + None, + None + ], + ..RECV_TEMPL + }) + ); + } + } + + #[test] + fn test_established_sack_no_overflow_on_near_max_seqnumber() { + let mut s = socket_established(); + s.remote_has_sack = true; + s.remote_seq_no = TcpSeqNumber(-4); + s.remote_last_ack = Some(TcpSeqNumber(-4)); + + // Send an out-of-order segment 10 bytes past the expected sequence, + // creating a 10-byte hole at the front of the assembler. + send!( + s, + TcpRepr { + seq_number: TcpSeqNumber(-4 + 10), + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"AAAAAAAAAA"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(TcpSeqNumber(-4)), + window_len: 64, + sack_ranges: [ + Some(((-4_i32 + 10) as u32, (-4_i32 + 20) as u32,)), + None, + None, + ], + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_established_sliding_window_recv() { + let mut s = socket_established(); + // Update our scaling parameters for a TCP with a scaled buffer. + assert_eq!(s.rx_buffer.len(), 0); + s.rx_buffer = SocketBuffer::new(vec![0; 262143]); + s.assembler = Assembler::new(); + s.remote_win_scale = Some(0); + s.remote_last_win = 65535; + s.remote_win_shift = 2; + + // Create a TCP segment that will mostly fill an IP frame. + let mut segment: Vec = Vec::with_capacity(1400); + for _ in 0..100 { + segment.extend_from_slice(b"abcdefghijklmn") + } + assert_eq!(segment.len(), 1400); + + // Send the frame + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &segment, + ..SEND_TEMPL + } + ); + + // Ensure that the received window size is shifted right by 2. + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1400), + window_len: 65185, + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_established_send() { + let mut s = socket_established(); + // First roundtrip after establishing. + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + assert_eq!(s.tx_buffer.len(), 6); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + ..SEND_TEMPL + } + ); + assert_eq!(s.tx_buffer.len(), 0); + // Second roundtrip. + s.send_slice(b"foobar").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"foobar"[..], + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6), + ..SEND_TEMPL + } + ); + assert_eq!(s.tx_buffer.len(), 0); + } + + #[test] + fn test_established_send_no_ack_send() { + let mut s = socket_established(); + s.set_nagle_enabled(false); + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + s.send_slice(b"foobar").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"foobar"[..], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_established_send_buf_gt_win() { + let mut data = [0; 32]; + for (i, elem) in data.iter_mut().enumerate() { + *elem = i as u8 + } + + let mut s = socket_established(); + s.remote_win_len = 16; + s.send_slice(&data[..]).unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &data[0..16], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_established_send_window_shrink() { + let mut s = socket_established(); + + // 6 octets fit on the remote side's window, so we send them. + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + assert_eq!(s.tx_buffer.len(), 6); + + println!( + "local_seq_no={} remote_win_len={} remote_last_seq={}", + s.local_seq_no, s.remote_win_len, s.remote_last_seq + ); + + // - Peer doesn't ack them yet + // - Sends data so we need to reply with an ACK + // - ...AND and sends a window announcement that SHRINKS the window, so data we've + // previously sent is now outside the window. Yes, this is allowed by TCP. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 3, + payload: &b"xyzxyz"[..], + ..SEND_TEMPL + } + ); + assert_eq!(s.tx_buffer.len(), 6); + + println!( + "local_seq_no={} remote_win_len={} remote_last_seq={}", + s.local_seq_no, s.remote_win_len, s.remote_last_seq + ); + + // More data should not get sent since it doesn't fit in the window + s.send_slice(b"foobar").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 64 - 6, + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_established_receive_partially_outside_window() { + let mut s = socket_established(); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + + // Peer decides to retransmit (perhaps because the ACK was lost) + // and also pushed data. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + + s.recv(|data| { + assert_eq!(data, b"def"); + (3, ()) + }) + .unwrap(); + } + + #[test] + fn test_established_receive_partially_outside_window_fin() { + let mut s = socket_established(); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + + // Peer decides to retransmit (perhaps because the ACK was lost) + // and also pushed data, and sent a FIN. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + control: TcpControl::Fin, + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + + s.recv(|data| { + assert_eq!(data, b"def"); + (3, ()) + }) + .unwrap(); + + // We should accept the FIN, because even though the last packet was partially + // outside the receive window, there is no hole after adding its data to the assembler. + assert_eq!(s.state, State::CloseWait); + } + + #[test] + fn test_established_send_wrap() { + let mut s = socket_established(); + let local_seq_start = TcpSeqNumber(i32::MAX - 1); + s.local_seq_no = local_seq_start + 1; + s.remote_last_seq = local_seq_start + 1; + s.send_slice(b"abc").unwrap(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: local_seq_start + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_established_no_ack() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: None, + ..SEND_TEMPL + } + ); + } + + #[test] + fn test_established_bad_ack() { + let mut s = socket_established(); + // Already acknowledged data. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(TcpSeqNumber(LOCAL_SEQ.0 - 1)), + ..SEND_TEMPL + } + ); + assert_eq!(s.local_seq_no, LOCAL_SEQ + 1); + // Data not yet transmitted. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 10), + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + assert_eq!(s.local_seq_no, LOCAL_SEQ + 1); + } + + #[test] + fn test_established_bad_seq() { + let mut s = socket_established(); + // Data outside of receive window. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 256, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1); + + // Challenge ACKs are rate-limited, we don't get a second one immediately. + send!( + s, + time 100, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 256, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + + // If we wait a bit, we do get a new one. + send!( + s, + time 2000, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 256, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1); + } + + #[test] + fn test_established_fin() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::CloseWait); + sanity!(s, socket_close_wait()); + } + + #[test] + fn test_established_fin_after_missing() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1 + 6, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"123456"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + assert_eq!(s.state, State::Established); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6 + 6), + window_len: 52, + ..RECV_TEMPL + }) + ); + assert_eq!(s.state, State::Established); + } + + #[test] + fn test_established_send_fin() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::CloseWait); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_established_rst() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_established_rst_no_ack() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1, + ack_number: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_established_close() { + let mut s = socket_established(); + s.close(); + assert_eq!(s.state, State::FinWait1); + sanity!(s, socket_fin_wait_1()); + } + + #[test] + fn test_established_abort() { + let mut s = socket_established(); + s.abort(); + assert_eq!(s.state, State::Closed); + recv!( + s, + [TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_established_rst_bad_seq() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ, // Wrong seq + ack_number: None, + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + + assert_eq!(s.state, State::Established); + + // Send something to advance seq by 1 + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, // correct seq + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"a"[..], + ..SEND_TEMPL + } + ); + + // Send wrong rst again, check that the challenge ack is correctly updated + // The ack number must be updated even if we don't call dispatch on the socket + // See https://github.com/smoltcp-rs/smoltcp/issues/338 + send!( + s, + time 2000, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ, // Wrong seq + ack_number: None, + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 2), // this has changed + window_len: 63, + ..RECV_TEMPL + }) + ); + } + + // =========================================================================================// + // Tests for the FIN-WAIT-1 state. + // =========================================================================================// + + #[test] + fn test_fin_wait_1_fin_ack() { + let mut s = socket_fin_wait_1(); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::FinWait2); + sanity!(s, socket_fin_wait_2()); + } + + #[test] + fn test_fin_wait_1_fin_fin() { + let mut s = socket_fin_wait_1(); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closing); + sanity!(s, socket_closing()); + } + + #[test] + fn test_fin_wait_1_fin_with_data_queued() { + let mut s = socket_established(); + s.remote_win_len = 6; + s.send_slice(b"abcdef123456").unwrap(); + s.close(); + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }) + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::FinWait1); + } + + #[test] + fn test_fin_wait_1_recv() { + let mut s = socket_fin_wait_1(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::FinWait1); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + } + + #[test] + fn test_fin_wait_1_close() { + let mut s = socket_fin_wait_1(); + s.close(); + assert_eq!(s.state, State::FinWait1); + } + + // =========================================================================================// + // Tests for the FIN-WAIT-2 state. + // =========================================================================================// + + #[test] + fn test_fin_wait_2_fin() { + let mut s = socket_fin_wait_2(); + send!(s, time 1_000, TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + }); + assert_eq!(s.state, State::TimeWait); + sanity!(s, socket_time_wait(false)); + } + + #[test] + fn test_fin_wait_2_recv() { + let mut s = socket_fin_wait_2(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::FinWait2); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_fin_wait_2_close() { + let mut s = socket_fin_wait_2(); + s.close(); + assert_eq!(s.state, State::FinWait2); + } + + // =========================================================================================// + // Tests for the CLOSING state. + // =========================================================================================// + + #[test] + fn test_closing_ack_fin() { + let mut s = socket_closing(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + send!(s, time 1_000, TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + }); + assert_eq!(s.state, State::TimeWait); + sanity!(s, socket_time_wait(true)); + } + + #[test] + fn test_closing_close() { + let mut s = socket_closing(); + s.close(); + assert_eq!(s.state, State::Closing); + } + + // =========================================================================================// + // Tests for the TIME-WAIT state. + // =========================================================================================// + + #[test] + fn test_time_wait_from_fin_wait_2_ack() { + let mut s = socket_time_wait(false); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_time_wait_from_closing_no_ack() { + let mut s = socket_time_wait(true); + recv!(s, []); + } + + #[test] + fn test_time_wait_close() { + let mut s = socket_time_wait(false); + s.close(); + assert_eq!(s.state, State::TimeWait); + } + + #[test] + fn test_time_wait_retransmit() { + let mut s = socket_time_wait(false); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + send!(s, time 5_000, TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + }, Some(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + })); + assert_eq!( + s.timer, + Timer::Close { + expires_at: Instant::from_secs(5) + CLOSE_DELAY + } + ); + } + + #[test] + fn test_time_wait_timeout() { + let mut s = socket_time_wait(false); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::TimeWait); + recv_nothing!(s, time 60_000); + assert_eq!(s.state, State::Closed); + } + + // =========================================================================================// + // Tests for the CLOSE-WAIT state. + // =========================================================================================// + + #[test] + fn test_close_wait_ack() { + let mut s = socket_close_wait(); + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + ..SEND_TEMPL + } + ); + } + + #[test] + fn test_close_wait_close() { + let mut s = socket_close_wait(); + s.close(); + assert_eq!(s.state, State::LastAck); + sanity!(s, socket_last_ack()); + } + + // =========================================================================================// + // Tests for the LAST-ACK state. + // =========================================================================================// + #[test] + fn test_last_ack_fin_ack() { + let mut s = socket_last_ack(); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::LastAck); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_last_ack_ack_not_of_fin() { + let mut s = socket_last_ack(); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::LastAck); + + // A duplicate ACK (ack_number == SND.UNA, not the FIN ACK) must elicit a + // challenge ACK per RFC 9293 §3.10.7.4 and must keep the state in LAST-ACK. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }) + ); + assert_eq!(s.state, State::LastAck); + + // ACK received of fin: socket should change to Closed. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + // RFC 9293 §3.10.7.4: duplicate ACK in LAST-ACK must elicit a challenge ACK, + // not be silently dropped. + #[test] + fn test_last_ack_duplicate_ack_challenge_ack() { + let mut s = socket_last_ack(); + // Trigger dispatch so our FIN is sent and remote_last_seq advances. + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::LastAck); + + // Remote re-sends an ACK for SND.UNA (not the FIN). RFC 9293 requires a + // challenge ACK in response so the remote can learn the current state. + let challenge = send( + &mut s, + Instant::from_millis(0), + &TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }, + ); + assert_eq!( + challenge, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }), + "expected challenge ACK in response to duplicate ACK in LAST-ACK" + ); + // State must remain LAST-ACK: we have not received the FIN ACK. + assert_eq!(s.state, State::LastAck); + + // A second duplicate in the same second is rate-limited; the FIN ACK + // must still be correctly accepted regardless. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + // A partial ACK in LAST-ACK (ack_len > 0 but not FIN ACK) advances SND.UNA + // without a challenge ACK; the FIN will be retransmitted by the timer. + #[test] + fn test_last_ack_partial_ack_no_challenge_ack() { + // Build a LAST-ACK socket that has one byte of data still unacknowledged + // before the FIN. We manually wire the state so we can send a partial ACK. + let mut s = socket_last_ack(); + // Push one byte into the tx buffer to simulate data that preceded the FIN. + let _ = s.tx_buffer.enqueue_slice(b"x"); + // Mark it as already sent (remote_last_seq is past the data byte and the FIN). + s.remote_last_seq = LOCAL_SEQ + 1 + 1 + 1; // data(1) + FIN(1) + + // Remote ACKs just the data byte, not the FIN (partial ACK). + // ack_number = local_seq_no + 1 => ack_len = 1, ack_of_fin = false. + // Per RFC 9293, a valid partial ACK should advance SND.UNA normally; + // no challenge ACK should be emitted. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), // acks the data byte, not FIN + ..SEND_TEMPL + } + ); + // State remains LAST-ACK; FIN retransmission is handled by the timer. + assert_eq!(s.state, State::LastAck); + // SND.UNA has advanced to the partial ACK number. + assert_eq!(s.local_seq_no, LOCAL_SEQ + 1 + 1); + } + + #[test] + fn test_last_ack_close() { + let mut s = socket_last_ack(); + s.close(); + assert_eq!(s.state, State::LastAck); + } + + // =========================================================================================// + // Tests for transitioning through multiple states. + // =========================================================================================// + + #[test] + fn test_listen() { + let mut s = socket(); + s.listen(LISTEN_END).unwrap(); + assert_eq!(s.state, State::Listen); + } + + #[test] + fn test_three_way_handshake() { + let mut s = socket_listen(); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::SynReceived); + assert_eq!(s.tuple, Some(TUPLE)); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::Established); + assert_eq!(s.local_seq_no, LOCAL_SEQ + 1); + assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1); + } + + #[test] + fn test_remote_close() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::CloseWait); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + s.close(); + assert_eq!(s.state, State::LastAck); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_local_close() { + let mut s = socket_established(); + s.close(); + assert_eq!(s.state, State::FinWait1); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::FinWait2); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_simultaneous_close() { + let mut s = socket_established(); + s.close(); + assert_eq!(s.state, State::FinWait1); + recv!( + s, + [TcpRepr { + // due to reordering, this is logically located... + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closing); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + // ... at this point + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + recv!(s, []); + } + + #[test] + fn test_simultaneous_close_combined_fin_ack() { + let mut s = socket_established(); + s.close(); + assert_eq!(s.state, State::FinWait1); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_simultaneous_close_raced() { + let mut s = socket_established(); + s.close(); + assert_eq!(s.state, State::FinWait1); + + // Socket receives FIN before it has a chance to send its own FIN + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closing); + + // FIN + ack-of-FIN + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::Closing); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + recv!(s, []); + } + + #[test] + fn test_simultaneous_close_raced_with_data() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + s.close(); + assert_eq!(s.state, State::FinWait1); + + // Socket receives FIN before it has a chance to send its own data+FIN + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closing); + + // data + FIN + ack-of-FIN + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::Closing); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + recv!(s, []); + } + + #[test] + fn test_fin_with_data() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + s.close(); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ) + } + + #[test] + fn test_mutual_close_with_data_1() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + s.close(); + assert_eq!(s.state, State::FinWait1); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 1), + ..SEND_TEMPL + } + ); + } + + #[test] + fn test_mutual_close_with_data_2() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + s.close(); + assert_eq!(s.state, State::FinWait1); + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::FinWait2); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 1), + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + }] + ); + assert_eq!(s.state, State::TimeWait); + } + + // =========================================================================================// + // Tests for retransmission on packet loss. + // =========================================================================================// + + #[test] + fn test_duplicate_seq_ack() { + let mut s = socket_recved(); + // remote retransmission + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 58, + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_data_retransmit() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + recv_nothing!(s, time 1050); + recv!(s, time 2000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_data_retransmit_bursts() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef012345").unwrap(); + + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + recv_nothing!(s, time 0); + + recv_nothing!(s, time 50); + + recv!(s, time 1000, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 1500, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + recv_nothing!(s, time 1550); + } + + #[test] + fn test_data_retransmit_bursts_half_ack() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef012345").unwrap(); + + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + // Acknowledge the first packet + send!(s, time 5, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + window_len: 6, + ..SEND_TEMPL + }); + // The second packet should be re-sent. + recv!(s, time 1500, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + + recv_nothing!(s, time 1550); + } + + #[test] + fn test_retransmit_timer_restart_on_partial_ack() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef012345").unwrap(); + + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + // Acknowledge the first packet + send!(s, time 600, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + window_len: 6, + ..SEND_TEMPL + }); + // The ACK of the first packet should restart the retransmit timer and delay a retransmission. + recv_nothing!(s, time 2399); + // The second packet should be re-sent. + recv!(s, time 2400, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + } + + #[test] + fn test_data_retransmit_bursts_half_ack_close() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef012345").unwrap(); + s.close(); + + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + // Acknowledge the first packet + send!(s, time 5, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + window_len: 6, + ..SEND_TEMPL + }); + // The second packet should be re-sent. + recv!(s, time 1500, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"012345"[..], + ..RECV_TEMPL + }), exact); + + recv_nothing!(s, time 1550); + } + + #[test] + fn test_send_data_after_syn_ack_retransmit() { + let mut s = socket_syn_received(); + recv!(s, time 50, Ok(TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + })); + recv!(s, time 1050, Ok(TcpRepr { // retransmit + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + })); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::Established); + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ) + } + + #[test] + fn test_established_retransmit_for_dup_ack() { + let mut s = socket_established(); + // Duplicate ACKs do not replace the retransmission timer + s.send_slice(b"abc").unwrap(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + // Retransmit timer is on because all data was sent + assert_eq!(s.tx_buffer.len(), 3); + // ACK nothing new + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // Retransmit + recv!(s, time 4000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_established_retransmit_reset_after_ack() { + let mut s = socket_established(); + s.remote_win_len = 6; + s.send_slice(b"abcdef").unwrap(); + s.send_slice(b"123456").unwrap(); + s.send_slice(b"ABCDEF").unwrap(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + send!(s, time 1005, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + window_len: 6, + ..SEND_TEMPL + }); + recv!(s, time 1010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"123456"[..], + ..RECV_TEMPL + })); + send!(s, time 1015, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6), + window_len: 6, + ..SEND_TEMPL + }); + recv!(s, time 1020, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"ABCDEF"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_established_queue_during_retransmission() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef123456ABCDEF").unwrap(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); // this one is dropped + recv!(s, time 1005, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"123456"[..], + ..RECV_TEMPL + })); // this one is received + recv!(s, time 1010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"ABCDEF"[..], + ..RECV_TEMPL + })); // also dropped + recv!(s, time 3000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); // retransmission + send!(s, time 3005, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6), + ..SEND_TEMPL + }); // acknowledgement of both segments + recv!(s, time 3010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"ABCDEF"[..], + ..RECV_TEMPL + })); // retransmission of only unacknowledged data + } + + #[test] + fn test_close_wait_retransmit_reset_after_ack() { + let mut s = socket_close_wait(); + s.remote_win_len = 6; + s.send_slice(b"abcdef").unwrap(); + s.send_slice(b"123456").unwrap(); + s.send_slice(b"ABCDEF").unwrap(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + send!(s, time 1005, TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + window_len: 6, + ..SEND_TEMPL + }); + recv!(s, time 1010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1 + 1), + payload: &b"123456"[..], + ..RECV_TEMPL + })); + send!(s, time 1015, TcpRepr { + seq_number: REMOTE_SEQ + 1 + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6), + window_len: 6, + ..SEND_TEMPL + }); + recv!(s, time 1020, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1 + 1), + payload: &b"ABCDEF"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_fin_wait_1_retransmit_reset_after_ack() { + let mut s = socket_established(); + s.remote_win_len = 6; + s.send_slice(b"abcdef").unwrap(); + s.send_slice(b"123456").unwrap(); + s.send_slice(b"ABCDEF").unwrap(); + s.close(); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + send!(s, time 1005, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + window_len: 6, + ..SEND_TEMPL + }); + recv!(s, time 1010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"123456"[..], + ..RECV_TEMPL + })); + send!(s, time 1015, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6), + window_len: 6, + ..SEND_TEMPL + }); + recv!(s, time 1020, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"ABCDEF"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_fast_retransmit_after_triple_duplicate_ack() { + let mut s = socket_established(); + s.remote_mss = 6; + + // Normal ACK of previously received segment + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + + // Send a long string of text divided into several packets + // because of previously received "window_len" + s.send_slice(b"xxxxxxyyyyyywwwwwwzzzzzz").unwrap(); + // This packet is lost + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"xxxxxx"[..], + ..RECV_TEMPL + })); + recv!(s, time 1005, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"yyyyyy"[..], + ..RECV_TEMPL + })); + recv!(s, time 1010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + (6 * 2), + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"wwwwww"[..], + ..RECV_TEMPL + })); + recv!(s, time 1015, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + (6 * 3), + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"zzzzzz"[..], + ..RECV_TEMPL + })); + + // First duplicate ACK + send!(s, time 1050, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + // Second duplicate ACK + send!(s, time 1055, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + // Third duplicate ACK + // Should trigger a fast retransmit of dropped packet + send!(s, time 1060, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + + // Fast retransmit packet + recv!(s, time 1100, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"xxxxxx"[..], + ..RECV_TEMPL + })); + + recv!(s, time 1105, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"yyyyyy"[..], + ..RECV_TEMPL + })); + recv!(s, time 1110, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + (6 * 2), + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"wwwwww"[..], + ..RECV_TEMPL + })); + recv!(s, time 1115, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + (6 * 3), + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"zzzzzz"[..], + ..RECV_TEMPL + })); + + // After all was send out, enter *normal* retransmission, + // don't stay in fast retransmission. + assert!(match s.timer { + Timer::Retransmit { expires_at, .. } => expires_at > Instant::from_millis(1115), + _ => false, + }); + + // ACK all received segments + send!(s, time 1120, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + (6 * 4)), + ..SEND_TEMPL + }); + } + + #[test] + fn test_fast_retransmit_duplicate_detection_with_data() { + let mut s = socket_established(); + + s.send_slice(b"abc").unwrap(); // This is lost + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + + // Normal ACK of previously received segment + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // First duplicate + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // Second duplicate + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + + assert_eq!(s.local_rx_dup_acks, 2, "duplicate ACK counter is not set"); + + // This packet has content, hence should not be detected + // as a duplicate ACK and should reset the duplicate ACK count + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"xxxxxx"[..], + ..SEND_TEMPL + } + ); + + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 3, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 58, + ..RECV_TEMPL + }] + ); + + assert_eq!( + s.local_rx_dup_acks, 0, + "duplicate ACK counter is not reset when receiving data" + ); + } + + #[test] + fn test_fast_retransmit_duplicate_detection_with_window_update() { + let mut s = socket_established(); + + s.send_slice(b"abc").unwrap(); // This is lost + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + + // Normal ACK of previously received segment + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // First duplicate + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // Second duplicate + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + + assert_eq!(s.local_rx_dup_acks, 2, "duplicate ACK counter is not set"); + + // This packet has a window update, hence should not be detected + // as a duplicate ACK and should reset the duplicate ACK count + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 400, + ..SEND_TEMPL + } + ); + + assert_eq!( + s.local_rx_dup_acks, 0, + "duplicate ACK counter is not reset when receiving a window update" + ); + } + + #[test] + fn test_fast_retransmit_duplicate_detection() { + let mut s = socket_established(); + s.remote_mss = 6; + + // Normal ACK of previously received segment + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + + // First duplicate, should not be counted as there is nothing to resend + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + + assert_eq!( + s.local_rx_dup_acks, 0, + "duplicate ACK counter is set but wound not transmit data" + ); + + // Send a long string of text divided into several packets + // because of small remote_mss + s.send_slice(b"xxxxxxyyyyyywwwwwwzzzzzz").unwrap(); + + // This packet is reordered in network + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"xxxxxx"[..], + ..RECV_TEMPL + })); + recv!(s, time 1005, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"yyyyyy"[..], + ..RECV_TEMPL + })); + recv!(s, time 1010, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + (6 * 2), + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"wwwwww"[..], + ..RECV_TEMPL + })); + recv!(s, time 1015, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + (6 * 3), + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"zzzzzz"[..], + ..RECV_TEMPL + })); + + // First duplicate ACK + send!(s, time 1050, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + // Second duplicate ACK + send!(s, time 1055, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + // Reordered packet arrives which should reset duplicate ACK count + send!(s, time 1060, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + (6 * 3)), + ..SEND_TEMPL + }); + + assert_eq!( + s.local_rx_dup_acks, 0, + "duplicate ACK counter is not reset when receiving ACK which updates send window" + ); + + // ACK all received segments + send!(s, time 1120, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + (6 * 4)), + ..SEND_TEMPL + }); + } + + #[test] + fn test_fast_retransmit_dup_acks_counter() { + let mut s = socket_established(); + + s.send_slice(b"abc").unwrap(); // This is lost + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + + // A lot of retransmits happen here + s.local_rx_dup_acks = u8::MAX - 1; + + // Send 3 more ACKs, which could overflow local_rx_dup_acks, + // but intended behaviour is that we saturate the bounds + // of local_rx_dup_acks + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + send!(s, time 0, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + assert_eq!( + s.local_rx_dup_acks, + u8::MAX, + "duplicate ACK count should not overflow but saturate" + ); + } + + #[test] + fn test_fast_retransmit_zero_window() { + let mut s = socket_established(); + + send!(s, time 1000, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + + s.send_slice(b"abc").unwrap(); + + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + })); + + // 3 dup acks + send!(s, time 1050, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + send!(s, time 1050, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + send!(s, time 1050, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, // boom + ..SEND_TEMPL + }); + + // even though we're in "fast retransmit", we shouldn't + // force-send anything because the remote's window is full. + recv_nothing!(s); + } + + #[test] + fn test_retransmit_exponential_backoff() { + let mut s = socket_established(); + s.send_slice(b"abcdef").unwrap(); + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + + let expected_retransmission_instant = s.rtte.retransmission_timeout().total_millis() as i64; + recv_nothing!(s, time expected_retransmission_instant - 1); + recv!(s, time expected_retransmission_instant, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + + // "current time" is expected_retransmission_instant, and we want to wait 2 * retransmission timeout + let expected_retransmission_instant = 3 * expected_retransmission_instant; + + recv_nothing!(s, time expected_retransmission_instant - 1); + recv!(s, time expected_retransmission_instant, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + } + + #[test] + fn test_data_retransmit_ack_more_than_expected() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"aaaaaabbbbbbcccccc").unwrap(); + + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"aaaaaa"[..], + ..RECV_TEMPL + })); + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"bbbbbb"[..], + ..RECV_TEMPL + })); + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 12, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"cccccc"[..], + ..RECV_TEMPL + })); + recv_nothing!(s, time 0); + + recv_nothing!(s, time 50); + + // retransmit timer expires, we want to retransmit all 3 packets + // but we only manage to retransmit 2 (due to e.g. lack of device buffer space) + assert!(s.timer.is_retransmit()); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"aaaaaa"[..], + ..RECV_TEMPL + })); + recv!(s, time 1000, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"bbbbbb"[..], + ..RECV_TEMPL + })); + + // ack first packet. + send!( + s, + time 3000, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + ..SEND_TEMPL + } + ); + + // this should keep retransmit timer on, because there's + // still unacked data. + assert!(s.timer.is_retransmit()); + + // ack all three packets. + // This might confuse the TCP stack because after the retransmit + // it "thinks" the 3rd packet hasn't been transmitted yet, but it is getting acked. + send!( + s, + time 3000, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 18), + ..SEND_TEMPL + } + ); + + // this should exit retransmit mode. + assert!(!s.timer.is_retransmit()); + // and consider all data ACKed. + assert!(s.tx_buffer.is_empty()); + recv_nothing!(s, time 5000); + } + + #[test] + fn test_retransmit_fin() { + let mut s = socket_established(); + s.close(); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + + recv_nothing!(s, time 999); + recv!(s, time 1000, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + } + + #[test] + fn test_retransmit_fin_wait() { + let mut s = socket_fin_wait_1(); + // we send FIN + recv!( + s, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }] + ); + // remote also sends FIN, does NOT ack ours. + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // we ack it + recv!( + s, + [TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 2, + ack_number: Some(REMOTE_SEQ + 2), + ..RECV_TEMPL + }] + ); + + // we haven't got an ACK for our FIN, we should retransmit. + recv_nothing!(s, time 999); + recv!( + s, + time 1000, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 2), + ..RECV_TEMPL + }] + ); + recv_nothing!(s, time 2999); + recv!( + s, + time 3000, + [TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 2), + ..RECV_TEMPL + }] + ); + } + + // =========================================================================================// + // Tests for window management. + // =========================================================================================// + + #[test] + fn test_maximum_segment_size() { + let mut s = socket_listen(); + s.tx_buffer = SocketBuffer::new(vec![0; 32767]); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + max_seg_size: Some(1000), + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 32767, + ..SEND_TEMPL + } + ); + s.send_slice(&[0; 1200][..]).unwrap(); + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &[0; 1000][..], + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_recv_out_of_recv_win() { + let mut s = socket_established(); + s.set_ack_delay(Some(ACK_DELAY_DEFAULT)); + s.remote_mss = 32; + + // No ACKs are sent due to the ACK delay. + send!( + s, + TcpRepr { + control: TcpControl::Psh, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[0; 32], + ..SEND_TEMPL + } + ); + recv_nothing!(s); + + // RMSS+1 bytes of data has been received, so ACK is sent without delay. + send!( + s, + TcpRepr { + control: TcpControl::Psh, + seq_number: REMOTE_SEQ + 33, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[0; 1], + ..SEND_TEMPL + } + ); + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 34), + window_len: 31, + ..RECV_TEMPL + }) + ); + + // This frees up a byte in the receive buffer. However, the remote shouldn't be aware of + // this since no ACKs are sent. + s.recv_slice(&mut [0; 1]).unwrap(); + recv_nothing!(s); + + // Now, if the remote wants to send one byte outside of the receive window that we + // previously advertised, it should not succeed. + send!( + s, + TcpRepr { + control: TcpControl::Psh, + seq_number: REMOTE_SEQ + 34, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[0; 32], + ..SEND_TEMPL + } + ); + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 65), + window_len: 1, // The last byte isn't accepted. + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_close_wait_no_window_update() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[1, 2, 3, 4], + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::CloseWait); + + // we ack the FIN, with the reduced window size. + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 6), + window_len: 60, + ..RECV_TEMPL + }) + ); + + let rx_buf = &mut [0; 32]; + assert_eq!(s.recv_slice(rx_buf), Ok(4)); + + // check that we do NOT send a window update even if it has changed. + recv_nothing!(s); + } + + #[test] + fn test_time_wait_no_window_update() { + let mut s = socket_fin_wait_2(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 2), + payload: &[1, 2, 3, 4], + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + + // we ack the FIN, with the reduced window size. + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 2, + ack_number: Some(REMOTE_SEQ + 6), + window_len: 60, + ..RECV_TEMPL + }) + ); + + let rx_buf = &mut [0; 32]; + assert_eq!(s.recv_slice(rx_buf), Ok(4)); + + // check that we do NOT send a window update even if it has changed. + recv_nothing!(s); + } + + // =========================================================================================// + // Tests for flow control. + // =========================================================================================// + + #[test] + fn test_psh_transmit() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef").unwrap(); + s.send_slice(b"123456").unwrap(); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Psh, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"123456"[..], + ..RECV_TEMPL + }), exact); + } + + #[test] + fn test_psh_receive() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Psh, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 58, + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_zero_window_ack() { + let mut s = socket_established(); + s.rx_buffer = SocketBuffer::new(vec![0; 6]); + s.assembler = Assembler::new(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 0, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 6, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"123456"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 0, + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_zero_window_fin() { + let mut s = socket_established(); + s.rx_buffer = SocketBuffer::new(vec![0; 6]); + s.assembler = Assembler::new(); + s.ack_delay = None; + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 0, + ..RECV_TEMPL + }] + ); + + // Even though the sequence space for the FIN itself is outside the window, + // it is not data, so FIN must be accepted when window full. + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 6, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[], + control: TcpControl::Fin, + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::CloseWait); + + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 7), + window_len: 0, + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_zero_window_ack_on_window_growth() { + let mut s = socket_established(); + s.rx_buffer = SocketBuffer::new(vec![0; 6]); + s.assembler = Assembler::new(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 0, + ..RECV_TEMPL + }] + ); + recv_nothing!(s, time 0); + s.recv(|buffer| { + assert_eq!(&buffer[..3], b"abc"); + (3, ()) + }) + .unwrap(); + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 3, + ..RECV_TEMPL + })); + recv_nothing!(s, time 0); + s.recv(|buffer| { + assert_eq!(buffer, b"def"); + (buffer.len(), ()) + }) + .unwrap(); + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 6, + ..RECV_TEMPL + })); + } + + #[test] + fn test_window_update_with_delay_ack() { + let mut s = socket_established_with_buffer_sizes(6, 6); + s.ack_delay = Some(Duration::from_millis(10)); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 5); + + s.recv(|buffer| { + assert_eq!(&buffer[..2], b"ab"); + (2, ()) + }) + .unwrap(); + recv!( + s, + time 5, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 2, + ..RECV_TEMPL + }) + ); + + s.recv(|buffer| { + assert_eq!(&buffer[..1], b"c"); + (1, ()) + }) + .unwrap(); + recv_nothing!(s, time 5); + + s.recv(|buffer| { + assert_eq!(&buffer[..1], b"d"); + (1, ()) + }) + .unwrap(); + recv!( + s, + time 5, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 4, + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_fill_peer_window() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + recv!( + s, + [ + TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }, + TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"123456"[..], + ..RECV_TEMPL + }, + TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"!@#$%^"[..], + ..RECV_TEMPL + } + ] + ); + } + + #[test] + fn test_announce_window_after_read() { + let mut s = socket_established(); + s.rx_buffer = SocketBuffer::new(vec![0; 6]); + s.assembler = Assembler::new(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + window_len: 3, + ..RECV_TEMPL + }] + ); + // Test that `dispatch` updates `remote_last_win` + assert_eq!(s.remote_last_win, s.rx_buffer.window() as u16); + s.recv(|buffer| (buffer.len(), ())).unwrap(); + assert!(s.window_to_update()); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + window_len: 6, + ..RECV_TEMPL + }] + ); + assert_eq!(s.remote_last_win, s.rx_buffer.window() as u16); + // Provoke immediate ACK to test that `process` updates `remote_last_win` + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 6, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"def"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + window_len: 6, + ..RECV_TEMPL + }) + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 3, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 9), + window_len: 0, + ..RECV_TEMPL + }) + ); + assert_eq!(s.remote_last_win, s.rx_buffer.window() as u16); + s.recv(|buffer| (buffer.len(), ())).unwrap(); + assert!(s.window_to_update()); + } + + // =========================================================================================// + // Tests for zero-window probes. + // =========================================================================================// + + #[test] + fn test_zero_window_probe_enter_on_win_update() { + let mut s = socket_established(); + + assert!(!s.timer.is_zero_window_probe()); + + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + + assert!(!s.timer.is_zero_window_probe()); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + assert!(s.timer.is_zero_window_probe()); + } + + #[test] + fn test_zero_window_probe_enter_on_send() { + let mut s = socket_established(); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + assert!(!s.timer.is_zero_window_probe()); + + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + + assert!(s.timer.is_zero_window_probe()); + } + + #[test] + fn test_zero_window_probe_exit() { + let mut s = socket_established(); + + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + + assert!(!s.timer.is_zero_window_probe()); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + assert!(s.timer.is_zero_window_probe()); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 6, + ..SEND_TEMPL + } + ); + + assert!(!s.timer.is_zero_window_probe()); + } + + #[test] + fn test_zero_window_probe_exit_ack() { + let mut s = socket_established(); + + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + recv!( + s, + time 1000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + + send!( + s, + time 1010, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 2), + window_len: 6, + ..SEND_TEMPL + } + ); + + recv!( + s, + time 1010, + [TcpRepr { + seq_number: LOCAL_SEQ + 2, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"bcdef1"[..], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_zero_window_probe_backoff_nack_reply() { + let mut s = socket_established(); + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 999); + recv!( + s, + time 1000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + send!( + s, + time 1100, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 2999); + recv!( + s, + time 3000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + send!( + s, + time 3100, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 6999); + recv!( + s, + time 7000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_zero_window_probe_backoff_no_reply() { + let mut s = socket_established(); + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 999); + recv!( + s, + time 1000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + + recv_nothing!(s, time 2999); + recv!( + s, + time 3000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_zero_window_probe_shift() { + let mut s = socket_established(); + + s.send_slice(b"abcdef123456!@#$%^").unwrap(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + window_len: 0, + ..SEND_TEMPL + } + ); + + recv_nothing!(s, time 999); + recv!( + s, + time 1000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + + recv_nothing!(s, time 2999); + recv!( + s, + time 3000, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"a"[..], + ..RECV_TEMPL + }] + ); + + // ack the ZWP byte, but still advertise zero window. + // this should restart the ZWP timer. + send!( + s, + time 3100, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 2), + window_len: 0, + ..SEND_TEMPL + } + ); + + // ZWP should be sent at 3100+1000 = 4100 + recv_nothing!(s, time 4099); + recv!( + s, + time 4100, + [TcpRepr { + seq_number: LOCAL_SEQ + 2, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"b"[..], + ..RECV_TEMPL + }] + ); + } + + // =========================================================================================// + // Tests for timeouts. + // =========================================================================================// + + #[test] + fn test_listen_timeout() { + let mut s = socket_listen(); + s.set_timeout(Some(Duration::from_millis(100))); + assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Ingress); + } + + #[test] + fn test_connect_timeout() { + let mut s = socket(); + s.local_seq_no = LOCAL_SEQ; + s.socket + .connect(&mut s.cx, REMOTE_END, LOCAL_END.port) + .unwrap(); + s.set_timeout(Some(Duration::from_millis(100))); + recv!(s, time 150, Ok(TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + })); + assert_eq!(s.state, State::SynSent); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(250)) + ); + recv!(s, time 250, Ok(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(TcpSeqNumber(0)), + window_scale: None, + ..RECV_TEMPL + })); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_established_timeout() { + let mut s = socket_established(); + s.set_timeout(Some(Duration::from_millis(2000))); + recv_nothing!(s, time 250); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(2250)) + ); + s.send_slice(b"abcdef").unwrap(); + assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Now); + recv!(s, time 255, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(1255)) + ); + recv!(s, time 1255, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + })); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(2255)) + ); + recv!(s, time 2255, Ok(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_established_keep_alive_timeout() { + let mut s = socket_established(); + s.set_keep_alive(Some(Duration::from_millis(50))); + s.set_timeout(Some(Duration::from_millis(100))); + recv!(s, time 100, Ok(TcpRepr { + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + payload: &[0], + ..RECV_TEMPL + })); + recv_nothing!(s, time 100); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(150)) + ); + send!(s, time 105, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(155)) + ); + recv!(s, time 155, Ok(TcpRepr { + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + payload: &[0], + ..RECV_TEMPL + })); + recv_nothing!(s, time 155); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(205)) + ); + recv_nothing!(s, time 200); + recv!(s, time 205, Ok(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + recv_nothing!(s, time 205); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_fin_wait_1_timeout() { + let mut s = socket_fin_wait_1(); + s.set_timeout(Some(Duration::from_millis(1000))); + recv!(s, time 100, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + recv!(s, time 1100, Ok(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_last_ack_timeout() { + let mut s = socket_last_ack(); + s.set_timeout(Some(Duration::from_millis(1000))); + recv!(s, time 100, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + })); + recv!(s, time 1100, Ok(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1 + 1, + ack_number: Some(REMOTE_SEQ + 1 + 1), + ..RECV_TEMPL + })); + assert_eq!(s.state, State::Closed); + } + + #[test] + fn test_closed_timeout() { + let mut s = socket_established(); + s.set_timeout(Some(Duration::from_millis(200))); + s.remote_last_ts = Some(Instant::from_millis(100)); + s.abort(); + assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Now); + recv!(s, time 100, Ok(TcpRepr { + control: TcpControl::Rst, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + })); + assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Ingress); + } + + // =========================================================================================// + // Tests for keep-alive. + // =========================================================================================// + + #[test] + fn test_responds_to_keep_alive() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_sends_keep_alive() { + let mut s = socket_established(); + s.set_keep_alive(Some(Duration::from_millis(100))); + + // drain the forced keep-alive packet + assert_eq!(s.socket.poll_at(&mut s.cx), PollAt::Now); + recv!(s, time 0, Ok(TcpRepr { + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + payload: &[0], + ..RECV_TEMPL + })); + + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(100)) + ); + recv_nothing!(s, time 95); + recv!(s, time 100, Ok(TcpRepr { + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + payload: &[0], + ..RECV_TEMPL + })); + + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(200)) + ); + recv_nothing!(s, time 195); + recv!(s, time 200, Ok(TcpRepr { + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + payload: &[0], + ..RECV_TEMPL + })); + + send!(s, time 250, TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + }); + assert_eq!( + s.socket.poll_at(&mut s.cx), + PollAt::Time(Instant::from_millis(350)) + ); + recv_nothing!(s, time 345); + recv!(s, time 350, Ok(TcpRepr { + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"\x00"[..], + ..RECV_TEMPL + })); + } + + // =========================================================================================// + // Tests for time-to-live configuration. + // =========================================================================================// + + #[test] + fn test_set_hop_limit() { + let mut s = socket_syn_received(); + + s.set_hop_limit(Some(0x2a)); + assert_eq!( + s.socket.dispatch(&mut s.cx, |_, (ip_repr, _)| { + assert_eq!(ip_repr.hop_limit(), 0x2a); + Ok::<_, ()>(()) + }), + Ok(()) + ); + + // assert that user-configurable settings are kept, + // see https://github.com/smoltcp-rs/smoltcp/issues/601. + s.reset(); + assert_eq!(s.hop_limit(), Some(0x2a)); + } + + #[test] + #[should_panic(expected = "the time-to-live value of a packet must not be zero")] + fn test_set_hop_limit_zero() { + let mut s = socket_syn_received(); + s.set_hop_limit(Some(0)); + } + + // =========================================================================================// + // Tests for reassembly. + // =========================================================================================// + + #[test] + fn test_out_of_order() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 3, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"def"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + ..RECV_TEMPL + }) + ); + s.recv(|buffer| { + assert_eq!(buffer, b""); + (buffer.len(), ()) + }) + .unwrap(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 6), + window_len: 58, + ..RECV_TEMPL + }) + ); + s.recv(|buffer| { + assert_eq!(buffer, b"abcdef"); + (buffer.len(), ()) + }) + .unwrap(); + } + + #[test] + fn test_buffer_wraparound_rx() { + let mut s = socket_established(); + s.rx_buffer = SocketBuffer::new(vec![0; 6]); + s.assembler = Assembler::new(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + s.recv(|buffer| { + assert_eq!(buffer, b"abc"); + (buffer.len(), ()) + }) + .unwrap(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 3, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"defghi"[..], + ..SEND_TEMPL + } + ); + let mut data = [0; 6]; + assert_eq!(s.recv_slice(&mut data[..]), Ok(6)); + assert_eq!(data, &b"defghi"[..]); + } + + #[test] + fn test_buffer_wraparound_tx() { + let mut s = socket_established(); + s.set_nagle_enabled(false); + + s.tx_buffer = SocketBuffer::new(vec![b'.'; 9]); + assert_eq!(s.send_slice(b"xxxyyy"), Ok(6)); + assert_eq!(s.tx_buffer.dequeue_many(3), &b"xxx"[..]); + assert_eq!(s.tx_buffer.len(), 3); + + // "abcdef" not contiguous in tx buffer + assert_eq!(s.send_slice(b"abcdef"), Ok(6)); + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"yyyabc"[..], + ..RECV_TEMPL + }) + ); + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"def"[..], + ..RECV_TEMPL + }) + ); + } + + // =========================================================================================// + // Tests for graceful vs ungraceful rx close + // =========================================================================================// + + #[test] + fn test_rx_close_fin() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + assert_eq!(s.recv(|_| (0, ())), Err(RecvError::Finished)); + } + + #[test] + fn test_rx_close_fin_in_fin_wait_1() { + let mut s = socket_fin_wait_1(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::Closing); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + assert_eq!(s.recv(|_| (0, ())), Err(RecvError::Finished)); + } + + #[test] + fn test_rx_close_fin_in_fin_wait_2() { + let mut s = socket_fin_wait_2(); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + assert_eq!(s.state, State::TimeWait); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + assert_eq!(s.recv(|_| (0, ())), Err(RecvError::Finished)); + } + + #[test] + fn test_rx_close_fin_with_hole() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + send!( + s, + TcpRepr { + control: TcpControl::Fin, + seq_number: REMOTE_SEQ + 1 + 6, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"ghi"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + window_len: 61, + ..RECV_TEMPL + }) + ); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + s.recv(|data| { + assert_eq!(data, b""); + (0, ()) + }) + .unwrap(); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1 + 9, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + // Error must be `Illegal` even if we've received a FIN, + // because we are missing data. + assert_eq!(s.recv(|_| (0, ())), Err(RecvError::InvalidState)); + } + + #[test] + fn test_rx_close_rst() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1 + 3, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + assert_eq!(s.recv(|_| (0, ())), Err(RecvError::InvalidState)); + } + + #[test] + fn test_rx_close_rst_with_hole() { + let mut s = socket_established(); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + 6, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"ghi"[..], + ..SEND_TEMPL + }, + Some(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + window_len: 61, + ..RECV_TEMPL + }) + ); + send!( + s, + TcpRepr { + control: TcpControl::Rst, + seq_number: REMOTE_SEQ + 1 + 9, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + assert_eq!(s.recv(|_| (0, ())), Err(RecvError::InvalidState)); + } + + // =========================================================================================// + // Tests for delayed ACK + // =========================================================================================// + + #[test] + fn test_delayed_ack() { + let mut s = socket_established(); + s.set_ack_delay(Some(ACK_DELAY_DEFAULT)); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + + // No ACK is immediately sent. + recv_nothing!(s); + + // After 10ms, it is sent. + recv!(s, time 11, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + window_len: 61, + ..RECV_TEMPL + })); + } + + #[test] + fn test_delayed_ack_win() { + let mut s = socket_established(); + s.set_ack_delay(Some(ACK_DELAY_DEFAULT)); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + + // Reading the data off the buffer should cause a window update. + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + + // However, no ACK or window update is immediately sent. + recv_nothing!(s); + + // After 10ms, it is sent. + recv!(s, time 11, Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + ..RECV_TEMPL + })); + } + + #[test] + fn test_delayed_ack_reply() { + let mut s = socket_established(); + s.set_ack_delay(Some(ACK_DELAY_DEFAULT)); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abc"[..], + ..SEND_TEMPL + } + ); + + s.recv(|data| { + assert_eq!(data, b"abc"); + (3, ()) + }) + .unwrap(); + + s.send_slice(&b"xyz"[..]).unwrap(); + + // Writing data to the socket causes ACK to not be delayed, + // because it is immediately sent with the data. + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + 3), + payload: &b"xyz"[..], + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_delayed_ack_every_rmss() { + let mut s = socket_established_with_buffer_sizes(DEFAULT_MSS * 2, DEFAULT_MSS * 2); + s.set_ack_delay(Some(ACK_DELAY_DEFAULT)); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[0; DEFAULT_MSS - 1], + ..SEND_TEMPL + } + ); + + // No ACK is immediately sent. + recv_nothing!(s); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + (DEFAULT_MSS - 1), + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"a"[..], + ..SEND_TEMPL + } + ); + + // No ACK is immediately sent. + recv_nothing!(s); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + DEFAULT_MSS, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"a"[..], + ..SEND_TEMPL + } + ); + + // RMSS+1 bytes of data has been received, so ACK is sent without delay. + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + (DEFAULT_MSS + 1)), + window_len: (DEFAULT_MSS - 1) as u16, + ..RECV_TEMPL + }) + ); + } + + #[test] + fn test_delayed_ack_every_rmss_or_more() { + let mut s = socket_established_with_buffer_sizes(DEFAULT_MSS * 2, DEFAULT_MSS * 2); + s.set_ack_delay(Some(ACK_DELAY_DEFAULT)); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &[0; DEFAULT_MSS], + ..SEND_TEMPL + } + ); + + // No ACK is immediately sent. + recv_nothing!(s); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + DEFAULT_MSS, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"a"[..], + ..SEND_TEMPL + } + ); + + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1 + (DEFAULT_MSS + 1), + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"b"[..], + ..SEND_TEMPL + } + ); + + // RMSS+2 bytes of data has been received, so ACK is sent without delay. + recv!( + s, + Ok(TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1 + (DEFAULT_MSS + 2)), + window_len: (DEFAULT_MSS - 2) as u16, + ..RECV_TEMPL + }) + ); + } + + // =========================================================================================// + // Tests for Nagle's Algorithm + // =========================================================================================// + + #[test] + fn test_nagle() { + let mut s = socket_established(); + s.remote_mss = 6; + + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }] + ); + + // If there's data in flight, full segments get sent. + s.send_slice(b"foobar").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"foobar"[..], + ..RECV_TEMPL + }] + ); + + s.send_slice(b"aaabbbccc").unwrap(); + // If there's data in flight, not-full segments don't get sent. + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"aaabbb"[..], + ..RECV_TEMPL + }] + ); + + // Data gets ACKd, so there's no longer data in flight + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6 + 6), + ..SEND_TEMPL + } + ); + + // Now non-full segment gets sent. + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6 + 6 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"ccc"[..], + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_final_packet_in_stream_doesnt_wait_for_nagle() { + let mut s = socket_established(); + s.remote_mss = 6; + s.send_slice(b"abcdef0").unwrap(); + s.socket.close(); + + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::None, + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + ..RECV_TEMPL + }), exact); + recv!(s, time 0, Ok(TcpRepr { + control: TcpControl::Fin, + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"0"[..], + ..RECV_TEMPL + }), exact); + } + + // =========================================================================================// + // Tests for packet filtering. + // =========================================================================================// + + #[test] + fn test_doesnt_accept_wrong_port() { + let mut s = socket_established(); + s.rx_buffer = SocketBuffer::new(vec![0; 6]); + s.assembler = Assembler::new(); + + let tcp_repr = TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + dst_port: LOCAL_PORT + 1, + ..SEND_TEMPL + }; + assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr)); + + let tcp_repr = TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + src_port: REMOTE_PORT + 1, + ..SEND_TEMPL + }; + assert!(!s.socket.accepts(&mut s.cx, &SEND_IP_TEMPL, &tcp_repr)); + } + + #[test] + fn test_doesnt_accept_wrong_ip() { + let mut s = socket_established(); + + let tcp_repr = TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + payload: &b"abcdef"[..], + ..SEND_TEMPL + }; + + let ip_repr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + payload_len: tcp_repr.buffer_len(), + hop_limit: 64, + }); + assert!(s.socket.accepts(&mut s.cx, &ip_repr, &tcp_repr)); + + let ip_repr_wrong_src = IpReprIpvX(IpvXRepr { + src_addr: OTHER_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Tcp, + payload_len: tcp_repr.buffer_len(), + hop_limit: 64, + }); + assert!(!s.socket.accepts(&mut s.cx, &ip_repr_wrong_src, &tcp_repr)); + + let ip_repr_wrong_dst = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: OTHER_ADDR, + next_header: IpProtocol::Tcp, + payload_len: tcp_repr.buffer_len(), + hop_limit: 64, + }); + assert!(!s.socket.accepts(&mut s.cx, &ip_repr_wrong_dst, &tcp_repr)); + } + + // =========================================================================================// + // Timer tests + // =========================================================================================// + + #[test] + fn test_timer_retransmit() { + const RTO: Duration = Duration::from_millis(100); + let mut r = Timer::new(); + assert!(!r.should_retransmit(Instant::from_secs(1))); + r.set_for_retransmit(Instant::from_millis(1000), RTO); + assert!(!r.should_retransmit(Instant::from_millis(1000))); + assert!(!r.should_retransmit(Instant::from_millis(1050))); + assert!(r.should_retransmit(Instant::from_millis(1101))); + r.set_for_retransmit(Instant::from_millis(1101), RTO); + assert!(!r.should_retransmit(Instant::from_millis(1101))); + assert!(!r.should_retransmit(Instant::from_millis(1150))); + assert!(!r.should_retransmit(Instant::from_millis(1200))); + assert!(r.should_retransmit(Instant::from_millis(1301))); + r.set_for_idle(Instant::from_millis(1301), None); + assert!(!r.should_retransmit(Instant::from_millis(1350))); + } + + #[test] + fn test_rtt_estimator() { + let mut r = RttEstimator::default(); + + let rtos = &[ + 6000, 5000, 4252, 3692, 3272, 2956, 2720, 2540, 2408, 2308, 2232, 2176, 2132, 2100, + 2076, 2060, 2048, 2036, 2028, 2024, 2020, 2016, 2012, 2012, + ]; + + for &rto in rtos { + r.sample(2000); + assert_eq!(r.retransmission_timeout(), Duration::from_millis(rto)); + } + } + + #[test] + fn test_set_get_congestion_control() { + let mut s = socket_established(); + + #[cfg(feature = "socket-tcp-reno")] + { + s.set_congestion_control(CongestionControl::Reno); + assert_eq!(s.congestion_control(), CongestionControl::Reno); + } + + #[cfg(feature = "socket-tcp-cubic")] + { + s.set_congestion_control(CongestionControl::Cubic); + assert_eq!(s.congestion_control(), CongestionControl::Cubic); + } + + s.set_congestion_control(CongestionControl::None); + assert_eq!(s.congestion_control(), CongestionControl::None); + } + + // =========================================================================================// + // Timestamp tests + // =========================================================================================// + + #[test] + fn test_tsval_established_connection() { + let mut s = socket_established(); + s.set_tsval_generator(Some(|| 1)); + + assert!(s.timestamp_enabled()); + + // First roundtrip after establishing. + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + timestamp: Some(TcpTimestampRepr::new(1, 0)), + ..RECV_TEMPL + }] + ); + assert_eq!(s.tx_buffer.len(), 6); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6), + timestamp: Some(TcpTimestampRepr::new(500, 1)), + ..SEND_TEMPL + } + ); + assert_eq!(s.tx_buffer.len(), 0); + // Second roundtrip. + s.send_slice(b"foobar").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1 + 6, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"foobar"[..], + timestamp: Some(TcpTimestampRepr::new(1, 500)), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1 + 6 + 6), + ..SEND_TEMPL + } + ); + assert_eq!(s.tx_buffer.len(), 0); + } + + #[test] + fn test_tsval_disabled_in_remote_client() { + let mut s = socket_listen(); + s.set_tsval_generator(Some(|| 1)); + assert!(s.timestamp_enabled()); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::SynReceived); + assert_eq!(s.tuple, Some(TUPLE)); + assert!(!s.timestamp_enabled()); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::Established); + assert_eq!(s.local_seq_no, LOCAL_SEQ + 1); + assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1); + } + + #[test] + fn test_tsval_disabled_in_local_server() { + let mut s = socket_listen(); + // s.set_timestamp(false); // commented to alert if the default state changes + assert!(!s.timestamp_enabled()); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: None, + timestamp: Some(TcpTimestampRepr::new(500, 0)), + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::SynReceived); + assert_eq!(s.tuple, Some(TUPLE)); + assert!(!s.timestamp_enabled()); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: Some(REMOTE_SEQ + 1), + max_seg_size: Some(BASE_MSS), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + seq_number: REMOTE_SEQ + 1, + ack_number: Some(LOCAL_SEQ + 1), + ..SEND_TEMPL + } + ); + assert_eq!(s.state(), State::Established); + assert_eq!(s.local_seq_no, LOCAL_SEQ + 1); + assert_eq!(s.remote_seq_no, REMOTE_SEQ + 1); + } + + #[test] + fn test_tsval_disabled_in_remote_server() { + let mut s = socket(); + s.set_tsval_generator(Some(|| 1)); + assert!(s.timestamp_enabled()); + s.local_seq_no = LOCAL_SEQ; + s.socket + .connect(&mut s.cx, REMOTE_END, LOCAL_END.port) + .unwrap(); + assert_eq!(s.tuple, Some(TUPLE)); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + timestamp: Some(TcpTimestampRepr::new(1, 0)), + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + timestamp: None, + ..SEND_TEMPL + } + ); + assert!(!s.timestamp_enabled()); + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + timestamp: None, + ..RECV_TEMPL + }] + ); + } + + #[test] + fn test_tsval_disabled_in_local_client() { + let mut s = socket(); + // s.set_timestamp(false); // commented to alert if the default state changes + assert!(!s.timestamp_enabled()); + s.local_seq_no = LOCAL_SEQ; + s.socket + .connect(&mut s.cx, REMOTE_END, LOCAL_END.port) + .unwrap(); + assert_eq!(s.tuple, Some(TUPLE)); + recv!( + s, + [TcpRepr { + control: TcpControl::Syn, + seq_number: LOCAL_SEQ, + ack_number: None, + max_seg_size: Some(BASE_MSS), + window_scale: Some(0), + sack_permitted: true, + ..RECV_TEMPL + }] + ); + send!( + s, + TcpRepr { + control: TcpControl::Syn, + seq_number: REMOTE_SEQ, + ack_number: Some(LOCAL_SEQ + 1), + max_seg_size: Some(BASE_MSS - 80), + window_scale: Some(0), + timestamp: Some(TcpTimestampRepr::new(500, 0)), + ..SEND_TEMPL + } + ); + assert!(!s.timestamp_enabled()); + s.send_slice(b"abcdef").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abcdef"[..], + timestamp: None, + ..RECV_TEMPL + }] + ); + } + + // =========================================================================================// + // Tests for source IP address change. + // =========================================================================================// + + #[test] + fn test_established_close_on_src_ip_change() { + let mut s = socket_established(); + + // Verify socket is working normally + s.send_slice(b"abc").unwrap(); + recv!( + s, + [TcpRepr { + seq_number: LOCAL_SEQ + 1, + ack_number: Some(REMOTE_SEQ + 1), + payload: &b"abc"[..], + ..RECV_TEMPL + }] + ); + + // Simulate interface IP change - remove the socket's source IP + // and add a different one. + let mut new_addrs = heapless::Vec::::new(); + new_addrs.push(IpCidr::new(OTHER_ADDR.into(), 24)).unwrap(); + s.cx.set_ip_addrs(new_addrs); + + // The socket's source IP is no longer on the interface. + // When dispatch() runs, it should detect this and reset the socket + // silently (no RST sent, since that would use the invalid source IP). + s.send_slice(b"def").unwrap(); + recv_nothing!(s); + assert_eq!(s.state, State::Closed); + } +} diff --git a/vendor/smoltcp/src/socket/tcp/congestion.rs b/vendor/smoltcp/src/socket/tcp/congestion.rs new file mode 100644 index 00000000..c904f214 --- /dev/null +++ b/vendor/smoltcp/src/socket/tcp/congestion.rs @@ -0,0 +1,101 @@ +use crate::time::Instant; + +use super::RttEstimator; + +pub(super) mod no_control; + +#[cfg(feature = "socket-tcp-cubic")] +pub(super) mod cubic; + +#[cfg(feature = "socket-tcp-reno")] +pub(super) mod reno; + +#[allow(unused_variables)] +pub(super) trait Controller { + /// Returns the number of bytes that can be sent. + fn window(&self) -> usize; + + /// Set the remote window size. + fn set_remote_window(&mut self, remote_window: usize) {} + + fn on_ack(&mut self, now: Instant, len: usize, rtt: &RttEstimator) {} + + fn on_retransmit(&mut self, now: Instant) {} + + fn on_duplicate_ack(&mut self, now: Instant) {} + + fn pre_transmit(&mut self, now: Instant) {} + + fn post_transmit(&mut self, now: Instant, len: usize) {} + + /// Set the maximum segment size. + fn set_mss(&mut self, mss: usize) {} +} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) enum AnyController { + None(no_control::NoControl), + + #[cfg(feature = "socket-tcp-reno")] + Reno(reno::Reno), + + #[cfg(feature = "socket-tcp-cubic")] + Cubic(cubic::Cubic), +} + +impl AnyController { + /// Create a new congestion controller. + /// `AnyController::new()` selects the best congestion controller based on the features. + /// + /// - If `socket-tcp-cubic` feature is enabled, it will use `Cubic`. + /// - If `socket-tcp-reno` feature is enabled, it will use `Reno`. + /// - If both `socket-tcp-cubic` and `socket-tcp-reno` features are enabled, it will use `Cubic`. + /// - `Cubic` is more efficient regarding throughput. + /// - `Reno` is more conservative and is suitable for low-power devices. + /// - If no congestion controller is available, it will use `NoControl`. + /// + /// Users can also select a congestion controller manually by [`super::Socket::set_congestion_control()`] + /// method at run-time. + #[allow(unreachable_code)] + #[inline] + pub fn new() -> Self { + #[cfg(feature = "socket-tcp-cubic")] + { + return AnyController::Cubic(cubic::Cubic::new()); + } + + #[cfg(feature = "socket-tcp-reno")] + { + return AnyController::Reno(reno::Reno::new()); + } + + AnyController::None(no_control::NoControl) + } + + #[inline] + pub fn inner_mut(&mut self) -> &mut dyn Controller { + match self { + AnyController::None(n) => n, + + #[cfg(feature = "socket-tcp-reno")] + AnyController::Reno(r) => r, + + #[cfg(feature = "socket-tcp-cubic")] + AnyController::Cubic(c) => c, + } + } + + #[inline] + pub fn inner(&self) -> &dyn Controller { + match self { + AnyController::None(n) => n, + + #[cfg(feature = "socket-tcp-reno")] + AnyController::Reno(r) => r, + + #[cfg(feature = "socket-tcp-cubic")] + AnyController::Cubic(c) => c, + } + } +} diff --git a/vendor/smoltcp/src/socket/tcp/congestion/cubic.rs b/vendor/smoltcp/src/socket/tcp/congestion/cubic.rs new file mode 100644 index 00000000..76b45039 --- /dev/null +++ b/vendor/smoltcp/src/socket/tcp/congestion/cubic.rs @@ -0,0 +1,308 @@ +use crate::time::Instant; + +use super::Controller; + +// Constants for the Cubic congestion control algorithm. +// See RFC 8312. +const BETA_CUBIC: f64 = 0.7; +const C: f64 = 0.4; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Cubic { + cwnd: usize, // Congestion window + min_cwnd: usize, // The minimum size of congestion window + w_max: usize, // Window size just before congestion + recovery_start: Option, + rwnd: usize, // Remote window + last_update: Instant, + ssthresh: usize, +} + +impl Cubic { + pub fn new() -> Cubic { + Cubic { + cwnd: 1024 * 2, + min_cwnd: 1024 * 2, + w_max: 1024 * 2, + recovery_start: None, + rwnd: 64 * 1024, + last_update: Instant::from_millis(0), + ssthresh: usize::MAX, + } + } +} + +impl Controller for Cubic { + fn window(&self) -> usize { + self.cwnd + } + + fn on_retransmit(&mut self, now: Instant) { + self.w_max = self.cwnd; + self.ssthresh = self.cwnd >> 1; + self.recovery_start = Some(now); + } + + fn on_duplicate_ack(&mut self, now: Instant) { + self.w_max = self.cwnd; + self.ssthresh = self.cwnd >> 1; + self.recovery_start = Some(now); + } + + fn set_remote_window(&mut self, remote_window: usize) { + if self.rwnd < remote_window { + self.rwnd = remote_window; + } + } + + fn on_ack(&mut self, _now: Instant, len: usize, _rtt: &crate::socket::tcp::RttEstimator) { + // Slow start. + if self.cwnd < self.ssthresh { + self.cwnd = self + .cwnd + .saturating_add(len) + .min(self.rwnd) + .max(self.min_cwnd); + } + } + + fn pre_transmit(&mut self, now: Instant) { + let Some(recovery_start) = self.recovery_start else { + self.recovery_start = Some(now); + return; + }; + + let now_millis = now.total_millis(); + + // If the last update was less than 100ms ago, don't update the congestion window. + if self.last_update > recovery_start && now_millis - self.last_update.total_millis() < 100 { + return; + } + + // Elapsed time since the start of the recovery phase. + let t = now_millis - recovery_start.total_millis(); + if t < 0 { + return; + } + + // K = (w_max * (1 - beta) / C)^(1/3) + let k3 = ((self.w_max as f64) * (1.0 - BETA_CUBIC)) / C; + let k = if let Some(k) = cube_root(k3) { + k + } else { + return; + }; + + // cwnd = C(T - K)^3 + w_max + let s = t as f64 / 1000.0 - k; + let s = s * s * s; + let cwnd = C * s + self.w_max as f64; + + self.last_update = now; + + self.cwnd = (cwnd as usize).max(self.min_cwnd).min(self.rwnd); + } + + fn set_mss(&mut self, mss: usize) { + self.min_cwnd = mss; + } +} + +#[inline] +fn abs(a: f64) -> f64 { + if a < 0.0 { -a } else { a } +} + +/// Calculate cube root by using the Newton-Raphson method. +fn cube_root(a: f64) -> Option { + if a <= 0.0 { + return None; + } + + let (tolerance, init) = if a < 1_000.0 { + (1.0, 8.879040017426005) // cube_root(700.0) + } else if a < 1_000_000.0 { + (5.0, 88.79040017426004) // cube_root(700_000.0) + } else if a < 1_000_000_000.0 { + (50.0, 887.9040017426004) // cube_root(700_000_000.0) + } else if a < 1_000_000_000_000.0 { + (500.0, 8879.040017426003) // cube_root(700_000_000_000.0) + } else if a < 1_000_000_000_000_000.0 { + (5000.0, 88790.40017426001) // cube_root(700_000_000_000.0) + } else { + (50000.0, 887904.0017426) // cube_root(700_000_000_000_000.0) + }; + + let mut x = init; // initial value + let mut n = 20; // The maximum iteration + loop { + let next_x = (2.0 * x + a / (x * x)) / 3.0; + if abs(next_x - x) < tolerance { + return Some(next_x); + } + x = next_x; + + if n == 0 { + return Some(next_x); + } + + n -= 1; + } +} + +#[cfg(test)] +mod test { + use crate::{socket::tcp::RttEstimator, time::Instant}; + + use super::*; + + #[test] + fn test_cubic() { + let remote_window = 64 * 1024 * 1024; + let now = Instant::from_millis(0); + + for i in 0..10 { + for j in 0..9 { + let mut cubic = Cubic::new(); + // Set remote window. + cubic.set_remote_window(remote_window); + + cubic.set_mss(1480); + + if i & 1 == 0 { + cubic.on_retransmit(now); + } else { + cubic.on_duplicate_ack(now); + } + + cubic.pre_transmit(now); + + let mut n = i; + for _ in 0..j { + n *= i; + } + + let elapsed = Instant::from_millis(n); + cubic.pre_transmit(elapsed); + + let cwnd = cubic.window(); + println!("Cubic: elapsed = {}, cwnd = {}", elapsed, cwnd); + + assert!(cwnd >= cubic.min_cwnd); + assert!(cubic.window() <= remote_window); + } + } + } + + #[test] + fn cubic_time_inversion() { + let mut cubic = Cubic::new(); + + let t1 = Instant::from_micros(0); + let t2 = Instant::from_micros(i64::MAX); + + cubic.on_retransmit(t2); + cubic.pre_transmit(t1); + + let cwnd = cubic.window(); + println!("Cubic:time_inversion: cwnd: {}, cubic: {cubic:?}", cwnd); + + assert!(cwnd >= cubic.min_cwnd); + assert!(cwnd <= cubic.rwnd); + } + + #[test] + fn cubic_long_elapsed_time() { + let mut cubic = Cubic::new(); + + let t1 = Instant::from_millis(0); + let t2 = Instant::from_micros(i64::MAX); + + cubic.on_retransmit(t1); + cubic.pre_transmit(t2); + + let cwnd = cubic.window(); + println!("Cubic:long_elapsed_time: cwnd: {}", cwnd); + + assert!(cwnd >= cubic.min_cwnd); + assert!(cwnd <= cubic.rwnd); + } + + #[test] + fn cubic_last_update() { + let mut cubic = Cubic::new(); + + let t1 = Instant::from_millis(0); + let t2 = Instant::from_millis(100); + let t3 = Instant::from_millis(199); + let t4 = Instant::from_millis(20000); + + cubic.on_retransmit(t1); + + cubic.pre_transmit(t2); + let cwnd2 = cubic.window(); + + cubic.pre_transmit(t3); + let cwnd3 = cubic.window(); + + cubic.pre_transmit(t4); + let cwnd4 = cubic.window(); + + println!( + "Cubic:last_update: cwnd2: {}, cwnd3: {}, cwnd4: {}", + cwnd2, cwnd3, cwnd4 + ); + + assert_eq!(cwnd2, cwnd3); + assert_ne!(cwnd2, cwnd4); + } + + #[test] + fn cubic_slow_start() { + let mut cubic = Cubic::new(); + + let t1 = Instant::from_micros(0); + + let cwnd = cubic.window(); + let ack_len = 1024; + + cubic.on_ack(t1, ack_len, &RttEstimator::default()); + + assert!(cubic.window() > cwnd); + + for i in 1..1000 { + let t2 = Instant::from_micros(i); + cubic.on_ack(t2, ack_len * 100, &RttEstimator::default()); + assert!(cubic.window() <= cubic.rwnd); + } + + let t3 = Instant::from_micros(2000); + + let cwnd = cubic.window(); + cubic.on_retransmit(t3); + assert_eq!(cwnd >> 1, cubic.ssthresh); + } + + #[test] + fn cubic_pre_transmit() { + let mut cubic = Cubic::new(); + cubic.pre_transmit(Instant::from_micros(2000)); + } + + #[test] + fn test_cube_root() { + for n in (1..1000000).step_by(99) { + let a = n as f64; + let a = a * a * a; + let result = cube_root(a); + println!("cube_root({a}) = {}", result.unwrap()); + } + } + + #[test] + #[should_panic] + fn cube_root_zero() { + cube_root(0.0).unwrap(); + } +} diff --git a/vendor/smoltcp/src/socket/tcp/congestion/no_control.rs b/vendor/smoltcp/src/socket/tcp/congestion/no_control.rs new file mode 100644 index 00000000..9ca4be3d --- /dev/null +++ b/vendor/smoltcp/src/socket/tcp/congestion/no_control.rs @@ -0,0 +1,11 @@ +use super::Controller; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NoControl; + +impl Controller for NoControl { + fn window(&self) -> usize { + usize::MAX + } +} diff --git a/vendor/smoltcp/src/socket/tcp/congestion/reno.rs b/vendor/smoltcp/src/socket/tcp/congestion/reno.rs new file mode 100644 index 00000000..8c029548 --- /dev/null +++ b/vendor/smoltcp/src/socket/tcp/congestion/reno.rs @@ -0,0 +1,130 @@ +use crate::{socket::tcp::RttEstimator, time::Instant}; + +use super::Controller; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Reno { + cwnd: usize, + min_cwnd: usize, + ssthresh: usize, + rwnd: usize, +} + +impl Reno { + pub fn new() -> Self { + Reno { + cwnd: 1024 * 2, + min_cwnd: 1024 * 2, + ssthresh: usize::MAX, + rwnd: 64 * 1024, + } + } +} + +impl Controller for Reno { + fn window(&self) -> usize { + self.cwnd + } + + fn on_ack(&mut self, _now: Instant, len: usize, _rtt: &RttEstimator) { + let len = if self.cwnd < self.ssthresh { + // Slow start. + len + } else { + self.ssthresh = self.cwnd; + self.min_cwnd + }; + + self.cwnd = self + .cwnd + .saturating_add(len) + .min(self.rwnd) + .max(self.min_cwnd); + } + + fn on_duplicate_ack(&mut self, _now: Instant) { + self.ssthresh = (self.cwnd >> 1).max(self.min_cwnd); + } + + fn on_retransmit(&mut self, _now: Instant) { + self.cwnd = (self.cwnd >> 1).max(self.min_cwnd); + } + + fn set_mss(&mut self, mss: usize) { + self.min_cwnd = mss; + } + + fn set_remote_window(&mut self, remote_window: usize) { + if self.rwnd < remote_window { + self.rwnd = remote_window; + } + } +} + +#[cfg(test)] +mod test { + use crate::time::Instant; + + use super::*; + + #[test] + fn test_reno() { + let remote_window = 64 * 1024; + let now = Instant::from_millis(0); + + for i in 0..10 { + for j in 0..9 { + let mut reno = Reno::new(); + reno.set_mss(1480); + + // Set remote window. + reno.set_remote_window(remote_window); + + reno.on_ack(now, 4096, &RttEstimator::default()); + + let mut n = i; + for _ in 0..j { + n *= i; + } + + if i & 1 == 0 { + reno.on_retransmit(now); + } else { + reno.on_duplicate_ack(now); + } + + let elapsed = Instant::from_millis(1000); + reno.on_ack(elapsed, n, &RttEstimator::default()); + + let cwnd = reno.window(); + println!("Reno: elapsed = {}, cwnd = {}", elapsed, cwnd); + + assert!(cwnd >= reno.min_cwnd); + assert!(reno.window() <= remote_window); + } + } + } + + #[test] + fn reno_min_cwnd() { + let remote_window = 64 * 1024; + let now = Instant::from_millis(0); + + let mut reno = Reno::new(); + reno.set_remote_window(remote_window); + + for _ in 0..100 { + reno.on_retransmit(now); + assert!(reno.window() >= reno.min_cwnd); + } + } + + #[test] + fn reno_set_rwnd() { + let mut reno = Reno::new(); + reno.set_remote_window(64 * 1024 * 1024); + + println!("{reno:?}"); + } +} diff --git a/vendor/smoltcp/src/socket/udp.rs b/vendor/smoltcp/src/socket/udp.rs new file mode 100644 index 00000000..824bad82 --- /dev/null +++ b/vendor/smoltcp/src/socket/udp.rs @@ -0,0 +1,1256 @@ +use core::cmp::min; +#[cfg(feature = "async")] +use core::task::Waker; + +use crate::iface::Context; +use crate::phy::PacketMeta; +use crate::socket::PollAt; +#[cfg(feature = "async")] +use crate::socket::WakerRegistration; +use crate::storage::Empty; +use crate::wire::{IpAddress, IpEndpoint, IpListenEndpoint, IpProtocol, IpRepr, UdpRepr}; + +/// Metadata for a sent or received UDP packet. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct UdpMetadata { + /// The IP endpoint from which an incoming datagram was received, or to which an outgoing + /// datagram will be sent. + pub endpoint: IpEndpoint, + /// The IP address to which an incoming datagram was sent, or from which an outgoing datagram + /// will be sent. Incoming datagrams always have this set. On outgoing datagrams, if it is not + /// set, and the socket is not bound to a single address anyway, a suitable address will be + /// determined using the algorithms of RFC 6724 (candidate source address selection) or some + /// heuristic (for IPv4). + pub local_address: Option, + pub meta: PacketMeta, +} + +impl> From for UdpMetadata { + fn from(value: T) -> Self { + Self { + endpoint: value.into(), + local_address: None, + meta: PacketMeta::default(), + } + } +} + +impl core::fmt::Display for UdpMetadata { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[cfg(feature = "packetmeta-id")] + return write!(f, "{}, PacketID: {:?}", self.endpoint, self.meta); + + #[cfg(not(feature = "packetmeta-id"))] + write!(f, "{}", self.endpoint) + } +} + +/// A UDP packet metadata. +pub type PacketMetadata = crate::storage::PacketMetadata; + +/// A UDP packet ring buffer. +pub type PacketBuffer<'a> = crate::storage::PacketBuffer<'a, UdpMetadata>; + +/// Error returned by [`Socket::bind`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + InvalidState, + Unaddressable, +} + +impl core::fmt::Display for BindError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + BindError::InvalidState => write!(f, "invalid state"), + BindError::Unaddressable => write!(f, "unaddressable"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for BindError {} + +/// Error returned by [`Socket::send`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + Unaddressable, + BufferFull, +} + +impl core::fmt::Display for SendError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + SendError::Unaddressable => write!(f, "unaddressable"), + SendError::BufferFull => write!(f, "buffer full"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for SendError {} + +/// Error returned by [`Socket::recv`] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + Exhausted, + Truncated, +} + +impl core::fmt::Display for RecvError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + RecvError::Exhausted => write!(f, "exhausted"), + RecvError::Truncated => write!(f, "truncated"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for RecvError {} + +/// A User Datagram Protocol socket. +/// +/// A UDP socket is bound to a specific endpoint, and owns transmit and receive +/// packet buffers. +#[derive(Debug)] +pub struct Socket<'a> { + endpoint: IpListenEndpoint, + /// Remote endpoint for connected UDP sockets. + /// When set, the socket will only accept packets from this remote endpoint. + remote_endpoint: Option, + rx_buffer: PacketBuffer<'a>, + tx_buffer: PacketBuffer<'a>, + /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + hop_limit: Option, + #[cfg(feature = "async")] + rx_waker: WakerRegistration, + #[cfg(feature = "async")] + tx_waker: WakerRegistration, +} + +impl<'a> Socket<'a> { + /// Create an UDP socket with the given buffers. + pub fn new(rx_buffer: PacketBuffer<'a>, tx_buffer: PacketBuffer<'a>) -> Socket<'a> { + Socket { + endpoint: IpListenEndpoint::default(), + remote_endpoint: None, + rx_buffer, + tx_buffer, + hop_limit: None, + #[cfg(feature = "async")] + rx_waker: WakerRegistration::new(), + #[cfg(feature = "async")] + tx_waker: WakerRegistration::new(), + } + } + + /// Register a waker for receive operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `recv` method calls, such as receiving data, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `recv` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_recv_waker(&mut self, waker: &Waker) { + self.rx_waker.register(waker) + } + + /// Register a waker for send operations. + /// + /// The waker is woken on state changes that might affect the return value + /// of `send` method calls, such as space becoming available in the transmit + /// buffer, or the socket closing. + /// + /// Notes: + /// + /// - Only one waker can be registered at a time. If another waker was previously registered, + /// it is overwritten and will no longer be woken. + /// - The Waker is woken only once. Once woken, you must register it again to receive more wakes. + /// - "Spurious wakes" are allowed: a wake doesn't guarantee the result of `send` has + /// necessarily changed. + #[cfg(feature = "async")] + pub fn register_send_waker(&mut self, waker: &Waker) { + self.tx_waker.register(waker) + } + + /// Return the bound endpoint. + #[inline] + pub fn endpoint(&self) -> IpListenEndpoint { + self.endpoint + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// See also the [set_hop_limit](#method.set_hop_limit) method + pub fn hop_limit(&self) -> Option { + self.hop_limit + } + + /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + /// + /// A socket without an explicitly set hop limit value uses the default [IANA recommended] + /// value (64). + /// + /// # Panics + /// + /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7]. + /// + /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml + /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7 + pub fn set_hop_limit(&mut self, hop_limit: Option) { + // A host MUST NOT send a datagram with a hop limit value of 0 + if let Some(0) = hop_limit { + panic!("the time-to-live value of a packet must not be zero") + } + + self.hop_limit = hop_limit + } + + /// Bind the socket to the given endpoint. + /// + /// This function returns `Err(Error::Illegal)` if the socket was open + /// (see [is_open](#method.is_open)), and `Err(Error::Unaddressable)` + /// if the port in the given endpoint is zero. + pub fn bind>(&mut self, endpoint: T) -> Result<(), BindError> { + let endpoint = endpoint.into(); + if endpoint.port == 0 { + return Err(BindError::Unaddressable); + } + + if self.is_open() { + return Err(BindError::InvalidState); + } + + self.endpoint = endpoint; + + #[cfg(feature = "async")] + { + self.rx_waker.wake(); + self.tx_waker.wake(); + } + + Ok(()) + } + + /// Connect the socket to a remote endpoint. + /// + /// When connected, the socket will only accept packets from the specified + /// remote endpoint and will use it as the default destination for sends. + /// This follows standard UDP "connected" socket semantics as defined by POSIX. + /// + /// # Errors + /// + /// Returns `Err(BindError::Unaddressable)` if the remote endpoint has an + /// unspecified address or port 0. + pub fn connect>(&mut self, remote_endpoint: T) -> Result<(), BindError> { + let remote_endpoint = remote_endpoint.into(); + + if remote_endpoint.addr.is_unspecified() || remote_endpoint.port == 0 { + return Err(BindError::Unaddressable); + } + + self.remote_endpoint = Some(remote_endpoint); + + #[cfg(feature = "async")] + { + self.rx_waker.wake(); + } + + Ok(()) + } + + /// Disconnect the socket from its remote endpoint. + /// + /// After disconnecting, the socket will accept packets from any source. + pub fn disconnect(&mut self) { + self.remote_endpoint = None; + + #[cfg(feature = "async")] + { + self.rx_waker.wake(); + } + } + + /// Check if the socket is connected to a remote endpoint. + #[inline] + pub fn is_connected(&self) -> bool { + self.remote_endpoint.is_some() + } + + /// Get the remote endpoint this socket is connected to, if any. + #[inline] + pub fn remote_endpoint(&self) -> Option { + self.remote_endpoint + } + + /// Close the socket. + pub fn close(&mut self) { + // Clear the bound endpoint of the socket. + self.endpoint = IpListenEndpoint::default(); + + // Clear the remote endpoint (disconnect). + self.remote_endpoint = None; + + // Reset the RX and TX buffers of the socket. + self.tx_buffer.reset(); + self.rx_buffer.reset(); + + #[cfg(feature = "async")] + { + self.rx_waker.wake(); + self.tx_waker.wake(); + } + } + + /// Check whether the socket is open. + #[inline] + pub fn is_open(&self) -> bool { + self.endpoint.port != 0 + } + + /// Check whether the transmit buffer is full. + #[inline] + pub fn can_send(&self) -> bool { + !self.tx_buffer.is_full() + } + + /// Check whether the receive buffer is not empty. + #[inline] + pub fn can_recv(&self) -> bool { + !self.rx_buffer.is_empty() + } + + /// Return the maximum number packets the socket can receive. + #[inline] + pub fn packet_recv_capacity(&self) -> usize { + self.rx_buffer.packet_capacity() + } + + /// Return the maximum number packets the socket can transmit. + #[inline] + pub fn packet_send_capacity(&self) -> usize { + self.tx_buffer.packet_capacity() + } + + /// Return the maximum number of bytes inside the recv buffer. + #[inline] + pub fn payload_recv_capacity(&self) -> usize { + self.rx_buffer.payload_capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + #[inline] + pub fn payload_send_capacity(&self) -> usize { + self.tx_buffer.payload_capacity() + } + + /// Enqueue a packet to be sent to a given remote endpoint, and return a pointer + /// to its payload. + /// + /// This function returns `Err(Error::Exhausted)` if the transmit buffer is full, + /// `Err(Error::Unaddressable)` if local or remote port, or remote address are unspecified, + /// and `Err(Error::Truncated)` if there is not enough transmit buffer capacity + /// to ever send this packet. + pub fn send( + &mut self, + size: usize, + meta: impl Into, + ) -> Result<&mut [u8], SendError> { + let meta = meta.into(); + if self.endpoint.port == 0 { + return Err(SendError::Unaddressable); + } + if meta.endpoint.addr.is_unspecified() { + return Err(SendError::Unaddressable); + } + if meta.endpoint.port == 0 { + return Err(SendError::Unaddressable); + } + + let payload_buf = self + .tx_buffer + .enqueue(size, meta) + .map_err(|_| SendError::BufferFull)?; + + net_trace!( + "udp:{}:{}: buffer to send {} octets", + self.endpoint, + meta.endpoint, + size + ); + Ok(payload_buf) + } + + /// Enqueue a packet to be send to a given remote endpoint and pass the buffer + /// to the provided closure. The closure then returns the size of the data written + /// into the buffer. + /// + /// Also see [send](#method.send). + pub fn send_with( + &mut self, + max_size: usize, + meta: impl Into, + f: F, + ) -> Result + where + F: FnOnce(&mut [u8]) -> usize, + { + let meta = meta.into(); + if self.endpoint.port == 0 { + return Err(SendError::Unaddressable); + } + if meta.endpoint.addr.is_unspecified() { + return Err(SendError::Unaddressable); + } + if meta.endpoint.port == 0 { + return Err(SendError::Unaddressable); + } + + let size = self + .tx_buffer + .enqueue_with_infallible(max_size, meta, f) + .map_err(|_| SendError::BufferFull)?; + + net_trace!( + "udp:{}:{}: buffer to send {} octets", + self.endpoint, + meta.endpoint, + size + ); + Ok(size) + } + + /// Enqueue a packet to be sent to a given remote endpoint, and fill it from a slice. + /// + /// See also [send](#method.send). + pub fn send_slice( + &mut self, + data: &[u8], + meta: impl Into, + ) -> Result<(), SendError> { + self.send(data.len(), meta)?.copy_from_slice(data); + Ok(()) + } + + /// Dequeue a packet received from a remote endpoint, and return the endpoint as well + /// as a pointer to the payload. + /// + /// This function returns `Err(Error::Exhausted)` if the receive buffer is empty. + pub fn recv(&mut self) -> Result<(&[u8], UdpMetadata), RecvError> { + let (remote_endpoint, payload_buf) = + self.rx_buffer.dequeue().map_err(|_| RecvError::Exhausted)?; + + net_trace!( + "udp:{}:{}: receive {} buffered octets", + self.endpoint, + remote_endpoint.endpoint, + payload_buf.len() + ); + Ok((payload_buf, remote_endpoint)) + } + + /// Dequeue a packet received from a remote endpoint, copy the payload into the given slice, + /// and return the amount of octets copied as well as the endpoint. + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// the packet is dropped and a `RecvError::Truncated` error is returned. + /// + /// See also [recv](#method.recv). + pub fn recv_slice(&mut self, data: &mut [u8]) -> Result<(usize, UdpMetadata), RecvError> { + let (buffer, endpoint) = self.recv().map_err(|_| RecvError::Exhausted)?; + + if data.len() < buffer.len() { + return Err(RecvError::Truncated); + } + + let length = min(data.len(), buffer.len()); + data[..length].copy_from_slice(&buffer[..length]); + Ok((length, endpoint)) + } + + /// Peek at a packet received from a remote endpoint, and return the endpoint as well + /// as a pointer to the payload without removing the packet from the receive buffer. + /// This function otherwise behaves identically to [recv](#method.recv). + /// + /// It returns `Err(Error::Exhausted)` if the receive buffer is empty. + pub fn peek(&mut self) -> Result<(&[u8], &UdpMetadata), RecvError> { + let endpoint = self.endpoint; + self.rx_buffer.peek().map_err(|_| RecvError::Exhausted).map( + |(remote_endpoint, payload_buf)| { + net_trace!( + "udp:{}:{}: peek {} buffered octets", + endpoint, + remote_endpoint.endpoint, + payload_buf.len() + ); + (payload_buf, remote_endpoint) + }, + ) + } + + /// Peek at a packet received from a remote endpoint, copy the payload into the given slice, + /// and return the amount of octets copied as well as the endpoint without removing the + /// packet from the receive buffer. + /// This function otherwise behaves identically to [recv_slice](#method.recv_slice). + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// no data is copied into the provided buffer and a `RecvError::Truncated` error is returned. + /// + /// See also [peek](#method.peek). + pub fn peek_slice(&mut self, data: &mut [u8]) -> Result<(usize, &UdpMetadata), RecvError> { + let (buffer, endpoint) = self.peek()?; + + if data.len() < buffer.len() { + return Err(RecvError::Truncated); + } + + let length = min(data.len(), buffer.len()); + data[..length].copy_from_slice(&buffer[..length]); + Ok((length, endpoint)) + } + + /// Return the amount of octets queued in the transmit buffer. + /// + /// Note that the Berkeley sockets interface does not have an equivalent of this API. + pub fn send_queue(&self) -> usize { + self.tx_buffer.payload_bytes_count() + } + + /// Return the amount of octets queued in the receive buffer. This value can be larger than + /// the slice read by the next `recv` or `peek` call because it includes all queued octets, + /// and not only the octets that may be returned as a contiguous slice. + /// + /// Note that the Berkeley sockets interface does not have an equivalent of this API. + pub fn recv_queue(&self) -> usize { + self.rx_buffer.payload_bytes_count() + } + + /// Check if the socket accepts a given packet. + /// + /// Returns a match priority score: + /// - 0: Does not accept + /// - 1: Accepts with wildcard binding (addr=None) and not connected + /// - 2: Accepts with specific local address binding and not connected + /// - 3: Accepts and connected to the source (highest priority) + /// + /// Higher scores indicate better matches. This allows proper routing + /// when multiple sockets are bound to the same port. + pub(crate) fn accepts(&self, cx: &mut Context, ip_repr: &IpRepr, repr: &UdpRepr) -> u8 { + // Check destination port + if self.endpoint.port != repr.dst_port { + return 0; + } + + // Check destination address + let addr_matches = if self.endpoint.addr.is_some() { + self.endpoint.addr == Some(ip_repr.dst_addr()) + || cx.is_broadcast(&ip_repr.dst_addr()) + || ip_repr.dst_addr().is_multicast() + } else { + true + }; + + if !addr_matches { + return 0; + } + + // If socket is connected, check if source matches + if let Some(remote) = self.remote_endpoint { + if remote.addr == ip_repr.src_addr() && remote.port == repr.src_port { + // Connected socket with matching source has highest priority + return 3; + } else { + // Connected socket but source doesn't match - reject + return 0; + } + } + + // Not connected - accept based on local address binding + if self.endpoint.addr.is_some() { + // Specific address binding has higher priority + 2 + } else { + // Wildcard binding (addr=None) has lower priority + 1 + } + } + + pub(crate) fn process( + &mut self, + cx: &mut Context, + meta: PacketMeta, + ip_repr: &IpRepr, + repr: &UdpRepr, + payload: &[u8], + ) { + debug_assert!(self.accepts(cx, ip_repr, repr) > 0); + + let size = payload.len(); + + let remote_endpoint = IpEndpoint { + addr: ip_repr.src_addr(), + port: repr.src_port, + }; + + net_trace!( + "udp:{}:{}: receiving {} octets", + self.endpoint, + remote_endpoint, + size + ); + + let metadata = UdpMetadata { + endpoint: remote_endpoint, + local_address: Some(ip_repr.dst_addr()), + meta, + }; + + match self.rx_buffer.enqueue(size, metadata) { + Ok(buf) => buf.copy_from_slice(payload), + Err(_) => net_trace!( + "udp:{}:{}: buffer full, dropped incoming packet", + self.endpoint, + remote_endpoint + ), + } + + #[cfg(feature = "async")] + self.rx_waker.wake(); + } + + pub(crate) fn dispatch(&mut self, cx: &mut Context, emit: F) -> Result<(), E> + where + F: FnOnce(&mut Context, PacketMeta, (IpRepr, UdpRepr, &[u8])) -> Result<(), E>, + { + let endpoint = self.endpoint; + let hop_limit = self.hop_limit.unwrap_or(64); + + let res = self.tx_buffer.dequeue_with(|packet_meta, payload_buf| { + let src_addr = if let Some(s) = packet_meta.local_address { + s + } else { + match endpoint.addr { + Some(addr) => addr, + None => match cx.get_source_address(&packet_meta.endpoint.addr) { + Some(addr) => addr, + None => { + net_trace!( + "udp:{}:{}: cannot find suitable source address, dropping.", + endpoint, + packet_meta.endpoint + ); + return Ok(()); + } + }, + } + }; + + net_trace!( + "udp:{}:{}: sending {} octets", + endpoint, + packet_meta.endpoint, + payload_buf.len() + ); + + let repr = UdpRepr { + src_port: endpoint.port, + dst_port: packet_meta.endpoint.port, + }; + let ip_repr = IpRepr::new( + src_addr, + packet_meta.endpoint.addr, + IpProtocol::Udp, + repr.header_len() + payload_buf.len(), + hop_limit, + ); + + emit(cx, packet_meta.meta, (ip_repr, repr, payload_buf)) + }); + match res { + Err(Empty) => Ok(()), + Ok(Err(e)) => Err(e), + Ok(Ok(())) => { + #[cfg(feature = "async")] + self.tx_waker.wake(); + Ok(()) + } + } + } + + pub(crate) fn poll_at(&self, _cx: &mut Context) -> PollAt { + if self.tx_buffer.is_empty() { + PollAt::Ingress + } else { + PollAt::Now + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::wire::{IpRepr, UdpRepr}; + + use crate::phy::Medium; + use crate::tests::setup; + use rstest::*; + + fn buffer(packets: usize) -> PacketBuffer<'static> { + PacketBuffer::new( + (0..packets) + .map(|_| PacketMetadata::EMPTY) + .collect::>(), + vec![0; 16 * packets], + ) + } + + fn socket( + rx_buffer: PacketBuffer<'static>, + tx_buffer: PacketBuffer<'static>, + ) -> Socket<'static> { + Socket::new(rx_buffer, tx_buffer) + } + + const LOCAL_PORT: u16 = 53; + const REMOTE_PORT: u16 = 49500; + + cfg_if::cfg_if! { + if #[cfg(feature = "proto-ipv4")] { + use crate::wire::Ipv4Address as IpvXAddress; + use crate::wire::Ipv4Repr as IpvXRepr; + use IpRepr::Ipv4 as IpReprIpvX; + + const LOCAL_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 1); + const REMOTE_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 2); + const OTHER_ADDR: IpvXAddress = IpvXAddress::new(192, 168, 1, 3); + + const LOCAL_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv4(LOCAL_ADDR), + port: LOCAL_PORT, + }; + const REMOTE_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv4(REMOTE_ADDR), + port: REMOTE_PORT, + }; + } else { + use crate::wire::Ipv6Address as IpvXAddress; + use crate::wire::Ipv6Repr as IpvXRepr; + use IpRepr::Ipv6 as IpReprIpvX; + + const LOCAL_ADDR: IpvXAddress = IpvXAddress::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const REMOTE_ADDR: IpvXAddress = IpvXAddress::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + const OTHER_ADDR: IpvXAddress = IpvXAddress::new(0xfe80, 0, 0, 0, 0, 0, 0, 3); + + const LOCAL_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv6(LOCAL_ADDR), + port: LOCAL_PORT, + }; + const REMOTE_END: IpEndpoint = IpEndpoint { + addr: IpAddress::Ipv6(REMOTE_ADDR), + port: REMOTE_PORT, + }; + } + } + + fn remote_metadata_with_local() -> UdpMetadata { + // Would be great as a const once we have const `.into()`. + UdpMetadata { + local_address: Some(LOCAL_ADDR.into()), + ..REMOTE_END.into() + } + } + + pub const LOCAL_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 64, + }); + + pub const REMOTE_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 64, + }); + + pub const BAD_IP_REPR: IpRepr = IpReprIpvX(IpvXRepr { + src_addr: REMOTE_ADDR, + dst_addr: OTHER_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 64, + }); + + const LOCAL_UDP_REPR: UdpRepr = UdpRepr { + src_port: LOCAL_PORT, + dst_port: REMOTE_PORT, + }; + + const REMOTE_UDP_REPR: UdpRepr = UdpRepr { + src_port: REMOTE_PORT, + dst_port: LOCAL_PORT, + }; + + const PAYLOAD: &[u8] = b"abcdef"; + + #[test] + fn test_bind_unaddressable() { + let mut socket = socket(buffer(0), buffer(0)); + assert_eq!(socket.bind(0), Err(BindError::Unaddressable)); + } + + #[test] + fn test_bind_twice() { + let mut socket = socket(buffer(0), buffer(0)); + assert_eq!(socket.bind(1), Ok(())); + assert_eq!(socket.bind(2), Err(BindError::InvalidState)); + } + + #[test] + #[should_panic(expected = "the time-to-live value of a packet must not be zero")] + fn test_set_hop_limit_zero() { + let mut s = socket(buffer(0), buffer(1)); + s.set_hop_limit(Some(0)); + } + + #[test] + fn test_send_unaddressable() { + let mut socket = socket(buffer(0), buffer(1)); + + assert_eq!( + socket.send_slice(b"abcdef", REMOTE_END), + Err(SendError::Unaddressable) + ); + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + assert_eq!( + socket.send_slice( + b"abcdef", + IpEndpoint { + addr: IpvXAddress::UNSPECIFIED.into(), + ..REMOTE_END + } + ), + Err(SendError::Unaddressable) + ); + assert_eq!( + socket.send_slice( + b"abcdef", + IpEndpoint { + port: 0, + ..REMOTE_END + } + ), + Err(SendError::Unaddressable) + ); + assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(())); + } + + #[test] + fn test_send_with_source() { + let mut socket = socket(buffer(0), buffer(1)); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + assert_eq!( + socket.send_slice(b"abcdef", remote_metadata_with_local()), + Ok(()) + ); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_send_dispatch(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + let mut socket = socket(buffer(0), buffer(1)); + + assert_eq!(socket.bind(LOCAL_END), Ok(())); + + assert!(socket.can_send()); + assert_eq!( + socket.dispatch(cx, |_, _, _| unreachable!()), + Ok::<_, ()>(()) + ); + + assert_eq!(socket.send_slice(b"abcdef", REMOTE_END), Ok(())); + assert_eq!( + socket.send_slice(b"123456", REMOTE_END), + Err(SendError::BufferFull) + ); + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(cx, |_, _, (ip_repr, udp_repr, payload)| { + assert_eq!(ip_repr, LOCAL_IP_REPR); + assert_eq!(udp_repr, LOCAL_UDP_REPR); + assert_eq!(payload, PAYLOAD); + Err(()) + }), + Err(()) + ); + assert!(!socket.can_send()); + + assert_eq!( + socket.dispatch(cx, |_, _, (ip_repr, udp_repr, payload)| { + assert_eq!(ip_repr, LOCAL_IP_REPR); + assert_eq!(udp_repr, LOCAL_UDP_REPR); + assert_eq!(payload, PAYLOAD); + Ok::<_, ()>(()) + }), + Ok(()) + ); + assert!(socket.can_send()); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_recv_process(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(0)); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + assert!(!socket.can_recv()); + assert_eq!(socket.recv(), Err(RecvError::Exhausted)); + + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR) > 0); + socket.process( + cx, + PacketMeta::default(), + &REMOTE_IP_REPR, + &REMOTE_UDP_REPR, + PAYLOAD, + ); + assert!(socket.can_recv()); + + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR) > 0); + socket.process( + cx, + PacketMeta::default(), + &REMOTE_IP_REPR, + &REMOTE_UDP_REPR, + PAYLOAD, + ); + + assert_eq!( + socket.recv(), + Ok((&b"abcdef"[..], remote_metadata_with_local())) + ); + assert!(!socket.can_recv()); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_peek_process(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(0)); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + assert_eq!(socket.peek(), Err(RecvError::Exhausted)); + + socket.process( + cx, + PacketMeta::default(), + &REMOTE_IP_REPR, + &REMOTE_UDP_REPR, + PAYLOAD, + ); + assert_eq!( + socket.peek(), + Ok((&b"abcdef"[..], &remote_metadata_with_local(),)) + ); + assert_eq!( + socket.recv(), + Ok((&b"abcdef"[..], remote_metadata_with_local(),)) + ); + assert_eq!(socket.peek(), Err(RecvError::Exhausted)); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_recv_truncated_slice(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(0)); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR) > 0); + socket.process( + cx, + PacketMeta::default(), + &REMOTE_IP_REPR, + &REMOTE_UDP_REPR, + PAYLOAD, + ); + + let mut slice = [0; 4]; + assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated)); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_peek_truncated_slice(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(0)); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + socket.process( + cx, + PacketMeta::default(), + &REMOTE_IP_REPR, + &REMOTE_UDP_REPR, + PAYLOAD, + ); + + let mut slice = [0; 4]; + assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Truncated)); + assert_eq!(socket.recv_slice(&mut slice[..]), Err(RecvError::Truncated)); + assert_eq!(socket.peek_slice(&mut slice[..]), Err(RecvError::Exhausted)); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_set_hop_limit(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut s = socket(buffer(0), buffer(1)); + + assert_eq!(s.bind(LOCAL_END), Ok(())); + + s.set_hop_limit(Some(0x2a)); + assert_eq!(s.send_slice(b"abcdef", REMOTE_END), Ok(())); + assert_eq!( + s.dispatch(cx, |_, _, (ip_repr, _, _)| { + assert_eq!( + ip_repr, + IpReprIpvX(IpvXRepr { + src_addr: LOCAL_ADDR, + dst_addr: REMOTE_ADDR, + next_header: IpProtocol::Udp, + payload_len: 8 + 6, + hop_limit: 0x2a, + }) + ); + Ok::<_, ()>(()) + }), + Ok(()) + ); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_doesnt_accept_wrong_port(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(0)); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + let mut udp_repr = REMOTE_UDP_REPR; + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &udp_repr) > 0); + udp_repr.dst_port += 1; + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &udp_repr) == 0); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_doesnt_accept_wrong_ip(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut port_bound_socket = socket(buffer(1), buffer(0)); + assert_eq!(port_bound_socket.bind(LOCAL_PORT), Ok(())); + assert!(port_bound_socket.accepts(cx, &BAD_IP_REPR, &REMOTE_UDP_REPR) > 0); + + let mut ip_bound_socket = socket(buffer(1), buffer(0)); + assert_eq!(ip_bound_socket.bind(LOCAL_END), Ok(())); + assert!(ip_bound_socket.accepts(cx, &BAD_IP_REPR, &REMOTE_UDP_REPR) == 0); + } + + #[test] + fn test_send_large_packet() { + // buffer(4) creates a payload buffer of size 16*4 + let mut socket = socket(buffer(0), buffer(4)); + assert_eq!(socket.bind(LOCAL_END), Ok(())); + + let too_large = b"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefx"; + assert_eq!( + socket.send_slice(too_large, REMOTE_END), + Err(SendError::BufferFull) + ); + assert_eq!(socket.send_slice(&too_large[..16 * 4], REMOTE_END), Ok(())); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_process_empty_payload(#[case] medium: Medium) { + let meta = Box::leak(Box::new([PacketMetadata::EMPTY])); + let recv_buffer = PacketBuffer::new(&mut meta[..], vec![]); + let mut socket = socket(recv_buffer, buffer(0)); + + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + let repr = UdpRepr { + src_port: REMOTE_PORT, + dst_port: LOCAL_PORT, + }; + socket.process(cx, PacketMeta::default(), &REMOTE_IP_REPR, &repr, &[]); + assert_eq!(socket.recv(), Ok((&[][..], remote_metadata_with_local()))); + } + + #[test] + fn test_closing() { + let meta = Box::leak(Box::new([PacketMetadata::EMPTY])); + let recv_buffer = PacketBuffer::new(&mut meta[..], vec![]); + let mut socket = socket(recv_buffer, buffer(0)); + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + assert!(socket.is_open()); + socket.close(); + assert!(!socket.is_open()); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_connect_and_accept(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + let mut socket = socket(buffer(1), buffer(0)); + assert_eq!(socket.bind(LOCAL_PORT), Ok(())); + + // Before connect, accepts from any source + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR) > 0); + + // Connect to remote endpoint + assert_eq!(socket.connect(REMOTE_END), Ok(())); + assert!(socket.is_connected()); + assert_eq!(socket.remote_endpoint(), Some(REMOTE_END)); + + // After connect, only accepts from connected remote + assert_eq!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR), 3); // Highest priority + + // Should reject packets from different source + let other_ip_repr = IpRepr::Ipv4(IpvXRepr { + src_addr: IpvXAddress::new(10, 0, 0, 99), + dst_addr: LOCAL_ADDR, + next_header: IpProtocol::Udp, + payload_len: 0, + hop_limit: 64, + }); + assert_eq!(socket.accepts(cx, &other_ip_repr, &REMOTE_UDP_REPR), 0); + + // Disconnect + socket.disconnect(); + assert!(!socket.is_connected()); + assert_eq!(socket.remote_endpoint(), None); + + // After disconnect, accepts from any source again + assert!(socket.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR) > 0); + } + + #[rstest] + #[case::ip(Medium::Ip)] + #[cfg(feature = "medium-ip")] + #[case::ethernet(Medium::Ethernet)] + #[cfg(feature = "medium-ethernet")] + #[case::ieee802154(Medium::Ieee802154)] + #[cfg(feature = "medium-ieee802154")] + fn test_connected_socket_priority(#[case] medium: Medium) { + let (mut iface, _, _) = setup(medium); + let cx = iface.context(); + + // Socket 1: wildcard binding, not connected + let mut socket1 = socket(buffer(1), buffer(0)); + assert_eq!(socket1.bind(LOCAL_PORT), Ok(())); + + // Socket 2: wildcard binding, connected to remote + let mut socket2 = socket(buffer(1), buffer(0)); + assert_eq!(socket2.bind(LOCAL_PORT), Ok(())); + assert_eq!(socket2.connect(REMOTE_END), Ok(())); + + // Socket 2 should have higher priority (score 3 vs 1) + let score1 = socket1.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR); + let score2 = socket2.accepts(cx, &REMOTE_IP_REPR, &REMOTE_UDP_REPR); + assert_eq!(score1, 1); // Wildcard, not connected + assert_eq!(score2, 3); // Connected + assert!(score2 > score1); + } +} diff --git a/vendor/smoltcp/src/socket/waker.rs b/vendor/smoltcp/src/socket/waker.rs new file mode 100644 index 00000000..4f421978 --- /dev/null +++ b/vendor/smoltcp/src/socket/waker.rs @@ -0,0 +1,33 @@ +use core::task::Waker; + +/// Utility struct to register and wake a waker. +#[derive(Debug)] +pub struct WakerRegistration { + waker: Option, +} + +impl WakerRegistration { + pub const fn new() -> Self { + Self { waker: None } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&mut self, w: &Waker) { + match self.waker { + // Optimization: If both the old and new Wakers wake the same task, we can simply + // keep the old waker, skipping the clone. (In most executor implementations, + // cloning a waker is somewhat expensive, comparable to cloning an Arc). + Some(ref w2) if (w2.will_wake(w)) => {} + // In all other cases + // - we have no waker registered + // - we have a waker registered but it's for a different task. + // then clone the new waker and store it + _ => self.waker = Some(w.clone()), + } + } + + /// Wake the registered waker, if any. + pub fn wake(&mut self) { + self.waker.take().map(|w| w.wake()); + } +} diff --git a/vendor/smoltcp/src/storage/assembler.rs b/vendor/smoltcp/src/storage/assembler.rs new file mode 100644 index 00000000..27834c1b --- /dev/null +++ b/vendor/smoltcp/src/storage/assembler.rs @@ -0,0 +1,707 @@ +use core::fmt; + +use crate::config::ASSEMBLER_MAX_SEGMENT_COUNT; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TooManyHolesError; + +impl fmt::Display for TooManyHolesError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "too many holes") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TooManyHolesError {} + +/// A contiguous chunk of absent data, followed by a contiguous chunk of present data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Contig { + hole_size: usize, + data_size: usize, +} + +impl fmt::Display for Contig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.has_hole() { + write!(f, "({})", self.hole_size)?; + } + if self.has_hole() && self.has_data() { + write!(f, " ")?; + } + if self.has_data() { + write!(f, "{}", self.data_size)?; + } + Ok(()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Contig { + fn format(&self, fmt: defmt::Formatter) { + if self.has_hole() { + defmt::write!(fmt, "({})", self.hole_size); + } + if self.has_hole() && self.has_data() { + defmt::write!(fmt, " "); + } + if self.has_data() { + defmt::write!(fmt, "{}", self.data_size); + } + } +} + +impl Contig { + const fn empty() -> Contig { + Contig { + hole_size: 0, + data_size: 0, + } + } + + fn hole_and_data(hole_size: usize, data_size: usize) -> Contig { + Contig { + hole_size, + data_size, + } + } + + fn has_hole(&self) -> bool { + self.hole_size != 0 + } + + fn has_data(&self) -> bool { + self.data_size != 0 + } + + fn total_size(&self) -> usize { + self.hole_size + self.data_size + } + + fn shrink_hole_by(&mut self, size: usize) { + self.hole_size -= size; + } + + fn shrink_hole_to(&mut self, size: usize) { + debug_assert!(self.hole_size >= size); + + let total_size = self.total_size(); + self.hole_size = size; + self.data_size = total_size - size; + } +} + +/// A buffer (re)assembler. +/// +/// Currently, up to a hardcoded limit of 4 or 32 holes can be tracked in the buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Assembler { + contigs: [Contig; ASSEMBLER_MAX_SEGMENT_COUNT], +} + +impl fmt::Display for Assembler { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[ ")?; + for contig in self.contigs.iter() { + if !contig.has_data() { + break; + } + write!(f, "{contig} ")?; + } + write!(f, "]")?; + Ok(()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Assembler { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "[ "); + for contig in self.contigs.iter() { + if !contig.has_data() { + break; + } + defmt::write!(fmt, "{} ", contig); + } + defmt::write!(fmt, "]"); + } +} + +// Invariant on Assembler::contigs: +// - There's an index `i` where all contigs before have data, and all contigs after don't (are unused). +// - All contigs with data must have hole_size != 0, except the first. + +impl Assembler { + /// Create a new buffer assembler. + pub const fn new() -> Assembler { + const EMPTY: Contig = Contig::empty(); + Assembler { + contigs: [EMPTY; ASSEMBLER_MAX_SEGMENT_COUNT], + } + } + + pub fn clear(&mut self) { + self.contigs.fill(Contig::empty()); + } + + fn front(&self) -> Contig { + self.contigs[0] + } + + /// Return length of the front contiguous range without removing it from the assembler + pub fn peek_front(&self) -> usize { + let front = self.front(); + if front.has_hole() { 0 } else { front.data_size } + } + + fn back(&self) -> Contig { + self.contigs[self.contigs.len() - 1] + } + + /// Return whether the assembler contains no data. + pub fn is_empty(&self) -> bool { + !self.front().has_data() + } + + /// Remove a contig at the given index. + fn remove_contig_at(&mut self, at: usize) { + debug_assert!(self.contigs[at].has_data()); + + for i in at..self.contigs.len() - 1 { + if !self.contigs[i].has_data() { + return; + } + self.contigs[i] = self.contigs[i + 1]; + } + + // Removing the last one. + self.contigs[self.contigs.len() - 1] = Contig::empty(); + } + + /// Add a contig at the given index, and return a pointer to it. + fn add_contig_at(&mut self, at: usize) -> Result<&mut Contig, TooManyHolesError> { + if self.back().has_data() { + return Err(TooManyHolesError); + } + + for i in (at + 1..self.contigs.len()).rev() { + self.contigs[i] = self.contigs[i - 1]; + } + + self.contigs[at] = Contig::empty(); + Ok(&mut self.contigs[at]) + } + + /// Add a new contiguous range to the assembler, + /// or return `Err(TooManyHolesError)` if too many discontinuities are already recorded. + pub fn add(&mut self, mut offset: usize, size: usize) -> Result<(), TooManyHolesError> { + if size == 0 { + return Ok(()); + } + + let mut i = 0; + + // Find index of the contig containing the start of the range. + loop { + if i == self.contigs.len() { + // The new range is after all the previous ranges, but there/s no space to add it. + return Err(TooManyHolesError); + } + let contig = &mut self.contigs[i]; + if !contig.has_data() { + // The new range is after all the previous ranges. Add it. + *contig = Contig::hole_and_data(offset, size); + return Ok(()); + } + if offset <= contig.total_size() { + break; + } + offset -= contig.total_size(); + i += 1; + } + + let contig = &mut self.contigs[i]; + if offset < contig.hole_size { + // Range starts within the hole. + + if offset + size < contig.hole_size { + // Range also ends within the hole. + let new_contig = self.add_contig_at(i)?; + new_contig.hole_size = offset; + new_contig.data_size = size; + + // Previous contigs[index] got moved to contigs[index+1] + self.contigs[i + 1].shrink_hole_by(offset + size); + return Ok(()); + } + + // The range being added covers both a part of the hole and a part of the data + // in this contig, shrink the hole in this contig. + contig.shrink_hole_to(offset); + } + + // coalesce contigs to the right. + let mut j = i + 1; + while j < self.contigs.len() + && self.contigs[j].has_data() + && offset + size >= self.contigs[i].total_size() + self.contigs[j].hole_size + { + self.contigs[i].data_size += self.contigs[j].total_size(); + j += 1; + } + let shift = j - i - 1; + if shift != 0 { + for x in i + 1..self.contigs.len() { + if !self.contigs[x].has_data() { + break; + } + + self.contigs[x] = self + .contigs + .get(x + shift) + .copied() + .unwrap_or_else(Contig::empty); + } + } + + if offset + size > self.contigs[i].total_size() { + // The added range still extends beyond the current contig. Increase data size. + let left = offset + size - self.contigs[i].total_size(); + self.contigs[i].data_size += left; + + // Decrease hole size of the next, if any. + if i + 1 < self.contigs.len() && self.contigs[i + 1].has_data() { + self.contigs[i + 1].hole_size -= left; + } + } + + Ok(()) + } + + /// Remove a contiguous range from the front of the assembler. + /// If no such range, return 0. + pub fn remove_front(&mut self) -> usize { + let front = self.front(); + if front.has_hole() || !front.has_data() { + 0 + } else { + self.remove_contig_at(0); + debug_assert!(front.data_size > 0); + front.data_size + } + } + + /// Add a segment, then remove_front. + /// + /// This is equivalent to calling `add` then `remove_front` individually, + /// except it's guaranteed to not fail when offset = 0. + /// This is required for TCP: we must never drop the next expected segment, or + /// the protocol might get stuck. + pub fn add_then_remove_front( + &mut self, + offset: usize, + size: usize, + ) -> Result { + // This is the only case where a segment at offset=0 would cause the + // total amount of contigs to rise (and therefore can potentially cause + // a TooManyHolesError). Handle it in a way that is guaranteed to succeed. + if offset == 0 && size < self.contigs[0].hole_size { + self.contigs[0].hole_size -= size; + return Ok(size); + } + + self.add(offset, size)?; + Ok(self.remove_front()) + } + + /// Iterate over all of the contiguous data ranges. + /// + /// Returns `(offset, size)` tuples for each contiguous data range, where + /// offset is relative to the start of the assembler. + /// + /// Data Hole Data + /// |--- 100 ---|--- 200 ---|--- 100 ---| + /// + /// Would return the ranges: ``(0, 100), (300, 400)`` + pub fn iter_data(&self) -> impl Iterator + '_ { + let mut offset = 0; + self.contigs.iter().filter_map(move |contig| { + offset += contig.hole_size; + let left = offset; + offset += contig.data_size; + let right = offset; + if left < right { + Some((left, right)) + } else { + None + } + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::vec::Vec; + + impl From> for Assembler { + fn from(vec: Vec<(usize, usize)>) -> Assembler { + const EMPTY: Contig = Contig::empty(); + + let mut contigs = [EMPTY; ASSEMBLER_MAX_SEGMENT_COUNT]; + for (i, &(hole_size, data_size)) in vec.iter().enumerate() { + contigs[i] = Contig { + hole_size, + data_size, + }; + } + Assembler { contigs } + } + } + + macro_rules! contigs { + [$( $x:expr ),*] => ({ + Assembler::from(vec![$( $x ),*]) + }) + } + + #[test] + fn test_new() { + let assr = Assembler::new(); + assert_eq!(assr, contigs![]); + } + + #[test] + fn test_empty_add_full() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(0, 16), Ok(())); + assert_eq!(assr, contigs![(0, 16)]); + } + + #[test] + fn test_empty_add_front() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(0, 4), Ok(())); + assert_eq!(assr, contigs![(0, 4)]); + } + + #[test] + fn test_empty_add_back() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(12, 4), Ok(())); + assert_eq!(assr, contigs![(12, 4)]); + } + + #[test] + fn test_empty_add_mid() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(4, 8), Ok(())); + assert_eq!(assr, contigs![(4, 8)]); + } + + #[test] + fn test_partial_add_front() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(0, 4), Ok(())); + assert_eq!(assr, contigs![(0, 12)]); + } + + #[test] + fn test_partial_add_back() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(12, 4), Ok(())); + assert_eq!(assr, contigs![(4, 12)]); + } + + #[test] + fn test_partial_add_front_overlap() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(0, 8), Ok(())); + assert_eq!(assr, contigs![(0, 12)]); + } + + #[test] + fn test_partial_add_front_overlap_split() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(2, 6), Ok(())); + assert_eq!(assr, contigs![(2, 10)]); + } + + #[test] + fn test_partial_add_back_overlap() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(8, 8), Ok(())); + assert_eq!(assr, contigs![(4, 12)]); + } + + #[test] + fn test_partial_add_back_overlap_split() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(10, 4), Ok(())); + assert_eq!(assr, contigs![(4, 10)]); + } + + #[test] + fn test_partial_add_both_overlap() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(0, 16), Ok(())); + assert_eq!(assr, contigs![(0, 16)]); + } + + #[test] + fn test_partial_add_both_overlap_split() { + let mut assr = contigs![(4, 8)]; + assert_eq!(assr.add(2, 12), Ok(())); + assert_eq!(assr, contigs![(2, 12)]); + } + + #[test] + fn test_rejected_add_keeps_state() { + let mut assr = Assembler::new(); + for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT { + assert_eq!(assr.add(c * 10, 3), Ok(())); + } + // Maximum of allowed holes is reached + let assr_before = assr.clone(); + assert_eq!(assr.add(1, 3), Err(TooManyHolesError)); + assert_eq!(assr_before, assr); + } + + #[test] + fn test_empty_remove_front() { + let mut assr = contigs![]; + assert_eq!(assr.remove_front(), 0); + } + + #[test] + fn test_trailing_hole_remove_front() { + let mut assr = contigs![(0, 4)]; + assert_eq!(assr.remove_front(), 4); + assert_eq!(assr, contigs![]); + } + + #[test] + fn test_trailing_data_remove_front() { + let mut assr = contigs![(0, 4), (4, 4)]; + assert_eq!(assr.remove_front(), 4); + assert_eq!(assr, contigs![(4, 4)]); + } + + #[test] + fn test_boundary_case_remove_front() { + let mut vec = vec![(1, 1); ASSEMBLER_MAX_SEGMENT_COUNT]; + vec[0] = (0, 2); + let mut assr = Assembler::from(vec); + assert_eq!(assr.remove_front(), 2); + let mut vec = vec![(1, 1); ASSEMBLER_MAX_SEGMENT_COUNT]; + vec[ASSEMBLER_MAX_SEGMENT_COUNT - 1] = (0, 0); + let exp_assr = Assembler::from(vec); + assert_eq!(assr, exp_assr); + } + + #[test] + fn test_shrink_next_hole() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(100, 10), Ok(())); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add(40, 30), Ok(())); + assert_eq!(assr, contigs![(40, 30), (30, 10)]); + } + + #[test] + fn test_join_two() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(10, 10), Ok(())); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add(15, 40), Ok(())); + assert_eq!(assr, contigs![(10, 50)]); + } + + #[test] + fn test_join_two_reversed() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add(10, 10), Ok(())); + assert_eq!(assr.add(15, 40), Ok(())); + assert_eq!(assr, contigs![(10, 50)]); + } + + #[test] + fn test_join_two_overlong() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add(10, 10), Ok(())); + assert_eq!(assr.add(15, 60), Ok(())); + assert_eq!(assr, contigs![(10, 65)]); + } + + #[test] + fn test_iter_empty() { + let assr = Assembler::new(); + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![]); + } + + #[test] + fn test_iter_full() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(0, 16), Ok(())); + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(0, 16)]); + } + + #[test] + fn test_iter_one_front() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(0, 4), Ok(())); + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(0, 4)]); + } + + #[test] + fn test_iter_one_back() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(12, 4), Ok(())); + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(12, 16)]); + } + + #[test] + fn test_iter_one_mid() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(4, 8), Ok(())); + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(4, 12)]); + } + + #[test] + fn test_iter_one_trailing_gap() { + let assr = contigs![(4, 8)]; + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(4, 12)]); + } + + #[test] + fn test_iter_two_split() { + let assr = contigs![(2, 6), (4, 1)]; + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(2, 8), (12, 13)]); + } + + #[test] + fn test_iter_three_split() { + let assr = contigs![(2, 6), (2, 1), (2, 2)]; + let segments: Vec<_> = assr.iter_data().collect(); + assert_eq!(segments, vec![(2, 8), (10, 11), (13, 15)]); + } + + #[test] + fn test_issue_694() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(0, 1), Ok(())); + assert_eq!(assr.add(2, 1), Ok(())); + assert_eq!(assr.add(1, 1), Ok(())); + } + + #[test] + fn test_add_then_remove_front() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add_then_remove_front(10, 10), Ok(0)); + assert_eq!(assr, contigs![(10, 10), (30, 10)]); + } + + #[test] + fn test_add_then_remove_front_at_front() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add_then_remove_front(0, 10), Ok(10)); + assert_eq!(assr, contigs![(40, 10)]); + } + + #[test] + fn test_add_then_remove_front_at_front_touch() { + let mut assr = Assembler::new(); + assert_eq!(assr.add(50, 10), Ok(())); + assert_eq!(assr.add_then_remove_front(0, 50), Ok(60)); + assert_eq!(assr, contigs![]); + } + + #[test] + fn test_add_then_remove_front_at_front_full() { + let mut assr = Assembler::new(); + for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT { + assert_eq!(assr.add(c * 10, 3), Ok(())); + } + // Maximum of allowed holes is reached + let assr_before = assr.clone(); + assert_eq!(assr.add_then_remove_front(1, 3), Err(TooManyHolesError)); + assert_eq!(assr_before, assr); + } + + #[test] + fn test_add_then_remove_front_at_front_full_offset_0() { + let mut assr = Assembler::new(); + for c in 1..=ASSEMBLER_MAX_SEGMENT_COUNT { + assert_eq!(assr.add(c * 10, 3), Ok(())); + } + assert_eq!(assr.add_then_remove_front(0, 3), Ok(3)); + } + + // Test against an obviously-correct but inefficient bitmap impl. + #[test] + fn test_random() { + use rand::Rng; + + const MAX_INDEX: usize = 256; + + for max_size in [2, 5, 10, 100] { + for _ in 0..300 { + //println!("==="); + let mut assr = Assembler::new(); + let mut map = [false; MAX_INDEX]; + + for _ in 0..60 { + let offset = rand::thread_rng().gen_range(0..MAX_INDEX - max_size - 1); + let size = rand::thread_rng().gen_range(1..=max_size); + + //println!("add {}..{} {}", offset, offset + size, size); + // Real impl + let res = assr.add(offset, size); + + // Bitmap impl + let mut map2 = map; + map2[offset..][..size].fill(true); + + let mut contigs = vec![]; + let mut hole: usize = 0; + let mut data: usize = 0; + for b in map2 { + if b { + data += 1; + } else { + if data != 0 { + contigs.push((hole, data)); + hole = 0; + data = 0; + } + hole += 1; + } + } + + // Compare. + let wanted_res = if contigs.len() > ASSEMBLER_MAX_SEGMENT_COUNT { + Err(TooManyHolesError) + } else { + Ok(()) + }; + assert_eq!(res, wanted_res); + if res.is_ok() { + map = map2; + assert_eq!(assr, Assembler::from(contigs)); + } + } + } + } + } +} diff --git a/vendor/smoltcp/src/storage/mod.rs b/vendor/smoltcp/src/storage/mod.rs new file mode 100644 index 00000000..b03de712 --- /dev/null +++ b/vendor/smoltcp/src/storage/mod.rs @@ -0,0 +1,31 @@ +/*! Specialized containers. + +The `storage` module provides containers for use in other modules. +The containers support both pre-allocated memory, without the `std` +or `alloc` crates being available, and heap-allocated memory. +*/ + +mod assembler; +mod packet_buffer; +mod ring_buffer; + +pub use self::assembler::Assembler; +pub use self::packet_buffer::{PacketBuffer, PacketMetadata}; +pub use self::ring_buffer::RingBuffer; + +/// A trait for setting a value to a known state. +/// +/// In-place analog of Default. +pub trait Resettable { + fn reset(&mut self); +} + +/// Error returned when enqueuing into a full buffer. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Full; + +/// Error returned when dequeuing from an empty buffer. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Empty; diff --git a/vendor/smoltcp/src/storage/packet_buffer.rs b/vendor/smoltcp/src/storage/packet_buffer.rs new file mode 100644 index 00000000..058e980a --- /dev/null +++ b/vendor/smoltcp/src/storage/packet_buffer.rs @@ -0,0 +1,409 @@ +use managed::ManagedSlice; + +use crate::storage::{Full, RingBuffer}; + +use super::Empty; + +/// Size and header of a packet. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PacketMetadata { + size: usize, + header: Option, +} + +impl PacketMetadata { + /// Empty packet description. + pub const EMPTY: PacketMetadata = PacketMetadata { + size: 0, + header: None, + }; + + fn padding(size: usize) -> PacketMetadata { + PacketMetadata { + size: size, + header: None, + } + } + + fn packet(size: usize, header: H) -> PacketMetadata { + PacketMetadata { + size: size, + header: Some(header), + } + } + + fn is_padding(&self) -> bool { + self.header.is_none() + } +} + +/// An UDP packet ring buffer. +#[derive(Debug)] +pub struct PacketBuffer<'a, H: 'a> { + metadata_ring: RingBuffer<'a, PacketMetadata>, + payload_ring: RingBuffer<'a, u8>, +} + +impl<'a, H> PacketBuffer<'a, H> { + /// Create a new packet buffer with the provided metadata and payload storage. + /// + /// Metadata storage limits the maximum _number_ of packets in the buffer and payload + /// storage limits the maximum _total size_ of packets. + pub fn new(metadata_storage: MS, payload_storage: PS) -> PacketBuffer<'a, H> + where + MS: Into>>, + PS: Into>, + { + PacketBuffer { + metadata_ring: RingBuffer::new(metadata_storage), + payload_ring: RingBuffer::new(payload_storage), + } + } + + /// Query whether the buffer is empty. + pub fn is_empty(&self) -> bool { + self.metadata_ring.is_empty() + } + + /// Query whether the buffer is full. + pub fn is_full(&self) -> bool { + self.metadata_ring.is_full() + } + + // There is currently no enqueue_with() because of the complexity of managing padding + // in case of failure. + + /// Enqueue a single packet with the given header into the buffer, and + /// return a reference to its payload, or return `Err(Full)` + /// if the buffer is full. + pub fn enqueue(&mut self, size: usize, header: H) -> Result<&mut [u8], Full> { + if self.payload_ring.capacity() < size || self.metadata_ring.is_full() { + return Err(Full); + } + + // Ring is currently empty. Clear it (resetting `read_at`) to maximize + // for contiguous space. + if self.payload_ring.is_empty() { + self.payload_ring.clear(); + } + + let window = self.payload_ring.window(); + let contig_window = self.payload_ring.contiguous_window(); + + if window < size { + return Err(Full); + } else if contig_window < size { + if window - contig_window < size { + // The buffer length is larger than the current contiguous window + // and is larger than the contiguous window will be after adding + // the padding necessary to circle around to the beginning of the + // ring buffer. + return Err(Full); + } else { + // Add padding to the end of the ring buffer so that the + // contiguous window is at the beginning of the ring buffer. + *self.metadata_ring.enqueue_one()? = PacketMetadata::padding(contig_window); + // note(discard): function does not write to the result + // enqueued padding buffer location + let _buf_enqueued = self.payload_ring.enqueue_many(contig_window); + } + } + + *self.metadata_ring.enqueue_one()? = PacketMetadata::packet(size, header); + + let payload_buf = self.payload_ring.enqueue_many(size); + debug_assert!(payload_buf.len() == size); + Ok(payload_buf) + } + + /// Call `f` with a packet from the buffer large enough to fit `max_size` bytes. The packet + /// is shrunk to the size returned from `f` and enqueued into the buffer. + pub fn enqueue_with_infallible<'b, F>( + &'b mut self, + max_size: usize, + header: H, + f: F, + ) -> Result + where + F: FnOnce(&'b mut [u8]) -> usize, + { + if self.payload_ring.capacity() < max_size || self.metadata_ring.is_full() { + return Err(Full); + } + + let window = self.payload_ring.window(); + let contig_window = self.payload_ring.contiguous_window(); + + if window < max_size { + return Err(Full); + } else if contig_window < max_size { + if window - contig_window < max_size { + // The buffer length is larger than the current contiguous window + // and is larger than the contiguous window will be after adding + // the padding necessary to circle around to the beginning of the + // ring buffer. + return Err(Full); + } else { + // Add padding to the end of the ring buffer so that the + // contiguous window is at the beginning of the ring buffer. + *self.metadata_ring.enqueue_one()? = PacketMetadata::padding(contig_window); + // note(discard): function does not write to the result + // enqueued padding buffer location + let _buf_enqueued = self.payload_ring.enqueue_many(contig_window); + } + } + + let (size, _) = self + .payload_ring + .enqueue_many_with(|data| (f(&mut data[..max_size]), ())); + + *self.metadata_ring.enqueue_one()? = PacketMetadata::packet(size, header); + + Ok(size) + } + + fn dequeue_padding(&mut self) { + let _ = self.metadata_ring.dequeue_one_with(|metadata| { + if metadata.is_padding() { + // note(discard): function does not use value of dequeued padding bytes + let _buf_dequeued = self.payload_ring.dequeue_many(metadata.size); + Ok(()) // dequeue metadata + } else { + Err(()) // don't dequeue metadata + } + }); + } + + /// Call `f` with a single packet from the buffer, and dequeue the packet if `f` + /// returns successfully, or return `Err(EmptyError)` if the buffer is empty. + pub fn dequeue_with<'c, R, E, F>(&'c mut self, f: F) -> Result, Empty> + where + F: FnOnce(&mut H, &'c mut [u8]) -> Result, + { + self.dequeue_padding(); + + self.metadata_ring.dequeue_one_with(|metadata| { + self.payload_ring + .dequeue_many_with(|payload_buf| { + debug_assert!(payload_buf.len() >= metadata.size); + + match f( + metadata.header.as_mut().unwrap(), + &mut payload_buf[..metadata.size], + ) { + Ok(val) => (metadata.size, Ok(val)), + Err(err) => (0, Err(err)), + } + }) + .1 + }) + } + + /// Dequeue a single packet from the buffer, and return a reference to its payload + /// as well as its header, or return `Err(Error::Exhausted)` if the buffer is empty. + pub fn dequeue(&mut self) -> Result<(H, &mut [u8]), Empty> { + self.dequeue_padding(); + + let meta = self.metadata_ring.dequeue_one()?; + + let payload_buf = self.payload_ring.dequeue_many(meta.size); + debug_assert!(payload_buf.len() == meta.size); + Ok((meta.header.take().unwrap(), payload_buf)) + } + + /// Peek at a single packet from the buffer without removing it, and return a reference to + /// its payload as well as its header, or return `Err(Error:Exhausted)` if the buffer is empty. + /// + /// This function otherwise behaves identically to [dequeue](#method.dequeue). + pub fn peek(&mut self) -> Result<(&H, &[u8]), Empty> { + self.dequeue_padding(); + + if let Some(metadata) = self.metadata_ring.get_allocated(0, 1).first() { + Ok(( + metadata.header.as_ref().unwrap(), + self.payload_ring.get_allocated(0, metadata.size), + )) + } else { + Err(Empty) + } + } + + /// Return the maximum number packets that can be stored. + pub fn packet_capacity(&self) -> usize { + self.metadata_ring.capacity() + } + + /// Return the maximum number of bytes in the payload ring buffer. + pub fn payload_capacity(&self) -> usize { + self.payload_ring.capacity() + } + + /// Return the current number of bytes in the payload ring buffer. + pub fn payload_bytes_count(&self) -> usize { + self.payload_ring.len() + } + + /// Reset the packet buffer and clear any staged. + #[allow(unused)] + pub(crate) fn reset(&mut self) { + self.payload_ring.clear(); + self.metadata_ring.clear(); + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn buffer() -> PacketBuffer<'static, ()> { + PacketBuffer::new(vec![PacketMetadata::EMPTY; 4], vec![0u8; 16]) + } + + #[test] + fn test_simple() { + let mut buffer = buffer(); + buffer.enqueue(6, ()).unwrap().copy_from_slice(b"abcdef"); + assert_eq!(buffer.enqueue(16, ()), Err(Full)); + assert_eq!(buffer.metadata_ring.len(), 1); + assert_eq!(buffer.dequeue().unwrap().1, &b"abcdef"[..]); + assert_eq!(buffer.dequeue(), Err(Empty)); + } + + #[test] + fn test_peek() { + let mut buffer = buffer(); + assert_eq!(buffer.peek(), Err(Empty)); + buffer.enqueue(6, ()).unwrap().copy_from_slice(b"abcdef"); + assert_eq!(buffer.metadata_ring.len(), 1); + assert_eq!(buffer.peek().unwrap().1, &b"abcdef"[..]); + assert_eq!(buffer.dequeue().unwrap().1, &b"abcdef"[..]); + assert_eq!(buffer.peek(), Err(Empty)); + } + + #[test] + fn test_padding() { + let mut buffer = buffer(); + assert!(buffer.enqueue(6, ()).is_ok()); + assert!(buffer.enqueue(8, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + buffer.enqueue(4, ()).unwrap().copy_from_slice(b"abcd"); + assert_eq!(buffer.metadata_ring.len(), 3); + assert!(buffer.dequeue().is_ok()); + + assert_eq!(buffer.dequeue().unwrap().1, &b"abcd"[..]); + assert_eq!(buffer.metadata_ring.len(), 0); + } + + #[test] + fn test_padding_with_large_payload() { + let mut buffer = buffer(); + assert!(buffer.enqueue(12, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + buffer + .enqueue(12, ()) + .unwrap() + .copy_from_slice(b"abcdefghijkl"); + } + + #[test] + fn test_dequeue_with() { + let mut buffer = buffer(); + assert!(buffer.enqueue(6, ()).is_ok()); + assert!(buffer.enqueue(8, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + buffer.enqueue(4, ()).unwrap().copy_from_slice(b"abcd"); + assert_eq!(buffer.metadata_ring.len(), 3); + assert!(buffer.dequeue().is_ok()); + + assert!(matches!( + buffer.dequeue_with(|_, _| Result::<(), u32>::Err(123)), + Ok(Err(_)) + )); + assert_eq!(buffer.metadata_ring.len(), 1); + + assert!( + buffer + .dequeue_with(|&mut (), payload| { + assert_eq!(payload, &b"abcd"[..]); + Result::<(), ()>::Ok(()) + }) + .is_ok() + ); + assert_eq!(buffer.metadata_ring.len(), 0); + } + + #[test] + fn test_metadata_full_empty() { + let mut buffer = buffer(); + assert!(buffer.is_empty()); + assert!(!buffer.is_full()); + assert!(buffer.enqueue(1, ()).is_ok()); + assert!(!buffer.is_empty()); + assert!(buffer.enqueue(1, ()).is_ok()); + assert!(buffer.enqueue(1, ()).is_ok()); + assert!(!buffer.is_full()); + assert!(!buffer.is_empty()); + assert!(buffer.enqueue(1, ()).is_ok()); + assert!(buffer.is_full()); + assert!(!buffer.is_empty()); + assert_eq!(buffer.metadata_ring.len(), 4); + assert_eq!(buffer.enqueue(1, ()), Err(Full)); + } + + #[test] + fn test_window_too_small() { + let mut buffer = buffer(); + assert!(buffer.enqueue(4, ()).is_ok()); + assert!(buffer.enqueue(8, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + assert_eq!(buffer.enqueue(16, ()), Err(Full)); + assert_eq!(buffer.metadata_ring.len(), 1); + } + + #[test] + fn test_contiguous_window_too_small() { + let mut buffer = buffer(); + assert!(buffer.enqueue(4, ()).is_ok()); + assert!(buffer.enqueue(8, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + assert_eq!(buffer.enqueue(8, ()), Err(Full)); + assert_eq!(buffer.metadata_ring.len(), 1); + } + + #[test] + fn test_contiguous_window_wrap() { + let mut buffer = buffer(); + assert!(buffer.enqueue(15, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + assert!(buffer.enqueue(16, ()).is_ok()); + } + + #[test] + fn test_capacity_too_small() { + let mut buffer = buffer(); + assert_eq!(buffer.enqueue(32, ()), Err(Full)); + } + + #[test] + fn test_contig_window_prioritized() { + let mut buffer = buffer(); + assert!(buffer.enqueue(4, ()).is_ok()); + assert!(buffer.dequeue().is_ok()); + assert!(buffer.enqueue(5, ()).is_ok()); + } + + #[test] + fn clear() { + let mut buffer = buffer(); + + // Ensure enqueuing data in the buffer fills it somewhat. + assert!(buffer.is_empty()); + assert!(buffer.enqueue(6, ()).is_ok()); + + // Ensure that resetting the buffer causes it to be empty. + assert!(!buffer.is_empty()); + buffer.reset(); + assert!(buffer.is_empty()); + } +} diff --git a/vendor/smoltcp/src/storage/ring_buffer.rs b/vendor/smoltcp/src/storage/ring_buffer.rs new file mode 100644 index 00000000..7d461b68 --- /dev/null +++ b/vendor/smoltcp/src/storage/ring_buffer.rs @@ -0,0 +1,803 @@ +// Some of the functions in ring buffer is marked as #[must_use]. It notes that +// these functions may have side effects, and it's implemented by [RFC 1940]. +// [RFC 1940]: https://github.com/rust-lang/rust/issues/43302 + +use core::cmp; +use managed::ManagedSlice; + +use crate::storage::Resettable; + +use super::{Empty, Full}; + +/// A ring buffer. +/// +/// This ring buffer implementation provides many ways to interact with it: +/// +/// * Enqueueing or dequeueing one element from corresponding side of the buffer; +/// * Enqueueing or dequeueing a slice of elements from corresponding side of the buffer; +/// * Accessing allocated and unallocated areas directly. +/// +/// It is also zero-copy; all methods provide references into the buffer's storage. +/// Note that all references are mutable; it is considered more important to allow +/// in-place processing than to protect from accidental mutation. +/// +/// This implementation is suitable for both simple uses such as a FIFO queue +/// of UDP packets, and advanced ones such as a TCP reassembly buffer. +#[derive(Debug)] +pub struct RingBuffer<'a, T: 'a> { + storage: ManagedSlice<'a, T>, + read_at: usize, + length: usize, +} + +impl<'a, T: 'a> RingBuffer<'a, T> { + /// Create a ring buffer with the given storage. + /// + /// During creation, every element in `storage` is reset. + pub fn new(storage: S) -> RingBuffer<'a, T> + where + S: Into>, + { + RingBuffer { + storage: storage.into(), + read_at: 0, + length: 0, + } + } + + /// Clear the ring buffer. + pub fn clear(&mut self) { + self.read_at = 0; + self.length = 0; + } + + /// Return the maximum number of elements in the ring buffer. + pub fn capacity(&self) -> usize { + self.storage.len() + } + + /// Clear the ring buffer, and reset every element. + pub fn reset(&mut self) + where + T: Resettable, + { + self.clear(); + for elem in self.storage.iter_mut() { + elem.reset(); + } + } + + /// Return the current number of elements in the ring buffer. + pub fn len(&self) -> usize { + self.length + } + + /// Return the number of elements that can be added to the ring buffer. + pub fn window(&self) -> usize { + self.capacity() - self.len() + } + + /// Return the largest number of elements that can be added to the buffer + /// without wrapping around (i.e. in a single `enqueue_many` call). + pub fn contiguous_window(&self) -> usize { + cmp::min(self.window(), self.capacity() - self.get_idx(self.length)) + } + + /// Query whether the buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Query whether the buffer is full. + pub fn is_full(&self) -> bool { + self.window() == 0 + } + + /// Shorthand for `(self.read + idx) % self.capacity()` with an + /// additional check to ensure that the capacity is not zero. + fn get_idx(&self, idx: usize) -> usize { + let len = self.capacity(); + if len > 0 { + (self.read_at + idx) % len + } else { + 0 + } + } + + /// Shorthand for `(self.read + idx) % self.capacity()` with no + /// additional checks to ensure the capacity is not zero. + fn get_idx_unchecked(&self, idx: usize) -> usize { + (self.read_at + idx) % self.capacity() + } +} + +/// This is the "discrete" ring buffer interface: it operates with single elements, +/// and boundary conditions (empty/full) are errors. +impl<'a, T: 'a> RingBuffer<'a, T> { + /// Call `f` with a single buffer element, and enqueue the element if `f` + /// returns successfully, or return `Err(Full)` if the buffer is full. + pub fn enqueue_one_with<'b, R, E, F>(&'b mut self, f: F) -> Result, Full> + where + F: FnOnce(&'b mut T) -> Result, + { + if self.is_full() { + return Err(Full); + } + + let index = self.get_idx_unchecked(self.length); + let res = f(&mut self.storage[index]); + if res.is_ok() { + self.length += 1; + } + Ok(res) + } + + /// Enqueue a single element into the buffer, and return a reference to it, + /// or return `Err(Full)` if the buffer is full. + /// + /// This function is a shortcut for `ring_buf.enqueue_one_with(Ok)`. + pub fn enqueue_one(&mut self) -> Result<&mut T, Full> { + self.enqueue_one_with(Ok)? + } + + /// Call `f` with a single buffer element, and dequeue the element if `f` + /// returns successfully, or return `Err(Empty)` if the buffer is empty. + pub fn dequeue_one_with<'b, R, E, F>(&'b mut self, f: F) -> Result, Empty> + where + F: FnOnce(&'b mut T) -> Result, + { + if self.is_empty() { + return Err(Empty); + } + + let next_at = self.get_idx_unchecked(1); + let res = f(&mut self.storage[self.read_at]); + + if res.is_ok() { + self.length -= 1; + self.read_at = next_at; + } + Ok(res) + } + + /// Dequeue an element from the buffer, and return a reference to it, + /// or return `Err(Empty)` if the buffer is empty. + /// + /// This function is a shortcut for `ring_buf.dequeue_one_with(Ok)`. + pub fn dequeue_one(&mut self) -> Result<&mut T, Empty> { + self.dequeue_one_with(Ok)? + } +} + +/// This is the "continuous" ring buffer interface: it operates with element slices, +/// and boundary conditions (empty/full) simply result in empty slices. +impl<'a, T: 'a> RingBuffer<'a, T> { + /// Call `f` with the largest contiguous slice of unallocated buffer elements, + /// and enqueue the amount of elements returned by `f`. + /// + /// # Panics + /// This function panics if the amount of elements returned by `f` is larger + /// than the size of the slice passed into it. + pub fn enqueue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R) + where + F: FnOnce(&'b mut [T]) -> (usize, R), + { + if self.length == 0 { + // Ring is currently empty. Reset `read_at` to optimize + // for contiguous space. + self.read_at = 0; + } + + let write_at = self.get_idx(self.length); + let max_size = self.contiguous_window(); + let (size, result) = f(&mut self.storage[write_at..write_at + max_size]); + assert!(size <= max_size); + self.length += size; + (size, result) + } + + /// Enqueue a slice of elements up to the given size into the buffer, + /// and return a reference to them. + /// + /// This function may return a slice smaller than the given size + /// if the free space in the buffer is not contiguous. + #[must_use] + pub fn enqueue_many(&mut self, size: usize) -> &mut [T] { + self.enqueue_many_with(|buf| { + let size = cmp::min(size, buf.len()); + (size, &mut buf[..size]) + }) + .1 + } + + /// Enqueue as many elements from the given slice into the buffer as possible, + /// and return the amount of elements that could fit. + #[must_use] + pub fn enqueue_slice(&mut self, data: &[T]) -> usize + where + T: Copy, + { + let (size_1, data) = self.enqueue_many_with(|buf| { + let size = cmp::min(buf.len(), data.len()); + buf[..size].copy_from_slice(&data[..size]); + (size, &data[size..]) + }); + let (size_2, ()) = self.enqueue_many_with(|buf| { + let size = cmp::min(buf.len(), data.len()); + buf[..size].copy_from_slice(&data[..size]); + (size, ()) + }); + size_1 + size_2 + } + + /// Call `f` with the largest contiguous slice of allocated buffer elements, + /// and dequeue the amount of elements returned by `f`. + /// + /// # Panics + /// This function panics if the amount of elements returned by `f` is larger + /// than the size of the slice passed into it. + pub fn dequeue_many_with<'b, R, F>(&'b mut self, f: F) -> (usize, R) + where + F: FnOnce(&'b mut [T]) -> (usize, R), + { + let capacity = self.capacity(); + let max_size = cmp::min(self.len(), capacity - self.read_at); + let (size, result) = f(&mut self.storage[self.read_at..self.read_at + max_size]); + assert!(size <= max_size); + self.read_at = if capacity > 0 { + (self.read_at + size) % capacity + } else { + 0 + }; + self.length -= size; + (size, result) + } + + /// Dequeue a slice of elements up to the given size from the buffer, + /// and return a reference to them. + /// + /// This function may return a slice smaller than the given size + /// if the allocated space in the buffer is not contiguous. + #[must_use] + pub fn dequeue_many(&mut self, size: usize) -> &mut [T] { + self.dequeue_many_with(|buf| { + let size = cmp::min(size, buf.len()); + (size, &mut buf[..size]) + }) + .1 + } + + /// Dequeue as many elements from the buffer into the given slice as possible, + /// and return the amount of elements that could fit. + #[must_use] + pub fn dequeue_slice(&mut self, data: &mut [T]) -> usize + where + T: Copy, + { + let (size_1, data) = self.dequeue_many_with(|buf| { + let size = cmp::min(buf.len(), data.len()); + data[..size].copy_from_slice(&buf[..size]); + (size, &mut data[size..]) + }); + let (size_2, ()) = self.dequeue_many_with(|buf| { + let size = cmp::min(buf.len(), data.len()); + data[..size].copy_from_slice(&buf[..size]); + (size, ()) + }); + size_1 + size_2 + } +} + +/// This is the "random access" ring buffer interface: it operates with element slices, +/// and allows to access elements of the buffer that are not adjacent to its head or tail. +impl<'a, T: 'a> RingBuffer<'a, T> { + /// Return the largest contiguous slice of unallocated buffer elements starting + /// at the given offset past the last allocated element, and up to the given size. + #[must_use] + pub fn get_unallocated(&mut self, offset: usize, mut size: usize) -> &mut [T] { + let start_at = self.get_idx(self.length + offset); + // We can't access past the end of unallocated data. + if offset > self.window() { + return &mut []; + } + // We can't enqueue more than there is free space. + let clamped_window = self.window() - offset; + if size > clamped_window { + size = clamped_window + } + // We can't contiguously enqueue past the end of the storage. + let until_end = self.capacity() - start_at; + if size > until_end { + size = until_end + } + + &mut self.storage[start_at..start_at + size] + } + + /// Write as many elements from the given slice into unallocated buffer elements + /// starting at the given offset past the last allocated element, and return + /// the amount written. + #[must_use] + pub fn write_unallocated(&mut self, offset: usize, data: &[T]) -> usize + where + T: Copy, + { + let (size_1, offset, data) = { + let slice = self.get_unallocated(offset, data.len()); + let slice_len = slice.len(); + slice.copy_from_slice(&data[..slice_len]); + (slice_len, offset + slice_len, &data[slice_len..]) + }; + let size_2 = { + let slice = self.get_unallocated(offset, data.len()); + let slice_len = slice.len(); + slice.copy_from_slice(&data[..slice_len]); + slice_len + }; + size_1 + size_2 + } + + /// Enqueue the given number of unallocated buffer elements. + /// + /// # Panics + /// Panics if the number of elements given exceeds the number of unallocated elements. + pub fn enqueue_unallocated(&mut self, count: usize) { + assert!(count <= self.window()); + self.length += count; + } + + /// Return the largest contiguous slice of allocated buffer elements starting + /// at the given offset past the first allocated element, and up to the given size. + #[must_use] + pub fn get_allocated(&self, offset: usize, mut size: usize) -> &[T] { + let start_at = self.get_idx(offset); + // We can't read past the end of the allocated data. + if offset > self.length { + return &mut []; + } + // We can't read more than we have allocated. + let clamped_length = self.length - offset; + if size > clamped_length { + size = clamped_length + } + // We can't contiguously dequeue past the end of the storage. + let until_end = self.capacity() - start_at; + if size > until_end { + size = until_end + } + + &self.storage[start_at..start_at + size] + } + + /// Read as many elements from allocated buffer elements into the given slice + /// starting at the given offset past the first allocated element, and return + /// the amount read. + #[must_use] + pub fn read_allocated(&mut self, offset: usize, data: &mut [T]) -> usize + where + T: Copy, + { + let (size_1, offset, data) = { + let slice = self.get_allocated(offset, data.len()); + data[..slice.len()].copy_from_slice(slice); + (slice.len(), offset + slice.len(), &mut data[slice.len()..]) + }; + let size_2 = { + let slice = self.get_allocated(offset, data.len()); + data[..slice.len()].copy_from_slice(slice); + slice.len() + }; + size_1 + size_2 + } + + /// Dequeue the given number of allocated buffer elements. + /// + /// # Panics + /// Panics if the number of elements given exceeds the number of allocated elements. + pub fn dequeue_allocated(&mut self, count: usize) { + assert!(count <= self.len()); + self.length -= count; + self.read_at = self.get_idx(count); + } +} + +impl<'a, T: 'a> From> for RingBuffer<'a, T> { + fn from(slice: ManagedSlice<'a, T>) -> RingBuffer<'a, T> { + RingBuffer::new(slice) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_buffer_length_changes() { + let mut ring = RingBuffer::new(vec![0; 2]); + assert!(ring.is_empty()); + assert!(!ring.is_full()); + assert_eq!(ring.len(), 0); + assert_eq!(ring.capacity(), 2); + assert_eq!(ring.window(), 2); + + ring.length = 1; + assert!(!ring.is_empty()); + assert!(!ring.is_full()); + assert_eq!(ring.len(), 1); + assert_eq!(ring.capacity(), 2); + assert_eq!(ring.window(), 1); + + ring.length = 2; + assert!(!ring.is_empty()); + assert!(ring.is_full()); + assert_eq!(ring.len(), 2); + assert_eq!(ring.capacity(), 2); + assert_eq!(ring.window(), 0); + } + + #[test] + fn test_buffer_enqueue_dequeue_one_with() { + let mut ring = RingBuffer::new(vec![0; 5]); + assert_eq!( + ring.dequeue_one_with(|_| -> Result::<(), ()> { unreachable!() }), + Err(Empty) + ); + + ring.enqueue_one_with(Ok::<_, ()>).unwrap().unwrap(); + assert!(!ring.is_empty()); + assert!(!ring.is_full()); + + for i in 1..5 { + ring.enqueue_one_with(|e| Ok::<_, ()>(*e = i)) + .unwrap() + .unwrap(); + assert!(!ring.is_empty()); + } + assert!(ring.is_full()); + assert_eq!( + ring.enqueue_one_with(|_| -> Result::<(), ()> { unreachable!() }), + Err(Full) + ); + + for i in 0..5 { + assert_eq!( + ring.dequeue_one_with(|e| Ok::<_, ()>(*e)).unwrap().unwrap(), + i + ); + assert!(!ring.is_full()); + } + assert_eq!( + ring.dequeue_one_with(|_| -> Result::<(), ()> { unreachable!() }), + Err(Empty) + ); + assert!(ring.is_empty()); + } + + #[test] + fn test_buffer_enqueue_dequeue_one() { + let mut ring = RingBuffer::new(vec![0; 5]); + assert_eq!(ring.dequeue_one(), Err(Empty)); + + ring.enqueue_one().unwrap(); + assert!(!ring.is_empty()); + assert!(!ring.is_full()); + + for i in 1..5 { + *ring.enqueue_one().unwrap() = i; + assert!(!ring.is_empty()); + } + assert!(ring.is_full()); + assert_eq!(ring.enqueue_one(), Err(Full)); + + for i in 0..5 { + assert_eq!(*ring.dequeue_one().unwrap(), i); + assert!(!ring.is_full()); + } + assert_eq!(ring.dequeue_one(), Err(Empty)); + assert!(ring.is_empty()); + } + + #[test] + fn test_buffer_enqueue_many_with() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!( + ring.enqueue_many_with(|buf| { + assert_eq!(buf.len(), 12); + buf[0..2].copy_from_slice(b"ab"); + (2, true) + }), + (2, true) + ); + assert_eq!(ring.len(), 2); + assert_eq!(&ring.storage[..], b"ab.........."); + + ring.enqueue_many_with(|buf| { + assert_eq!(buf.len(), 12 - 2); + buf[0..4].copy_from_slice(b"cdXX"); + (2, ()) + }); + assert_eq!(ring.len(), 4); + assert_eq!(&ring.storage[..], b"abcdXX......"); + + ring.enqueue_many_with(|buf| { + assert_eq!(buf.len(), 12 - 4); + buf[0..4].copy_from_slice(b"efgh"); + (4, ()) + }); + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"abcdefgh...."); + + for _ in 0..4 { + *ring.dequeue_one().unwrap() = b'.'; + } + assert_eq!(ring.len(), 4); + assert_eq!(&ring.storage[..], b"....efgh...."); + + ring.enqueue_many_with(|buf| { + assert_eq!(buf.len(), 12 - 8); + buf[0..4].copy_from_slice(b"ijkl"); + (4, ()) + }); + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"....efghijkl"); + + ring.enqueue_many_with(|buf| { + assert_eq!(buf.len(), 4); + buf[0..4].copy_from_slice(b"abcd"); + (4, ()) + }); + assert_eq!(ring.len(), 12); + assert_eq!(&ring.storage[..], b"abcdefghijkl"); + + for _ in 0..4 { + *ring.dequeue_one().unwrap() = b'.'; + } + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"abcd....ijkl"); + } + + #[test] + fn test_buffer_enqueue_many() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + ring.enqueue_many(8).copy_from_slice(b"abcdefgh"); + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"abcdefgh...."); + + ring.enqueue_many(8).copy_from_slice(b"ijkl"); + assert_eq!(ring.len(), 12); + assert_eq!(&ring.storage[..], b"abcdefghijkl"); + } + + #[test] + fn test_buffer_enqueue_slice() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!(ring.enqueue_slice(b"abcdefgh"), 8); + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"abcdefgh...."); + + for _ in 0..4 { + *ring.dequeue_one().unwrap() = b'.'; + } + assert_eq!(ring.len(), 4); + assert_eq!(&ring.storage[..], b"....efgh...."); + + assert_eq!(ring.enqueue_slice(b"ijklabcd"), 8); + assert_eq!(ring.len(), 12); + assert_eq!(&ring.storage[..], b"abcdefghijkl"); + } + + #[test] + fn test_buffer_dequeue_many_with() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12); + + assert_eq!( + ring.dequeue_many_with(|buf| { + assert_eq!(buf.len(), 12); + assert_eq!(buf, b"abcdefghijkl"); + buf[..4].copy_from_slice(b"...."); + (4, true) + }), + (4, true) + ); + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"....efghijkl"); + + ring.dequeue_many_with(|buf| { + assert_eq!(buf, b"efghijkl"); + buf[..4].copy_from_slice(b"...."); + (4, ()) + }); + assert_eq!(ring.len(), 4); + assert_eq!(&ring.storage[..], b"........ijkl"); + + assert_eq!(ring.enqueue_slice(b"abcd"), 4); + assert_eq!(ring.len(), 8); + + ring.dequeue_many_with(|buf| { + assert_eq!(buf, b"ijkl"); + buf[..4].copy_from_slice(b"...."); + (4, ()) + }); + ring.dequeue_many_with(|buf| { + assert_eq!(buf, b"abcd"); + buf[..4].copy_from_slice(b"...."); + (4, ()) + }); + assert_eq!(ring.len(), 0); + assert_eq!(&ring.storage[..], b"............"); + } + + #[test] + fn test_buffer_dequeue_many() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12); + + { + let buf = ring.dequeue_many(8); + assert_eq!(buf, b"abcdefgh"); + buf.copy_from_slice(b"........"); + } + assert_eq!(ring.len(), 4); + assert_eq!(&ring.storage[..], b"........ijkl"); + + { + let buf = ring.dequeue_many(8); + assert_eq!(buf, b"ijkl"); + buf.copy_from_slice(b"...."); + } + assert_eq!(ring.len(), 0); + assert_eq!(&ring.storage[..], b"............"); + } + + #[test] + fn test_buffer_dequeue_slice() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!(ring.enqueue_slice(b"abcdefghijkl"), 12); + + { + let mut buf = [0; 8]; + assert_eq!(ring.dequeue_slice(&mut buf[..]), 8); + assert_eq!(&buf[..], b"abcdefgh"); + assert_eq!(ring.len(), 4); + } + + assert_eq!(ring.enqueue_slice(b"abcd"), 4); + + { + let mut buf = [0; 8]; + assert_eq!(ring.dequeue_slice(&mut buf[..]), 8); + assert_eq!(&buf[..], b"ijklabcd"); + assert_eq!(ring.len(), 0); + } + } + + #[test] + fn test_buffer_get_unallocated() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!(ring.get_unallocated(16, 4), b""); + + { + let buf = ring.get_unallocated(0, 4); + buf.copy_from_slice(b"abcd"); + } + assert_eq!(&ring.storage[..], b"abcd........"); + + let buf_enqueued = ring.enqueue_many(4); + assert_eq!(buf_enqueued.len(), 4); + assert_eq!(ring.len(), 4); + + { + let buf = ring.get_unallocated(4, 8); + buf.copy_from_slice(b"ijkl"); + } + assert_eq!(&ring.storage[..], b"abcd....ijkl"); + + ring.enqueue_many(8).copy_from_slice(b"EFGHIJKL"); + ring.dequeue_many(4).copy_from_slice(b"abcd"); + assert_eq!(ring.len(), 8); + assert_eq!(&ring.storage[..], b"abcdEFGHIJKL"); + + { + let buf = ring.get_unallocated(0, 8); + buf.copy_from_slice(b"ABCD"); + } + assert_eq!(&ring.storage[..], b"ABCDEFGHIJKL"); + } + + #[test] + fn test_buffer_write_unallocated() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + ring.enqueue_many(6).copy_from_slice(b"abcdef"); + ring.dequeue_many(6).copy_from_slice(b"ABCDEF"); + + assert_eq!(ring.write_unallocated(0, b"ghi"), 3); + assert_eq!(ring.get_unallocated(0, 3), b"ghi"); + + assert_eq!(ring.write_unallocated(3, b"jklmno"), 6); + assert_eq!(ring.get_unallocated(3, 3), b"jkl"); + + assert_eq!(ring.write_unallocated(9, b"pqrstu"), 3); + assert_eq!(ring.get_unallocated(9, 3), b"pqr"); + } + + #[test] + fn test_buffer_get_allocated() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + + assert_eq!(ring.get_allocated(16, 4), b""); + assert_eq!(ring.get_allocated(0, 4), b""); + + let len_enqueued = ring.enqueue_slice(b"abcd"); + assert_eq!(ring.get_allocated(0, 8), b"abcd"); + assert_eq!(len_enqueued, 4); + + let len_enqueued = ring.enqueue_slice(b"efghijkl"); + ring.dequeue_many(4).copy_from_slice(b"...."); + assert_eq!(ring.get_allocated(4, 8), b"ijkl"); + assert_eq!(len_enqueued, 8); + + let len_enqueued = ring.enqueue_slice(b"abcd"); + assert_eq!(ring.get_allocated(4, 8), b"ijkl"); + assert_eq!(len_enqueued, 4); + } + + #[test] + fn test_buffer_read_allocated() { + let mut ring = RingBuffer::new(vec![b'.'; 12]); + ring.enqueue_many(12).copy_from_slice(b"abcdefghijkl"); + + let mut data = [0; 6]; + assert_eq!(ring.read_allocated(0, &mut data[..]), 6); + assert_eq!(&data[..], b"abcdef"); + + ring.dequeue_many(6).copy_from_slice(b"ABCDEF"); + ring.enqueue_many(3).copy_from_slice(b"mno"); + + let mut data = [0; 6]; + assert_eq!(ring.read_allocated(3, &mut data[..]), 6); + assert_eq!(&data[..], b"jklmno"); + + let mut data = [0; 6]; + assert_eq!(ring.read_allocated(6, &mut data[..]), 3); + assert_eq!(&data[..], b"mno\x00\x00\x00"); + } + + #[test] + fn test_buffer_with_no_capacity() { + let mut no_capacity: RingBuffer = RingBuffer::new(vec![]); + + // Call all functions that calculate the remainder against rx_buffer.capacity() + // with a backing storage with a length of 0. + assert_eq!(no_capacity.get_unallocated(0, 0), &[]); + assert_eq!(no_capacity.get_allocated(0, 0), &[]); + no_capacity.dequeue_allocated(0); + assert_eq!(no_capacity.enqueue_many(0), &[]); + assert_eq!(no_capacity.enqueue_one(), Err(Full)); + assert_eq!(no_capacity.contiguous_window(), 0); + } + + /// Use the buffer a bit. Then empty it and put in an item of + /// maximum size. By detecting a length of 0, the implementation + /// can reset the current buffer position. + #[test] + fn test_buffer_write_wholly() { + let mut ring = RingBuffer::new(vec![b'.'; 8]); + ring.enqueue_many(2).copy_from_slice(b"ab"); + ring.enqueue_many(2).copy_from_slice(b"cd"); + assert_eq!(ring.len(), 4); + let buf_dequeued = ring.dequeue_many(4); + assert_eq!(buf_dequeued, b"abcd"); + assert_eq!(ring.len(), 0); + + let large = ring.enqueue_many(8); + assert_eq!(large.len(), 8); + } +} diff --git a/vendor/smoltcp/src/tests.rs b/vendor/smoltcp/src/tests.rs new file mode 100644 index 00000000..165bd52f --- /dev/null +++ b/vendor/smoltcp/src/tests.rs @@ -0,0 +1,147 @@ +use std::collections::VecDeque; + +use crate::iface::*; +use crate::phy::{self, Device, DeviceCapabilities, Medium}; +use crate::time::Instant; +use crate::wire::*; + +pub(crate) fn setup<'a>(medium: Medium) -> (Interface, SocketSet<'a>, TestingDevice) { + let mut device = TestingDevice::new(medium); + + let config = Config::new(match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + HardwareAddress::Ethernet(EthernetAddress([0x02, 0x02, 0x02, 0x02, 0x02, 0x02])) + } + #[cfg(feature = "medium-ip")] + Medium::Ip => HardwareAddress::Ip, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => HardwareAddress::Ieee802154(Ieee802154Address::Extended([ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + ])), + }); + + let mut iface = Interface::new(config, &mut device, Instant::ZERO); + + #[cfg(feature = "proto-ipv4")] + { + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v4(192, 168, 1, 1), 24)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8)) + .unwrap(); + }); + } + + #[cfg(feature = "proto-ipv6")] + { + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128)) + .unwrap(); + ip_addrs + .push(IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64)) + .unwrap(); + }); + } + + (iface, SocketSet::new(vec![]), device) +} + +/// A testing device. +#[derive(Debug)] +pub struct TestingDevice { + pub(crate) tx_queue: VecDeque>, + pub(crate) rx_queue: VecDeque>, + max_transmission_unit: usize, + medium: Medium, +} + +#[allow(clippy::new_without_default)] +impl TestingDevice { + /// Creates a testing device. + /// + /// Every packet transmitted through this device will be received through it + /// in FIFO order. + pub fn new(medium: Medium) -> Self { + TestingDevice { + tx_queue: VecDeque::new(), + rx_queue: VecDeque::new(), + max_transmission_unit: match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => 1514, + #[cfg(feature = "medium-ip")] + Medium::Ip => 1500, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => 1500, + }, + medium, + } + } +} + +impl Device for TestingDevice { + type RxToken<'a> = RxToken; + type TxToken<'a> = TxToken<'a>; + + fn capabilities(&self) -> DeviceCapabilities { + DeviceCapabilities { + medium: self.medium, + max_transmission_unit: self.max_transmission_unit, + ..DeviceCapabilities::default() + } + } + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.rx_queue.pop_front().map(move |buffer| { + let rx = RxToken { buffer }; + let tx = TxToken { + queue: &mut self.tx_queue, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(TxToken { + queue: &mut self.tx_queue, + }) + } +} + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(&self.buffer) + } +} + +#[doc(hidden)] +#[derive(Debug)] +pub struct TxToken<'a> { + queue: &'a mut VecDeque>, +} + +impl<'a> phy::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + self.queue.push_back(buffer); + result + } +} diff --git a/vendor/smoltcp/src/time.rs b/vendor/smoltcp/src/time.rs new file mode 100644 index 00000000..f06a8d0a --- /dev/null +++ b/vendor/smoltcp/src/time.rs @@ -0,0 +1,460 @@ +/*! Time structures. + +The `time` module contains structures used to represent both +absolute and relative time. + + - [Instant] is used to represent absolute time. + - [Duration] is used to represent relative time. + +[Instant]: struct.Instant.html +[Duration]: struct.Duration.html +*/ + +use core::{fmt, ops}; + +/// A representation of an absolute time value. +/// +/// The `Instant` type is a wrapper around a `i64` value that +/// represents a number of microseconds, monotonically increasing +/// since an arbitrary moment in time, such as system startup. +/// +/// * A value of `0` is inherently arbitrary. +/// * A value less than `0` indicates a time before the starting +/// point. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Instant { + micros: i64, +} + +impl Instant { + pub const ZERO: Instant = Instant::from_micros_const(0); + + /// Create a new `Instant` from a number of microseconds. + pub fn from_micros>(micros: T) -> Instant { + Instant { + micros: micros.into(), + } + } + + pub const fn from_micros_const(micros: i64) -> Instant { + Instant { micros } + } + + /// Create a new `Instant` from a number of milliseconds. + pub fn from_millis>(millis: T) -> Instant { + Instant { + micros: millis.into() * 1000, + } + } + + /// Create a new `Instant` from a number of milliseconds. + pub const fn from_millis_const(millis: i64) -> Instant { + Instant { + micros: millis * 1000, + } + } + + /// Create a new `Instant` from a number of seconds. + pub fn from_secs>(secs: T) -> Instant { + Instant { + micros: secs.into() * 1000000, + } + } + + /// Create a new `Instant` from the current [std::time::SystemTime]. + /// + /// See [std::time::SystemTime::now] + /// + /// [std::time::SystemTime]: https://doc.rust-lang.org/std/time/struct.SystemTime.html + /// [std::time::SystemTime::now]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#method.now + #[cfg(feature = "std")] + pub fn now() -> Instant { + Self::from(::std::time::SystemTime::now()) + } + + /// The fractional number of milliseconds that have passed + /// since the beginning of time. + pub const fn millis(&self) -> i64 { + self.micros % 1000000 / 1000 + } + + /// The fractional number of microseconds that have passed + /// since the beginning of time. + pub const fn micros(&self) -> i64 { + self.micros % 1000000 + } + + /// The number of whole seconds that have passed since the + /// beginning of time. + pub const fn secs(&self) -> i64 { + self.micros / 1000000 + } + + /// The total number of milliseconds that have passed since + /// the beginning of time. + pub const fn total_millis(&self) -> i64 { + self.micros / 1000 + } + /// The total number of milliseconds that have passed since + /// the beginning of time. + pub const fn total_micros(&self) -> i64 { + self.micros + } +} + +#[cfg(feature = "std")] +impl From<::std::time::Instant> for Instant { + fn from(other: ::std::time::Instant) -> Instant { + let elapsed = other.elapsed(); + Instant::from_micros((elapsed.as_secs() * 1_000000) as i64 + elapsed.subsec_micros() as i64) + } +} + +#[cfg(feature = "std")] +impl From<::std::time::SystemTime> for Instant { + fn from(other: ::std::time::SystemTime) -> Instant { + let n = other + .duration_since(::std::time::UNIX_EPOCH) + .expect("start time must not be before the unix epoch"); + Self::from_micros(n.as_secs() as i64 * 1000000 + n.subsec_micros() as i64) + } +} + +#[cfg(feature = "std")] +impl From for ::std::time::SystemTime { + fn from(val: Instant) -> Self { + ::std::time::UNIX_EPOCH + ::std::time::Duration::from_micros(val.micros as u64) + } +} + +impl fmt::Display for Instant { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{:0>3}s", self.secs(), self.millis()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Instant { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}.{:03}s", self.secs(), self.millis()); + } +} + +impl ops::Add for Instant { + type Output = Instant; + + fn add(self, rhs: Duration) -> Instant { + Instant::from_micros(self.micros + rhs.total_micros() as i64) + } +} + +impl ops::AddAssign for Instant { + fn add_assign(&mut self, rhs: Duration) { + self.micros += rhs.total_micros() as i64; + } +} + +impl ops::Sub for Instant { + type Output = Instant; + + fn sub(self, rhs: Duration) -> Instant { + Instant::from_micros(self.micros - rhs.total_micros() as i64) + } +} + +impl ops::SubAssign for Instant { + fn sub_assign(&mut self, rhs: Duration) { + self.micros -= rhs.total_micros() as i64; + } +} + +impl ops::Sub for Instant { + type Output = Duration; + + fn sub(self, rhs: Instant) -> Duration { + Duration::from_micros((self.micros - rhs.micros).unsigned_abs()) + } +} + +/// A relative amount of time. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Duration { + micros: u64, +} + +impl Duration { + pub const ZERO: Duration = Duration::from_micros(0); + /// The longest possible duration we can encode. + pub const MAX: Duration = Duration::from_micros(u64::MAX); + /// Create a new `Duration` from a number of microseconds. + pub const fn from_micros(micros: u64) -> Duration { + Duration { micros } + } + + /// Create a new `Duration` from a number of milliseconds. + pub const fn from_millis(millis: u64) -> Duration { + Duration { + micros: millis * 1000, + } + } + + /// Create a new `Duration` from a number of seconds. + pub const fn from_secs(secs: u64) -> Duration { + Duration { + micros: secs * 1000000, + } + } + + /// The fractional number of milliseconds in this `Duration`. + pub const fn millis(&self) -> u64 { + self.micros / 1000 % 1000 + } + + /// The fractional number of milliseconds in this `Duration`. + pub const fn micros(&self) -> u64 { + self.micros % 1000000 + } + + /// The number of whole seconds in this `Duration`. + pub const fn secs(&self) -> u64 { + self.micros / 1000000 + } + + /// The total number of milliseconds in this `Duration`. + pub const fn total_millis(&self) -> u64 { + self.micros / 1000 + } + + /// The total number of microseconds in this `Duration`. + pub const fn total_micros(&self) -> u64 { + self.micros + } +} + +impl fmt::Display for Duration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{:03}s", self.secs(), self.millis()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Duration { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}.{:03}s", self.secs(), self.millis()); + } +} + +impl ops::Add for Duration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Duration { + Duration::from_micros(self.micros + rhs.total_micros()) + } +} + +impl ops::AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + self.micros += rhs.total_micros(); + } +} + +impl ops::Sub for Duration { + type Output = Duration; + + fn sub(self, rhs: Duration) -> Duration { + Duration::from_micros( + self.micros + .checked_sub(rhs.total_micros()) + .expect("overflow when subtracting durations"), + ) + } +} + +impl ops::SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + self.micros = self + .micros + .checked_sub(rhs.total_micros()) + .expect("overflow when subtracting durations"); + } +} + +impl ops::Mul for Duration { + type Output = Duration; + + fn mul(self, rhs: u32) -> Duration { + Duration::from_micros(self.micros * rhs as u64) + } +} + +impl ops::MulAssign for Duration { + fn mul_assign(&mut self, rhs: u32) { + self.micros *= rhs as u64; + } +} + +impl ops::Div for Duration { + type Output = Duration; + + fn div(self, rhs: u32) -> Duration { + Duration::from_micros(self.micros / rhs as u64) + } +} + +impl ops::DivAssign for Duration { + fn div_assign(&mut self, rhs: u32) { + self.micros /= rhs as u64; + } +} + +impl ops::Shl for Duration { + type Output = Duration; + + fn shl(self, rhs: u32) -> Duration { + Duration::from_micros(self.micros << rhs) + } +} + +impl ops::ShlAssign for Duration { + fn shl_assign(&mut self, rhs: u32) { + self.micros <<= rhs; + } +} + +impl ops::Shr for Duration { + type Output = Duration; + + fn shr(self, rhs: u32) -> Duration { + Duration::from_micros(self.micros >> rhs) + } +} + +impl ops::ShrAssign for Duration { + fn shr_assign(&mut self, rhs: u32) { + self.micros >>= rhs; + } +} + +impl From<::core::time::Duration> for Duration { + fn from(other: ::core::time::Duration) -> Duration { + Duration::from_micros(other.as_secs() * 1000000 + other.subsec_micros() as u64) + } +} + +impl From for ::core::time::Duration { + fn from(val: Duration) -> Self { + ::core::time::Duration::from_micros(val.total_micros()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_instant_ops() { + // std::ops::Add + assert_eq!( + Instant::from_millis(4) + Duration::from_millis(6), + Instant::from_millis(10) + ); + // std::ops::Sub + assert_eq!( + Instant::from_millis(7) - Duration::from_millis(5), + Instant::from_millis(2) + ); + } + + #[test] + fn test_instant_getters() { + let instant = Instant::from_millis(5674); + assert_eq!(instant.secs(), 5); + assert_eq!(instant.millis(), 674); + assert_eq!(instant.total_millis(), 5674); + } + + #[test] + fn test_instant_display() { + assert_eq!(format!("{}", Instant::from_millis(74)), "0.074s"); + assert_eq!(format!("{}", Instant::from_millis(5674)), "5.674s"); + assert_eq!(format!("{}", Instant::from_millis(5000)), "5.000s"); + } + + #[test] + #[cfg(feature = "std")] + fn test_instant_conversions() { + let mut epoc: ::std::time::SystemTime = Instant::from_millis(0).into(); + assert_eq!( + Instant::from(::std::time::UNIX_EPOCH), + Instant::from_millis(0) + ); + assert_eq!(epoc, ::std::time::UNIX_EPOCH); + epoc = Instant::from_millis(2085955200i64 * 1000).into(); + assert_eq!( + epoc, + ::std::time::UNIX_EPOCH + ::std::time::Duration::from_secs(2085955200) + ); + } + + #[test] + fn test_duration_ops() { + // std::ops::Add + assert_eq!( + Duration::from_millis(40) + Duration::from_millis(2), + Duration::from_millis(42) + ); + // std::ops::Sub + assert_eq!( + Duration::from_millis(555) - Duration::from_millis(42), + Duration::from_millis(513) + ); + // std::ops::Mul + assert_eq!(Duration::from_millis(13) * 22, Duration::from_millis(286)); + // std::ops::Div + assert_eq!(Duration::from_millis(53) / 4, Duration::from_micros(13250)); + } + + #[test] + fn test_duration_assign_ops() { + let mut duration = Duration::from_millis(4735); + duration += Duration::from_millis(1733); + assert_eq!(duration, Duration::from_millis(6468)); + duration -= Duration::from_millis(1234); + assert_eq!(duration, Duration::from_millis(5234)); + duration *= 4; + assert_eq!(duration, Duration::from_millis(20936)); + duration /= 5; + assert_eq!(duration, Duration::from_micros(4187200)); + } + + #[test] + #[should_panic(expected = "overflow when subtracting durations")] + fn test_sub_from_zero_overflow() { + let _ = Duration::from_millis(0) - Duration::from_millis(1); + } + + #[test] + #[should_panic(expected = "attempt to divide by zero")] + fn test_div_by_zero() { + let _ = Duration::from_millis(4) / 0; + } + + #[test] + fn test_duration_getters() { + let instant = Duration::from_millis(4934); + assert_eq!(instant.secs(), 4); + assert_eq!(instant.millis(), 934); + assert_eq!(instant.total_millis(), 4934); + } + + #[test] + fn test_duration_conversions() { + let mut std_duration = ::core::time::Duration::from_millis(4934); + let duration: Duration = std_duration.into(); + assert_eq!(duration, Duration::from_millis(4934)); + assert_eq!(Duration::from(std_duration), Duration::from_millis(4934)); + + std_duration = duration.into(); + assert_eq!(std_duration, ::core::time::Duration::from_millis(4934)); + } +} diff --git a/vendor/smoltcp/src/wire/arp.rs b/vendor/smoltcp/src/wire/arp.rs new file mode 100644 index 00000000..1ca8011b --- /dev/null +++ b/vendor/smoltcp/src/wire/arp.rs @@ -0,0 +1,463 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use super::{EthernetAddress, Ipv4Address}; + +pub use super::EthernetProtocol as Protocol; + +enum_with_unknown! { + /// ARP hardware type. + pub enum Hardware(u16) { + Ethernet = 1 + } +} + +enum_with_unknown! { + /// ARP operation type. + pub enum Operation(u16) { + Request = 1, + Reply = 2 + } +} + +/// A read/write wrapper around an Address Resolution Protocol packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + pub const HTYPE: Field = 0..2; + pub const PTYPE: Field = 2..4; + pub const HLEN: usize = 4; + pub const PLEN: usize = 5; + pub const OPER: Field = 6..8; + + #[inline] + pub const fn SHA(hardware_len: u8, _protocol_len: u8) -> Field { + let start = OPER.end; + start..(start + hardware_len as usize) + } + + #[inline] + pub const fn SPA(hardware_len: u8, protocol_len: u8) -> Field { + let start = SHA(hardware_len, protocol_len).end; + start..(start + protocol_len as usize) + } + + #[inline] + pub const fn THA(hardware_len: u8, protocol_len: u8) -> Field { + let start = SPA(hardware_len, protocol_len).end; + start..(start + hardware_len as usize) + } + + #[inline] + pub const fn TPA(hardware_len: u8, protocol_len: u8) -> Field { + let start = THA(hardware_len, protocol_len).end; + start..(start + protocol_len as usize) + } +} + +impl> Packet { + /// Imbue a raw octet buffer with ARP packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_hardware_len] or + /// [set_protocol_len]. + /// + /// [set_hardware_len]: #method.set_hardware_len + /// [set_protocol_len]: #method.set_protocol_len + #[allow(clippy::if_same_then_else)] + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::OPER.end { + Err(Error) + } else if len < field::TPA(self.hardware_len(), self.protocol_len()).end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the hardware type field. + #[inline] + pub fn hardware_type(&self) -> Hardware { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::HTYPE]); + Hardware::from(raw) + } + + /// Return the protocol type field. + #[inline] + pub fn protocol_type(&self) -> Protocol { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::PTYPE]); + Protocol::from(raw) + } + + /// Return the hardware length field. + #[inline] + pub fn hardware_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::HLEN] + } + + /// Return the protocol length field. + #[inline] + pub fn protocol_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::PLEN] + } + + /// Return the operation field. + #[inline] + pub fn operation(&self) -> Operation { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::OPER]); + Operation::from(raw) + } + + /// Return the source hardware address field. + pub fn source_hardware_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::SHA(self.hardware_len(), self.protocol_len())] + } + + /// Return the source protocol address field. + pub fn source_protocol_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::SPA(self.hardware_len(), self.protocol_len())] + } + + /// Return the target hardware address field. + pub fn target_hardware_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::THA(self.hardware_len(), self.protocol_len())] + } + + /// Return the target protocol address field. + pub fn target_protocol_addr(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::TPA(self.hardware_len(), self.protocol_len())] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the hardware type field. + #[inline] + pub fn set_hardware_type(&mut self, value: Hardware) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::HTYPE], value.into()) + } + + /// Set the protocol type field. + #[inline] + pub fn set_protocol_type(&mut self, value: Protocol) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::PTYPE], value.into()) + } + + /// Set the hardware length field. + #[inline] + pub fn set_hardware_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::HLEN] = value + } + + /// Set the protocol length field. + #[inline] + pub fn set_protocol_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::PLEN] = value + } + + /// Set the operation field. + #[inline] + pub fn set_operation(&mut self, value: Operation) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::OPER], value.into()) + } + + /// Set the source hardware address field. + /// + /// # Panics + /// The function panics if `value` is not `self.hardware_len()` long. + pub fn set_source_hardware_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::SHA(hardware_len, protocol_len)].copy_from_slice(value) + } + + /// Set the source protocol address field. + /// + /// # Panics + /// The function panics if `value` is not `self.protocol_len()` long. + pub fn set_source_protocol_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::SPA(hardware_len, protocol_len)].copy_from_slice(value) + } + + /// Set the target hardware address field. + /// + /// # Panics + /// The function panics if `value` is not `self.hardware_len()` long. + pub fn set_target_hardware_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::THA(hardware_len, protocol_len)].copy_from_slice(value) + } + + /// Set the target protocol address field. + /// + /// # Panics + /// The function panics if `value` is not `self.protocol_len()` long. + pub fn set_target_protocol_addr(&mut self, value: &[u8]) { + let (hardware_len, protocol_len) = (self.hardware_len(), self.protocol_len()); + let data = self.buffer.as_mut(); + data[field::TPA(hardware_len, protocol_len)].copy_from_slice(value) + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an Address Resolution Protocol packet. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Repr { + /// An Ethernet and IPv4 Address Resolution Protocol packet. + EthernetIpv4 { + operation: Operation, + source_hardware_addr: EthernetAddress, + source_protocol_addr: Ipv4Address, + target_hardware_addr: EthernetAddress, + target_protocol_addr: Ipv4Address, + }, +} + +impl Repr { + /// Parse an Address Resolution Protocol packet and return a high-level representation, + /// or return `Err(Error)` if the packet is not recognized. + pub fn parse>(packet: &Packet) -> Result { + packet.check_len()?; + + match ( + packet.hardware_type(), + packet.protocol_type(), + packet.hardware_len(), + packet.protocol_len(), + ) { + (Hardware::Ethernet, Protocol::Ipv4, 6, 4) => Ok(Repr::EthernetIpv4 { + operation: packet.operation(), + source_hardware_addr: EthernetAddress::from_bytes(packet.source_hardware_addr()), + source_protocol_addr: crate::wire::ipv4_from_octets( + packet.source_protocol_addr().try_into().unwrap(), + ), + target_hardware_addr: EthernetAddress::from_bytes(packet.target_hardware_addr()), + target_protocol_addr: crate::wire::ipv4_from_octets( + packet.target_protocol_addr().try_into().unwrap(), + ), + }), + _ => Err(Error), + } + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + match *self { + Repr::EthernetIpv4 { .. } => field::TPA(6, 4).end, + } + } + + /// Emit a high-level representation into an Address Resolution Protocol packet. + pub fn emit + AsMut<[u8]>>(&self, packet: &mut Packet) { + match *self { + Repr::EthernetIpv4 { + operation, + source_hardware_addr, + source_protocol_addr, + target_hardware_addr, + target_protocol_addr, + } => { + packet.set_hardware_type(Hardware::Ethernet); + packet.set_protocol_type(Protocol::Ipv4); + packet.set_hardware_len(6); + packet.set_protocol_len(4); + packet.set_operation(operation); + packet.set_source_hardware_addr(source_hardware_addr.as_bytes()); + packet.set_source_protocol_addr(&source_protocol_addr.octets()); + packet.set_target_hardware_addr(target_hardware_addr.as_bytes()); + packet.set_target_protocol_addr(&target_protocol_addr.octets()); + } + } + } +} + +impl> fmt::Display for Packet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + _ => { + write!(f, "ARP (unrecognized)")?; + write!( + f, + " htype={:?} ptype={:?} hlen={:?} plen={:?} op={:?}", + self.hardware_type(), + self.protocol_type(), + self.hardware_len(), + self.protocol_len(), + self.operation() + )?; + write!( + f, + " sha={:?} spa={:?} tha={:?} tpa={:?}", + self.source_hardware_addr(), + self.source_protocol_addr(), + self.target_hardware_addr(), + self.target_protocol_addr() + )?; + Ok(()) + } + } + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::EthernetIpv4 { + operation, + source_hardware_addr, + source_protocol_addr, + target_hardware_addr, + target_protocol_addr, + } => { + write!( + f, + "ARP type=Ethernet+IPv4 src={source_hardware_addr}/{source_protocol_addr} tgt={target_hardware_addr}/{target_protocol_addr} op={operation:?}" + ) + } + } + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + match Packet::new_checked(buffer) { + Err(err) => write!(f, "{indent}({err})"), + Ok(packet) => write!(f, "{indent}{packet}"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + static PACKET_BYTES: [u8; 28] = [ + 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x21, + 0x22, 0x23, 0x24, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x41, 0x42, 0x43, 0x44, + ]; + + #[test] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(packet.hardware_type(), Hardware::Ethernet); + assert_eq!(packet.protocol_type(), Protocol::Ipv4); + assert_eq!(packet.hardware_len(), 6); + assert_eq!(packet.protocol_len(), 4); + assert_eq!(packet.operation(), Operation::Request); + assert_eq!( + packet.source_hardware_addr(), + &[0x11, 0x12, 0x13, 0x14, 0x15, 0x16] + ); + assert_eq!(packet.source_protocol_addr(), &[0x21, 0x22, 0x23, 0x24]); + assert_eq!( + packet.target_hardware_addr(), + &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36] + ); + assert_eq!(packet.target_protocol_addr(), &[0x41, 0x42, 0x43, 0x44]); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 28]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_hardware_type(Hardware::Ethernet); + packet.set_protocol_type(Protocol::Ipv4); + packet.set_hardware_len(6); + packet.set_protocol_len(4); + packet.set_operation(Operation::Request); + packet.set_source_hardware_addr(&[0x11, 0x12, 0x13, 0x14, 0x15, 0x16]); + packet.set_source_protocol_addr(&[0x21, 0x22, 0x23, 0x24]); + packet.set_target_hardware_addr(&[0x31, 0x32, 0x33, 0x34, 0x35, 0x36]); + packet.set_target_protocol_addr(&[0x41, 0x42, 0x43, 0x44]); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } + + fn packet_repr() -> Repr { + Repr::EthernetIpv4 { + operation: Operation::Request, + source_hardware_addr: EthernetAddress::from_bytes(&[ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + ]), + source_protocol_addr: crate::wire::ipv4_from_octets([0x21, 0x22, 0x23, 0x24]), + target_hardware_addr: EthernetAddress::from_bytes(&[ + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + ]), + target_protocol_addr: crate::wire::ipv4_from_octets([0x41, 0x42, 0x43, 0x44]), + } + } + + #[test] + fn test_parse() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&packet).unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + fn test_emit() { + let mut bytes = vec![0xa5; 28]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet_repr().emit(&mut packet); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } +} diff --git a/vendor/smoltcp/src/wire/dhcpv4.rs b/vendor/smoltcp/src/wire/dhcpv4.rs new file mode 100644 index 00000000..fce6d053 --- /dev/null +++ b/vendor/smoltcp/src/wire/dhcpv4.rs @@ -0,0 +1,1318 @@ +// See https://tools.ietf.org/html/rfc2131 for the DHCP specification. + +use bitflags::bitflags; +use byteorder::{ByteOrder, NetworkEndian}; +use core::iter; +use heapless::Vec; + +use super::{Error, Result}; +use crate::wire::arp::Hardware; +use crate::wire::{EthernetAddress, Ipv4Address}; + +pub const SERVER_PORT: u16 = 67; +pub const CLIENT_PORT: u16 = 68; +pub const MAX_DNS_SERVER_COUNT: usize = 3; + +const DHCP_MAGIC_NUMBER: u32 = 0x63825363; + +enum_with_unknown! { + /// The possible opcodes of a DHCP packet. + pub enum OpCode(u8) { + Request = 1, + Reply = 2, + } +} + +enum_with_unknown! { + /// The possible message types of a DHCP packet. + pub enum MessageType(u8) { + Discover = 1, + Offer = 2, + Request = 3, + Decline = 4, + Ack = 5, + Nak = 6, + Release = 7, + Inform = 8, + } +} + +bitflags! { + pub struct Flags: u16 { + const BROADCAST = 0b1000_0000_0000_0000; + } +} + +impl MessageType { + const fn opcode(&self) -> OpCode { + match *self { + MessageType::Discover + | MessageType::Inform + | MessageType::Request + | MessageType::Decline + | MessageType::Release => OpCode::Request, + MessageType::Offer | MessageType::Ack | MessageType::Nak => OpCode::Reply, + MessageType::Unknown(_) => OpCode::Unknown(0), + } + } +} + +/// A buffer for DHCP options. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DhcpOptionWriter<'a> { + /// The underlying buffer, directly from the DHCP packet representation. + buffer: &'a mut [u8], +} + +impl<'a> DhcpOptionWriter<'a> { + pub fn new(buffer: &'a mut [u8]) -> Self { + Self { buffer } + } + + /// Emit a [`DhcpOption`] into a [`DhcpOptionWriter`]. + pub fn emit(&mut self, option: DhcpOption<'_>) -> Result<()> { + if option.data.len() > u8::MAX as _ { + return Err(Error); + } + + let total_len = 2 + option.data.len(); + if self.buffer.len() < total_len { + return Err(Error); + } + + let (buf, rest) = core::mem::take(&mut self.buffer).split_at_mut(total_len); + self.buffer = rest; + + buf[0] = option.kind; + buf[1] = option.data.len() as _; + buf[2..].copy_from_slice(option.data); + + Ok(()) + } + + pub fn end(&mut self) -> Result<()> { + if self.buffer.is_empty() { + return Err(Error); + } + + self.buffer[0] = field::OPT_END; + self.buffer = &mut []; + Ok(()) + } +} + +/// A representation of a single DHCP option. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DhcpOption<'a> { + pub kind: u8, + pub data: &'a [u8], +} + +/// A read/write wrapper around a Dynamic Host Configuration Protocol packet buffer. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +pub(crate) mod field { + #![allow(non_snake_case)] + #![allow(unused)] + + use crate::wire::field::*; + + pub const OP: usize = 0; + pub const HTYPE: usize = 1; + pub const HLEN: usize = 2; + pub const HOPS: usize = 3; + pub const XID: Field = 4..8; + pub const SECS: Field = 8..10; + pub const FLAGS: Field = 10..12; + pub const CIADDR: Field = 12..16; + pub const YIADDR: Field = 16..20; + pub const SIADDR: Field = 20..24; + pub const GIADDR: Field = 24..28; + pub const CHADDR: Field = 28..34; + pub const SNAME: Field = 34..108; + pub const FILE: Field = 108..236; + pub const MAGIC_NUMBER: Field = 236..240; + pub const OPTIONS: Rest = 240..; + + // Vendor Extensions + pub const OPT_END: u8 = 255; + pub const OPT_PAD: u8 = 0; + pub const OPT_SUBNET_MASK: u8 = 1; + pub const OPT_TIME_OFFSET: u8 = 2; + pub const OPT_ROUTER: u8 = 3; + pub const OPT_TIME_SERVER: u8 = 4; + pub const OPT_NAME_SERVER: u8 = 5; + pub const OPT_DOMAIN_NAME_SERVER: u8 = 6; + pub const OPT_LOG_SERVER: u8 = 7; + pub const OPT_COOKIE_SERVER: u8 = 8; + pub const OPT_LPR_SERVER: u8 = 9; + pub const OPT_IMPRESS_SERVER: u8 = 10; + pub const OPT_RESOURCE_LOCATION_SERVER: u8 = 11; + pub const OPT_HOST_NAME: u8 = 12; + pub const OPT_BOOT_FILE_SIZE: u8 = 13; + pub const OPT_MERIT_DUMP: u8 = 14; + pub const OPT_DOMAIN_NAME: u8 = 15; + pub const OPT_SWAP_SERVER: u8 = 16; + pub const OPT_ROOT_PATH: u8 = 17; + pub const OPT_EXTENSIONS_PATH: u8 = 18; + + // IP Layer Parameters per Host + pub const OPT_IP_FORWARDING: u8 = 19; + pub const OPT_NON_LOCAL_SOURCE_ROUTING: u8 = 20; + pub const OPT_POLICY_FILTER: u8 = 21; + pub const OPT_MAX_DATAGRAM_REASSEMBLY_SIZE: u8 = 22; + pub const OPT_DEFAULT_TTL: u8 = 23; + pub const OPT_PATH_MTU_AGING_TIMEOUT: u8 = 24; + pub const OPT_PATH_MTU_PLATEAU_TABLE: u8 = 25; + + // IP Layer Parameters per Interface + pub const OPT_INTERFACE_MTU: u8 = 26; + pub const OPT_ALL_SUBNETS_ARE_LOCAL: u8 = 27; + pub const OPT_BROADCAST_ADDRESS: u8 = 28; + pub const OPT_PERFORM_MASK_DISCOVERY: u8 = 29; + pub const OPT_MASK_SUPPLIER: u8 = 30; + pub const OPT_PERFORM_ROUTER_DISCOVERY: u8 = 31; + pub const OPT_ROUTER_SOLICITATION_ADDRESS: u8 = 32; + pub const OPT_STATIC_ROUTE: u8 = 33; + + // Link Layer Parameters per Interface + pub const OPT_TRAILER_ENCAPSULATION: u8 = 34; + pub const OPT_ARP_CACHE_TIMEOUT: u8 = 35; + pub const OPT_ETHERNET_ENCAPSULATION: u8 = 36; + + // TCP Parameters + pub const OPT_TCP_DEFAULT_TTL: u8 = 37; + pub const OPT_TCP_KEEPALIVE_INTERVAL: u8 = 38; + pub const OPT_TCP_KEEPALIVE_GARBAGE: u8 = 39; + + // Application and Service Parameters + pub const OPT_NIS_DOMAIN: u8 = 40; + pub const OPT_NIS_SERVERS: u8 = 41; + pub const OPT_NTP_SERVERS: u8 = 42; + pub const OPT_VENDOR_SPECIFIC_INFO: u8 = 43; + pub const OPT_NETBIOS_NAME_SERVER: u8 = 44; + pub const OPT_NETBIOS_DISTRIBUTION_SERVER: u8 = 45; + pub const OPT_NETBIOS_NODE_TYPE: u8 = 46; + pub const OPT_NETBIOS_SCOPE: u8 = 47; + pub const OPT_X_WINDOW_FONT_SERVER: u8 = 48; + pub const OPT_X_WINDOW_DISPLAY_MANAGER: u8 = 49; + pub const OPT_NIS_PLUS_DOMAIN: u8 = 64; + pub const OPT_NIS_PLUS_SERVERS: u8 = 65; + pub const OPT_MOBILE_IP_HOME_AGENT: u8 = 68; + pub const OPT_SMTP_SERVER: u8 = 69; + pub const OPT_POP3_SERVER: u8 = 70; + pub const OPT_NNTP_SERVER: u8 = 71; + pub const OPT_WWW_SERVER: u8 = 72; + pub const OPT_FINGER_SERVER: u8 = 73; + pub const OPT_IRC_SERVER: u8 = 74; + pub const OPT_STREETTALK_SERVER: u8 = 75; + pub const OPT_STDA_SERVER: u8 = 76; + + // DHCP Extensions + pub const OPT_REQUESTED_IP: u8 = 50; + pub const OPT_IP_LEASE_TIME: u8 = 51; + pub const OPT_OPTION_OVERLOAD: u8 = 52; + pub const OPT_TFTP_SERVER_NAME: u8 = 66; + pub const OPT_BOOTFILE_NAME: u8 = 67; + pub const OPT_DHCP_MESSAGE_TYPE: u8 = 53; + pub const OPT_SERVER_IDENTIFIER: u8 = 54; + pub const OPT_PARAMETER_REQUEST_LIST: u8 = 55; + pub const OPT_MESSAGE: u8 = 56; + pub const OPT_MAX_DHCP_MESSAGE_SIZE: u8 = 57; + pub const OPT_RENEWAL_TIME_VALUE: u8 = 58; + pub const OPT_REBINDING_TIME_VALUE: u8 = 59; + pub const OPT_VENDOR_CLASS_ID: u8 = 60; + pub const OPT_CLIENT_ID: u8 = 61; +} + +impl> Packet { + /// Imbue a raw octet buffer with DHCP packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::MAGIC_NUMBER.end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Returns the operation code of this packet. + pub fn opcode(&self) -> OpCode { + let data = self.buffer.as_ref(); + OpCode::from(data[field::OP]) + } + + /// Returns the hardware protocol type (e.g. ethernet). + pub fn hardware_type(&self) -> Hardware { + let data = self.buffer.as_ref(); + Hardware::from(u16::from(data[field::HTYPE])) + } + + /// Returns the length of a hardware address in bytes (e.g. 6 for ethernet). + pub fn hardware_len(&self) -> u8 { + self.buffer.as_ref()[field::HLEN] + } + + /// Returns the transaction ID. + /// + /// The transaction ID (called `xid` in the specification) is a random number used to + /// associate messages and responses between client and server. The number is chosen by + /// the client. + pub fn transaction_id(&self) -> u32 { + let field = &self.buffer.as_ref()[field::XID]; + NetworkEndian::read_u32(field) + } + + /// Returns the hardware address of the client (called `chaddr` in the specification). + /// + /// Only ethernet is supported by `smoltcp`, so this functions returns + /// an `EthernetAddress`. + pub fn client_hardware_address(&self) -> EthernetAddress { + let field = &self.buffer.as_ref()[field::CHADDR]; + EthernetAddress::from_bytes(field) + } + + /// Returns the value of the `hops` field. + /// + /// The `hops` field is set to zero by clients and optionally used by relay agents. + pub fn hops(&self) -> u8 { + self.buffer.as_ref()[field::HOPS] + } + + /// Returns the value of the `secs` field. + /// + /// The secs field is filled by clients and describes the number of seconds elapsed + /// since client began process. + pub fn secs(&self) -> u16 { + let field = &self.buffer.as_ref()[field::SECS]; + NetworkEndian::read_u16(field) + } + + /// Returns the value of the `magic cookie` field in the DHCP options. + /// + /// This field should be always be `0x63825363`. + pub fn magic_number(&self) -> u32 { + let field = &self.buffer.as_ref()[field::MAGIC_NUMBER]; + NetworkEndian::read_u32(field) + } + + /// Returns the Ipv4 address of the client, zero if not set. + /// + /// This corresponds to the `ciaddr` field in the DHCP specification. According to it, + /// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state + /// and can respond to ARP requests”. + pub fn client_ip(&self) -> Ipv4Address { + let field = &self.buffer.as_ref()[field::CIADDR]; + crate::wire::ipv4_from_octets(field.try_into().unwrap()) + } + + /// Returns the value of the `yiaddr` field, zero if not set. + pub fn your_ip(&self) -> Ipv4Address { + let field = &self.buffer.as_ref()[field::YIADDR]; + crate::wire::ipv4_from_octets(field.try_into().unwrap()) + } + + /// Returns the value of the `siaddr` field, zero if not set. + pub fn server_ip(&self) -> Ipv4Address { + let field = &self.buffer.as_ref()[field::SIADDR]; + crate::wire::ipv4_from_octets(field.try_into().unwrap()) + } + + /// Returns the value of the `giaddr` field, zero if not set. + pub fn relay_agent_ip(&self) -> Ipv4Address { + let field = &self.buffer.as_ref()[field::GIADDR]; + crate::wire::ipv4_from_octets(field.try_into().unwrap()) + } + + pub fn flags(&self) -> Flags { + let field = &self.buffer.as_ref()[field::FLAGS]; + Flags::from_bits_truncate(NetworkEndian::read_u16(field)) + } + + /// Return an iterator over the options. + #[inline] + pub fn options(&self) -> impl Iterator> + '_ { + let mut buf = &self.buffer.as_ref()[field::OPTIONS]; + iter::from_fn(move || { + loop { + match buf.first().copied() { + // No more options, return. + None => return None, + Some(field::OPT_END) => return None, + + // Skip padding. + Some(field::OPT_PAD) => buf = &buf[1..], + Some(kind) => { + if buf.len() < 2 { + return None; + } + + let len = buf[1] as usize; + + if buf.len() < 2 + len { + return None; + } + + let opt = DhcpOption { + kind, + data: &buf[2..2 + len], + }; + + buf = &buf[2 + len..]; + return Some(opt); + } + } + } + }) + } + + pub fn get_sname(&self) -> Result<&str> { + let data = &self.buffer.as_ref()[field::SNAME]; + let len = data.iter().position(|&x| x == 0).ok_or(Error)?; + if len == 0 { + return Err(Error); + } + + let data = core::str::from_utf8(&data[..len]).map_err(|_| Error)?; + Ok(data) + } + + pub fn get_boot_file(&self) -> Result<&str> { + let data = &self.buffer.as_ref()[field::FILE]; + let len = data.iter().position(|&x| x == 0).ok_or(Error)?; + if len == 0 { + return Err(Error); + } + let data = core::str::from_utf8(&data[..len]).map_err(|_| Error)?; + Ok(data) + } +} + +impl + AsMut<[u8]>> Packet { + /// Sets the optional `sname` (“server name”) and `file` (“boot file name”) fields to zero. + /// + /// The fields are not commonly used, so we set their value always to zero. **This method + /// must be called when creating a packet, otherwise the emitted values for these fields + /// are undefined!** + pub fn set_sname_and_boot_file_to_zero(&mut self) { + let data = self.buffer.as_mut(); + for byte in &mut data[field::SNAME] { + *byte = 0; + } + for byte in &mut data[field::FILE] { + *byte = 0; + } + } + + /// Sets the `OpCode` for the packet. + pub fn set_opcode(&mut self, value: OpCode) { + let data = self.buffer.as_mut(); + data[field::OP] = value.into(); + } + + /// Sets the hardware address type (only ethernet is supported). + pub fn set_hardware_type(&mut self, value: Hardware) { + let data = self.buffer.as_mut(); + let number: u16 = value.into(); + assert!(number <= u16::from(u8::MAX)); // TODO: Replace with TryFrom when it's stable + data[field::HTYPE] = number as u8; + } + + /// Sets the hardware address length. + /// + /// Only ethernet is supported, so this field should be set to the value `6`. + pub fn set_hardware_len(&mut self, value: u8) { + self.buffer.as_mut()[field::HLEN] = value; + } + + /// Sets the transaction ID. + /// + /// The transaction ID (called `xid` in the specification) is a random number used to + /// associate messages and responses between client and server. The number is chosen by + /// the client. + pub fn set_transaction_id(&mut self, value: u32) { + let field = &mut self.buffer.as_mut()[field::XID]; + NetworkEndian::write_u32(field, value) + } + + /// Sets the ethernet address of the client. + /// + /// Sets the `chaddr` field. + pub fn set_client_hardware_address(&mut self, value: EthernetAddress) { + let field = &mut self.buffer.as_mut()[field::CHADDR]; + field.copy_from_slice(value.as_bytes()); + } + + /// Sets the hops field. + /// + /// The `hops` field is set to zero by clients and optionally used by relay agents. + pub fn set_hops(&mut self, value: u8) { + self.buffer.as_mut()[field::HOPS] = value; + } + + /// Sets the `secs` field. + /// + /// The secs field is filled by clients and describes the number of seconds elapsed + /// since client began process. + pub fn set_secs(&mut self, value: u16) { + let field = &mut self.buffer.as_mut()[field::SECS]; + NetworkEndian::write_u16(field, value); + } + + /// Sets the value of the `magic cookie` field in the DHCP options. + /// + /// This field should be always be `0x63825363`. + pub fn set_magic_number(&mut self, value: u32) { + let field = &mut self.buffer.as_mut()[field::MAGIC_NUMBER]; + NetworkEndian::write_u32(field, value); + } + + /// Sets the Ipv4 address of the client. + /// + /// This corresponds to the `ciaddr` field in the DHCP specification. According to it, + /// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state + /// and can respond to ARP requests”. + pub fn set_client_ip(&mut self, value: Ipv4Address) { + let field = &mut self.buffer.as_mut()[field::CIADDR]; + field.copy_from_slice(&value.octets()); + } + + /// Sets the value of the `yiaddr` field. + pub fn set_your_ip(&mut self, value: Ipv4Address) { + let field = &mut self.buffer.as_mut()[field::YIADDR]; + field.copy_from_slice(&value.octets()); + } + + /// Sets the value of the `siaddr` field. + pub fn set_server_ip(&mut self, value: Ipv4Address) { + let field = &mut self.buffer.as_mut()[field::SIADDR]; + field.copy_from_slice(&value.octets()); + } + + /// Sets the value of the `giaddr` field. + pub fn set_relay_agent_ip(&mut self, value: Ipv4Address) { + let field = &mut self.buffer.as_mut()[field::GIADDR]; + field.copy_from_slice(&value.octets()); + } + + /// Sets the flags to the specified value. + pub fn set_flags(&mut self, val: Flags) { + let field = &mut self.buffer.as_mut()[field::FLAGS]; + NetworkEndian::write_u16(field, val.bits()); + } +} + +impl + AsMut<[u8]> + ?Sized> Packet<&mut T> { + /// Return a pointer to the options. + #[inline] + pub fn options_mut(&mut self) -> DhcpOptionWriter<'_> { + DhcpOptionWriter::new(&mut self.buffer.as_mut()[field::OPTIONS]) + } +} + +/// A high-level representation of a Dynamic Host Configuration Protocol packet. +/// +/// DHCP messages have the following layout (see [RFC 2131](https://tools.ietf.org/html/rfc2131) +/// for details): +/// +/// ```no_rust +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | message_type | htype (N/A) | hlen (N/A) | hops | +/// +---------------+---------------+---------------+---------------+ +/// | transaction_id | +/// +-------------------------------+-------------------------------+ +/// | secs | flags | +/// +-------------------------------+-------------------------------+ +/// | client_ip | +/// +---------------------------------------------------------------+ +/// | your_ip | +/// +---------------------------------------------------------------+ +/// | server_ip | +/// +---------------------------------------------------------------+ +/// | relay_agent_ip | +/// +---------------------------------------------------------------+ +/// | | +/// | client_hardware_address | +/// | | +/// | | +/// +---------------------------------------------------------------+ +/// | | +/// | sname (N/A) | +/// +---------------------------------------------------------------+ +/// | | +/// | file (N/A) | +/// +---------------------------------------------------------------+ +/// | | +/// | options | +/// +---------------------------------------------------------------+ +/// ``` +/// +/// It is assumed that the access layer is Ethernet, so `htype` (the field representing the +/// hardware address type) is always set to `1`, and `hlen` (which represents the hardware address +/// length) is set to `6`. +/// +/// The `options` field has a variable length. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr<'a> { + /// This field is also known as `op` in the RFC. It indicates the type of DHCP message this + /// packet represents. + pub message_type: MessageType, + /// This field is also known as `xid` in the RFC. It is a random number chosen by the client, + /// used by the client and server to associate messages and responses between a client and a + /// server. + pub transaction_id: u32, + /// seconds elapsed since client began address acquisition or renewal + /// process the DHCPREQUEST message MUST use the same value in the DHCP + /// message header's 'secs' field and be sent to the same IP broadcast + /// address as the original DHCPDISCOVER message. + pub secs: u16, + /// This field is also known as `chaddr` in the RFC and for networks where the access layer is + /// ethernet, it is the client MAC address. + pub client_hardware_address: EthernetAddress, + /// This field is also known as `ciaddr` in the RFC. It is only filled in if client is in + /// BOUND, RENEW or REBINDING state and can respond to ARP requests. + pub client_ip: Ipv4Address, + /// This field is also known as `yiaddr` in the RFC. + pub your_ip: Ipv4Address, + /// This field is also known as `siaddr` in the RFC. It may be set by the server in DHCPOFFER + /// and DHCPACK messages, and represent the address of the next server to use in bootstrap. + pub server_ip: Ipv4Address, + /// Default gateway + pub router: Option, + /// This field comes from a corresponding DhcpOption. + pub subnet_mask: Option, + /// This field is also known as `giaddr` in the RFC. In order to allow DHCP clients on subnets + /// not directly served by DHCP servers to communicate with DHCP servers, DHCP relay agents can + /// be installed on these subnets. The DHCP client broadcasts on the local link; the relay + /// agent receives the broadcast and transmits it to one or more DHCP servers using unicast. + /// The relay agent stores its own IP address in the `relay_agent_ip` field of the DHCP packet. + /// The DHCP server uses the `relay_agent_ip` to determine the subnet on which the relay agent + /// received the broadcast, and allocates an IP address on that subnet. When the DHCP server + /// replies to the client, it sends the reply to the `relay_agent_ip` address, again using + /// unicast. The relay agent then retransmits the response on the local network + pub relay_agent_ip: Ipv4Address, + /// Broadcast flags. It can be set in DHCPDISCOVER, DHCPINFORM and DHCPREQUEST message if the + /// client requires the response to be broadcasted. + pub broadcast: bool, + /// The "requested IP address" option. It can be used by clients in DHCPREQUEST or DHCPDISCOVER + /// messages, or by servers in DHCPDECLINE messages. + pub requested_ip: Option, + /// The "client identifier" option. + /// + /// The 'client identifier' is an opaque key, not to be interpreted by the server; for example, + /// the 'client identifier' may contain a hardware address, identical to the contents of the + /// 'chaddr' field, or it may contain another type of identifier, such as a DNS name. The + /// 'client identifier' chosen by a DHCP client MUST be unique to that client within the subnet + /// to which the client is attached. If the client uses a 'client identifier' in one message, + /// it MUST use that same identifier in all subsequent messages, to ensure that all servers + /// correctly identify the client. + pub client_identifier: Option, + /// The "server identifier" option. It is used both to identify a DHCP server + /// in a DHCP message and as a destination address from clients to servers. + pub server_identifier: Option, + /// The parameter request list informs the server about which configuration parameters + /// the client is interested in. + pub parameter_request_list: Option<&'a [u8]>, + /// DNS servers + pub dns_servers: Option>, + /// The maximum size dhcp packet the interface can receive + pub max_size: Option, + /// The DHCP IP lease duration, specified in seconds. + pub lease_duration: Option, + /// The DHCP IP renew duration (T1 interval), in seconds, if specified in the packet. + pub renew_duration: Option, + /// The DHCP IP rebind duration (T2 interval), in seconds, if specified in the packet. + pub rebind_duration: Option, + /// When returned from [`Repr::parse`], this field will be `None`. + /// However, when calling [`Repr::emit`], this field should contain only + /// additional DHCP options not known to smoltcp. + pub additional_options: &'a [DhcpOption<'a>], +} + +impl<'a> Repr<'a> { + /// Return the length of a packet that will be emitted from this high-level representation. + pub fn buffer_len(&self) -> usize { + let mut len = field::OPTIONS.start; + // message type and end-of-options options + len += 3 + 1; + if self.requested_ip.is_some() { + len += 6; + } + if self.client_identifier.is_some() { + len += 9; + } + if self.server_identifier.is_some() { + len += 6; + } + if self.max_size.is_some() { + len += 4; + } + if self.router.is_some() { + len += 6; + } + if self.subnet_mask.is_some() { + len += 6; + } + if self.lease_duration.is_some() { + len += 6; + } + if let Some(dns_servers) = &self.dns_servers { + len += 2; + len += dns_servers.iter().count() * core::mem::size_of::(); + } + if let Some(list) = self.parameter_request_list { + len += list.len() + 2; + } + for opt in self.additional_options { + len += 2 + opt.data.len() + } + + len + } + + /// Parse a DHCP packet and return a high-level representation. + pub fn parse(packet: &'a Packet<&'a T>) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + let transaction_id = packet.transaction_id(); + let client_hardware_address = packet.client_hardware_address(); + let client_ip = packet.client_ip(); + let your_ip = packet.your_ip(); + let server_ip = packet.server_ip(); + let relay_agent_ip = packet.relay_agent_ip(); + let secs = packet.secs(); + + // only ethernet is supported right now + match packet.hardware_type() { + Hardware::Ethernet => { + if packet.hardware_len() != 6 { + return Err(Error); + } + } + Hardware::Unknown(_) => return Err(Error), // unimplemented + } + + if packet.magic_number() != DHCP_MAGIC_NUMBER { + return Err(Error); + } + + let mut message_type = Err(Error); + let mut requested_ip = None; + let mut client_identifier = None; + let mut server_identifier = None; + let mut router = None; + let mut subnet_mask = None; + let mut parameter_request_list = None; + let mut dns_servers = None; + let mut max_size = None; + let mut lease_duration = None; + let mut renew_duration = None; + let mut rebind_duration = None; + + for option in packet.options() { + let data = option.data; + match (option.kind, data.len()) { + (field::OPT_DHCP_MESSAGE_TYPE, 1) => { + let value = MessageType::from(data[0]); + if value.opcode() == packet.opcode() { + message_type = Ok(value); + } + } + (field::OPT_REQUESTED_IP, 4) => { + requested_ip = Some(crate::wire::ipv4_from_octets(data.try_into().unwrap())); + } + (field::OPT_CLIENT_ID, 7) => { + let hardware_type = Hardware::from(u16::from(data[0])); + if hardware_type != Hardware::Ethernet { + return Err(Error); + } + client_identifier = Some(EthernetAddress::from_bytes(&data[1..])); + } + (field::OPT_SERVER_IDENTIFIER, 4) => { + server_identifier = Some(crate::wire::ipv4_from_octets(data.try_into().unwrap())); + } + (field::OPT_ROUTER, 4) => { + router = Some(crate::wire::ipv4_from_octets(data.try_into().unwrap())); + } + (field::OPT_SUBNET_MASK, 4) => { + subnet_mask = Some(crate::wire::ipv4_from_octets(data.try_into().unwrap())); + } + (field::OPT_MAX_DHCP_MESSAGE_SIZE, 2) => { + max_size = Some(u16::from_be_bytes([data[0], data[1]])); + } + (field::OPT_RENEWAL_TIME_VALUE, 4) => { + renew_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]])) + } + (field::OPT_REBINDING_TIME_VALUE, 4) => { + rebind_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]])) + } + (field::OPT_IP_LEASE_TIME, 4) => { + lease_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]])) + } + (field::OPT_PARAMETER_REQUEST_LIST, _) => { + parameter_request_list = Some(data); + } + (field::OPT_DOMAIN_NAME_SERVER, _) => { + let mut servers = Vec::new(); + const IP_ADDR_BYTE_LEN: usize = 4; + let mut addrs = data.chunks_exact(IP_ADDR_BYTE_LEN); + for chunk in &mut addrs { + // We ignore push failures because that will only happen + // if we attempt to push more than 4 addresses, and the only + // solution to that is to support more addresses. + servers + .push(crate::wire::ipv4_from_octets(chunk.try_into().unwrap())) + .ok(); + } + dns_servers = Some(servers); + + if !addrs.remainder().is_empty() { + net_trace!("DHCP domain name servers contained invalid address"); + } + } + _ => {} + } + } + + let broadcast = packet.flags().contains(Flags::BROADCAST); + + Ok(Repr { + secs, + transaction_id, + client_hardware_address, + client_ip, + your_ip, + server_ip, + relay_agent_ip, + broadcast, + requested_ip, + server_identifier, + router, + subnet_mask, + client_identifier, + parameter_request_list, + dns_servers, + max_size, + lease_duration, + renew_duration, + rebind_duration, + message_type: message_type?, + additional_options: &[], + }) + } + + /// Emit a high-level representation into a Dynamic Host + /// Configuration Protocol packet. + pub fn emit(&self, packet: &mut Packet<&mut T>) -> Result<()> + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + packet.set_sname_and_boot_file_to_zero(); + packet.set_opcode(self.message_type.opcode()); + packet.set_hardware_type(Hardware::Ethernet); + packet.set_hardware_len(6); + packet.set_transaction_id(self.transaction_id); + packet.set_client_hardware_address(self.client_hardware_address); + packet.set_hops(0); + packet.set_secs(self.secs); + packet.set_magic_number(0x63825363); + packet.set_client_ip(self.client_ip); + packet.set_your_ip(self.your_ip); + packet.set_server_ip(self.server_ip); + packet.set_relay_agent_ip(self.relay_agent_ip); + + let mut flags = Flags::empty(); + if self.broadcast { + flags |= Flags::BROADCAST; + } + packet.set_flags(flags); + + { + let mut options = packet.options_mut(); + + options.emit(DhcpOption { + kind: field::OPT_DHCP_MESSAGE_TYPE, + data: &[self.message_type.into()], + })?; + + if let Some(val) = &self.client_identifier { + let mut data = [0; 7]; + data[0] = u16::from(Hardware::Ethernet) as u8; + data[1..].copy_from_slice(val.as_bytes()); + + options.emit(DhcpOption { + kind: field::OPT_CLIENT_ID, + data: &data, + })?; + } + + if let Some(val) = &self.server_identifier { + options.emit(DhcpOption { + kind: field::OPT_SERVER_IDENTIFIER, + data: &val.octets(), + })?; + } + + if let Some(val) = &self.router { + options.emit(DhcpOption { + kind: field::OPT_ROUTER, + data: &val.octets(), + })?; + } + if let Some(val) = &self.subnet_mask { + options.emit(DhcpOption { + kind: field::OPT_SUBNET_MASK, + data: &val.octets(), + })?; + } + if let Some(val) = &self.requested_ip { + options.emit(DhcpOption { + kind: field::OPT_REQUESTED_IP, + data: &val.octets(), + })?; + } + if let Some(val) = &self.max_size { + options.emit(DhcpOption { + kind: field::OPT_MAX_DHCP_MESSAGE_SIZE, + data: &val.to_be_bytes(), + })?; + } + if let Some(val) = &self.lease_duration { + options.emit(DhcpOption { + kind: field::OPT_IP_LEASE_TIME, + data: &val.to_be_bytes(), + })?; + } + if let Some(val) = &self.parameter_request_list { + options.emit(DhcpOption { + kind: field::OPT_PARAMETER_REQUEST_LIST, + data: val, + })?; + } + + if let Some(dns_servers) = &self.dns_servers { + const IP_SIZE: usize = core::mem::size_of::(); + let mut servers = [0; MAX_DNS_SERVER_COUNT * IP_SIZE]; + + let data_len = dns_servers + .iter() + .enumerate() + .inspect(|(i, ip)| { + servers[(i * IP_SIZE)..((i + 1) * IP_SIZE)].copy_from_slice(&ip.octets()); + }) + .count() + * IP_SIZE; + options.emit(DhcpOption { + kind: field::OPT_DOMAIN_NAME_SERVER, + data: &servers[..data_len], + })?; + } + + for option in self.additional_options { + options.emit(*option)?; + } + + options.end()?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::wire::Ipv4Address; + + const MAGIC_COOKIE: u32 = 0x63825363; + + static DISCOVER_BYTES: &[u8] = &[ + 0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x82, 0x01, 0xfc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, + 0x35, 0x01, 0x01, 0x3d, 0x07, 0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42, 0x32, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + static ACK_DNS_SERVER_BYTES: &[u8] = &[ + 0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06, + 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17, + 0xeb, 0xc9, 0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, + 0x35, 0x01, 0x05, 0x36, 0x04, 0xa3, 0x01, 0x4a, 0x16, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, + 0x2b, 0x05, 0xdc, 0x03, 0x4e, 0x41, 0x50, 0x0f, 0x15, 0x6e, 0x61, 0x74, 0x2e, 0x70, 0x68, + 0x79, 0x73, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x78, 0x2e, 0x61, 0x63, 0x2e, 0x75, 0x6b, 0x00, + 0x03, 0x04, 0x0a, 0xff, 0x06, 0xfe, 0x06, 0x10, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, + 0x07, 0xa3, 0x01, 0x4a, 0x03, 0xa3, 0x01, 0x4a, 0x04, 0x2c, 0x10, 0xa3, 0x01, 0x4a, 0x03, + 0xa3, 0x01, 0x4a, 0x04, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08, + 0xff, + ]; + + static ACK_LEASE_TIME_BYTES: &[u8] = &[ + 0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x22, 0x10, 0x0b, 0x0a, 0x22, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91, + 0x62, 0xd2, 0xa8, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63, + 0x35, 0x01, 0x05, 0x36, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0x33, 0x04, 0x00, 0x00, 0x02, 0x56, + 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + const IP_NULL: Ipv4Address = Ipv4Address::new(0, 0, 0, 0); + const CLIENT_MAC: EthernetAddress = EthernetAddress([0x0, 0x0b, 0x82, 0x01, 0xfc, 0x42]); + const DHCP_SIZE: u16 = 1500; + + #[test] + fn test_deconstruct_discover() { + let packet = Packet::new_unchecked(DISCOVER_BYTES); + assert_eq!(packet.magic_number(), MAGIC_COOKIE); + assert_eq!(packet.opcode(), OpCode::Request); + assert_eq!(packet.hardware_type(), Hardware::Ethernet); + assert_eq!(packet.hardware_len(), 6); + assert_eq!(packet.hops(), 0); + assert_eq!(packet.transaction_id(), 0x3d1d); + assert_eq!(packet.secs(), 0); + assert_eq!(packet.client_ip(), IP_NULL); + assert_eq!(packet.your_ip(), IP_NULL); + assert_eq!(packet.server_ip(), IP_NULL); + assert_eq!(packet.relay_agent_ip(), IP_NULL); + assert_eq!(packet.client_hardware_address(), CLIENT_MAC); + + let mut options = packet.options(); + assert_eq!( + options.next(), + Some(DhcpOption { + kind: field::OPT_DHCP_MESSAGE_TYPE, + data: &[0x01] + }) + ); + assert_eq!( + options.next(), + Some(DhcpOption { + kind: field::OPT_CLIENT_ID, + data: &[0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42], + }) + ); + assert_eq!( + options.next(), + Some(DhcpOption { + kind: field::OPT_REQUESTED_IP, + data: &[0x00, 0x00, 0x00, 0x00], + }) + ); + assert_eq!( + options.next(), + Some(DhcpOption { + kind: field::OPT_MAX_DHCP_MESSAGE_SIZE, + data: &DHCP_SIZE.to_be_bytes(), + }) + ); + assert_eq!( + options.next(), + Some(DhcpOption { + kind: field::OPT_PARAMETER_REQUEST_LIST, + data: &[1, 3, 6, 42] + }) + ); + assert_eq!(options.next(), None); + } + + #[test] + fn test_construct_discover() { + let mut bytes = vec![0xa5; 276]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_magic_number(MAGIC_COOKIE); + packet.set_sname_and_boot_file_to_zero(); + packet.set_opcode(OpCode::Request); + packet.set_hardware_type(Hardware::Ethernet); + packet.set_hardware_len(6); + packet.set_hops(0); + packet.set_transaction_id(0x3d1d); + packet.set_secs(0); + packet.set_flags(Flags::empty()); + packet.set_client_ip(IP_NULL); + packet.set_your_ip(IP_NULL); + packet.set_server_ip(IP_NULL); + packet.set_relay_agent_ip(IP_NULL); + packet.set_client_hardware_address(CLIENT_MAC); + + let mut options = packet.options_mut(); + + options + .emit(DhcpOption { + kind: field::OPT_DHCP_MESSAGE_TYPE, + data: &[0x01], + }) + .unwrap(); + options + .emit(DhcpOption { + kind: field::OPT_CLIENT_ID, + data: &[0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42], + }) + .unwrap(); + options + .emit(DhcpOption { + kind: field::OPT_REQUESTED_IP, + data: &[0x00, 0x00, 0x00, 0x00], + }) + .unwrap(); + options + .emit(DhcpOption { + kind: field::OPT_MAX_DHCP_MESSAGE_SIZE, + data: &DHCP_SIZE.to_be_bytes(), + }) + .unwrap(); + options + .emit(DhcpOption { + kind: field::OPT_PARAMETER_REQUEST_LIST, + data: &[1, 3, 6, 42], + }) + .unwrap(); + options.end().unwrap(); + + let packet = &mut packet.into_inner()[..]; + for byte in &mut packet[269..276] { + *byte = 0; // padding bytes + } + + assert_eq!(packet, DISCOVER_BYTES); + } + + const fn offer_repr() -> Repr<'static> { + Repr { + message_type: MessageType::Offer, + transaction_id: 0x3d1d, + client_hardware_address: CLIENT_MAC, + client_ip: IP_NULL, + your_ip: IP_NULL, + server_ip: IP_NULL, + router: Some(IP_NULL), + subnet_mask: Some(IP_NULL), + relay_agent_ip: IP_NULL, + secs: 0, + broadcast: false, + requested_ip: None, + client_identifier: Some(CLIENT_MAC), + server_identifier: None, + parameter_request_list: None, + dns_servers: None, + max_size: None, + renew_duration: None, + rebind_duration: None, + lease_duration: Some(0xffff_ffff), // Infinite lease + additional_options: &[], + } + } + + const fn discover_repr() -> Repr<'static> { + Repr { + message_type: MessageType::Discover, + transaction_id: 0x3d1d, + client_hardware_address: CLIENT_MAC, + client_ip: IP_NULL, + your_ip: IP_NULL, + server_ip: IP_NULL, + router: None, + subnet_mask: None, + relay_agent_ip: IP_NULL, + broadcast: false, + secs: 0, + max_size: Some(DHCP_SIZE), + renew_duration: None, + rebind_duration: None, + lease_duration: None, + requested_ip: Some(IP_NULL), + client_identifier: Some(CLIENT_MAC), + server_identifier: None, + parameter_request_list: Some(&[1, 3, 6, 42]), + dns_servers: None, + additional_options: &[], + } + } + + #[test] + fn test_parse_discover() { + let packet = Packet::new_unchecked(DISCOVER_BYTES); + let repr = Repr::parse(&packet).unwrap(); + assert_eq!(repr, discover_repr()); + } + + #[test] + fn test_emit_discover() { + let repr = discover_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet).unwrap(); + let packet = &*packet.into_inner(); + let packet_len = packet.len(); + assert_eq!(packet, &DISCOVER_BYTES[..packet_len]); + for byte in &DISCOVER_BYTES[packet_len..] { + assert_eq!(*byte, 0); // padding bytes + } + } + + #[test] + fn test_emit_offer() { + let repr = offer_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet).unwrap(); + } + + #[test] + fn test_emit_offer_dns() { + let repr = { + let mut repr = offer_repr(); + repr.dns_servers = Some( + Vec::from_slice(&[ + Ipv4Address::new(163, 1, 74, 6), + Ipv4Address::new(163, 1, 74, 7), + Ipv4Address::new(163, 1, 74, 3), + ]) + .unwrap(), + ); + repr + }; + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet).unwrap(); + + let packet = Packet::new_unchecked(&bytes); + let repr_parsed = Repr::parse(&packet).unwrap(); + + assert_eq!( + repr_parsed.dns_servers, + Some( + Vec::from_slice(&[ + Ipv4Address::new(163, 1, 74, 6), + Ipv4Address::new(163, 1, 74, 7), + Ipv4Address::new(163, 1, 74, 3), + ]) + .unwrap() + ) + ); + } + + #[test] + fn test_emit_dhcp_option() { + static DATA: &[u8] = &[1, 3, 6]; + let dhcp_option = DhcpOption { + kind: field::OPT_PARAMETER_REQUEST_LIST, + data: DATA, + }; + + let mut bytes = vec![0xa5; 5]; + let mut writer = DhcpOptionWriter::new(&mut bytes); + writer.emit(dhcp_option).unwrap(); + + assert_eq!( + &bytes[0..2], + &[field::OPT_PARAMETER_REQUEST_LIST, DATA.len() as u8] + ); + assert_eq!(&bytes[2..], DATA); + } + + #[test] + fn test_parse_ack_dns_servers() { + let packet = Packet::new_unchecked(ACK_DNS_SERVER_BYTES); + let repr = Repr::parse(&packet).unwrap(); + + // The packet described by ACK_BYTES advertises 4 DNS servers + // Here we ensure that we correctly parse the first 3 into our fixed + // length-3 array (see issue #305) + assert_eq!( + repr.dns_servers, + Some( + Vec::from_slice(&[ + Ipv4Address::new(163, 1, 74, 6), + Ipv4Address::new(163, 1, 74, 7), + Ipv4Address::new(163, 1, 74, 3) + ]) + .unwrap() + ) + ); + } + + #[test] + fn test_parse_ack_lease_duration() { + let packet = Packet::new_unchecked(ACK_LEASE_TIME_BYTES); + let repr = Repr::parse(&packet).unwrap(); + + // Verify that the lease time in the ACK is properly parsed. The packet contains a lease + // duration of 598s. + assert_eq!(repr.lease_duration, Some(598)); + } +} diff --git a/vendor/smoltcp/src/wire/dns.rs b/vendor/smoltcp/src/wire/dns.rs new file mode 100644 index 00000000..3657937a --- /dev/null +++ b/vendor/smoltcp/src/wire/dns.rs @@ -0,0 +1,792 @@ +#![allow(dead_code)] + +use bitflags::bitflags; +use byteorder::{ByteOrder, NetworkEndian}; +use core::iter; +use core::iter::Iterator; + +use super::{Error, Result}; +#[cfg(feature = "proto-ipv4")] +use crate::wire::Ipv4Address; +#[cfg(feature = "proto-ipv6")] +use crate::wire::Ipv6Address; + +enum_with_unknown! { + /// DNS OpCodes + pub enum Opcode(u8) { + Query = 0x00, + Status = 0x01, + } +} +enum_with_unknown! { + /// DNS OpCodes + pub enum Rcode(u8) { + NoError = 0x00, + FormErr = 0x01, + ServFail = 0x02, + NXDomain = 0x03, + NotImp = 0x04, + Refused = 0x05, + YXDomain = 0x06, + YXRRSet = 0x07, + NXRRSet = 0x08, + NotAuth = 0x09, + NotZone = 0x0a, + } +} + +enum_with_unknown! { + /// DNS record types + pub enum Type(u16) { + A = 0x0001, + Ns = 0x0002, + Cname = 0x0005, + Soa = 0x0006, + Aaaa = 0x001c, + } +} + +bitflags! { + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct Flags: u16 { + const RESPONSE = 0b1000_0000_0000_0000; + const AUTHORITATIVE = 0b0000_0100_0000_0000; + const TRUNCATED = 0b0000_0010_0000_0000; + const RECURSION_DESIRED = 0b0000_0001_0000_0000; + const RECURSION_AVAILABLE = 0b0000_0000_1000_0000; + const AUTHENTIC_DATA = 0b0000_0000_0010_0000; + const CHECK_DISABLED = 0b0000_0000_0001_0000; + } +} + +mod field { + use crate::wire::field::*; + + pub const ID: Field = 0..2; + pub const FLAGS: Field = 2..4; + pub const QDCOUNT: Field = 4..6; + pub const ANCOUNT: Field = 6..8; + pub const NSCOUNT: Field = 8..10; + pub const ARCOUNT: Field = 10..12; + + pub const HEADER_END: usize = 12; +} + +// DNS class IN (Internet) +const CLASS_IN: u16 = 1; + +/// A read/write wrapper around a DNS packet buffer. +#[derive(Debug, PartialEq, Eq)] +pub struct Packet> { + buffer: T, +} + +impl> Packet { + /// Imbue a raw octet buffer with DNS packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is smaller than + /// the header length. + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::HEADER_END { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + pub fn payload(&self) -> &[u8] { + &self.buffer.as_ref()[field::HEADER_END..] + } + + pub fn transaction_id(&self) -> u16 { + let field = &self.buffer.as_ref()[field::ID]; + NetworkEndian::read_u16(field) + } + + pub fn flags(&self) -> Flags { + let field = &self.buffer.as_ref()[field::FLAGS]; + Flags::from_bits_truncate(NetworkEndian::read_u16(field)) + } + + pub fn opcode(&self) -> Opcode { + let field = &self.buffer.as_ref()[field::FLAGS]; + let flags = NetworkEndian::read_u16(field); + Opcode::from((flags >> 11 & 0xF) as u8) + } + + pub fn rcode(&self) -> Rcode { + let field = &self.buffer.as_ref()[field::FLAGS]; + let flags = NetworkEndian::read_u16(field); + Rcode::from((flags & 0xF) as u8) + } + + pub fn question_count(&self) -> u16 { + let field = &self.buffer.as_ref()[field::QDCOUNT]; + NetworkEndian::read_u16(field) + } + + pub fn answer_record_count(&self) -> u16 { + let field = &self.buffer.as_ref()[field::ANCOUNT]; + NetworkEndian::read_u16(field) + } + + pub fn authority_record_count(&self) -> u16 { + let field = &self.buffer.as_ref()[field::NSCOUNT]; + NetworkEndian::read_u16(field) + } + + pub fn additional_record_count(&self) -> u16 { + let field = &self.buffer.as_ref()[field::ARCOUNT]; + NetworkEndian::read_u16(field) + } + + /// Parse part of a name from `bytes`, following pointers if any. + pub fn parse_name<'a>(&'a self, mut bytes: &'a [u8]) -> impl Iterator> { + let mut packet = self.buffer.as_ref(); + + iter::from_fn(move || { + loop { + if bytes.is_empty() { + return Some(Err(Error)); + } + match bytes[0] { + 0x00 => return None, + x if x & 0xC0 == 0x00 => { + let len = (x & 0x3F) as usize; + if bytes.len() < 1 + len { + return Some(Err(Error)); + } + let label = &bytes[1..1 + len]; + bytes = &bytes[1 + len..]; + return Some(Ok(label)); + } + x if x & 0xC0 == 0xC0 => { + if bytes.len() < 2 { + return Some(Err(Error)); + } + let y = bytes[1]; + let ptr = ((x & 0x3F) as usize) << 8 | (y as usize); + if packet.len() <= ptr { + return Some(Err(Error)); + } + + // RFC1035 says: "In this scheme, an entire domain name or a list of labels at + // the end of a domain name is replaced with a pointer to a ***prior*** occurrence + // of the same name. + // + // Is it unclear if this means the pointer MUST point backwards in the packet or not. Either way, + // pointers that don't point backwards are never seen in the fields, so use this to check that + // there are no pointer loops. + + // Split packet into parts before and after `ptr`. + // parse the part after, keep only the part before in `packet`. This ensure we never + // parse the same byte twice, therefore eliminating pointer loops. + + bytes = &packet[ptr..]; + packet = &packet[..ptr]; + } + _ => return Some(Err(Error)), + } + } + }) + } +} + +impl + AsMut<[u8]>> Packet { + pub fn payload_mut(&mut self) -> &mut [u8] { + let data = self.buffer.as_mut(); + &mut data[field::HEADER_END..] + } + + pub fn set_transaction_id(&mut self, val: u16) { + let field = &mut self.buffer.as_mut()[field::ID]; + NetworkEndian::write_u16(field, val) + } + + pub fn set_flags(&mut self, val: Flags) { + let field = &mut self.buffer.as_mut()[field::FLAGS]; + let mask = Flags::all().bits; + let old = NetworkEndian::read_u16(field); + NetworkEndian::write_u16(field, (old & !mask) | val.bits()); + } + + pub fn set_opcode(&mut self, val: Opcode) { + let field = &mut self.buffer.as_mut()[field::FLAGS]; + let mask = 0x3800; + let val: u8 = val.into(); + let val = (val as u16) << 11; + let old = NetworkEndian::read_u16(field); + NetworkEndian::write_u16(field, (old & !mask) | val); + } + + pub fn set_question_count(&mut self, val: u16) { + let field = &mut self.buffer.as_mut()[field::QDCOUNT]; + NetworkEndian::write_u16(field, val) + } + pub fn set_answer_record_count(&mut self, val: u16) { + let field = &mut self.buffer.as_mut()[field::ANCOUNT]; + NetworkEndian::write_u16(field, val) + } + pub fn set_authority_record_count(&mut self, val: u16) { + let field = &mut self.buffer.as_mut()[field::NSCOUNT]; + NetworkEndian::write_u16(field, val) + } + pub fn set_additional_record_count(&mut self, val: u16) { + let field = &mut self.buffer.as_mut()[field::ARCOUNT]; + NetworkEndian::write_u16(field, val) + } +} + +/// Parse part of a name from `bytes`, not following pointers. +/// Returns the unused part of `bytes`, and the pointer offset if the sequence ends with a pointer. +fn parse_name_part<'a>( + mut bytes: &'a [u8], + mut f: impl FnMut(&'a [u8]), +) -> Result<(&'a [u8], Option)> { + loop { + let x = *bytes.first().ok_or(Error)?; + bytes = &bytes[1..]; + match x { + 0x00 => return Ok((bytes, None)), + x if x & 0xC0 == 0x00 => { + let len = (x & 0x3F) as usize; + let label = bytes.get(..len).ok_or(Error)?; + bytes = &bytes[len..]; + f(label); + } + x if x & 0xC0 == 0xC0 => { + let y = *bytes.first().ok_or(Error)?; + bytes = &bytes[1..]; + + let ptr = ((x & 0x3F) as usize) << 8 | (y as usize); + return Ok((bytes, Some(ptr))); + } + _ => return Err(Error), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Question<'a> { + pub name: &'a [u8], + pub type_: Type, +} + +impl<'a> Question<'a> { + pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Question<'a>)> { + let (rest, _) = parse_name_part(buffer, |_| ())?; + let name = &buffer[..buffer.len() - rest.len()]; + + if rest.len() < 4 { + return Err(Error); + } + let type_ = NetworkEndian::read_u16(&rest[0..2]).into(); + let class = NetworkEndian::read_u16(&rest[2..4]); + let rest = &rest[4..]; + + if class != CLASS_IN { + return Err(Error); + } + + Ok((rest, Question { name, type_ })) + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + self.name.len() + 4 + } + + /// Emit a high-level representation into a DNS packet. + pub fn emit(&self, packet: &mut [u8]) { + packet[..self.name.len()].copy_from_slice(self.name); + let rest = &mut packet[self.name.len()..]; + NetworkEndian::write_u16(&mut rest[0..2], self.type_.into()); + NetworkEndian::write_u16(&mut rest[2..4], CLASS_IN); + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Record<'a> { + pub name: &'a [u8], + pub ttl: u32, + pub data: RecordData<'a>, +} + +impl<'a> RecordData<'a> { + pub fn parse(type_: Type, data: &'a [u8]) -> Result> { + match type_ { + #[cfg(feature = "proto-ipv4")] + Type::A => Ok(RecordData::A(crate::wire::ipv4_from_octets( + data.try_into().map_err(|_| Error)?, + ))), + #[cfg(feature = "proto-ipv6")] + Type::Aaaa => Ok(RecordData::Aaaa(crate::wire::ipv6_from_octets( + data.try_into().map_err(|_| Error)?, + ))), + Type::Cname => Ok(RecordData::Cname(data)), + x => Ok(RecordData::Other(x, data)), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecordData<'a> { + #[cfg(feature = "proto-ipv4")] + A(Ipv4Address), + #[cfg(feature = "proto-ipv6")] + Aaaa(Ipv6Address), + Cname(&'a [u8]), + Other(Type, &'a [u8]), +} + +impl<'a> Record<'a> { + pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], Record<'a>)> { + let (rest, _) = parse_name_part(buffer, |_| ())?; + let name = &buffer[..buffer.len() - rest.len()]; + + if rest.len() < 10 { + return Err(Error); + } + let type_ = NetworkEndian::read_u16(&rest[0..2]).into(); + let class = NetworkEndian::read_u16(&rest[2..4]); + let ttl = NetworkEndian::read_u32(&rest[4..8]); + let len = NetworkEndian::read_u16(&rest[8..10]) as usize; + let rest = &rest[10..]; + + if class != CLASS_IN { + return Err(Error); + } + + let data = rest.get(..len).ok_or(Error)?; + let rest = &rest[len..]; + + Ok(( + rest, + Record { + name, + ttl, + data: RecordData::parse(type_, data)?, + }, + )) + } +} + +/// High-level DNS packet representation. +/// +/// Currently only supports query packets. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr<'a> { + pub transaction_id: u16, + pub opcode: Opcode, + pub flags: Flags, + pub question: Question<'a>, +} + +impl<'a> Repr<'a> { + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + field::HEADER_END + self.question.buffer_len() + } + + /// Emit a high-level representation into a DNS packet. + pub fn emit(&self, packet: &mut Packet<&mut T>) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + packet.set_transaction_id(self.transaction_id); + packet.set_flags(self.flags); + packet.set_opcode(self.opcode); + packet.set_question_count(1); + packet.set_answer_record_count(0); + packet.set_authority_record_count(0); + packet.set_additional_record_count(0); + self.question.emit(packet.payload_mut()) + } +} + +#[cfg(feature = "proto-ipv4")] // tests assume ipv4 +#[cfg(test)] +mod test { + use super::*; + use std::vec::Vec; + + #[test] + fn test_parse_name() { + let bytes = &[ + 0x78, 0x6c, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, + 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, + 0x05, 0xf3, 0x00, 0x11, 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69, + 0x04, 0x63, 0x31, 0x30, 0x72, 0xc0, 0x10, 0xc0, 0x2e, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x04, 0x1f, 0x0d, 0x53, 0x24, + ]; + let packet = Packet::new_unchecked(bytes); + + let name_vec = |bytes| { + let mut v = Vec::new(); + packet + .parse_name(bytes) + .try_for_each(|label| label.map(|label| v.push(label))) + .map(|_| v) + }; + + //assert_eq!(parse_name_len(bytes, 0x0c), Ok(18)); + assert_eq!( + name_vec(&bytes[0x0c..]), + Ok(vec![&b"www"[..], &b"facebook"[..], &b"com"[..]]) + ); + //assert_eq!(parse_name_len(bytes, 0x22), Ok(2)); + assert_eq!( + name_vec(&bytes[0x22..]), + Ok(vec![&b"www"[..], &b"facebook"[..], &b"com"[..]]) + ); + //assert_eq!(parse_name_len(bytes, 0x2e), Ok(17)); + assert_eq!( + name_vec(&bytes[0x2e..]), + Ok(vec![ + &b"star-mini"[..], + &b"c10r"[..], + &b"facebook"[..], + &b"com"[..] + ]) + ); + //assert_eq!(parse_name_len(bytes, 0x3f), Ok(2)); + assert_eq!( + name_vec(&bytes[0x3f..]), + Ok(vec![ + &b"star-mini"[..], + &b"c10r"[..], + &b"facebook"[..], + &b"com"[..] + ]) + ); + } + + struct Parsed<'a> { + packet: Packet<&'a [u8]>, + questions: Vec>, + answers: Vec>, + authorities: Vec>, + additionals: Vec>, + } + + impl<'a> Parsed<'a> { + fn parse(bytes: &'a [u8]) -> Result { + let packet = Packet::new_unchecked(bytes); + let mut questions = Vec::new(); + let mut answers = Vec::new(); + let mut authorities = Vec::new(); + let mut additionals = Vec::new(); + + let mut payload = &bytes[12..]; + + for _ in 0..packet.question_count() { + let (p, r) = Question::parse(payload)?; + questions.push(r); + payload = p; + } + for _ in 0..packet.answer_record_count() { + let (p, r) = Record::parse(payload)?; + answers.push(r); + payload = p; + } + for _ in 0..packet.authority_record_count() { + let (p, r) = Record::parse(payload)?; + authorities.push(r); + payload = p; + } + for _ in 0..packet.additional_record_count() { + let (p, r) = Record::parse(payload)?; + additionals.push(r); + payload = p; + } + + // Check that there are no bytes left + assert_eq!(payload.len(), 0); + + Ok(Parsed { + packet, + questions, + answers, + authorities, + additionals, + }) + } + } + + #[test] + fn test_parse_request() { + let p = Parsed::parse(&[ + 0x51, 0x84, 0x01, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + ]) + .unwrap(); + + assert_eq!(p.packet.transaction_id(), 0x5184); + assert_eq!( + p.packet.flags(), + Flags::RECURSION_DESIRED | Flags::AUTHENTIC_DATA + ); + assert_eq!(p.packet.opcode(), Opcode::Query); + assert_eq!(p.packet.question_count(), 1); + assert_eq!(p.packet.answer_record_count(), 0); + assert_eq!(p.packet.authority_record_count(), 0); + assert_eq!(p.packet.additional_record_count(), 0); + + assert_eq!(p.questions.len(), 1); + assert_eq!( + p.questions[0].name, + &[ + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert_eq!(p.questions[0].type_, Type::A); + + assert_eq!(p.answers.len(), 0); + assert_eq!(p.authorities.len(), 0); + assert_eq!(p.additionals.len(), 0); + } + + #[test] + fn test_parse_response() { + let p = Parsed::parse(&[ + 0x51, 0x84, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xca, 0x00, 0x04, 0xac, 0xd9, + 0xa8, 0xae, + ]) + .unwrap(); + + assert_eq!(p.packet.transaction_id(), 0x5184); + assert_eq!( + p.packet.flags(), + Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE + ); + assert_eq!(p.packet.opcode(), Opcode::Query); + assert_eq!(p.packet.rcode(), Rcode::NoError); + assert_eq!(p.packet.question_count(), 1); + assert_eq!(p.packet.answer_record_count(), 1); + assert_eq!(p.packet.authority_record_count(), 0); + assert_eq!(p.packet.additional_record_count(), 0); + + assert_eq!( + p.questions[0].name, + &[ + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert_eq!(p.questions[0].type_, Type::A); + + assert_eq!(p.answers[0].name, &[0xc0, 0x0c]); + assert_eq!(p.answers[0].ttl, 202); + assert_eq!( + p.answers[0].data, + RecordData::A(Ipv4Address::new(0xac, 0xd9, 0xa8, 0xae)) + ); + } + + #[test] + fn test_parse_response_multiple_a() { + let p = Parsed::parse(&[ + 0x4b, 0x9e, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72, + 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, + 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, + 0x04, 0x0d, 0xe0, 0x77, 0x35, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x28, 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x43, 0xc0, 0x0c, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x04, 0x0d, 0xe0, 0x77, 0x62, + ]) + .unwrap(); + + assert_eq!(p.packet.transaction_id(), 0x4b9e); + assert_eq!( + p.packet.flags(), + Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE + ); + assert_eq!(p.packet.opcode(), Opcode::Query); + assert_eq!(p.packet.rcode(), Rcode::NoError); + assert_eq!(p.packet.question_count(), 1); + assert_eq!(p.packet.answer_record_count(), 4); + assert_eq!(p.packet.authority_record_count(), 0); + assert_eq!(p.packet.additional_record_count(), 0); + + assert_eq!( + p.questions[0].name, + &[ + 0x09, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, + 0x00 + ] + ); + assert_eq!(p.questions[0].type_, Type::A); + + assert_eq!(p.answers[0].name, &[0xc0, 0x0c]); + assert_eq!(p.answers[0].ttl, 9); + assert_eq!( + p.answers[0].data, + RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x35)) + ); + + assert_eq!(p.answers[1].name, &[0xc0, 0x0c]); + assert_eq!(p.answers[1].ttl, 9); + assert_eq!( + p.answers[1].data, + RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x28)) + ); + + assert_eq!(p.answers[2].name, &[0xc0, 0x0c]); + assert_eq!(p.answers[2].ttl, 9); + assert_eq!( + p.answers[2].data, + RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x43)) + ); + + assert_eq!(p.answers[3].name, &[0xc0, 0x0c]); + assert_eq!(p.answers[3].ttl, 9); + assert_eq!( + p.answers[3].data, + RecordData::A(Ipv4Address::new(0x0d, 0xe0, 0x77, 0x62)) + ); + } + + #[test] + fn test_parse_response_cname() { + let p = Parsed::parse(&[ + 0x78, 0x6c, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, + 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, + 0x05, 0xf3, 0x00, 0x11, 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69, + 0x04, 0x63, 0x31, 0x30, 0x72, 0xc0, 0x10, 0xc0, 0x2e, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x04, 0x1f, 0x0d, 0x53, 0x24, + ]) + .unwrap(); + + assert_eq!(p.packet.transaction_id(), 0x786c); + assert_eq!( + p.packet.flags(), + Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE + ); + assert_eq!(p.packet.opcode(), Opcode::Query); + assert_eq!(p.packet.rcode(), Rcode::NoError); + assert_eq!(p.packet.question_count(), 1); + assert_eq!(p.packet.answer_record_count(), 2); + assert_eq!(p.packet.authority_record_count(), 0); + assert_eq!(p.packet.additional_record_count(), 0); + + assert_eq!( + p.questions[0].name, + &[ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x66, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x03, + 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert_eq!(p.questions[0].type_, Type::A); + + // cname + assert_eq!(p.answers[0].name, &[0xc0, 0x0c]); + assert_eq!(p.answers[0].ttl, 1523); + assert_eq!( + p.answers[0].data, + RecordData::Cname(&[ + 0x09, 0x73, 0x74, 0x61, 0x72, 0x2d, 0x6d, 0x69, 0x6e, 0x69, 0x04, 0x63, 0x31, 0x30, + 0x72, 0xc0, 0x10 + ]) + ); + // a + assert_eq!(p.answers[1].name, &[0xc0, 0x2e]); + assert_eq!(p.answers[1].ttl, 5); + assert_eq!( + p.answers[1].data, + RecordData::A(Ipv4Address::new(0x1f, 0x0d, 0x53, 0x24)) + ); + } + + #[test] + fn test_parse_response_nxdomain() { + let p = Parsed::parse(&[ + 0x63, 0xc4, 0x81, 0x83, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x13, 0x61, + 0x68, 0x61, 0x73, 0x64, 0x67, 0x68, 0x6c, 0x61, 0x6b, 0x73, 0x6a, 0x68, 0x62, 0x61, + 0x61, 0x73, 0x6c, 0x64, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, + 0x20, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x03, 0x83, 0x00, 0x3d, 0x01, 0x61, 0x0c, + 0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, + 0x65, 0x74, 0x00, 0x05, 0x6e, 0x73, 0x74, 0x6c, 0x64, 0x0c, 0x76, 0x65, 0x72, 0x69, + 0x73, 0x69, 0x67, 0x6e, 0x2d, 0x67, 0x72, 0x73, 0xc0, 0x20, 0x5f, 0xce, 0x8b, 0x85, + 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x03, 0x84, 0x00, 0x09, 0x3a, 0x80, 0x00, 0x01, + 0x51, 0x80, + ]) + .unwrap(); + + assert_eq!(p.packet.transaction_id(), 0x63c4); + assert_eq!( + p.packet.flags(), + Flags::RESPONSE | Flags::RECURSION_DESIRED | Flags::RECURSION_AVAILABLE + ); + assert_eq!(p.packet.opcode(), Opcode::Query); + assert_eq!(p.packet.rcode(), Rcode::NXDomain); + assert_eq!(p.packet.question_count(), 1); + assert_eq!(p.packet.answer_record_count(), 0); + assert_eq!(p.packet.authority_record_count(), 1); + assert_eq!(p.packet.additional_record_count(), 0); + + assert_eq!(p.questions[0].type_, Type::A); + + // SOA authority + assert_eq!(p.authorities[0].name, &[0xc0, 0x20]); // com. + assert_eq!(p.authorities[0].ttl, 899); + assert!(matches!( + p.authorities[0].data, + RecordData::Other(Type::Soa, _) + )); + } + + #[test] + fn test_emit() { + let name = &[ + 0x09, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, + 0x00, + ]; + + let repr = Repr { + transaction_id: 0x1234, + flags: Flags::RECURSION_DESIRED, + opcode: Opcode::Query, + question: Question { + name, + type_: Type::A, + }, + }; + + let mut buf = vec![0; repr.buffer_len()]; + repr.emit(&mut Packet::new_unchecked(&mut buf)); + + let want = &[ + 0x12, 0x34, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x72, + 0x75, 0x73, 0x74, 0x2d, 0x6c, 0x61, 0x6e, 0x67, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, + 0x01, 0x00, 0x01, + ]; + assert_eq!(&buf, want); + } +} diff --git a/vendor/smoltcp/src/wire/ethernet.rs b/vendor/smoltcp/src/wire/ethernet.rs new file mode 100644 index 00000000..9e2411f5 --- /dev/null +++ b/vendor/smoltcp/src/wire/ethernet.rs @@ -0,0 +1,424 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; + +enum_with_unknown! { + /// Ethernet protocol type. + pub enum EtherType(u16) { + Ipv4 = 0x0800, + Arp = 0x0806, + Ipv6 = 0x86DD + } +} + +impl fmt::Display for EtherType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + EtherType::Ipv4 => write!(f, "IPv4"), + EtherType::Ipv6 => write!(f, "IPv6"), + EtherType::Arp => write!(f, "ARP"), + EtherType::Unknown(id) => write!(f, "0x{id:04x}"), + } + } +} + +/// A six-octet Ethernet II address. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct Address(pub [u8; 6]); + +impl Address { + /// The broadcast address. + pub const BROADCAST: Address = Address([0xff; 6]); + + /// Construct an Ethernet address from a sequence of octets, in big-endian. + /// + /// # Panics + /// The function panics if `data` is not six octets long. + pub fn from_bytes(data: &[u8]) -> Address { + let mut bytes = [0; 6]; + bytes.copy_from_slice(data); + Address(bytes) + } + + /// Return an Ethernet address as a sequence of octets, in big-endian. + pub const fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Query whether the address is an unicast address. + pub fn is_unicast(&self) -> bool { + !(self.is_broadcast() || self.is_multicast()) + } + + /// Query whether this address is the broadcast address. + pub fn is_broadcast(&self) -> bool { + *self == Self::BROADCAST + } + + /// Query whether the "multicast" bit in the OUI is set. + pub const fn is_multicast(&self) -> bool { + self.0[0] & 0x01 != 0 + } + + /// Query whether the "locally administered" bit in the OUI is set. + pub const fn is_local(&self) -> bool { + self.0[0] & 0x02 != 0 + } + + /// Convert the address to an Extended Unique Identifier (EUI-64) + pub fn as_eui_64(&self) -> Option<[u8; 8]> { + let mut bytes = [0; 8]; + bytes[0..3].copy_from_slice(&self.0[0..3]); + bytes[3] = 0xFF; + bytes[4] = 0xFE; + bytes[5..8].copy_from_slice(&self.0[3..6]); + bytes[0] ^= 1 << 1; + Some(bytes) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.0; + write!( + f, + "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5] + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Address { + fn format(&self, fmt: defmt::Formatter) { + let bytes = self.0; + defmt::write!( + fmt, + "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", + bytes[0], + bytes[1], + bytes[2], + bytes[3], + bytes[4], + bytes[5] + ) + } +} + +/// A read/write wrapper around an Ethernet II frame buffer. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Frame> { + buffer: T, +} + +mod field { + use crate::wire::field::*; + + pub const DESTINATION: Field = 0..6; + pub const SOURCE: Field = 6..12; + pub const ETHERTYPE: Field = 12..14; + pub const PAYLOAD: Rest = 14..; +} + +/// The Ethernet header length +pub const HEADER_LEN: usize = field::PAYLOAD.start; + +impl> Frame { + /// Imbue a raw octet buffer with Ethernet frame structure. + pub const fn new_unchecked(buffer: T) -> Frame { + Frame { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < HEADER_LEN { Err(Error) } else { Ok(()) } + } + + /// Consumes the frame, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the length of a frame header. + pub const fn header_len() -> usize { + HEADER_LEN + } + + /// Return the length of a buffer required to hold a packet with the payload + /// of a given length. + pub const fn buffer_len(payload_len: usize) -> usize { + HEADER_LEN + payload_len + } + + /// Return the destination address field. + #[inline] + pub fn dst_addr(&self) -> Address { + let data = self.buffer.as_ref(); + Address::from_bytes(&data[field::DESTINATION]) + } + + /// Return the source address field. + #[inline] + pub fn src_addr(&self) -> Address { + let data = self.buffer.as_ref(); + Address::from_bytes(&data[field::SOURCE]) + } + + /// Return the EtherType field, without checking for 802.1Q. + #[inline] + pub fn ethertype(&self) -> EtherType { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::ETHERTYPE]); + EtherType::from(raw) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Frame<&'a T> { + /// Return a pointer to the payload, without checking for 802.1Q. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + &data[field::PAYLOAD] + } +} + +impl + AsMut<[u8]>> Frame { + /// Set the destination address field. + #[inline] + pub fn set_dst_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::DESTINATION].copy_from_slice(value.as_bytes()) + } + + /// Set the source address field. + #[inline] + pub fn set_src_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::SOURCE].copy_from_slice(value.as_bytes()) + } + + /// Set the EtherType field. + #[inline] + pub fn set_ethertype(&mut self, value: EtherType) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::ETHERTYPE], value.into()) + } + + /// Return a mutable pointer to the payload. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let data = self.buffer.as_mut(); + &mut data[field::PAYLOAD] + } +} + +impl> AsRef<[u8]> for Frame { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +impl> fmt::Display for Frame { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "EthernetII src={} dst={} type={}", + self.src_addr(), + self.dst_addr(), + self.ethertype() + ) + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Frame { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + let frame = match Frame::new_checked(buffer) { + Err(err) => return write!(f, "{indent}({err})"), + Ok(frame) => frame, + }; + write!(f, "{indent}{frame}")?; + + match frame.ethertype() { + #[cfg(feature = "proto-ipv4")] + EtherType::Arp => { + indent.increase(f)?; + super::ArpPacket::<&[u8]>::pretty_print(&frame.payload(), f, indent) + } + #[cfg(feature = "proto-ipv4")] + EtherType::Ipv4 => { + indent.increase(f)?; + super::Ipv4Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent) + } + #[cfg(feature = "proto-ipv6")] + EtherType::Ipv6 => { + indent.increase(f)?; + super::Ipv6Packet::<&[u8]>::pretty_print(&frame.payload(), f, indent) + } + _ => Ok(()), + } + } +} + +/// A high-level representation of an Internet Protocol version 4 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr { + pub src_addr: Address, + pub dst_addr: Address, + pub ethertype: EtherType, +} + +impl Repr { + /// Parse an Ethernet II frame and return a high-level representation. + pub fn parse + ?Sized>(frame: &Frame<&T>) -> Result { + frame.check_len()?; + Ok(Repr { + src_addr: frame.src_addr(), + dst_addr: frame.dst_addr(), + ethertype: frame.ethertype(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + HEADER_LEN + } + + /// Emit a high-level representation into an Ethernet II frame. + pub fn emit + AsMut<[u8]>>(&self, frame: &mut Frame) { + assert!(frame.buffer.as_ref().len() >= self.buffer_len()); + frame.set_src_addr(self.src_addr); + frame.set_dst_addr(self.dst_addr); + frame.set_ethertype(self.ethertype); + } +} + +#[cfg(test)] +mod test { + // Tests that are valid with any combination of + // "proto-*" features. + use super::*; + + #[test] + fn test_broadcast() { + assert!(Address::BROADCAST.is_broadcast()); + assert!(!Address::BROADCAST.is_unicast()); + assert!(Address::BROADCAST.is_multicast()); + assert!(Address::BROADCAST.is_local()); + } +} + +#[cfg(test)] +#[cfg(feature = "proto-ipv4")] +mod test_ipv4 { + // Tests that are valid only with "proto-ipv4" + use super::*; + + static FRAME_BYTES: [u8; 64] = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x08, 0x00, 0xaa, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, + ]; + + static PAYLOAD_BYTES: [u8; 50] = [ + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + ]; + + #[test] + fn test_deconstruct() { + let frame = Frame::new_unchecked(&FRAME_BYTES[..]); + assert_eq!( + frame.dst_addr(), + Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]) + ); + assert_eq!( + frame.src_addr(), + Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16]) + ); + assert_eq!(frame.ethertype(), EtherType::Ipv4); + assert_eq!(frame.payload(), &PAYLOAD_BYTES[..]); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 64]; + let mut frame = Frame::new_unchecked(&mut bytes); + frame.set_dst_addr(Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])); + frame.set_src_addr(Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16])); + frame.set_ethertype(EtherType::Ipv4); + frame.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); + assert_eq!(&frame.into_inner()[..], &FRAME_BYTES[..]); + } +} + +#[cfg(test)] +#[cfg(feature = "proto-ipv6")] +mod test_ipv6 { + // Tests that are valid only with "proto-ipv6" + use super::*; + + static FRAME_BYTES: [u8; 54] = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x86, 0xdd, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + + static PAYLOAD_BYTES: [u8; 40] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + + #[test] + fn test_deconstruct() { + let frame = Frame::new_unchecked(&FRAME_BYTES[..]); + assert_eq!( + frame.dst_addr(), + Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06]) + ); + assert_eq!( + frame.src_addr(), + Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16]) + ); + assert_eq!(frame.ethertype(), EtherType::Ipv6); + assert_eq!(frame.payload(), &PAYLOAD_BYTES[..]); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 54]; + let mut frame = Frame::new_unchecked(&mut bytes); + frame.set_dst_addr(Address([0x01, 0x02, 0x03, 0x04, 0x05, 0x06])); + frame.set_src_addr(Address([0x11, 0x12, 0x13, 0x14, 0x15, 0x16])); + frame.set_ethertype(EtherType::Ipv6); + assert_eq!(PAYLOAD_BYTES.len(), frame.payload_mut().len()); + frame.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); + assert_eq!(&frame.into_inner()[..], &FRAME_BYTES[..]); + } +} diff --git a/vendor/smoltcp/src/wire/icmp.rs b/vendor/smoltcp/src/wire/icmp.rs new file mode 100644 index 00000000..6bbc574c --- /dev/null +++ b/vendor/smoltcp/src/wire/icmp.rs @@ -0,0 +1,25 @@ +#[cfg(feature = "proto-ipv4")] +use crate::wire::icmpv4; +#[cfg(feature = "proto-ipv6")] +use crate::wire::icmpv6; + +#[derive(Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr<'a> { + #[cfg(feature = "proto-ipv4")] + Ipv4(icmpv4::Repr<'a>), + #[cfg(feature = "proto-ipv6")] + Ipv6(icmpv6::Repr<'a>), +} +#[cfg(feature = "proto-ipv4")] +impl<'a> From> for Repr<'a> { + fn from(s: icmpv4::Repr<'a>) -> Self { + Repr::Ipv4(s) + } +} +#[cfg(feature = "proto-ipv6")] +impl<'a> From> for Repr<'a> { + fn from(s: icmpv6::Repr<'a>) -> Self { + Repr::Ipv6(s) + } +} diff --git a/vendor/smoltcp/src/wire/icmpv4.rs b/vendor/smoltcp/src/wire/icmpv4.rs new file mode 100644 index 00000000..3b39d801 --- /dev/null +++ b/vendor/smoltcp/src/wire/icmpv4.rs @@ -0,0 +1,704 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::{cmp, fmt}; + +use super::{Error, Result}; +use crate::phy::ChecksumCapabilities; +use crate::wire::ip::checksum; +use crate::wire::{Ipv4Packet, Ipv4Repr}; + +enum_with_unknown! { + /// Internet protocol control message type. + pub enum Message(u8) { + /// Echo reply + EchoReply = 0, + /// Destination unreachable + DstUnreachable = 3, + /// Message redirect + Redirect = 5, + /// Echo request + EchoRequest = 8, + /// Router advertisement + RouterAdvert = 9, + /// Router solicitation + RouterSolicit = 10, + /// Time exceeded + TimeExceeded = 11, + /// Parameter problem + ParamProblem = 12, + /// Timestamp + Timestamp = 13, + /// Timestamp reply + TimestampReply = 14 + } +} + +impl fmt::Display for Message { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Message::EchoReply => write!(f, "echo reply"), + Message::DstUnreachable => write!(f, "destination unreachable"), + Message::Redirect => write!(f, "message redirect"), + Message::EchoRequest => write!(f, "echo request"), + Message::RouterAdvert => write!(f, "router advertisement"), + Message::RouterSolicit => write!(f, "router solicitation"), + Message::TimeExceeded => write!(f, "time exceeded"), + Message::ParamProblem => write!(f, "parameter problem"), + Message::Timestamp => write!(f, "timestamp"), + Message::TimestampReply => write!(f, "timestamp reply"), + Message::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for type "Destination Unreachable". + pub enum DstUnreachable(u8) { + /// Destination network unreachable + NetUnreachable = 0, + /// Destination host unreachable + HostUnreachable = 1, + /// Destination protocol unreachable + ProtoUnreachable = 2, + /// Destination port unreachable + PortUnreachable = 3, + /// Fragmentation required, and DF flag set + FragRequired = 4, + /// Source route failed + SrcRouteFailed = 5, + /// Destination network unknown + DstNetUnknown = 6, + /// Destination host unknown + DstHostUnknown = 7, + /// Source host isolated + SrcHostIsolated = 8, + /// Network administratively prohibited + NetProhibited = 9, + /// Host administratively prohibited + HostProhibited = 10, + /// Network unreachable for ToS + NetUnreachToS = 11, + /// Host unreachable for ToS + HostUnreachToS = 12, + /// Communication administratively prohibited + CommProhibited = 13, + /// Host precedence violation + HostPrecedViol = 14, + /// Precedence cutoff in effect + PrecedCutoff = 15 + } +} + +impl fmt::Display for DstUnreachable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + DstUnreachable::NetUnreachable => write!(f, "destination network unreachable"), + DstUnreachable::HostUnreachable => write!(f, "destination host unreachable"), + DstUnreachable::ProtoUnreachable => write!(f, "destination protocol unreachable"), + DstUnreachable::PortUnreachable => write!(f, "destination port unreachable"), + DstUnreachable::FragRequired => write!(f, "fragmentation required, and DF flag set"), + DstUnreachable::SrcRouteFailed => write!(f, "source route failed"), + DstUnreachable::DstNetUnknown => write!(f, "destination network unknown"), + DstUnreachable::DstHostUnknown => write!(f, "destination host unknown"), + DstUnreachable::SrcHostIsolated => write!(f, "source host isolated"), + DstUnreachable::NetProhibited => write!(f, "network administratively prohibited"), + DstUnreachable::HostProhibited => write!(f, "host administratively prohibited"), + DstUnreachable::NetUnreachToS => write!(f, "network unreachable for ToS"), + DstUnreachable::HostUnreachToS => write!(f, "host unreachable for ToS"), + DstUnreachable::CommProhibited => { + write!(f, "communication administratively prohibited") + } + DstUnreachable::HostPrecedViol => write!(f, "host precedence violation"), + DstUnreachable::PrecedCutoff => write!(f, "precedence cutoff in effect"), + DstUnreachable::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for type "Redirect Message". + pub enum Redirect(u8) { + /// Redirect Datagram for the Network + Net = 0, + /// Redirect Datagram for the Host + Host = 1, + /// Redirect Datagram for the ToS & network + NetToS = 2, + /// Redirect Datagram for the ToS & host + HostToS = 3 + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for type "Time Exceeded". + pub enum TimeExceeded(u8) { + /// TTL expired in transit + TtlExpired = 0, + /// Fragment reassembly time exceeded + FragExpired = 1 + } +} + +impl fmt::Display for TimeExceeded { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + TimeExceeded::TtlExpired => write!(f, "time-to-live exceeded in transit"), + TimeExceeded::FragExpired => write!(f, "fragment reassembly time exceeded"), + TimeExceeded::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for type "Parameter Problem". + pub enum ParamProblem(u8) { + /// Pointer indicates the error + AtPointer = 0, + /// Missing a required option + MissingOption = 1, + /// Bad length + BadLength = 2 + } +} + +/// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + use crate::wire::field::*; + + pub const TYPE: usize = 0; + pub const CODE: usize = 1; + pub const CHECKSUM: Field = 2..4; + + pub const UNUSED: Field = 4..8; + + pub const ECHO_IDENT: Field = 4..6; + pub const ECHO_SEQNO: Field = 6..8; + + pub const HEADER_END: usize = 8; +} + +impl> Packet { + /// Imbue a raw octet buffer with ICMPv4 packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_header_len]. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::HEADER_END { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the message type field. + #[inline] + pub fn msg_type(&self) -> Message { + let data = self.buffer.as_ref(); + Message::from(data[field::TYPE]) + } + + /// Return the message code field. + #[inline] + pub fn msg_code(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::CODE] + } + + /// Return the checksum field. + #[inline] + pub fn checksum(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::CHECKSUM]) + } + + /// Return the identifier field (for echo request and reply packets). + /// + /// # Panics + /// This function may panic if this packet is not an echo request or reply packet. + #[inline] + pub fn echo_ident(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::ECHO_IDENT]) + } + + /// Return the sequence number field (for echo request and reply packets). + /// + /// # Panics + /// This function may panic if this packet is not an echo request or reply packet. + #[inline] + pub fn echo_seq_no(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::ECHO_SEQNO]) + } + + /// Return the header length. + /// The result depends on the value of the message type field. + pub fn header_len(&self) -> usize { + match self.msg_type() { + Message::EchoRequest => field::ECHO_SEQNO.end, + Message::EchoReply => field::ECHO_SEQNO.end, + Message::DstUnreachable => field::UNUSED.end, + _ => field::UNUSED.end, // make a conservative assumption + } + } + + /// Validate the header checksum. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_checksum(&self) -> bool { + if cfg!(fuzzing) { + return true; + } + + let data = self.buffer.as_ref(); + checksum::data(data) == !0 + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the type-specific data. + #[inline] + pub fn data(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + &data[self.header_len()..] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the message type field. + #[inline] + pub fn set_msg_type(&mut self, value: Message) { + let data = self.buffer.as_mut(); + data[field::TYPE] = value.into() + } + + /// Set the message code field. + #[inline] + pub fn set_msg_code(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::CODE] = value + } + + /// Set the checksum field. + #[inline] + pub fn set_checksum(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) + } + + /// Set the identifier field (for echo request and reply packets). + /// + /// # Panics + /// This function may panic if this packet is not an echo request or reply packet. + #[inline] + pub fn set_echo_ident(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value) + } + + /// Set the sequence number field (for echo request and reply packets). + /// + /// # Panics + /// This function may panic if this packet is not an echo request or reply packet. + #[inline] + pub fn set_echo_seq_no(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value) + } + + /// Compute and fill in the header checksum. + pub fn fill_checksum(&mut self) { + self.set_checksum(0); + let checksum = { + let data = self.buffer.as_ref(); + !checksum::data(data) + }; + self.set_checksum(checksum) + } +} + +impl + AsMut<[u8]> + ?Sized> Packet<&mut T> { + /// Return a mutable pointer to the type-specific data. + #[inline] + pub fn data_mut(&mut self) -> &mut [u8] { + let range = self.header_len()..; + let data = self.buffer.as_mut(); + &mut data[range] + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an Internet Control Message Protocol version 4 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Repr<'a> { + EchoRequest { + ident: u16, + seq_no: u16, + data: &'a [u8], + }, + EchoReply { + ident: u16, + seq_no: u16, + data: &'a [u8], + }, + DstUnreachable { + reason: DstUnreachable, + header: Ipv4Repr, + data: &'a [u8], + }, + TimeExceeded { + reason: TimeExceeded, + header: Ipv4Repr, + data: &'a [u8], + }, +} + +impl<'a> Repr<'a> { + /// Parse an Internet Control Message Protocol version 4 packet and return + /// a high-level representation. + pub fn parse( + packet: &Packet<&'a T>, + checksum_caps: &ChecksumCapabilities, + ) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + + // Valid checksum is expected. + if checksum_caps.icmpv4.rx() && !packet.verify_checksum() { + return Err(Error); + } + + match (packet.msg_type(), packet.msg_code()) { + (Message::EchoRequest, 0) => Ok(Repr::EchoRequest { + ident: packet.echo_ident(), + seq_no: packet.echo_seq_no(), + data: packet.data(), + }), + + (Message::EchoReply, 0) => Ok(Repr::EchoReply { + ident: packet.echo_ident(), + seq_no: packet.echo_seq_no(), + data: packet.data(), + }), + + (Message::DstUnreachable, code) => { + let ip_packet = Ipv4Packet::new_checked(packet.data())?; + + let payload = &packet.data()[ip_packet.header_len() as usize..]; + // RFC 792 requires exactly eight bytes to be returned. + // We allow more, since there isn't a reason not to, but require at least eight. + if payload.len() < 8 { + return Err(Error); + } + + Ok(Repr::DstUnreachable { + reason: DstUnreachable::from(code), + header: Ipv4Repr { + src_addr: ip_packet.src_addr(), + dst_addr: ip_packet.dst_addr(), + next_header: ip_packet.next_header(), + payload_len: payload.len(), + hop_limit: ip_packet.hop_limit(), + }, + data: payload, + }) + } + + (Message::TimeExceeded, code) => { + let ip_packet = Ipv4Packet::new_checked(packet.data())?; + + let payload = &packet.data()[ip_packet.header_len() as usize..]; + // RFC 792 requires exactly eight bytes to be returned. + // We allow more, since there isn't a reason not to, but require at least eight. + if payload.len() < 8 { + return Err(Error); + } + + Ok(Repr::TimeExceeded { + reason: TimeExceeded::from(code), + header: Ipv4Repr { + src_addr: ip_packet.src_addr(), + dst_addr: ip_packet.dst_addr(), + next_header: ip_packet.next_header(), + payload_len: payload.len(), + hop_limit: ip_packet.hop_limit(), + }, + data: payload, + }) + } + + _ => Err(Error), + } + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + match self { + &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => { + field::ECHO_SEQNO.end + data.len() + } + &Repr::DstUnreachable { header, data, .. } + | &Repr::TimeExceeded { header, data, .. } => { + field::UNUSED.end + header.buffer_len() + data.len() + } + } + } + + /// Emit a high-level representation into an Internet Control Message Protocol version 4 + /// packet. + pub fn emit(&self, packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + packet.set_msg_code(0); + match *self { + Repr::EchoRequest { + ident, + seq_no, + data, + } => { + packet.set_msg_type(Message::EchoRequest); + packet.set_msg_code(0); + packet.set_echo_ident(ident); + packet.set_echo_seq_no(seq_no); + let data_len = cmp::min(packet.data_mut().len(), data.len()); + packet.data_mut()[..data_len].copy_from_slice(&data[..data_len]) + } + + Repr::EchoReply { + ident, + seq_no, + data, + } => { + packet.set_msg_type(Message::EchoReply); + packet.set_msg_code(0); + packet.set_echo_ident(ident); + packet.set_echo_seq_no(seq_no); + let data_len = cmp::min(packet.data_mut().len(), data.len()); + packet.data_mut()[..data_len].copy_from_slice(&data[..data_len]) + } + + Repr::DstUnreachable { + reason, + header, + data, + } => { + packet.set_msg_type(Message::DstUnreachable); + packet.set_msg_code(reason.into()); + + let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut()); + header.emit(&mut ip_packet, checksum_caps); + let payload = &mut ip_packet.into_inner()[header.buffer_len()..]; + payload.copy_from_slice(data) + } + + Repr::TimeExceeded { + reason, + header, + data, + } => { + packet.set_msg_type(Message::TimeExceeded); + packet.set_msg_code(reason.into()); + + let mut ip_packet = Ipv4Packet::new_unchecked(packet.data_mut()); + header.emit(&mut ip_packet, checksum_caps); + let payload = &mut ip_packet.into_inner()[header.buffer_len()..]; + payload.copy_from_slice(data) + } + } + + if checksum_caps.icmpv4.tx() { + packet.fill_checksum() + } else { + // make sure we get a consistently zeroed checksum, + // since implementations might rely on it + packet.set_checksum(0); + } + } +} + +impl + ?Sized> fmt::Display for Packet<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self, &ChecksumCapabilities::default()) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "ICMPv4 ({err})")?; + write!(f, " type={:?}", self.msg_type())?; + match self.msg_type() { + Message::DstUnreachable => { + write!(f, " code={:?}", DstUnreachable::from(self.msg_code())) + } + Message::TimeExceeded => { + write!(f, " code={:?}", TimeExceeded::from(self.msg_code())) + } + _ => write!(f, " code={}", self.msg_code()), + } + } + } + } +} + +impl<'a> fmt::Display for Repr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::EchoRequest { + ident, + seq_no, + data, + } => write!( + f, + "ICMPv4 echo request id={} seq={} len={}", + ident, + seq_no, + data.len() + ), + Repr::EchoReply { + ident, + seq_no, + data, + } => write!( + f, + "ICMPv4 echo reply id={} seq={} len={}", + ident, + seq_no, + data.len() + ), + Repr::DstUnreachable { reason, .. } => { + write!(f, "ICMPv4 destination unreachable ({reason})") + } + Repr::TimeExceeded { reason, .. } => { + write!(f, "ICMPv4 time exceeded ({reason})") + } + } + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + let packet = match Packet::new_checked(buffer) { + Err(err) => return write!(f, "{indent}({err})"), + Ok(packet) => packet, + }; + write!(f, "{indent}{packet}")?; + + match packet.msg_type() { + Message::DstUnreachable | Message::TimeExceeded => { + indent.increase(f)?; + super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent) + } + _ => Ok(()), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + static ECHO_PACKET_BYTES: [u8; 12] = [ + 0x08, 0x00, 0x8e, 0xfe, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff, + ]; + + static ECHO_DATA_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + + #[test] + fn test_echo_deconstruct() { + let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]); + assert_eq!(packet.msg_type(), Message::EchoRequest); + assert_eq!(packet.msg_code(), 0); + assert_eq!(packet.checksum(), 0x8efe); + assert_eq!(packet.echo_ident(), 0x1234); + assert_eq!(packet.echo_seq_no(), 0xabcd); + assert_eq!(packet.data(), &ECHO_DATA_BYTES[..]); + assert!(packet.verify_checksum()); + } + + #[test] + fn test_echo_construct() { + let mut bytes = vec![0xa5; 12]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_msg_type(Message::EchoRequest); + packet.set_msg_code(0); + packet.set_echo_ident(0x1234); + packet.set_echo_seq_no(0xabcd); + packet.data_mut().copy_from_slice(&ECHO_DATA_BYTES[..]); + packet.fill_checksum(); + assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]); + } + + fn echo_packet_repr() -> Repr<'static> { + Repr::EchoRequest { + ident: 0x1234, + seq_no: 0xabcd, + data: &ECHO_DATA_BYTES, + } + } + + #[test] + fn test_echo_parse() { + let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(repr, echo_packet_repr()); + } + + #[test] + fn test_echo_emit() { + let repr = echo_packet_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet, &ChecksumCapabilities::default()); + assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]); + } + + #[test] + fn test_check_len() { + let bytes = [0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + assert_eq!(Packet::new_checked(&[]), Err(Error)); + assert_eq!(Packet::new_checked(&bytes[..4]), Err(Error)); + assert!(Packet::new_checked(&bytes[..]).is_ok()); + } +} diff --git a/vendor/smoltcp/src/wire/icmpv6.rs b/vendor/smoltcp/src/wire/icmpv6.rs new file mode 100644 index 00000000..3f6c6d58 --- /dev/null +++ b/vendor/smoltcp/src/wire/icmpv6.rs @@ -0,0 +1,1090 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::{cmp, fmt}; + +use super::{Error, Result}; +use crate::phy::ChecksumCapabilities; +use crate::wire::MldRepr; +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +use crate::wire::NdiscRepr; +#[cfg(feature = "proto-rpl")] +use crate::wire::RplRepr; +use crate::wire::ip::checksum; +use crate::wire::{IPV6_HEADER_LEN, IPV6_MIN_MTU}; +use crate::wire::{IpProtocol, Ipv6Address, Ipv6Packet, Ipv6Repr}; + +/// Error packets must not exceed min MTU +const MAX_ERROR_PACKET_LEN: usize = IPV6_MIN_MTU - IPV6_HEADER_LEN; + +enum_with_unknown! { + /// Internet protocol control message type. + pub enum Message(u8) { + /// Destination Unreachable. + DstUnreachable = 0x01, + /// Packet Too Big. + PktTooBig = 0x02, + /// Time Exceeded. + TimeExceeded = 0x03, + /// Parameter Problem. + ParamProblem = 0x04, + /// Echo Request + EchoRequest = 0x80, + /// Echo Reply + EchoReply = 0x81, + /// Multicast Listener Query + MldQuery = 0x82, + /// Router Solicitation + RouterSolicit = 0x85, + /// Router Advertisement + RouterAdvert = 0x86, + /// Neighbor Solicitation + NeighborSolicit = 0x87, + /// Neighbor Advertisement + NeighborAdvert = 0x88, + /// Redirect + Redirect = 0x89, + /// Multicast Listener Report + MldReport = 0x8f, + /// RPL Control Message + RplControl = 0x9b, + } +} + +impl Message { + /// Per [RFC 4443 § 2.1] ICMPv6 message types with the highest order + /// bit set are informational messages while message types without + /// the highest order bit set are error messages. + /// + /// [RFC 4443 § 2.1]: https://tools.ietf.org/html/rfc4443#section-2.1 + pub fn is_error(&self) -> bool { + (u8::from(*self) & 0x80) != 0x80 + } + + /// Return a boolean value indicating if the given message type + /// is an [NDISC] message type. + /// + /// [NDISC]: https://tools.ietf.org/html/rfc4861 + pub const fn is_ndisc(&self) -> bool { + match *self { + Message::RouterSolicit + | Message::RouterAdvert + | Message::NeighborSolicit + | Message::NeighborAdvert + | Message::Redirect => true, + _ => false, + } + } + + /// Return a boolean value indicating if the given message type + /// is an [MLD] message type. + /// + /// [MLD]: https://tools.ietf.org/html/rfc3810 + pub const fn is_mld(&self) -> bool { + match *self { + Message::MldQuery | Message::MldReport => true, + _ => false, + } + } +} + +impl fmt::Display for Message { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Message::DstUnreachable => write!(f, "destination unreachable"), + Message::PktTooBig => write!(f, "packet too big"), + Message::TimeExceeded => write!(f, "time exceeded"), + Message::ParamProblem => write!(f, "parameter problem"), + Message::EchoReply => write!(f, "echo reply"), + Message::EchoRequest => write!(f, "echo request"), + Message::RouterSolicit => write!(f, "router solicitation"), + Message::RouterAdvert => write!(f, "router advertisement"), + Message::NeighborSolicit => write!(f, "neighbor solicitation"), + Message::NeighborAdvert => write!(f, "neighbor advert"), + Message::Redirect => write!(f, "redirect"), + Message::MldQuery => write!(f, "multicast listener query"), + Message::MldReport => write!(f, "multicast listener report"), + Message::RplControl => write!(f, "RPL control message"), + Message::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for type "Destination Unreachable". + pub enum DstUnreachable(u8) { + /// No Route to destination. + NoRoute = 0, + /// Communication with destination administratively prohibited. + AdminProhibit = 1, + /// Beyond scope of source address. + BeyondScope = 2, + /// Address unreachable. + AddrUnreachable = 3, + /// Port unreachable. + PortUnreachable = 4, + /// Source address failed ingress/egress policy. + FailedPolicy = 5, + /// Reject route to destination. + RejectRoute = 6 + } +} + +impl fmt::Display for DstUnreachable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + DstUnreachable::NoRoute => write!(f, "no route to destination"), + DstUnreachable::AdminProhibit => write!( + f, + "communication with destination administratively prohibited" + ), + DstUnreachable::BeyondScope => write!(f, "beyond scope of source address"), + DstUnreachable::AddrUnreachable => write!(f, "address unreachable"), + DstUnreachable::PortUnreachable => write!(f, "port unreachable"), + DstUnreachable::FailedPolicy => { + write!(f, "source address failed ingress/egress policy") + } + DstUnreachable::RejectRoute => write!(f, "reject route to destination"), + DstUnreachable::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for the type "Parameter Problem". + pub enum ParamProblem(u8) { + /// Erroneous header field encountered. + ErroneousHdrField = 0, + /// Unrecognized Next Header type encountered. + UnrecognizedNxtHdr = 1, + /// Unrecognized IPv6 option encountered. + UnrecognizedOption = 2 + } +} + +impl fmt::Display for ParamProblem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ParamProblem::ErroneousHdrField => write!(f, "erroneous header field."), + ParamProblem::UnrecognizedNxtHdr => write!(f, "unrecognized next header type."), + ParamProblem::UnrecognizedOption => write!(f, "unrecognized IPv6 option."), + ParamProblem::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// Internet protocol control message subtype for the type "Time Exceeded". + pub enum TimeExceeded(u8) { + /// Hop limit exceeded in transit. + HopLimitExceeded = 0, + /// Fragment reassembly time exceeded. + FragReassemExceeded = 1 + } +} + +impl fmt::Display for TimeExceeded { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + TimeExceeded::HopLimitExceeded => write!(f, "hop limit exceeded in transit"), + TimeExceeded::FragReassemExceeded => write!(f, "fragment reassembly time exceeded"), + TimeExceeded::Unknown(id) => write!(f, "{id}"), + } + } +} + +/// A read/write wrapper around an Internet Control Message Protocol version 6 packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + pub(super) buffer: T, +} + +// Ranges and constants describing key boundaries in the ICMPv6 header. +pub(super) mod field { + use crate::wire::field::*; + + // ICMPv6: See https://tools.ietf.org/html/rfc4443 + pub const TYPE: usize = 0; + pub const CODE: usize = 1; + pub const CHECKSUM: Field = 2..4; + + pub const UNUSED: Field = 4..8; + pub const MTU: Field = 4..8; + pub const POINTER: Field = 4..8; + pub const ECHO_IDENT: Field = 4..6; + pub const ECHO_SEQNO: Field = 6..8; + + pub const HEADER_END: usize = 8; + + // NDISC: See https://tools.ietf.org/html/rfc4861 + // Router Advertisement message offsets + pub const CUR_HOP_LIMIT: usize = 4; + pub const ROUTER_FLAGS: usize = 5; + pub const ROUTER_LT: Field = 6..8; + pub const REACHABLE_TM: Field = 8..12; + pub const RETRANS_TM: Field = 12..16; + + // Neighbor Solicitation message offsets + pub const TARGET_ADDR: Field = 8..24; + + // Neighbor Advertisement message offsets + pub const NEIGH_FLAGS: usize = 4; + + // Redirected Header message offsets + pub const DEST_ADDR: Field = 24..40; + + // MLD: + // - https://tools.ietf.org/html/rfc3810 + // - https://tools.ietf.org/html/rfc3810 + // Multicast Listener Query message + pub const MAX_RESP_CODE: Field = 4..6; + pub const QUERY_RESV: Field = 6..8; + pub const QUERY_MCAST_ADDR: Field = 8..24; + pub const SQRV: usize = 24; + pub const QQIC: usize = 25; + pub const QUERY_NUM_SRCS: Field = 26..28; + + // Multicast Listener Report Message + pub const RECORD_RESV: Field = 4..6; + pub const NR_MCAST_RCRDS: Field = 6..8; + + // Multicast Address Record Offsets + pub const RECORD_TYPE: usize = 0; + pub const AUX_DATA_LEN: usize = 1; + pub const RECORD_NUM_SRCS: Field = 2..4; + pub const RECORD_MCAST_ADDR: Field = 4..20; +} + +impl> Packet { + /// Imbue a raw octet buffer with ICMPv6 packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + + if len < 4 { + return Err(Error); + } + + match self.msg_type() { + Message::DstUnreachable + | Message::PktTooBig + | Message::TimeExceeded + | Message::ParamProblem + | Message::EchoRequest + | Message::EchoReply + | Message::MldQuery + | Message::RouterSolicit + | Message::RouterAdvert + | Message::NeighborSolicit + | Message::NeighborAdvert + | Message::Redirect + | Message::MldReport => { + if len < field::HEADER_END || len < self.header_len() { + return Err(Error); + } + } + #[cfg(feature = "proto-rpl")] + Message::RplControl => match super::rpl::RplControlMessage::from(self.msg_code()) { + super::rpl::RplControlMessage::DodagInformationSolicitation => { + // TODO(thvdveld): replace magic number + if len < 6 { + return Err(Error); + } + } + super::rpl::RplControlMessage::DodagInformationObject => { + // TODO(thvdveld): replace magic number + if len < 28 { + return Err(Error); + } + } + super::rpl::RplControlMessage::DestinationAdvertisementObject => { + // TODO(thvdveld): replace magic number + if len < 8 || (self.dao_dodag_id_present() && len < 24) { + return Err(Error); + } + } + super::rpl::RplControlMessage::DestinationAdvertisementObjectAck => { + // TODO(thvdveld): replace magic number + if len < 8 || (self.dao_dodag_id_present() && len < 24) { + return Err(Error); + } + } + super::rpl::RplControlMessage::SecureDodagInformationSolicitation + | super::rpl::RplControlMessage::SecureDodagInformationObject + | super::rpl::RplControlMessage::SecureDestinationAdvertisementObject + | super::rpl::RplControlMessage::SecureDestinationAdvertisementObjectAck + | super::rpl::RplControlMessage::ConsistencyCheck => return Err(Error), + super::rpl::RplControlMessage::Unknown(_) => return Err(Error), + }, + #[cfg(not(feature = "proto-rpl"))] + Message::RplControl => return Err(Error), + Message::Unknown(_) => return Err(Error), + } + + Ok(()) + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the message type field. + #[inline] + pub fn msg_type(&self) -> Message { + let data = self.buffer.as_ref(); + Message::from(data[field::TYPE]) + } + + /// Return the message code field. + #[inline] + pub fn msg_code(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::CODE] + } + + /// Return the checksum field. + #[inline] + pub fn checksum(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::CHECKSUM]) + } + + /// Return the identifier field (for echo request and reply packets). + #[inline] + pub fn echo_ident(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::ECHO_IDENT]) + } + + /// Return the sequence number field (for echo request and reply packets). + #[inline] + pub fn echo_seq_no(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::ECHO_SEQNO]) + } + + /// Return the MTU field (for packet too big messages). + #[inline] + pub fn pkt_too_big_mtu(&self) -> u32 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u32(&data[field::MTU]) + } + + /// Return the pointer field (for parameter problem messages). + #[inline] + pub fn param_problem_ptr(&self) -> u32 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u32(&data[field::POINTER]) + } + + /// Return the header length. The result depends on the value of + /// the message type field. + pub fn header_len(&self) -> usize { + match self.msg_type() { + Message::DstUnreachable => field::UNUSED.end, + Message::PktTooBig => field::MTU.end, + Message::TimeExceeded => field::UNUSED.end, + Message::ParamProblem => field::POINTER.end, + Message::EchoRequest => field::ECHO_SEQNO.end, + Message::EchoReply => field::ECHO_SEQNO.end, + Message::RouterSolicit => field::UNUSED.end, + Message::RouterAdvert => field::RETRANS_TM.end, + Message::NeighborSolicit => field::TARGET_ADDR.end, + Message::NeighborAdvert => field::TARGET_ADDR.end, + Message::Redirect => field::DEST_ADDR.end, + Message::MldQuery => field::QUERY_NUM_SRCS.end, + Message::MldReport => field::NR_MCAST_RCRDS.end, + // For packets that are not included in RFC 4443, do not + // include the last 32 bits of the ICMPv6 header in + // `header_bytes`. This must be done so that these bytes + // can be accessed in the `payload`. + _ => field::CHECKSUM.end, + } + } + + /// Validate the header checksum. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_checksum(&self, src_addr: &Ipv6Address, dst_addr: &Ipv6Address) -> bool { + if cfg!(fuzzing) { + return true; + } + + let data = self.buffer.as_ref(); + checksum::combine(&[ + checksum::pseudo_header_v6(src_addr, dst_addr, IpProtocol::Icmpv6, data.len() as u32), + checksum::data(data), + ]) == !0 + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the type-specific data. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + &data[self.header_len()..] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the message type field. + #[inline] + pub fn set_msg_type(&mut self, value: Message) { + let data = self.buffer.as_mut(); + data[field::TYPE] = value.into() + } + + /// Set the message code field. + #[inline] + pub fn set_msg_code(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::CODE] = value + } + + /// Clear any reserved fields in the message header. + /// + /// # Panics + /// This function panics if the message type has not been set. + /// See [set_msg_type]. + /// + /// [set_msg_type]: #method.set_msg_type + #[inline] + pub fn clear_reserved(&mut self) { + match self.msg_type() { + Message::RouterSolicit + | Message::NeighborSolicit + | Message::NeighborAdvert + | Message::Redirect => { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::UNUSED], 0); + } + Message::MldQuery => { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::QUERY_RESV], 0); + data[field::SQRV] &= 0xf; + } + Message::MldReport => { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::RECORD_RESV], 0); + } + ty => panic!("Message type `{ty}` does not have any reserved fields."), + } + } + + #[inline] + pub fn set_checksum(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) + } + + /// Set the identifier field (for echo request and reply packets). + /// + /// # Panics + /// This function may panic if this packet is not an echo request or reply packet. + #[inline] + pub fn set_echo_ident(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value) + } + + /// Set the sequence number field (for echo request and reply packets). + /// + /// # Panics + /// This function may panic if this packet is not an echo request or reply packet. + #[inline] + pub fn set_echo_seq_no(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value) + } + + /// Set the MTU field (for packet too big messages). + /// + /// # Panics + /// This function may panic if this packet is not an packet too big packet. + #[inline] + pub fn set_pkt_too_big_mtu(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::MTU], value) + } + + /// Set the pointer field (for parameter problem messages). + /// + /// # Panics + /// This function may panic if this packet is not a parameter problem message. + #[inline] + pub fn set_param_problem_ptr(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::POINTER], value) + } + + /// Compute and fill in the header checksum. + pub fn fill_checksum(&mut self, src_addr: &Ipv6Address, dst_addr: &Ipv6Address) { + self.set_checksum(0); + let checksum = { + let data = self.buffer.as_ref(); + !checksum::combine(&[ + checksum::pseudo_header_v6( + src_addr, + dst_addr, + IpProtocol::Icmpv6, + data.len() as u32, + ), + checksum::data(data), + ]) + }; + self.set_checksum(checksum) + } + + /// Return a mutable pointer to the type-specific data. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let range = self.header_len()..; + let data = self.buffer.as_mut(); + &mut data[range] + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an Internet Control Message Protocol version 6 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Repr<'a> { + DstUnreachable { + reason: DstUnreachable, + header: Ipv6Repr, + data: &'a [u8], + }, + PktTooBig { + mtu: u32, + header: Ipv6Repr, + data: &'a [u8], + }, + TimeExceeded { + reason: TimeExceeded, + header: Ipv6Repr, + data: &'a [u8], + }, + ParamProblem { + reason: ParamProblem, + pointer: u32, + header: Ipv6Repr, + data: &'a [u8], + }, + EchoRequest { + ident: u16, + seq_no: u16, + data: &'a [u8], + }, + EchoReply { + ident: u16, + seq_no: u16, + data: &'a [u8], + }, + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + Ndisc(NdiscRepr<'a>), + Mld(MldRepr<'a>), + #[cfg(feature = "proto-rpl")] + Rpl(RplRepr<'a>), +} + +impl<'a> Repr<'a> { + /// Parse an Internet Control Message Protocol version 6 packet and return + /// a high-level representation. + pub fn parse( + src_addr: &Ipv6Address, + dst_addr: &Ipv6Address, + packet: &Packet<&'a T>, + checksum_caps: &ChecksumCapabilities, + ) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + + fn create_packet_from_payload<'a, T>(packet: &Packet<&'a T>) -> Result<(&'a [u8], Ipv6Repr)> + where + T: AsRef<[u8]> + ?Sized, + { + // The packet must be truncated to fit the min MTU. Since we don't know the offset of + // the ICMPv6 header in the L2 frame, we should only check whether the payload's IPv6 + // header is present, the rest is allowed to be truncated. + let ip_packet = if packet.payload().len() >= IPV6_HEADER_LEN { + Ipv6Packet::new_unchecked(packet.payload()) + } else { + return Err(Error); + }; + + let payload = &packet.payload()[ip_packet.header_len()..]; + let repr = Ipv6Repr { + src_addr: ip_packet.src_addr(), + dst_addr: ip_packet.dst_addr(), + next_header: ip_packet.next_header(), + payload_len: ip_packet.payload_len().into(), + hop_limit: ip_packet.hop_limit(), + }; + Ok((payload, repr)) + } + // Valid checksum is expected. + if checksum_caps.icmpv6.rx() && !packet.verify_checksum(src_addr, dst_addr) { + return Err(Error); + } + + match (packet.msg_type(), packet.msg_code()) { + (Message::DstUnreachable, code) => { + let (payload, repr) = create_packet_from_payload(packet)?; + Ok(Repr::DstUnreachable { + reason: DstUnreachable::from(code), + header: repr, + data: payload, + }) + } + (Message::PktTooBig, 0) => { + let (payload, repr) = create_packet_from_payload(packet)?; + Ok(Repr::PktTooBig { + mtu: packet.pkt_too_big_mtu(), + header: repr, + data: payload, + }) + } + (Message::TimeExceeded, code) => { + let (payload, repr) = create_packet_from_payload(packet)?; + Ok(Repr::TimeExceeded { + reason: TimeExceeded::from(code), + header: repr, + data: payload, + }) + } + (Message::ParamProblem, code) => { + let (payload, repr) = create_packet_from_payload(packet)?; + Ok(Repr::ParamProblem { + reason: ParamProblem::from(code), + pointer: packet.param_problem_ptr(), + header: repr, + data: payload, + }) + } + (Message::EchoRequest, 0) => Ok(Repr::EchoRequest { + ident: packet.echo_ident(), + seq_no: packet.echo_seq_no(), + data: packet.payload(), + }), + (Message::EchoReply, 0) => Ok(Repr::EchoReply { + ident: packet.echo_ident(), + seq_no: packet.echo_seq_no(), + data: packet.payload(), + }), + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + (msg_type, 0) if msg_type.is_ndisc() => NdiscRepr::parse(packet).map(Repr::Ndisc), + (msg_type, 0) if msg_type.is_mld() => MldRepr::parse(packet).map(Repr::Mld), + #[cfg(feature = "proto-rpl")] + (Message::RplControl, _) => RplRepr::parse(packet).map(Repr::Rpl), + _ => Err(Error), + } + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub fn buffer_len(&self) -> usize { + match self { + &Repr::DstUnreachable { header, data, .. } + | &Repr::PktTooBig { header, data, .. } + | &Repr::TimeExceeded { header, data, .. } + | &Repr::ParamProblem { header, data, .. } => cmp::min( + field::UNUSED.end + header.buffer_len() + data.len(), + MAX_ERROR_PACKET_LEN, + ), + &Repr::EchoRequest { data, .. } | &Repr::EchoReply { data, .. } => { + field::ECHO_SEQNO.end + data.len() + } + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + &Repr::Ndisc(ndisc) => ndisc.buffer_len(), + &Repr::Mld(mld) => mld.buffer_len(), + #[cfg(feature = "proto-rpl")] + Repr::Rpl(rpl) => rpl.buffer_len(), + } + } + + /// Emit a high-level representation into an Internet Control Message Protocol version 6 + /// packet. + pub fn emit( + &self, + src_addr: &Ipv6Address, + dst_addr: &Ipv6Address, + packet: &mut Packet<&mut T>, + checksum_caps: &ChecksumCapabilities, + ) where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + fn emit_contained_packet(packet: &mut Packet<&mut T>, header: Ipv6Repr, data: &[u8]) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + let icmp_header_len = packet.header_len(); + let mut ip_packet = Ipv6Packet::new_unchecked(packet.payload_mut()); + header.emit(&mut ip_packet); + let payload = &mut ip_packet.into_inner()[header.buffer_len()..]; + // FIXME: this should rather be checked at link level, as we can't know in advance how + // much space we have for the packet due to IPv6 options and etc + let payload_len = cmp::min( + data.len(), + MAX_ERROR_PACKET_LEN - icmp_header_len - IPV6_HEADER_LEN, + ); + payload[..payload_len].copy_from_slice(&data[..payload_len]); + } + + match *self { + Repr::DstUnreachable { + reason, + header, + data, + } => { + packet.set_msg_type(Message::DstUnreachable); + packet.set_msg_code(reason.into()); + + emit_contained_packet(packet, header, data); + } + + Repr::PktTooBig { mtu, header, data } => { + packet.set_msg_type(Message::PktTooBig); + packet.set_msg_code(0); + packet.set_pkt_too_big_mtu(mtu); + + emit_contained_packet(packet, header, data); + } + + Repr::TimeExceeded { + reason, + header, + data, + } => { + packet.set_msg_type(Message::TimeExceeded); + packet.set_msg_code(reason.into()); + + emit_contained_packet(packet, header, data); + } + + Repr::ParamProblem { + reason, + pointer, + header, + data, + } => { + packet.set_msg_type(Message::ParamProblem); + packet.set_msg_code(reason.into()); + packet.set_param_problem_ptr(pointer); + + emit_contained_packet(packet, header, data); + } + + Repr::EchoRequest { + ident, + seq_no, + data, + } => { + packet.set_msg_type(Message::EchoRequest); + packet.set_msg_code(0); + packet.set_echo_ident(ident); + packet.set_echo_seq_no(seq_no); + let data_len = cmp::min(packet.payload_mut().len(), data.len()); + packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len]) + } + + Repr::EchoReply { + ident, + seq_no, + data, + } => { + packet.set_msg_type(Message::EchoReply); + packet.set_msg_code(0); + packet.set_echo_ident(ident); + packet.set_echo_seq_no(seq_no); + let data_len = cmp::min(packet.payload_mut().len(), data.len()); + packet.payload_mut()[..data_len].copy_from_slice(&data[..data_len]) + } + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + Repr::Ndisc(ndisc) => ndisc.emit(packet), + + Repr::Mld(mld) => mld.emit(packet), + + #[cfg(feature = "proto-rpl")] + Repr::Rpl(ref rpl) => rpl.emit(packet), + } + + if checksum_caps.icmpv6.tx() { + packet.fill_checksum(src_addr, dst_addr); + } else { + // make sure we get a consistently zeroed checksum, since implementations might rely on it + packet.set_checksum(0); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::wire::{IpProtocol, Ipv6Address, Ipv6Repr}; + + const MOCK_IP_ADDR_1: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const MOCK_IP_ADDR_2: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + + static ECHO_PACKET_BYTES: [u8; 12] = [ + 0x80, 0x00, 0x19, 0xb3, 0x12, 0x34, 0xab, 0xcd, 0xaa, 0x00, 0x00, 0xff, + ]; + + static ECHO_PACKET_PAYLOAD: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + + static PKT_TOO_BIG_BYTES: [u8; 60] = [ + 0x02, 0x00, 0x0f, 0xc9, 0x00, 0x00, 0x05, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, + 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff, + ]; + + static PKT_TOO_BIG_IP_PAYLOAD: [u8; 52] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xbf, 0x00, 0x00, 0x35, 0x00, + 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff, + ]; + + static PKT_TOO_BIG_UDP_PAYLOAD: [u8; 12] = [ + 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff, + ]; + + fn echo_packet_repr() -> Repr<'static> { + Repr::EchoRequest { + ident: 0x1234, + seq_no: 0xabcd, + data: &ECHO_PACKET_PAYLOAD, + } + } + + fn too_big_packet_repr() -> Repr<'static> { + Repr::PktTooBig { + mtu: 1500, + header: Ipv6Repr { + src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2), + next_header: IpProtocol::Udp, + payload_len: 12, + hop_limit: 0x40, + }, + data: &PKT_TOO_BIG_UDP_PAYLOAD, + } + } + + #[test] + fn test_echo_deconstruct() { + let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]); + assert_eq!(packet.msg_type(), Message::EchoRequest); + assert_eq!(packet.msg_code(), 0); + assert_eq!(packet.checksum(), 0x19b3); + assert_eq!(packet.echo_ident(), 0x1234); + assert_eq!(packet.echo_seq_no(), 0xabcd); + assert_eq!(packet.payload(), &ECHO_PACKET_PAYLOAD[..]); + assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2)); + assert!(!packet.msg_type().is_error()); + } + + #[test] + fn test_echo_construct() { + let mut bytes = vec![0xa5; 12]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_msg_type(Message::EchoRequest); + packet.set_msg_code(0); + packet.set_echo_ident(0x1234); + packet.set_echo_seq_no(0xabcd); + packet + .payload_mut() + .copy_from_slice(&ECHO_PACKET_PAYLOAD[..]); + packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2); + assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]); + } + + #[test] + fn test_echo_repr_parse() { + let packet = Packet::new_unchecked(&ECHO_PACKET_BYTES[..]); + let repr = Repr::parse( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &packet, + &ChecksumCapabilities::default(), + ) + .unwrap(); + assert_eq!(repr, echo_packet_repr()); + } + + #[test] + fn test_echo_emit() { + let repr = echo_packet_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &mut packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &ECHO_PACKET_BYTES[..]); + } + + #[test] + fn test_too_big_deconstruct() { + let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]); + assert_eq!(packet.msg_type(), Message::PktTooBig); + assert_eq!(packet.msg_code(), 0); + assert_eq!(packet.checksum(), 0x0fc9); + assert_eq!(packet.pkt_too_big_mtu(), 1500); + assert_eq!(packet.payload(), &PKT_TOO_BIG_IP_PAYLOAD[..]); + assert!(packet.verify_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2)); + assert!(packet.msg_type().is_error()); + } + + #[test] + fn test_too_big_construct() { + let mut bytes = vec![0xa5; 60]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_msg_type(Message::PktTooBig); + packet.set_msg_code(0); + packet.set_pkt_too_big_mtu(1500); + packet + .payload_mut() + .copy_from_slice(&PKT_TOO_BIG_IP_PAYLOAD[..]); + packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2); + assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]); + } + + #[test] + fn test_too_big_repr_parse() { + let packet = Packet::new_unchecked(&PKT_TOO_BIG_BYTES[..]); + let repr = Repr::parse( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &packet, + &ChecksumCapabilities::default(), + ) + .unwrap(); + assert_eq!(repr, too_big_packet_repr()); + } + + #[test] + fn test_too_big_emit() { + let repr = too_big_packet_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &mut packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &PKT_TOO_BIG_BYTES[..]); + } + + #[test] + fn test_buffer_length_is_truncated_to_mtu() { + let repr = Repr::PktTooBig { + mtu: 1280, + header: Ipv6Repr { + src_addr: Ipv6Address::UNSPECIFIED, + dst_addr: Ipv6Address::UNSPECIFIED, + next_header: IpProtocol::Tcp, + hop_limit: 64, + payload_len: 1280, + }, + data: &vec![0; 9999], + }; + assert_eq!(repr.buffer_len(), 1280 - IPV6_HEADER_LEN); + } + + #[test] + fn test_mtu_truncated_payload_roundtrip() { + let ip_packet_repr = Ipv6Repr { + src_addr: Ipv6Address::UNSPECIFIED, + dst_addr: Ipv6Address::UNSPECIFIED, + next_header: IpProtocol::Tcp, + hop_limit: 64, + payload_len: IPV6_MIN_MTU - IPV6_HEADER_LEN, + }; + let mut ip_packet = Ipv6Packet::new_unchecked(vec![0; IPV6_MIN_MTU]); + ip_packet_repr.emit(&mut ip_packet); + + let repr1 = Repr::PktTooBig { + mtu: IPV6_MIN_MTU as u32, + header: ip_packet_repr, + data: &ip_packet.as_ref()[IPV6_HEADER_LEN..], + }; + // this is needed to make sure roundtrip gives the same value + // it is not needed for ensuring the correct bytes get emitted + let repr1 = Repr::PktTooBig { + mtu: IPV6_MIN_MTU as u32, + header: ip_packet_repr, + data: &ip_packet.as_ref()[IPV6_HEADER_LEN..repr1.buffer_len() - field::UNUSED.end], + }; + let mut data = vec![0; MAX_ERROR_PACKET_LEN]; + let mut packet = Packet::new_unchecked(&mut data); + repr1.emit( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &mut packet, + &ChecksumCapabilities::default(), + ); + + let packet = Packet::new_unchecked(&data); + let repr2 = Repr::parse( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &packet, + &ChecksumCapabilities::default(), + ) + .unwrap(); + + assert_eq!(repr1, repr2); + } + + #[test] + fn test_truncated_payload_ipv6_header_parse_fails() { + let repr = too_big_packet_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &mut packet, + &ChecksumCapabilities::default(), + ); + let packet = Packet::new_unchecked(&bytes[..field::HEADER_END + IPV6_HEADER_LEN - 1]); + assert!( + Repr::parse( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &packet, + &ChecksumCapabilities::ignored(), + ) + .is_err() + ); + } +} diff --git a/vendor/smoltcp/src/wire/ieee802154.rs b/vendor/smoltcp/src/wire/ieee802154.rs new file mode 100644 index 00000000..fb51229f --- /dev/null +++ b/vendor/smoltcp/src/wire/ieee802154.rs @@ -0,0 +1,1174 @@ +use core::fmt; + +use byteorder::{ByteOrder, LittleEndian}; + +use super::{Error, Result}; +use crate::wire::Ipv6Address; + +enum_with_unknown! { + /// IEEE 802.15.4 frame type. + pub enum FrameType(u8) { + Beacon = 0b000, + Data = 0b001, + Acknowledgement = 0b010, + MacCommand = 0b011, + Multipurpose = 0b101, + FragmentOrFrak = 0b110, + Extended = 0b111, + } +} + +impl fmt::Display for FrameType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FrameType::Beacon => write!(f, "Beacon"), + FrameType::Data => write!(f, "Data"), + FrameType::Acknowledgement => write!(f, "Ack"), + FrameType::MacCommand => write!(f, "MAC command"), + FrameType::Multipurpose => write!(f, "Multipurpose"), + FrameType::FragmentOrFrak => write!(f, "FragmentOrFrak"), + FrameType::Extended => write!(f, "Extended"), + FrameType::Unknown(id) => write!(f, "0b{id:04b}"), + } + } +} +enum_with_unknown! { + /// IEEE 802.15.4 addressing mode for destination and source addresses. + pub enum AddressingMode(u8) { + Absent = 0b00, + Short = 0b10, + Extended = 0b11, + } +} + +impl AddressingMode { + /// Return the size in octets of the address. + const fn size(&self) -> usize { + match self { + AddressingMode::Absent => 0, + AddressingMode::Short => 2, + AddressingMode::Extended => 8, + AddressingMode::Unknown(_) => 0, // TODO(thvdveld): what do we need to here? + } + } +} + +impl fmt::Display for AddressingMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AddressingMode::Absent => write!(f, "Absent"), + AddressingMode::Short => write!(f, "Short"), + AddressingMode::Extended => write!(f, "Extended"), + AddressingMode::Unknown(id) => write!(f, "0b{id:04b}"), + } + } +} + +/// A IEEE 802.15.4 PAN. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct Pan(pub u16); + +impl Pan { + pub const BROADCAST: Self = Self(0xffff); + + /// Return the PAN ID as bytes. + pub fn as_bytes(&self) -> [u8; 2] { + let mut pan = [0u8; 2]; + LittleEndian::write_u16(&mut pan, self.0); + pan + } +} + +impl fmt::Display for Pan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:0x}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Pan { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} + +/// A IEEE 802.15.4 address. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Address { + Absent, + Short([u8; 2]), + Extended([u8; 8]), +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Address { + fn format(&self, f: defmt::Formatter) { + match self { + Self::Absent => defmt::write!(f, "not-present"), + Self::Short(bytes) => defmt::write!(f, "{:02x}:{:02x}", bytes[0], bytes[1]), + Self::Extended(bytes) => defmt::write!( + f, + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + bytes[0], + bytes[1], + bytes[2], + bytes[3], + bytes[4], + bytes[5], + bytes[6], + bytes[7] + ), + } + } +} + +#[cfg(test)] +impl Default for Address { + fn default() -> Self { + Address::Extended([0u8; 8]) + } +} + +impl Address { + /// The broadcast address. + pub const BROADCAST: Address = Address::Short([0xff; 2]); + + /// Query whether the address is an unicast address. + pub fn is_unicast(&self) -> bool { + !self.is_broadcast() + } + + /// Query whether this address is the broadcast address. + pub fn is_broadcast(&self) -> bool { + *self == Self::BROADCAST + } + + const fn short_from_bytes(a: [u8; 2]) -> Self { + Self::Short(a) + } + + const fn extended_from_bytes(a: [u8; 8]) -> Self { + Self::Extended(a) + } + + pub fn from_bytes(a: &[u8]) -> Self { + if a.len() == 2 { + let mut b = [0u8; 2]; + b.copy_from_slice(a); + Address::Short(b) + } else if a.len() == 8 { + let mut b = [0u8; 8]; + b.copy_from_slice(a); + Address::Extended(b) + } else { + panic!("Not an IEEE802.15.4 address"); + } + } + + pub const fn as_bytes(&self) -> &[u8] { + match self { + Address::Absent => &[], + Address::Short(value) => value, + Address::Extended(value) => value, + } + } + + /// Convert the extended address to an Extended Unique Identifier (EUI-64) + pub fn as_eui_64(&self) -> Option<[u8; 8]> { + match self { + Address::Absent | Address::Short(_) => None, + Address::Extended(value) => { + let mut bytes = [0; 8]; + bytes.copy_from_slice(&value[..]); + + bytes[0] ^= 1 << 1; + + Some(bytes) + } + } + } + + /// Convert an extended address to a link-local IPv6 address using the EUI-64 format from + /// RFC2464. + pub fn as_link_local_address(&self) -> Option { + let mut bytes = [0; 16]; + bytes[0] = 0xfe; + bytes[1] = 0x80; + bytes[8..].copy_from_slice(&self.as_eui_64()?); + + Some(crate::wire::ipv6_from_octets(bytes)) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Absent => write!(f, "not-present"), + Self::Short(bytes) => write!(f, "{:02x}:{:02x}", bytes[0], bytes[1]), + Self::Extended(bytes) => write!( + f, + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7] + ), + } + } +} + +enum_with_unknown! { + /// IEEE 802.15.4 addressing mode for destination and source addresses. + pub enum FrameVersion(u8) { + Ieee802154_2003 = 0b00, + Ieee802154_2006 = 0b01, + Ieee802154 = 0b10, + } +} + +/// A read/write wrapper around an IEEE 802.15.4 frame buffer. +#[derive(Debug, Clone)] +pub struct Frame> { + buffer: T, +} + +mod field { + use crate::wire::field::*; + + pub const FRAMECONTROL: Field = 0..2; + pub const SEQUENCE_NUMBER: usize = 2; + pub const ADDRESSING: Rest = 3..; +} + +macro_rules! fc_bit_field { + ($field:ident, $bit:literal) => { + #[inline] + pub fn $field(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]); + + ((raw >> $bit) & 0b1) == 0b1 + } + }; +} + +macro_rules! set_fc_bit_field { + ($field:ident, $bit:literal) => { + #[inline] + pub fn $field(&mut self, val: bool) { + let data = &mut self.buffer.as_mut()[field::FRAMECONTROL]; + let mut raw = LittleEndian::read_u16(data); + raw |= ((val as u16) << $bit); + + data.copy_from_slice(&raw.to_le_bytes()); + } + }; +} + +impl> Frame { + /// Input a raw octet buffer with Ethernet frame structure. + pub const fn new_unchecked(buffer: T) -> Frame { + Frame { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + + // We don't handle unknown frame versions. + if matches!(packet.frame_version(), FrameVersion::Unknown(_)) { + return Err(Error); + } + + // We don't handle unknown addressing modes. + if matches!(packet.dst_addressing_mode(), AddressingMode::Unknown(_)) + || matches!(packet.src_addressing_mode(), AddressingMode::Unknown(_)) + { + return Err(Error); + } + + // We don't handle absent addressing mode with PAN ID compression for older frame versions. + if matches!( + packet.frame_version(), + FrameVersion::Ieee802154_2003 | FrameVersion::Ieee802154_2006 + ) && packet.pan_id_compression() + && matches!(packet.dst_addressing_mode(), AddressingMode::Absent) + && matches!(packet.src_addressing_mode(), AddressingMode::Absent) + { + return Err(Error); + } + + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + // We need at least 3 bytes + if self.buffer.as_ref().len() < 3 { + return Err(Error); + } + + // We don't handle frames with a payload larger than 127 bytes. + if self.buffer.as_ref().len() > 127 { + return Err(Error); + } + + let mut offset = field::ADDRESSING.start + + if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.addr_present_flags() + { + let mut offset = if dst_pan_id { 2 } else { 0 }; + offset += dst_addr.size(); + offset += if src_pan_id { 2 } else { 0 }; + offset += src_addr.size(); + + if offset > self.buffer.as_ref().len() { + return Err(Error); + } + offset + } else { + 0 + }; + + if self.security_enabled() { + // First check that we can access the security header control bits. + if offset + 1 > self.buffer.as_ref().len() { + return Err(Error); + } + + offset += self.security_header_len(); + } + + if offset > self.buffer.as_ref().len() { + return Err(Error); + } + + Ok(()) + } + + /// Consumes the frame, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the FrameType field. + #[inline] + pub fn frame_type(&self) -> FrameType { + let data = self.buffer.as_ref(); + let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]); + let ft = (raw & 0b111) as u8; + FrameType::from(ft) + } + + fc_bit_field!(security_enabled, 3); + fc_bit_field!(frame_pending, 4); + fc_bit_field!(ack_request, 5); + fc_bit_field!(pan_id_compression, 6); + + fc_bit_field!(sequence_number_suppression, 8); + fc_bit_field!(ie_present, 9); + + /// Return the destination addressing mode. + #[inline] + pub fn dst_addressing_mode(&self) -> AddressingMode { + let data = self.buffer.as_ref(); + let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]); + let am = ((raw >> 10) & 0b11) as u8; + AddressingMode::from(am) + } + + /// Return the frame version. + #[inline] + pub fn frame_version(&self) -> FrameVersion { + let data = self.buffer.as_ref(); + let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]); + let fv = ((raw >> 12) & 0b11) as u8; + FrameVersion::from(fv) + } + + /// Return the source addressing mode. + #[inline] + pub fn src_addressing_mode(&self) -> AddressingMode { + let data = self.buffer.as_ref(); + let raw = LittleEndian::read_u16(&data[field::FRAMECONTROL]); + let am = ((raw >> 14) & 0b11) as u8; + AddressingMode::from(am) + } + + /// Return the sequence number of the frame. + #[inline] + pub fn sequence_number(&self) -> Option { + match self.frame_type() { + FrameType::Beacon + | FrameType::Data + | FrameType::Acknowledgement + | FrameType::MacCommand + | FrameType::Multipurpose => { + let data = self.buffer.as_ref(); + let raw = data[field::SEQUENCE_NUMBER]; + Some(raw) + } + FrameType::Extended | FrameType::FragmentOrFrak | FrameType::Unknown(_) => None, + } + } + + /// Return the addressing fields. + #[inline] + fn addressing_fields(&self) -> Option<&[u8]> { + match self.frame_type() { + FrameType::Beacon + | FrameType::Data + | FrameType::MacCommand + | FrameType::Multipurpose => (), + FrameType::Acknowledgement if self.frame_version() == FrameVersion::Ieee802154 => (), + FrameType::Acknowledgement + | FrameType::Extended + | FrameType::FragmentOrFrak + | FrameType::Unknown(_) => return None, + } + + if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.addr_present_flags() { + let mut offset = if dst_pan_id { 2 } else { 0 }; + offset += dst_addr.size(); + offset += if src_pan_id { 2 } else { 0 }; + offset += src_addr.size(); + + let data = self.buffer.as_ref(); + Some(&data[field::ADDRESSING][..offset]) + } else { + None + } + } + + fn addr_present_flags(&self) -> Option<(bool, AddressingMode, bool, AddressingMode)> { + let dst_addr_mode = self.dst_addressing_mode(); + let src_addr_mode = self.src_addressing_mode(); + let pan_id_compression = self.pan_id_compression(); + + use AddressingMode::*; + match self.frame_version() { + FrameVersion::Ieee802154_2003 | FrameVersion::Ieee802154_2006 => { + match (dst_addr_mode, src_addr_mode) { + (Absent, src) => Some((false, Absent, true, src)), + (dst, Absent) => Some((true, dst, false, Absent)), + + (dst, src) if pan_id_compression => Some((true, dst, false, src)), + (dst, src) if !pan_id_compression => Some((true, dst, true, src)), + _ => None, + } + } + FrameVersion::Ieee802154 => { + Some(match (dst_addr_mode, src_addr_mode, pan_id_compression) { + (Absent, Absent, false) => (false, Absent, false, Absent), + (Absent, Absent, true) => (true, Absent, false, Absent), + (dst, Absent, false) if !matches!(dst, Absent) => (true, dst, false, Absent), + (dst, Absent, true) if !matches!(dst, Absent) => (false, dst, false, Absent), + (Absent, src, false) if !matches!(src, Absent) => (false, Absent, true, src), + (Absent, src, true) if !matches!(src, Absent) => (false, Absent, true, src), + (Extended, Extended, false) => (true, Extended, false, Extended), + (Extended, Extended, true) => (false, Extended, false, Extended), + (Short, Short, false) => (true, Short, true, Short), + (Short, Extended, false) => (true, Short, true, Extended), + (Extended, Short, false) => (true, Extended, true, Short), + (Short, Extended, true) => (true, Short, false, Extended), + (Extended, Short, true) => (true, Extended, false, Short), + (Short, Short, true) => (true, Short, false, Short), + _ => return None, + }) + } + _ => None, + } + } + + /// Return the destination PAN field. + #[inline] + pub fn dst_pan_id(&self) -> Option { + if let Some((true, _, _, _)) = self.addr_present_flags() { + let addressing_fields = self.addressing_fields()?; + Some(Pan(LittleEndian::read_u16(&addressing_fields[..2]))) + } else { + None + } + } + + /// Return the destination address field. + #[inline] + pub fn dst_addr(&self) -> Option

{ + if let Some((dst_pan_id, dst_addr, _, _)) = self.addr_present_flags() { + let addressing_fields = self.addressing_fields()?; + let offset = if dst_pan_id { 2 } else { 0 }; + + match dst_addr { + AddressingMode::Absent => Some(Address::Absent), + AddressingMode::Short => { + let mut raw = [0u8; 2]; + raw.clone_from_slice(&addressing_fields[offset..offset + 2]); + raw.reverse(); + Some(Address::short_from_bytes(raw)) + } + AddressingMode::Extended => { + let mut raw = [0u8; 8]; + raw.clone_from_slice(&addressing_fields[offset..offset + 8]); + raw.reverse(); + Some(Address::extended_from_bytes(raw)) + } + AddressingMode::Unknown(_) => None, + } + } else { + None + } + } + + /// Return the destination PAN field. + #[inline] + pub fn src_pan_id(&self) -> Option { + if let Some((dst_pan_id, dst_addr, true, _)) = self.addr_present_flags() { + let mut offset = if dst_pan_id { 2 } else { 0 }; + offset += dst_addr.size(); + let addressing_fields = self.addressing_fields()?; + Some(Pan(LittleEndian::read_u16( + &addressing_fields[offset..][..2], + ))) + } else { + None + } + } + + /// Return the source address field. + #[inline] + pub fn src_addr(&self) -> Option
{ + if let Some((dst_pan_id, dst_addr, src_pan_id, src_addr)) = self.addr_present_flags() { + let addressing_fields = self.addressing_fields()?; + let mut offset = if dst_pan_id { 2 } else { 0 }; + offset += dst_addr.size(); + offset += if src_pan_id { 2 } else { 0 }; + + match src_addr { + AddressingMode::Absent => Some(Address::Absent), + AddressingMode::Short => { + let mut raw = [0u8; 2]; + raw.clone_from_slice(&addressing_fields[offset..offset + 2]); + raw.reverse(); + Some(Address::short_from_bytes(raw)) + } + AddressingMode::Extended => { + let mut raw = [0u8; 8]; + raw.clone_from_slice(&addressing_fields[offset..offset + 8]); + raw.reverse(); + Some(Address::extended_from_bytes(raw)) + } + AddressingMode::Unknown(_) => None, + } + } else { + None + } + } + + /// Return the index where the auxiliary security header starts. + fn aux_security_header_start(&self) -> usize { + // We start with 3, because 2 bytes for frame control and the sequence number. + let mut index = 3; + index += if let Some(addrs) = self.addressing_fields() { + addrs.len() + } else { + 0 + }; + index + } + + /// Return the size of the security header. + fn security_header_len(&self) -> usize { + let mut size = 1; + size += if self.frame_counter_suppressed() { + 0 + } else { + 4 + }; + size += if let Some(len) = self.key_identifier_length() { + len as usize + } else { + 0 + }; + size + } + + /// Return the index where the payload starts. + fn payload_start(&self) -> usize { + let mut index = self.aux_security_header_start(); + + if self.security_enabled() { + index += self.security_header_len(); + } + + index + } + + /// Return the length of the key identifier field. + fn key_identifier_length(&self) -> Option { + Some(match self.key_identifier_mode() { + 0 => 0, + 1 => 1, + 2 => 5, + 3 => 9, + _ => return None, + }) + } + + /// Return the security level of the auxiliary security header. + pub fn security_level(&self) -> u8 { + let index = self.aux_security_header_start(); + let b = self.buffer.as_ref()[index..][0]; + b & 0b111 + } + + /// Return the key identifier mode used by the auxiliary security header. + pub fn key_identifier_mode(&self) -> u8 { + let index = self.aux_security_header_start(); + let b = self.buffer.as_ref()[index..][0]; + (b >> 3) & 0b11 + } + + /// Return `true` when the frame counter in the security header is suppressed. + pub fn frame_counter_suppressed(&self) -> bool { + let index = self.aux_security_header_start(); + let b = self.buffer.as_ref()[index..][0]; + ((b >> 5) & 0b1) == 0b1 + } + + /// Return the frame counter field. + pub fn frame_counter(&self) -> Option { + if self.frame_counter_suppressed() { + None + } else { + let index = self.aux_security_header_start(); + let b = &self.buffer.as_ref()[index..]; + Some(LittleEndian::read_u32(&b[1..1 + 4])) + } + } + + /// Return the Key Identifier field. + fn key_identifier(&self) -> &[u8] { + let index = self.aux_security_header_start(); + let b = &self.buffer.as_ref()[index..]; + let length = if let Some(len) = self.key_identifier_length() { + len as usize + } else { + 0 + }; + &b[5..][..length] + } + + /// Return the Key Source field. + pub fn key_source(&self) -> Option<&[u8]> { + let ki = self.key_identifier(); + let len = ki.len(); + if len > 1 { Some(&ki[..len - 1]) } else { None } + } + + /// Return the Key Index field. + pub fn key_index(&self) -> Option { + let ki = self.key_identifier(); + let len = ki.len(); + + if len > 0 { Some(ki[len - 1]) } else { None } + } + + /// Return the Message Integrity Code (MIC). + pub fn message_integrity_code(&self) -> Option<&[u8]> { + let mic_len = match self.security_level() { + 0 | 4 => return None, + 1 | 5 => 4, + 2 | 6 => 8, + 3 | 7 => 16, + _ => panic!(), + }; + + let data = &self.buffer.as_ref(); + let len = data.len(); + + Some(&data[len - mic_len..]) + } + + /// Return the MAC header. + pub fn mac_header(&self) -> &[u8] { + let data = &self.buffer.as_ref(); + &data[..self.payload_start()] + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Frame<&'a T> { + /// Return a pointer to the payload. + #[inline] + pub fn payload(&self) -> Option<&'a [u8]> { + match self.frame_type() { + FrameType::Data => { + let index = self.payload_start(); + let data = &self.buffer.as_ref(); + + Some(&data[index..]) + } + _ => None, + } + } +} + +impl + AsMut<[u8]>> Frame { + /// Set the frame type. + #[inline] + pub fn set_frame_type(&mut self, frame_type: FrameType) { + let data = &mut self.buffer.as_mut()[field::FRAMECONTROL]; + let mut raw = LittleEndian::read_u16(data); + + raw = (raw & !(0b111)) | (u8::from(frame_type) as u16 & 0b111); + data.copy_from_slice(&raw.to_le_bytes()); + } + + set_fc_bit_field!(set_security_enabled, 3); + set_fc_bit_field!(set_frame_pending, 4); + set_fc_bit_field!(set_ack_request, 5); + set_fc_bit_field!(set_pan_id_compression, 6); + + /// Set the frame version. + #[inline] + pub fn set_frame_version(&mut self, version: FrameVersion) { + let data = &mut self.buffer.as_mut()[field::FRAMECONTROL]; + let mut raw = LittleEndian::read_u16(data); + + raw = (raw & !(0b11 << 12)) | ((u8::from(version) as u16 & 0b11) << 12); + data.copy_from_slice(&raw.to_le_bytes()); + } + + /// Set the frame sequence number. + #[inline] + pub fn set_sequence_number(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::SEQUENCE_NUMBER] = value; + } + + /// Set the destination PAN ID. + #[inline] + pub fn set_dst_pan_id(&mut self, value: Pan) { + // NOTE the destination addressing mode must be different than Absent. + // This is the reason why we set it to Extended. + self.set_dst_addressing_mode(AddressingMode::Extended); + + let data = self.buffer.as_mut(); + data[field::ADDRESSING][..2].copy_from_slice(&value.as_bytes()); + } + + /// Set the destination address. + #[inline] + pub fn set_dst_addr(&mut self, value: Address) { + match value { + Address::Absent => self.set_dst_addressing_mode(AddressingMode::Absent), + Address::Short(mut value) => { + value.reverse(); + self.set_dst_addressing_mode(AddressingMode::Short); + let data = self.buffer.as_mut(); + data[field::ADDRESSING][2..2 + 2].copy_from_slice(&value); + value.reverse(); + } + Address::Extended(mut value) => { + value.reverse(); + self.set_dst_addressing_mode(AddressingMode::Extended); + let data = &mut self.buffer.as_mut()[field::ADDRESSING]; + data[2..2 + 8].copy_from_slice(&value); + value.reverse(); + } + } + } + + /// Set the destination addressing mode. + #[inline] + fn set_dst_addressing_mode(&mut self, value: AddressingMode) { + let data = &mut self.buffer.as_mut()[field::FRAMECONTROL]; + let mut raw = LittleEndian::read_u16(data); + + raw = (raw & !(0b11 << 10)) | ((u8::from(value) as u16 & 0b11) << 10); + data.copy_from_slice(&raw.to_le_bytes()); + } + + /// Set the source PAN ID. + #[inline] + pub fn set_src_pan_id(&mut self, value: Pan) { + let offset = match self.dst_addressing_mode() { + AddressingMode::Absent => 0, + AddressingMode::Short => 2, + AddressingMode::Extended => 8, + _ => unreachable!(), + } + 2; + + let data = &mut self.buffer.as_mut()[field::ADDRESSING]; + data[offset..offset + 2].copy_from_slice(&value.as_bytes()); + } + + /// Set the source address. + #[inline] + pub fn set_src_addr(&mut self, value: Address) { + let offset = match self.dst_addressing_mode() { + AddressingMode::Absent => 0, + AddressingMode::Short => 2, + AddressingMode::Extended => 8, + _ => unreachable!(), + } + 2; + + let offset = offset + if self.pan_id_compression() { 0 } else { 2 }; + + match value { + Address::Absent => self.set_src_addressing_mode(AddressingMode::Absent), + Address::Short(mut value) => { + value.reverse(); + self.set_src_addressing_mode(AddressingMode::Short); + let data = &mut self.buffer.as_mut()[field::ADDRESSING]; + data[offset..offset + 2].copy_from_slice(&value); + value.reverse(); + } + Address::Extended(mut value) => { + value.reverse(); + self.set_src_addressing_mode(AddressingMode::Extended); + let data = &mut self.buffer.as_mut()[field::ADDRESSING]; + data[offset..offset + 8].copy_from_slice(&value); + value.reverse(); + } + } + } + + /// Set the source addressing mode. + #[inline] + fn set_src_addressing_mode(&mut self, value: AddressingMode) { + let data = &mut self.buffer.as_mut()[field::FRAMECONTROL]; + let mut raw = LittleEndian::read_u16(data); + + raw = (raw & !(0b11 << 14)) | ((u8::from(value) as u16 & 0b11) << 14); + data.copy_from_slice(&raw.to_le_bytes()); + } + + /// Return a mutable pointer to the payload. + #[inline] + pub fn payload_mut(&mut self) -> Option<&mut [u8]> { + match self.frame_type() { + FrameType::Data => { + let index = self.payload_start(); + let data = self.buffer.as_mut(); + Some(&mut data[index..]) + } + _ => None, + } + } +} + +impl> fmt::Display for Frame { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "IEEE802.15.4 frame type={}", self.frame_type())?; + + if let Some(seq) = self.sequence_number() { + write!(f, " seq={:02x}", seq)?; + } + + if let Some(pan) = self.dst_pan_id() { + write!(f, " dst-pan={}", pan)?; + } + + if let Some(pan) = self.src_pan_id() { + write!(f, " src-pan={}", pan)?; + } + + if let Some(addr) = self.dst_addr() { + write!(f, " dst={}", addr)?; + } + + if let Some(addr) = self.src_addr() { + write!(f, " src={}", addr)?; + } + + Ok(()) + } +} + +#[cfg(feature = "defmt")] +impl> defmt::Format for Frame { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "IEEE802.15.4 frame type={}", self.frame_type()); + + if let Some(seq) = self.sequence_number() { + defmt::write!(f, " seq={:02x}", seq); + } + + if let Some(pan) = self.dst_pan_id() { + defmt::write!(f, " dst-pan={}", pan); + } + + if let Some(pan) = self.src_pan_id() { + defmt::write!(f, " src-pan={}", pan); + } + + if let Some(addr) = self.dst_addr() { + defmt::write!(f, " dst={}", addr); + } + + if let Some(addr) = self.src_addr() { + defmt::write!(f, " src={}", addr); + } + } +} + +/// A high-level representation of an IEEE802.15.4 frame. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr { + pub frame_type: FrameType, + pub security_enabled: bool, + pub frame_pending: bool, + pub ack_request: bool, + pub sequence_number: Option, + pub pan_id_compression: bool, + pub frame_version: FrameVersion, + pub dst_pan_id: Option, + pub dst_addr: Option
, + pub src_pan_id: Option, + pub src_addr: Option
, +} + +impl Repr { + /// Parse an IEEE 802.15.4 frame and return a high-level representation. + pub fn parse + ?Sized>(packet: &Frame<&T>) -> Result { + // Ensure the basic accessors will work. + packet.check_len()?; + + Ok(Repr { + frame_type: packet.frame_type(), + security_enabled: packet.security_enabled(), + frame_pending: packet.frame_pending(), + ack_request: packet.ack_request(), + sequence_number: packet.sequence_number(), + pan_id_compression: packet.pan_id_compression(), + frame_version: packet.frame_version(), + dst_pan_id: packet.dst_pan_id(), + dst_addr: packet.dst_addr(), + src_pan_id: packet.src_pan_id(), + src_addr: packet.src_addr(), + }) + } + + /// Return the length of a buffer required to hold a packet with the payload of a given length. + #[inline] + pub const fn buffer_len(&self) -> usize { + 3 + 2 + + match self.dst_addr { + Some(Address::Absent) | None => 0, + Some(Address::Short(_)) => 2, + Some(Address::Extended(_)) => 8, + } + + if !self.pan_id_compression { 2 } else { 0 } + + match self.src_addr { + Some(Address::Absent) | None => 0, + Some(Address::Short(_)) => 2, + Some(Address::Extended(_)) => 8, + } + } + + /// Emit a high-level representation into an IEEE802.15.4 frame. + pub fn emit + AsMut<[u8]>>(&self, frame: &mut Frame) { + frame.set_frame_type(self.frame_type); + frame.set_security_enabled(self.security_enabled); + frame.set_frame_pending(self.frame_pending); + frame.set_ack_request(self.ack_request); + frame.set_pan_id_compression(self.pan_id_compression); + frame.set_frame_version(self.frame_version); + + if let Some(sequence_number) = self.sequence_number { + frame.set_sequence_number(sequence_number); + } + + if let Some(dst_pan_id) = self.dst_pan_id { + frame.set_dst_pan_id(dst_pan_id); + } + if let Some(dst_addr) = self.dst_addr { + frame.set_dst_addr(dst_addr); + } + + if !self.pan_id_compression && self.src_pan_id.is_some() { + frame.set_src_pan_id(self.src_pan_id.unwrap()); + } + + if let Some(src_addr) = self.src_addr { + frame.set_src_addr(src_addr); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_broadcast() { + assert!(Address::BROADCAST.is_broadcast()); + assert!(!Address::BROADCAST.is_unicast()); + } + + #[test] + fn prepare_frame() { + let mut buffer = [0u8; 128]; + + let repr = Repr { + frame_type: FrameType::Data, + security_enabled: false, + frame_pending: false, + ack_request: true, + pan_id_compression: true, + frame_version: FrameVersion::Ieee802154, + sequence_number: Some(1), + dst_pan_id: Some(Pan(0xabcd)), + dst_addr: Some(Address::BROADCAST), + src_pan_id: None, + src_addr: Some(Address::Extended([ + 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00, + ])), + }; + + let buffer_len = repr.buffer_len(); + + let mut frame = Frame::new_unchecked(&mut buffer[..buffer_len]); + repr.emit(&mut frame); + + println!("{frame:2x?}"); + + assert_eq!(frame.frame_type(), FrameType::Data); + assert!(!frame.security_enabled()); + assert!(!frame.frame_pending()); + assert!(frame.ack_request()); + assert!(frame.pan_id_compression()); + assert_eq!(frame.frame_version(), FrameVersion::Ieee802154); + assert_eq!(frame.sequence_number(), Some(1)); + assert_eq!(frame.dst_pan_id(), Some(Pan(0xabcd))); + assert_eq!(frame.dst_addr(), Some(Address::BROADCAST)); + assert_eq!(frame.src_pan_id(), None); + assert_eq!( + frame.src_addr(), + Some(Address::Extended([ + 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00 + ])) + ); + } + + macro_rules! vector_test { + ($name:ident $bytes:expr ; $($test_method:ident -> $expected:expr,)*) => { + #[test] + #[allow(clippy::bool_assert_comparison)] + fn $name() -> Result<()> { + let frame = &$bytes; + let frame = Frame::new_checked(frame)?; + + $( + assert_eq!(frame.$test_method(), $expected, stringify!($test_method)); + )* + + Ok(()) + } + } + } + + vector_test! { + extended_addr + [ + 0b0000_0001, 0b1100_1100, // frame control + 0b0, // seq + 0xcd, 0xab, // pan id + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, // dst addr + 0x03, 0x04, // pan id + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, // src addr + ]; + frame_type -> FrameType::Data, + dst_addr -> Some(Address::Extended([0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00])), + src_addr -> Some(Address::Extended([0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00])), + dst_pan_id -> Some(Pan(0xabcd)), + } + + vector_test! { + short_addr + [ + 0x01, 0x98, // frame control + 0x00, // sequence number + 0x34, 0x12, 0x78, 0x56, // PAN identifier and address of destination + 0x34, 0x12, 0xbc, 0x9a, // PAN identifier and address of source + ]; + frame_type -> FrameType::Data, + security_enabled -> false, + frame_pending -> false, + ack_request -> false, + pan_id_compression -> false, + dst_addressing_mode -> AddressingMode::Short, + frame_version -> FrameVersion::Ieee802154_2006, + src_addressing_mode -> AddressingMode::Short, + dst_pan_id -> Some(Pan(0x1234)), + dst_addr -> Some(Address::Short([0x56, 0x78])), + src_pan_id -> Some(Pan(0x1234)), + src_addr -> Some(Address::Short([0x9a, 0xbc])), + } + + vector_test! { + zolertia_remote + [ + 0x41, 0xd8, // frame control + 0x01, // sequence number + 0xcd, 0xab, // Destination PAN id + 0xff, 0xff, // Short destination address + 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00, // Extended source address + 0x2b, 0x00, 0x00, 0x00, // payload + ]; + frame_type -> FrameType::Data, + security_enabled -> false, + frame_pending -> false, + ack_request -> false, + pan_id_compression -> true, + dst_addressing_mode -> AddressingMode::Short, + frame_version -> FrameVersion::Ieee802154_2006, + src_addressing_mode -> AddressingMode::Extended, + payload -> Some(&[0x2b, 0x00, 0x00, 0x00][..]), + } + + vector_test! { + security + [ + 0x69,0xdc, // frame control + 0x32, // sequence number + 0xcd,0xab, // destination PAN id + 0xbf,0x9b,0x15,0x06,0x00,0x4b,0x12,0x00, // extended destination address + 0xc7,0xd9,0xb5,0x14,0x00,0x4b,0x12,0x00, // extended source address + 0x05, // security control field + 0x31,0x01,0x00,0x00, // frame counter + 0x3e,0xe8,0xfb,0x85,0xe4,0xcc,0xf4,0x48,0x90,0xfe,0x56,0x66,0xf7,0x1c,0x65,0x9e,0xf9, // data + 0x93,0xc8,0x34,0x2e,// MIC + ]; + frame_type -> FrameType::Data, + security_enabled -> true, + frame_pending -> false, + ack_request -> true, + pan_id_compression -> true, + dst_addressing_mode -> AddressingMode::Extended, + frame_version -> FrameVersion::Ieee802154_2006, + src_addressing_mode -> AddressingMode::Extended, + dst_pan_id -> Some(Pan(0xabcd)), + dst_addr -> Some(Address::Extended([0x00,0x12,0x4b,0x00,0x06,0x15,0x9b,0xbf])), + src_pan_id -> None, + src_addr -> Some(Address::Extended([0x00,0x12,0x4b,0x00,0x14,0xb5,0xd9,0xc7])), + security_level -> 5, + key_identifier_mode -> 0, + frame_counter -> Some(305), + key_source -> None, + key_index -> None, + payload -> Some(&[0x3e,0xe8,0xfb,0x85,0xe4,0xcc,0xf4,0x48,0x90,0xfe,0x56,0x66,0xf7,0x1c,0x65,0x9e,0xf9,0x93,0xc8,0x34,0x2e][..]), + message_integrity_code -> Some(&[0x93, 0xC8, 0x34, 0x2E][..]), + mac_header -> &[ + 0x69,0xdc, // frame control + 0x32, // sequence number + 0xcd,0xab, // destination PAN id + 0xbf,0x9b,0x15,0x06,0x00,0x4b,0x12,0x00, // extended destination address + 0xc7,0xd9,0xb5,0x14,0x00,0x4b,0x12,0x00, // extended source address + 0x05, // security control field + 0x31,0x01,0x00,0x00, // frame counter + ][..], + } +} diff --git a/vendor/smoltcp/src/wire/igmp.rs b/vendor/smoltcp/src/wire/igmp.rs new file mode 100644 index 00000000..28ec1e51 --- /dev/null +++ b/vendor/smoltcp/src/wire/igmp.rs @@ -0,0 +1,447 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use crate::time::Duration; +use crate::wire::ip::checksum; + +use crate::wire::Ipv4Address; + +enum_with_unknown! { + /// Internet Group Management Protocol v1/v2 message version/type. + pub enum Message(u8) { + /// Membership Query + MembershipQuery = 0x11, + /// Version 2 Membership Report + MembershipReportV2 = 0x16, + /// Leave Group + LeaveGroup = 0x17, + /// Version 1 Membership Report + MembershipReportV1 = 0x12 + } +} + +/// A read/write wrapper around an Internet Group Management Protocol v1/v2 packet buffer. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + use crate::wire::field::*; + + pub const TYPE: usize = 0; + pub const MAX_RESP_CODE: usize = 1; + pub const CHECKSUM: Field = 2..4; + pub const GROUP_ADDRESS: Field = 4..8; +} + +impl fmt::Display for Message { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Message::MembershipQuery => write!(f, "membership query"), + Message::MembershipReportV2 => write!(f, "version 2 membership report"), + Message::LeaveGroup => write!(f, "leave group"), + Message::MembershipReportV1 => write!(f, "version 1 membership report"), + Message::Unknown(id) => write!(f, "{id}"), + } + } +} + +/// Internet Group Management Protocol v1/v2 defined in [RFC 2236]. +/// +/// [RFC 2236]: https://tools.ietf.org/html/rfc2236 +impl> Packet { + /// Imbue a raw octet buffer with IGMPv2 packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::GROUP_ADDRESS.end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the message type field. + #[inline] + pub fn msg_type(&self) -> Message { + let data = self.buffer.as_ref(); + Message::from(data[field::TYPE]) + } + + /// Return the maximum response time, using the encoding specified in + /// [RFC 3376]: 4.1.1. Max Resp Code. + /// + /// [RFC 3376]: https://tools.ietf.org/html/rfc3376 + #[inline] + pub fn max_resp_code(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::MAX_RESP_CODE] + } + + /// Return the checksum field. + #[inline] + pub fn checksum(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::CHECKSUM]) + } + + /// Return the source address field. + #[inline] + pub fn group_addr(&self) -> Ipv4Address { + let data = self.buffer.as_ref(); + crate::wire::ipv4_from_octets(data[field::GROUP_ADDRESS].try_into().unwrap()) + } + + /// Validate the header checksum. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_checksum(&self) -> bool { + if cfg!(fuzzing) { + return true; + } + + let data = self.buffer.as_ref(); + checksum::data(data) == !0 + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the message type field. + #[inline] + pub fn set_msg_type(&mut self, value: Message) { + let data = self.buffer.as_mut(); + data[field::TYPE] = value.into() + } + + /// Set the maximum response time, using the encoding specified in + /// [RFC 3376]: 4.1.1. Max Resp Code. + #[inline] + pub fn set_max_resp_code(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::MAX_RESP_CODE] = value; + } + + /// Set the checksum field. + #[inline] + pub fn set_checksum(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) + } + + /// Set the group address field + #[inline] + pub fn set_group_address(&mut self, addr: Ipv4Address) { + let data = self.buffer.as_mut(); + data[field::GROUP_ADDRESS].copy_from_slice(&addr.octets()); + } + + /// Compute and fill in the header checksum. + pub fn fill_checksum(&mut self) { + self.set_checksum(0); + let checksum = { + let data = self.buffer.as_ref(); + !checksum::data(data) + }; + self.set_checksum(checksum) + } +} + +/// A high-level representation of an Internet Group Management Protocol v1/v2 header. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr { + MembershipQuery { + max_resp_time: Duration, + group_addr: Ipv4Address, + version: IgmpVersion, + }, + MembershipReport { + group_addr: Ipv4Address, + version: IgmpVersion, + }, + LeaveGroup { + group_addr: Ipv4Address, + }, +} + +/// Type of IGMP membership report version +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum IgmpVersion { + /// IGMPv1 + Version1, + /// IGMPv2 + Version2, +} + +impl Repr { + /// Parse an Internet Group Management Protocol v1/v2 packet and return + /// a high-level representation. + pub fn parse(packet: &Packet<&T>) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + + // Check if the address is 0.0.0.0 or multicast + let addr = packet.group_addr(); + if !addr.is_unspecified() && !addr.is_multicast() { + return Err(Error); + } + + // construct a packet based on the Type field + match packet.msg_type() { + Message::MembershipQuery => { + let max_resp_time = max_resp_code_to_duration(packet.max_resp_code()); + // See RFC 3376: 7.1. Query Version Distinctions + let version = if packet.max_resp_code() == 0 { + IgmpVersion::Version1 + } else { + IgmpVersion::Version2 + }; + Ok(Repr::MembershipQuery { + max_resp_time, + group_addr: addr, + version, + }) + } + Message::MembershipReportV2 => Ok(Repr::MembershipReport { + group_addr: packet.group_addr(), + version: IgmpVersion::Version2, + }), + Message::LeaveGroup => Ok(Repr::LeaveGroup { + group_addr: packet.group_addr(), + }), + Message::MembershipReportV1 => { + // for backwards compatibility with IGMPv1 + Ok(Repr::MembershipReport { + group_addr: packet.group_addr(), + version: IgmpVersion::Version1, + }) + } + _ => Err(Error), + } + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + // always 8 bytes + field::GROUP_ADDRESS.end + } + + /// Emit a high-level representation into an Internet Group Management Protocol v2 packet. + pub fn emit(&self, packet: &mut Packet<&mut T>) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + match *self { + Repr::MembershipQuery { + max_resp_time, + group_addr, + version, + } => { + packet.set_msg_type(Message::MembershipQuery); + match version { + IgmpVersion::Version1 => packet.set_max_resp_code(0), + IgmpVersion::Version2 => { + packet.set_max_resp_code(duration_to_max_resp_code(max_resp_time)) + } + } + packet.set_group_address(group_addr); + } + Repr::MembershipReport { + group_addr, + version, + } => { + match version { + IgmpVersion::Version1 => packet.set_msg_type(Message::MembershipReportV1), + IgmpVersion::Version2 => packet.set_msg_type(Message::MembershipReportV2), + }; + packet.set_max_resp_code(0); + packet.set_group_address(group_addr); + } + Repr::LeaveGroup { group_addr } => { + packet.set_msg_type(Message::LeaveGroup); + packet.set_group_address(group_addr); + } + } + + packet.fill_checksum() + } +} + +fn max_resp_code_to_duration(value: u8) -> Duration { + let value: u64 = value.into(); + let decisecs = if value < 128 { + value + } else { + let mant = value & 0xF; + let exp = (value >> 4) & 0x7; + (mant | 0x10) << (exp + 3) + }; + Duration::from_millis(decisecs * 100) +} + +const fn duration_to_max_resp_code(duration: Duration) -> u8 { + let decisecs = duration.total_millis() / 100; + if decisecs < 128 { + decisecs as u8 + } else if decisecs < 31744 { + let mut mant = decisecs >> 3; + let mut exp = 0u8; + while mant > 0x1F && exp < 0x8 { + mant >>= 1; + exp += 1; + } + 0x80 | (exp << 4) | (mant as u8 & 0xF) + } else { + 0xFF + } +} + +impl + ?Sized> fmt::Display for Packet<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => write!(f, "IGMP ({err})"), + } + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::MembershipQuery { + max_resp_time, + group_addr, + version, + } => write!( + f, + "IGMP membership query max_resp_time={max_resp_time} group_addr={group_addr} version={version:?}" + ), + Repr::MembershipReport { + group_addr, + version, + } => write!( + f, + "IGMP membership report group_addr={group_addr} version={version:?}" + ), + Repr::LeaveGroup { group_addr } => { + write!(f, "IGMP leave group group_addr={group_addr})") + } + } + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + match Packet::new_checked(buffer) { + Err(err) => writeln!(f, "{indent}({err})"), + Ok(packet) => writeln!(f, "{indent}{packet}"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + static LEAVE_PACKET_BYTES: [u8; 8] = [0x17, 0x00, 0x02, 0x69, 0xe0, 0x00, 0x06, 0x96]; + static REPORT_PACKET_BYTES: [u8; 8] = [0x16, 0x00, 0x08, 0xda, 0xe1, 0x00, 0x00, 0x25]; + + #[test] + fn test_leave_group_deconstruct() { + let packet = Packet::new_unchecked(&LEAVE_PACKET_BYTES[..]); + assert_eq!(packet.msg_type(), Message::LeaveGroup); + assert_eq!(packet.max_resp_code(), 0); + assert_eq!(packet.checksum(), 0x269); + assert_eq!( + packet.group_addr(), + crate::wire::ipv4_from_octets([224, 0, 6, 150]) + ); + assert!(packet.verify_checksum()); + } + + #[test] + fn test_report_deconstruct() { + let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]); + assert_eq!(packet.msg_type(), Message::MembershipReportV2); + assert_eq!(packet.max_resp_code(), 0); + assert_eq!(packet.checksum(), 0x08da); + assert_eq!( + packet.group_addr(), + crate::wire::ipv4_from_octets([225, 0, 0, 37]) + ); + assert!(packet.verify_checksum()); + } + + #[test] + fn test_leave_construct() { + let mut bytes = vec![0xa5; 8]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_msg_type(Message::LeaveGroup); + packet.set_max_resp_code(0); + packet.set_group_address(crate::wire::ipv4_from_octets([224, 0, 6, 150])); + packet.fill_checksum(); + assert_eq!(&*packet.into_inner(), &LEAVE_PACKET_BYTES[..]); + } + + #[test] + fn test_report_construct() { + let mut bytes = vec![0xa5; 8]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_msg_type(Message::MembershipReportV2); + packet.set_max_resp_code(0); + packet.set_group_address(crate::wire::ipv4_from_octets([225, 0, 0, 37])); + packet.fill_checksum(); + assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]); + } + + #[test] + fn max_resp_time_to_duration_and_back() { + for i in 0..256usize { + let time1 = i as u8; + let duration = max_resp_code_to_duration(time1); + let time2 = duration_to_max_resp_code(duration); + assert!(time1 == time2); + } + } + + #[test] + fn duration_to_max_resp_time_max() { + for duration in 31744..65536 { + let time = duration_to_max_resp_code(Duration::from_millis(duration * 100)); + assert_eq!(time, 0xFF); + } + } +} diff --git a/vendor/smoltcp/src/wire/ip.rs b/vendor/smoltcp/src/wire/ip.rs new file mode 100644 index 00000000..d7e7e015 --- /dev/null +++ b/vendor/smoltcp/src/wire/ip.rs @@ -0,0 +1,1158 @@ +use core::convert::From; +use core::fmt; + +use super::{Error, Result}; +use crate::phy::ChecksumCapabilities; +#[cfg(feature = "proto-ipv4")] +use crate::wire::{Ipv4Address, Ipv4AddressExt, Ipv4Cidr, Ipv4Packet, Ipv4Repr}; +#[cfg(feature = "proto-ipv6")] +use crate::wire::{Ipv6Address, Ipv6AddressExt, Ipv6Cidr, Ipv6Packet, Ipv6Repr}; + +/// Internet protocol version. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Version { + #[cfg(feature = "proto-ipv4")] + Ipv4, + #[cfg(feature = "proto-ipv6")] + Ipv6, +} + +impl Version { + /// Return the version of an IP packet stored in the provided buffer. + /// + /// This function never returns `Ok(IpVersion::Unspecified)`; instead, + /// unknown versions result in `Err(Error)`. + pub const fn of_packet(data: &[u8]) -> Result { + match data[0] >> 4 { + #[cfg(feature = "proto-ipv4")] + 4 => Ok(Version::Ipv4), + #[cfg(feature = "proto-ipv6")] + 6 => Ok(Version::Ipv6), + _ => Err(Error), + } + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "proto-ipv4")] + Version::Ipv4 => write!(f, "IPv4"), + #[cfg(feature = "proto-ipv6")] + Version::Ipv6 => write!(f, "IPv6"), + } + } +} + +enum_with_unknown! { + /// IP datagram encapsulated protocol. + pub enum Protocol(u8) { + HopByHop = 0x00, + Icmp = 0x01, + Igmp = 0x02, + Tcp = 0x06, + Udp = 0x11, + Ipv6Route = 0x2b, + Ipv6Frag = 0x2c, + IpSecEsp = 0x32, + IpSecAh = 0x33, + Icmpv6 = 0x3a, + Ipv6NoNxt = 0x3b, + Ipv6Opts = 0x3c + } +} + +impl fmt::Display for Protocol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Protocol::HopByHop => write!(f, "Hop-by-Hop"), + Protocol::Icmp => write!(f, "ICMP"), + Protocol::Igmp => write!(f, "IGMP"), + Protocol::Tcp => write!(f, "TCP"), + Protocol::Udp => write!(f, "UDP"), + Protocol::Ipv6Route => write!(f, "IPv6-Route"), + Protocol::Ipv6Frag => write!(f, "IPv6-Frag"), + Protocol::IpSecEsp => write!(f, "IPsec-ESP"), + Protocol::IpSecAh => write!(f, "IPsec-AH"), + Protocol::Icmpv6 => write!(f, "ICMPv6"), + Protocol::Ipv6NoNxt => write!(f, "IPv6-NoNxt"), + Protocol::Ipv6Opts => write!(f, "IPv6-Opts"), + Protocol::Unknown(id) => write!(f, "0x{id:02x}"), + } + } +} + +/// An internetworking address. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Address { + /// An IPv4 address. + #[cfg(feature = "proto-ipv4")] + Ipv4(Ipv4Address), + /// An IPv6 address. + #[cfg(feature = "proto-ipv6")] + Ipv6(Ipv6Address), +} + +impl Address { + /// Create an address wrapping an IPv4 address with the given octets. + #[cfg(feature = "proto-ipv4")] + pub const fn v4(a0: u8, a1: u8, a2: u8, a3: u8) -> Address { + Address::Ipv4(Ipv4Address::new(a0, a1, a2, a3)) + } + + /// Create an address wrapping an IPv6 address with the given octets. + #[cfg(feature = "proto-ipv6")] + #[allow(clippy::too_many_arguments)] + pub const fn v6( + a0: u16, + a1: u16, + a2: u16, + a3: u16, + a4: u16, + a5: u16, + a6: u16, + a7: u16, + ) -> Address { + Address::Ipv6(Ipv6Address::new(a0, a1, a2, a3, a4, a5, a6, a7)) + } + + /// Return the protocol version. + pub const fn version(&self) -> Version { + match self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(_) => Version::Ipv4, + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(_) => Version::Ipv6, + } + } + + /// Query whether the address is a valid unicast address. + pub fn is_unicast(&self) -> bool { + match self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => addr.x_is_unicast(), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => addr.x_is_unicast(), + } + } + + /// Query whether the address is a valid multicast address. + pub const fn is_multicast(&self) -> bool { + match self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => addr.is_multicast(), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => addr.is_multicast(), + } + } + + /// Query whether the address is the broadcast address. + pub fn is_broadcast(&self) -> bool { + match self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => addr.is_broadcast(), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(_) => false, + } + } + + /// Query whether the address falls into the "unspecified" range. + pub fn is_unspecified(&self) -> bool { + match self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => addr.is_unspecified(), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => addr.is_unspecified(), + } + } + + /// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`, + /// where `prefix_len` is the number of leading zeroes. Return `None` otherwise. + pub fn prefix_len(&self) -> Option { + match self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => addr.prefix_len(), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => addr.prefix_len(), + } + } +} + +#[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] +impl From<::core::net::IpAddr> for Address { + fn from(x: ::core::net::IpAddr) -> Address { + match x { + ::core::net::IpAddr::V4(ipv4) => Address::Ipv4(ipv4), + ::core::net::IpAddr::V6(ipv6) => Address::Ipv6(ipv6), + } + } +} + +impl From
for ::core::net::IpAddr { + fn from(x: Address) -> ::core::net::IpAddr { + match x { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(ipv4) => ::core::net::IpAddr::V4(ipv4), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(ipv6) => ::core::net::IpAddr::V6(ipv6), + } + } +} + +#[cfg(feature = "proto-ipv4")] +impl From for Address { + fn from(ipv4: Ipv4Address) -> Address { + Address::Ipv4(ipv4) + } +} + +#[cfg(feature = "proto-ipv6")] +impl From for Address { + fn from(addr: Ipv6Address) -> Self { + Address::Ipv6(addr) + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => write!(f, "{addr}"), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => write!(f, "{addr}"), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Address { + fn format(&self, f: defmt::Formatter) { + match self { + #[cfg(feature = "proto-ipv4")] + &Address::Ipv4(addr) => defmt::write!(f, "{:?}", addr), + #[cfg(feature = "proto-ipv6")] + &Address::Ipv6(addr) => defmt::write!(f, "{:?}", addr), + } + } +} + +/// A specification of a CIDR block, containing an address and a variable-length +/// subnet masking prefix length. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum Cidr { + #[cfg(feature = "proto-ipv4")] + Ipv4(Ipv4Cidr), + #[cfg(feature = "proto-ipv6")] + Ipv6(Ipv6Cidr), +} + +impl Cidr { + /// Create a CIDR block from the given address and prefix length. + /// + /// # Panics + /// This function panics if the given prefix length is invalid for the given address. + pub const fn new(addr: Address, prefix_len: u8) -> Cidr { + match addr { + #[cfg(feature = "proto-ipv4")] + Address::Ipv4(addr) => Cidr::Ipv4(Ipv4Cidr::new(addr, prefix_len)), + #[cfg(feature = "proto-ipv6")] + Address::Ipv6(addr) => Cidr::Ipv6(Ipv6Cidr::new(addr, prefix_len)), + } + } + + /// Return the IP address of this CIDR block. + pub const fn address(&self) -> Address { + match *self { + #[cfg(feature = "proto-ipv4")] + Cidr::Ipv4(cidr) => Address::Ipv4(cidr.address()), + #[cfg(feature = "proto-ipv6")] + Cidr::Ipv6(cidr) => Address::Ipv6(cidr.address()), + } + } + + /// Return the prefix length of this CIDR block. + pub const fn prefix_len(&self) -> u8 { + match *self { + #[cfg(feature = "proto-ipv4")] + Cidr::Ipv4(cidr) => cidr.prefix_len(), + #[cfg(feature = "proto-ipv6")] + Cidr::Ipv6(cidr) => cidr.prefix_len(), + } + } + + /// Query whether the subnetwork described by this CIDR block contains + /// the given address. + pub fn contains_addr(&self, addr: &Address) -> bool { + match (self, addr) { + #[cfg(feature = "proto-ipv4")] + (Cidr::Ipv4(cidr), Address::Ipv4(addr)) => cidr.contains_addr(addr), + #[cfg(feature = "proto-ipv6")] + (Cidr::Ipv6(cidr), Address::Ipv6(addr)) => cidr.contains_addr(addr), + #[allow(unreachable_patterns)] + _ => false, + } + } + + /// Query whether the subnetwork described by this CIDR block contains + /// the subnetwork described by the given CIDR block. + pub fn contains_subnet(&self, subnet: &Cidr) -> bool { + match (self, subnet) { + #[cfg(feature = "proto-ipv4")] + (Cidr::Ipv4(cidr), Cidr::Ipv4(other)) => cidr.contains_subnet(other), + #[cfg(feature = "proto-ipv6")] + (Cidr::Ipv6(cidr), Cidr::Ipv6(other)) => cidr.contains_subnet(other), + #[allow(unreachable_patterns)] + _ => false, + } + } +} + +#[cfg(feature = "proto-ipv4")] +impl From for Cidr { + fn from(addr: Ipv4Cidr) -> Self { + Cidr::Ipv4(addr) + } +} + +#[cfg(feature = "proto-ipv6")] +impl From for Cidr { + fn from(addr: Ipv6Cidr) -> Self { + Cidr::Ipv6(addr) + } +} + +impl fmt::Display for Cidr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + #[cfg(feature = "proto-ipv4")] + Cidr::Ipv4(cidr) => write!(f, "{cidr}"), + #[cfg(feature = "proto-ipv6")] + Cidr::Ipv6(cidr) => write!(f, "{cidr}"), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Cidr { + fn format(&self, f: defmt::Formatter) { + match self { + #[cfg(feature = "proto-ipv4")] + &Cidr::Ipv4(cidr) => defmt::write!(f, "{:?}", cidr), + #[cfg(feature = "proto-ipv6")] + &Cidr::Ipv6(cidr) => defmt::write!(f, "{:?}", cidr), + } + } +} + +/// An internet endpoint address. +/// +/// `Endpoint` always fully specifies both the address and the port. +/// +/// See also ['ListenEndpoint'], which allows not specifying the address +/// in order to listen on a given port on any address. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct Endpoint { + pub addr: Address, + pub port: u16, +} + +impl Endpoint { + /// Create an endpoint address from given address and port. + pub const fn new(addr: Address, port: u16) -> Endpoint { + Endpoint { addr, port } + } +} + +#[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] +impl From<::core::net::SocketAddr> for Endpoint { + fn from(x: ::core::net::SocketAddr) -> Endpoint { + Endpoint { + addr: x.ip().into(), + port: x.port(), + } + } +} + +#[cfg(feature = "proto-ipv4")] +impl From<::core::net::SocketAddrV4> for Endpoint { + fn from(x: ::core::net::SocketAddrV4) -> Endpoint { + Endpoint { + addr: (*x.ip()).into(), + port: x.port(), + } + } +} + +#[cfg(feature = "proto-ipv6")] +impl From<::core::net::SocketAddrV6> for Endpoint { + fn from(x: ::core::net::SocketAddrV6) -> Endpoint { + Endpoint { + addr: (*x.ip()).into(), + port: x.port(), + } + } +} + +impl From for ::core::net::SocketAddr { + fn from(x: Endpoint) -> ::core::net::SocketAddr { + ::core::net::SocketAddr::new(x.addr.into(), x.port) + } +} + +impl fmt::Display for Endpoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.addr, self.port) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Endpoint { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{:?}:{=u16}", self.addr, self.port); + } +} + +impl> From<(T, u16)> for Endpoint { + fn from((addr, port): (T, u16)) -> Endpoint { + Endpoint { + addr: addr.into(), + port, + } + } +} + +/// An internet endpoint address for listening. +/// +/// In contrast with [`Endpoint`], `ListenEndpoint` allows not specifying the address, +/// in order to listen on a given port at all our addresses. +/// +/// An endpoint can be constructed from a port, in which case the address is unspecified. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct ListenEndpoint { + pub addr: Option
, + pub port: u16, +} + +impl ListenEndpoint { + /// Query whether the endpoint has a specified address and port. + pub const fn is_specified(&self) -> bool { + self.addr.is_some() && self.port != 0 + } +} + +#[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] +impl From<::core::net::SocketAddr> for ListenEndpoint { + fn from(x: ::core::net::SocketAddr) -> ListenEndpoint { + ListenEndpoint { + addr: Some(x.ip().into()), + port: x.port(), + } + } +} + +#[cfg(feature = "proto-ipv4")] +impl From<::core::net::SocketAddrV4> for ListenEndpoint { + fn from(x: ::core::net::SocketAddrV4) -> ListenEndpoint { + ListenEndpoint { + addr: Some((*x.ip()).into()), + port: x.port(), + } + } +} + +#[cfg(feature = "proto-ipv6")] +impl From<::core::net::SocketAddrV6> for ListenEndpoint { + fn from(x: ::core::net::SocketAddrV6) -> ListenEndpoint { + ListenEndpoint { + addr: Some((*x.ip()).into()), + port: x.port(), + } + } +} + +impl fmt::Display for ListenEndpoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(addr) = self.addr { + write!(f, "{}:{}", addr, self.port) + } else { + write!(f, "*:{}", self.port) + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for ListenEndpoint { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{:?}:{=u16}", self.addr, self.port); + } +} + +impl From for ListenEndpoint { + fn from(port: u16) -> ListenEndpoint { + ListenEndpoint { addr: None, port } + } +} + +impl From for ListenEndpoint { + fn from(endpoint: Endpoint) -> ListenEndpoint { + ListenEndpoint { + addr: Some(endpoint.addr), + port: endpoint.port, + } + } +} + +impl> From<(T, u16)> for ListenEndpoint { + fn from((addr, port): (T, u16)) -> ListenEndpoint { + ListenEndpoint { + addr: Some(addr.into()), + port, + } + } +} + +/// An IP packet representation. +/// +/// This enum abstracts the various versions of IP packets. It either contains an IPv4 +/// or IPv6 concrete high-level representation. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr { + #[cfg(feature = "proto-ipv4")] + Ipv4(Ipv4Repr), + #[cfg(feature = "proto-ipv6")] + Ipv6(Ipv6Repr), +} + +#[cfg(feature = "proto-ipv4")] +impl From for Repr { + fn from(repr: Ipv4Repr) -> Repr { + Repr::Ipv4(repr) + } +} + +#[cfg(feature = "proto-ipv6")] +impl From for Repr { + fn from(repr: Ipv6Repr) -> Repr { + Repr::Ipv6(repr) + } +} + +/// A read/write wrapper around a generic Internet Protocol packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + use crate::wire::field::*; + // 4-bit version number + pub const VER: Field = 0..1; +} + +impl> Packet { + /// Create a raw octet buffer with an IP packet structure. This packet structure can be either + /// IPv4 or Ipv6 + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that reading the version field of the buffer will not panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + // Both IPv4 and IPv6 headers contain Internet Protocol version in the upper nibble of the + // first packet byte + if self.buffer.as_ref().len() < field::VER.end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer + pub fn into_inner(self) -> T { + self.buffer + } + + /// Returns the version field. + pub fn version(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::VER.start] >> 4 + } +} + +impl Repr { + /// Create a new IpRepr, choosing the right IP version for the src/dst addrs. + /// + /// # Panics + /// + /// Panics if `src_addr` and `dst_addr` are different IP version. + pub fn new( + src_addr: Address, + dst_addr: Address, + next_header: Protocol, + payload_len: usize, + hop_limit: u8, + ) -> Self { + match (src_addr, dst_addr) { + #[cfg(feature = "proto-ipv4")] + (Address::Ipv4(src_addr), Address::Ipv4(dst_addr)) => Self::Ipv4(Ipv4Repr { + src_addr, + dst_addr, + next_header, + payload_len, + hop_limit, + }), + #[cfg(feature = "proto-ipv6")] + (Address::Ipv6(src_addr), Address::Ipv6(dst_addr)) => Self::Ipv6(Ipv6Repr { + src_addr, + dst_addr, + next_header, + payload_len, + hop_limit, + }), + #[allow(unreachable_patterns)] + _ => panic!("IP version mismatch: src={src_addr:?} dst={dst_addr:?}"), + } + } + + /// Parse an Internet Protocol packet and return an [IpRepr] containing either an Internet + /// Protocol version 4 or Internet Protocol version 6 packet. Delegates the parsing to the + /// specific Internet Protocol parsing function. Includes [ChecksumCapabilities] to handle + /// Internet Protocol version 4 parsing. + /// Returns `Err(Error)` if the packet does not include a valid IPv4 or IPv6 packet, or if the + /// specific Internet Protocol version feature is not enabled for the supplied packet + pub fn parse + ?Sized>( + packet: &Packet<&T>, + checksum_caps: &ChecksumCapabilities, + ) -> Result { + packet.check_len()?; + match packet.version() { + #[cfg(feature = "proto-ipv4")] + 4 => { + let packet = Ipv4Packet::new_checked(packet.buffer)?; + let ipv4_repr = Ipv4Repr::parse(&packet, checksum_caps)?; + Ok(Repr::Ipv4(ipv4_repr)) + } + #[cfg(feature = "proto-ipv6")] + 6 => { + let packet = Ipv6Packet::new_checked(packet.buffer)?; + let ipv6_repr = Ipv6Repr::parse(&packet)?; + Ok(Repr::Ipv6(ipv6_repr)) + } + _ => Err(Error), + } + } + + /// Return the protocol version. + pub const fn version(&self) -> Version { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(_) => Version::Ipv4, + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(_) => Version::Ipv6, + } + } + + /// Return the source address. + pub const fn src_addr(&self) -> Address { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(repr) => Address::Ipv4(repr.src_addr), + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(repr) => Address::Ipv6(repr.src_addr), + } + } + + /// Return the destination address. + pub const fn dst_addr(&self) -> Address { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(repr) => Address::Ipv4(repr.dst_addr), + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(repr) => Address::Ipv6(repr.dst_addr), + } + } + + /// Return the next header (protocol). + pub const fn next_header(&self) -> Protocol { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(repr) => repr.next_header, + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(repr) => repr.next_header, + } + } + + /// Return the payload length. + pub const fn payload_len(&self) -> usize { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(repr) => repr.payload_len, + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(repr) => repr.payload_len, + } + } + + /// Set the payload length. + pub fn set_payload_len(&mut self, length: usize) { + match self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(Ipv4Repr { payload_len, .. }) => *payload_len = length, + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(Ipv6Repr { payload_len, .. }) => *payload_len = length, + } + } + + /// Return the TTL value. + pub const fn hop_limit(&self) -> u8 { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(Ipv4Repr { hop_limit, .. }) => hop_limit, + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(Ipv6Repr { hop_limit, .. }) => hop_limit, + } + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn header_len(&self) -> usize { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(repr) => repr.buffer_len(), + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(repr) => repr.buffer_len(), + } + } + + /// Emit this high-level representation into a buffer. + pub fn emit + AsMut<[u8]>>( + &self, + buffer: T, + _checksum_caps: &ChecksumCapabilities, + ) { + match *self { + #[cfg(feature = "proto-ipv4")] + Repr::Ipv4(repr) => repr.emit(&mut Ipv4Packet::new_unchecked(buffer), _checksum_caps), + #[cfg(feature = "proto-ipv6")] + Repr::Ipv6(repr) => repr.emit(&mut Ipv6Packet::new_unchecked(buffer)), + } + } + + /// Return the total length of a packet that will be emitted from this + /// high-level representation. + /// + /// This is the same as `repr.buffer_len() + repr.payload_len()`. + pub const fn buffer_len(&self) -> usize { + self.header_len() + self.payload_len() + } +} + +pub mod checksum { + use byteorder::{ByteOrder, NetworkEndian}; + + use super::*; + + const fn propagate_carries(word: u32) -> u16 { + let sum = (word >> 16) + (word & 0xffff); + ((sum >> 16) as u16) + (sum as u16) + } + + /// Compute an RFC 1071 compliant checksum (without the final complement). + pub fn data(mut data: &[u8]) -> u16 { + let mut accum = 0; + + // For each 32-byte chunk... + const CHUNK_SIZE: usize = 32; + while data.len() >= CHUNK_SIZE { + let mut d = &data[..CHUNK_SIZE]; + // ... take by 2 bytes and sum them. + while d.len() >= 2 { + accum += NetworkEndian::read_u16(d) as u32; + d = &d[2..]; + } + + data = &data[CHUNK_SIZE..]; + } + + // Sum the rest that does not fit the last 32-byte chunk, + // taking by 2 bytes. + while data.len() >= 2 { + accum += NetworkEndian::read_u16(data) as u32; + data = &data[2..]; + } + + // Add the last remaining odd byte, if any. + if let Some(&value) = data.first() { + accum += (value as u32) << 8; + } + + propagate_carries(accum) + } + + /// Combine several RFC 1071 compliant checksums. + pub fn combine(checksums: &[u16]) -> u16 { + let mut accum: u32 = 0; + for &word in checksums { + accum += word as u32; + } + propagate_carries(accum) + } + + #[cfg(feature = "proto-ipv4")] + pub fn pseudo_header_v4( + src_addr: &Ipv4Address, + dst_addr: &Ipv4Address, + next_header: Protocol, + length: u32, + ) -> u16 { + let mut proto_len = [0u8; 4]; + proto_len[1] = next_header.into(); + NetworkEndian::write_u16(&mut proto_len[2..4], length as u16); + + combine(&[ + data(&src_addr.octets()), + data(&dst_addr.octets()), + data(&proto_len[..]), + ]) + } + + #[cfg(feature = "proto-ipv6")] + pub fn pseudo_header_v6( + src_addr: &Ipv6Address, + dst_addr: &Ipv6Address, + next_header: Protocol, + length: u32, + ) -> u16 { + let mut proto_len = [0u8; 4]; + proto_len[1] = next_header.into(); + NetworkEndian::write_u16(&mut proto_len[2..4], length as u16); + + combine(&[ + data(&src_addr.octets()), + data(&dst_addr.octets()), + data(&proto_len[..]), + ]) + } + + pub fn pseudo_header( + src_addr: &Address, + dst_addr: &Address, + next_header: Protocol, + length: u32, + ) -> u16 { + match (src_addr, dst_addr) { + #[cfg(feature = "proto-ipv4")] + (Address::Ipv4(src_addr), Address::Ipv4(dst_addr)) => { + pseudo_header_v4(src_addr, dst_addr, next_header, length) + } + #[cfg(feature = "proto-ipv6")] + (Address::Ipv6(src_addr), Address::Ipv6(dst_addr)) => { + pseudo_header_v6(src_addr, dst_addr, next_header, length) + } + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + + // We use this in pretty printer implementations. + pub(crate) fn format_checksum( + f: &mut fmt::Formatter, + correct: bool, + partially_correct: bool, + ) -> fmt::Result { + if !correct { + if partially_correct { + write!(f, " (partial checksum correct)") + } else { + write!(f, " (checksum incorrect)") + } + } else { + Ok(()) + } + } +} + +use crate::wire::pretty_print::PrettyIndent; + +pub fn pretty_print_ip_payload>( + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ip_repr: T, + payload: &[u8], +) -> fmt::Result { + #[cfg(feature = "proto-ipv4")] + use super::pretty_print::PrettyPrint; + #[cfg(feature = "proto-ipv4")] + use crate::wire::Icmpv4Packet; + use crate::wire::ip::checksum::format_checksum; + use crate::wire::{TcpPacket, TcpRepr, UdpPacket, UdpRepr}; + + let checksum_caps = ChecksumCapabilities::ignored(); + let repr = ip_repr.into(); + match repr.next_header() { + #[cfg(feature = "proto-ipv4")] + Protocol::Icmp => { + indent.increase(f)?; + Icmpv4Packet::<&[u8]>::pretty_print(&payload, f, indent) + } + Protocol::Udp => { + indent.increase(f)?; + match UdpPacket::<&[u8]>::new_checked(payload) { + Err(err) => write!(f, "{indent}({err})"), + Ok(udp_packet) => { + match UdpRepr::parse( + &udp_packet, + &repr.src_addr(), + &repr.dst_addr(), + &checksum_caps, + ) { + Err(err) => write!(f, "{indent}{udp_packet} ({err})"), + Ok(udp_repr) => { + write!( + f, + "{}{} len={}", + indent, + udp_repr, + udp_packet.payload().len() + )?; + let valid = + udp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr()); + let partially_valid = udp_packet + .verify_partial_checksum(&repr.src_addr(), &repr.dst_addr()); + + format_checksum(f, valid, partially_valid) + } + } + } + } + } + Protocol::Tcp => { + indent.increase(f)?; + match TcpPacket::<&[u8]>::new_checked(payload) { + Err(err) => write!(f, "{indent}({err})"), + Ok(tcp_packet) => { + match TcpRepr::parse( + &tcp_packet, + &repr.src_addr(), + &repr.dst_addr(), + &checksum_caps, + ) { + Err(err) => write!(f, "{indent}{tcp_packet} ({err})"), + Ok(tcp_repr) => { + write!(f, "{indent}{tcp_repr}")?; + let valid = + tcp_packet.verify_checksum(&repr.src_addr(), &repr.dst_addr()); + let partially_valid = tcp_packet + .verify_partial_checksum(&repr.src_addr(), &repr.dst_addr()); + + format_checksum(f, valid, partially_valid) + } + } + } + } + } + _ => Ok(()), + } +} + +#[cfg(test)] +pub(crate) mod test { + #![allow(unused)] + + use super::*; + use crate::wire::{IpAddress, IpCidr, IpProtocol, IpRepr}; + #[cfg(feature = "proto-ipv4")] + use crate::wire::{Ipv4Address, Ipv4Repr}; + + #[test] + #[cfg(feature = "proto-ipv4")] + fn to_prefix_len_ipv4() { + fn test_eq>(prefix_len: u8, mask: A) { + assert_eq!(Some(prefix_len), mask.into().prefix_len()); + } + + test_eq(0, Ipv4Address::new(0, 0, 0, 0)); + test_eq(1, Ipv4Address::new(128, 0, 0, 0)); + test_eq(2, Ipv4Address::new(192, 0, 0, 0)); + test_eq(3, Ipv4Address::new(224, 0, 0, 0)); + test_eq(4, Ipv4Address::new(240, 0, 0, 0)); + test_eq(5, Ipv4Address::new(248, 0, 0, 0)); + test_eq(6, Ipv4Address::new(252, 0, 0, 0)); + test_eq(7, Ipv4Address::new(254, 0, 0, 0)); + test_eq(8, Ipv4Address::new(255, 0, 0, 0)); + test_eq(9, Ipv4Address::new(255, 128, 0, 0)); + test_eq(10, Ipv4Address::new(255, 192, 0, 0)); + test_eq(11, Ipv4Address::new(255, 224, 0, 0)); + test_eq(12, Ipv4Address::new(255, 240, 0, 0)); + test_eq(13, Ipv4Address::new(255, 248, 0, 0)); + test_eq(14, Ipv4Address::new(255, 252, 0, 0)); + test_eq(15, Ipv4Address::new(255, 254, 0, 0)); + test_eq(16, Ipv4Address::new(255, 255, 0, 0)); + test_eq(17, Ipv4Address::new(255, 255, 128, 0)); + test_eq(18, Ipv4Address::new(255, 255, 192, 0)); + test_eq(19, Ipv4Address::new(255, 255, 224, 0)); + test_eq(20, Ipv4Address::new(255, 255, 240, 0)); + test_eq(21, Ipv4Address::new(255, 255, 248, 0)); + test_eq(22, Ipv4Address::new(255, 255, 252, 0)); + test_eq(23, Ipv4Address::new(255, 255, 254, 0)); + test_eq(24, Ipv4Address::new(255, 255, 255, 0)); + test_eq(25, Ipv4Address::new(255, 255, 255, 128)); + test_eq(26, Ipv4Address::new(255, 255, 255, 192)); + test_eq(27, Ipv4Address::new(255, 255, 255, 224)); + test_eq(28, Ipv4Address::new(255, 255, 255, 240)); + test_eq(29, Ipv4Address::new(255, 255, 255, 248)); + test_eq(30, Ipv4Address::new(255, 255, 255, 252)); + test_eq(31, Ipv4Address::new(255, 255, 255, 254)); + test_eq(32, Ipv4Address::new(255, 255, 255, 255)); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn to_prefix_len_ipv4_error() { + assert_eq!( + None, + IpAddress::from(Ipv4Address::new(255, 255, 255, 1)).prefix_len() + ); + } + + #[test] + #[cfg(feature = "proto-ipv6")] + fn to_prefix_len_ipv6() { + fn test_eq>(prefix_len: u8, mask: A) { + assert_eq!(Some(prefix_len), mask.into().prefix_len()); + } + + test_eq(0, Ipv6Address::new(0, 0, 0, 0, 0, 0, 0, 0)); + test_eq( + 128, + Ipv6Address::new( + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, + ), + ); + } + + #[test] + #[cfg(feature = "proto-ipv6")] + fn to_prefix_len_ipv6_error() { + assert_eq!( + None, + IpAddress::from(Ipv6Address::new( + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0, 1 + )) + .prefix_len() + ); + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] + fn parse_ipv4_packet() { + let ipv4_packet_bytes: [u8; 20] = [ + 0x45, 0x00, 0x00, 0x14, 0x00, 0x01, 0x40, 0x00, 0x40, 0x01, 0xd2, 0x7c, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, + ]; + + let expected = Ipv4Repr { + src_addr: crate::wire::ipv4::Address::new(0x11, 0x12, 0x13, 0x14), + dst_addr: crate::wire::ipv4::Address::new(0x21, 0x22, 0x23, 0x24), + next_header: Protocol::Icmp, + payload_len: 0, + hop_limit: 64, + }; + + let packet = Packet::new_unchecked(&ipv4_packet_bytes[..]); + let ip_repr = IpRepr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(ip_repr.version(), Version::Ipv4); + let IpRepr::Ipv4(ipv4_repr) = ip_repr else { + panic!("expected Ipv4Repr"); + }; + assert_eq!(ipv4_repr, expected); + assert_eq!(packet.into_inner(), ipv4_packet_bytes); + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] + fn parse_ipv4_packet_error() { + let ipv4_packet_bytes: [u8; 29] = [ + 0x45, 0x00, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x6e, 0x11, 0x12, + 0x13, 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + ]; + + let packet = Packet::new_unchecked(&ipv4_packet_bytes[..]); + let ip_repr_result = IpRepr::parse(&packet, &ChecksumCapabilities::default()); + assert!(ip_repr_result.is_err()); + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] + fn parse_ipv6_packet() { + let ipv6_packet_bytes: [u8; 52] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff, + ]; + + let expected = Ipv6Repr { + src_addr: crate::wire::ipv6::Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: crate::wire::ipv6::Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1), + next_header: Protocol::Udp, + payload_len: 12, + hop_limit: 64, + }; + + let packet = Packet::new_unchecked(&ipv6_packet_bytes[..]); + let ip_repr = IpRepr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(ip_repr.version(), Version::Ipv6); + let IpRepr::Ipv6(ipv6_repr) = ip_repr else { + panic!("expected Ipv6Repr"); + }; + assert_eq!(ipv6_repr, expected); + assert_eq!(packet.into_inner(), ipv6_packet_bytes); + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] + fn parse_ipv6_packet_error() { + let ipv6_packet_bytes: [u8; 51] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, + ]; + + let packet = Packet::new_unchecked(&ipv6_packet_bytes[..]); + let ip_repr_result = IpRepr::parse(&packet, &ChecksumCapabilities::default()); + assert!(ip_repr_result.is_err()); + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] + fn parse_packet_too_short() { + // Test empty packet where no version can be parsed + let ip_packet = [0u8; 0]; + let packet_result = Packet::new_checked(&ip_packet[..]); + assert!(packet_result.is_err()); + } + + #[test] + #[cfg(all(feature = "proto-ipv4", feature = "proto-ipv6"))] + fn parse_packet_invalid_version() { + let packet_bytes: [u8; 1] = [0xFF]; + let packet = Packet::new_unchecked(&packet_bytes[..]); + let ip_repr_result = IpRepr::parse(&packet, &ChecksumCapabilities::default()); + assert!(ip_repr_result.is_err()); + } +} diff --git a/vendor/smoltcp/src/wire/ipsec_ah.rs b/vendor/smoltcp/src/wire/ipsec_ah.rs new file mode 100644 index 00000000..f8697877 --- /dev/null +++ b/vendor/smoltcp/src/wire/ipsec_ah.rs @@ -0,0 +1,289 @@ +use byteorder::{ByteOrder, NetworkEndian}; + +use super::{Error, IpProtocol, Result}; + +/// A read/write wrapper around an IPSec Authentication Header packet buffer. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Next Header | Payload Len | RESERVED | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Security Parameters Index (SPI) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Sequence Number Field | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// + Integrity Check Value-ICV (variable) | +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::Field; + + pub const NEXT_HEADER: usize = 0; + pub const PAYLOAD_LEN: usize = 1; + pub const RESERVED: Field = 2..4; + pub const SPI: Field = 4..8; + pub const SEQUENCE_NUMBER: Field = 8..12; + + pub const fn ICV(payload_len: u8) -> Field { + // The `payload_len` is the length of this Authentication Header in 4-octet units, minus 2. + let header_len = (payload_len as usize + 2) * 4; + + SEQUENCE_NUMBER.end..header_len + } +} + +impl> Packet { + /// Imbue a raw octet buffer with IPsec Authentication Header packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short or shorter than payload length. + /// + /// The result of this check is invalidated by calling [set_payload_len]. + /// + /// [set_payload_len]: #method.set_payload_len + #[allow(clippy::if_same_then_else)] + pub fn check_len(&self) -> Result<()> { + let data = self.buffer.as_ref(); + let len = data.len(); + if len < field::SEQUENCE_NUMBER.end { + Err(Error) + } else if len < field::ICV(data[field::PAYLOAD_LEN]).end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return next header protocol type + /// The value is taken from the list of IP protocol numbers. + pub fn next_header(&self) -> IpProtocol { + let data = self.buffer.as_ref(); + IpProtocol::from(data[field::NEXT_HEADER]) + } + + /// Return the length of this Authentication Header in 4-octet units, minus 2 + pub fn payload_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::PAYLOAD_LEN] + } + + /// Return the security parameters index + pub fn security_parameters_index(&self) -> u32 { + let field = &self.buffer.as_ref()[field::SPI]; + NetworkEndian::read_u32(field) + } + + /// Return sequence number + pub fn sequence_number(&self) -> u32 { + let field = &self.buffer.as_ref()[field::SEQUENCE_NUMBER]; + NetworkEndian::read_u32(field) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the integrity check value + #[inline] + pub fn integrity_check_value(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + &data[field::ICV(data[field::PAYLOAD_LEN])] + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +impl + AsMut<[u8]>> Packet { + /// Set next header protocol field + fn set_next_header(&mut self, value: IpProtocol) { + let data = self.buffer.as_mut(); + data[field::NEXT_HEADER] = value.into() + } + + /// Set payload length field + fn set_payload_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::PAYLOAD_LEN] = value + } + + /// Clear reserved field + fn clear_reserved(&mut self) { + let data = self.buffer.as_mut(); + data[field::RESERVED].fill(0) + } + + /// Set security parameters index field + fn set_security_parameters_index(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::SPI], value) + } + + /// Set sequence number + fn set_sequence_number(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::SEQUENCE_NUMBER], value) + } + + /// Return a mutable pointer to the integrity check value. + #[inline] + pub fn integrity_check_value_mut(&mut self) -> &mut [u8] { + let data = self.buffer.as_mut(); + let range = field::ICV(data[field::PAYLOAD_LEN]); + &mut data[range] + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr<'a> { + next_header: IpProtocol, + security_parameters_index: u32, + sequence_number: u32, + integrity_check_value: &'a [u8], +} + +impl<'a> Repr<'a> { + /// Parse an IPSec Authentication Header packet and return a high-level representation. + pub fn parse + ?Sized>(packet: &Packet<&'a T>) -> Result> { + packet.check_len()?; + Ok(Repr { + next_header: packet.next_header(), + security_parameters_index: packet.security_parameters_index(), + sequence_number: packet.sequence_number(), + integrity_check_value: packet.integrity_check_value(), + }) + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + self.integrity_check_value.len() + field::SEQUENCE_NUMBER.end + } + + /// Emit a high-level representation into an IPSec Authentication Header. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, packet: &mut Packet<&'a mut T>) { + packet.set_next_header(self.next_header); + + let payload_len = ((field::SEQUENCE_NUMBER.end + self.integrity_check_value.len()) / 4) - 2; + packet.set_payload_len(payload_len as u8); + + packet.clear_reserved(); + packet.set_security_parameters_index(self.security_parameters_index); + packet.set_sequence_number(self.sequence_number); + packet + .integrity_check_value_mut() + .copy_from_slice(self.integrity_check_value); + } +} + +#[cfg(test)] +mod test { + use super::*; + + static PACKET_BYTES1: [u8; 24] = [ + 0x32, 0x04, 0x00, 0x00, 0x81, 0x79, 0xb7, 0x05, 0x00, 0x00, 0x00, 0x01, 0x27, 0xcf, 0xc0, + 0xa5, 0xe4, 0x3d, 0x69, 0xb3, 0x72, 0x8e, 0xc5, 0xb0, + ]; + + static PACKET_BYTES2: [u8; 24] = [ + 0x32, 0x04, 0x00, 0x00, 0xba, 0x8b, 0xd0, 0x60, 0x00, 0x00, 0x00, 0x01, 0xaf, 0xd2, 0xe7, + 0xa1, 0x73, 0xd3, 0x29, 0x0b, 0xfe, 0x6b, 0x63, 0x73, + ]; + + #[test] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES1[..]); + assert_eq!(packet.next_header(), IpProtocol::IpSecEsp); + assert_eq!(packet.payload_len(), 4); + assert_eq!(packet.security_parameters_index(), 0x8179b705); + assert_eq!(packet.sequence_number(), 1); + assert_eq!( + packet.integrity_check_value(), + &[ + 0x27, 0xcf, 0xc0, 0xa5, 0xe4, 0x3d, 0x69, 0xb3, 0x72, 0x8e, 0xc5, 0xb0 + ] + ); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 24]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_next_header(IpProtocol::IpSecEsp); + packet.set_payload_len(4); + packet.clear_reserved(); + packet.set_security_parameters_index(0xba8bd060); + packet.set_sequence_number(1); + const ICV: [u8; 12] = [ + 0xaf, 0xd2, 0xe7, 0xa1, 0x73, 0xd3, 0x29, 0x0b, 0xfe, 0x6b, 0x63, 0x73, + ]; + packet.integrity_check_value_mut().copy_from_slice(&ICV); + assert_eq!(bytes, PACKET_BYTES2); + } + #[test] + fn test_check_len() { + assert!(matches!(Packet::new_checked(&PACKET_BYTES1[..10]), Err(_))); + assert!(matches!(Packet::new_checked(&PACKET_BYTES1[..22]), Err(_))); + assert!(matches!(Packet::new_checked(&PACKET_BYTES1[..]), Ok(_))); + } + + fn packet_repr<'a>() -> Repr<'a> { + Repr { + next_header: IpProtocol::IpSecEsp, + security_parameters_index: 0xba8bd060, + sequence_number: 1, + integrity_check_value: &[ + 0xaf, 0xd2, 0xe7, 0xa1, 0x73, 0xd3, 0x29, 0x0b, 0xfe, 0x6b, 0x63, 0x73, + ], + } + } + + #[test] + fn test_parse() { + let packet = Packet::new_unchecked(&PACKET_BYTES2[..]); + assert_eq!(Repr::parse(&packet).unwrap(), packet_repr()); + } + + #[test] + fn test_emit() { + let mut bytes = vec![0x17; 24]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet_repr().emit(&mut packet); + assert_eq!(bytes, PACKET_BYTES2); + } + + #[test] + fn test_buffer_len() { + let header = Packet::new_unchecked(&PACKET_BYTES1[..]); + let repr = Repr::parse(&header).unwrap(); + assert_eq!(repr.buffer_len(), PACKET_BYTES1.len()); + } +} diff --git a/vendor/smoltcp/src/wire/ipsec_esp.rs b/vendor/smoltcp/src/wire/ipsec_esp.rs new file mode 100644 index 00000000..7a359613 --- /dev/null +++ b/vendor/smoltcp/src/wire/ipsec_esp.rs @@ -0,0 +1,178 @@ +use super::{Error, Result}; +use byteorder::{ByteOrder, NetworkEndian}; + +/// A read/write wrapper around an IPSec Encapsulating Security Payload (ESP) packet buffer. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + use crate::wire::field::Field; + + pub const SPI: Field = 0..4; + pub const SEQUENCE_NUMBER: Field = 4..8; +} + +impl> Packet { + /// Imbue a raw octet buffer with IPsec Encapsulating Security Payload packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let data = self.buffer.as_ref(); + let len = data.len(); + if len < field::SEQUENCE_NUMBER.end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the security parameters index + pub fn security_parameters_index(&self) -> u32 { + let field = &self.buffer.as_ref()[field::SPI]; + NetworkEndian::read_u32(field) + } + + /// Return sequence number + pub fn sequence_number(&self) -> u32 { + let field = &self.buffer.as_ref()[field::SEQUENCE_NUMBER]; + NetworkEndian::read_u32(field) + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +impl + AsMut<[u8]>> Packet { + /// Set security parameters index field + fn set_security_parameters_index(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::SPI], value) + } + + /// Set sequence number + fn set_sequence_number(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::SEQUENCE_NUMBER], value) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr { + security_parameters_index: u32, + sequence_number: u32, +} + +impl Repr { + /// Parse an IPSec Encapsulating Security Payload packet and return a high-level representation. + pub fn parse>(packet: &Packet) -> Result { + packet.check_len()?; + Ok(Repr { + security_parameters_index: packet.security_parameters_index(), + sequence_number: packet.sequence_number(), + }) + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + field::SEQUENCE_NUMBER.end + } + + /// Emit a high-level representation into an IPSec Encapsulating Security Payload. + pub fn emit + AsMut<[u8]>>(&self, packet: &mut Packet) { + packet.set_security_parameters_index(self.security_parameters_index); + packet.set_sequence_number(self.sequence_number); + } +} + +#[cfg(test)] +mod test { + use super::*; + + static PACKET_BYTES: [u8; 136] = [ + 0xfb, 0x51, 0x28, 0xa6, 0x00, 0x00, 0x00, 0x02, 0x5d, 0xbe, 0x2d, 0x56, 0xd4, 0x6a, 0x57, + 0xf5, 0xfc, 0x69, 0x8b, 0x3c, 0xa6, 0xb6, 0x88, 0x3a, 0x6c, 0xc1, 0x33, 0x92, 0xdb, 0x40, + 0xab, 0x11, 0x54, 0xb4, 0x0f, 0x22, 0x4d, 0x37, 0x3a, 0x06, 0x94, 0x1e, 0xd4, 0x25, 0xaf, + 0xf0, 0xb0, 0x11, 0x1f, 0x07, 0x96, 0x2a, 0xa7, 0x20, 0xb1, 0xf5, 0x52, 0xb2, 0x12, 0x46, + 0xd6, 0xa5, 0x13, 0x4e, 0x97, 0x75, 0x44, 0x19, 0xc7, 0x29, 0x35, 0xc5, 0xed, 0xa4, 0x0c, + 0xe7, 0x87, 0xec, 0x9c, 0xb1, 0x12, 0x42, 0x74, 0x7c, 0x12, 0x3c, 0x7f, 0x44, 0x9c, 0x6b, + 0x46, 0x27, 0x28, 0xd2, 0x0e, 0xb1, 0x28, 0xd3, 0xd8, 0xc2, 0xd1, 0xac, 0x25, 0xfe, 0xef, + 0xed, 0x13, 0xfd, 0x8f, 0x18, 0x9c, 0x2d, 0xb1, 0x0e, 0x50, 0xe9, 0xaa, 0x65, 0x93, 0x56, + 0x40, 0x43, 0xa3, 0x72, 0x54, 0xba, 0x1b, 0xb1, 0xaf, 0xca, 0x04, 0x15, 0xf9, 0xef, 0xb7, + 0x1d, + ]; + + #[test] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(packet.security_parameters_index(), 0xfb5128a6); + assert_eq!(packet.sequence_number(), 2); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 8]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_security_parameters_index(0xfb5128a6); + packet.set_sequence_number(2); + assert_eq!(&bytes, &PACKET_BYTES[..8]); + } + #[test] + fn test_check_len() { + assert!(matches!(Packet::new_checked(&PACKET_BYTES[..7]), Err(_))); + assert!(matches!(Packet::new_checked(&PACKET_BYTES[..]), Ok(_))); + } + + fn packet_repr() -> Repr { + Repr { + security_parameters_index: 0xfb5128a6, + sequence_number: 2, + } + } + + #[test] + fn test_parse() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(Repr::parse(&packet).unwrap(), packet_repr()); + } + + #[test] + fn test_emit() { + let mut bytes = vec![0x17; 8]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet_repr().emit(&mut packet); + assert_eq!(&bytes, &PACKET_BYTES[..8]); + } + + #[test] + fn test_buffer_len() { + let header = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse(&header).unwrap(); + assert_eq!(repr.buffer_len(), 8); + } +} diff --git a/vendor/smoltcp/src/wire/ipv4.rs b/vendor/smoltcp/src/wire/ipv4.rs new file mode 100644 index 00000000..e4027fe2 --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv4.rs @@ -0,0 +1,1122 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use crate::phy::ChecksumCapabilities; +use crate::wire::ip::{checksum, pretty_print_ip_payload}; + +pub use super::IpProtocol as Protocol; + +/// Minimum MTU required of all links supporting IPv4. See [RFC 791 § 3.1]. +/// +/// [RFC 791 § 3.1]: https://tools.ietf.org/html/rfc791#section-3.1 +// RFC 791 states the following: +// +// > Every internet module must be able to forward a datagram of 68 +// > octets without further fragmentation... Every internet destination +// > must be able to receive a datagram of 576 octets either in one piece +// > or in fragments to be reassembled. +// +// As a result, we can assume that every host we send packets to can +// accept a packet of the following size. +pub const MIN_MTU: usize = 576; + +/// All multicast-capable nodes +pub const MULTICAST_ALL_SYSTEMS: Address = Address::new(224, 0, 0, 1); + +/// All multicast-capable routers +pub const MULTICAST_ALL_ROUTERS: Address = Address::new(224, 0, 0, 2); + +/// Minimum IHL length 5x32 bit words or 20 bytes +/// [RFC 791 § 3.1]: https://tools.ietf.org/html/rfc791#section-3.1 +const MINIMUM_IHL_BYTES: u8 = 20; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Key { + id: u16, + src_addr: Address, + dst_addr: Address, + protocol: Protocol, +} + +pub use core::net::Ipv4Addr as Address; + +pub(crate) trait AddressExt { + /// Query whether the address is an unicast address. + /// + /// `x_` prefix is to avoid a collision with the still-unstable method in `core::ip`. + fn x_is_unicast(&self) -> bool; + + /// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`, + /// where `prefix_len` is the number of leading zeroes. Return `None` otherwise. + fn prefix_len(&self) -> Option; +} + +impl AddressExt for Address { + /// Query whether the address is an unicast address. + fn x_is_unicast(&self) -> bool { + !(self.is_broadcast() || self.is_multicast() || self.is_unspecified()) + } + + fn prefix_len(&self) -> Option { + let mut ones = true; + let mut prefix_len = 0; + for byte in self.octets() { + let mut mask = 0x80; + for _ in 0..8 { + let one = byte & mask != 0; + if ones { + // Expect 1s until first 0 + if one { + prefix_len += 1; + } else { + ones = false; + } + } else if one { + // 1 where 0 was expected + return None; + } + mask >>= 1; + } + } + Some(prefix_len) + } +} + +/// A specification of an IPv4 CIDR block, containing an address and a variable-length +/// subnet masking prefix length. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct Cidr { + address: Address, + prefix_len: u8, +} + +impl Cidr { + /// Create an IPv4 CIDR block from the given address and prefix length. + /// + /// # Panics + /// This function panics if the prefix length is larger than 32. + pub const fn new(address: Address, prefix_len: u8) -> Cidr { + assert!(prefix_len <= 32); + Cidr { + address, + prefix_len, + } + } + + /// Create an IPv4 CIDR block from the given address and network mask. + pub fn from_netmask(addr: Address, netmask: Address) -> Result { + let netmask = netmask.to_bits(); + if netmask.leading_zeros() == 0 && netmask.trailing_zeros() == netmask.count_zeros() { + Ok(Cidr { + address: addr, + prefix_len: netmask.count_ones() as u8, + }) + } else { + Err(Error) + } + } + + /// Return the address of this IPv4 CIDR block. + pub const fn address(&self) -> Address { + self.address + } + + /// Return the prefix length of this IPv4 CIDR block. + pub const fn prefix_len(&self) -> u8 { + self.prefix_len + } + + /// Return the network mask of this IPv4 CIDR. + pub const fn netmask(&self) -> Address { + if self.prefix_len == 0 { + return Address::new(0, 0, 0, 0); + } + + let number = 0xffffffffu32 << (32 - self.prefix_len); + Address::from_bits(number) + } + + /// Return the broadcast address of this IPv4 CIDR. + pub fn broadcast(&self) -> Option
{ + let network = self.network(); + + if network.prefix_len == 31 || network.prefix_len == 32 { + return None; + } + + let network_number = network.address.to_bits(); + let number = network_number | 0xffffffffu32 >> network.prefix_len; + Some(Address::from_bits(number)) + } + + /// Return the network block of this IPv4 CIDR. + pub const fn network(&self) -> Cidr { + Cidr { + address: Address::from_bits(self.address.to_bits() & self.netmask().to_bits()), + prefix_len: self.prefix_len, + } + } + + /// Query whether the subnetwork described by this IPv4 CIDR block contains + /// the given address. + pub fn contains_addr(&self, addr: &Address) -> bool { + self.address.to_bits() & self.netmask().to_bits() + == addr.to_bits() & self.netmask().to_bits() + } + + /// Query whether the subnetwork described by this IPv4 CIDR block contains + /// the subnetwork described by the given IPv4 CIDR block. + pub fn contains_subnet(&self, subnet: &Cidr) -> bool { + self.prefix_len <= subnet.prefix_len && self.contains_addr(&subnet.address) + } +} + +impl fmt::Display for Cidr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.address, self.prefix_len) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Cidr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}/{=u8}", self.address, self.prefix_len); + } +} + +/// A read/write wrapper around an Internet Protocol version 4 packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + use crate::wire::field::*; + + pub const VER_IHL: usize = 0; + pub const DSCP_ECN: usize = 1; + pub const LENGTH: Field = 2..4; + pub const IDENT: Field = 4..6; + pub const FLG_OFF: Field = 6..8; + pub const TTL: usize = 8; + pub const PROTOCOL: usize = 9; + pub const CHECKSUM: Field = 10..12; + pub const SRC_ADDR: Field = 12..16; + pub const DST_ADDR: Field = 16..20; +} + +pub const HEADER_LEN: usize = field::DST_ADDR.end; + +impl> Packet { + /// Imbue a raw octet buffer with IPv4 packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// Returns `Err(Error)` if the header length is greater + /// than total length. + /// Returns `Err(Error)` if the header length is less than minimum allowed IHL + /// + /// The result of this check is invalidated by calling [set_header_len] + /// and [set_total_len]. + /// + /// [set_header_len]: #method.set_header_len + /// [set_total_len]: #method.set_total_len + #[allow(clippy::if_same_then_else)] + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::DST_ADDR.end { + Err(Error) + } else if len < self.header_len() as usize { + Err(Error) + } else if self.header_len() as u16 > self.total_len() { + Err(Error) + } else if len < self.total_len() as usize { + Err(Error) + } else if self.header_len() < MINIMUM_IHL_BYTES { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the version field. + #[inline] + pub fn version(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::VER_IHL] >> 4 + } + + /// Return the header length, in octets. + #[inline] + pub fn header_len(&self) -> u8 { + let data = self.buffer.as_ref(); + (data[field::VER_IHL] & 0x0f) * 4 + } + + /// Return the Differential Services Code Point field. + pub fn dscp(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::DSCP_ECN] >> 2 + } + + /// Return the Explicit Congestion Notification field. + pub fn ecn(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::DSCP_ECN] & 0x03 + } + + /// Return the total length field. + #[inline] + pub fn total_len(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::LENGTH]) + } + + /// Return the fragment identification field. + #[inline] + pub fn ident(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::IDENT]) + } + + /// Return the "don't fragment" flag. + #[inline] + pub fn dont_frag(&self) -> bool { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::FLG_OFF]) & 0x4000 != 0 + } + + /// Return the "more fragments" flag. + #[inline] + pub fn more_frags(&self) -> bool { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::FLG_OFF]) & 0x2000 != 0 + } + + /// Return the fragment offset, in octets. + #[inline] + pub fn frag_offset(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::FLG_OFF]) << 3 + } + + /// Return the time to live field. + #[inline] + pub fn hop_limit(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::TTL] + } + + /// Return the next_header (protocol) field. + #[inline] + pub fn next_header(&self) -> Protocol { + let data = self.buffer.as_ref(); + Protocol::from(data[field::PROTOCOL]) + } + + /// Return the header checksum field. + #[inline] + pub fn checksum(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::CHECKSUM]) + } + + /// Return the source address field. + #[inline] + pub fn src_addr(&self) -> Address { + let data = self.buffer.as_ref(); + crate::wire::ipv4_from_octets(data[field::SRC_ADDR].try_into().unwrap()) + } + + /// Return the destination address field. + #[inline] + pub fn dst_addr(&self) -> Address { + let data = self.buffer.as_ref(); + crate::wire::ipv4_from_octets(data[field::DST_ADDR].try_into().unwrap()) + } + + /// Validate the header checksum. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_checksum(&self) -> bool { + if cfg!(fuzzing) { + return true; + } + + let data = self.buffer.as_ref(); + checksum::data(&data[..self.header_len() as usize]) == !0 + } + + /// Returns the key for identifying the packet. + pub fn get_key(&self) -> Key { + Key { + id: self.ident(), + src_addr: self.src_addr(), + dst_addr: self.dst_addr(), + protocol: self.next_header(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the payload. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let range = self.header_len() as usize..self.total_len() as usize; + let data = self.buffer.as_ref(); + &data[range] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the version field. + #[inline] + pub fn set_version(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::VER_IHL] = (data[field::VER_IHL] & !0xf0) | (value << 4); + } + + /// Set the header length, in octets. + #[inline] + pub fn set_header_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::VER_IHL] = (data[field::VER_IHL] & !0x0f) | ((value / 4) & 0x0f); + } + + /// Set the Differential Services Code Point field. + pub fn set_dscp(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0xfc) | (value << 2) + } + + /// Set the Explicit Congestion Notification field. + pub fn set_ecn(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::DSCP_ECN] = (data[field::DSCP_ECN] & !0x03) | (value & 0x03) + } + + /// Set the total length field. + #[inline] + pub fn set_total_len(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::LENGTH], value) + } + + /// Set the fragment identification field. + #[inline] + pub fn set_ident(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::IDENT], value) + } + + /// Clear the entire flags field. + #[inline] + pub fn clear_flags(&mut self) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]); + let raw = raw & !0xe000; + NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw); + } + + /// Set the "don't fragment" flag. + #[inline] + pub fn set_dont_frag(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]); + let raw = if value { raw | 0x4000 } else { raw & !0x4000 }; + NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw); + } + + /// Set the "more fragments" flag. + #[inline] + pub fn set_more_frags(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]); + let raw = if value { raw | 0x2000 } else { raw & !0x2000 }; + NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw); + } + + /// Set the fragment offset, in octets. + #[inline] + pub fn set_frag_offset(&mut self, value: u16) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLG_OFF]); + let raw = (raw & 0xe000) | (value >> 3); + NetworkEndian::write_u16(&mut data[field::FLG_OFF], raw); + } + + /// Set the time to live field. + #[inline] + pub fn set_hop_limit(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::TTL] = value + } + + /// Set the next header (protocol) field. + #[inline] + pub fn set_next_header(&mut self, value: Protocol) { + let data = self.buffer.as_mut(); + data[field::PROTOCOL] = value.into() + } + + /// Set the header checksum field. + #[inline] + pub fn set_checksum(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) + } + + /// Set the source address field. + #[inline] + pub fn set_src_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::SRC_ADDR].copy_from_slice(&value.octets()) + } + + /// Set the destination address field. + #[inline] + pub fn set_dst_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::DST_ADDR].copy_from_slice(&value.octets()) + } + + /// Compute and fill in the header checksum. + pub fn fill_checksum(&mut self) { + self.set_checksum(0); + let checksum = { + let data = self.buffer.as_ref(); + !checksum::data(&data[..self.header_len() as usize]) + }; + self.set_checksum(checksum) + } + + /// Return a mutable pointer to the payload. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let range = self.header_len() as usize..self.total_len() as usize; + let data = self.buffer.as_mut(); + &mut data[range] + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an Internet Protocol version 4 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr { + pub src_addr: Address, + pub dst_addr: Address, + pub next_header: Protocol, + pub payload_len: usize, + pub hop_limit: u8, +} + +impl Repr { + /// Parse an Internet Protocol version 4 packet and return a high-level representation. + pub fn parse + ?Sized>( + packet: &Packet<&T>, + checksum_caps: &ChecksumCapabilities, + ) -> Result { + packet.check_len()?; + // Version 4 is expected. + if packet.version() != 4 { + return Err(Error); + } + // Valid checksum is expected. + if checksum_caps.ipv4.rx() && !packet.verify_checksum() { + return Err(Error); + } + + #[cfg(not(feature = "proto-ipv4-fragmentation"))] + // We do not support fragmentation. + if packet.more_frags() || packet.frag_offset() != 0 { + return Err(Error); + } + + let payload_len = packet.total_len() as usize - packet.header_len() as usize; + + // All DSCP values are acceptable, since they are of no concern to receiving endpoint. + // All ECN values are acceptable, since ECN requires opt-in from both endpoints. + // All TTL values are acceptable, since we do not perform routing. + Ok(Repr { + src_addr: packet.src_addr(), + dst_addr: packet.dst_addr(), + next_header: packet.next_header(), + payload_len, + hop_limit: packet.hop_limit(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + // We never emit any options. + field::DST_ADDR.end + } + + /// Emit a high-level representation into an Internet Protocol version 4 packet. + pub fn emit + AsMut<[u8]>>( + &self, + packet: &mut Packet, + checksum_caps: &ChecksumCapabilities, + ) { + packet.set_version(4); + packet.set_header_len(field::DST_ADDR.end as u8); + packet.set_dscp(0); + packet.set_ecn(0); + let total_len = packet.header_len() as u16 + self.payload_len as u16; + packet.set_total_len(total_len); + packet.set_ident(0); + packet.clear_flags(); + packet.set_more_frags(false); + packet.set_dont_frag(true); + packet.set_frag_offset(0); + packet.set_hop_limit(self.hop_limit); + packet.set_next_header(self.next_header); + packet.set_src_addr(self.src_addr); + packet.set_dst_addr(self.dst_addr); + + if checksum_caps.ipv4.tx() { + packet.fill_checksum(); + } else { + // make sure we get a consistently zeroed checksum, + // since implementations might rely on it + packet.set_checksum(0); + } + } +} + +impl + ?Sized> fmt::Display for Packet<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self, &ChecksumCapabilities::ignored()) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "IPv4 ({err})")?; + write!( + f, + " src={} dst={} proto={} hop_limit={}", + self.src_addr(), + self.dst_addr(), + self.next_header(), + self.hop_limit() + )?; + if self.version() != 4 { + write!(f, " ver={}", self.version())?; + } + if self.header_len() != 20 { + write!(f, " hlen={}", self.header_len())?; + } + if self.dscp() != 0 { + write!(f, " dscp={}", self.dscp())?; + } + if self.ecn() != 0 { + write!(f, " ecn={}", self.ecn())?; + } + write!(f, " tlen={}", self.total_len())?; + if self.dont_frag() { + write!(f, " df")?; + } + if self.more_frags() { + write!(f, " mf")?; + } + if self.frag_offset() != 0 { + write!(f, " off={}", self.frag_offset())?; + } + if self.more_frags() || self.frag_offset() != 0 { + write!(f, " id={}", self.ident())?; + } + Ok(()) + } + } + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "IPv4 src={} dst={} proto={}", + self.src_addr, self.dst_addr, self.next_header + ) + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + use crate::wire::ip::checksum::format_checksum; + + let checksum_caps = ChecksumCapabilities::ignored(); + + let (ip_repr, payload) = match Packet::new_checked(buffer) { + Err(err) => return write!(f, "{indent}({err})"), + Ok(ip_packet) => match Repr::parse(&ip_packet, &checksum_caps) { + Err(_) => return Ok(()), + Ok(ip_repr) => { + if ip_packet.more_frags() || ip_packet.frag_offset() != 0 { + write!( + f, + "{}IPv4 Fragment more_frags={} offset={}", + indent, + ip_packet.more_frags(), + ip_packet.frag_offset() + )?; + return Ok(()); + } else { + write!(f, "{indent}{ip_repr}")?; + format_checksum(f, ip_packet.verify_checksum(), false)?; + (ip_repr, ip_packet.payload()) + } + } + }, + }; + + pretty_print_ip_payload(f, indent, ip_repr, payload) + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_1: Address = Address::new(192, 168, 1, 1); + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_2: Address = Address::new(192, 168, 1, 2); + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_3: Address = Address::new(192, 168, 1, 3); + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_4: Address = Address::new(192, 168, 1, 4); + #[allow(unused)] + pub(crate) const MOCK_UNSPECIFIED: Address = Address::UNSPECIFIED; + + static PACKET_BYTES: [u8; 30] = [ + 0x45, 0x00, 0x00, 0x1e, 0x01, 0x02, 0x62, 0x03, 0x1a, 0x01, 0xd5, 0x6e, 0x11, 0x12, 0x13, + 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + ]; + + static PAYLOAD_BYTES: [u8; 10] = [0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff]; + + #[test] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(packet.version(), 4); + assert_eq!(packet.header_len(), 20); + assert_eq!(packet.dscp(), 0); + assert_eq!(packet.ecn(), 0); + assert_eq!(packet.total_len(), 30); + assert_eq!(packet.ident(), 0x102); + assert!(packet.more_frags()); + assert!(packet.dont_frag()); + assert_eq!(packet.frag_offset(), 0x203 * 8); + assert_eq!(packet.hop_limit(), 0x1a); + assert_eq!(packet.next_header(), Protocol::Icmp); + assert_eq!(packet.checksum(), 0xd56e); + assert_eq!(packet.src_addr(), Address::new(0x11, 0x12, 0x13, 0x14)); + assert_eq!(packet.dst_addr(), Address::new(0x21, 0x22, 0x23, 0x24)); + assert!(packet.verify_checksum()); + assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]); + } + + #[test] + fn test_construct() { + let mut bytes = vec![0xa5; 30]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_version(4); + packet.set_header_len(20); + packet.clear_flags(); + packet.set_dscp(0); + packet.set_ecn(0); + packet.set_total_len(30); + packet.set_ident(0x102); + packet.set_more_frags(true); + packet.set_dont_frag(true); + packet.set_frag_offset(0x203 * 8); + packet.set_hop_limit(0x1a); + packet.set_next_header(Protocol::Icmp); + packet.set_src_addr(Address::new(0x11, 0x12, 0x13, 0x14)); + packet.set_dst_addr(Address::new(0x21, 0x22, 0x23, 0x24)); + packet.fill_checksum(); + packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } + + #[test] + fn test_overlong() { + let mut bytes = vec![]; + bytes.extend(&PACKET_BYTES[..]); + bytes.push(0); + + assert_eq!( + Packet::new_unchecked(&bytes).payload().len(), + PAYLOAD_BYTES.len() + ); + assert_eq!( + Packet::new_unchecked(&mut bytes).payload_mut().len(), + PAYLOAD_BYTES.len() + ); + } + + #[test] + fn test_total_len_overflow() { + let mut bytes = vec![]; + bytes.extend(&PACKET_BYTES[..]); + Packet::new_unchecked(&mut bytes).set_total_len(128); + + assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error); + } + + static REPR_PACKET_BYTES: [u8; 24] = [ + 0x45, 0x00, 0x00, 0x18, 0x00, 0x00, 0x40, 0x00, 0x40, 0x01, 0xd2, 0x79, 0x11, 0x12, 0x13, + 0x14, 0x21, 0x22, 0x23, 0x24, 0xaa, 0x00, 0x00, 0xff, + ]; + + static REPR_PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + + const fn packet_repr() -> Repr { + Repr { + src_addr: Address::new(0x11, 0x12, 0x13, 0x14), + dst_addr: Address::new(0x21, 0x22, 0x23, 0x24), + next_header: Protocol::Icmp, + payload_len: 4, + hop_limit: 64, + } + } + + #[test] + fn test_parse() { + let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]); + let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + fn test_parse_bad_version() { + let mut bytes = vec![0; 24]; + bytes.copy_from_slice(&REPR_PACKET_BYTES[..]); + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_version(6); + packet.fill_checksum(); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!( + Repr::parse(&packet, &ChecksumCapabilities::default()), + Err(Error) + ); + } + + #[test] + fn test_parse_total_len_less_than_header_len() { + let mut bytes = vec![0; 40]; + bytes[0] = 0x09; + assert_eq!(Packet::new_checked(&mut bytes), Err(Error)); + } + + #[test] + fn test_parse_small_ihl() { + let mut bytes = vec![0; 24]; + bytes.copy_from_slice(&REPR_PACKET_BYTES[..]); + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_header_len(16); + + assert_eq!(Packet::new_checked(&mut bytes), Err(Error)); + } + + #[test] + fn test_emit() { + let repr = packet_repr(); + let mut bytes = vec![0xa5; repr.buffer_len() + REPR_PAYLOAD_BYTES.len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet, &ChecksumCapabilities::default()); + packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES); + assert_eq!(&*packet.into_inner(), &REPR_PACKET_BYTES[..]); + } + + #[test] + fn test_unspecified() { + assert!(Address::UNSPECIFIED.is_unspecified()); + assert!(!Address::UNSPECIFIED.is_broadcast()); + assert!(!Address::UNSPECIFIED.is_multicast()); + assert!(!Address::UNSPECIFIED.is_link_local()); + assert!(!Address::UNSPECIFIED.is_loopback()); + } + + #[test] + fn test_broadcast() { + assert!(!Address::BROADCAST.is_unspecified()); + assert!(Address::BROADCAST.is_broadcast()); + assert!(!Address::BROADCAST.is_multicast()); + assert!(!Address::BROADCAST.is_link_local()); + assert!(!Address::BROADCAST.is_loopback()); + } + + #[test] + fn test_cidr() { + let cidr = Cidr::new(Address::new(192, 168, 1, 10), 24); + + let inside_subnet = [ + [192, 168, 1, 0], + [192, 168, 1, 1], + [192, 168, 1, 2], + [192, 168, 1, 10], + [192, 168, 1, 127], + [192, 168, 1, 255], + ]; + + let outside_subnet = [ + [192, 168, 0, 0], + [127, 0, 0, 1], + [192, 168, 2, 0], + [192, 168, 0, 255], + [0, 0, 0, 0], + [255, 255, 255, 255], + ]; + + let subnets = [ + ([192, 168, 1, 0], 32), + ([192, 168, 1, 255], 24), + ([192, 168, 1, 10], 30), + ]; + + let not_subnets = [ + ([192, 168, 1, 10], 23), + ([127, 0, 0, 1], 8), + ([192, 168, 1, 0], 0), + ([192, 168, 0, 255], 32), + ]; + + for addr in inside_subnet.iter().map(|a| crate::wire::ipv4_from_octets(*a)) { + assert!(cidr.contains_addr(&addr)); + } + + for addr in outside_subnet.iter().map(|a| crate::wire::ipv4_from_octets(*a)) { + assert!(!cidr.contains_addr(&addr)); + } + + for subnet in subnets + .iter() + .map(|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p)) + { + assert!(cidr.contains_subnet(&subnet)); + } + + for subnet in not_subnets + .iter() + .map(|&(a, p)| Cidr::new(Address::new(a[0], a[1], a[2], a[3]), p)) + { + assert!(!cidr.contains_subnet(&subnet)); + } + + let cidr_without_prefix = Cidr::new(cidr.address(), 0); + assert!(cidr_without_prefix.contains_addr(&Address::new(127, 0, 0, 1))); + } + + #[test] + fn test_cidr_from_netmask() { + assert!(Cidr::from_netmask(Address::new(0, 0, 0, 0), Address::new(1, 0, 2, 0)).is_err()); + assert!(Cidr::from_netmask(Address::new(0, 0, 0, 0), Address::new(0, 0, 0, 0)).is_err()); + assert_eq!( + Cidr::from_netmask(Address::new(0, 0, 0, 1), Address::new(255, 255, 255, 0)).unwrap(), + Cidr::new(Address::new(0, 0, 0, 1), 24) + ); + assert_eq!( + Cidr::from_netmask(Address::new(192, 168, 0, 1), Address::new(255, 255, 0, 0)).unwrap(), + Cidr::new(Address::new(192, 168, 0, 1), 16) + ); + assert_eq!( + Cidr::from_netmask(Address::new(172, 16, 0, 1), Address::new(255, 240, 0, 0)).unwrap(), + Cidr::new(Address::new(172, 16, 0, 1), 12) + ); + assert_eq!( + Cidr::from_netmask( + Address::new(255, 255, 255, 1), + Address::new(255, 255, 255, 0) + ) + .unwrap(), + Cidr::new(Address::new(255, 255, 255, 1), 24) + ); + assert_eq!( + Cidr::from_netmask( + Address::new(255, 255, 255, 255), + Address::new(255, 255, 255, 255) + ) + .unwrap(), + Cidr::new(Address::new(255, 255, 255, 255), 32) + ); + } + + #[test] + fn test_cidr_netmask() { + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 0), 0).netmask(), + Address::new(0, 0, 0, 0) + ); + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 1), 24).netmask(), + Address::new(255, 255, 255, 0) + ); + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 0), 32).netmask(), + Address::new(255, 255, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(127, 0, 0, 0), 8).netmask(), + Address::new(255, 0, 0, 0) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 0, 0), 16).netmask(), + Address::new(255, 255, 0, 0) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 1, 1), 16).netmask(), + Address::new(255, 255, 0, 0) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 1, 1), 17).netmask(), + Address::new(255, 255, 128, 0) + ); + assert_eq!( + Cidr::new(Address::new(172, 16, 0, 0), 12).netmask(), + Address::new(255, 240, 0, 0) + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 1), 24).netmask(), + Address::new(255, 255, 255, 0) + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 255), 32).netmask(), + Address::new(255, 255, 255, 255) + ); + } + + #[test] + fn test_cidr_broadcast() { + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 0), 0).broadcast().unwrap(), + Address::new(255, 255, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 1), 24).broadcast().unwrap(), + Address::new(0, 0, 0, 255) + ); + assert_eq!(Cidr::new(Address::new(0, 0, 0, 0), 32).broadcast(), None); + assert_eq!( + Cidr::new(Address::new(127, 0, 0, 0), 8) + .broadcast() + .unwrap(), + Address::new(127, 255, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 0, 0), 16) + .broadcast() + .unwrap(), + Address::new(192, 168, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 1, 1), 16) + .broadcast() + .unwrap(), + Address::new(192, 168, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 1, 1), 17) + .broadcast() + .unwrap(), + Address::new(192, 168, 127, 255) + ); + assert_eq!( + Cidr::new(Address::new(172, 16, 0, 1), 12) + .broadcast() + .unwrap(), + Address::new(172, 31, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 1), 24) + .broadcast() + .unwrap(), + Address::new(255, 255, 255, 255) + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 254), 31).broadcast(), + None + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 255), 32).broadcast(), + None + ); + } + + #[test] + fn test_cidr_network() { + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 0), 0).network(), + Cidr::new(Address::new(0, 0, 0, 0), 0) + ); + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 1), 24).network(), + Cidr::new(Address::new(0, 0, 0, 0), 24) + ); + assert_eq!( + Cidr::new(Address::new(0, 0, 0, 0), 32).network(), + Cidr::new(Address::new(0, 0, 0, 0), 32) + ); + assert_eq!( + Cidr::new(Address::new(127, 0, 0, 0), 8).network(), + Cidr::new(Address::new(127, 0, 0, 0), 8) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 0, 0), 16).network(), + Cidr::new(Address::new(192, 168, 0, 0), 16) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 1, 1), 16).network(), + Cidr::new(Address::new(192, 168, 0, 0), 16) + ); + assert_eq!( + Cidr::new(Address::new(192, 168, 1, 1), 17).network(), + Cidr::new(Address::new(192, 168, 0, 0), 17) + ); + assert_eq!( + Cidr::new(Address::new(172, 16, 0, 1), 12).network(), + Cidr::new(Address::new(172, 16, 0, 0), 12) + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 1), 24).network(), + Cidr::new(Address::new(255, 255, 255, 0), 24) + ); + assert_eq!( + Cidr::new(Address::new(255, 255, 255, 255), 32).network(), + Cidr::new(Address::new(255, 255, 255, 255), 32) + ); + } +} diff --git a/vendor/smoltcp/src/wire/ipv6.rs b/vendor/smoltcp/src/wire/ipv6.rs new file mode 100644 index 00000000..803dac7e --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv6.rs @@ -0,0 +1,1197 @@ +#![deny(missing_docs)] + +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use crate::wire::HardwareAddress; +use crate::wire::ip::pretty_print_ip_payload; + +pub use super::IpProtocol as Protocol; + +/// Minimum MTU required of all links supporting IPv6. See [RFC 8200 § 5]. +/// +/// [RFC 8200 § 5]: https://tools.ietf.org/html/rfc8200#section-5 +pub const MIN_MTU: usize = 1280; + +/// Size of IPv6 adderess in octets. +/// +/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc4291#section-2 +pub const ADDR_SIZE: usize = 16; + +/// The link-local [all nodes multicast address]. +/// +/// [all nodes multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1 +pub const LINK_LOCAL_ALL_NODES: Address = Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1); + +/// The link-local [all routers multicast address]. +/// +/// [all routers multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1 +pub const LINK_LOCAL_ALL_ROUTERS: Address = Address::new(0xff02, 0, 0, 0, 0, 0, 0, 2); + +/// The link-local [all MLVDv2-capable routers multicast address]. +/// +/// [all MLVDv2-capable routers multicast address]: https://tools.ietf.org/html/rfc3810#section-11 +pub const LINK_LOCAL_ALL_MLDV2_ROUTERS: Address = Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x16); + +/// The link-local [all RPL nodes multicast address]. +/// +/// [all RPL nodes multicast address]: https://www.rfc-editor.org/rfc/rfc6550.html#section-20.19 +pub const LINK_LOCAL_ALL_RPL_NODES: Address = Address::new(0xff02, 0, 0, 0, 0, 0, 0, 0x1a); + +/// The [scope] of an address. +/// +/// [scope]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7 +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum MulticastScope { + /// Interface Local scope + InterfaceLocal = 0x1, + /// Link local scope + LinkLocal = 0x2, + /// Administratively configured + AdminLocal = 0x4, + /// Single site scope + SiteLocal = 0x5, + /// Organization scope + OrganizationLocal = 0x8, + /// Global scope + Global = 0xE, + /// Unknown scope + Unknown = 0xFF, +} + +impl From for MulticastScope { + fn from(value: u8) -> Self { + match value { + 0x1 => Self::InterfaceLocal, + 0x2 => Self::LinkLocal, + 0x4 => Self::AdminLocal, + 0x5 => Self::SiteLocal, + 0x8 => Self::OrganizationLocal, + 0xE => Self::Global, + _ => Self::Unknown, + } + } +} + +pub use core::net::Ipv6Addr as Address; + +pub(crate) trait AddressExt { + /// Create an IPv6 address based on the provided prefix and hardware identifier. + fn from_link_prefix( + link_prefix: &Cidr, + interface_identifier: HardwareAddress, + ) -> Option
; + + /// Query whether the IPv6 address is an [unicast address]. + /// + /// [unicast address]: https://tools.ietf.org/html/rfc4291#section-2.5 + /// + /// `x_` prefix is to avoid a collision with the still-unstable method in `core::ip`. + fn x_is_unicast(&self) -> bool; + + /// Query whether the IPv6 address is a [global unicast address]. + /// + /// [global unicast address]: https://datatracker.ietf.org/doc/html/rfc3587 + fn is_global_unicast(&self) -> bool; + + /// Query whether the IPv6 address is in the [link-local] scope. + /// + /// [link-local]: https://tools.ietf.org/html/rfc4291#section-2.5.6 + fn is_link_local(&self) -> bool; + + /// Helper function used to mask an address given a prefix. + /// + /// # Panics + /// This function panics if `mask` is greater than 128. + fn mask(&self, mask: u8) -> [u8; ADDR_SIZE]; + + /// The solicited node for the given unicast address. + /// + /// # Panics + /// This function panics if the given address is not + /// unicast. + fn solicited_node(&self) -> Address; + + /// Return the scope of the address. + /// + /// `x_` prefix is to avoid a collision with the still-unstable method in `core::ip`. + fn x_multicast_scope(&self) -> MulticastScope; + + /// Query whether the IPv6 address is a [solicited-node multicast address]. + /// + /// [Solicited-node multicast address]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1 + fn is_solicited_node_multicast(&self) -> bool; + + /// If `self` is a CIDR-compatible subnet mask, return `Some(prefix_len)`, + /// where `prefix_len` is the number of leading zeroes. Return `None` otherwise. + fn prefix_len(&self) -> Option; +} + +impl AddressExt for Address { + fn from_link_prefix( + link_prefix: &Cidr, + interface_identifier: HardwareAddress, + ) -> Option
{ + if let Some(eui64) = interface_identifier.as_eui_64() { + if link_prefix.prefix_len() != 64 { + return None; + } + let mut bytes = [0; 16]; + bytes[0..8].copy_from_slice(&link_prefix.address().octets()[0..8]); + bytes[8..16].copy_from_slice(&eui64); + Some(crate::wire::ipv6_from_octets(bytes)) + } else { + None + } + } + + fn x_is_unicast(&self) -> bool { + !(self.is_multicast() || self.is_unspecified()) + } + + fn is_global_unicast(&self) -> bool { + (self.octets()[0] >> 5) == 0b001 + } + + fn is_link_local(&self) -> bool { + self.octets()[0..8] == [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + } + + fn mask(&self, mask: u8) -> [u8; ADDR_SIZE] { + assert!(mask <= 128); + let mut bytes = [0u8; ADDR_SIZE]; + let idx = (mask as usize) / 8; + let modulus = (mask as usize) % 8; + let octets = self.octets(); + let (first, second) = octets.split_at(idx); + bytes[0..idx].copy_from_slice(first); + if idx < ADDR_SIZE { + let part = second[0]; + bytes[idx] = part & (!(0xff >> modulus) as u8); + } + bytes + } + + fn solicited_node(&self) -> Address { + assert!(self.x_is_unicast()); + let o = self.octets(); + Address::from([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, o[13], + o[14], o[15], + ]) + } + + fn x_multicast_scope(&self) -> MulticastScope { + if self.is_multicast() { + return MulticastScope::from(self.octets()[1] & 0b1111); + } + + if self.is_link_local() { + MulticastScope::LinkLocal + } else if self.is_unique_local() || self.is_global_unicast() { + // ULA are considered global scope + // https://www.rfc-editor.org/rfc/rfc6724#section-3.1 + MulticastScope::Global + } else { + MulticastScope::Unknown + } + } + + fn is_solicited_node_multicast(&self) -> bool { + self.octets()[0..13] + == [ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + ] + } + + fn prefix_len(&self) -> Option { + let mut ones = true; + let mut prefix_len = 0; + for byte in self.octets() { + let mut mask = 0x80; + for _ in 0..8 { + let one = byte & mask != 0; + if ones { + // Expect 1s until first 0 + if one { + prefix_len += 1; + } else { + ones = false; + } + } else if one { + // 1 where 0 was expected + return None; + } + mask >>= 1; + } + } + Some(prefix_len) + } +} + +/// A specification of an IPv6 CIDR block, containing an address and a variable-length +/// subnet masking prefix length. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub struct Cidr { + address: Address, + prefix_len: u8, +} + +impl Cidr { + /// The [solicited node prefix]. + /// + /// [solicited node prefix]: https://tools.ietf.org/html/rfc4291#section-2.7.1 + pub const SOLICITED_NODE_PREFIX: Cidr = Cidr { + address: Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0), + prefix_len: 104, + }; + + /// The link-local address prefix. + pub const LINK_LOCAL_PREFIX: Cidr = Cidr { + address: Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0), + prefix_len: 10, + }; + + /// Create an IPv6 CIDR block from the given address and prefix length. + /// + /// # Panics + /// This function panics if the prefix length is larger than 128. + pub const fn new(address: Address, prefix_len: u8) -> Cidr { + assert!(prefix_len <= 128); + Cidr { + address, + prefix_len, + } + } + + /// Create an IPv6 CIDR based on the provided prefix and hardware identifier. + pub fn from_link_prefix( + link_prefix: &Cidr, + interface_identifier: HardwareAddress, + ) -> Option { + Address::from_link_prefix(link_prefix, interface_identifier) + .map(|address| Self::new(address, link_prefix.prefix_len())) + } + + /// Return the address of this IPv6 CIDR block. + pub const fn address(&self) -> Address { + self.address + } + + /// Return the prefix length of this IPv6 CIDR block. + pub const fn prefix_len(&self) -> u8 { + self.prefix_len + } + + /// Query whether the subnetwork described by this IPv6 CIDR block contains + /// the given address. + pub fn contains_addr(&self, addr: &Address) -> bool { + // right shift by 128 is not legal + if self.prefix_len == 0 { + return true; + } + + self.address.mask(self.prefix_len) == addr.mask(self.prefix_len) + } + + /// Query whether the subnetwork described by this IPV6 CIDR block contains + /// the subnetwork described by the given IPv6 CIDR block. + pub fn contains_subnet(&self, subnet: &Cidr) -> bool { + self.prefix_len <= subnet.prefix_len && self.contains_addr(&subnet.address) + } +} + +impl fmt::Display for Cidr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // https://tools.ietf.org/html/rfc4291#section-2.3 + write!(f, "{}/{}", self.address, self.prefix_len) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Cidr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}/{=u8}", self.address, self.prefix_len); + } +} + +/// A read/write wrapper around an Internet Protocol version 6 packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +// Ranges and constants describing the IPv6 header +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |Version| Traffic Class | Flow Label | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Payload Length | Next Header | Hop Limit | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// + + +// | | +// + Source Address + +// | | +// + + +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// + + +// | | +// + Destination Address + +// | | +// + + +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// See https://tools.ietf.org/html/rfc2460#section-3 for details. +mod field { + use crate::wire::field::*; + // 4-bit version number, 8-bit traffic class, and the + // 20-bit flow label. + pub const VER_TC_FLOW: Field = 0..4; + // 16-bit value representing the length of the payload. + // Note: Options are included in this length. + pub const LENGTH: Field = 4..6; + // 8-bit value identifying the type of header following this + // one. Note: The same numbers are used in IPv4. + pub const NXT_HDR: usize = 6; + // 8-bit value decremented by each node that forwards this + // packet. The packet is discarded when the value is 0. + pub const HOP_LIMIT: usize = 7; + // IPv6 address of the source node. + pub const SRC_ADDR: Field = 8..24; + // IPv6 address of the destination node. + pub const DST_ADDR: Field = 24..40; +} + +/// Length of an IPv6 header. +pub const HEADER_LEN: usize = field::DST_ADDR.end; + +impl> Packet { + /// Create a raw octet buffer with an IPv6 packet structure. + #[inline] + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + #[inline] + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_payload_len]. + /// + /// [set_payload_len]: #method.set_payload_len + #[inline] + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::DST_ADDR.end || len < self.total_len() { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + #[inline] + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the header length. + #[inline] + pub const fn header_len(&self) -> usize { + // This is not a strictly necessary function, but it makes + // code more readable. + field::DST_ADDR.end + } + + /// Return the version field. + #[inline] + pub fn version(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::VER_TC_FLOW.start] >> 4 + } + + /// Return the traffic class. + #[inline] + pub fn traffic_class(&self) -> u8 { + let data = self.buffer.as_ref(); + ((NetworkEndian::read_u16(&data[0..2]) & 0x0ff0) >> 4) as u8 + } + + /// Return the flow label field. + #[inline] + pub fn flow_label(&self) -> u32 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u24(&data[1..4]) & 0x000fffff + } + + /// Return the payload length field. + #[inline] + pub fn payload_len(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::LENGTH]) + } + + /// Return the payload length added to the known header length. + #[inline] + pub fn total_len(&self) -> usize { + self.header_len() + self.payload_len() as usize + } + + /// Return the next header field. + #[inline] + pub fn next_header(&self) -> Protocol { + let data = self.buffer.as_ref(); + Protocol::from(data[field::NXT_HDR]) + } + + /// Return the hop limit field. + #[inline] + pub fn hop_limit(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::HOP_LIMIT] + } + + /// Return the source address field. + #[inline] + pub fn src_addr(&self) -> Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::SRC_ADDR].try_into().unwrap()) + } + + /// Return the destination address field. + #[inline] + pub fn dst_addr(&self) -> Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::DST_ADDR].try_into().unwrap()) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the payload. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + let range = self.header_len()..self.total_len(); + &data[range] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the version field. + #[inline] + pub fn set_version(&mut self, value: u8) { + let data = self.buffer.as_mut(); + // Make sure to retain the lower order bits which contain + // the higher order bits of the traffic class + data[0] = (data[0] & 0x0f) | ((value & 0x0f) << 4); + } + + /// Set the traffic class field. + #[inline] + pub fn set_traffic_class(&mut self, value: u8) { + let data = self.buffer.as_mut(); + // Put the higher order 4-bits of value in the lower order + // 4-bits of the first byte + data[0] = (data[0] & 0xf0) | ((value & 0xf0) >> 4); + // Put the lower order 4-bits of value in the higher order + // 4-bits of the second byte + data[1] = (data[1] & 0x0f) | ((value & 0x0f) << 4); + } + + /// Set the flow label field. + #[inline] + pub fn set_flow_label(&mut self, value: u32) { + let data = self.buffer.as_mut(); + // Retain the lower order 4-bits of the traffic class + let raw = (((data[1] & 0xf0) as u32) << 16) | (value & 0x0fffff); + NetworkEndian::write_u24(&mut data[1..4], raw); + } + + /// Set the payload length field. + #[inline] + pub fn set_payload_len(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::LENGTH], value); + } + + /// Set the next header field. + #[inline] + pub fn set_next_header(&mut self, value: Protocol) { + let data = self.buffer.as_mut(); + data[field::NXT_HDR] = value.into(); + } + + /// Set the hop limit field. + #[inline] + pub fn set_hop_limit(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::HOP_LIMIT] = value; + } + + /// Set the source address field. + #[inline] + pub fn set_src_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::SRC_ADDR].copy_from_slice(&value.octets()); + } + + /// Set the destination address field. + #[inline] + pub fn set_dst_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::DST_ADDR].copy_from_slice(&value.octets()); + } + + /// Return a mutable pointer to the payload. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let range = self.header_len()..self.total_len(); + let data = self.buffer.as_mut(); + &mut data[range] + } +} + +impl + ?Sized> fmt::Display for Packet<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "IPv6 ({err})")?; + Ok(()) + } + } + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an Internet Protocol version 6 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Repr { + /// IPv6 address of the source node. + pub src_addr: Address, + /// IPv6 address of the destination node. + pub dst_addr: Address, + /// Protocol contained in the next header. + pub next_header: Protocol, + /// Length of the payload including the extension headers. + pub payload_len: usize, + /// The 8-bit hop limit field. + pub hop_limit: u8, +} + +impl Repr { + /// Parse an Internet Protocol version 6 packet and return a high-level representation. + pub fn parse + ?Sized>(packet: &Packet<&T>) -> Result { + // Ensure basic accessors will work + packet.check_len()?; + if packet.version() != 6 { + return Err(Error); + } + Ok(Repr { + src_addr: packet.src_addr(), + dst_addr: packet.dst_addr(), + next_header: packet.next_header(), + payload_len: packet.payload_len() as usize, + hop_limit: packet.hop_limit(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + // This function is not strictly necessary, but it can make client code more readable. + field::DST_ADDR.end + } + + /// Emit a high-level representation into an Internet Protocol version 6 packet. + pub fn emit + AsMut<[u8]>>(&self, packet: &mut Packet) { + // Make no assumptions about the original state of the packet buffer. + // Make sure to set every byte. + packet.set_version(6); + packet.set_traffic_class(0); + packet.set_flow_label(0); + packet.set_payload_len(self.payload_len as u16); + packet.set_hop_limit(self.hop_limit); + packet.set_next_header(self.next_header); + packet.set_src_addr(self.src_addr); + packet.set_dst_addr(self.dst_addr); + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "IPv6 src={} dst={} nxt_hdr={} hop_limit={}", + self.src_addr, self.dst_addr, self.next_header, self.hop_limit + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Repr { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "IPv6 src={} dst={} nxt_hdr={} hop_limit={}", + self.src_addr, + self.dst_addr, + self.next_header, + self.hop_limit + ) + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +// TODO: This is very similar to the implementation for IPv4. Make +// a way to have less copy and pasted code here. +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + let (ip_repr, payload) = match Packet::new_checked(buffer) { + Err(err) => return write!(f, "{indent}({err})"), + Ok(ip_packet) => match Repr::parse(&ip_packet) { + Err(_) => return Ok(()), + Ok(ip_repr) => { + write!(f, "{indent}{ip_repr}")?; + (ip_repr, ip_packet.payload()) + } + }, + }; + + pretty_print_ip_payload(f, indent, ip_repr, payload) + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use crate::wire::pretty_print::PrettyPrinter; + + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_1: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_2: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_3: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 3); + #[allow(unused)] + pub(crate) const MOCK_IP_ADDR_4: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 4); + #[allow(unused)] + pub(crate) const MOCK_UNSPECIFIED: Address = Address::UNSPECIFIED; + + const LINK_LOCAL_ADDR: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const UNIQUE_LOCAL_ADDR: Address = Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1); + const GLOBAL_UNICAST_ADDR: Address = Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 1); + + const TEST_SOL_NODE_MCAST_ADDR: Address = Address::new(0xff02, 0, 0, 0, 0, 1, 0xff01, 101); + + #[test] + fn test_basic_multicast() { + assert!(!LINK_LOCAL_ALL_ROUTERS.is_unspecified()); + assert!(LINK_LOCAL_ALL_ROUTERS.is_multicast()); + assert!(!LINK_LOCAL_ALL_ROUTERS.is_link_local()); + assert!(!LINK_LOCAL_ALL_ROUTERS.is_loopback()); + assert!(!LINK_LOCAL_ALL_ROUTERS.is_unique_local()); + assert!(!LINK_LOCAL_ALL_ROUTERS.is_global_unicast()); + assert!(!LINK_LOCAL_ALL_ROUTERS.is_solicited_node_multicast()); + assert!(!LINK_LOCAL_ALL_NODES.is_unspecified()); + assert!(LINK_LOCAL_ALL_NODES.is_multicast()); + assert!(!LINK_LOCAL_ALL_NODES.is_link_local()); + assert!(!LINK_LOCAL_ALL_NODES.is_loopback()); + assert!(!LINK_LOCAL_ALL_NODES.is_unique_local()); + assert!(!LINK_LOCAL_ALL_NODES.is_global_unicast()); + assert!(!LINK_LOCAL_ALL_NODES.is_solicited_node_multicast()); + } + + #[test] + fn test_basic_link_local() { + assert!(!LINK_LOCAL_ADDR.is_unspecified()); + assert!(!LINK_LOCAL_ADDR.is_multicast()); + assert!(LINK_LOCAL_ADDR.is_link_local()); + assert!(!LINK_LOCAL_ADDR.is_loopback()); + assert!(!LINK_LOCAL_ADDR.is_unique_local()); + assert!(!LINK_LOCAL_ADDR.is_global_unicast()); + assert!(!LINK_LOCAL_ADDR.is_solicited_node_multicast()); + } + + #[test] + fn test_basic_loopback() { + assert!(!Address::LOCALHOST.is_unspecified()); + assert!(!Address::LOCALHOST.is_multicast()); + assert!(!Address::LOCALHOST.is_link_local()); + assert!(Address::LOCALHOST.is_loopback()); + assert!(!Address::LOCALHOST.is_unique_local()); + assert!(!Address::LOCALHOST.is_global_unicast()); + assert!(!Address::LOCALHOST.is_solicited_node_multicast()); + } + + #[test] + fn test_unique_local() { + assert!(!UNIQUE_LOCAL_ADDR.is_unspecified()); + assert!(!UNIQUE_LOCAL_ADDR.is_multicast()); + assert!(!UNIQUE_LOCAL_ADDR.is_link_local()); + assert!(!UNIQUE_LOCAL_ADDR.is_loopback()); + assert!(UNIQUE_LOCAL_ADDR.is_unique_local()); + assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast()); + assert!(!UNIQUE_LOCAL_ADDR.is_solicited_node_multicast()); + } + + #[test] + fn test_global_unicast() { + assert!(!GLOBAL_UNICAST_ADDR.is_unspecified()); + assert!(!GLOBAL_UNICAST_ADDR.is_multicast()); + assert!(!GLOBAL_UNICAST_ADDR.is_link_local()); + assert!(!GLOBAL_UNICAST_ADDR.is_loopback()); + assert!(!GLOBAL_UNICAST_ADDR.is_unique_local()); + assert!(GLOBAL_UNICAST_ADDR.is_global_unicast()); + assert!(!GLOBAL_UNICAST_ADDR.is_solicited_node_multicast()); + } + + #[test] + fn test_sollicited_node_multicast() { + assert!(!TEST_SOL_NODE_MCAST_ADDR.is_unspecified()); + assert!(TEST_SOL_NODE_MCAST_ADDR.is_multicast()); + assert!(!TEST_SOL_NODE_MCAST_ADDR.is_link_local()); + assert!(!TEST_SOL_NODE_MCAST_ADDR.is_loopback()); + assert!(!TEST_SOL_NODE_MCAST_ADDR.is_unique_local()); + assert!(!TEST_SOL_NODE_MCAST_ADDR.is_global_unicast()); + assert!(TEST_SOL_NODE_MCAST_ADDR.is_solicited_node_multicast()); + } + + #[test] + fn test_mask() { + let addr = Address::new(0x0123, 0x4567, 0x89ab, 0, 0, 0, 0, 1); + assert_eq!( + addr.mask(11), + [0x01, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + addr.mask(15), + [0x01, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + addr.mask(26), + [0x01, 0x23, 0x45, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + addr.mask(128), + [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 + ] + ); + assert_eq!( + addr.mask(127), + [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } + + #[test] + fn test_cidr() { + // fe80::1/56 + // 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + let cidr = Cidr::new(LINK_LOCAL_ADDR, 56); + + let inside_subnet = [ + // fe80::2 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, + ], + // fe80::1122:3344:5566:7788 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, + ], + // fe80::ff00:0:0:0 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ], + // fe80::ff + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, + ], + ]; + + let outside_subnet = [ + // fe80:0:0:101::1 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ], + // ::1 + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ], + // ff02::1 + [ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ], + // ff02::2 + [ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, + ], + ]; + + let subnets = [ + // fe80::ffff:ffff:ffff:ffff/65 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 65, + ), + // fe80::1/128 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, + ], + 128, + ), + // fe80::1234:5678/96 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x34, 0x56, 0x78, + ], + 96, + ), + ]; + + let not_subnets = [ + // fe80::101:ffff:ffff:ffff:ffff/55 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 55, + ), + // fe80::101:ffff:ffff:ffff:ffff/56 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 56, + ), + // fe80::101:ffff:ffff:ffff:ffff/57 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 57, + ), + // ::1/128 + ( + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, + ], + 128, + ), + ]; + + for addr in inside_subnet.iter().map(|a| crate::wire::ipv6_from_octets(*a)) { + assert!(cidr.contains_addr(&addr)); + } + + for addr in outside_subnet.iter().map(|a| crate::wire::ipv6_from_octets(*a)) { + assert!(!cidr.contains_addr(&addr)); + } + + for subnet in subnets.iter().map(|&(a, p)| Cidr::new(Address::from(a), p)) { + assert!(cidr.contains_subnet(&subnet)); + } + + for subnet in not_subnets + .iter() + .map(|&(a, p)| Cidr::new(Address::from(a), p)) + { + assert!(!cidr.contains_subnet(&subnet)); + } + + let cidr_without_prefix = Cidr::new(LINK_LOCAL_ADDR, 0); + assert!(cidr_without_prefix.contains_addr(&Address::LOCALHOST)); + } + + #[test] + fn test_from_eui_64() { + #[cfg(feature = "medium-ethernet")] + use crate::wire::EthernetAddress; + #[cfg(feature = "medium-ieee802154")] + use crate::wire::Ieee802154Address; + let tests: std::vec::Vec<(HardwareAddress, Address)> = vec![ + #[cfg(feature = "medium-ethernet")] + ( + HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[ + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + ])), + Address::new(0x2001, 0xdb8, 0x3, 0x0, 0xa8bb, 0xccff, 0xfedd, 0xeeff), + ), + #[cfg(feature = "medium-ieee802154")] + ( + HardwareAddress::Ieee802154(Ieee802154Address::from_bytes(&[ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + ])), + Address::new(0x2001, 0xdb8, 0x3, 0x0, 0x0211, 0x2233, 0x4455, 0x6677), + ), + ]; + + let prefix = Cidr::new(GLOBAL_UNICAST_ADDR.mask(64).into(), 64); + let wrong_prefix = Cidr::new(GLOBAL_UNICAST_ADDR.mask(72).into(), 72); + + for (hardware, result) in tests { + let generated = Address::from_link_prefix(&prefix, hardware).unwrap(); + assert!(prefix.contains_addr(&generated)); + assert_eq!(generated, result); + assert!(Address::from_link_prefix(&wrong_prefix, hardware).is_none()); + + let generated = Cidr::from_link_prefix(&prefix, hardware).unwrap(); + assert!(prefix.contains_subnet(&generated)); + assert_eq!(generated.address(), result); + assert!(Cidr::from_link_prefix(&wrong_prefix, hardware).is_none()); + } + } + + #[test] + fn test_scope() { + use super::*; + assert_eq!( + Address::new(0xff01, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::InterfaceLocal + ); + assert_eq!( + Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::LinkLocal + ); + assert_eq!( + Address::new(0xff03, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::Unknown + ); + assert_eq!( + Address::new(0xff04, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::AdminLocal + ); + assert_eq!( + Address::new(0xff05, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::SiteLocal + ); + assert_eq!( + Address::new(0xff08, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::OrganizationLocal + ); + assert_eq!( + Address::new(0xff0e, 0, 0, 0, 0, 0, 0, 1).x_multicast_scope(), + MulticastScope::Global + ); + + assert_eq!( + LINK_LOCAL_ALL_NODES.x_multicast_scope(), + MulticastScope::LinkLocal + ); + + // For source address selection, unicast addresses also have a scope: + assert_eq!( + LINK_LOCAL_ADDR.x_multicast_scope(), + MulticastScope::LinkLocal + ); + assert_eq!( + GLOBAL_UNICAST_ADDR.x_multicast_scope(), + MulticastScope::Global + ); + assert_eq!( + UNIQUE_LOCAL_ADDR.x_multicast_scope(), + MulticastScope::Global + ); + } + + static REPR_PACKET_BYTES: [u8; 52] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff, + ]; + static REPR_PAYLOAD_BYTES: [u8; 12] = [ + 0x00, 0x01, 0x00, 0x02, 0x00, 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff, + ]; + + const fn packet_repr() -> Repr { + Repr { + src_addr: Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + dst_addr: LINK_LOCAL_ALL_NODES, + next_header: Protocol::Udp, + payload_len: 12, + hop_limit: 64, + } + } + + #[test] + fn test_packet_deconstruction() { + let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]); + assert_eq!(packet.check_len(), Ok(())); + assert_eq!(packet.version(), 6); + assert_eq!(packet.traffic_class(), 0); + assert_eq!(packet.flow_label(), 0); + assert_eq!(packet.total_len(), 0x34); + assert_eq!(packet.payload_len() as usize, REPR_PAYLOAD_BYTES.len()); + assert_eq!(packet.next_header(), Protocol::Udp); + assert_eq!(packet.hop_limit(), 0x40); + assert_eq!(packet.src_addr(), Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); + assert_eq!(packet.dst_addr(), LINK_LOCAL_ALL_NODES); + assert_eq!(packet.payload(), &REPR_PAYLOAD_BYTES[..]); + } + + #[test] + fn test_packet_construction() { + let mut bytes = [0xff; 52]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + // Version, Traffic Class, and Flow Label are not + // byte aligned. make sure the setters and getters + // do not interfere with each other. + packet.set_version(6); + assert_eq!(packet.version(), 6); + packet.set_traffic_class(0x99); + assert_eq!(packet.version(), 6); + assert_eq!(packet.traffic_class(), 0x99); + packet.set_flow_label(0x54321); + assert_eq!(packet.traffic_class(), 0x99); + assert_eq!(packet.flow_label(), 0x54321); + packet.set_payload_len(0xc); + packet.set_next_header(Protocol::Udp); + packet.set_hop_limit(0xfe); + packet.set_src_addr(LINK_LOCAL_ALL_ROUTERS); + packet.set_dst_addr(LINK_LOCAL_ALL_NODES); + packet + .payload_mut() + .copy_from_slice(&REPR_PAYLOAD_BYTES[..]); + let mut expected_bytes = [ + 0x69, 0x95, 0x43, 0x21, 0x00, 0x0c, 0x11, 0xfe, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let start = expected_bytes.len() - REPR_PAYLOAD_BYTES.len(); + expected_bytes[start..].copy_from_slice(&REPR_PAYLOAD_BYTES[..]); + assert_eq!(packet.check_len(), Ok(())); + assert_eq!(&*packet.into_inner(), &expected_bytes[..]); + } + + #[test] + fn test_overlong() { + let mut bytes = vec![]; + bytes.extend(&REPR_PACKET_BYTES[..]); + bytes.push(0); + + assert_eq!( + Packet::new_unchecked(&bytes).payload().len(), + REPR_PAYLOAD_BYTES.len() + ); + assert_eq!( + Packet::new_unchecked(&mut bytes).payload_mut().len(), + REPR_PAYLOAD_BYTES.len() + ); + } + + #[test] + fn test_total_len_overflow() { + let mut bytes = vec![]; + bytes.extend(&REPR_PACKET_BYTES[..]); + Packet::new_unchecked(&mut bytes).set_payload_len(0x80); + + assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error); + } + + #[test] + fn test_repr_parse_valid() { + let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]); + let repr = Repr::parse(&packet).unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + fn test_repr_parse_bad_version() { + let mut bytes = [0; 40]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_version(4); + packet.set_payload_len(0); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet), Err(Error)); + } + + #[test] + fn test_repr_parse_smaller_than_header() { + let mut bytes = [0; 40]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_version(6); + packet.set_payload_len(39); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet), Err(Error)); + } + + #[test] + fn test_repr_parse_smaller_than_payload() { + let mut bytes = [0; 40]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_version(6); + packet.set_payload_len(1); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet), Err(Error)); + } + + #[test] + fn test_basic_repr_emit() { + let repr = packet_repr(); + let mut bytes = vec![0xff; repr.buffer_len() + REPR_PAYLOAD_BYTES.len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet); + packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES); + assert_eq!(&*packet.into_inner(), &REPR_PACKET_BYTES[..]); + } + + #[test] + fn test_pretty_print() { + assert_eq!( + format!( + "{}", + PrettyPrinter::>::new("\n", &&REPR_PACKET_BYTES[..]) + ), + "\nIPv6 src=fe80::1 dst=ff02::1 nxt_hdr=UDP hop_limit=64\n \\ UDP src=1 dst=2 len=4" + ); + } +} diff --git a/vendor/smoltcp/src/wire/ipv6ext_header.rs b/vendor/smoltcp/src/wire/ipv6ext_header.rs new file mode 100644 index 00000000..8ee11195 --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv6ext_header.rs @@ -0,0 +1,306 @@ +#![allow(unused)] + +use super::IpProtocol; +use super::{Error, Result}; + +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + pub const MIN_HEADER_SIZE: usize = 8; + + pub const NXT_HDR: usize = 0; + pub const LENGTH: usize = 1; + // Variable-length field. + // + // Length of the header is in 8-octet units, not including the first 8 octets. + // The first two octets are the next header type and the header length. + pub const fn PAYLOAD(length_field: u8) -> Field { + let bytes = length_field as usize * 8 + 8; + 2..bytes + } +} + +/// A read/write wrapper around an IPv6 Extension Header buffer. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Header> { + buffer: T, +} + +/// Core getter methods relevant to any IPv6 extension header. +impl> Header { + /// Create a raw octet buffer with an IPv6 Extension Header structure. + pub const fn new_unchecked(buffer: T) -> Self { + Header { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let header = Self::new_unchecked(buffer); + header.check_len()?; + Ok(header) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_header_len]. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + let data = self.buffer.as_ref(); + + let len = data.len(); + if len < field::MIN_HEADER_SIZE { + return Err(Error); + } + + let of = field::PAYLOAD(data[field::LENGTH]); + if len < of.end { + return Err(Error); + } + + Ok(()) + } + + /// Consume the header, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the next header field. + pub fn next_header(&self) -> IpProtocol { + let data = self.buffer.as_ref(); + IpProtocol::from(data[field::NXT_HDR]) + } + + /// Return the header length field. + pub fn header_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::LENGTH] + } +} + +impl<'h, T: AsRef<[u8]> + ?Sized> Header<&'h T> { + /// Return the payload of the IPv6 extension header. + pub fn payload(&self) -> &'h [u8] { + let data = self.buffer.as_ref(); + &data[field::PAYLOAD(data[field::LENGTH])] + } +} + +impl + AsMut<[u8]>> Header { + /// Set the next header field. + #[inline] + pub fn set_next_header(&mut self, value: IpProtocol) { + let data = self.buffer.as_mut(); + data[field::NXT_HDR] = value.into(); + } + + /// Set the extension header data length. The length of the header is + /// in 8-octet units, not including the first 8 octets. + #[inline] + pub fn set_header_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::LENGTH] = value; + } +} + +impl + AsMut<[u8]> + ?Sized> Header<&mut T> { + /// Return a mutable pointer to the payload data. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let data = self.buffer.as_mut(); + let len = data[field::LENGTH]; + &mut data[field::PAYLOAD(len)] + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr<'a> { + pub next_header: IpProtocol, + pub length: u8, + pub data: &'a [u8], +} + +impl<'a> Repr<'a> { + /// Parse an IPv6 Extension Header Header and return a high-level representation. + pub fn parse(header: &Header<&'a T>) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + header.check_len()?; + Ok(Self { + next_header: header.next_header(), + length: header.header_len(), + data: header.payload(), + }) + } + + /// Return the length, in bytes, of a header that will be emitted from this high-level + /// representation. + pub const fn header_len(&self) -> usize { + 2 + } + + /// Emit a high-level representation into an IPv6 Extension Header. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { + header.set_next_header(self.next_header); + header.set_header_len(self.length); + } +} + +#[cfg(test)] +mod test { + use super::*; + + // A Hop-by-Hop Option header with a PadN option of option data length 4. + static REPR_PACKET_PAD4: [u8; 8] = [0x6, 0x0, 0x1, 0x4, 0x0, 0x0, 0x0, 0x0]; + + // A Hop-by-Hop Option header with a PadN option of option data length 12. + static REPR_PACKET_PAD12: [u8; 16] = [ + 0x06, 0x1, 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + #[test] + fn test_check_len() { + // zero byte buffer + assert_eq!( + Err(Error), + Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len() + ); + // no length field + assert_eq!( + Err(Error), + Header::new_unchecked(&REPR_PACKET_PAD4[..1]).check_len() + ); + // less than 8 bytes + assert_eq!( + Err(Error), + Header::new_unchecked(&REPR_PACKET_PAD4[..7]).check_len() + ); + // valid + assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len()); + // valid + assert_eq!( + Ok(()), + Header::new_unchecked(&REPR_PACKET_PAD12).check_len() + ); + // length field value greater than number of bytes + let header: [u8; 8] = [0x06, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; + assert_eq!(Err(Error), Header::new_unchecked(&header).check_len()); + } + + #[test] + fn test_header_deconstruct() { + let header = Header::new_unchecked(&REPR_PACKET_PAD4); + assert_eq!(header.next_header(), IpProtocol::Tcp); + assert_eq!(header.header_len(), 0); + assert_eq!(header.payload(), &REPR_PACKET_PAD4[2..]); + + let header = Header::new_unchecked(&REPR_PACKET_PAD12); + assert_eq!(header.next_header(), IpProtocol::Tcp); + assert_eq!(header.header_len(), 1); + assert_eq!(header.payload(), &REPR_PACKET_PAD12[2..]); + } + + #[test] + fn test_overlong() { + let mut bytes = vec![]; + bytes.extend(&REPR_PACKET_PAD4[..]); + bytes.push(0); + + assert_eq!( + Header::new_unchecked(&bytes).payload().len(), + REPR_PACKET_PAD4[2..].len() + ); + assert_eq!( + Header::new_unchecked(&mut bytes).payload_mut().len(), + REPR_PACKET_PAD4[2..].len() + ); + + let mut bytes = vec![]; + bytes.extend(&REPR_PACKET_PAD12[..]); + bytes.push(0); + + assert_eq!( + Header::new_unchecked(&bytes).payload().len(), + REPR_PACKET_PAD12[2..].len() + ); + assert_eq!( + Header::new_unchecked(&mut bytes).payload_mut().len(), + REPR_PACKET_PAD12[2..].len() + ); + } + + #[test] + fn test_header_len_overflow() { + let mut bytes = vec![]; + bytes.extend(REPR_PACKET_PAD4); + let len = bytes.len() as u8; + Header::new_unchecked(&mut bytes).set_header_len(len + 1); + + assert_eq!(Header::new_checked(&bytes).unwrap_err(), Error); + + let mut bytes = vec![]; + bytes.extend(REPR_PACKET_PAD12); + let len = bytes.len() as u8; + Header::new_unchecked(&mut bytes).set_header_len(len + 1); + + assert_eq!(Header::new_checked(&bytes).unwrap_err(), Error); + } + + #[test] + fn test_repr_parse_valid() { + let header = Header::new_unchecked(&REPR_PACKET_PAD4); + let repr = Repr::parse(&header).unwrap(); + assert_eq!( + repr, + Repr { + next_header: IpProtocol::Tcp, + length: 0, + data: &REPR_PACKET_PAD4[2..] + } + ); + + let header = Header::new_unchecked(&REPR_PACKET_PAD12); + let repr = Repr::parse(&header).unwrap(); + assert_eq!( + repr, + Repr { + next_header: IpProtocol::Tcp, + length: 1, + data: &REPR_PACKET_PAD12[2..] + } + ); + } + + #[test] + fn test_repr_emit() { + let repr = Repr { + next_header: IpProtocol::Tcp, + length: 0, + data: &REPR_PACKET_PAD4[2..], + }; + let mut bytes = [0u8; 2]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..2]); + + let repr = Repr { + next_header: IpProtocol::Tcp, + length: 1, + data: &REPR_PACKET_PAD12[2..], + }; + let mut bytes = [0u8; 2]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..2]); + } +} diff --git a/vendor/smoltcp/src/wire/ipv6fragment.rs b/vendor/smoltcp/src/wire/ipv6fragment.rs new file mode 100644 index 00000000..a83bab7a --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv6fragment.rs @@ -0,0 +1,284 @@ +use super::{Error, Result}; +use core::fmt; + +use byteorder::{ByteOrder, NetworkEndian}; + +/// A read/write wrapper around an IPv6 Fragment Header. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Header> { + buffer: T, +} + +// Format of the Fragment Header +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Next Header | Reserved | Fragment Offset |Res|M| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Identification | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// See https://tools.ietf.org/html/rfc8200#section-4.5 for details. +// +// **NOTE**: The fields start counting after the header length field. +mod field { + use crate::wire::field::*; + + // 16-bit field containing the fragment offset, reserved and more fragments values. + pub const FR_OF_M: Field = 0..2; + // 32-bit field identifying the fragmented packet + pub const IDENT: Field = 2..6; + /// 1 bit flag indicating if there are more fragments coming. + pub const M: usize = 1; +} + +impl> Header { + /// Create a raw octet buffer with an IPv6 Fragment Header structure. + pub const fn new_unchecked(buffer: T) -> Header { + Header { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let header = Self::new_unchecked(buffer); + header.check_len()?; + Ok(header) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let data = self.buffer.as_ref(); + let len = data.len(); + + if len < field::IDENT.end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the header, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the fragment offset field. + #[inline] + pub fn frag_offset(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::FR_OF_M]) >> 3 + } + + /// Return more fragment flag field. + #[inline] + pub fn more_frags(&self) -> bool { + let data = self.buffer.as_ref(); + (data[field::M] & 0x1) == 1 + } + + /// Return the fragment identification value field. + #[inline] + pub fn ident(&self) -> u32 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u32(&data[field::IDENT]) + } +} + +impl + AsMut<[u8]>> Header { + /// Set reserved fields. + /// + /// Set 8-bit reserved field after the next header field. + /// Set 2-bit reserved field between fragment offset and more fragments. + #[inline] + pub fn clear_reserved(&mut self) { + let data = self.buffer.as_mut(); + // Retain the higher order 5 bits and lower order 1 bit + data[field::M] &= 0xf9; + } + + /// Set the fragment offset field. + #[inline] + pub fn set_frag_offset(&mut self, value: u16) { + let data = self.buffer.as_mut(); + // Retain the lower order 3 bits + let raw = ((value & 0x1fff) << 3) | ((data[field::M] & 0x7) as u16); + NetworkEndian::write_u16(&mut data[field::FR_OF_M], raw); + } + + /// Set the more fragments flag field. + #[inline] + pub fn set_more_frags(&mut self, value: bool) { + let data = self.buffer.as_mut(); + // Retain the high order 7 bits + let raw = (data[field::M] & 0xfe) | (value as u8 & 0x1); + data[field::M] = raw; + } + + /// Set the fragmentation identification field. + #[inline] + pub fn set_ident(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::IDENT], value); + } +} + +impl + ?Sized> fmt::Display for Header<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "IPv6 Fragment ({err})")?; + Ok(()) + } + } + } +} + +/// A high-level representation of an IPv6 Fragment header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr { + /// The offset of the data following this header, relative to the start of the Fragmentable + /// Part of the original packet. + pub frag_offset: u16, + /// When there are more fragments following this header + pub more_frags: bool, + /// The identification for every packet that is fragmented. + pub ident: u32, +} + +impl Repr { + /// Parse an IPv6 Fragment Header and return a high-level representation. + pub fn parse(header: &Header<&T>) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + header.check_len()?; + Ok(Repr { + frag_offset: header.frag_offset(), + more_frags: header.more_frags(), + ident: header.ident(), + }) + } + + /// Return the length, in bytes, of a header that will be emitted from this high-level + /// representation. + pub const fn buffer_len(&self) -> usize { + field::IDENT.end + } + + /// Emit a high-level representation into an IPv6 Fragment Header. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { + header.clear_reserved(); + header.set_frag_offset(self.frag_offset); + header.set_more_frags(self.more_frags); + header.set_ident(self.ident); + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "IPv6 Fragment offset={} more={} ident={}", + self.frag_offset, self.more_frags, self.ident + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + + // A Fragment Header with more fragments remaining + static BYTES_HEADER_MORE_FRAG: [u8; 6] = [0x0, 0x1, 0x0, 0x0, 0x30, 0x39]; + + // A Fragment Header with no more fragments remaining + static BYTES_HEADER_LAST_FRAG: [u8; 6] = [0xa, 0x0, 0x0, 0x1, 0x9, 0x32]; + + #[test] + fn test_check_len() { + // less than 6 bytes + assert_eq!( + Err(Error), + Header::new_unchecked(&BYTES_HEADER_MORE_FRAG[..5]).check_len() + ); + // valid + assert_eq!( + Ok(()), + Header::new_unchecked(&BYTES_HEADER_MORE_FRAG).check_len() + ); + } + + #[test] + fn test_header_deconstruct() { + let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG); + assert_eq!(header.frag_offset(), 0); + assert!(header.more_frags()); + assert_eq!(header.ident(), 12345); + + let header = Header::new_unchecked(&BYTES_HEADER_LAST_FRAG); + assert_eq!(header.frag_offset(), 320); + assert!(!header.more_frags()); + assert_eq!(header.ident(), 67890); + } + + #[test] + fn test_repr_parse_valid() { + let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG); + let repr = Repr::parse(&header).unwrap(); + assert_eq!( + repr, + Repr { + frag_offset: 0, + more_frags: true, + ident: 12345 + } + ); + + let header = Header::new_unchecked(&BYTES_HEADER_LAST_FRAG); + let repr = Repr::parse(&header).unwrap(); + assert_eq!( + repr, + Repr { + frag_offset: 320, + more_frags: false, + ident: 67890 + } + ); + } + + #[test] + fn test_repr_emit() { + let repr = Repr { + frag_offset: 0, + more_frags: true, + ident: 12345, + }; + let mut bytes = [0u8; 6]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + assert_eq!(header.into_inner(), &BYTES_HEADER_MORE_FRAG[0..6]); + + let repr = Repr { + frag_offset: 320, + more_frags: false, + ident: 67890, + }; + let mut bytes = [0u8; 6]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + assert_eq!(header.into_inner(), &BYTES_HEADER_LAST_FRAG[0..6]); + } + + #[test] + fn test_buffer_len() { + let header = Header::new_unchecked(&BYTES_HEADER_MORE_FRAG); + let repr = Repr::parse(&header).unwrap(); + assert_eq!(repr.buffer_len(), BYTES_HEADER_MORE_FRAG.len()); + } +} diff --git a/vendor/smoltcp/src/wire/ipv6hbh.rs b/vendor/smoltcp/src/wire/ipv6hbh.rs new file mode 100644 index 00000000..eb8bfd46 --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv6hbh.rs @@ -0,0 +1,195 @@ +use super::{Error, Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, Result}; +use crate::config; +use crate::wire::ipv6option::RouterAlert; +use heapless::Vec; + +/// A read/write wrapper around an IPv6 Hop-by-Hop Header buffer. +pub struct Header> { + buffer: T, +} + +impl> Header { + /// Create a raw octet buffer with an IPv6 Hop-by-Hop Header structure. + pub const fn new_unchecked(buffer: T) -> Self { + Header { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let header = Self::new_unchecked(buffer); + header.check_len()?; + Ok(header) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_header_len]. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + if self.buffer.as_ref().is_empty() { + return Err(Error); + } + + Ok(()) + } + + /// Consume the header, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Header<&'a T> { + /// Return the options of the IPv6 Hop-by-Hop header. + pub fn options(&self) -> &'a [u8] { + self.buffer.as_ref() + } +} + +impl + AsMut<[u8]> + ?Sized> Header<&mut T> { + /// Return a mutable pointer to the options of the IPv6 Hop-by-Hop header. + pub fn options_mut(&mut self) -> &mut [u8] { + self.buffer.as_mut() + } +} + +/// A high-level representation of an IPv6 Hop-by-Hop Header. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Repr<'a> { + pub options: Vec, { config::IPV6_HBH_MAX_OPTIONS }>, +} + +impl<'a> Repr<'a> { + /// Parse an IPv6 Hop-by-Hop Header and return a high-level representation. + pub fn parse(header: &'a Header<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + header.check_len()?; + + let mut options = Vec::new(); + + let iter = Ipv6OptionsIterator::new(header.options()); + + for option in iter { + let option = option?; + + if let Err(e) = options.push(option) { + net_trace!("error when parsing hop-by-hop options: {}", e); + break; + } + } + + Ok(Self { options }) + } + + /// Return the length, in bytes, of a header that will be emitted from this high-level + /// representation. + pub fn buffer_len(&self) -> usize { + self.options.iter().map(|o| o.buffer_len()).sum() + } + + /// Emit a high-level representation into an IPv6 Hop-by-Hop Header. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { + let mut buffer = header.options_mut(); + + for opt in &self.options { + opt.emit(&mut Ipv6Option::new_unchecked( + &mut buffer[..opt.buffer_len()], + )); + buffer = &mut buffer[opt.buffer_len()..]; + } + } + + /// The hop-by-hop header containing a MLDv2 router alert option + pub fn mldv2_router_alert() -> Self { + let mut options = Vec::new(); + options + .push(Ipv6OptionRepr::RouterAlert( + RouterAlert::MulticastListenerDiscovery, + )) + .unwrap(); + Self { options } + } + + /// Append a PadN option to the vector of hop-by-hop options + pub fn push_padn_option(&mut self, n: u8) { + self.options.push(Ipv6OptionRepr::PadN(n)).unwrap(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::wire::Error; + + // A Hop-by-Hop Option header with a PadN option of option data length 4. + static REPR_PACKET_PAD4: [u8; 6] = [0x1, 0x4, 0x0, 0x0, 0x0, 0x0]; + + // A Hop-by-Hop Option header with a PadN option of option data length 12. + static REPR_PACKET_PAD12: [u8; 14] = [ + 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + #[test] + fn test_check_len() { + // zero byte buffer + assert_eq!( + Err(Error), + Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len() + ); + // valid + assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len()); + // valid + assert_eq!( + Ok(()), + Header::new_unchecked(&REPR_PACKET_PAD12).check_len() + ); + } + + #[test] + fn test_repr_parse_valid() { + let header = Header::new_unchecked(&REPR_PACKET_PAD4); + let repr = Repr::parse(&header).unwrap(); + + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(4)).unwrap(); + assert_eq!(repr, Repr { options }); + + let header = Header::new_unchecked(&REPR_PACKET_PAD12); + let repr = Repr::parse(&header).unwrap(); + + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(12)).unwrap(); + assert_eq!(repr, Repr { options }); + } + + #[test] + fn test_repr_emit() { + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(4)).unwrap(); + let repr = Repr { options }; + + let mut bytes = [0u8; 6]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + + assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..]); + + let mut options = Vec::new(); + options.push(Ipv6OptionRepr::PadN(12)).unwrap(); + let repr = Repr { options }; + + let mut bytes = [0u8; 14]; + let mut header = Header::new_unchecked(&mut bytes); + repr.emit(&mut header); + + assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..]); + } +} diff --git a/vendor/smoltcp/src/wire/ipv6option.rs b/vendor/smoltcp/src/wire/ipv6option.rs new file mode 100644 index 00000000..25dbeea1 --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv6option.rs @@ -0,0 +1,735 @@ +use super::{Error, Result}; +#[cfg(feature = "proto-rpl")] +use super::{RplHopByHopPacket, RplHopByHopRepr}; + +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +enum_with_unknown! { + /// IPv6 Extension Header Option Type + pub enum Type(u8) { + /// 1 byte of padding + Pad1 = 0, + /// Multiple bytes of padding + PadN = 1, + /// Router Alert + RouterAlert = 5, + /// RPL Option + Rpl = 0x63, + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Type::Pad1 => write!(f, "Pad1"), + Type::PadN => write!(f, "PadN"), + Type::Rpl => write!(f, "RPL"), + Type::RouterAlert => write!(f, "RouterAlert"), + Type::Unknown(id) => write!(f, "{id}"), + } + } +} + +enum_with_unknown! { + /// A high-level representation of an IPv6 Router Alert Header Option. + /// + /// Router Alert options always contain exactly one `u16`; see [RFC 2711 § 2.1]. + /// + /// [RFC 2711 § 2.1]: https://tools.ietf.org/html/rfc2711#section-2.1 + pub enum RouterAlert(u16) { + MulticastListenerDiscovery = 0, + Rsvp = 1, + ActiveNetworks = 2, + } +} + +impl RouterAlert { + /// Per [RFC 2711 § 2.1], Router Alert options always have 2 bytes of data. + /// + /// [RFC 2711 § 2.1]: https://tools.ietf.org/html/rfc2711#section-2.1 + pub const DATA_LEN: u8 = 2; +} + +/// Action required when parsing the given IPv6 Extension +/// Header Option Type fails +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FailureType { + /// Skip this option and continue processing the packet + Skip = 0b00000000, + /// Discard the containing packet + Discard = 0b01000000, + /// Discard the containing packet and notify the sender + DiscardSendAll = 0b10000000, + /// Discard the containing packet and only notify the sender + /// if the sender is a unicast address + DiscardSendUnicast = 0b11000000, +} + +impl From for FailureType { + fn from(value: u8) -> FailureType { + match value & 0b11000000 { + 0b00000000 => FailureType::Skip, + 0b01000000 => FailureType::Discard, + 0b10000000 => FailureType::DiscardSendAll, + 0b11000000 => FailureType::DiscardSendUnicast, + _ => unreachable!(), + } + } +} + +impl From for u8 { + fn from(value: FailureType) -> Self { + match value { + FailureType::Skip => 0b00000000, + FailureType::Discard => 0b01000000, + FailureType::DiscardSendAll => 0b10000000, + FailureType::DiscardSendUnicast => 0b11000000, + } + } +} + +impl fmt::Display for FailureType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + FailureType::Skip => write!(f, "skip"), + FailureType::Discard => write!(f, "discard"), + FailureType::DiscardSendAll => write!(f, "discard and send error"), + FailureType::DiscardSendUnicast => write!(f, "discard and send error if unicast"), + } + } +} + +impl From for FailureType { + fn from(other: Type) -> FailureType { + let raw: u8 = other.into(); + Self::from(raw & 0b11000000u8) + } +} + +/// A read/write wrapper around an IPv6 Extension Header Option. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Ipv6Option> { + buffer: T, +} + +// Format of Option +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - +// | Option Type | Opt Data Len | Option Data +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - +// +// +// See https://tools.ietf.org/html/rfc8200#section-4.2 for details. +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + // 8-bit identifier of the type of option. + pub const TYPE: usize = 0; + // 8-bit unsigned integer. Length of the DATA field of this option, in octets. + pub const LENGTH: usize = 1; + // Variable-length field. Option-Type-specific data. + pub const fn DATA(length: u8) -> Field { + 2..length as usize + 2 + } +} + +impl> Ipv6Option { + /// Create a raw octet buffer with an IPv6 Extension Header Option structure. + pub const fn new_unchecked(buffer: T) -> Ipv6Option { + Ipv6Option { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let opt = Self::new_unchecked(buffer); + opt.check_len()?; + Ok(opt) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_data_len]. + /// + /// [set_data_len]: #method.set_data_len + pub fn check_len(&self) -> Result<()> { + let data = self.buffer.as_ref(); + let len = data.len(); + + if len < field::LENGTH { + return Err(Error); + } + + if self.option_type() == Type::Pad1 { + return Ok(()); + } + + if len == field::LENGTH { + return Err(Error); + } + + let df = field::DATA(data[field::LENGTH]); + + if len < df.end { + return Err(Error); + } + + Ok(()) + } + + /// Consume the ipv6 option, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the option type. + #[inline] + pub fn option_type(&self) -> Type { + let data = self.buffer.as_ref(); + Type::from(data[field::TYPE]) + } + + /// Return the length of the data. + /// + /// # Panics + /// This function panics if this is an 1-byte padding option. + #[inline] + pub fn data_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::LENGTH] + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Ipv6Option<&'a T> { + /// Return the option data. + /// + /// # Panics + /// This function panics if this is an 1-byte padding option. + #[inline] + pub fn data(&self) -> &'a [u8] { + let len = self.data_len(); + let data = self.buffer.as_ref(); + &data[field::DATA(len)] + } +} + +impl + AsMut<[u8]>> Ipv6Option { + /// Set the option type. + #[inline] + pub fn set_option_type(&mut self, value: Type) { + let data = self.buffer.as_mut(); + data[field::TYPE] = value.into(); + } + + /// Set the option data length. + /// + /// # Panics + /// This function panics if this is an 1-byte padding option. + #[inline] + pub fn set_data_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::LENGTH] = value; + } +} + +impl + AsMut<[u8]> + ?Sized> Ipv6Option<&mut T> { + /// Return a mutable pointer to the option data. + /// + /// # Panics + /// This function panics if this is an 1-byte padding option. + #[inline] + pub fn data_mut(&mut self) -> &mut [u8] { + let len = self.data_len(); + let data = self.buffer.as_mut(); + &mut data[field::DATA(len)] + } +} + +impl + ?Sized> fmt::Display for Ipv6Option<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "IPv6 Extension Option ({err})")?; + Ok(()) + } + } + } +} + +/// A high-level representation of an IPv6 Extension Header Option. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Repr<'a> { + Pad1, + PadN(u8), + RouterAlert(RouterAlert), + #[cfg(feature = "proto-rpl")] + Rpl(RplHopByHopRepr), + Unknown { + type_: Type, + length: u8, + data: &'a [u8], + }, +} + +impl<'a> Repr<'a> { + /// Parse an IPv6 Extension Header Option and return a high-level representation. + pub fn parse(opt: &Ipv6Option<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + opt.check_len()?; + match opt.option_type() { + Type::Pad1 => Ok(Repr::Pad1), + Type::PadN => Ok(Repr::PadN(opt.data_len())), + Type::RouterAlert => { + if opt.data_len() == RouterAlert::DATA_LEN { + let raw = NetworkEndian::read_u16(opt.data()); + Ok(Repr::RouterAlert(RouterAlert::from(raw))) + } else { + Err(Error) + } + } + #[cfg(feature = "proto-rpl")] + Type::Rpl => Ok(Repr::Rpl(RplHopByHopRepr::parse( + &RplHopByHopPacket::new_checked(opt.data())?, + ))), + #[cfg(not(feature = "proto-rpl"))] + Type::Rpl => Ok(Repr::Unknown { + type_: Type::Rpl, + length: opt.data_len(), + data: opt.data(), + }), + + unknown_type @ Type::Unknown(_) => Ok(Repr::Unknown { + type_: unknown_type, + length: opt.data_len(), + data: opt.data(), + }), + } + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + match *self { + Repr::Pad1 => 1, + Repr::PadN(length) => field::DATA(length).end, + Repr::RouterAlert(_) => field::DATA(RouterAlert::DATA_LEN).end, + #[cfg(feature = "proto-rpl")] + Repr::Rpl(opt) => field::DATA(opt.buffer_len() as u8).end, + Repr::Unknown { length, .. } => field::DATA(length).end, + } + } + + /// Emit a high-level representation into an IPv6 Extension Header Option. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, opt: &mut Ipv6Option<&'a mut T>) { + match *self { + Repr::Pad1 => opt.set_option_type(Type::Pad1), + Repr::PadN(len) => { + opt.set_option_type(Type::PadN); + opt.set_data_len(len); + // Ensure all padding bytes are set to zero. + for x in opt.data_mut().iter_mut() { + *x = 0 + } + } + Repr::RouterAlert(router_alert) => { + opt.set_option_type(Type::RouterAlert); + opt.set_data_len(RouterAlert::DATA_LEN); + NetworkEndian::write_u16(opt.data_mut(), router_alert.into()); + } + #[cfg(feature = "proto-rpl")] + Repr::Rpl(rpl) => { + opt.set_option_type(Type::Rpl); + opt.set_data_len(4); + rpl.emit(&mut crate::wire::RplHopByHopPacket::new_unchecked( + opt.data_mut(), + )); + } + Repr::Unknown { + type_, + length, + data, + } => { + opt.set_option_type(type_); + opt.set_data_len(length); + opt.data_mut().copy_from_slice(&data[..length as usize]); + } + } + } +} + +/// A iterator for IPv6 options. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Ipv6OptionsIterator<'a> { + pos: usize, + length: usize, + data: &'a [u8], + hit_error: bool, +} + +impl<'a> Ipv6OptionsIterator<'a> { + /// Create a new `Ipv6OptionsIterator`, used to iterate over the + /// options contained in a IPv6 Extension Header (e.g. the Hop-by-Hop + /// header). + pub fn new(data: &'a [u8]) -> Ipv6OptionsIterator<'a> { + let length = data.len(); + Ipv6OptionsIterator { + pos: 0, + hit_error: false, + length, + data, + } + } +} + +impl<'a> Iterator for Ipv6OptionsIterator<'a> { + type Item = Result>; + + fn next(&mut self) -> Option { + if self.pos < self.length && !self.hit_error { + // If we still have data to parse and we have not previously + // hit an error, attempt to parse the next option. + match Ipv6Option::new_checked(&self.data[self.pos..]) { + Ok(hdr) => match Repr::parse(&hdr) { + Ok(repr) => { + self.pos += repr.buffer_len(); + Some(Ok(repr)) + } + Err(e) => { + self.hit_error = true; + Some(Err(e)) + } + }, + Err(e) => { + self.hit_error = true; + Some(Err(e)) + } + } + } else { + // If we failed to parse a previous option or hit the end of the + // buffer, we do not continue to iterate. + None + } + } +} + +impl<'a> fmt::Display for Repr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "IPv6 Option ")?; + match *self { + Repr::Pad1 => write!(f, "{} ", Type::Pad1), + Repr::PadN(len) => write!(f, "{} length={} ", Type::PadN, len), + Repr::RouterAlert(alert) => write!(f, "{} value={:?}", Type::RouterAlert, alert), + #[cfg(feature = "proto-rpl")] + Repr::Rpl(rpl) => write!(f, "{} {rpl}", Type::Rpl), + Repr::Unknown { type_, length, .. } => write!(f, "{type_} length={length} "), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + static IPV6OPTION_BYTES_PAD1: [u8; 1] = [0x0]; + static IPV6OPTION_BYTES_PADN: [u8; 3] = [0x1, 0x1, 0x0]; + static IPV6OPTION_BYTES_UNKNOWN: [u8; 5] = [0xff, 0x3, 0x0, 0x0, 0x0]; + static IPV6OPTION_BYTES_ROUTER_ALERT_MLD: [u8; 4] = [0x05, 0x02, 0x00, 0x00]; + static IPV6OPTION_BYTES_ROUTER_ALERT_RSVP: [u8; 4] = [0x05, 0x02, 0x00, 0x01]; + static IPV6OPTION_BYTES_ROUTER_ALERT_ACTIVE_NETWORKS: [u8; 4] = [0x05, 0x02, 0x00, 0x02]; + static IPV6OPTION_BYTES_ROUTER_ALERT_UNKNOWN: [u8; 4] = [0x05, 0x02, 0xbe, 0xef]; + #[cfg(feature = "proto-rpl")] + static IPV6OPTION_BYTES_RPL: [u8; 6] = [0x63, 0x04, 0x00, 0x1e, 0x08, 0x00]; + + #[test] + fn test_check_len() { + let bytes = [0u8]; + // zero byte buffer + assert_eq!( + Err(Error), + Ipv6Option::new_unchecked(&bytes[..0]).check_len() + ); + // pad1 + assert_eq!( + Ok(()), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1).check_len() + ); + + // padn with truncated data + assert_eq!( + Err(Error), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN[..2]).check_len() + ); + // padn + assert_eq!( + Ok(()), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN).check_len() + ); + + // router alert with truncated data + assert_eq!( + Err(Error), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD[..3]).check_len() + ); + // router alert + assert_eq!( + Ok(()), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD).check_len() + ); + + // unknown option type with truncated data + assert_eq!( + Err(Error), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..4]).check_len() + ); + assert_eq!( + Err(Error), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN[..1]).check_len() + ); + // unknown type + assert_eq!( + Ok(()), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN).check_len() + ); + + #[cfg(feature = "proto-rpl")] + { + assert_eq!( + Ok(()), + Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL).check_len() + ); + } + } + + #[test] + #[should_panic(expected = "index out of bounds")] + fn test_data_len() { + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1); + opt.data_len(); + } + + #[test] + fn test_option_deconstruct() { + // one octet of padding + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1); + assert_eq!(opt.option_type(), Type::Pad1); + + // two octets of padding + let bytes: [u8; 2] = [0x1, 0x0]; + let opt = Ipv6Option::new_unchecked(&bytes); + assert_eq!(opt.option_type(), Type::PadN); + assert_eq!(opt.data_len(), 0); + + // three octets of padding + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN); + assert_eq!(opt.option_type(), Type::PadN); + assert_eq!(opt.data_len(), 1); + assert_eq!(opt.data(), &[0]); + + // extra bytes in buffer + let bytes: [u8; 10] = [0x1, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff]; + let opt = Ipv6Option::new_unchecked(&bytes); + assert_eq!(opt.option_type(), Type::PadN); + assert_eq!(opt.data_len(), 7); + assert_eq!(opt.data(), &[0, 0, 0, 0, 0, 0, 0]); + + // router alert + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD); + assert_eq!(opt.option_type(), Type::RouterAlert); + assert_eq!(opt.data_len(), 2); + assert_eq!(opt.data(), &[0, 0]); + + // unrecognized option + let bytes: [u8; 1] = [0xff]; + let opt = Ipv6Option::new_unchecked(&bytes); + assert_eq!(opt.option_type(), Type::Unknown(255)); + + // unrecognized option without length and data + assert_eq!(Ipv6Option::new_checked(&bytes), Err(Error)); + + #[cfg(feature = "proto-rpl")] + { + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL); + assert_eq!(opt.option_type(), Type::Rpl); + assert_eq!(opt.data_len(), 4); + assert_eq!(opt.data(), &[0x00, 0x1e, 0x08, 0x00]); + } + } + + #[test] + fn test_option_parse() { + // one octet of padding + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PAD1); + let pad1 = Repr::parse(&opt).unwrap(); + assert_eq!(pad1, Repr::Pad1); + assert_eq!(pad1.buffer_len(), 1); + + // two or more octets of padding + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_PADN); + let padn = Repr::parse(&opt).unwrap(); + assert_eq!(padn, Repr::PadN(1)); + assert_eq!(padn.buffer_len(), 3); + + // router alert (MLD) + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_MLD); + let alert = Repr::parse(&opt).unwrap(); + assert_eq!( + alert, + Repr::RouterAlert(RouterAlert::MulticastListenerDiscovery) + ); + assert_eq!(alert.buffer_len(), 4); + + // router alert (RSVP) + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_RSVP); + let alert = Repr::parse(&opt).unwrap(); + assert_eq!(alert, Repr::RouterAlert(RouterAlert::Rsvp)); + assert_eq!(alert.buffer_len(), 4); + + // router alert (active networks) + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_ACTIVE_NETWORKS); + let alert = Repr::parse(&opt).unwrap(); + assert_eq!(alert, Repr::RouterAlert(RouterAlert::ActiveNetworks)); + assert_eq!(alert.buffer_len(), 4); + + // router alert (unknown) + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_ROUTER_ALERT_UNKNOWN); + let alert = Repr::parse(&opt).unwrap(); + assert_eq!(alert, Repr::RouterAlert(RouterAlert::Unknown(0xbeef))); + assert_eq!(alert.buffer_len(), 4); + + // router alert (incorrect data length) + let opt = Ipv6Option::new_unchecked(&[0x05, 0x03, 0x00, 0x00, 0x00]); + let alert = Repr::parse(&opt); + assert_eq!(alert, Err(Error)); + + // unrecognized option type + let data = [0u8; 3]; + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_UNKNOWN); + let unknown = Repr::parse(&opt).unwrap(); + assert_eq!( + unknown, + Repr::Unknown { + type_: Type::Unknown(255), + length: 3, + data: &data + } + ); + + #[cfg(feature = "proto-rpl")] + { + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL); + let rpl = Repr::parse(&opt).unwrap(); + + assert_eq!( + rpl, + Repr::Rpl(crate::wire::RplHopByHopRepr { + down: false, + rank_error: false, + forwarding_error: false, + instance_id: crate::wire::RplInstanceId::from(0x1e), + sender_rank: 0x0800, + }) + ); + } + } + + #[test] + fn test_option_emit() { + let repr = Repr::Pad1; + let mut bytes = [255u8; 1]; // don't assume bytes are initialized to zero + let mut opt = Ipv6Option::new_unchecked(&mut bytes); + repr.emit(&mut opt); + assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PAD1); + + let repr = Repr::PadN(1); + let mut bytes = [255u8; 3]; // don't assume bytes are initialized to zero + let mut opt = Ipv6Option::new_unchecked(&mut bytes); + repr.emit(&mut opt); + assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_PADN); + + let repr = Repr::RouterAlert(RouterAlert::MulticastListenerDiscovery); + let mut bytes = [255u8; 4]; // don't assume bytes are initialized to zero + let mut opt = Ipv6Option::new_unchecked(&mut bytes); + repr.emit(&mut opt); + assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_ROUTER_ALERT_MLD); + + let data = [0u8; 3]; + let repr = Repr::Unknown { + type_: Type::Unknown(255), + length: 3, + data: &data, + }; + let mut bytes = [254u8; 5]; // don't assume bytes are initialized to zero + let mut opt = Ipv6Option::new_unchecked(&mut bytes); + repr.emit(&mut opt); + assert_eq!(opt.into_inner(), &IPV6OPTION_BYTES_UNKNOWN); + + #[cfg(feature = "proto-rpl")] + { + let opt = Ipv6Option::new_unchecked(&IPV6OPTION_BYTES_RPL); + let rpl = Repr::parse(&opt).unwrap(); + let mut bytes = [0u8; 6]; + rpl.emit(&mut Ipv6Option::new_unchecked(&mut bytes)); + + assert_eq!(&bytes, &IPV6OPTION_BYTES_RPL); + } + } + + #[test] + fn test_failure_type() { + let mut failure_type: FailureType = Type::Pad1.into(); + assert_eq!(failure_type, FailureType::Skip); + failure_type = Type::PadN.into(); + assert_eq!(failure_type, FailureType::Skip); + failure_type = Type::RouterAlert.into(); + assert_eq!(failure_type, FailureType::Skip); + failure_type = Type::Unknown(0b01000001).into(); + assert_eq!(failure_type, FailureType::Discard); + failure_type = Type::Unknown(0b10100000).into(); + assert_eq!(failure_type, FailureType::DiscardSendAll); + failure_type = Type::Unknown(0b11000100).into(); + assert_eq!(failure_type, FailureType::DiscardSendUnicast); + } + + #[test] + fn test_options_iter() { + let options = [ + 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x11, 0x00, 0x05, + 0x02, 0x00, 0x01, 0x01, 0x08, 0x00, + ]; + + let iterator = Ipv6OptionsIterator::new(&options); + for (i, opt) in iterator.enumerate() { + match (i, opt) { + (0, Ok(Repr::Pad1)) => continue, + (1, Ok(Repr::PadN(1))) => continue, + (2, Ok(Repr::PadN(2))) => continue, + (3, Ok(Repr::PadN(0))) => continue, + (4, Ok(Repr::Pad1)) => continue, + ( + 5, + Ok(Repr::Unknown { + type_: Type::Unknown(0x11), + length: 0, + .. + }), + ) => continue, + (6, Ok(Repr::RouterAlert(RouterAlert::Rsvp))) => continue, + (7, Err(Error)) => continue, + (i, res) => panic!("Unexpected option `{res:?}` at index {i}"), + } + } + } +} diff --git a/vendor/smoltcp/src/wire/ipv6routing.rs b/vendor/smoltcp/src/wire/ipv6routing.rs new file mode 100644 index 00000000..deabfa3c --- /dev/null +++ b/vendor/smoltcp/src/wire/ipv6routing.rs @@ -0,0 +1,607 @@ +use super::{Error, Result}; +use core::fmt; + +use crate::wire::Ipv6Address as Address; + +enum_with_unknown! { + /// IPv6 Extension Routing Header Routing Type + pub enum Type(u8) { + /// Source Route (DEPRECATED) + /// + /// See for details. + Type0 = 0, + /// Nimrod (DEPRECATED 2009-05-06) + Nimrod = 1, + /// Type 2 Routing Header for Mobile IPv6 + /// + /// See for details. + Type2 = 2, + /// RPL Source Routing Header + /// + /// See for details. + Rpl = 3, + /// RFC3692-style Experiment 1 + /// + /// See for details. + Experiment1 = 253, + /// RFC3692-style Experiment 2 + /// + /// See for details. + Experiment2 = 254, + /// Reserved for future use + Reserved = 252 + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Type::Type0 => write!(f, "Type0"), + Type::Nimrod => write!(f, "Nimrod"), + Type::Type2 => write!(f, "Type2"), + Type::Rpl => write!(f, "Rpl"), + Type::Experiment1 => write!(f, "Experiment1"), + Type::Experiment2 => write!(f, "Experiment2"), + Type::Reserved => write!(f, "Reserved"), + Type::Unknown(id) => write!(f, "{id}"), + } + } +} + +/// A read/write wrapper around an IPv6 Routing Header buffer. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Header> { + buffer: T, +} + +// Format of the Routing Header +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Next Header | Hdr Ext Len | Routing Type | Segments Left | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// . . +// . type-specific data . +// . . +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// +// See https://tools.ietf.org/html/rfc8200#section-4.4 for details. +// +// **NOTE**: The fields start counting after the header length field. +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + // Minimum size of the header. + pub const MIN_HEADER_SIZE: usize = 2; + + // 8-bit identifier of a particular Routing header variant. + pub const TYPE: usize = 0; + // 8-bit unsigned integer. The number of route segments remaining. + pub const SEG_LEFT: usize = 1; + + // The Type 2 Routing Header has the following format: + // + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Next Header | Hdr Ext Len=2 | Routing Type=2|Segments Left=1| + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | | + // + Home Address + + // | | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // 16-byte field containing the home address of the destination mobile node. + pub const HOME_ADDRESS: Field = 6..22; + + // The RPL Source Routing Header has the following format: + // + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Next Header | Hdr Ext Len | Routing Type | Segments Left | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | CmprI | CmprE | Pad | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // . . + // . Addresses[1..n] . + // . . + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // 8-bit field containing the CmprI and CmprE values. + pub const CMPR: usize = 2; + // 8-bit field containing the Pad value. + pub const PAD: usize = 3; + // Variable length field containing addresses + pub const ADDRESSES: usize = 6; +} + +/// Core getter methods relevant to any routing type. +impl> Header { + /// Create a raw octet buffer with an IPv6 Routing Header structure. + pub const fn new_unchecked(buffer: T) -> Header { + Header { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let header = Self::new_unchecked(buffer); + header.check_len()?; + Ok(header) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_header_len]. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::MIN_HEADER_SIZE { + return Err(Error); + } + + match self.routing_type() { + Type::Type2 if len < field::HOME_ADDRESS.end => return Err(Error), + Type::Rpl if len < field::ADDRESSES => return Err(Error), + _ => (), + } + + Ok(()) + } + + /// Consume the header, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the routing type field. + #[inline] + pub fn routing_type(&self) -> Type { + let data = self.buffer.as_ref(); + Type::from(data[field::TYPE]) + } + + /// Return the segments left field. + #[inline] + pub fn segments_left(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::SEG_LEFT] + } +} + +/// Getter methods for the Type 2 Routing Header routing type. +impl> Header { + /// Return the IPv6 Home Address + /// + /// # Panics + /// This function may panic if this header is not the Type2 Routing Header routing type. + pub fn home_address(&self) -> Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::HOME_ADDRESS].try_into().unwrap()) + } +} + +/// Getter methods for the RPL Source Routing Header routing type. +impl> Header { + /// Return the number of prefix octets elided from addresses[1..n-1]. + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn cmpr_i(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::CMPR] >> 4 + } + + /// Return the number of prefix octets elided from the last address (`addresses[n]`). + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn cmpr_e(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::CMPR] & 0xf + } + + /// Return the number of octets used for padding after `addresses[n]`. + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn pad(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::PAD] >> 4 + } + + /// Return the address vector in bytes + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn addresses(&self) -> &[u8] { + let data = self.buffer.as_ref(); + &data[field::ADDRESSES..] + } +} + +/// Core setter methods relevant to any routing type. +impl + AsMut<[u8]>> Header { + /// Set the routing type. + #[inline] + pub fn set_routing_type(&mut self, value: Type) { + let data = self.buffer.as_mut(); + data[field::TYPE] = value.into(); + } + + /// Set the segments left field. + #[inline] + pub fn set_segments_left(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::SEG_LEFT] = value; + } + + /// Initialize reserved fields to 0. + /// + /// # Panics + /// This function may panic if the routing type is not set. + #[inline] + pub fn clear_reserved(&mut self) { + let routing_type = self.routing_type(); + let data = self.buffer.as_mut(); + + match routing_type { + Type::Type2 => { + data[2] = 0; + data[3] = 0; + data[4] = 0; + data[5] = 0; + } + Type::Rpl => { + // Retain the higher order 4 bits of the padding field + data[field::PAD] &= 0xF0; + data[4] = 0; + data[5] = 0; + } + + _ => panic!("Unrecognized routing type when clearing reserved fields."), + } + } +} + +/// Setter methods for the RPL Source Routing Header routing type. +impl + AsMut<[u8]>> Header { + /// Set the Ipv6 Home Address + /// + /// # Panics + /// This function may panic if this header is not the Type 2 Routing Header routing type. + pub fn set_home_address(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::HOME_ADDRESS].copy_from_slice(&value.octets()); + } +} + +/// Setter methods for the RPL Source Routing Header routing type. +impl + AsMut<[u8]>> Header { + /// Set the number of prefix octets elided from addresses[1..n-1]. + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn set_cmpr_i(&mut self, value: u8) { + let data = self.buffer.as_mut(); + let raw = (value << 4) | (data[field::CMPR] & 0xF); + data[field::CMPR] = raw; + } + + /// Set the number of prefix octets elided from the last address (`addresses[n]`). + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn set_cmpr_e(&mut self, value: u8) { + let data = self.buffer.as_mut(); + let raw = (value & 0xF) | (data[field::CMPR] & 0xF0); + data[field::CMPR] = raw; + } + + /// Set the number of octets used for padding after `addresses[n]`. + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn set_pad(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::PAD] = value << 4; + } + + /// Set address data + /// + /// # Panics + /// This function may panic if this header is not the RPL Source Routing Header routing type. + pub fn set_addresses(&mut self, value: &[u8]) { + let data = self.buffer.as_mut(); + let addresses = &mut data[field::ADDRESSES..]; + addresses.copy_from_slice(value); + } +} + +impl + ?Sized> fmt::Display for Header<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "IPv6 Routing ({err})")?; + Ok(()) + } + } + } +} + +/// A high-level representation of an IPv6 Routing Header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Repr<'a> { + Type2 { + /// Number of route segments remaining. + segments_left: u8, + /// The home address of the destination mobile node. + home_address: Address, + }, + Rpl { + /// Number of route segments remaining. + segments_left: u8, + /// Number of prefix octets from each segment, except the last segment, that are elided. + cmpr_i: u8, + /// Number of prefix octets from the last segment that are elided. + cmpr_e: u8, + /// Number of octets that are used for padding after `address[n]` at the end of the + /// RPL Source Route Header. + pad: u8, + /// Vector of addresses, numbered 1 to `n`. + addresses: &'a [u8], + }, +} + +impl<'a> Repr<'a> { + /// Parse an IPv6 Routing Header and return a high-level representation. + pub fn parse(header: &'a Header<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + header.check_len()?; + match header.routing_type() { + Type::Type2 => Ok(Repr::Type2 { + segments_left: header.segments_left(), + home_address: header.home_address(), + }), + Type::Rpl => Ok(Repr::Rpl { + segments_left: header.segments_left(), + cmpr_i: header.cmpr_i(), + cmpr_e: header.cmpr_e(), + pad: header.pad(), + addresses: header.addresses(), + }), + + _ => Err(Error), + } + } + + /// Return the length, in bytes, of a header that will be emitted from this high-level + /// representation. + pub const fn buffer_len(&self) -> usize { + match self { + // Routing Type + Segments Left + Reserved + Home Address + Repr::Type2 { home_address, .. } => 2 + 4 + home_address.octets().len(), + Repr::Rpl { addresses, .. } => 2 + 4 + addresses.len(), + } + } + + /// Emit a high-level representation into an IPv6 Routing Header. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { + match *self { + Repr::Type2 { + segments_left, + home_address, + } => { + header.set_routing_type(Type::Type2); + header.set_segments_left(segments_left); + header.clear_reserved(); + header.set_home_address(home_address); + } + Repr::Rpl { + segments_left, + cmpr_i, + cmpr_e, + pad, + addresses, + } => { + header.set_routing_type(Type::Rpl); + header.set_segments_left(segments_left); + header.set_cmpr_i(cmpr_i); + header.set_cmpr_e(cmpr_e); + header.set_pad(pad); + header.clear_reserved(); + header.set_addresses(addresses); + } + } + } +} + +impl<'a> fmt::Display for Repr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Repr::Type2 { + segments_left, + home_address, + } => { + write!( + f, + "IPv6 Routing type={} seg_left={} home_address={}", + Type::Type2, + segments_left, + home_address + ) + } + Repr::Rpl { + segments_left, + cmpr_i, + cmpr_e, + pad, + .. + } => { + write!( + f, + "IPv6 Routing type={} seg_left={} cmpr_i={} cmpr_e={} pad={}", + Type::Rpl, + segments_left, + cmpr_i, + cmpr_e, + pad + ) + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + // A Type 2 Routing Header + static BYTES_TYPE2: [u8; 22] = [ + 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1, + ]; + + // A representation of a Type 2 Routing header + static REPR_TYPE2: Repr = Repr::Type2 { + segments_left: 1, + home_address: Address::LOCALHOST, + }; + + // A Source Routing Header with full IPv6 addresses in bytes + static BYTES_SRH_FULL: [u8; 38] = [ + 0x3, 0x2, 0x0, 0x0, 0x0, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x2, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3, 0x1, + ]; + + // A representation of a Source Routing Header with full IPv6 addresses + static REPR_SRH_FULL: Repr = Repr::Rpl { + segments_left: 2, + cmpr_i: 0, + cmpr_e: 0, + pad: 0, + addresses: &[ + 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1, + ], + }; + + // A Source Routing Header with elided IPv6 addresses in bytes + static BYTES_SRH_ELIDED: [u8; 14] = [ + 0x3, 0x2, 0xfe, 0x50, 0x0, 0x0, 0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + ]; + + // A representation of a Source Routing Header with elided IPv6 addresses + static REPR_SRH_ELIDED: Repr = Repr::Rpl { + segments_left: 2, + cmpr_i: 15, + cmpr_e: 14, + pad: 5, + addresses: &[0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0], + }; + + #[test] + fn test_check_len() { + // less than min header size + assert_eq!( + Err(Error), + Header::new_unchecked(&BYTES_TYPE2[..3]).check_len() + ); + assert_eq!( + Err(Error), + Header::new_unchecked(&BYTES_SRH_FULL[..3]).check_len() + ); + assert_eq!( + Err(Error), + Header::new_unchecked(&BYTES_SRH_ELIDED[..3]).check_len() + ); + // valid + assert_eq!(Ok(()), Header::new_unchecked(&BYTES_TYPE2[..]).check_len()); + assert_eq!( + Ok(()), + Header::new_unchecked(&BYTES_SRH_FULL[..]).check_len() + ); + assert_eq!( + Ok(()), + Header::new_unchecked(&BYTES_SRH_ELIDED[..]).check_len() + ); + } + + #[test] + fn test_header_deconstruct() { + let header = Header::new_unchecked(&BYTES_TYPE2[..]); + assert_eq!(header.routing_type(), Type::Type2); + assert_eq!(header.segments_left(), 1); + assert_eq!(header.home_address(), Address::LOCALHOST); + + let header = Header::new_unchecked(&BYTES_SRH_FULL[..]); + assert_eq!(header.routing_type(), Type::Rpl); + assert_eq!(header.segments_left(), 2); + assert_eq!(header.addresses(), &BYTES_SRH_FULL[6..]); + + let header = Header::new_unchecked(&BYTES_SRH_ELIDED[..]); + assert_eq!(header.routing_type(), Type::Rpl); + assert_eq!(header.segments_left(), 2); + assert_eq!(header.addresses(), &BYTES_SRH_ELIDED[6..]); + } + + #[test] + fn test_repr_parse_valid() { + let header = Header::new_checked(&BYTES_TYPE2[..]).unwrap(); + let repr = Repr::parse(&header).unwrap(); + assert_eq!(repr, REPR_TYPE2); + + let header = Header::new_checked(&BYTES_SRH_FULL[..]).unwrap(); + let repr = Repr::parse(&header).unwrap(); + assert_eq!(repr, REPR_SRH_FULL); + + let header = Header::new_checked(&BYTES_SRH_ELIDED[..]).unwrap(); + let repr = Repr::parse(&header).unwrap(); + assert_eq!(repr, REPR_SRH_ELIDED); + } + + #[test] + fn test_repr_emit() { + let mut bytes = [0xFFu8; 22]; + let mut header = Header::new_unchecked(&mut bytes[..]); + REPR_TYPE2.emit(&mut header); + assert_eq!(header.into_inner(), &BYTES_TYPE2[..]); + + let mut bytes = [0xFFu8; 38]; + let mut header = Header::new_unchecked(&mut bytes[..]); + REPR_SRH_FULL.emit(&mut header); + assert_eq!(header.into_inner(), &BYTES_SRH_FULL[..]); + + let mut bytes = [0xFFu8; 14]; + let mut header = Header::new_unchecked(&mut bytes[..]); + REPR_SRH_ELIDED.emit(&mut header); + assert_eq!(header.into_inner(), &BYTES_SRH_ELIDED[..]); + } + + #[test] + fn test_buffer_len() { + assert_eq!(REPR_TYPE2.buffer_len(), 22); + assert_eq!(REPR_SRH_FULL.buffer_len(), 38); + assert_eq!(REPR_SRH_ELIDED.buffer_len(), 14); + } +} diff --git a/vendor/smoltcp/src/wire/mld.rs b/vendor/smoltcp/src/wire/mld.rs new file mode 100644 index 00000000..b3fd6335 --- /dev/null +++ b/vendor/smoltcp/src/wire/mld.rs @@ -0,0 +1,638 @@ +// Packet implementation for the Multicast Listener Discovery +// protocol. See [RFC 3810] and [RFC 2710]. +// +// [RFC 3810]: https://tools.ietf.org/html/rfc3810 +// [RFC 2710]: https://tools.ietf.org/html/rfc2710 + +use byteorder::{ByteOrder, NetworkEndian}; + +use super::{Error, Result}; +use crate::wire::Ipv6Address; +use crate::wire::icmpv6::{Message, Packet, field}; + +enum_with_unknown! { + /// MLDv2 Multicast Listener Report Record Type. See [RFC 3810 § 5.2.12] for + /// more details. + /// + /// [RFC 3810 § 5.2.12]: https://tools.ietf.org/html/rfc3010#section-5.2.12 + pub enum RecordType(u8) { + /// Interface has a filter mode of INCLUDE for the specified multicast address. + ModeIsInclude = 0x01, + /// Interface has a filter mode of EXCLUDE for the specified multicast address. + ModeIsExclude = 0x02, + /// Interface has changed to a filter mode of INCLUDE for the specified + /// multicast address. + ChangeToInclude = 0x03, + /// Interface has changed to a filter mode of EXCLUDE for the specified + /// multicast address. + ChangeToExclude = 0x04, + /// Interface wishes to listen to the sources in the specified list. + AllowNewSources = 0x05, + /// Interface no longer wishes to listen to the sources in the specified list. + BlockOldSources = 0x06 + } +} + +/// Getters for the Multicast Listener Query message header. +/// See [RFC 3810 § 5.1]. +/// +/// [RFC 3810 § 5.1]: https://tools.ietf.org/html/rfc3010#section-5.1 +impl> Packet { + /// Return the maximum response code field. + #[inline] + pub fn max_resp_code(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::MAX_RESP_CODE]) + } + + /// Return the address being queried. + #[inline] + pub fn mcast_addr(&self) -> Ipv6Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::QUERY_MCAST_ADDR].try_into().unwrap()) + } + + /// Return the Suppress Router-Side Processing flag. + #[inline] + pub fn s_flag(&self) -> bool { + let data = self.buffer.as_ref(); + (data[field::SQRV] & 0x08) != 0 + } + + /// Return the Querier's Robustness Variable. + #[inline] + pub fn qrv(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::SQRV] & 0x7 + } + + /// Return the Querier's Query Interval Code. + #[inline] + pub fn qqic(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::QQIC] + } + + /// Return number of sources. + #[inline] + pub fn num_srcs(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::QUERY_NUM_SRCS]) + } +} + +/// Getters for the Multicast Listener Report message header. +/// See [RFC 3810 § 5.2]. +/// +/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2 +impl> Packet { + /// Return the number of Multicast Address Records. + #[inline] + pub fn nr_mcast_addr_rcrds(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::NR_MCAST_RCRDS]) + } +} + +/// Setters for the Multicast Listener Query message header. +/// See [RFC 3810 § 5.1]. +/// +/// [RFC 3810 § 5.1]: https://tools.ietf.org/html/rfc3010#section-5.1 +impl + AsMut<[u8]>> Packet { + /// Set the maximum response code field. + #[inline] + pub fn set_max_resp_code(&mut self, code: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::MAX_RESP_CODE], code); + } + + /// Set the address being queried. + #[inline] + pub fn set_mcast_addr(&mut self, addr: Ipv6Address) { + let data = self.buffer.as_mut(); + data[field::QUERY_MCAST_ADDR].copy_from_slice(&addr.octets()); + } + + /// Set the Suppress Router-Side Processing flag. + #[inline] + pub fn set_s_flag(&mut self) { + let data = self.buffer.as_mut(); + let current = data[field::SQRV]; + data[field::SQRV] = 0x8 | (current & 0x7); + } + + /// Clear the Suppress Router-Side Processing flag. + #[inline] + pub fn clear_s_flag(&mut self) { + let data = self.buffer.as_mut(); + data[field::SQRV] &= 0x7; + } + + /// Set the Querier's Robustness Variable. + #[inline] + pub fn set_qrv(&mut self, value: u8) { + assert!(value < 8); + let data = self.buffer.as_mut(); + data[field::SQRV] = (data[field::SQRV] & 0x8) | value & 0x7; + } + + /// Set the Querier's Query Interval Code. + #[inline] + pub fn set_qqic(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::QQIC] = value; + } + + /// Set number of sources. + #[inline] + pub fn set_num_srcs(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::QUERY_NUM_SRCS], value); + } +} + +/// Setters for the Multicast Listener Report message header. +/// See [RFC 3810 § 5.2]. +/// +/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2 +impl + AsMut<[u8]>> Packet { + /// Set the number of Multicast Address Records. + #[inline] + pub fn set_nr_mcast_addr_rcrds(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::NR_MCAST_RCRDS], value) + } +} + +/// A read/write wrapper around an MLDv2 Listener Report Message Address Record. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AddressRecord> { + buffer: T, +} + +impl> AddressRecord { + /// Imbue a raw octet buffer with a Address Record structure. + pub const fn new_unchecked(buffer: T) -> Self { + Self { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error::Truncated)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::RECORD_MCAST_ADDR.end { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } +} + +/// Getters for a MLDv2 Listener Report Message Address Record. +/// See [RFC 3810 § 5.2]. +/// +/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2 +impl> AddressRecord { + /// Return the record type for the given sources. + #[inline] + pub fn record_type(&self) -> RecordType { + let data = self.buffer.as_ref(); + RecordType::from(data[field::RECORD_TYPE]) + } + + /// Return the length of the auxiliary data. + #[inline] + pub fn aux_data_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::AUX_DATA_LEN] + } + + /// Return the number of sources field. + #[inline] + pub fn num_srcs(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::RECORD_NUM_SRCS]) + } + + /// Return the multicast address field. + #[inline] + pub fn mcast_addr(&self) -> Ipv6Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::RECORD_MCAST_ADDR].try_into().unwrap()) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> AddressRecord<&'a T> { + /// Return a pointer to the address records. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + &data[field::RECORD_MCAST_ADDR.end..] + } +} + +/// Setters for a MLDv2 Listener Report Message Address Record. +/// See [RFC 3810 § 5.2]. +/// +/// [RFC 3810 § 5.2]: https://tools.ietf.org/html/rfc3010#section-5.2 +impl + AsRef<[u8]>> AddressRecord { + /// Return the record type for the given sources. + #[inline] + pub fn set_record_type(&mut self, rty: RecordType) { + let data = self.buffer.as_mut(); + data[field::RECORD_TYPE] = rty.into(); + } + + /// Return the length of the auxiliary data. + #[inline] + pub fn set_aux_data_len(&mut self, len: u8) { + let data = self.buffer.as_mut(); + data[field::AUX_DATA_LEN] = len; + } + + /// Return the number of sources field. + #[inline] + pub fn set_num_srcs(&mut self, num_srcs: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::RECORD_NUM_SRCS], num_srcs); + } + + /// Return the multicast address field. + /// + /// # Panics + /// This function panics if the given address is not a multicast address. + #[inline] + pub fn set_mcast_addr(&mut self, addr: Ipv6Address) { + assert!(addr.is_multicast()); + let data = self.buffer.as_mut(); + data[field::RECORD_MCAST_ADDR].copy_from_slice(&addr.octets()); + } +} + +impl + AsMut<[u8]>> AddressRecord { + /// Return a pointer to the address records. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let data = self.buffer.as_mut(); + &mut data[field::RECORD_MCAST_ADDR.end..] + } +} + +/// A high level representation of an MLDv2 Listener Report Message Address Record. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AddressRecordRepr<'a> { + pub record_type: RecordType, + pub aux_data_len: u8, + pub num_srcs: u16, + pub mcast_addr: Ipv6Address, + pub payload: &'a [u8], +} + +impl<'a> AddressRecordRepr<'a> { + /// Create a new MLDv2 address record representation with an empty payload. + pub const fn new(record_type: RecordType, mcast_addr: Ipv6Address) -> Self { + Self { + record_type, + aux_data_len: 0, + num_srcs: 0, + mcast_addr, + payload: &[], + } + } + + /// Parse an MLDv2 address record and return a high-level representation. + pub fn parse(record: &AddressRecord<&'a T>) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + Ok(Self { + num_srcs: record.num_srcs(), + mcast_addr: record.mcast_addr(), + record_type: record.record_type(), + aux_data_len: record.aux_data_len(), + payload: record.payload(), + }) + } + + /// Return the length of a record that will be emitted from this high-level + /// representation, not including any payload data. + pub fn buffer_len(&self) -> usize { + field::RECORD_MCAST_ADDR.end + } + + /// Emit a high-level representation into an MLDv2 address record. + pub fn emit + AsMut<[u8]>>(&self, record: &mut AddressRecord) { + record.set_record_type(self.record_type); + record.set_aux_data_len(self.aux_data_len); + record.set_num_srcs(self.num_srcs); + record.set_mcast_addr(self.mcast_addr); + } +} + +/// A high-level representation of an MLDv2 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr<'a> { + Query { + max_resp_code: u16, + mcast_addr: Ipv6Address, + s_flag: bool, + qrv: u8, + qqic: u8, + num_srcs: u16, + data: &'a [u8], + }, + Report { + nr_mcast_addr_rcrds: u16, + data: &'a [u8], + }, + ReportRecordReprs(&'a [AddressRecordRepr<'a>]), +} + +impl<'a> Repr<'a> { + /// Parse an MLDv2 packet and return a high-level representation. + pub fn parse(packet: &Packet<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + match packet.msg_type() { + Message::MldQuery => Ok(Repr::Query { + max_resp_code: packet.max_resp_code(), + mcast_addr: packet.mcast_addr(), + s_flag: packet.s_flag(), + qrv: packet.qrv(), + qqic: packet.qqic(), + num_srcs: packet.num_srcs(), + data: packet.payload(), + }), + Message::MldReport => Ok(Repr::Report { + nr_mcast_addr_rcrds: packet.nr_mcast_addr_rcrds(), + data: packet.payload(), + }), + _ => Err(Error), + } + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + match self { + Repr::Query { data, .. } => field::QUERY_NUM_SRCS.end + data.len(), + Repr::Report { data, .. } => field::NR_MCAST_RCRDS.end + data.len(), + Repr::ReportRecordReprs(_data) => field::NR_MCAST_RCRDS.end, + } + } + + /// Emit a high-level representation into an MLDv2 packet. + pub fn emit(&self, packet: &mut Packet<&mut T>) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + match self { + Repr::Query { + max_resp_code, + mcast_addr, + s_flag, + qrv, + qqic, + num_srcs, + data, + } => { + packet.set_msg_type(Message::MldQuery); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_max_resp_code(*max_resp_code); + packet.set_mcast_addr(*mcast_addr); + if *s_flag { + packet.set_s_flag(); + } else { + packet.clear_s_flag(); + } + packet.set_qrv(*qrv); + packet.set_qqic(*qqic); + packet.set_num_srcs(*num_srcs); + packet.payload_mut().copy_from_slice(&data[..]); + } + Repr::Report { + nr_mcast_addr_rcrds, + data, + } => { + packet.set_msg_type(Message::MldReport); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_nr_mcast_addr_rcrds(*nr_mcast_addr_rcrds); + packet.payload_mut().copy_from_slice(&data[..]); + } + Repr::ReportRecordReprs(records) => { + packet.set_msg_type(Message::MldReport); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_nr_mcast_addr_rcrds(records.len() as u16); + let mut payload = packet.payload_mut(); + for record in *records { + record.emit(&mut AddressRecord::new_unchecked(&mut *payload)); + payload = &mut payload[record.buffer_len()..]; + } + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::phy::ChecksumCapabilities; + use crate::wire::icmpv6::Message; + use crate::wire::{IPV6_LINK_LOCAL_ALL_NODES, IPV6_LINK_LOCAL_ALL_ROUTERS, Icmpv6Repr}; + + static QUERY_PACKET_BYTES: [u8; 44] = [ + 0x82, 0x00, 0x73, 0x74, 0x04, 0x00, 0x00, 0x00, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x12, 0x00, 0x01, 0xff, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + ]; + + static QUERY_PACKET_PAYLOAD: [u8; 16] = [ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, + ]; + + static REPORT_PACKET_BYTES: [u8; 44] = [ + 0x8f, 0x00, 0x73, 0x85, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + ]; + + static REPORT_PACKET_PAYLOAD: [u8; 36] = [ + 0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + ]; + + fn create_repr<'a>(ty: Message) -> Icmpv6Repr<'a> { + match ty { + Message::MldQuery => Icmpv6Repr::Mld(Repr::Query { + max_resp_code: 0x400, + mcast_addr: IPV6_LINK_LOCAL_ALL_NODES, + s_flag: true, + qrv: 0x02, + qqic: 0x12, + num_srcs: 0x01, + data: &QUERY_PACKET_PAYLOAD, + }), + Message::MldReport => Icmpv6Repr::Mld(Repr::Report { + nr_mcast_addr_rcrds: 1, + data: &REPORT_PACKET_PAYLOAD, + }), + _ => { + panic!("Message type must be a MLDv2 message type"); + } + } + } + + #[test] + fn test_query_deconstruct() { + let packet = Packet::new_unchecked(&QUERY_PACKET_BYTES[..]); + assert_eq!(packet.msg_type(), Message::MldQuery); + assert_eq!(packet.msg_code(), 0); + assert_eq!(packet.checksum(), 0x7374); + assert_eq!(packet.max_resp_code(), 0x0400); + assert_eq!(packet.mcast_addr(), IPV6_LINK_LOCAL_ALL_NODES); + assert!(packet.s_flag()); + assert_eq!(packet.qrv(), 0x02); + assert_eq!(packet.qqic(), 0x12); + assert_eq!(packet.num_srcs(), 0x01); + assert_eq!( + crate::wire::ipv6_from_octets(packet.payload().try_into().unwrap()), + IPV6_LINK_LOCAL_ALL_ROUTERS + ); + } + + #[test] + fn test_query_construct() { + let mut bytes = [0xff; 44]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_msg_type(Message::MldQuery); + packet.set_msg_code(0); + packet.set_max_resp_code(0x0400); + packet.set_mcast_addr(IPV6_LINK_LOCAL_ALL_NODES); + packet.set_s_flag(); + packet.set_qrv(0x02); + packet.set_qqic(0x12); + packet.set_num_srcs(0x01); + packet + .payload_mut() + .copy_from_slice(&IPV6_LINK_LOCAL_ALL_ROUTERS.octets()); + packet.clear_reserved(); + packet.fill_checksum(&IPV6_LINK_LOCAL_ALL_NODES, &IPV6_LINK_LOCAL_ALL_ROUTERS); + assert_eq!(&*packet.into_inner(), &QUERY_PACKET_BYTES[..]); + } + + #[test] + fn test_record_deconstruct() { + let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]); + assert_eq!(packet.msg_type(), Message::MldReport); + assert_eq!(packet.msg_code(), 0); + assert_eq!(packet.checksum(), 0x7385); + assert_eq!(packet.nr_mcast_addr_rcrds(), 0x01); + let addr_rcrd = AddressRecord::new_unchecked(packet.payload()); + assert_eq!(addr_rcrd.record_type(), RecordType::ModeIsInclude); + assert_eq!(addr_rcrd.aux_data_len(), 0x00); + assert_eq!(addr_rcrd.num_srcs(), 0x01); + assert_eq!(addr_rcrd.mcast_addr(), IPV6_LINK_LOCAL_ALL_NODES); + assert_eq!( + crate::wire::ipv6_from_octets(addr_rcrd.payload().try_into().unwrap()), + IPV6_LINK_LOCAL_ALL_ROUTERS + ); + } + + #[test] + fn test_record_construct() { + let mut bytes = [0xff; 44]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_msg_type(Message::MldReport); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_nr_mcast_addr_rcrds(1); + { + let mut addr_rcrd = AddressRecord::new_unchecked(packet.payload_mut()); + addr_rcrd.set_record_type(RecordType::ModeIsInclude); + addr_rcrd.set_aux_data_len(0); + addr_rcrd.set_num_srcs(1); + addr_rcrd.set_mcast_addr(IPV6_LINK_LOCAL_ALL_NODES); + addr_rcrd + .payload_mut() + .copy_from_slice(&IPV6_LINK_LOCAL_ALL_ROUTERS.octets()); + } + packet.fill_checksum(&IPV6_LINK_LOCAL_ALL_NODES, &IPV6_LINK_LOCAL_ALL_ROUTERS); + assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]); + } + + #[test] + fn test_query_repr_parse() { + let packet = Packet::new_unchecked(&QUERY_PACKET_BYTES[..]); + let repr = Icmpv6Repr::parse( + &IPV6_LINK_LOCAL_ALL_NODES, + &IPV6_LINK_LOCAL_ALL_ROUTERS, + &packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(repr, Ok(create_repr(Message::MldQuery))); + } + + #[test] + fn test_report_repr_parse() { + let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]); + let repr = Icmpv6Repr::parse( + &IPV6_LINK_LOCAL_ALL_NODES, + &IPV6_LINK_LOCAL_ALL_ROUTERS, + &packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(repr, Ok(create_repr(Message::MldReport))); + } + + #[test] + fn test_query_repr_emit() { + let mut bytes = [0x2a; 44]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + let repr = create_repr(Message::MldQuery); + repr.emit( + &IPV6_LINK_LOCAL_ALL_NODES, + &IPV6_LINK_LOCAL_ALL_ROUTERS, + &mut packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &QUERY_PACKET_BYTES[..]); + } + + #[test] + fn test_report_repr_emit() { + let mut bytes = [0x2a; 44]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + let repr = create_repr(Message::MldReport); + repr.emit( + &IPV6_LINK_LOCAL_ALL_NODES, + &IPV6_LINK_LOCAL_ALL_ROUTERS, + &mut packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]); + } +} diff --git a/vendor/smoltcp/src/wire/mod.rs b/vendor/smoltcp/src/wire/mod.rs new file mode 100644 index 00000000..7b5e27ad --- /dev/null +++ b/vendor/smoltcp/src/wire/mod.rs @@ -0,0 +1,630 @@ +/*! Low-level packet access and construction. + +The `wire` module deals with the packet *representation*. It provides two levels +of functionality. + + * First, it provides functions to extract fields from sequences of octets, + and to insert fields into sequences of octets. This happens `Packet` family of + structures, e.g. [EthernetFrame] or [Ipv4Packet]. + * Second, in cases where the space of valid field values is much smaller than the space + of possible field values, it provides a compact, high-level representation + of packet data that can be parsed from and emitted into a sequence of octets. + This happens through the `Repr` family of structs and enums, e.g. [ArpRepr] or [Ipv4Repr]. + +[EthernetFrame]: struct.EthernetFrame.html +[Ipv4Packet]: struct.Ipv4Packet.html +[ArpRepr]: enum.ArpRepr.html +[Ipv4Repr]: struct.Ipv4Repr.html + +The functions in the `wire` module are designed for use together with `-Cpanic=abort`. + +The `Packet` family of data structures guarantees that, if the `Packet::check_len()` method +returned `Ok(())`, then no accessor or setter method will panic; however, the guarantee +provided by `Packet::check_len()` may no longer hold after changing certain fields, +which are listed in the documentation for the specific packet. + +The `Packet::new_checked` method is a shorthand for a combination of `Packet::new_unchecked` +and `Packet::check_len`. +When parsing untrusted input, it is *necessary* to use `Packet::new_checked()`; +so long as the buffer is not modified, no accessor will fail. +When emitting output, though, it is *incorrect* to use `Packet::new_checked()`; +the length check is likely to succeed on a zeroed buffer, but fail on a buffer +filled with data from a previous packet, such as when reusing buffers, resulting +in nondeterministic panics with some network devices but not others. +The buffer length for emission is not calculated by the `Packet` layer. + +In the `Repr` family of data structures, the `Repr::parse()` method never panics +as long as `Packet::new_checked()` (or `Packet::check_len()`) has succeeded, and +the `Repr::emit()` method never panics as long as the underlying buffer is exactly +`Repr::buffer_len()` octets long. + +# Examples + +To emit an IP packet header into an octet buffer, and then parse it back: + +```rust +# #[cfg(feature = "proto-ipv4")] +# { +use smoltcp::phy::ChecksumCapabilities; +use smoltcp::wire::*; +let repr = Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 0, 1), + dst_addr: Ipv4Address::new(10, 0, 0, 2), + next_header: IpProtocol::Tcp, + payload_len: 10, + hop_limit: 64, +}; +let mut buffer = vec![0; repr.buffer_len() + repr.payload_len]; +{ // emission + let mut packet = Ipv4Packet::new_unchecked(&mut buffer); + repr.emit(&mut packet, &ChecksumCapabilities::default()); +} +{ // parsing + let packet = Ipv4Packet::new_checked(&buffer) + .expect("truncated packet"); + let parsed = Ipv4Repr::parse(&packet, &ChecksumCapabilities::default()) + .expect("malformed packet"); + assert_eq!(repr, parsed); +} +# } +``` +*/ + +mod field { + pub type Field = ::core::ops::Range; + pub type Rest = ::core::ops::RangeFrom; +} + +pub mod pretty_print; + +#[doc(hidden)] +#[inline] +pub const fn ipv4_from_octets(octets: [u8; 4]) -> core::net::Ipv4Addr { + core::net::Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]) +} + +#[doc(hidden)] +#[inline] +pub const fn ipv6_from_octets(octets: [u8; 16]) -> core::net::Ipv6Addr { + core::net::Ipv6Addr::new( + u16::from_be_bytes([octets[0], octets[1]]), + u16::from_be_bytes([octets[2], octets[3]]), + u16::from_be_bytes([octets[4], octets[5]]), + u16::from_be_bytes([octets[6], octets[7]]), + u16::from_be_bytes([octets[8], octets[9]]), + u16::from_be_bytes([octets[10], octets[11]]), + u16::from_be_bytes([octets[12], octets[13]]), + u16::from_be_bytes([octets[14], octets[15]]), + ) +} + +#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))] +mod arp; +#[cfg(feature = "proto-dhcpv4")] +pub(crate) mod dhcpv4; +#[cfg(feature = "proto-dns")] +pub(crate) mod dns; +#[cfg(feature = "medium-ethernet")] +mod ethernet; +#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] +mod icmp; +#[cfg(feature = "proto-ipv4")] +mod icmpv4; +#[cfg(feature = "proto-ipv6")] +mod icmpv6; +#[cfg(feature = "medium-ieee802154")] +pub mod ieee802154; +#[cfg(feature = "proto-ipv4")] +mod igmp; +pub(crate) mod ip; +#[cfg(feature = "proto-ipv4")] +pub(crate) mod ipv4; +#[cfg(feature = "proto-ipv6")] +pub(crate) mod ipv6; +#[cfg(feature = "proto-ipv6")] +mod ipv6ext_header; +#[cfg(feature = "proto-ipv6")] +mod ipv6fragment; +#[cfg(feature = "proto-ipv6")] +mod ipv6hbh; +#[cfg(feature = "proto-ipv6")] +mod ipv6option; +#[cfg(feature = "proto-ipv6")] +mod ipv6routing; +#[cfg(feature = "proto-ipv6")] +mod mld; +#[cfg(all( + feature = "proto-ipv6", + any(feature = "medium-ethernet", feature = "medium-ieee802154") +))] +mod ndisc; +#[cfg(all( + feature = "proto-ipv6", + any(feature = "medium-ethernet", feature = "medium-ieee802154") +))] +mod ndiscoption; +#[cfg(feature = "proto-rpl")] +mod rpl; +#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))] +mod sixlowpan; +mod tcp; +mod udp; + +#[cfg(feature = "proto-ipsec-ah")] +mod ipsec_ah; + +#[cfg(feature = "proto-ipsec-esp")] +mod ipsec_esp; + +use core::fmt; + +use crate::phy::Medium; + +pub use self::pretty_print::PrettyPrinter; + +#[cfg(feature = "medium-ethernet")] +pub use self::ethernet::{ + Address as EthernetAddress, EtherType as EthernetProtocol, Frame as EthernetFrame, + HEADER_LEN as ETHERNET_HEADER_LEN, Repr as EthernetRepr, +}; + +#[cfg(all(feature = "proto-ipv4", feature = "medium-ethernet"))] +pub use self::arp::{ + Hardware as ArpHardware, Operation as ArpOperation, Packet as ArpPacket, Repr as ArpRepr, +}; + +#[cfg(feature = "proto-rpl")] +pub use self::rpl::{ + InstanceId as RplInstanceId, Repr as RplRepr, data::HopByHopOption as RplHopByHopRepr, + data::Packet as RplHopByHopPacket, options::Packet as RplOptionPacket, + options::Repr as RplOptionRepr, +}; + +#[cfg(all(feature = "proto-sixlowpan", feature = "medium-ieee802154"))] +pub use self::sixlowpan::{ + AddressContext as SixlowpanAddressContext, NextHeader as SixlowpanNextHeader, SixlowpanPacket, + frag::{Key as SixlowpanFragKey, Packet as SixlowpanFragPacket, Repr as SixlowpanFragRepr}, + iphc::{Packet as SixlowpanIphcPacket, Repr as SixlowpanIphcRepr}, + nhc::{ + ExtHeaderId as SixlowpanExtHeaderId, ExtHeaderPacket as SixlowpanExtHeaderPacket, + ExtHeaderRepr as SixlowpanExtHeaderRepr, NhcPacket as SixlowpanNhcPacket, + UdpNhcPacket as SixlowpanUdpNhcPacket, UdpNhcRepr as SixlowpanUdpNhcRepr, + }, +}; + +#[cfg(feature = "medium-ieee802154")] +pub use self::ieee802154::{ + Address as Ieee802154Address, AddressingMode as Ieee802154AddressingMode, + Frame as Ieee802154Frame, FrameType as Ieee802154FrameType, + FrameVersion as Ieee802154FrameVersion, Pan as Ieee802154Pan, Repr as Ieee802154Repr, +}; + +pub use self::ip::{ + Address as IpAddress, Cidr as IpCidr, Endpoint as IpEndpoint, + ListenEndpoint as IpListenEndpoint, Protocol as IpProtocol, Repr as IpRepr, + Version as IpVersion, +}; + +#[cfg(feature = "proto-ipv4")] +pub use self::ipv4::{ + Address as Ipv4Address, Cidr as Ipv4Cidr, HEADER_LEN as IPV4_HEADER_LEN, Key as Ipv4FragKey, + MIN_MTU as IPV4_MIN_MTU, MULTICAST_ALL_ROUTERS as IPV4_MULTICAST_ALL_ROUTERS, + MULTICAST_ALL_SYSTEMS as IPV4_MULTICAST_ALL_SYSTEMS, Packet as Ipv4Packet, Repr as Ipv4Repr, +}; + +#[cfg(feature = "proto-ipv4")] +pub(crate) use self::ipv4::AddressExt as Ipv4AddressExt; + +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6::{ + Address as Ipv6Address, Cidr as Ipv6Cidr, HEADER_LEN as IPV6_HEADER_LEN, + LINK_LOCAL_ALL_MLDV2_ROUTERS as IPV6_LINK_LOCAL_ALL_MLDV2_ROUTERS, + LINK_LOCAL_ALL_NODES as IPV6_LINK_LOCAL_ALL_NODES, + LINK_LOCAL_ALL_ROUTERS as IPV6_LINK_LOCAL_ALL_ROUTERS, + LINK_LOCAL_ALL_RPL_NODES as IPV6_LINK_LOCAL_ALL_RPL_NODES, MIN_MTU as IPV6_MIN_MTU, + Packet as Ipv6Packet, Repr as Ipv6Repr, +}; +#[cfg(feature = "proto-ipv6")] +pub(crate) use self::ipv6::{AddressExt as Ipv6AddressExt, MulticastScope as Ipv6MulticastScope}; + +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6option::{ + FailureType as Ipv6OptionFailureType, Ipv6Option, Ipv6OptionsIterator, Repr as Ipv6OptionRepr, + RouterAlert as Ipv6OptionRouterAlert, Type as Ipv6OptionType, +}; + +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6ext_header::{Header as Ipv6ExtHeader, Repr as Ipv6ExtHeaderRepr}; + +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6fragment::{Header as Ipv6FragmentHeader, Repr as Ipv6FragmentRepr}; + +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6hbh::{Header as Ipv6HopByHopHeader, Repr as Ipv6HopByHopRepr}; + +#[cfg(feature = "proto-ipv6")] +pub use self::ipv6routing::{ + Header as Ipv6RoutingHeader, Repr as Ipv6RoutingRepr, Type as Ipv6RoutingType, +}; + +#[cfg(feature = "proto-ipv4")] +pub use self::icmpv4::{ + DstUnreachable as Icmpv4DstUnreachable, Message as Icmpv4Message, Packet as Icmpv4Packet, + ParamProblem as Icmpv4ParamProblem, Redirect as Icmpv4Redirect, Repr as Icmpv4Repr, + TimeExceeded as Icmpv4TimeExceeded, +}; + +#[cfg(feature = "proto-ipv4")] +pub use self::igmp::{IgmpVersion, Packet as IgmpPacket, Repr as IgmpRepr}; + +#[cfg(feature = "proto-ipv6")] +pub use self::icmpv6::{ + DstUnreachable as Icmpv6DstUnreachable, Message as Icmpv6Message, Packet as Icmpv6Packet, + ParamProblem as Icmpv6ParamProblem, Repr as Icmpv6Repr, TimeExceeded as Icmpv6TimeExceeded, +}; + +#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))] +pub use self::icmp::Repr as IcmpRepr; + +#[cfg(all( + feature = "proto-ipv6", + any(feature = "medium-ethernet", feature = "medium-ieee802154") +))] +pub use self::ndisc::{ + NeighborFlags as NdiscNeighborFlags, Repr as NdiscRepr, RouterFlags as NdiscRouterFlags, +}; + +#[cfg(all( + feature = "proto-ipv6", + any(feature = "medium-ethernet", feature = "medium-ieee802154") +))] +pub use self::ndiscoption::{ + NdiscOption, PrefixInfoFlags as NdiscPrefixInfoFlags, + PrefixInformation as NdiscPrefixInformation, RedirectedHeader as NdiscRedirectedHeader, + Repr as NdiscOptionRepr, Type as NdiscOptionType, +}; + +#[cfg(feature = "proto-ipv6")] +pub use self::mld::{ + AddressRecord as MldAddressRecord, AddressRecordRepr as MldAddressRecordRepr, + RecordType as MldRecordType, Repr as MldRepr, +}; + +pub use self::udp::{HEADER_LEN as UDP_HEADER_LEN, Packet as UdpPacket, Repr as UdpRepr}; + +pub use self::tcp::{ + Control as TcpControl, HEADER_LEN as TCP_HEADER_LEN, Packet as TcpPacket, Repr as TcpRepr, + SeqNumber as TcpSeqNumber, TcpOption, TcpTimestampGenerator, TcpTimestampRepr, +}; + +#[cfg(feature = "proto-dhcpv4")] +pub use self::dhcpv4::{ + CLIENT_PORT as DHCP_CLIENT_PORT, DhcpOption, DhcpOptionWriter, Flags as DhcpFlags, + MAX_DNS_SERVER_COUNT as DHCP_MAX_DNS_SERVER_COUNT, MessageType as DhcpMessageType, + OpCode as DhcpOpCode, Packet as DhcpPacket, Repr as DhcpRepr, SERVER_PORT as DHCP_SERVER_PORT, +}; + +#[cfg(feature = "proto-dns")] +pub use self::dns::{ + Flags as DnsFlags, Opcode as DnsOpcode, Packet as DnsPacket, Question as DnsQuestion, + Rcode as DnsRcode, Record as DnsRecord, RecordData as DnsRecordData, Repr as DnsRepr, + Type as DnsQueryType, +}; + +#[cfg(feature = "proto-ipsec-ah")] +pub use self::ipsec_ah::{Packet as IpSecAuthHeaderPacket, Repr as IpSecAuthHeaderRepr}; + +#[cfg(feature = "proto-ipsec-esp")] +pub use self::ipsec_esp::{Packet as IpSecEspPacket, Repr as IpSecEspRepr}; + +/// Parsing a packet failed. +/// +/// Either it is malformed, or it is not supported by smoltcp. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Error; + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "wire::Error") + } +} + +pub type Result = core::result::Result; + +/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address. +#[cfg(any( + feature = "medium-ip", + feature = "medium-ethernet", + feature = "medium-ieee802154" +))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum HardwareAddress { + #[cfg(feature = "medium-ip")] + Ip, + #[cfg(feature = "medium-ethernet")] + Ethernet(EthernetAddress), + #[cfg(feature = "medium-ieee802154")] + Ieee802154(Ieee802154Address), +} + +#[cfg(any( + feature = "medium-ip", + feature = "medium-ethernet", + feature = "medium-ieee802154" +))] +#[cfg(test)] +impl Default for HardwareAddress { + fn default() -> Self { + #![allow(unreachable_code)] + #[cfg(feature = "medium-ethernet")] + { + return Self::Ethernet(EthernetAddress::default()); + } + #[cfg(feature = "medium-ip")] + { + return Self::Ip; + } + #[cfg(feature = "medium-ieee802154")] + { + Self::Ieee802154(Ieee802154Address::default()) + } + } +} + +#[cfg(any( + feature = "medium-ip", + feature = "medium-ethernet", + feature = "medium-ieee802154" +))] +impl HardwareAddress { + pub const fn as_bytes(&self) -> &[u8] { + match self { + #[cfg(feature = "medium-ip")] + HardwareAddress::Ip => unreachable!(), + #[cfg(feature = "medium-ethernet")] + HardwareAddress::Ethernet(addr) => addr.as_bytes(), + #[cfg(feature = "medium-ieee802154")] + HardwareAddress::Ieee802154(addr) => addr.as_bytes(), + } + } + + /// Query whether the address is an unicast address. + pub fn is_unicast(&self) -> bool { + match self { + #[cfg(feature = "medium-ip")] + HardwareAddress::Ip => unreachable!(), + #[cfg(feature = "medium-ethernet")] + HardwareAddress::Ethernet(addr) => addr.is_unicast(), + #[cfg(feature = "medium-ieee802154")] + HardwareAddress::Ieee802154(addr) => addr.is_unicast(), + } + } + + /// Query whether the address is a broadcast address. + pub fn is_broadcast(&self) -> bool { + match self { + #[cfg(feature = "medium-ip")] + HardwareAddress::Ip => unreachable!(), + #[cfg(feature = "medium-ethernet")] + HardwareAddress::Ethernet(addr) => addr.is_broadcast(), + #[cfg(feature = "medium-ieee802154")] + HardwareAddress::Ieee802154(addr) => addr.is_broadcast(), + } + } + + #[cfg(feature = "medium-ethernet")] + pub(crate) fn ethernet_or_panic(&self) -> EthernetAddress { + match self { + HardwareAddress::Ethernet(addr) => *addr, + #[allow(unreachable_patterns)] + _ => panic!("HardwareAddress is not Ethernet."), + } + } + + #[cfg(feature = "medium-ieee802154")] + pub(crate) fn ieee802154_or_panic(&self) -> Ieee802154Address { + match self { + HardwareAddress::Ieee802154(addr) => *addr, + #[allow(unreachable_patterns)] + _ => panic!("HardwareAddress is not Ethernet."), + } + } + + #[inline] + pub(crate) fn medium(&self) -> Medium { + match self { + #[cfg(feature = "medium-ip")] + HardwareAddress::Ip => Medium::Ip, + #[cfg(feature = "medium-ethernet")] + HardwareAddress::Ethernet(_) => Medium::Ethernet, + #[cfg(feature = "medium-ieee802154")] + HardwareAddress::Ieee802154(_) => Medium::Ieee802154, + } + } + + pub fn as_eui_64(&self) -> Option<[u8; 8]> { + match self { + #[cfg(feature = "medium-ip")] + HardwareAddress::Ip => None, + #[cfg(feature = "medium-ethernet")] + HardwareAddress::Ethernet(ethernet) => ethernet.as_eui_64(), + #[cfg(feature = "medium-ieee802154")] + HardwareAddress::Ieee802154(ieee802154) => ieee802154.as_eui_64(), + } + } +} + +#[cfg(any( + feature = "medium-ip", + feature = "medium-ethernet", + feature = "medium-ieee802154" +))] +impl core::fmt::Display for HardwareAddress { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + #[cfg(feature = "medium-ip")] + HardwareAddress::Ip => write!(f, "no hardware addr"), + #[cfg(feature = "medium-ethernet")] + HardwareAddress::Ethernet(addr) => write!(f, "{addr}"), + #[cfg(feature = "medium-ieee802154")] + HardwareAddress::Ieee802154(addr) => write!(f, "{addr}"), + } + } +} + +#[cfg(feature = "medium-ethernet")] +impl From for HardwareAddress { + fn from(addr: EthernetAddress) -> Self { + HardwareAddress::Ethernet(addr) + } +} + +#[cfg(feature = "medium-ieee802154")] +impl From for HardwareAddress { + fn from(addr: Ieee802154Address) -> Self { + HardwareAddress::Ieee802154(addr) + } +} + +#[cfg(not(feature = "medium-ieee802154"))] +pub const MAX_HARDWARE_ADDRESS_LEN: usize = 6; +#[cfg(feature = "medium-ieee802154")] +pub const MAX_HARDWARE_ADDRESS_LEN: usize = 8; + +/// Unparsed hardware address. +/// +/// Used to make NDISC parsing agnostic of the hardware medium in use. +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RawHardwareAddress { + len: u8, + data: [u8; MAX_HARDWARE_ADDRESS_LEN], +} + +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +impl RawHardwareAddress { + /// Create a new `RawHardwareAddress` from a byte slice. + /// + /// # Panics + /// Panics if `addr.len() > MAX_HARDWARE_ADDRESS_LEN`. + pub fn from_bytes(addr: &[u8]) -> Self { + let mut data = [0u8; MAX_HARDWARE_ADDRESS_LEN]; + data[..addr.len()].copy_from_slice(addr); + + Self { + len: addr.len() as u8, + data, + } + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data[..self.len as usize] + } + + pub const fn len(&self) -> usize { + self.len as usize + } + + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn parse(&self, medium: Medium) -> Result { + match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => { + if self.len() != 6 { + return Err(Error); + } + Ok(HardwareAddress::Ethernet(EthernetAddress::from_bytes( + self.as_bytes(), + ))) + } + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => { + if self.len() != 8 { + return Err(Error); + } + Ok(HardwareAddress::Ieee802154(Ieee802154Address::from_bytes( + self.as_bytes(), + ))) + } + #[cfg(feature = "medium-ip")] + Medium::Ip => unreachable!(), + } + } +} + +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +impl core::fmt::Display for RawHardwareAddress { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + for (i, &b) in self.as_bytes().iter().enumerate() { + if i != 0 { + write!(f, ":")?; + } + write!(f, "{b:02x}")?; + } + Ok(()) + } +} + +#[cfg(feature = "medium-ethernet")] +impl From for RawHardwareAddress { + fn from(addr: EthernetAddress) -> Self { + Self::from_bytes(addr.as_bytes()) + } +} + +#[cfg(feature = "medium-ieee802154")] +impl From for RawHardwareAddress { + fn from(addr: Ieee802154Address) -> Self { + Self::from_bytes(addr.as_bytes()) + } +} + +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +impl From for RawHardwareAddress { + fn from(addr: HardwareAddress) -> Self { + Self::from_bytes(addr.as_bytes()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[cfg(feature = "medium-ethernet")] + #[case((Medium::Ethernet, &[0u8; 6][..]), Ok(HardwareAddress::Ethernet(EthernetAddress([0, 0, 0, 0, 0, 0]))))] + #[cfg(feature = "medium-ethernet")] + #[case((Medium::Ethernet, &[1u8; 5][..]), Err(Error))] + #[cfg(feature = "medium-ethernet")] + #[case((Medium::Ethernet, &[1u8; 7][..]), Err(Error))] + #[cfg(feature = "medium-ieee802154")] + #[case((Medium::Ieee802154, &[0u8; 8][..]), Ok(HardwareAddress::Ieee802154(Ieee802154Address::Extended([0, 0, 0, 0, 0, 0, 0, 0]))))] + #[cfg(feature = "medium-ieee802154")] + #[case((Medium::Ieee802154, &[1u8; 2][..]), Err(Error))] + #[cfg(feature = "medium-ieee802154")] + #[case((Medium::Ieee802154, &[1u8; 1][..]), Err(Error))] + fn parse_hardware_address( + #[case] input: (Medium, &[u8]), + #[case] expected: Result, + ) { + let (medium, input) = input; + + // NOTE: we check the length since `RawHardwareAddress::parse()` panics if the length is + // invalid. MAX_HARDWARE_ADDRESS_LEN is based on the medium, and depending on the feature + // flags, it can be different. + if input.len() < MAX_HARDWARE_ADDRESS_LEN { + let raw = RawHardwareAddress::from_bytes(input); + assert_eq!(raw.parse(medium), expected); + } + } +} diff --git a/vendor/smoltcp/src/wire/ndisc.rs b/vendor/smoltcp/src/wire/ndisc.rs new file mode 100644 index 00000000..0986d15d --- /dev/null +++ b/vendor/smoltcp/src/wire/ndisc.rs @@ -0,0 +1,545 @@ +use bitflags::bitflags; +use byteorder::{ByteOrder, NetworkEndian}; + +use super::{Error, Result}; +use crate::time::Duration; +use crate::wire::Ipv6Address; +use crate::wire::RawHardwareAddress; +use crate::wire::icmpv6::{Message, Packet, field}; +use crate::wire::{NdiscOption, NdiscOptionRepr}; +use crate::wire::{NdiscPrefixInformation, NdiscRedirectedHeader}; + +bitflags! { + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct RouterFlags: u8 { + const MANAGED = 0b10000000; + const OTHER = 0b01000000; + } +} + +bitflags! { + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct NeighborFlags: u8 { + const ROUTER = 0b10000000; + const SOLICITED = 0b01000000; + const OVERRIDE = 0b00100000; + } +} + +/// Getters for the Router Advertisement message header. +/// See [RFC 4861 § 4.2]. +/// +/// [RFC 4861 § 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2 +impl> Packet { + /// Return the current hop limit field. + #[inline] + pub fn current_hop_limit(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::CUR_HOP_LIMIT] + } + + /// Return the Router Advertisement flags. + #[inline] + pub fn router_flags(&self) -> RouterFlags { + let data = self.buffer.as_ref(); + RouterFlags::from_bits_truncate(data[field::ROUTER_FLAGS]) + } + + /// Return the router lifetime field. + #[inline] + pub fn router_lifetime(&self) -> Duration { + let data = self.buffer.as_ref(); + Duration::from_secs(NetworkEndian::read_u16(&data[field::ROUTER_LT]) as u64) + } + + /// Return the reachable time field. + #[inline] + pub fn reachable_time(&self) -> Duration { + let data = self.buffer.as_ref(); + Duration::from_millis(NetworkEndian::read_u32(&data[field::REACHABLE_TM]) as u64) + } + + /// Return the retransmit time field. + #[inline] + pub fn retrans_time(&self) -> Duration { + let data = self.buffer.as_ref(); + Duration::from_millis(NetworkEndian::read_u32(&data[field::RETRANS_TM]) as u64) + } +} + +/// Common getters for the [Neighbor Solicitation], [Neighbor Advertisement], and +/// [Redirect] message types. +/// +/// [Neighbor Solicitation]: https://tools.ietf.org/html/rfc4861#section-4.3 +/// [Neighbor Advertisement]: https://tools.ietf.org/html/rfc4861#section-4.4 +/// [Redirect]: https://tools.ietf.org/html/rfc4861#section-4.5 +impl> Packet { + /// Return the target address field. + #[inline] + pub fn target_addr(&self) -> Ipv6Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::TARGET_ADDR].try_into().unwrap()) + } +} + +/// Getters for the Neighbor Solicitation message header. +/// See [RFC 4861 § 4.3]. +/// +/// [RFC 4861 § 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3 +impl> Packet { + /// Return the Neighbor Solicitation flags. + #[inline] + pub fn neighbor_flags(&self) -> NeighborFlags { + let data = self.buffer.as_ref(); + NeighborFlags::from_bits_truncate(data[field::NEIGH_FLAGS]) + } +} + +/// Getters for the Redirect message header. +/// See [RFC 4861 § 4.5]. +/// +/// [RFC 4861 § 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5 +impl> Packet { + /// Return the destination address field. + #[inline] + pub fn dest_addr(&self) -> Ipv6Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::DEST_ADDR].try_into().unwrap()) + } +} + +/// Setters for the Router Advertisement message header. +/// See [RFC 4861 § 4.2]. +/// +/// [RFC 4861 § 4.2]: https://tools.ietf.org/html/rfc4861#section-4.2 +impl + AsMut<[u8]>> Packet { + /// Set the current hop limit field. + #[inline] + pub fn set_current_hop_limit(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::CUR_HOP_LIMIT] = value; + } + + /// Set the Router Advertisement flags. + #[inline] + pub fn set_router_flags(&mut self, flags: RouterFlags) { + self.buffer.as_mut()[field::ROUTER_FLAGS] = flags.bits(); + } + + /// Set the router lifetime field. + #[inline] + pub fn set_router_lifetime(&mut self, value: Duration) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::ROUTER_LT], value.secs() as u16); + } + + /// Set the reachable time field. + #[inline] + pub fn set_reachable_time(&mut self, value: Duration) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::REACHABLE_TM], value.total_millis() as u32); + } + + /// Set the retransmit time field. + #[inline] + pub fn set_retrans_time(&mut self, value: Duration) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::RETRANS_TM], value.total_millis() as u32); + } +} + +/// Common setters for the [Neighbor Solicitation], [Neighbor Advertisement], and +/// [Redirect] message types. +/// +/// [Neighbor Solicitation]: https://tools.ietf.org/html/rfc4861#section-4.3 +/// [Neighbor Advertisement]: https://tools.ietf.org/html/rfc4861#section-4.4 +/// [Redirect]: https://tools.ietf.org/html/rfc4861#section-4.5 +impl + AsMut<[u8]>> Packet { + /// Set the target address field. + #[inline] + pub fn set_target_addr(&mut self, value: Ipv6Address) { + let data = self.buffer.as_mut(); + data[field::TARGET_ADDR].copy_from_slice(&value.octets()); + } +} + +/// Setters for the Neighbor Solicitation message header. +/// See [RFC 4861 § 4.3]. +/// +/// [RFC 4861 § 4.3]: https://tools.ietf.org/html/rfc4861#section-4.3 +impl + AsMut<[u8]>> Packet { + /// Set the Neighbor Solicitation flags. + #[inline] + pub fn set_neighbor_flags(&mut self, flags: NeighborFlags) { + self.buffer.as_mut()[field::NEIGH_FLAGS] = flags.bits(); + } +} + +/// Setters for the Redirect message header. +/// See [RFC 4861 § 4.5]. +/// +/// [RFC 4861 § 4.5]: https://tools.ietf.org/html/rfc4861#section-4.5 +impl + AsMut<[u8]>> Packet { + /// Set the destination address field. + #[inline] + pub fn set_dest_addr(&mut self, value: Ipv6Address) { + let data = self.buffer.as_mut(); + data[field::DEST_ADDR].copy_from_slice(&value.octets()); + } +} + +/// A high-level representation of an Neighbor Discovery packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr<'a> { + RouterSolicit { + lladdr: Option, + }, + RouterAdvert { + hop_limit: u8, + flags: RouterFlags, + router_lifetime: Duration, + reachable_time: Duration, + retrans_time: Duration, + lladdr: Option, + mtu: Option, + prefix_info: Option, + }, + NeighborSolicit { + target_addr: Ipv6Address, + lladdr: Option, + }, + NeighborAdvert { + flags: NeighborFlags, + target_addr: Ipv6Address, + lladdr: Option, + }, + Redirect { + target_addr: Ipv6Address, + dest_addr: Ipv6Address, + lladdr: Option, + redirected_hdr: Option>, + }, +} + +impl<'a> Repr<'a> { + /// Parse an NDISC packet and return a high-level representation of the + /// packet. + #[allow(clippy::single_match)] + pub fn parse(packet: &Packet<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + + let (mut src_ll_addr, mut mtu, mut prefix_info, mut target_ll_addr, mut redirected_hdr) = + (None, None, None, None, None); + + let mut offset = 0; + while packet.payload().len() > offset { + let pkt = NdiscOption::new_checked(&packet.payload()[offset..])?; + + // If an option doesn't parse, ignore it and still parse the others. + if let Ok(opt) = NdiscOptionRepr::parse(&pkt) { + match opt { + NdiscOptionRepr::SourceLinkLayerAddr(addr) => src_ll_addr = Some(addr), + NdiscOptionRepr::TargetLinkLayerAddr(addr) => target_ll_addr = Some(addr), + NdiscOptionRepr::PrefixInformation(prefix) => prefix_info = Some(prefix), + NdiscOptionRepr::RedirectedHeader(redirect) => redirected_hdr = Some(redirect), + NdiscOptionRepr::Mtu(m) => mtu = Some(m), + _ => {} + } + } + + let len = pkt.data_len() as usize * 8; + if len == 0 { + return Err(Error); + } + offset += len; + } + + match packet.msg_type() { + Message::RouterSolicit => Ok(Repr::RouterSolicit { + lladdr: src_ll_addr, + }), + Message::RouterAdvert => Ok(Repr::RouterAdvert { + hop_limit: packet.current_hop_limit(), + flags: packet.router_flags(), + router_lifetime: packet.router_lifetime(), + reachable_time: packet.reachable_time(), + retrans_time: packet.retrans_time(), + lladdr: src_ll_addr, + mtu, + prefix_info, + }), + Message::NeighborSolicit => Ok(Repr::NeighborSolicit { + target_addr: packet.target_addr(), + lladdr: src_ll_addr, + }), + Message::NeighborAdvert => Ok(Repr::NeighborAdvert { + flags: packet.neighbor_flags(), + target_addr: packet.target_addr(), + lladdr: target_ll_addr, + }), + Message::Redirect => Ok(Repr::Redirect { + target_addr: packet.target_addr(), + dest_addr: packet.dest_addr(), + lladdr: src_ll_addr, + redirected_hdr, + }), + _ => Err(Error), + } + } + + pub const fn buffer_len(&self) -> usize { + match self { + &Repr::RouterSolicit { lladdr } => match lladdr { + Some(addr) => { + field::UNUSED.end + { NdiscOptionRepr::SourceLinkLayerAddr(addr).buffer_len() } + } + None => field::UNUSED.end, + }, + &Repr::RouterAdvert { + lladdr, + mtu, + prefix_info, + .. + } => { + let mut offset = 0; + if let Some(lladdr) = lladdr { + offset += NdiscOptionRepr::TargetLinkLayerAddr(lladdr).buffer_len(); + } + if let Some(mtu) = mtu { + offset += NdiscOptionRepr::Mtu(mtu).buffer_len(); + } + if let Some(prefix_info) = prefix_info { + offset += NdiscOptionRepr::PrefixInformation(prefix_info).buffer_len(); + } + field::RETRANS_TM.end + offset + } + &Repr::NeighborSolicit { lladdr, .. } | &Repr::NeighborAdvert { lladdr, .. } => { + let mut offset = field::TARGET_ADDR.end; + if let Some(lladdr) = lladdr { + offset += NdiscOptionRepr::SourceLinkLayerAddr(lladdr).buffer_len(); + } + offset + } + &Repr::Redirect { + lladdr, + redirected_hdr, + .. + } => { + let mut offset = field::DEST_ADDR.end; + if let Some(lladdr) = lladdr { + offset += NdiscOptionRepr::TargetLinkLayerAddr(lladdr).buffer_len(); + } + if let Some(NdiscRedirectedHeader { header, data }) = redirected_hdr { + offset += + NdiscOptionRepr::RedirectedHeader(NdiscRedirectedHeader { header, data }) + .buffer_len(); + } + offset + } + } + } + + pub fn emit(&self, packet: &mut Packet<&mut T>) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + match *self { + Repr::RouterSolicit { lladdr } => { + packet.set_msg_type(Message::RouterSolicit); + packet.set_msg_code(0); + packet.clear_reserved(); + if let Some(lladdr) = lladdr { + let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut()); + NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt); + } + } + + Repr::RouterAdvert { + hop_limit, + flags, + router_lifetime, + reachable_time, + retrans_time, + lladdr, + mtu, + prefix_info, + } => { + packet.set_msg_type(Message::RouterAdvert); + packet.set_msg_code(0); + packet.set_current_hop_limit(hop_limit); + packet.set_router_flags(flags); + packet.set_router_lifetime(router_lifetime); + packet.set_reachable_time(reachable_time); + packet.set_retrans_time(retrans_time); + let mut offset = 0; + if let Some(lladdr) = lladdr { + let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut()); + let opt = NdiscOptionRepr::SourceLinkLayerAddr(lladdr); + opt.emit(&mut opt_pkt); + offset += opt.buffer_len(); + } + if let Some(mtu) = mtu { + let mut opt_pkt = + NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]); + NdiscOptionRepr::Mtu(mtu).emit(&mut opt_pkt); + offset += NdiscOptionRepr::Mtu(mtu).buffer_len(); + } + if let Some(prefix_info) = prefix_info { + let mut opt_pkt = + NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]); + NdiscOptionRepr::PrefixInformation(prefix_info).emit(&mut opt_pkt) + } + } + + Repr::NeighborSolicit { + target_addr, + lladdr, + } => { + packet.set_msg_type(Message::NeighborSolicit); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_target_addr(target_addr); + if let Some(lladdr) = lladdr { + let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut()); + NdiscOptionRepr::SourceLinkLayerAddr(lladdr).emit(&mut opt_pkt); + } + } + + Repr::NeighborAdvert { + flags, + target_addr, + lladdr, + } => { + packet.set_msg_type(Message::NeighborAdvert); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_neighbor_flags(flags); + packet.set_target_addr(target_addr); + if let Some(lladdr) = lladdr { + let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut()); + NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt); + } + } + + Repr::Redirect { + target_addr, + dest_addr, + lladdr, + redirected_hdr, + } => { + packet.set_msg_type(Message::Redirect); + packet.set_msg_code(0); + packet.clear_reserved(); + packet.set_target_addr(target_addr); + packet.set_dest_addr(dest_addr); + let offset = match lladdr { + Some(lladdr) => { + let mut opt_pkt = NdiscOption::new_unchecked(packet.payload_mut()); + NdiscOptionRepr::TargetLinkLayerAddr(lladdr).emit(&mut opt_pkt); + NdiscOptionRepr::TargetLinkLayerAddr(lladdr).buffer_len() + } + None => 0, + }; + if let Some(redirected_hdr) = redirected_hdr { + let mut opt_pkt = + NdiscOption::new_unchecked(&mut packet.payload_mut()[offset..]); + NdiscOptionRepr::RedirectedHeader(redirected_hdr).emit(&mut opt_pkt); + } + } + } + } +} + +#[cfg(feature = "medium-ethernet")] +#[cfg(test)] +mod test { + use super::*; + use crate::phy::ChecksumCapabilities; + use crate::wire::EthernetAddress; + use crate::wire::Icmpv6Repr; + + const MOCK_IP_ADDR_1: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const MOCK_IP_ADDR_2: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 2); + + static ROUTER_ADVERT_BYTES: [u8; 24] = [ + 0x86, 0x00, 0xa9, 0xde, 0x40, 0x80, 0x03, 0x84, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, + 0x84, 0x01, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56, + ]; + static SOURCE_LINK_LAYER_OPT: [u8; 8] = [0x01, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56]; + + fn create_repr<'a>() -> Icmpv6Repr<'a> { + Icmpv6Repr::Ndisc(Repr::RouterAdvert { + hop_limit: 64, + flags: RouterFlags::MANAGED, + router_lifetime: Duration::from_secs(900), + reachable_time: Duration::from_millis(900), + retrans_time: Duration::from_millis(900), + lladdr: Some(EthernetAddress([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]).into()), + mtu: None, + prefix_info: None, + }) + } + + #[test] + fn test_router_advert_deconstruct() { + let packet = Packet::new_unchecked(&ROUTER_ADVERT_BYTES[..]); + assert_eq!(packet.msg_type(), Message::RouterAdvert); + assert_eq!(packet.msg_code(), 0); + assert_eq!(packet.current_hop_limit(), 64); + assert_eq!(packet.router_flags(), RouterFlags::MANAGED); + assert_eq!(packet.router_lifetime(), Duration::from_secs(900)); + assert_eq!(packet.reachable_time(), Duration::from_millis(900)); + assert_eq!(packet.retrans_time(), Duration::from_millis(900)); + assert_eq!(packet.payload(), &SOURCE_LINK_LAYER_OPT[..]); + } + + #[test] + fn test_router_advert_construct() { + let mut bytes = vec![0x0; 24]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_msg_type(Message::RouterAdvert); + packet.set_msg_code(0); + packet.set_current_hop_limit(64); + packet.set_router_flags(RouterFlags::MANAGED); + packet.set_router_lifetime(Duration::from_secs(900)); + packet.set_reachable_time(Duration::from_millis(900)); + packet.set_retrans_time(Duration::from_millis(900)); + packet + .payload_mut() + .copy_from_slice(&SOURCE_LINK_LAYER_OPT[..]); + packet.fill_checksum(&MOCK_IP_ADDR_1, &MOCK_IP_ADDR_2); + assert_eq!(&*packet.into_inner(), &ROUTER_ADVERT_BYTES[..]); + } + + #[test] + fn test_router_advert_repr_parse() { + let packet = Packet::new_unchecked(&ROUTER_ADVERT_BYTES[..]); + assert_eq!( + Icmpv6Repr::parse( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &packet, + &ChecksumCapabilities::default() + ) + .unwrap(), + create_repr() + ); + } + + #[test] + fn test_router_advert_repr_emit() { + let mut bytes = [0x2a; 24]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + create_repr().emit( + &MOCK_IP_ADDR_1, + &MOCK_IP_ADDR_2, + &mut packet, + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &ROUTER_ADVERT_BYTES[..]); + } +} diff --git a/vendor/smoltcp/src/wire/ndiscoption.rs b/vendor/smoltcp/src/wire/ndiscoption.rs new file mode 100644 index 00000000..0b9ba8eb --- /dev/null +++ b/vendor/smoltcp/src/wire/ndiscoption.rs @@ -0,0 +1,780 @@ +use bitflags::bitflags; +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use crate::time::Duration; +use crate::wire::{Ipv6Address, Ipv6AddressExt, Ipv6Packet, Ipv6Repr, MAX_HARDWARE_ADDRESS_LEN}; + +use crate::wire::RawHardwareAddress; + +enum_with_unknown! { + /// NDISC Option Type + pub enum Type(u8) { + /// Source Link-layer Address + SourceLinkLayerAddr = 0x1, + /// Target Link-layer Address + TargetLinkLayerAddr = 0x2, + /// Prefix Information + PrefixInformation = 0x3, + /// Redirected Header + RedirectedHeader = 0x4, + /// MTU + Mtu = 0x5 + } +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Type::SourceLinkLayerAddr => write!(f, "source link-layer address"), + Type::TargetLinkLayerAddr => write!(f, "target link-layer address"), + Type::PrefixInformation => write!(f, "prefix information"), + Type::RedirectedHeader => write!(f, "redirected header"), + Type::Mtu => write!(f, "mtu"), + Type::Unknown(id) => write!(f, "{id}"), + } + } +} + +bitflags! { + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct PrefixInfoFlags: u8 { + const ON_LINK = 0b10000000; + const ADDRCONF = 0b01000000; + } +} + +/// A read/write wrapper around an [NDISC Option]. +/// +/// [NDISC Option]: https://tools.ietf.org/html/rfc4861#section-4.6 +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct NdiscOption> { + buffer: T, +} + +// Format of an NDISC Option +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Type | Length | ... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// ~ ... ~ +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// See https://tools.ietf.org/html/rfc4861#section-4.6 for details. +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + // 8-bit identifier of the type of option. + pub const TYPE: usize = 0; + // 8-bit unsigned integer. Length of the option, in units of 8 octets. + pub const LENGTH: usize = 1; + // Minimum length of an option. + pub const MIN_OPT_LEN: usize = 8; + // Variable-length field. Option-Type-specific data. + pub const fn DATA(length: u8) -> Field { + 2..length as usize * 8 + } + + // Source/Target Link-layer Option fields. + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length | Link-Layer Address ... + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // Prefix Information Option fields. + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length | Prefix Length |L|A| Reserved1 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Valid Lifetime | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Preferred Lifetime | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Reserved2 | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // + + + // | | + // + Prefix + + // | | + // + + + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // Prefix length. + pub const PREFIX_LEN: usize = 2; + // Flags field of prefix header. + pub const FLAGS: usize = 3; + // Valid lifetime. + pub const VALID_LT: Field = 4..8; + // Preferred lifetime. + pub const PREF_LT: Field = 8..12; + // Reserved bits + pub const PREF_RESERVED: Field = 12..16; + // Prefix + pub const PREFIX: Field = 16..32; + + // Redirected Header Option fields. + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // ~ IP header + data ~ + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // Reserved bits. + pub const REDIRECTED_RESERVED: Field = 2..8; + pub const REDIR_MIN_SZ: usize = 48; + + // MTU Option fields + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Type | Length | Reserved | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | MTU | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // MTU + pub const MTU: Field = 4..8; +} + +/// Core getter methods relevant to any type of NDISC option. +impl> NdiscOption { + /// Create a raw octet buffer with an NDISC Option structure. + pub const fn new_unchecked(buffer: T) -> NdiscOption { + NdiscOption { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let opt = Self::new_unchecked(buffer); + opt.check_len()?; + + // A data length field of 0 is invalid. + if opt.data_len() == 0 { + return Err(Error); + } + + Ok(opt) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// + /// The result of this check is invalidated by calling [set_data_len]. + /// + /// [set_data_len]: #method.set_data_len + pub fn check_len(&self) -> Result<()> { + let data = self.buffer.as_ref(); + let len = data.len(); + + if len < field::MIN_OPT_LEN { + Err(Error) + } else { + let data_range = field::DATA(data[field::LENGTH]); + if len < data_range.end { + Err(Error) + } else { + match self.option_type() { + Type::SourceLinkLayerAddr | Type::TargetLinkLayerAddr | Type::Mtu => Ok(()), + Type::PrefixInformation if data_range.end >= field::PREFIX.end => Ok(()), + Type::RedirectedHeader if data_range.end >= field::REDIR_MIN_SZ => Ok(()), + Type::Unknown(_) => Ok(()), + _ => Err(Error), + } + } + } + } + + /// Consume the NDISC option, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the option type. + #[inline] + pub fn option_type(&self) -> Type { + let data = self.buffer.as_ref(); + Type::from(data[field::TYPE]) + } + + /// Return the length of the data. + #[inline] + pub fn data_len(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::LENGTH] + } +} + +/// Getter methods only relevant for Source/Target Link-layer Address options. +impl> NdiscOption { + /// Return the Source/Target Link-layer Address. + #[inline] + pub fn link_layer_addr(&self) -> RawHardwareAddress { + let len = MAX_HARDWARE_ADDRESS_LEN.min(self.data_len() as usize * 8 - 2); + let data = self.buffer.as_ref(); + RawHardwareAddress::from_bytes(&data[2..len + 2]) + } +} + +/// Getter methods only relevant for the MTU option. +impl> NdiscOption { + /// Return the MTU value. + #[inline] + pub fn mtu(&self) -> u32 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u32(&data[field::MTU]) + } +} + +/// Getter methods only relevant for the Prefix Information option. +impl> NdiscOption { + /// Return the prefix length. + #[inline] + pub fn prefix_len(&self) -> u8 { + self.buffer.as_ref()[field::PREFIX_LEN] + } + + /// Return the prefix information flags. + #[inline] + pub fn prefix_flags(&self) -> PrefixInfoFlags { + PrefixInfoFlags::from_bits_truncate(self.buffer.as_ref()[field::FLAGS]) + } + + /// Return the valid lifetime of the prefix. + #[inline] + pub fn valid_lifetime(&self) -> Duration { + let data = self.buffer.as_ref(); + Duration::from_secs(NetworkEndian::read_u32(&data[field::VALID_LT]) as u64) + } + + /// Return the preferred lifetime of the prefix. + #[inline] + pub fn preferred_lifetime(&self) -> Duration { + let data = self.buffer.as_ref(); + Duration::from_secs(NetworkEndian::read_u32(&data[field::PREF_LT]) as u64) + } + + /// Return the prefix. + #[inline] + pub fn prefix(&self) -> Ipv6Address { + let data = self.buffer.as_ref(); + crate::wire::ipv6_from_octets(data[field::PREFIX].try_into().unwrap()) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> NdiscOption<&'a T> { + /// Return the option data. + #[inline] + pub fn data(&self) -> &'a [u8] { + let len = self.data_len(); + let data = self.buffer.as_ref(); + &data[field::DATA(len)] + } +} + +/// Core setter methods relevant to any type of NDISC option. +impl + AsMut<[u8]>> NdiscOption { + /// Set the option type. + #[inline] + pub fn set_option_type(&mut self, value: Type) { + let data = self.buffer.as_mut(); + data[field::TYPE] = value.into(); + } + + /// Set the option data length. + #[inline] + pub fn set_data_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::LENGTH] = value; + } +} + +/// Setter methods only relevant for Source/Target Link-layer Address options. +impl + AsMut<[u8]>> NdiscOption { + /// Set the Source/Target Link-layer Address. + #[inline] + pub fn set_link_layer_addr(&mut self, addr: RawHardwareAddress) { + let data = self.buffer.as_mut(); + data[2..2 + addr.len()].copy_from_slice(addr.as_bytes()) + } +} + +/// Setter methods only relevant for the MTU option. +impl + AsMut<[u8]>> NdiscOption { + /// Set the MTU value. + #[inline] + pub fn set_mtu(&mut self, value: u32) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::MTU], value); + } +} + +/// Setter methods only relevant for the Prefix Information option. +impl + AsMut<[u8]>> NdiscOption { + /// Set the prefix length. + #[inline] + pub fn set_prefix_len(&mut self, value: u8) { + self.buffer.as_mut()[field::PREFIX_LEN] = value; + } + + /// Set the prefix information flags. + #[inline] + pub fn set_prefix_flags(&mut self, flags: PrefixInfoFlags) { + self.buffer.as_mut()[field::FLAGS] = flags.bits(); + } + + /// Set the valid lifetime of the prefix. + #[inline] + pub fn set_valid_lifetime(&mut self, time: Duration) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::VALID_LT], time.secs() as u32); + } + + /// Set the preferred lifetime of the prefix. + #[inline] + pub fn set_preferred_lifetime(&mut self, time: Duration) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::PREF_LT], time.secs() as u32); + } + + /// Clear the reserved bits. + #[inline] + pub fn clear_prefix_reserved(&mut self) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u32(&mut data[field::PREF_RESERVED], 0); + } + + /// Set the prefix. + #[inline] + pub fn set_prefix(&mut self, addr: Ipv6Address) { + let data = self.buffer.as_mut(); + data[field::PREFIX].copy_from_slice(&addr.octets()); + } +} + +/// Setter methods only relevant for the Redirected Header option. +impl + AsMut<[u8]>> NdiscOption { + /// Clear the reserved bits. + #[inline] + pub fn clear_redirected_reserved(&mut self) { + let data = self.buffer.as_mut(); + data[field::REDIRECTED_RESERVED].fill_with(|| 0); + } +} + +impl + AsMut<[u8]> + ?Sized> NdiscOption<&mut T> { + /// Return a mutable pointer to the option data. + #[inline] + pub fn data_mut(&mut self) -> &mut [u8] { + let len = self.data_len(); + let data = self.buffer.as_mut(); + &mut data[field::DATA(len)] + } +} + +impl + ?Sized> fmt::Display for NdiscOption<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "NDISC Option ({err})")?; + Ok(()) + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PrefixInformation { + pub prefix_len: u8, + pub flags: PrefixInfoFlags, + pub valid_lifetime: Duration, + pub preferred_lifetime: Duration, + pub prefix: Ipv6Address, +} + +impl PrefixInformation { + /// Validates the prefix information option against check a, b, c in + /// + pub fn is_valid_prefix_info(&self) -> bool { + self.flags.contains(PrefixInfoFlags::ADDRCONF) + && !self.prefix.is_link_local() + && self.preferred_lifetime <= self.valid_lifetime + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RedirectedHeader<'a> { + pub header: Ipv6Repr, + pub data: &'a [u8], +} + +/// A high-level representation of an NDISC Option. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr<'a> { + SourceLinkLayerAddr(RawHardwareAddress), + TargetLinkLayerAddr(RawHardwareAddress), + PrefixInformation(PrefixInformation), + RedirectedHeader(RedirectedHeader<'a>), + Mtu(u32), + Unknown { + type_: u8, + length: u8, + data: &'a [u8], + }, +} + +impl<'a> Repr<'a> { + /// Parse an NDISC Option and return a high-level representation. + pub fn parse(opt: &NdiscOption<&'a T>) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + opt.check_len()?; + + match opt.option_type() { + Type::SourceLinkLayerAddr => { + if opt.data_len() >= 1 { + Ok(Repr::SourceLinkLayerAddr(opt.link_layer_addr())) + } else { + Err(Error) + } + } + Type::TargetLinkLayerAddr => { + if opt.data_len() >= 1 { + Ok(Repr::TargetLinkLayerAddr(opt.link_layer_addr())) + } else { + Err(Error) + } + } + Type::PrefixInformation => { + if opt.data_len() == 4 { + Ok(Repr::PrefixInformation(PrefixInformation { + prefix_len: opt.prefix_len(), + flags: opt.prefix_flags(), + valid_lifetime: opt.valid_lifetime(), + preferred_lifetime: opt.preferred_lifetime(), + prefix: opt.prefix(), + })) + } else { + Err(Error) + } + } + Type::RedirectedHeader => { + // If the options data length is less than 6, the option + // does not have enough data to fill out the IP header + // and common option fields. + if opt.data_len() < 6 { + Err(Error) + } else { + let redirected_packet = &opt.data()[field::REDIRECTED_RESERVED.len()..]; + + let ip_packet = Ipv6Packet::new_checked(redirected_packet)?; + let ip_repr = Ipv6Repr::parse(&ip_packet)?; + + Ok(Repr::RedirectedHeader(RedirectedHeader { + header: ip_repr, + data: &redirected_packet[ip_repr.buffer_len()..][..ip_repr.payload_len], + })) + } + } + Type::Mtu => { + if opt.data_len() == 1 { + Ok(Repr::Mtu(opt.mtu())) + } else { + Err(Error) + } + } + Type::Unknown(id) => { + // A length of 0 is invalid. + if opt.data_len() != 0 { + Ok(Repr::Unknown { + type_: id, + length: opt.data_len(), + data: opt.data(), + }) + } else { + Err(Error) + } + } + } + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + match self { + &Repr::SourceLinkLayerAddr(addr) | &Repr::TargetLinkLayerAddr(addr) => { + let len = 2 + addr.len(); + // Round up to next multiple of 8 + len.div_ceil(8) * 8 + } + &Repr::PrefixInformation(_) => field::PREFIX.end, + &Repr::RedirectedHeader(RedirectedHeader { header, data }) => { + (8 + header.buffer_len() + data.len()).div_ceil(8) * 8 + } + &Repr::Mtu(_) => field::MTU.end, + &Repr::Unknown { length, .. } => field::DATA(length).end, + } + } + + /// Emit a high-level representation into an NDISC Option. + pub fn emit(&self, opt: &mut NdiscOption<&'a mut T>) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + match *self { + Repr::SourceLinkLayerAddr(addr) => { + opt.set_option_type(Type::SourceLinkLayerAddr); + let opt_len = addr.len() + 2; + opt.set_data_len(opt_len.div_ceil(8) as u8); // round to next multiple of 8. + opt.set_link_layer_addr(addr); + } + Repr::TargetLinkLayerAddr(addr) => { + opt.set_option_type(Type::TargetLinkLayerAddr); + let opt_len = addr.len() + 2; + opt.set_data_len(opt_len.div_ceil(8) as u8); // round to next multiple of 8. + opt.set_link_layer_addr(addr); + } + Repr::PrefixInformation(PrefixInformation { + prefix_len, + flags, + valid_lifetime, + preferred_lifetime, + prefix, + }) => { + opt.clear_prefix_reserved(); + opt.set_option_type(Type::PrefixInformation); + opt.set_data_len(4); + opt.set_prefix_len(prefix_len); + opt.set_prefix_flags(flags); + opt.set_valid_lifetime(valid_lifetime); + opt.set_preferred_lifetime(preferred_lifetime); + opt.set_prefix(prefix); + } + Repr::RedirectedHeader(RedirectedHeader { header, data }) => { + // TODO(thvdveld): I think we need to check if the data we are sending is not + // exceeding the MTU. + opt.clear_redirected_reserved(); + opt.set_option_type(Type::RedirectedHeader); + opt.set_data_len((8 + header.buffer_len() + data.len()).div_ceil(8) as u8); + let mut packet = &mut opt.data_mut()[field::REDIRECTED_RESERVED.end - 2..]; + let mut ip_packet = Ipv6Packet::new_unchecked(&mut packet); + header.emit(&mut ip_packet); + ip_packet.payload_mut().copy_from_slice(data); + } + Repr::Mtu(mtu) => { + opt.set_option_type(Type::Mtu); + opt.set_data_len(1); + opt.set_mtu(mtu); + } + Repr::Unknown { + type_: id, + length, + data, + } => { + opt.set_option_type(Type::Unknown(id)); + opt.set_data_len(length); + opt.data_mut().copy_from_slice(data); + } + } + } +} + +impl<'a> fmt::Display for Repr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "NDISC Option: ")?; + match *self { + Repr::SourceLinkLayerAddr(addr) => { + write!(f, "SourceLinkLayer addr={addr}") + } + Repr::TargetLinkLayerAddr(addr) => { + write!(f, "TargetLinkLayer addr={addr}") + } + Repr::PrefixInformation(PrefixInformation { + prefix, prefix_len, .. + }) => { + write!(f, "PrefixInformation prefix={prefix}/{prefix_len}") + } + Repr::RedirectedHeader(RedirectedHeader { header, .. }) => { + write!(f, "RedirectedHeader header={header}") + } + Repr::Mtu(mtu) => { + write!(f, "MTU mtu={mtu}") + } + Repr::Unknown { + type_: id, length, .. + } => { + write!(f, "Unknown({id}) length={length}") + } + } + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for NdiscOption { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + match NdiscOption::new_checked(buffer) { + Err(err) => write!(f, "{indent}({err})"), + Ok(ndisc) => match Repr::parse(&ndisc) { + Err(_) => Ok(()), + Ok(repr) => { + write!(f, "{indent}{repr}") + } + }, + } + } +} + +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] +#[cfg(test)] +mod test { + use super::Error; + use super::{NdiscOption, PrefixInfoFlags, PrefixInformation, Repr, Type}; + use crate::time::Duration; + use crate::wire::Ipv6Address; + + #[cfg(feature = "medium-ethernet")] + use crate::wire::EthernetAddress; + #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))] + use crate::wire::Ieee802154Address; + + static PREFIX_OPT_BYTES: [u8; 32] = [ + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, + 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ]; + + #[test] + fn test_deconstruct() { + let opt = NdiscOption::new_unchecked(&PREFIX_OPT_BYTES[..]); + assert_eq!(opt.option_type(), Type::PrefixInformation); + assert_eq!(opt.data_len(), 4); + assert_eq!(opt.prefix_len(), 64); + assert_eq!( + opt.prefix_flags(), + PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF + ); + assert_eq!(opt.valid_lifetime(), Duration::from_secs(900)); + assert_eq!(opt.preferred_lifetime(), Duration::from_secs(1000)); + assert_eq!(opt.prefix(), Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); + } + + #[test] + fn test_construct() { + let mut bytes = [0x00; 32]; + let mut opt = NdiscOption::new_unchecked(&mut bytes[..]); + opt.set_option_type(Type::PrefixInformation); + opt.set_data_len(4); + opt.set_prefix_len(64); + opt.set_prefix_flags(PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF); + opt.set_valid_lifetime(Duration::from_secs(900)); + opt.set_preferred_lifetime(Duration::from_secs(1000)); + opt.set_prefix(Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1)); + assert_eq!(&PREFIX_OPT_BYTES[..], &*opt.into_inner()); + } + + #[test] + fn test_short_packet() { + assert_eq!(NdiscOption::new_checked(&[0x00, 0x00]), Err(Error)); + let bytes = [0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + assert_eq!(NdiscOption::new_checked(&bytes), Err(Error)); + } + + #[cfg(feature = "medium-ethernet")] + #[test] + fn test_repr_parse_link_layer_opt_ethernet() { + let mut bytes = [0x01, 0x01, 0x54, 0x52, 0x00, 0x12, 0x23, 0x34]; + let addr = EthernetAddress([0x54, 0x52, 0x00, 0x12, 0x23, 0x34]); + { + assert_eq!( + Repr::parse(&NdiscOption::new_unchecked(&bytes)), + Ok(Repr::SourceLinkLayerAddr(addr.into())) + ); + } + bytes[0] = 0x02; + { + assert_eq!( + Repr::parse(&NdiscOption::new_unchecked(&bytes)), + Ok(Repr::TargetLinkLayerAddr(addr.into())) + ); + } + } + + #[cfg(all(not(feature = "medium-ethernet"), feature = "medium-ieee802154"))] + #[test] + fn test_repr_parse_link_layer_opt_ieee802154() { + let mut bytes = [ + 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + let addr = Ieee802154Address::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + { + assert_eq!( + Repr::parse(&NdiscOption::new_unchecked(&bytes)), + Ok(Repr::SourceLinkLayerAddr(addr.into())) + ); + } + bytes[0] = 0x02; + { + assert_eq!( + Repr::parse(&NdiscOption::new_unchecked(&bytes)), + Ok(Repr::TargetLinkLayerAddr(addr.into())) + ); + } + } + + #[test] + fn test_repr_parse_prefix_info() { + let repr = Repr::PrefixInformation(PrefixInformation { + prefix_len: 64, + flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF, + valid_lifetime: Duration::from_secs(900), + preferred_lifetime: Duration::from_secs(1000), + prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + }); + assert_eq!( + Repr::parse(&NdiscOption::new_unchecked(&PREFIX_OPT_BYTES)), + Ok(repr) + ); + } + + #[test] + fn test_repr_emit_prefix_info() { + let mut bytes = [0x2a; 32]; + let repr = Repr::PrefixInformation(PrefixInformation { + prefix_len: 64, + flags: PrefixInfoFlags::ON_LINK | PrefixInfoFlags::ADDRCONF, + valid_lifetime: Duration::from_secs(900), + preferred_lifetime: Duration::from_secs(1000), + prefix: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), + }); + let mut opt = NdiscOption::new_unchecked(&mut bytes); + repr.emit(&mut opt); + assert_eq!(&opt.into_inner()[..], &PREFIX_OPT_BYTES[..]); + } + + #[test] + fn test_repr_parse_mtu() { + let bytes = [0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc]; + assert_eq!( + Repr::parse(&NdiscOption::new_unchecked(&bytes)), + Ok(Repr::Mtu(1500)) + ); + } +} diff --git a/vendor/smoltcp/src/wire/pretty_print.rs b/vendor/smoltcp/src/wire/pretty_print.rs new file mode 100644 index 00000000..fe7d8b89 --- /dev/null +++ b/vendor/smoltcp/src/wire/pretty_print.rs @@ -0,0 +1,126 @@ +/*! Pretty-printing of packet representation. + +The `pretty_print` module provides bits and pieces for printing concise, +easily human readable packet listings. + +# Example + +A packet can be formatted using the `PrettyPrinter` wrapper: + +```rust +use smoltcp::wire::*; +let buffer = vec![ + // Ethernet II + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x08, 0x00, + // IPv4 + 0x45, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x40, 0x00, + 0x40, 0x01, 0xd2, 0x79, + 0x11, 0x12, 0x13, 0x14, + 0x21, 0x22, 0x23, 0x24, + // ICMPv4 + 0x08, 0x00, 0x8e, 0xfe, + 0x12, 0x34, 0xab, 0xcd, + 0xaa, 0x00, 0x00, 0xff +]; + +let result = "\ +EthernetII src=11-12-13-14-15-16 dst=01-02-03-04-05-06 type=IPv4\n\ +\\ IPv4 src=17.18.19.20 dst=33.34.35.36 proto=ICMP (checksum incorrect)\n \ + \\ ICMPv4 echo request id=4660 seq=43981 len=4\ +"; + +#[cfg(all(feature = "medium-ethernet", feature = "proto-ipv4"))] +assert_eq!( + result, + &format!("{}", PrettyPrinter::>::new("", &buffer)) +); +``` +*/ + +use core::fmt; +use core::marker::PhantomData; + +/// Indentation state. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PrettyIndent { + prefix: &'static str, + level: usize, +} + +impl PrettyIndent { + /// Create an indentation state. The entire listing will be indented by the width + /// of `prefix`, and `prefix` will appear at the start of the first line. + pub fn new(prefix: &'static str) -> PrettyIndent { + PrettyIndent { prefix, level: 0 } + } + + /// Increase indentation level. + pub fn increase(&mut self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f)?; + self.level += 1; + Ok(()) + } +} + +impl fmt::Display for PrettyIndent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.level == 0 { + write!(f, "{}", self.prefix) + } else { + write!(f, "{0:1$}{0:2$}\\ ", "", self.prefix.len(), self.level - 1) + } + } +} + +/// Interface for printing listings. +pub trait PrettyPrint { + /// Write a concise, formatted representation of a packet contained in the provided + /// buffer, and any nested packets it may contain. + /// + /// `pretty_print` accepts a buffer and not a packet wrapper because the packet might + /// be truncated, and so it might not be possible to create the packet wrapper. + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + fmt: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result; +} + +/// Wrapper for using a `PrettyPrint` where a `Display` is expected. +pub struct PrettyPrinter<'a, T: PrettyPrint> { + prefix: &'static str, + buffer: &'a dyn AsRef<[u8]>, + phantom: PhantomData, +} + +impl<'a, T: PrettyPrint> PrettyPrinter<'a, T> { + /// Format the listing with the recorded parameters when Display::fmt is called. + pub fn new(prefix: &'static str, buffer: &'a dyn AsRef<[u8]>) -> PrettyPrinter<'a, T> { + PrettyPrinter { + prefix: prefix, + buffer: buffer, + phantom: PhantomData, + } + } +} + +impl<'a, T: PrettyPrint + AsRef<[u8]>> PrettyPrinter<'a, T> { + /// Create a `PrettyPrinter` which prints the given object. + pub fn print(printable: &'a T) -> PrettyPrinter<'a, T> { + PrettyPrinter { + prefix: "", + buffer: printable, + phantom: PhantomData, + } + } +} + +impl<'a, T: PrettyPrint> fmt::Display for PrettyPrinter<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + T::pretty_print(&self.buffer, f, &mut PrettyIndent::new(self.prefix)) + } +} diff --git a/vendor/smoltcp/src/wire/rpl.rs b/vendor/smoltcp/src/wire/rpl.rs new file mode 100644 index 00000000..75cf5e43 --- /dev/null +++ b/vendor/smoltcp/src/wire/rpl.rs @@ -0,0 +1,2727 @@ +//! Implementation of the RPL packet formats. See [RFC 6550 § 6]. +//! +//! [RFC 6550 § 6]: https://datatracker.ietf.org/doc/html/rfc6550#section-6 + +use byteorder::{ByteOrder, NetworkEndian}; + +use super::{Error, Result}; +use crate::wire::icmpv6::Packet; +use crate::wire::ipv6::{Address, AddressExt}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum InstanceId { + Global(u8), + Local(u8), +} + +impl From for InstanceId { + fn from(val: u8) -> Self { + const MASK: u8 = 0b0111_1111; + + if ((val >> 7) & 0xb1) == 0b0 { + Self::Global(val & MASK) + } else { + Self::Local(val & MASK) + } + } +} + +impl From for u8 { + fn from(val: InstanceId) -> Self { + match val { + InstanceId::Global(val) => 0b0000_0000 | val, + InstanceId::Local(val) => 0b1000_0000 | val, + } + } +} + +impl InstanceId { + /// Return the real part of the ID. + pub fn id(&self) -> u8 { + match self { + Self::Global(val) => *val, + Self::Local(val) => *val, + } + } + + /// Returns `true` when the DODAG ID is the destination address of the IPv6 packet. + #[inline] + pub fn dodag_is_destination(&self) -> bool { + match self { + Self::Global(_) => false, + Self::Local(val) => ((val >> 6) & 0b1) == 0b1, + } + } + + /// Returns `true` when the DODAG ID is the source address of the IPv6 packet. + /// + /// *NOTE*: this only makes sense when using a local RPL Instance ID and the packet is not a + /// RPL control message. + #[inline] + pub fn dodag_is_source(&self) -> bool { + !self.dodag_is_destination() + } +} + +mod field { + use crate::wire::field::*; + + pub const RPL_INSTANCE_ID: usize = 4; + + // DODAG information solicitation fields (DIS) + pub const DIS_FLAGS: usize = 4; + pub const DIS_RESERVED: usize = 5; + + // DODAG information object fields (DIO) + pub const DIO_VERSION_NUMBER: usize = 5; + pub const DIO_RANK: Field = 6..8; + pub const DIO_GROUNDED: usize = 8; + pub const DIO_MOP: usize = 8; + pub const DIO_PRF: usize = 8; + pub const DIO_DTSN: usize = 9; + //pub const DIO_FLAGS: usize = 10; + //pub const DIO_RESERVED: usize = 11; + pub const DIO_DODAG_ID: Field = 12..12 + 16; + + // Destination advertisement object (DAO) + pub const DAO_K: usize = 5; + pub const DAO_D: usize = 5; + //pub const DAO_FLAGS: usize = 5; + //pub const DAO_RESERVED: usize = 6; + pub const DAO_SEQUENCE: usize = 7; + pub const DAO_DODAG_ID: Field = 8..8 + 16; + + // Destination advertisement object ack (DAO-ACK) + pub const DAO_ACK_D: usize = 5; + //pub const DAO_ACK_RESERVED: usize = 5; + pub const DAO_ACK_SEQUENCE: usize = 6; + pub const DAO_ACK_STATUS: usize = 7; + pub const DAO_ACK_DODAG_ID: Field = 8..8 + 16; +} + +enum_with_unknown! { + /// RPL Control Message subtypes. + pub enum RplControlMessage(u8) { + DodagInformationSolicitation = 0x00, + DodagInformationObject = 0x01, + DestinationAdvertisementObject = 0x02, + DestinationAdvertisementObjectAck = 0x03, + SecureDodagInformationSolicitation = 0x80, + SecureDodagInformationObject = 0x81, + SecureDestinationAdvertisementObject = 0x82, + SecureDestinationAdvertisementObjectAck = 0x83, + ConsistencyCheck = 0x8a, + } +} + +impl core::fmt::Display for RplControlMessage { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + RplControlMessage::DodagInformationSolicitation => { + write!(f, "DODAG information solicitation (DIS)") + } + RplControlMessage::DodagInformationObject => { + write!(f, "DODAG information object (DIO)") + } + RplControlMessage::DestinationAdvertisementObject => { + write!(f, "destination advertisement object (DAO)") + } + RplControlMessage::DestinationAdvertisementObjectAck => write!( + f, + "destination advertisement object acknowledgement (DAO-ACK)" + ), + RplControlMessage::SecureDodagInformationSolicitation => { + write!(f, "secure DODAG information solicitation (DIS)") + } + RplControlMessage::SecureDodagInformationObject => { + write!(f, "secure DODAG information object (DIO)") + } + RplControlMessage::SecureDestinationAdvertisementObject => { + write!(f, "secure destination advertisement object (DAO)") + } + RplControlMessage::SecureDestinationAdvertisementObjectAck => write!( + f, + "secure destination advertisement object acknowledgement (DAO-ACK)" + ), + RplControlMessage::ConsistencyCheck => write!(f, "consistency check (CC)"), + RplControlMessage::Unknown(id) => write!(f, "{}", id), + } + } +} + +impl> Packet { + /// Return the RPL instance ID. + #[inline] + pub fn rpl_instance_id(&self) -> InstanceId { + get!(self.buffer, into: InstanceId, field: field::RPL_INSTANCE_ID) + } +} + +impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> { + /// Return a pointer to the options. + pub fn options(&self) -> Result<&'p [u8]> { + let len = self.buffer.as_ref().len(); + match RplControlMessage::from(self.msg_code()) { + RplControlMessage::DodagInformationSolicitation if len < field::DIS_RESERVED + 1 => { + return Err(Error); + } + RplControlMessage::DodagInformationObject if len < field::DIO_DODAG_ID.end => { + return Err(Error); + } + RplControlMessage::DestinationAdvertisementObject + if self.dao_dodag_id_present() && len < field::DAO_DODAG_ID.end => + { + return Err(Error); + } + RplControlMessage::DestinationAdvertisementObject if len < field::DAO_SEQUENCE + 1 => { + return Err(Error); + } + RplControlMessage::DestinationAdvertisementObjectAck + if self.dao_ack_dodag_id_present() && len < field::DAO_ACK_DODAG_ID.end => + { + return Err(Error); + } + RplControlMessage::DestinationAdvertisementObjectAck + if len < field::DAO_ACK_STATUS + 1 => + { + return Err(Error); + } + RplControlMessage::SecureDodagInformationSolicitation + | RplControlMessage::SecureDodagInformationObject + | RplControlMessage::SecureDestinationAdvertisementObject + | RplControlMessage::SecureDestinationAdvertisementObjectAck + | RplControlMessage::ConsistencyCheck => return Err(Error), + RplControlMessage::Unknown(_) => return Err(Error), + _ => {} + } + + let buffer = &self.buffer.as_ref(); + Ok(match RplControlMessage::from(self.msg_code()) { + RplControlMessage::DodagInformationSolicitation => &buffer[field::DIS_RESERVED + 1..], + RplControlMessage::DodagInformationObject => &buffer[field::DIO_DODAG_ID.end..], + RplControlMessage::DestinationAdvertisementObject if self.dao_dodag_id_present() => { + &buffer[field::DAO_DODAG_ID.end..] + } + RplControlMessage::DestinationAdvertisementObject => &buffer[field::DAO_SEQUENCE + 1..], + RplControlMessage::DestinationAdvertisementObjectAck + if self.dao_ack_dodag_id_present() => + { + &buffer[field::DAO_ACK_DODAG_ID.end..] + } + RplControlMessage::DestinationAdvertisementObjectAck => { + &buffer[field::DAO_ACK_STATUS + 1..] + } + RplControlMessage::SecureDodagInformationSolicitation + | RplControlMessage::SecureDodagInformationObject + | RplControlMessage::SecureDestinationAdvertisementObject + | RplControlMessage::SecureDestinationAdvertisementObjectAck + | RplControlMessage::ConsistencyCheck => unreachable!(), + RplControlMessage::Unknown(_) => unreachable!(), + }) + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the RPL Instance ID field. + #[inline] + pub fn set_rpl_instance_id(&mut self, value: u8) { + set!(self.buffer, value, field: field::RPL_INSTANCE_ID) + } +} + +impl<'p, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'p mut T> { + /// Return a pointer to the options. + pub fn options_mut(&mut self) -> &mut [u8] { + match RplControlMessage::from(self.msg_code()) { + RplControlMessage::DodagInformationSolicitation => { + &mut self.buffer.as_mut()[field::DIS_RESERVED + 1..] + } + RplControlMessage::DodagInformationObject => { + &mut self.buffer.as_mut()[field::DIO_DODAG_ID.end..] + } + RplControlMessage::DestinationAdvertisementObject => { + if self.dao_dodag_id_present() { + &mut self.buffer.as_mut()[field::DAO_DODAG_ID.end..] + } else { + &mut self.buffer.as_mut()[field::DAO_SEQUENCE + 1..] + } + } + RplControlMessage::DestinationAdvertisementObjectAck => { + if self.dao_ack_dodag_id_present() { + &mut self.buffer.as_mut()[field::DAO_ACK_DODAG_ID.end..] + } else { + &mut self.buffer.as_mut()[field::DAO_ACK_STATUS + 1..] + } + } + RplControlMessage::SecureDodagInformationSolicitation + | RplControlMessage::SecureDodagInformationObject + | RplControlMessage::SecureDestinationAdvertisementObject + | RplControlMessage::SecureDestinationAdvertisementObjectAck + | RplControlMessage::ConsistencyCheck => todo!("Secure messages not supported"), + RplControlMessage::Unknown(_) => todo!(), + } + } +} + +/// Getters for the DODAG information solicitation (DIS) message. +/// +/// ```txt +/// 0 1 2 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Flags | Reserved | Option(s)... +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +impl> Packet { + /// Return the DIS flags field. + #[inline] + pub fn dis_flags(&self) -> u8 { + get!(self.buffer, field: field::DIS_FLAGS) + } + + /// Return the DIS reserved field. + #[inline] + pub fn dis_reserved(&self) -> u8 { + get!(self.buffer, field: field::DIS_RESERVED) + } +} + +/// Setters for the DODAG information solicitation (DIS) message. +impl + AsMut<[u8]>> Packet { + /// Clear the DIS flags field. + pub fn clear_dis_flags(&mut self) { + self.buffer.as_mut()[field::DIS_FLAGS] = 0; + } + + /// Clear the DIS rserved field. + pub fn clear_dis_reserved(&mut self) { + self.buffer.as_mut()[field::DIS_RESERVED] = 0; + } +} + +enum_with_unknown! { + pub enum ModeOfOperation(u8) { + NoDownwardRoutesMaintained = 0x00, + NonStoringMode = 0x01, + StoringModeWithoutMulticast = 0x02, + StoringModeWithMulticast = 0x03, + } +} + +impl Default for ModeOfOperation { + fn default() -> Self { + Self::StoringModeWithoutMulticast + } +} + +/// Getters for the DODAG information object (DIO) message. +/// +/// ```txt +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | RPLInstanceID |Version Number | Rank | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// |G|0| MOP | Prf | DTSN | Flags | Reserved | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | | +/// + + +/// | | +/// + DODAGID + +/// | | +/// + + +/// | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Option(s)... +/// +-+-+-+-+-+-+-+-+ +/// ``` +impl> Packet { + /// Return the Version Number field. + #[inline] + pub fn dio_version_number(&self) -> u8 { + get!(self.buffer, field: field::DIO_VERSION_NUMBER) + } + + /// Return the Rank field. + #[inline] + pub fn dio_rank(&self) -> u16 { + get!(self.buffer, u16, field: field::DIO_RANK) + } + + /// Return the value of the Grounded flag. + #[inline] + pub fn dio_grounded(&self) -> bool { + get!(self.buffer, bool, field: field::DIO_GROUNDED, shift: 7, mask: 0b01) + } + + /// Return the mode of operation field. + #[inline] + pub fn dio_mode_of_operation(&self) -> ModeOfOperation { + get!(self.buffer, into: ModeOfOperation, field: field::DIO_MOP, shift: 3, mask: 0b111) + } + + /// Return the DODAG preference field. + #[inline] + pub fn dio_dodag_preference(&self) -> u8 { + get!(self.buffer, field: field::DIO_PRF, mask: 0b111) + } + + /// Return the destination advertisement trigger sequence number. + #[inline] + pub fn dio_dest_adv_trigger_seq_number(&self) -> u8 { + get!(self.buffer, field: field::DIO_DTSN) + } + + /// Return the DODAG id, which is an IPv6 address. + #[inline] + pub fn dio_dodag_id(&self) -> Address { + crate::wire::ipv6_from_octets( + self.buffer.as_ref()[field::DIO_DODAG_ID] + .try_into() + .unwrap(), + ) + } +} + +/// Setters for the DODAG information object (DIO) message. +impl + AsMut<[u8]>> Packet { + /// Set the Version Number field. + #[inline] + pub fn set_dio_version_number(&mut self, value: u8) { + set!(self.buffer, value, field: field::DIO_VERSION_NUMBER) + } + + /// Set the Rank field. + #[inline] + pub fn set_dio_rank(&mut self, value: u16) { + set!(self.buffer, value, u16, field: field::DIO_RANK) + } + + /// Set the value of the Grounded flag. + #[inline] + pub fn set_dio_grounded(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::DIO_GROUNDED, shift: 7, mask: 0b01) + } + + /// Set the mode of operation field. + #[inline] + pub fn set_dio_mode_of_operation(&mut self, mode: ModeOfOperation) { + let raw = (self.buffer.as_ref()[field::DIO_MOP] & !(0b111 << 3)) | (u8::from(mode) << 3); + self.buffer.as_mut()[field::DIO_MOP] = raw; + } + + /// Set the DODAG preference field. + #[inline] + pub fn set_dio_dodag_preference(&mut self, value: u8) { + set!(self.buffer, value, field: field::DIO_PRF, mask: 0b111) + } + + /// Set the destination advertisement trigger sequence number. + #[inline] + pub fn set_dio_dest_adv_trigger_seq_number(&mut self, value: u8) { + set!(self.buffer, value, field: field::DIO_DTSN) + } + + /// Set the DODAG id, which is an IPv6 address. + #[inline] + pub fn set_dio_dodag_id(&mut self, address: Address) { + set!(self.buffer, address: address, field: field::DIO_DODAG_ID) + } +} + +/// Getters for the Destination Advertisement Object (DAO) message. +/// +/// ```txt +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | RPLInstanceID |K|D| Flags | Reserved | DAOSequence | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | | +/// + + +/// | | +/// + DODAGID* + +/// | | +/// + + +/// | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Option(s)... +/// +-+-+-+-+-+-+-+-+ +/// ``` +impl> Packet { + /// Returns the Expect DAO-ACK flag. + #[inline] + pub fn dao_ack_request(&self) -> bool { + get!(self.buffer, bool, field: field::DAO_K, shift: 7, mask: 0b1) + } + + /// Returns the flag indicating that the DODAG ID is present or not. + #[inline] + pub fn dao_dodag_id_present(&self) -> bool { + get!(self.buffer, bool, field: field::DAO_D, shift: 6, mask: 0b1) + } + + /// Returns the DODAG sequence flag. + #[inline] + pub fn dao_dodag_sequence(&self) -> u8 { + get!(self.buffer, field: field::DAO_SEQUENCE) + } + + /// Returns the DODAG ID, an IPv6 address, when it is present. + #[inline] + pub fn dao_dodag_id(&self) -> Option
{ + if self.dao_dodag_id_present() { + Some(crate::wire::ipv6_from_octets( + self.buffer.as_ref()[field::DAO_DODAG_ID] + .try_into() + .unwrap(), + )) + } else { + None + } + } +} + +/// Setters for the Destination Advertisement Object (DAO) message. +impl + AsMut<[u8]>> Packet { + /// Set the Expect DAO-ACK flag. + #[inline] + pub fn set_dao_ack_request(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::DAO_K, shift: 7, mask: 0b1,) + } + + /// Set the flag indicating that the DODAG ID is present or not. + #[inline] + pub fn set_dao_dodag_id_present(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::DAO_D, shift: 6, mask: 0b1) + } + + /// Set the DODAG sequence flag. + #[inline] + pub fn set_dao_dodag_sequence(&mut self, value: u8) { + set!(self.buffer, value, field: field::DAO_SEQUENCE) + } + + /// Set the DODAG ID. + #[inline] + pub fn set_dao_dodag_id(&mut self, address: Option
) { + match address { + Some(address) => { + self.buffer.as_mut()[field::DAO_DODAG_ID].copy_from_slice(&address.octets()); + self.set_dao_dodag_id_present(true); + } + None => { + self.set_dao_dodag_id_present(false); + } + } + } +} + +/// Getters for the Destination Advertisement Object acknowledgement (DAO-ACK) message. +/// +/// ```txt +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | RPLInstanceID |D| Reserved | DAOSequence | Status | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | | +/// + + +/// | | +/// + DODAGID* + +/// | | +/// + + +/// | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Option(s)... +/// +-+-+-+-+-+-+-+-+ +/// ``` +impl> Packet { + /// Returns the flag indicating that the DODAG ID is present or not. + #[inline] + pub fn dao_ack_dodag_id_present(&self) -> bool { + get!(self.buffer, bool, field: field::DAO_ACK_D, shift: 7, mask: 0b1) + } + + /// Return the DODAG sequence number. + #[inline] + pub fn dao_ack_sequence(&self) -> u8 { + get!(self.buffer, field: field::DAO_ACK_SEQUENCE) + } + + /// Return the DOA status field. + #[inline] + pub fn dao_ack_status(&self) -> u8 { + get!(self.buffer, field: field::DAO_ACK_STATUS) + } + + /// Returns the DODAG ID, an IPv6 address, when it is present. + #[inline] + pub fn dao_ack_dodag_id(&self) -> Option
{ + if self.dao_ack_dodag_id_present() { + Some(crate::wire::ipv6_from_octets( + self.buffer.as_ref()[field::DAO_ACK_DODAG_ID] + .try_into() + .unwrap(), + )) + } else { + None + } + } +} + +/// Setters for the Destination Advertisement Object acknowledgement (DAO-ACK) message. +impl + AsMut<[u8]>> Packet { + /// Set the flag indicating that the DODAG ID is present or not. + #[inline] + pub fn set_dao_ack_dodag_id_present(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::DAO_ACK_D, shift: 7, mask: 0b1) + } + + /// Set the DODAG sequence number. + #[inline] + pub fn set_dao_ack_sequence(&mut self, value: u8) { + set!(self.buffer, value, field: field::DAO_ACK_SEQUENCE) + } + + /// Set the DOA status field. + #[inline] + pub fn set_dao_ack_status(&mut self, value: u8) { + set!(self.buffer, value, field: field::DAO_ACK_STATUS) + } + + /// Set the DODAG ID. + #[inline] + pub fn set_dao_ack_dodag_id(&mut self, address: Option
) { + match address { + Some(address) => { + self.buffer.as_mut()[field::DAO_ACK_DODAG_ID].copy_from_slice(&address.octets()); + self.set_dao_ack_dodag_id_present(true); + } + None => { + self.set_dao_ack_dodag_id_present(false); + } + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Repr<'p> { + DodagInformationSolicitation { + options: &'p [u8], + }, + DodagInformationObject { + rpl_instance_id: InstanceId, + version_number: u8, + rank: u16, + grounded: bool, + mode_of_operation: ModeOfOperation, + dodag_preference: u8, + dtsn: u8, + dodag_id: Address, + options: &'p [u8], + }, + DestinationAdvertisementObject { + rpl_instance_id: InstanceId, + expect_ack: bool, + sequence: u8, + dodag_id: Option
, + options: &'p [u8], + }, + DestinationAdvertisementObjectAck { + rpl_instance_id: InstanceId, + sequence: u8, + status: u8, + dodag_id: Option
, + }, +} + +impl core::fmt::Display for Repr<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Repr::DodagInformationSolicitation { .. } => { + write!(f, "DIS")?; + } + Repr::DodagInformationObject { + rpl_instance_id, + version_number, + rank, + grounded, + mode_of_operation, + dodag_preference, + dtsn, + dodag_id, + .. + } => { + write!( + f, + "DIO \ + IID={rpl_instance_id:?} \ + V={version_number} \ + R={rank} \ + G={grounded} \ + MOP={mode_of_operation:?} \ + Pref={dodag_preference} \ + DTSN={dtsn} \ + DODAGID={dodag_id}" + )?; + } + Repr::DestinationAdvertisementObject { + rpl_instance_id, + expect_ack, + sequence, + dodag_id, + .. + } => { + write!( + f, + "DAO \ + IID={rpl_instance_id:?} \ + Ack={expect_ack} \ + Seq={sequence} \ + DODAGID={dodag_id:?}", + )?; + } + Repr::DestinationAdvertisementObjectAck { + rpl_instance_id, + sequence, + status, + dodag_id, + .. + } => { + write!( + f, + "DAO-ACK \ + IID={rpl_instance_id:?} \ + Seq={sequence} \ + Status={status} \ + DODAGID={dodag_id:?}", + )?; + } + }; + + Ok(()) + } +} + +impl<'p> Repr<'p> { + pub fn set_options(&mut self, options: &'p [u8]) { + let opts = match self { + Repr::DodagInformationSolicitation { options } => options, + Repr::DodagInformationObject { options, .. } => options, + Repr::DestinationAdvertisementObject { options, .. } => options, + Repr::DestinationAdvertisementObjectAck { .. } => unreachable!(), + }; + + *opts = options; + } + + pub fn parse + ?Sized>(packet: &Packet<&'p T>) -> Result { + packet.check_len()?; + + let options = packet.options()?; + match RplControlMessage::from(packet.msg_code()) { + RplControlMessage::DodagInformationSolicitation => { + Ok(Repr::DodagInformationSolicitation { options }) + } + RplControlMessage::DodagInformationObject => Ok(Repr::DodagInformationObject { + rpl_instance_id: packet.rpl_instance_id(), + version_number: packet.dio_version_number(), + rank: packet.dio_rank(), + grounded: packet.dio_grounded(), + mode_of_operation: packet.dio_mode_of_operation(), + dodag_preference: packet.dio_dodag_preference(), + dtsn: packet.dio_dest_adv_trigger_seq_number(), + dodag_id: packet.dio_dodag_id(), + options, + }), + RplControlMessage::DestinationAdvertisementObject => { + Ok(Repr::DestinationAdvertisementObject { + rpl_instance_id: packet.rpl_instance_id(), + expect_ack: packet.dao_ack_request(), + sequence: packet.dao_dodag_sequence(), + dodag_id: packet.dao_dodag_id(), + options, + }) + } + RplControlMessage::DestinationAdvertisementObjectAck => { + Ok(Repr::DestinationAdvertisementObjectAck { + rpl_instance_id: packet.rpl_instance_id(), + sequence: packet.dao_ack_sequence(), + status: packet.dao_ack_status(), + dodag_id: packet.dao_ack_dodag_id(), + }) + } + RplControlMessage::SecureDodagInformationSolicitation + | RplControlMessage::SecureDodagInformationObject + | RplControlMessage::SecureDestinationAdvertisementObject + | RplControlMessage::SecureDestinationAdvertisementObjectAck + | RplControlMessage::ConsistencyCheck => Err(Error), + RplControlMessage::Unknown(_) => Err(Error), + } + } + + pub fn buffer_len(&self) -> usize { + let mut len = 4 + match self { + Repr::DodagInformationSolicitation { .. } => 2, + Repr::DodagInformationObject { .. } => 24, + Repr::DestinationAdvertisementObject { dodag_id, .. } => { + if dodag_id.is_some() { + 20 + } else { + 4 + } + } + Repr::DestinationAdvertisementObjectAck { dodag_id, .. } => { + if dodag_id.is_some() { + 20 + } else { + 4 + } + } + }; + + let opts = match self { + Repr::DodagInformationSolicitation { options } => &options[..], + Repr::DodagInformationObject { options, .. } => &options[..], + Repr::DestinationAdvertisementObject { options, .. } => &options[..], + Repr::DestinationAdvertisementObjectAck { .. } => &[], + }; + + len += opts.len(); + + len + } + + pub fn emit + AsMut<[u8]> + ?Sized>(&self, packet: &mut Packet<&mut T>) { + packet.set_msg_type(crate::wire::icmpv6::Message::RplControl); + + match self { + Repr::DodagInformationSolicitation { .. } => { + packet.set_msg_code(RplControlMessage::DodagInformationSolicitation.into()); + packet.clear_dis_flags(); + packet.clear_dis_reserved(); + } + Repr::DodagInformationObject { + rpl_instance_id, + version_number, + rank, + grounded, + mode_of_operation, + dodag_preference, + dtsn, + dodag_id, + .. + } => { + packet.set_msg_code(RplControlMessage::DodagInformationObject.into()); + packet.set_rpl_instance_id((*rpl_instance_id).into()); + packet.set_dio_version_number(*version_number); + packet.set_dio_rank(*rank); + packet.set_dio_grounded(*grounded); + packet.set_dio_mode_of_operation(*mode_of_operation); + packet.set_dio_dodag_preference(*dodag_preference); + packet.set_dio_dest_adv_trigger_seq_number(*dtsn); + packet.set_dio_dodag_id(*dodag_id); + } + Repr::DestinationAdvertisementObject { + rpl_instance_id, + expect_ack, + sequence, + dodag_id, + .. + } => { + packet.set_msg_code(RplControlMessage::DestinationAdvertisementObject.into()); + packet.set_rpl_instance_id((*rpl_instance_id).into()); + packet.set_dao_ack_request(*expect_ack); + packet.set_dao_dodag_sequence(*sequence); + packet.set_dao_dodag_id(*dodag_id); + } + Repr::DestinationAdvertisementObjectAck { + rpl_instance_id, + sequence, + status, + dodag_id, + .. + } => { + packet.set_msg_code(RplControlMessage::DestinationAdvertisementObjectAck.into()); + packet.set_rpl_instance_id((*rpl_instance_id).into()); + packet.set_dao_ack_sequence(*sequence); + packet.set_dao_ack_status(*status); + packet.set_dao_ack_dodag_id(*dodag_id); + } + } + + let options = match self { + Repr::DodagInformationSolicitation { options } => &options[..], + Repr::DodagInformationObject { options, .. } => &options[..], + Repr::DestinationAdvertisementObject { options, .. } => &options[..], + Repr::DestinationAdvertisementObjectAck { .. } => &[], + }; + + packet.options_mut().copy_from_slice(options); + } +} + +pub mod options { + use byteorder::{ByteOrder, NetworkEndian}; + + use super::{Error, InstanceId, Result}; + use crate::wire::ipv6::{Address, AddressExt}; + + /// A read/write wrapper around a RPL Control Message Option. + #[derive(Debug, Clone)] + pub struct Packet> { + buffer: T, + } + + enum_with_unknown! { + pub enum OptionType(u8) { + Pad1 = 0x00, + PadN = 0x01, + DagMetricContainer = 0x02, + RouteInformation = 0x03, + DodagConfiguration = 0x04, + RplTarget = 0x05, + TransitInformation = 0x06, + SolicitedInformation = 0x07, + PrefixInformation = 0x08, + RplTargetDescriptor = 0x09, + } + } + + impl From<&Repr<'_>> for OptionType { + fn from(repr: &Repr) -> Self { + match repr { + Repr::Pad1 => Self::Pad1, + Repr::PadN(_) => Self::PadN, + Repr::DagMetricContainer => Self::DagMetricContainer, + Repr::RouteInformation { .. } => Self::RouteInformation, + Repr::DodagConfiguration { .. } => Self::DodagConfiguration, + Repr::RplTarget { .. } => Self::RplTarget, + Repr::TransitInformation { .. } => Self::TransitInformation, + Repr::SolicitedInformation { .. } => Self::SolicitedInformation, + Repr::PrefixInformation { .. } => Self::PrefixInformation, + Repr::RplTargetDescriptor { .. } => Self::RplTargetDescriptor, + } + } + } + + mod field { + use crate::wire::field::*; + + // Generic fields. + pub const TYPE: usize = 0; + pub const LENGTH: usize = 1; + + pub const PADN: Rest = 2..; + + // Route Information fields. + pub const ROUTE_INFO_PREFIX_LENGTH: usize = 2; + pub const ROUTE_INFO_RESERVED: usize = 3; + pub const ROUTE_INFO_PREFERENCE: usize = 3; + pub const ROUTE_INFO_LIFETIME: Field = 4..9; + + // DODAG Configuration fields. + pub const DODAG_CONF_FLAGS: usize = 2; + pub const DODAG_CONF_AUTHENTICATION_ENABLED: usize = 2; + pub const DODAG_CONF_PATH_CONTROL_SIZE: usize = 2; + pub const DODAG_CONF_DIO_INTERVAL_DOUBLINGS: usize = 3; + pub const DODAG_CONF_DIO_INTERVAL_MINIMUM: usize = 4; + pub const DODAG_CONF_DIO_REDUNDANCY_CONSTANT: usize = 5; + pub const DODAG_CONF_DIO_MAX_RANK_INCREASE: Field = 6..8; + pub const DODAG_CONF_MIN_HOP_RANK_INCREASE: Field = 8..10; + pub const DODAG_CONF_OBJECTIVE_CODE_POINT: Field = 10..12; + pub const DODAG_CONF_DEFAULT_LIFETIME: usize = 13; + pub const DODAG_CONF_LIFETIME_UNIT: Field = 14..16; + + // RPL Target fields. + pub const RPL_TARGET_FLAGS: usize = 2; + pub const RPL_TARGET_PREFIX_LENGTH: usize = 3; + + // Transit Information fields. + pub const TRANSIT_INFO_FLAGS: usize = 2; + pub const TRANSIT_INFO_EXTERNAL: usize = 2; + pub const TRANSIT_INFO_PATH_CONTROL: usize = 3; + pub const TRANSIT_INFO_PATH_SEQUENCE: usize = 4; + pub const TRANSIT_INFO_PATH_LIFETIME: usize = 5; + pub const TRANSIT_INFO_PARENT_ADDRESS: Field = 6..6 + 16; + + // Solicited Information fields. + pub const SOLICITED_INFO_RPL_INSTANCE_ID: usize = 2; + pub const SOLICITED_INFO_FLAGS: usize = 3; + pub const SOLICITED_INFO_VERSION_PREDICATE: usize = 3; + pub const SOLICITED_INFO_INSTANCE_ID_PREDICATE: usize = 3; + pub const SOLICITED_INFO_DODAG_ID_PREDICATE: usize = 3; + pub const SOLICITED_INFO_DODAG_ID: Field = 4..20; + pub const SOLICITED_INFO_VERSION_NUMBER: usize = 20; + + // Prefix Information fields. + pub const PREFIX_INFO_PREFIX_LENGTH: usize = 2; + pub const PREFIX_INFO_RESERVED1: usize = 3; + pub const PREFIX_INFO_ON_LINK: usize = 3; + pub const PREFIX_INFO_AUTONOMOUS_CONF: usize = 3; + pub const PREFIX_INFO_ROUTER_ADDRESS_FLAG: usize = 3; + pub const PREFIX_INFO_VALID_LIFETIME: Field = 4..8; + pub const PREFIX_INFO_PREFERRED_LIFETIME: Field = 8..12; + pub const PREFIX_INFO_RESERVED2: Field = 12..16; + pub const PREFIX_INFO_PREFIX: Field = 16..16 + 16; + + // RPL Target Descriptor fields. + pub const TARGET_DESCRIPTOR: Field = 2..6; + } + + /// Getters for the RPL Control Message Options. + impl> Packet { + /// Imbue a raw octet buffer with RPL Control Message Option structure. + #[inline] + pub fn new_unchecked(buffer: T) -> Self { + Packet { buffer } + } + + #[inline] + pub fn new_checked(buffer: T) -> Result { + if buffer.as_ref().is_empty() { + return Err(Error); + } + + Ok(Packet { buffer }) + } + + /// Return the type field. + #[inline] + pub fn option_type(&self) -> OptionType { + OptionType::from(self.buffer.as_ref()[field::TYPE]) + } + + /// Return the length field. + #[inline] + pub fn option_length(&self) -> u8 { + get!(self.buffer, field: field::LENGTH) + } + } + + impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> { + /// Return a pointer to the next option. + #[inline] + pub fn next_option(&self) -> Option<&'p [u8]> { + if !self.buffer.as_ref().is_empty() { + match self.option_type() { + OptionType::Pad1 => Some(&self.buffer.as_ref()[1..]), + OptionType::Unknown(_) => unreachable!(), + _ => { + let len = self.option_length(); + Some(&self.buffer.as_ref()[2 + len as usize..]) + } + } + } else { + None + } + } + } + + impl + AsMut<[u8]>> Packet { + /// Set the Option Type field. + #[inline] + pub fn set_option_type(&mut self, option_type: OptionType) { + self.buffer.as_mut()[field::TYPE] = option_type.into(); + } + + /// Set the Option Length field. + #[inline] + pub fn set_option_length(&mut self, length: u8) { + self.buffer.as_mut()[field::LENGTH] = length; + } + } + + impl + AsMut<[u8]>> Packet { + #[inline] + pub fn clear_padn(&mut self, size: u8) { + for b in &mut self.buffer.as_mut()[field::PADN][..size as usize] { + *b = 0; + } + } + } + + /// Getters for the DAG Metric Container Option Message. + + /// Getters for the Route Information Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x03 | Option Length | Prefix Length |Resvd|Prf|Resvd| + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Route Lifetime | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | | + /// . Prefix (Variable Length) . + /// . . + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the Prefix Length field. + #[inline] + pub fn prefix_length(&self) -> u8 { + get!(self.buffer, field: field::ROUTE_INFO_PREFIX_LENGTH) + } + + /// Return the Route Preference field. + #[inline] + pub fn route_preference(&self) -> u8 { + (self.buffer.as_ref()[field::ROUTE_INFO_PREFERENCE] & 0b0001_1000) >> 3 + } + + /// Return the Route Lifetime field. + #[inline] + pub fn route_lifetime(&self) -> u32 { + get!(self.buffer, u32, field: field::ROUTE_INFO_LIFETIME) + } + } + + impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> { + /// Return the Prefix field. + #[inline] + pub fn prefix(&self) -> &'p [u8] { + let option_len = self.option_length(); + &self.buffer.as_ref()[field::ROUTE_INFO_LIFETIME.end..] + [..option_len as usize - field::ROUTE_INFO_LIFETIME.end] + } + } + + /// Setters for the Route Information Option Message. + impl + AsMut<[u8]>> Packet { + /// Set the Prefix Length field. + #[inline] + pub fn set_route_info_prefix_length(&mut self, value: u8) { + set!(self.buffer, value, field: field::ROUTE_INFO_PREFIX_LENGTH) + } + + /// Set the Route Preference field. + #[inline] + pub fn set_route_info_route_preference(&mut self, _value: u8) { + todo!(); + } + + /// Set the Route Lifetime field. + #[inline] + pub fn set_route_info_route_lifetime(&mut self, value: u32) { + set!(self.buffer, value, u32, field: field::ROUTE_INFO_LIFETIME) + } + + /// Set the prefix field. + #[inline] + pub fn set_route_info_prefix(&mut self, _prefix: &[u8]) { + todo!(); + } + + /// Clear the reserved field. + #[inline] + pub fn clear_route_info_reserved(&mut self) { + self.buffer.as_mut()[field::ROUTE_INFO_RESERVED] = 0; + } + } + + /// Getters for the DODAG Configuration Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x04 |Opt Length = 14| Flags |A| PCS | DIOIntDoubl. | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | DIOIntMin. | DIORedun. | MaxRankIncrease | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | MinHopRankIncrease | OCP | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Reserved | Def. Lifetime | Lifetime Unit | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the Authentication Enabled field. + #[inline] + pub fn authentication_enabled(&self) -> bool { + get!( + self.buffer, + bool, + field: field::DODAG_CONF_AUTHENTICATION_ENABLED, + shift: 3, + mask: 0b1 + ) + } + + /// Return the Path Control Size field. + #[inline] + pub fn path_control_size(&self) -> u8 { + get!(self.buffer, field: field::DODAG_CONF_PATH_CONTROL_SIZE, mask: 0b111) + } + + /// Return the DIO Interval Doublings field. + #[inline] + pub fn dio_interval_doublings(&self) -> u8 { + get!(self.buffer, field: field::DODAG_CONF_DIO_INTERVAL_DOUBLINGS) + } + + /// Return the DIO Interval Minimum field. + #[inline] + pub fn dio_interval_minimum(&self) -> u8 { + get!(self.buffer, field: field::DODAG_CONF_DIO_INTERVAL_MINIMUM) + } + + /// Return the DIO Redundancy Constant field. + #[inline] + pub fn dio_redundancy_constant(&self) -> u8 { + get!( + self.buffer, + field: field::DODAG_CONF_DIO_REDUNDANCY_CONSTANT + ) + } + + /// Return the Max Rank Increase field. + #[inline] + pub fn max_rank_increase(&self) -> u16 { + get!( + self.buffer, + u16, + field: field::DODAG_CONF_DIO_MAX_RANK_INCREASE + ) + } + + /// Return the Minimum Hop Rank Increase field. + #[inline] + pub fn minimum_hop_rank_increase(&self) -> u16 { + get!( + self.buffer, + u16, + field: field::DODAG_CONF_MIN_HOP_RANK_INCREASE + ) + } + + /// Return the Objective Code Point field. + #[inline] + pub fn objective_code_point(&self) -> u16 { + get!( + self.buffer, + u16, + field: field::DODAG_CONF_OBJECTIVE_CODE_POINT + ) + } + + /// Return the Default Lifetime field. + #[inline] + pub fn default_lifetime(&self) -> u8 { + get!(self.buffer, field: field::DODAG_CONF_DEFAULT_LIFETIME) + } + + /// Return the Lifetime Unit field. + #[inline] + pub fn lifetime_unit(&self) -> u16 { + get!(self.buffer, u16, field: field::DODAG_CONF_LIFETIME_UNIT) + } + } + + /// Getters for the DODAG Configuration Option Message. + impl + AsMut<[u8]>> Packet { + /// Clear the Flags field. + #[inline] + pub fn clear_dodag_conf_flags(&mut self) { + self.buffer.as_mut()[field::DODAG_CONF_FLAGS] = 0; + } + + /// Set the Authentication Enabled field. + #[inline] + pub fn set_dodag_conf_authentication_enabled(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::DODAG_CONF_AUTHENTICATION_ENABLED, + shift: 3, + mask: 0b1 + ) + } + + /// Set the Path Control Size field. + #[inline] + pub fn set_dodag_conf_path_control_size(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::DODAG_CONF_PATH_CONTROL_SIZE, + mask: 0b111 + ) + } + + /// Set the DIO Interval Doublings field. + #[inline] + pub fn set_dodag_conf_dio_interval_doublings(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::DODAG_CONF_DIO_INTERVAL_DOUBLINGS + ) + } + + /// Set the DIO Interval Minimum field. + #[inline] + pub fn set_dodag_conf_dio_interval_minimum(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::DODAG_CONF_DIO_INTERVAL_MINIMUM + ) + } + + /// Set the DIO Redundancy Constant field. + #[inline] + pub fn set_dodag_conf_dio_redundancy_constant(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::DODAG_CONF_DIO_REDUNDANCY_CONSTANT + ) + } + + /// Set the Max Rank Increase field. + #[inline] + pub fn set_dodag_conf_max_rank_increase(&mut self, value: u16) { + set!( + self.buffer, + value, + u16, + field: field::DODAG_CONF_DIO_MAX_RANK_INCREASE + ) + } + + /// Set the Minimum Hop Rank Increase field. + #[inline] + pub fn set_dodag_conf_minimum_hop_rank_increase(&mut self, value: u16) { + set!( + self.buffer, + value, + u16, + field: field::DODAG_CONF_MIN_HOP_RANK_INCREASE + ) + } + + /// Set the Objective Code Point field. + #[inline] + pub fn set_dodag_conf_objective_code_point(&mut self, value: u16) { + set!( + self.buffer, + value, + u16, + field: field::DODAG_CONF_OBJECTIVE_CODE_POINT + ) + } + + /// Set the Default Lifetime field. + #[inline] + pub fn set_dodag_conf_default_lifetime(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::DODAG_CONF_DEFAULT_LIFETIME + ) + } + + /// Set the Lifetime Unit field. + #[inline] + pub fn set_dodag_conf_lifetime_unit(&mut self, value: u16) { + set!( + self.buffer, + value, + u16, + field: field::DODAG_CONF_LIFETIME_UNIT + ) + } + } + + /// Getters for the RPL Target Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x05 | Option Length | Flags | Prefix Length | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | | + /// + + + /// | Target Prefix (Variable Length) | + /// . . + /// . . + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the Target Prefix Length field. + pub fn target_prefix_length(&self) -> u8 { + get!(self.buffer, field: field::RPL_TARGET_PREFIX_LENGTH) + } + } + + impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> { + /// Return the Target Prefix field. + #[inline] + pub fn target_prefix(&self) -> &'p [u8] { + let option_len = self.option_length(); + &self.buffer.as_ref()[field::RPL_TARGET_PREFIX_LENGTH + 1..] + [..option_len as usize - field::RPL_TARGET_PREFIX_LENGTH + 1] + } + } + + /// Setters for the RPL Target Option Message. + impl + AsMut<[u8]>> Packet { + /// Clear the Flags field. + #[inline] + pub fn clear_rpl_target_flags(&mut self) { + self.buffer.as_mut()[field::RPL_TARGET_FLAGS] = 0; + } + + /// Set the Target Prefix Length field. + #[inline] + pub fn set_rpl_target_prefix_length(&mut self, value: u8) { + set!(self.buffer, value, field: field::RPL_TARGET_PREFIX_LENGTH) + } + + /// Set the Target Prefix field. + #[inline] + pub fn set_rpl_target_prefix(&mut self, prefix: &[u8]) { + self.buffer.as_mut()[field::RPL_TARGET_PREFIX_LENGTH + 1..][..prefix.len()] + .copy_from_slice(prefix); + } + } + + /// Getters for the Transit Information Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x06 | Option Length |E| Flags | Path Control | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Path Sequence | Path Lifetime | | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + /// | | + /// + + + /// | | + /// + Parent Address* + + /// | | + /// + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the External flag. + #[inline] + pub fn is_external(&self) -> bool { + get!( + self.buffer, + bool, + field: field::TRANSIT_INFO_EXTERNAL, + shift: 7, + mask: 0b1, + ) + } + + /// Return the Path Control field. + #[inline] + pub fn path_control(&self) -> u8 { + get!(self.buffer, field: field::TRANSIT_INFO_PATH_CONTROL) + } + + /// Return the Path Sequence field. + #[inline] + pub fn path_sequence(&self) -> u8 { + get!(self.buffer, field: field::TRANSIT_INFO_PATH_SEQUENCE) + } + + /// Return the Path Lifetime field. + #[inline] + pub fn path_lifetime(&self) -> u8 { + get!(self.buffer, field: field::TRANSIT_INFO_PATH_LIFETIME) + } + + /// Return the Parent Address field. + #[inline] + pub fn parent_address(&self) -> Option
{ + if self.option_length() > 5 { + Some(crate::wire::ipv6_from_octets( + self.buffer.as_ref()[field::TRANSIT_INFO_PARENT_ADDRESS] + .try_into() + .unwrap(), + )) + } else { + None + } + } + } + + /// Setters for the Transit Information Option Message. + impl + AsMut<[u8]>> Packet { + /// Clear the Flags field. + #[inline] + pub fn clear_transit_info_flags(&mut self) { + self.buffer.as_mut()[field::TRANSIT_INFO_FLAGS] = 0; + } + + /// Set the External flag. + #[inline] + pub fn set_transit_info_is_external(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::TRANSIT_INFO_EXTERNAL, + shift: 7, + mask: 0b1 + ) + } + + /// Set the Path Control field. + #[inline] + pub fn set_transit_info_path_control(&mut self, value: u8) { + set!(self.buffer, value, field: field::TRANSIT_INFO_PATH_CONTROL) + } + + /// Set the Path Sequence field. + #[inline] + pub fn set_transit_info_path_sequence(&mut self, value: u8) { + set!(self.buffer, value, field: field::TRANSIT_INFO_PATH_SEQUENCE) + } + + /// Set the Path Lifetime field. + #[inline] + pub fn set_transit_info_path_lifetime(&mut self, value: u8) { + set!(self.buffer, value, field: field::TRANSIT_INFO_PATH_LIFETIME) + } + + /// Set the Parent Address field. + #[inline] + pub fn set_transit_info_parent_address(&mut self, address: Address) { + self.buffer.as_mut()[field::TRANSIT_INFO_PARENT_ADDRESS] + .copy_from_slice(&address.octets()); + } + } + + /// Getters for the Solicited Information Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x07 |Opt Length = 19| RPLInstanceID |V|I|D| Flags | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | | + /// + + + /// | | + /// + DODAGID + + /// | | + /// + + + /// | | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// |Version Number | + /// +-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the RPL Instance ID field. + #[inline] + pub fn rpl_instance_id(&self) -> u8 { + get!(self.buffer, field: field::SOLICITED_INFO_RPL_INSTANCE_ID) + } + + /// Return the Version Predicate flag. + #[inline] + pub fn version_predicate(&self) -> bool { + get!( + self.buffer, + bool, + field: field::SOLICITED_INFO_VERSION_PREDICATE, + shift: 7, + mask: 0b1, + ) + } + + /// Return the Instance ID Predicate flag. + #[inline] + pub fn instance_id_predicate(&self) -> bool { + get!( + self.buffer, + bool, + field: field::SOLICITED_INFO_INSTANCE_ID_PREDICATE, + shift: 6, + mask: 0b1, + ) + } + + /// Return the DODAG Predicate ID flag. + #[inline] + pub fn dodag_id_predicate(&self) -> bool { + get!( + self.buffer, + bool, + field: field::SOLICITED_INFO_DODAG_ID_PREDICATE, + shift: 5, + mask: 0b1, + ) + } + + /// Return the DODAG ID field. + #[inline] + pub fn dodag_id(&self) -> Address { + crate::wire::ipv6_from_octets( + self.buffer.as_ref()[field::SOLICITED_INFO_DODAG_ID] + .try_into() + .unwrap(), + ) + } + + /// Return the Version Number field. + #[inline] + pub fn version_number(&self) -> u8 { + get!(self.buffer, field: field::SOLICITED_INFO_VERSION_NUMBER) + } + } + + /// Setters for the Solicited Information Option Message. + impl + AsMut<[u8]>> Packet { + /// Clear the Flags field. + #[inline] + pub fn clear_solicited_info_flags(&mut self) { + self.buffer.as_mut()[field::SOLICITED_INFO_FLAGS] = 0; + } + + /// Set the RPL Instance ID field. + #[inline] + pub fn set_solicited_info_rpl_instance_id(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::SOLICITED_INFO_RPL_INSTANCE_ID + ) + } + + /// Set the Version Predicate flag. + #[inline] + pub fn set_solicited_info_version_predicate(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::SOLICITED_INFO_VERSION_PREDICATE, + shift: 7, + mask: 0b1 + ) + } + + /// Set the Instance ID Predicate flag. + #[inline] + pub fn set_solicited_info_instance_id_predicate(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::SOLICITED_INFO_INSTANCE_ID_PREDICATE, + shift: 6, + mask: 0b1 + ) + } + + /// Set the DODAG Predicate ID flag. + #[inline] + pub fn set_solicited_info_dodag_id_predicate(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::SOLICITED_INFO_DODAG_ID_PREDICATE, + shift: 5, + mask: 0b1 + ) + } + + /// Set the DODAG ID field. + #[inline] + pub fn set_solicited_info_dodag_id(&mut self, address: Address) { + set!( + self.buffer, + address: address, + field: field::SOLICITED_INFO_DODAG_ID + ) + } + + /// Set the Version Number field. + #[inline] + pub fn set_solicited_info_version_number(&mut self, value: u8) { + set!( + self.buffer, + value, + field: field::SOLICITED_INFO_VERSION_NUMBER + ) + } + } + + /// Getters for the Prefix Information Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x08 |Opt Length = 30| Prefix Length |L|A|R|Reserved1| + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Valid Lifetime | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Preferred Lifetime | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Reserved2 | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | | + /// + + + /// | | + /// + Prefix + + /// | | + /// + + + /// | | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the Prefix Length field. + #[inline] + pub fn prefix_info_prefix_length(&self) -> u8 { + get!(self.buffer, field: field::PREFIX_INFO_PREFIX_LENGTH) + } + + /// Return the On-Link flag. + #[inline] + pub fn on_link(&self) -> bool { + get!( + self.buffer, + bool, + field: field::PREFIX_INFO_ON_LINK, + shift: 7, + mask: 0b1, + ) + } + + /// Return the Autonomous Address-Configuration flag. + #[inline] + pub fn autonomous_address_configuration(&self) -> bool { + get!( + self.buffer, + bool, + field: field::PREFIX_INFO_AUTONOMOUS_CONF, + shift: 6, + mask: 0b1, + ) + } + + /// Return the Router Address flag. + #[inline] + pub fn router_address(&self) -> bool { + get!( + self.buffer, + bool, + field: field::PREFIX_INFO_ROUTER_ADDRESS_FLAG, + shift: 5, + mask: 0b1, + ) + } + + /// Return the Valid Lifetime field. + #[inline] + pub fn valid_lifetime(&self) -> u32 { + get!(self.buffer, u32, field: field::PREFIX_INFO_VALID_LIFETIME) + } + + /// Return the Preferred Lifetime field. + #[inline] + pub fn preferred_lifetime(&self) -> u32 { + get!( + self.buffer, + u32, + field: field::PREFIX_INFO_PREFERRED_LIFETIME + ) + } + } + + impl<'p, T: AsRef<[u8]> + ?Sized> Packet<&'p T> { + /// Return the Prefix field. + #[inline] + pub fn destination_prefix(&self) -> &'p [u8] { + &self.buffer.as_ref()[field::PREFIX_INFO_PREFIX] + } + } + + /// Setters for the Prefix Information Option Message. + impl + AsMut<[u8]>> Packet { + /// Clear the reserved fields. + #[inline] + pub fn clear_prefix_info_reserved(&mut self) { + self.buffer.as_mut()[field::PREFIX_INFO_RESERVED1] = 0; + self.buffer.as_mut()[field::PREFIX_INFO_RESERVED2].copy_from_slice(&[0; 4]); + } + + /// Set the Prefix Length field. + #[inline] + pub fn set_prefix_info_prefix_length(&mut self, value: u8) { + set!(self.buffer, value, field: field::PREFIX_INFO_PREFIX_LENGTH) + } + + /// Set the On-Link flag. + #[inline] + pub fn set_prefix_info_on_link(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::PREFIX_INFO_ON_LINK, shift: 7, mask: 0b1) + } + + /// Set the Autonomous Address-Configuration flag. + #[inline] + pub fn set_prefix_info_autonomous_address_configuration(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::PREFIX_INFO_AUTONOMOUS_CONF, + shift: 6, + mask: 0b1 + ) + } + + /// Set the Router Address flag. + #[inline] + pub fn set_prefix_info_router_address(&mut self, value: bool) { + set!( + self.buffer, + value, + bool, + field: field::PREFIX_INFO_ROUTER_ADDRESS_FLAG, + shift: 5, + mask: 0b1 + ) + } + + /// Set the Valid Lifetime field. + #[inline] + pub fn set_prefix_info_valid_lifetime(&mut self, value: u32) { + set!( + self.buffer, + value, + u32, + field: field::PREFIX_INFO_VALID_LIFETIME + ) + } + + /// Set the Preferred Lifetime field. + #[inline] + pub fn set_prefix_info_preferred_lifetime(&mut self, value: u32) { + set!( + self.buffer, + value, + u32, + field: field::PREFIX_INFO_PREFERRED_LIFETIME + ) + } + + /// Set the Prefix field. + #[inline] + pub fn set_prefix_info_destination_prefix(&mut self, prefix: &[u8]) { + self.buffer.as_mut()[field::PREFIX_INFO_PREFIX].copy_from_slice(prefix); + } + } + + /// Getters for the RPL Target Descriptor Option Message. + /// + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Type = 0x09 |Opt Length = 4 | Descriptor + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// Descriptor (cont.) | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + impl> Packet { + /// Return the Descriptor field. + #[inline] + pub fn descriptor(&self) -> u32 { + get!(self.buffer, u32, field: field::TARGET_DESCRIPTOR) + } + } + + /// Setters for the RPL Target Descriptor Option Message. + impl + AsMut<[u8]>> Packet { + /// Set the Descriptor field. + #[inline] + pub fn set_rpl_target_descriptor_descriptor(&mut self, value: u32) { + set!(self.buffer, value, u32, field: field::TARGET_DESCRIPTOR) + } + } + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum Repr<'p> { + Pad1, + PadN(u8), + DagMetricContainer, + RouteInformation { + prefix_length: u8, + preference: u8, + lifetime: u32, + prefix: &'p [u8], + }, + DodagConfiguration { + authentication_enabled: bool, + path_control_size: u8, + dio_interval_doublings: u8, + dio_interval_min: u8, + dio_redundancy_constant: u8, + max_rank_increase: u16, + minimum_hop_rank_increase: u16, + objective_code_point: u16, + default_lifetime: u8, + lifetime_unit: u16, + }, + RplTarget { + prefix_length: u8, + prefix: crate::wire::Ipv6Address, // FIXME: this is not the correct type, because the + // field can be an IPv6 address, a prefix or a + // multicast group. + }, + TransitInformation { + external: bool, + path_control: u8, + path_sequence: u8, + path_lifetime: u8, + parent_address: Option
, + }, + SolicitedInformation { + rpl_instance_id: InstanceId, + version_predicate: bool, + instance_id_predicate: bool, + dodag_id_predicate: bool, + dodag_id: Address, + version_number: u8, + }, + PrefixInformation { + prefix_length: u8, + on_link: bool, + autonomous_address_configuration: bool, + router_address: bool, + valid_lifetime: u32, + preferred_lifetime: u32, + destination_prefix: &'p [u8], + }, + RplTargetDescriptor { + descriptor: u32, + }, + } + + impl core::fmt::Display for Repr<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Repr::Pad1 => write!(f, "Pad1"), + Repr::PadN(n) => write!(f, "PadN({n})"), + Repr::DagMetricContainer => todo!(), + Repr::RouteInformation { + prefix_length, + preference, + lifetime, + prefix, + } => { + write!( + f, + "ROUTE INFO \ + PrefixLength={prefix_length} \ + Preference={preference} \ + Lifetime={lifetime} \ + Prefix={prefix:0x?}" + ) + } + Repr::DodagConfiguration { + dio_interval_doublings, + dio_interval_min, + dio_redundancy_constant, + max_rank_increase, + minimum_hop_rank_increase, + objective_code_point, + default_lifetime, + lifetime_unit, + .. + } => { + write!( + f, + "DODAG CONF \ + IntD={dio_interval_doublings} \ + IntMin={dio_interval_min} \ + RedCst={dio_redundancy_constant} \ + MaxRankIncr={max_rank_increase} \ + MinHopRankIncr={minimum_hop_rank_increase} \ + OCP={objective_code_point} \ + DefaultLifetime={default_lifetime} \ + LifeUnit={lifetime_unit}" + ) + } + Repr::RplTarget { + prefix_length, + prefix, + } => { + write!( + f, + "RPL Target \ + PrefixLength={prefix_length} \ + Prefix={prefix:0x?}" + ) + } + Repr::TransitInformation { + external, + path_control, + path_sequence, + path_lifetime, + parent_address, + } => { + write!( + f, + "Transit Info \ + External={external} \ + PathCtrl={path_control} \ + PathSqnc={path_sequence} \ + PathLifetime={path_lifetime} \ + Parent={parent_address:0x?}" + ) + } + Repr::SolicitedInformation { + rpl_instance_id, + version_predicate, + instance_id_predicate, + dodag_id_predicate, + dodag_id, + version_number, + } => { + write!( + f, + "Solicited Info \ + I={instance_id_predicate} \ + IID={rpl_instance_id:0x?} \ + D={dodag_id_predicate} \ + DODAGID={dodag_id} \ + V={version_predicate} \ + Version={version_number}" + ) + } + Repr::PrefixInformation { + prefix_length, + on_link, + autonomous_address_configuration, + router_address, + valid_lifetime, + preferred_lifetime, + destination_prefix, + } => { + write!( + f, + "Prefix Info \ + PrefixLength={prefix_length} \ + L={on_link} A={autonomous_address_configuration} R={router_address} \ + Valid={valid_lifetime} \ + Preferred={preferred_lifetime} \ + Prefix={destination_prefix:0x?}" + ) + } + Repr::RplTargetDescriptor { .. } => write!(f, "Target Descriptor"), + } + } + } + + impl<'p> Repr<'p> { + pub fn parse + ?Sized>(packet: &Packet<&'p T>) -> Result { + match packet.option_type() { + OptionType::Pad1 => Ok(Repr::Pad1), + OptionType::PadN => Ok(Repr::PadN(packet.option_length())), + OptionType::DagMetricContainer => todo!(), + OptionType::RouteInformation => Ok(Repr::RouteInformation { + prefix_length: packet.prefix_length(), + preference: packet.route_preference(), + lifetime: packet.route_lifetime(), + prefix: packet.prefix(), + }), + OptionType::DodagConfiguration => Ok(Repr::DodagConfiguration { + authentication_enabled: packet.authentication_enabled(), + path_control_size: packet.path_control_size(), + dio_interval_doublings: packet.dio_interval_doublings(), + dio_interval_min: packet.dio_interval_minimum(), + dio_redundancy_constant: packet.dio_redundancy_constant(), + max_rank_increase: packet.max_rank_increase(), + minimum_hop_rank_increase: packet.minimum_hop_rank_increase(), + objective_code_point: packet.objective_code_point(), + default_lifetime: packet.default_lifetime(), + lifetime_unit: packet.lifetime_unit(), + }), + OptionType::RplTarget => Ok(Repr::RplTarget { + prefix_length: packet.target_prefix_length(), + prefix: crate::wire::ipv6_from_octets( + packet.target_prefix().try_into().unwrap(), + ), + }), + OptionType::TransitInformation => Ok(Repr::TransitInformation { + external: packet.is_external(), + path_control: packet.path_control(), + path_sequence: packet.path_sequence(), + path_lifetime: packet.path_lifetime(), + parent_address: packet.parent_address(), + }), + OptionType::SolicitedInformation => Ok(Repr::SolicitedInformation { + rpl_instance_id: InstanceId::from(packet.rpl_instance_id()), + version_predicate: packet.version_predicate(), + instance_id_predicate: packet.instance_id_predicate(), + dodag_id_predicate: packet.dodag_id_predicate(), + dodag_id: packet.dodag_id(), + version_number: packet.version_number(), + }), + OptionType::PrefixInformation => Ok(Repr::PrefixInformation { + prefix_length: packet.prefix_info_prefix_length(), + on_link: packet.on_link(), + autonomous_address_configuration: packet.autonomous_address_configuration(), + router_address: packet.router_address(), + valid_lifetime: packet.valid_lifetime(), + preferred_lifetime: packet.preferred_lifetime(), + destination_prefix: packet.destination_prefix(), + }), + OptionType::RplTargetDescriptor => Ok(Repr::RplTargetDescriptor { + descriptor: packet.descriptor(), + }), + OptionType::Unknown(_) => Err(Error), + } + } + + pub fn buffer_len(&self) -> usize { + match self { + Repr::Pad1 => 1, + Repr::PadN(size) => 2 + *size as usize, + Repr::DagMetricContainer => todo!(), + Repr::RouteInformation { prefix, .. } => 2 + 6 + prefix.len(), + Repr::DodagConfiguration { .. } => 2 + 14, + Repr::RplTarget { prefix, .. } => 2 + 2 + prefix.octets().len(), + Repr::TransitInformation { parent_address, .. } => { + 2 + 4 + if parent_address.is_some() { 16 } else { 0 } + } + Repr::SolicitedInformation { .. } => 2 + 2 + 16 + 1, + Repr::PrefixInformation { .. } => 32, + Repr::RplTargetDescriptor { .. } => 2 + 4, + } + } + + pub fn emit + AsMut<[u8]> + ?Sized>(&self, packet: &mut Packet<&'p mut T>) { + let mut option_length = self.buffer_len() as u8; + + packet.set_option_type(self.into()); + + if !matches!(self, Repr::Pad1) { + option_length -= 2; + packet.set_option_length(option_length); + } + + match self { + Repr::Pad1 => {} + Repr::PadN(size) => { + packet.clear_padn(*size); + } + Repr::DagMetricContainer => { + unimplemented!(); + } + Repr::RouteInformation { + prefix_length, + preference, + lifetime, + prefix, + } => { + packet.clear_route_info_reserved(); + packet.set_route_info_prefix_length(*prefix_length); + packet.set_route_info_route_preference(*preference); + packet.set_route_info_route_lifetime(*lifetime); + packet.set_route_info_prefix(prefix); + } + Repr::DodagConfiguration { + authentication_enabled, + path_control_size, + dio_interval_doublings, + dio_interval_min, + dio_redundancy_constant, + max_rank_increase, + minimum_hop_rank_increase, + objective_code_point, + default_lifetime, + lifetime_unit, + } => { + packet.clear_dodag_conf_flags(); + packet.set_dodag_conf_authentication_enabled(*authentication_enabled); + packet.set_dodag_conf_path_control_size(*path_control_size); + packet.set_dodag_conf_dio_interval_doublings(*dio_interval_doublings); + packet.set_dodag_conf_dio_interval_minimum(*dio_interval_min); + packet.set_dodag_conf_dio_redundancy_constant(*dio_redundancy_constant); + packet.set_dodag_conf_max_rank_increase(*max_rank_increase); + packet.set_dodag_conf_minimum_hop_rank_increase(*minimum_hop_rank_increase); + packet.set_dodag_conf_objective_code_point(*objective_code_point); + packet.set_dodag_conf_default_lifetime(*default_lifetime); + packet.set_dodag_conf_lifetime_unit(*lifetime_unit); + } + Repr::RplTarget { + prefix_length, + prefix, + } => { + packet.clear_rpl_target_flags(); + packet.set_rpl_target_prefix_length(*prefix_length); + packet.set_rpl_target_prefix(&prefix.octets()); + } + Repr::TransitInformation { + external, + path_control, + path_sequence, + path_lifetime, + parent_address, + } => { + packet.clear_transit_info_flags(); + packet.set_transit_info_is_external(*external); + packet.set_transit_info_path_control(*path_control); + packet.set_transit_info_path_sequence(*path_sequence); + packet.set_transit_info_path_lifetime(*path_lifetime); + + if let Some(address) = parent_address { + packet.set_transit_info_parent_address(*address); + } + } + Repr::SolicitedInformation { + rpl_instance_id, + version_predicate, + instance_id_predicate, + dodag_id_predicate, + dodag_id, + version_number, + } => { + packet.clear_solicited_info_flags(); + packet.set_solicited_info_rpl_instance_id((*rpl_instance_id).into()); + packet.set_solicited_info_version_predicate(*version_predicate); + packet.set_solicited_info_instance_id_predicate(*instance_id_predicate); + packet.set_solicited_info_dodag_id_predicate(*dodag_id_predicate); + packet.set_solicited_info_version_number(*version_number); + packet.set_solicited_info_dodag_id(*dodag_id); + } + Repr::PrefixInformation { + prefix_length, + on_link, + autonomous_address_configuration, + router_address, + valid_lifetime, + preferred_lifetime, + destination_prefix, + } => { + packet.clear_prefix_info_reserved(); + packet.set_prefix_info_prefix_length(*prefix_length); + packet.set_prefix_info_on_link(*on_link); + packet.set_prefix_info_autonomous_address_configuration( + *autonomous_address_configuration, + ); + packet.set_prefix_info_router_address(*router_address); + packet.set_prefix_info_valid_lifetime(*valid_lifetime); + packet.set_prefix_info_preferred_lifetime(*preferred_lifetime); + packet.set_prefix_info_destination_prefix(destination_prefix); + } + Repr::RplTargetDescriptor { descriptor } => { + packet.set_rpl_target_descriptor_descriptor(*descriptor); + } + } + } + } +} + +pub mod data { + use super::{InstanceId, Result}; + use byteorder::{ByteOrder, NetworkEndian}; + + mod field { + use crate::wire::field::*; + + pub const FLAGS: usize = 0; + pub const INSTANCE_ID: usize = 1; + pub const SENDER_RANK: Field = 2..4; + } + + /// A read/write wrapper around a RPL Packet Information send with + /// an IPv6 Hop-by-Hop option, defined in RFC6553. + /// ```txt + /// 0 1 2 3 + /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | Option Type | Opt Data Len | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// |O|R|F|0|0|0|0|0| RPLInstanceID | SenderRank | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// | (sub-TLVs) | + /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /// ``` + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + pub struct Packet> { + buffer: T, + } + + impl> Packet { + #[inline] + pub fn new_unchecked(buffer: T) -> Self { + Self { buffer } + } + + #[inline] + pub fn new_checked(buffer: T) -> Result { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + #[inline] + pub fn check_len(&self) -> Result<()> { + if self.buffer.as_ref().len() == 4 { + Ok(()) + } else { + Err(crate::wire::Error) + } + } + + #[inline] + pub fn is_down(&self) -> bool { + get!(self.buffer, bool, field: field::FLAGS, shift: 7, mask: 0b1) + } + + #[inline] + pub fn has_rank_error(&self) -> bool { + get!(self.buffer, bool, field: field::FLAGS, shift: 6, mask: 0b1) + } + + #[inline] + pub fn has_forwarding_error(&self) -> bool { + get!(self.buffer, bool, field: field::FLAGS, shift: 5, mask: 0b1) + } + + #[inline] + pub fn rpl_instance_id(&self) -> InstanceId { + get!(self.buffer, into: InstanceId, field: field::INSTANCE_ID) + } + + #[inline] + pub fn sender_rank(&self) -> u16 { + get!(self.buffer, u16, field: field::SENDER_RANK) + } + } + + impl + AsMut<[u8]>> Packet { + #[inline] + pub fn set_is_down(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::FLAGS, shift: 7, mask: 0b1) + } + + #[inline] + pub fn set_has_rank_error(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::FLAGS, shift: 6, mask: 0b1) + } + + #[inline] + pub fn set_has_forwarding_error(&mut self, value: bool) { + set!(self.buffer, value, bool, field: field::FLAGS, shift: 5, mask: 0b1) + } + + #[inline] + pub fn set_rpl_instance_id(&mut self, value: u8) { + set!(self.buffer, value, field: field::INSTANCE_ID) + } + + #[inline] + pub fn set_sender_rank(&mut self, value: u16) { + set!(self.buffer, value, u16, field: field::SENDER_RANK) + } + } + + /// A high-level representation of an IPv6 Extension Header Option. + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct HopByHopOption { + pub down: bool, + pub rank_error: bool, + pub forwarding_error: bool, + pub instance_id: InstanceId, + pub sender_rank: u16, + } + + impl HopByHopOption { + /// Parse an IPv6 Extension Header Option and return a high-level representation. + pub fn parse(opt: &Packet<&T>) -> Self + where + T: AsRef<[u8]> + ?Sized, + { + Self { + down: opt.is_down(), + rank_error: opt.has_rank_error(), + forwarding_error: opt.has_forwarding_error(), + instance_id: opt.rpl_instance_id(), + sender_rank: opt.sender_rank(), + } + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + 4 + } + + /// Emit a high-level representation into an IPv6 Extension Header Option. + pub fn emit + AsMut<[u8]> + ?Sized>(&self, opt: &mut Packet<&mut T>) { + opt.set_is_down(self.down); + opt.set_has_rank_error(self.rank_error); + opt.set_has_forwarding_error(self.forwarding_error); + opt.set_rpl_instance_id(self.instance_id.into()); + opt.set_sender_rank(self.sender_rank); + } + } + + impl core::fmt::Display for HopByHopOption { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "down={} rank_error={} forw_error={} IID={:?} sender_rank={}", + self.down, + self.rank_error, + self.forwarding_error, + self.instance_id, + self.sender_rank + ) + } + } +} + +#[cfg(test)] +mod tests { + use super::Repr as RplRepr; + use super::options::{Packet as OptionPacket, Repr as OptionRepr}; + use super::*; + use crate::phy::ChecksumCapabilities; + use crate::wire::{icmpv6::*, *}; + + #[test] + fn dis_packet() { + let data = [0x7a, 0x3b, 0x3a, 0x1a, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00]; + + let ll_src_address = + Ieee802154Address::Extended([0x9e, 0xd3, 0xa2, 0x9c, 0x57, 0x1a, 0x4f, 0xe4]); + let ll_dst_address = Ieee802154Address::Short([0xff, 0xff]); + + let packet = SixlowpanIphcPacket::new_checked(&data).unwrap(); + let repr = + SixlowpanIphcRepr::parse(&packet, Some(ll_src_address), Some(ll_dst_address), &[]) + .unwrap(); + + let icmp_repr = match repr.next_header { + SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6) => { + let icmp_packet = Icmpv6Packet::new_checked(packet.payload()).unwrap(); + match Icmpv6Repr::parse( + &repr.src_addr, + &repr.dst_addr, + &icmp_packet, + &ChecksumCapabilities::ignored(), + ) { + Ok(icmp @ Icmpv6Repr::Rpl(RplRepr::DodagInformationSolicitation { .. })) => { + icmp + } + _ => unreachable!(), + } + } + _ => unreachable!(), + }; + + // We also try to emit the packet: + let mut buffer = vec![0u8; repr.buffer_len() + icmp_repr.buffer_len()]; + repr.emit(&mut SixlowpanIphcPacket::new_unchecked( + &mut buffer[..repr.buffer_len()], + )); + icmp_repr.emit( + &repr.src_addr.into(), + &repr.dst_addr.into(), + &mut Icmpv6Packet::new_unchecked( + &mut buffer[repr.buffer_len()..][..icmp_repr.buffer_len()], + ), + &ChecksumCapabilities::ignored(), + ); + + assert_eq!(&data[..], &buffer[..]); + } + + /// Parsing of DIO packets. + #[test] + fn dio_packet() { + let data = [ + 0x9b, 0x01, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x80, 0x08, 0xf0, 0x00, 0x00, 0xfd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x04, 0x0e, 0x00, 0x08, 0x0c, 0x00, 0x04, 0x00, 0x00, 0x80, 0x00, 0x01, 0x00, 0x1e, + 0x00, 0x3c, 0x08, 0x1e, 0x40, 0x40, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let addr = crate::wire::ipv6_from_octets([ + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, + ]); + + let dest_prefix = [ + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + + let packet = Packet::new_checked(&data[..]).unwrap(); + assert_eq!(packet.msg_type(), Message::RplControl); + assert_eq!( + RplControlMessage::from(packet.msg_code()), + RplControlMessage::DodagInformationObject + ); + + let mut dio_repr = RplRepr::parse(&packet).unwrap(); + match dio_repr { + RplRepr::DodagInformationObject { + rpl_instance_id, + version_number, + rank, + grounded, + mode_of_operation, + dodag_preference, + dtsn, + dodag_id, + .. + } => { + assert_eq!(rpl_instance_id, InstanceId::from(0)); + assert_eq!(version_number, 240); + assert_eq!(rank, 128); + assert!(!grounded); + assert_eq!(mode_of_operation, ModeOfOperation::NonStoringMode); + assert_eq!(dodag_preference, 0); + assert_eq!(dtsn, 240); + assert_eq!(dodag_id, addr); + } + _ => unreachable!(), + } + + let option = OptionPacket::new_unchecked(packet.options().unwrap()); + let dodag_conf_option = OptionRepr::parse(&option).unwrap(); + match dodag_conf_option { + OptionRepr::DodagConfiguration { + authentication_enabled, + path_control_size, + dio_interval_doublings, + dio_interval_min, + dio_redundancy_constant, + max_rank_increase, + minimum_hop_rank_increase, + objective_code_point, + default_lifetime, + lifetime_unit, + } => { + assert!(!authentication_enabled); + assert_eq!(path_control_size, 0); + assert_eq!(dio_interval_doublings, 8); + assert_eq!(dio_interval_min, 12); + assert_eq!(dio_redundancy_constant, 0); + assert_eq!(max_rank_increase, 1024); + assert_eq!(minimum_hop_rank_increase, 128); + assert_eq!(objective_code_point, 1); + assert_eq!(default_lifetime, 30); + assert_eq!(lifetime_unit, 60); + } + _ => unreachable!(), + } + + let option = OptionPacket::new_unchecked(option.next_option().unwrap()); + let prefix_info_option = OptionRepr::parse(&option).unwrap(); + match prefix_info_option { + OptionRepr::PrefixInformation { + prefix_length, + on_link, + autonomous_address_configuration, + valid_lifetime, + preferred_lifetime, + destination_prefix, + .. + } => { + assert_eq!(prefix_length, 64); + assert!(!on_link); + assert!(autonomous_address_configuration); + assert_eq!(valid_lifetime, u32::MAX); + assert_eq!(preferred_lifetime, u32::MAX); + assert_eq!(destination_prefix, &dest_prefix[..]); + } + _ => unreachable!(), + } + + let mut options_buffer = + vec![0u8; dodag_conf_option.buffer_len() + prefix_info_option.buffer_len()]; + + dodag_conf_option.emit(&mut OptionPacket::new_unchecked( + &mut options_buffer[..dodag_conf_option.buffer_len()], + )); + prefix_info_option.emit(&mut OptionPacket::new_unchecked( + &mut options_buffer[dodag_conf_option.buffer_len()..] + [..prefix_info_option.buffer_len()], + )); + + dio_repr.set_options(&options_buffer[..]); + + let mut buffer = vec![0u8; dio_repr.buffer_len()]; + dio_repr.emit(&mut Packet::new_unchecked(&mut buffer[..])); + + assert_eq!(&data[..], &buffer[..]); + } + + /// Parsing of DAO packets. + #[test] + fn dao_packet() { + let data = [ + 0x9b, 0x02, 0x00, 0x00, 0x00, 0x80, 0x00, 0xf1, 0x05, 0x12, 0x00, 0x80, 0xfd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, + 0x06, 0x14, 0x00, 0x00, 0x00, 0x1e, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + ]; + + let target_prefix = [ + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x02, + ]; + + let parent_addr = crate::wire::ipv6_from_octets([ + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, + ]); + + let packet = Packet::new_checked(&data[..]).unwrap(); + let mut dao_repr = RplRepr::parse(&packet).unwrap(); + match dao_repr { + RplRepr::DestinationAdvertisementObject { + rpl_instance_id, + expect_ack, + sequence, + dodag_id, + .. + } => { + assert_eq!(rpl_instance_id, InstanceId::from(0)); + assert!(expect_ack); + assert_eq!(sequence, 241); + assert_eq!(dodag_id, None); + } + _ => unreachable!(), + } + + let option = OptionPacket::new_unchecked(packet.options().unwrap()); + + let rpl_target_option = OptionRepr::parse(&option).unwrap(); + match rpl_target_option { + OptionRepr::RplTarget { + prefix_length, + prefix, + } => { + assert_eq!(prefix_length, 128); + assert_eq!(prefix.octets(), target_prefix); + } + _ => unreachable!(), + } + + let option = OptionPacket::new_unchecked(option.next_option().unwrap()); + let transit_info_option = OptionRepr::parse(&option).unwrap(); + match transit_info_option { + OptionRepr::TransitInformation { + external, + path_control, + path_sequence, + path_lifetime, + parent_address, + } => { + assert!(!external); + assert_eq!(path_control, 0); + assert_eq!(path_sequence, 0); + assert_eq!(path_lifetime, 30); + assert_eq!(parent_address, Some(parent_addr)); + } + _ => unreachable!(), + } + + let mut options_buffer = + vec![0u8; rpl_target_option.buffer_len() + transit_info_option.buffer_len()]; + + rpl_target_option.emit(&mut OptionPacket::new_unchecked( + &mut options_buffer[..rpl_target_option.buffer_len()], + )); + transit_info_option.emit(&mut OptionPacket::new_unchecked( + &mut options_buffer[rpl_target_option.buffer_len()..] + [..transit_info_option.buffer_len()], + )); + + dao_repr.set_options(&options_buffer[..]); + + let mut buffer = vec![0u8; dao_repr.buffer_len()]; + dao_repr.emit(&mut Packet::new_unchecked(&mut buffer[..])); + + assert_eq!(&data[..], &buffer[..]); + } + + /// Parsing of DAO-ACK packets. + #[test] + fn dao_ack_packet() { + let data = [0x9b, 0x03, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x00]; + + let packet = Packet::new_checked(&data[..]).unwrap(); + let dao_ack_repr = RplRepr::parse(&packet).unwrap(); + match dao_ack_repr { + RplRepr::DestinationAdvertisementObjectAck { + rpl_instance_id, + sequence, + status, + dodag_id, + .. + } => { + assert_eq!(rpl_instance_id, InstanceId::from(0)); + assert_eq!(sequence, 241); + assert_eq!(status, 0); + assert_eq!(dodag_id, None); + } + _ => unreachable!(), + } + + let mut buffer = vec![0u8; dao_ack_repr.buffer_len()]; + dao_ack_repr.emit(&mut Packet::new_unchecked(&mut buffer[..])); + + assert_eq!(&data[..], &buffer[..]); + + let data = [ + 0x9b, 0x03, 0x0, 0x0, 0x1e, 0x80, 0xf0, 0x00, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + + let packet = Packet::new_checked(&data[..]).unwrap(); + let dao_ack_repr = RplRepr::parse(&packet).unwrap(); + match dao_ack_repr { + RplRepr::DestinationAdvertisementObjectAck { + rpl_instance_id, + sequence, + status, + dodag_id, + .. + } => { + assert_eq!(rpl_instance_id, InstanceId::from(30)); + assert_eq!(sequence, 240); + assert_eq!(status, 0x0); + assert_eq!( + dodag_id, + Some(Ipv6Address::new(0xfe80, 0, 0, 0, 0x0200, 0, 0, 1)) + ); + } + _ => unreachable!(), + } + + let mut buffer = vec![0u8; dao_ack_repr.buffer_len()]; + dao_ack_repr.emit(&mut Packet::new_unchecked(&mut buffer[..])); + + assert_eq!(&data[..], &buffer[..]); + } +} diff --git a/vendor/smoltcp/src/wire/sixlowpan/frag.rs b/vendor/smoltcp/src/wire/sixlowpan/frag.rs new file mode 100644 index 00000000..eed7f051 --- /dev/null +++ b/vendor/smoltcp/src/wire/sixlowpan/frag.rs @@ -0,0 +1,276 @@ +//! Implementation of the fragment headers from [RFC 4944 § 5.3]. +//! +//! [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3 + +use super::{DISPATCH_FIRST_FRAGMENT_HEADER, DISPATCH_FRAGMENT_HEADER}; +use crate::wire::{Error, Result}; +use crate::wire::{Ieee802154Address, Ieee802154Repr}; +use byteorder::{ByteOrder, NetworkEndian}; + +/// Key used for identifying all the link fragments that belong to the same packet. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Key { + pub(crate) ll_src_addr: Ieee802154Address, + pub(crate) ll_dst_addr: Ieee802154Address, + pub(crate) datagram_size: u16, + pub(crate) datagram_tag: u16, +} + +/// A read/write wrapper around a 6LoWPAN Fragment header. +/// [RFC 4944 § 5.3] specifies the format of the header. +/// +/// A First Fragment header has the following format: +/// ```txt +/// 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// |1 1 0 0 0| datagram_size | datagram_tag | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +/// +/// Subsequent fragment headers have the following format: +/// ```txt +/// 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// |1 1 1 0 0| datagram_size | datagram_tag | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// |datagram_offset| +/// +-+-+-+-+-+-+-+-+ +/// ``` +/// +/// [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3 +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +pub const FIRST_FRAGMENT_HEADER_SIZE: usize = 4; +pub const NEXT_FRAGMENT_HEADER_SIZE: usize = 5; + +mod field { + use crate::wire::field::*; + + pub const DISPATCH: usize = 0; + pub const DATAGRAM_SIZE: Field = 0..2; + pub const DATAGRAM_TAG: Field = 2..4; + pub const DATAGRAM_OFFSET: usize = 4; + + pub const FIRST_FRAGMENT_REST: Rest = super::FIRST_FRAGMENT_HEADER_SIZE..; + pub const NEXT_FRAGMENT_REST: Rest = super::NEXT_FRAGMENT_HEADER_SIZE..; +} + +impl> Packet { + /// Input a raw octet buffer with a 6LoWPAN Fragment header structure. + pub const fn new_unchecked(buffer: T) -> Self { + Self { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + + let dispatch = packet.dispatch(); + + if dispatch != DISPATCH_FIRST_FRAGMENT_HEADER && dispatch != DISPATCH_FRAGMENT_HEADER { + return Err(Error); + } + + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let buffer = self.buffer.as_ref(); + if buffer.is_empty() { + return Err(Error); + } + + match self.dispatch() { + DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() >= FIRST_FRAGMENT_HEADER_SIZE => Ok(()), + DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() < FIRST_FRAGMENT_HEADER_SIZE => { + Err(Error) + } + DISPATCH_FRAGMENT_HEADER if buffer.len() >= NEXT_FRAGMENT_HEADER_SIZE => Ok(()), + DISPATCH_FRAGMENT_HEADER if buffer.len() < NEXT_FRAGMENT_HEADER_SIZE => Err(Error), + _ => Err(Error), + } + } + + /// Consumes the frame, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the dispatch field. + pub fn dispatch(&self) -> u8 { + let raw = self.buffer.as_ref(); + raw[field::DISPATCH] >> 3 + } + + /// Return the total datagram size. + pub fn datagram_size(&self) -> u16 { + let raw = self.buffer.as_ref(); + NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]) & 0b111_1111_1111 + } + + /// Return the datagram tag. + pub fn datagram_tag(&self) -> u16 { + let raw = self.buffer.as_ref(); + NetworkEndian::read_u16(&raw[field::DATAGRAM_TAG]) + } + + /// Return the datagram offset. + pub fn datagram_offset(&self) -> u8 { + match self.dispatch() { + DISPATCH_FIRST_FRAGMENT_HEADER => 0, + DISPATCH_FRAGMENT_HEADER => { + let raw = self.buffer.as_ref(); + raw[field::DATAGRAM_OFFSET] + } + _ => unreachable!(), + } + } + + /// Returns `true` when this header is from the first fragment of a link. + pub fn is_first_fragment(&self) -> bool { + self.dispatch() == DISPATCH_FIRST_FRAGMENT_HEADER + } + + /// Returns the key for identifying the packet it belongs to. + pub fn get_key(&self, ieee802154_repr: &Ieee802154Repr) -> Key { + Key { + ll_src_addr: ieee802154_repr.src_addr.unwrap(), + ll_dst_addr: ieee802154_repr.dst_addr.unwrap(), + datagram_size: self.datagram_size(), + datagram_tag: self.datagram_tag(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return the payload. + pub fn payload(&self) -> &'a [u8] { + match self.dispatch() { + DISPATCH_FIRST_FRAGMENT_HEADER => { + let raw = self.buffer.as_ref(); + &raw[field::FIRST_FRAGMENT_REST] + } + DISPATCH_FRAGMENT_HEADER => { + let raw = self.buffer.as_ref(); + &raw[field::NEXT_FRAGMENT_REST] + } + _ => unreachable!(), + } + } +} + +impl + AsMut<[u8]>> Packet { + fn set_dispatch_field(&mut self, value: u8) { + let raw = self.buffer.as_mut(); + raw[field::DISPATCH] = (raw[field::DISPATCH] & !(0b11111 << 3)) | (value << 3); + } + + fn set_datagram_size(&mut self, size: u16) { + let raw = self.buffer.as_mut(); + let mut v = NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]); + v = (v & !0b111_1111_1111) | size; + + NetworkEndian::write_u16(&mut raw[field::DATAGRAM_SIZE], v); + } + + fn set_datagram_tag(&mut self, tag: u16) { + let raw = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut raw[field::DATAGRAM_TAG], tag); + } + + fn set_datagram_offset(&mut self, offset: u8) { + let raw = self.buffer.as_mut(); + raw[field::DATAGRAM_OFFSET] = offset; + } +} + +/// A high-level representation of a 6LoWPAN Fragment header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Repr { + FirstFragment { size: u16, tag: u16 }, + Fragment { size: u16, tag: u16, offset: u8 }, +} + +impl core::fmt::Display for Repr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Repr::FirstFragment { size, tag } => { + write!(f, "FirstFrag size={size} tag={tag}") + } + Repr::Fragment { size, tag, offset } => { + write!(f, "NthFrag size={size} tag={tag} offset={offset}") + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Repr { + fn format(&self, fmt: defmt::Formatter) { + match self { + Repr::FirstFragment { size, tag } => { + defmt::write!(fmt, "FirstFrag size={} tag={}", size, tag); + } + Repr::Fragment { size, tag, offset } => { + defmt::write!(fmt, "NthFrag size={} tag={} offset={}", size, tag, offset); + } + } + } +} + +impl Repr { + /// Parse a 6LoWPAN Fragment header. + pub fn parse>(packet: &Packet) -> Result { + packet.check_len()?; + let size = packet.datagram_size(); + let tag = packet.datagram_tag(); + + match packet.dispatch() { + DISPATCH_FIRST_FRAGMENT_HEADER => Ok(Self::FirstFragment { size, tag }), + DISPATCH_FRAGMENT_HEADER => Ok(Self::Fragment { + size, + tag, + offset: packet.datagram_offset(), + }), + _ => Err(Error), + } + } + + /// Returns the length of the Fragment header. + pub const fn buffer_len(&self) -> usize { + match self { + Self::FirstFragment { .. } => field::FIRST_FRAGMENT_REST.start, + Self::Fragment { .. } => field::NEXT_FRAGMENT_REST.start, + } + } + + /// Emit a high-level representation into a 6LoWPAN Fragment header. + pub fn emit + AsMut<[u8]>>(&self, packet: &mut Packet) { + match self { + Self::FirstFragment { size, tag } => { + packet.set_dispatch_field(DISPATCH_FIRST_FRAGMENT_HEADER); + packet.set_datagram_size(*size); + packet.set_datagram_tag(*tag); + } + Self::Fragment { size, tag, offset } => { + packet.set_dispatch_field(DISPATCH_FRAGMENT_HEADER); + packet.set_datagram_size(*size); + packet.set_datagram_tag(*tag); + packet.set_datagram_offset(*offset); + } + } + } +} diff --git a/vendor/smoltcp/src/wire/sixlowpan/iphc.rs b/vendor/smoltcp/src/wire/sixlowpan/iphc.rs new file mode 100644 index 00000000..2917d23c --- /dev/null +++ b/vendor/smoltcp/src/wire/sixlowpan/iphc.rs @@ -0,0 +1,948 @@ +//! Implementation of IP Header Compression from [RFC 6282 § 3.1]. +//! It defines the compression of IPv6 headers. +//! +//! [RFC 6282 § 3.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1 + +use super::{ + AddressContext, AddressMode, DISPATCH_IPHC_HEADER, Error, NextHeader, Result, UnresolvedAddress, +}; +use crate::wire::{IpProtocol, ieee802154::Address as LlAddress, ipv6, ipv6::AddressExt}; +use byteorder::{ByteOrder, NetworkEndian}; + +mod field { + use crate::wire::field::*; + + pub const IPHC_FIELD: Field = 0..2; +} + +macro_rules! get_field { + ($name:ident, $mask:expr, $shift:expr) => { + fn $name(&self) -> u8 { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::IPHC_FIELD]); + ((raw >> $shift) & $mask) as u8 + } + }; +} + +macro_rules! set_field { + ($name:ident, $mask:expr, $shift:expr) => { + fn $name(&mut self, val: u8) { + let data = &mut self.buffer.as_mut()[field::IPHC_FIELD]; + let mut raw = NetworkEndian::read_u16(data); + + raw = (raw & !($mask << $shift)) | ((val as u16) << $shift); + NetworkEndian::write_u16(data, raw); + } + }; +} + +/// A read/write wrapper around a 6LoWPAN IPHC header. +/// [RFC 6282 § 3.1] specifies the format of the header. +/// +/// The header always start with the following base format (from [RFC 6282 § 3.1.1]): +/// ```txt +/// 0 1 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +/// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +/// | 0 | 1 | 1 | TF |NH | HLIM |CID|SAC| SAM | M |DAC| DAM | +/// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +/// ``` +/// With: +/// - TF: Traffic Class and Flow Label +/// - NH: Next Header +/// - HLIM: Hop Limit +/// - CID: Context Identifier Extension +/// - SAC: Source Address Compression +/// - SAM: Source Address Mode +/// - M: Multicast Compression +/// - DAC: Destination Address Compression +/// - DAM: Destination Address Mode +/// +/// Depending on the flags in the base format, the following fields are added to the header: +/// - Traffic Class and Flow Label +/// - Next Header +/// - Hop Limit +/// - IPv6 source address +/// - IPv6 destination address +/// +/// [RFC 6282 § 3.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1 +/// [RFC 6282 § 3.1.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1.1 +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +impl> Packet { + /// Input a raw octet buffer with a 6LoWPAN IPHC header structure. + pub const fn new_unchecked(buffer: T) -> Self { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let buffer = self.buffer.as_ref(); + if buffer.len() < 2 { + return Err(Error); + } + + let mut offset = self.ip_fields_start() + + self.traffic_class_size() + + self.next_header_size() + + self.hop_limit_size(); + offset += self.src_address_size(); + offset += self.dst_address_size(); + + if offset as usize > buffer.len() { + return Err(Error); + } + + Ok(()) + } + + /// Consumes the frame, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the Next Header field. + pub fn next_header(&self) -> NextHeader { + let nh = self.nh_field(); + + if nh == 1 { + // The next header field is compressed. + // It is also encoded using LOWPAN_NHC. + NextHeader::Compressed + } else { + // The full 8 bits for Next Header are carried in-line. + let start = (self.ip_fields_start() + self.traffic_class_size()) as usize; + + let data = self.buffer.as_ref(); + let nh = data[start..start + 1][0]; + NextHeader::Uncompressed(IpProtocol::from(nh)) + } + } + + /// Return the Hop Limit. + pub fn hop_limit(&self) -> u8 { + match self.hlim_field() { + 0b00 => { + let start = (self.ip_fields_start() + + self.traffic_class_size() + + self.next_header_size()) as usize; + + let data = self.buffer.as_ref(); + data[start..start + 1][0] + } + 0b01 => 1, + 0b10 => 64, + 0b11 => 255, + _ => unreachable!(), + } + } + + /// Return the Source Context Identifier. + pub fn src_context_id(&self) -> Option { + if self.cid_field() == 1 { + let data = self.buffer.as_ref(); + Some(data[2] >> 4) + } else { + None + } + } + + /// Return the Destination Context Identifier. + pub fn dst_context_id(&self) -> Option { + if self.cid_field() == 1 { + let data = self.buffer.as_ref(); + Some(data[2] & 0x0f) + } else { + None + } + } + + /// Return the ECN field (when it is inlined). + pub fn ecn_field(&self) -> Option { + match self.tf_field() { + 0b00..=0b10 => { + let start = self.ip_fields_start() as usize; + Some(self.buffer.as_ref()[start..][0] & 0b1100_0000) + } + 0b11 => None, + _ => unreachable!(), + } + } + + /// Return the DSCP field (when it is inlined). + pub fn dscp_field(&self) -> Option { + match self.tf_field() { + 0b00 | 0b10 => { + let start = self.ip_fields_start() as usize; + Some(self.buffer.as_ref()[start..][0] & 0b111111) + } + 0b01 | 0b11 => None, + _ => unreachable!(), + } + } + + /// Return the flow label field (when it is inlined). + pub fn flow_label_field(&self) -> Option { + match self.tf_field() { + 0b00 => { + let start = self.ip_fields_start() as usize; + Some(NetworkEndian::read_u16( + &self.buffer.as_ref()[start..][2..4], + )) + } + 0b01 => { + let start = self.ip_fields_start() as usize; + Some(NetworkEndian::read_u16( + &self.buffer.as_ref()[start..][1..3], + )) + } + 0b10 | 0b11 => None, + _ => unreachable!(), + } + } + + /// Return the Source Address. + pub fn src_addr(&self) -> Result> { + let start = (self.ip_fields_start() + + self.traffic_class_size() + + self.next_header_size() + + self.hop_limit_size()) as usize; + + let data = self.buffer.as_ref(); + match (self.sac_field(), self.sam_field()) { + (0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline( + &data[start..][..16], + ))), + (0, 0b01) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::InLine64bits(&data[start..][..8]), + )), + (0, 0b10) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::InLine16bits(&data[start..][..2]), + )), + (0, 0b11) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)), + (1, 0b00) => Ok(UnresolvedAddress::WithContext(( + 0, + AddressMode::Unspecified, + ))), + (1, 0b01) => { + if let Some(id) = self.src_context_id() { + Ok(UnresolvedAddress::WithContext(( + id as usize, + AddressMode::InLine64bits(&data[start..][..8]), + ))) + } else { + Err(Error) + } + } + (1, 0b10) => { + if let Some(id) = self.src_context_id() { + Ok(UnresolvedAddress::WithContext(( + id as usize, + AddressMode::InLine16bits(&data[start..][..2]), + ))) + } else { + Err(Error) + } + } + (1, 0b11) => { + if let Some(id) = self.src_context_id() { + Ok(UnresolvedAddress::WithContext(( + id as usize, + AddressMode::FullyElided, + ))) + } else { + Err(Error) + } + } + _ => Err(Error), + } + } + + /// Return the Destination Address. + pub fn dst_addr(&self) -> Result> { + let start = (self.ip_fields_start() + + self.traffic_class_size() + + self.next_header_size() + + self.hop_limit_size() + + self.src_address_size()) as usize; + + let data = self.buffer.as_ref(); + match (self.m_field(), self.dac_field(), self.dam_field()) { + (0, 0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline( + &data[start..][..16], + ))), + (0, 0, 0b01) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::InLine64bits(&data[start..][..8]), + )), + (0, 0, 0b10) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::InLine16bits(&data[start..][..2]), + )), + (0, 0, 0b11) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)), + (0, 1, 0b00) => Ok(UnresolvedAddress::Reserved), + (0, 1, 0b01) => { + if let Some(id) = self.dst_context_id() { + Ok(UnresolvedAddress::WithContext(( + id as usize, + AddressMode::InLine64bits(&data[start..][..8]), + ))) + } else { + Err(Error) + } + } + (0, 1, 0b10) => { + if let Some(id) = self.dst_context_id() { + Ok(UnresolvedAddress::WithContext(( + id as usize, + AddressMode::InLine16bits(&data[start..][..2]), + ))) + } else { + Err(Error) + } + } + (0, 1, 0b11) => { + if let Some(id) = self.dst_context_id() { + Ok(UnresolvedAddress::WithContext(( + id as usize, + AddressMode::FullyElided, + ))) + } else { + Err(Error) + } + } + (1, 0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline( + &data[start..][..16], + ))), + (1, 0, 0b01) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::Multicast48bits(&data[start..][..6]), + )), + (1, 0, 0b10) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::Multicast32bits(&data[start..][..4]), + )), + (1, 0, 0b11) => Ok(UnresolvedAddress::WithoutContext( + AddressMode::Multicast8bits(&data[start..][..1]), + )), + (1, 1, 0b00) => Ok(UnresolvedAddress::WithContext(( + 0, + AddressMode::NotSupported, + ))), + (1, 1, 0b01..=0b11) => Ok(UnresolvedAddress::Reserved), + _ => Err(Error), + } + } + + get_field!(dispatch_field, 0b111, 13); + get_field!(tf_field, 0b11, 11); + get_field!(nh_field, 0b1, 10); + get_field!(hlim_field, 0b11, 8); + get_field!(cid_field, 0b1, 7); + get_field!(sac_field, 0b1, 6); + get_field!(sam_field, 0b11, 4); + get_field!(m_field, 0b1, 3); + get_field!(dac_field, 0b1, 2); + get_field!(dam_field, 0b11, 0); + + /// Return the start for the IP fields. + fn ip_fields_start(&self) -> u8 { + 2 + self.cid_size() + } + + /// Get the size in octets of the traffic class field. + fn traffic_class_size(&self) -> u8 { + match self.tf_field() { + 0b00 => 4, + 0b01 => 3, + 0b10 => 1, + 0b11 => 0, + _ => unreachable!(), + } + } + + /// Get the size in octets of the next header field. + fn next_header_size(&self) -> u8 { + (self.nh_field() != 1) as u8 + } + + /// Get the size in octets of the hop limit field. + fn hop_limit_size(&self) -> u8 { + (self.hlim_field() == 0b00) as u8 + } + + /// Get the size in octets of the CID field. + fn cid_size(&self) -> u8 { + (self.cid_field() == 1) as u8 + } + + /// Get the size in octets of the source address. + fn src_address_size(&self) -> u8 { + match (self.sac_field(), self.sam_field()) { + (0, 0b00) => 16, // The full address is carried in-line. + (0, 0b01) => 8, // The first 64 bits are elided. + (0, 0b10) => 2, // The first 112 bits are elided. + (0, 0b11) => 0, // The address is fully elided. + (1, 0b00) => 0, // The UNSPECIFIED address. + (1, 0b01) => 8, // Address derived using context information. + (1, 0b10) => 2, // Address derived using context information. + (1, 0b11) => 0, // Address derived using context information. + _ => unreachable!(), + } + } + + /// Get the size in octets of the address address. + fn dst_address_size(&self) -> u8 { + match (self.m_field(), self.dac_field(), self.dam_field()) { + (0, 0, 0b00) => 16, // The full address is carried in-line. + (0, 0, 0b01) => 8, // The first 64 bits are elided. + (0, 0, 0b10) => 2, // The first 112 bits are elided. + (0, 0, 0b11) => 0, // The address is fully elided. + (0, 1, 0b00) => 0, // Reserved. + (0, 1, 0b01) => 8, // Address derived using context information. + (0, 1, 0b10) => 2, // Address derived using context information. + (0, 1, 0b11) => 0, // Address derived using context information. + (1, 0, 0b00) => 16, // The full address is carried in-line. + (1, 0, 0b01) => 6, // The address takes the form ffXX::00XX:XXXX:XXXX. + (1, 0, 0b10) => 4, // The address takes the form ffXX::00XX:XXXX. + (1, 0, 0b11) => 1, // The address takes the form ff02::00XX. + (1, 1, 0b00) => 6, // Match Unicast-Prefix-based IPv6. + (1, 1, 0b01) => 0, // Reserved. + (1, 1, 0b10) => 0, // Reserved. + (1, 1, 0b11) => 0, // Reserved. + _ => unreachable!(), + } + } + + /// Return the length of the header. + pub fn header_len(&self) -> usize { + let mut len = self.ip_fields_start(); + len += self.traffic_class_size(); + len += self.next_header_size(); + len += self.hop_limit_size(); + len += self.src_address_size(); + len += self.dst_address_size(); + + len as usize + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the payload. + pub fn payload(&self) -> &'a [u8] { + let mut len = self.ip_fields_start(); + len += self.traffic_class_size(); + len += self.next_header_size(); + len += self.hop_limit_size(); + len += self.src_address_size(); + len += self.dst_address_size(); + + let len = len as usize; + + let data = self.buffer.as_ref(); + &data[len..] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the dispatch field to `0b011`. + fn set_dispatch_field(&mut self) { + let data = &mut self.buffer.as_mut()[field::IPHC_FIELD]; + let mut raw = NetworkEndian::read_u16(data); + + raw = (raw & !(0b111 << 13)) | (0b11 << 13); + NetworkEndian::write_u16(data, raw); + } + + set_field!(set_tf_field, 0b11, 11); + set_field!(set_nh_field, 0b1, 10); + set_field!(set_hlim_field, 0b11, 8); + set_field!(set_cid_field, 0b1, 7); + set_field!(set_sac_field, 0b1, 6); + set_field!(set_sam_field, 0b11, 4); + set_field!(set_m_field, 0b1, 3); + set_field!(set_dac_field, 0b1, 2); + set_field!(set_dam_field, 0b11, 0); + + fn set_field(&mut self, idx: usize, value: &[u8]) { + let raw = self.buffer.as_mut(); + raw[idx..idx + value.len()].copy_from_slice(value); + } + + /// Set the Next Header. + /// + /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to. + fn set_next_header(&mut self, nh: NextHeader, mut idx: usize) -> usize { + match nh { + NextHeader::Uncompressed(nh) => { + self.set_nh_field(0); + self.set_field(idx, &[nh.into()]); + idx += 1; + } + NextHeader::Compressed => self.set_nh_field(1), + } + + idx + } + + /// Set the Hop Limit. + /// + /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to. + fn set_hop_limit(&mut self, hl: u8, mut idx: usize) -> usize { + match hl { + 255 => self.set_hlim_field(0b11), + 64 => self.set_hlim_field(0b10), + 1 => self.set_hlim_field(0b01), + _ => { + self.set_hlim_field(0b00); + self.set_field(idx, &[hl]); + idx += 1; + } + } + + idx + } + + /// Set the Source Address based on the IPv6 address and the Link-Local address. + /// + /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to. + fn set_src_address( + &mut self, + src_addr: ipv6::Address, + ll_src_addr: Option, + mut idx: usize, + ) -> usize { + self.set_cid_field(0); + self.set_sac_field(0); + let src = src_addr.octets(); + if src_addr == ipv6::Address::UNSPECIFIED { + self.set_sac_field(1); + self.set_sam_field(0b00); + } else if src_addr.is_link_local() { + // We have a link local address. + // The remainder of the address can be elided when the context contains + // a 802.15.4 short address or a 802.15.4 extended address which can be + // converted to a eui64 address. + let is_eui_64 = ll_src_addr + .map(|addr| { + addr.as_eui_64() + .map(|addr| addr[..] == src[8..]) + .unwrap_or(false) + }) + .unwrap_or(false); + + if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] { + let ll = [src[14], src[15]]; + + if ll_src_addr == Some(LlAddress::Short(ll)) { + // We have the context from the 802.15.4 frame. + // The context contains the short address. + // We can elide the source address. + self.set_sam_field(0b11); + } else { + // We don't have the context from the 802.15.4 frame. + // We cannot elide the source address, however we can elide 112 bits. + self.set_sam_field(0b10); + + self.set_field(idx, &src[14..]); + idx += 2; + } + } else if is_eui_64 { + // We have the context from the 802.15.4 frame. + // The context contains the extended address. + // We can elide the source address. + self.set_sam_field(0b11); + } else { + // We cannot elide the source address, however we can elide 64 bits. + self.set_sam_field(0b01); + + self.set_field(idx, &src[8..]); + idx += 8; + } + } else { + // We cannot elide anything. + self.set_sam_field(0b00); + self.set_field(idx, &src); + idx += 16; + } + + idx + } + + /// Set the Destination Address based on the IPv6 address and the Link-Local address. + /// + /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to. + fn set_dst_address( + &mut self, + dst_addr: ipv6::Address, + ll_dst_addr: Option, + mut idx: usize, + ) -> usize { + self.set_dac_field(0); + self.set_dam_field(0); + self.set_m_field(0); + let dst = dst_addr.octets(); + if dst_addr.is_multicast() { + self.set_m_field(1); + + if dst[1] == 0x02 && dst[2..15] == [0; 13] { + self.set_dam_field(0b11); + + self.set_field(idx, &[dst[15]]); + idx += 1; + } else if dst[2..13] == [0; 11] { + self.set_dam_field(0b10); + + self.set_field(idx, &[dst[1]]); + idx += 1; + self.set_field(idx, &dst[13..]); + idx += 3; + } else if dst[2..11] == [0; 9] { + self.set_dam_field(0b01); + + self.set_field(idx, &[dst[1]]); + idx += 1; + self.set_field(idx, &dst[11..]); + idx += 5; + } else { + self.set_dam_field(0b11); + + self.set_field(idx, &dst); + idx += 16; + } + } else if dst_addr.is_link_local() { + let is_eui_64 = ll_dst_addr + .map(|addr| { + addr.as_eui_64() + .map(|addr| addr[..] == dst[8..]) + .unwrap_or(false) + }) + .unwrap_or(false); + + if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] { + let ll = [dst[14], dst[15]]; + + if ll_dst_addr == Some(LlAddress::Short(ll)) { + self.set_dam_field(0b11); + } else { + self.set_dam_field(0b10); + + self.set_field(idx, &dst[14..]); + idx += 2; + } + } else if is_eui_64 { + self.set_dam_field(0b11); + } else { + self.set_dam_field(0b01); + + self.set_field(idx, &dst[8..]); + idx += 8; + } + } else { + self.set_dam_field(0b00); + + self.set_field(idx, &dst); + idx += 16; + } + + idx + } + + /// Return a mutable pointer to the payload. + pub fn payload_mut(&mut self) -> &mut [u8] { + let mut len = self.ip_fields_start(); + + len += self.traffic_class_size(); + len += self.next_header_size(); + len += self.hop_limit_size(); + len += self.src_address_size(); + len += self.dst_address_size(); + + let len = len as usize; + + let data = self.buffer.as_mut(); + &mut data[len..] + } +} + +/// A high-level representation of a 6LoWPAN IPHC header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Repr { + pub src_addr: ipv6::Address, + pub ll_src_addr: Option, + pub dst_addr: ipv6::Address, + pub ll_dst_addr: Option, + pub next_header: NextHeader, + pub hop_limit: u8, + // TODO(thvdveld): refactor the following fields into something else + pub ecn: Option, + pub dscp: Option, + pub flow_label: Option, +} + +impl core::fmt::Display for Repr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "IPHC src={} dst={} nxt-hdr={} hop-limit={}", + self.src_addr, self.dst_addr, self.next_header, self.hop_limit + ) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Repr { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!( + fmt, + "IPHC src={} dst={} nxt-hdr={} hop-limit={}", + self.src_addr, + self.dst_addr, + self.next_header, + self.hop_limit + ); + } +} + +impl Repr { + /// Parse a 6LoWPAN IPHC header and return a high-level representation. + /// + /// The `ll_src_addr` and `ll_dst_addr` are the link-local addresses used for resolving the + /// IPv6 packets. + pub fn parse + ?Sized>( + packet: &Packet<&T>, + ll_src_addr: Option, + ll_dst_addr: Option, + addr_context: &[AddressContext], + ) -> Result { + // Ensure basic accessors will work. + packet.check_len()?; + + if packet.dispatch_field() != DISPATCH_IPHC_HEADER { + // This is not an LOWPAN_IPHC packet. + return Err(Error); + } + + let src_addr = packet.src_addr()?.resolve(ll_src_addr, addr_context)?; + let dst_addr = packet.dst_addr()?.resolve(ll_dst_addr, addr_context)?; + + Ok(Self { + src_addr, + ll_src_addr, + dst_addr, + ll_dst_addr, + next_header: packet.next_header(), + hop_limit: packet.hop_limit(), + ecn: packet.ecn_field(), + dscp: packet.dscp_field(), + flow_label: packet.flow_label_field(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub fn buffer_len(&self) -> usize { + let mut len = 0; + len += 2; // The minimal header length + + len += match self.next_header { + NextHeader::Compressed => 0, // The next header is compressed (we don't need to inline what the next header is) + NextHeader::Uncompressed(_) => 1, // The next header field is inlined + }; + + // Hop Limit size + len += match self.hop_limit { + 255 | 64 | 1 => 0, // We can inline the hop limit + _ => 1, + }; + + // Add the length of the source address + len += if self.src_addr == ipv6::Address::UNSPECIFIED { + 0 + } else if self.src_addr.is_link_local() { + let src = self.src_addr.octets(); + let ll = [src[14], src[15]]; + + let is_eui_64 = self + .ll_src_addr + .map(|addr| { + addr.as_eui_64() + .map(|addr| addr[..] == src[8..]) + .unwrap_or(false) + }) + .unwrap_or(false); + + if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] { + if self.ll_src_addr == Some(LlAddress::Short(ll)) { + 0 + } else { + 2 + } + } else if is_eui_64 { + 0 + } else { + 8 + } + } else { + 16 + }; + + // Add the size of the destination header + let dst = self.dst_addr.octets(); + len += if self.dst_addr.is_multicast() { + if dst[1] == 0x02 && dst[2..15] == [0; 13] { + 1 + } else if dst[2..13] == [0; 11] { + 4 + } else if dst[2..11] == [0; 9] { + 6 + } else { + 16 + } + } else if self.dst_addr.is_link_local() { + let is_eui_64 = self + .ll_dst_addr + .map(|addr| { + addr.as_eui_64() + .map(|addr| addr[..] == dst[8..]) + .unwrap_or(false) + }) + .unwrap_or(false); + + if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] { + let ll = [dst[14], dst[15]]; + + if self.ll_dst_addr == Some(LlAddress::Short(ll)) { + 0 + } else { + 2 + } + } else if is_eui_64 { + 0 + } else { + 8 + } + } else { + 16 + }; + + len += match (self.ecn, self.dscp, self.flow_label) { + (Some(_), Some(_), Some(_)) => 4, + (Some(_), None, Some(_)) => 3, + (Some(_), Some(_), None) => 1, + (None, None, None) => 0, + _ => unreachable!(), + }; + + len + } + + /// Emit a high-level representation into a 6LoWPAN IPHC header. + pub fn emit + AsMut<[u8]>>(&self, packet: &mut Packet) { + let idx = 2; + + packet.set_dispatch_field(); + + // FIXME(thvdveld): we don't set anything from the traffic flow. + packet.set_tf_field(0b11); + + let idx = packet.set_next_header(self.next_header, idx); + let idx = packet.set_hop_limit(self.hop_limit, idx); + let idx = packet.set_src_address(self.src_addr, self.ll_src_addr, idx); + packet.set_dst_address(self.dst_addr, self.ll_dst_addr, idx); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn iphc_fields() { + let bytes = [ + 0x7a, 0x33, // IPHC + 0x3a, // Next header + ]; + + let packet = Packet::new_unchecked(bytes); + + assert_eq!(packet.dispatch_field(), 0b011); + assert_eq!(packet.tf_field(), 0b11); + assert_eq!(packet.nh_field(), 0b0); + assert_eq!(packet.hlim_field(), 0b10); + assert_eq!(packet.cid_field(), 0b0); + assert_eq!(packet.sac_field(), 0b0); + assert_eq!(packet.sam_field(), 0b11); + assert_eq!(packet.m_field(), 0b0); + assert_eq!(packet.dac_field(), 0b0); + assert_eq!(packet.dam_field(), 0b11); + + assert_eq!( + packet.next_header(), + NextHeader::Uncompressed(IpProtocol::Icmpv6) + ); + + assert_eq!(packet.src_address_size(), 0); + assert_eq!(packet.dst_address_size(), 0); + assert_eq!(packet.hop_limit(), 64); + + assert_eq!( + packet.src_addr(), + Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)) + ); + assert_eq!( + packet.dst_addr(), + Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)) + ); + + let bytes = [ + 0x7e, 0xf7, // IPHC, + 0x00, // CID + ]; + + let packet = Packet::new_unchecked(bytes); + + assert_eq!(packet.dispatch_field(), 0b011); + assert_eq!(packet.tf_field(), 0b11); + assert_eq!(packet.nh_field(), 0b1); + assert_eq!(packet.hlim_field(), 0b10); + assert_eq!(packet.cid_field(), 0b1); + assert_eq!(packet.sac_field(), 0b1); + assert_eq!(packet.sam_field(), 0b11); + assert_eq!(packet.m_field(), 0b0); + assert_eq!(packet.dac_field(), 0b1); + assert_eq!(packet.dam_field(), 0b11); + + assert_eq!(packet.next_header(), NextHeader::Compressed); + + assert_eq!(packet.src_address_size(), 0); + assert_eq!(packet.dst_address_size(), 0); + assert_eq!(packet.hop_limit(), 64); + + assert_eq!( + packet.src_addr(), + Ok(UnresolvedAddress::WithContext(( + 0, + AddressMode::FullyElided + ))) + ); + assert_eq!( + packet.dst_addr(), + Ok(UnresolvedAddress::WithContext(( + 0, + AddressMode::FullyElided + ))) + ); + } +} diff --git a/vendor/smoltcp/src/wire/sixlowpan/mod.rs b/vendor/smoltcp/src/wire/sixlowpan/mod.rs new file mode 100644 index 00000000..60766ed6 --- /dev/null +++ b/vendor/smoltcp/src/wire/sixlowpan/mod.rs @@ -0,0 +1,373 @@ +//! Implementation of [RFC 6282] which specifies a compression format for IPv6 datagrams over +//! IEEE802.154-based networks. +//! +//! [RFC 6282]: https://datatracker.ietf.org/doc/html/rfc6282 + +use super::{Error, Result}; +use crate::wire::IpProtocol; +use crate::wire::ieee802154::Address as LlAddress; +use crate::wire::ipv6; + +pub mod frag; +pub mod iphc; +pub mod nhc; + +const ADDRESS_CONTEXT_LENGTH: usize = 8; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AddressContext(pub [u8; ADDRESS_CONTEXT_LENGTH]); + +/// The representation of an unresolved address. 6LoWPAN compression of IPv6 addresses can be with +/// and without context information. The decompression with context information is not yet +/// implemented. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UnresolvedAddress<'a> { + WithoutContext(AddressMode<'a>), + WithContext((usize, AddressMode<'a>)), + Reserved, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AddressMode<'a> { + /// The full address is carried in-line. + FullInline(&'a [u8]), + /// The first 64-bits of the address are elided. The value of those bits + /// is the link-local prefix padded with zeros. The remaining 64 bits are + /// carried in-line. + InLine64bits(&'a [u8]), + /// The first 112 bits of the address are elided. The value of the first + /// 64 bits is the link-local prefix padded with zeros. The following 64 bits + /// are 0000:00ff:fe00:XXXX, where XXXX are the 16 bits carried in-line. + InLine16bits(&'a [u8]), + /// The address is fully elided. The first 64 bits of the address are + /// the link-local prefix padded with zeros. The remaining 64 bits are + /// computed from the encapsulating header (e.g., 802.15.4 or IPv6 source address) + /// as specified in Section 3.2.2. + FullyElided, + /// The address takes the form ffXX::00XX:XXXX:XXXX + Multicast48bits(&'a [u8]), + /// The address takes the form ffXX::00XX:XXXX. + Multicast32bits(&'a [u8]), + /// The address takes the form ff02::00XX. + Multicast8bits(&'a [u8]), + /// The unspecified address. + Unspecified, + NotSupported, +} + +const LINK_LOCAL_PREFIX: [u8; 2] = [0xfe, 0x80]; +const EUI64_MIDDLE_VALUE: [u8; 2] = [0xff, 0xfe]; + +impl<'a> UnresolvedAddress<'a> { + pub fn resolve( + self, + ll_address: Option, + addr_context: &[AddressContext], + ) -> Result { + let mut bytes = [0; 16]; + + let copy_context = |index: usize, bytes: &mut [u8]| -> Result<()> { + if index >= addr_context.len() { + return Err(Error); + } + + let context = addr_context[index]; + bytes[..ADDRESS_CONTEXT_LENGTH].copy_from_slice(&context.0); + + Ok(()) + }; + + match self { + UnresolvedAddress::WithoutContext(mode) => match mode { + AddressMode::FullInline(addr) => { + Ok(crate::wire::ipv6_from_octets(addr.try_into().unwrap())) + } + AddressMode::InLine64bits(inline) => { + bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]); + bytes[8..].copy_from_slice(inline); + Ok(crate::wire::ipv6_from_octets(bytes)) + } + AddressMode::InLine16bits(inline) => { + bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]); + bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]); + bytes[14..].copy_from_slice(inline); + Ok(crate::wire::ipv6_from_octets(bytes)) + } + AddressMode::FullyElided => { + bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]); + match ll_address { + Some(LlAddress::Short(ll)) => { + bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]); + bytes[14..].copy_from_slice(&ll); + } + Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() { + Some(addr) => bytes[8..].copy_from_slice(&addr), + None => return Err(Error), + }, + Some(LlAddress::Absent) => return Err(Error), + None => return Err(Error), + } + Ok(crate::wire::ipv6_from_octets(bytes)) + } + AddressMode::Multicast48bits(inline) => { + bytes[0] = 0xff; + bytes[1] = inline[0]; + bytes[11..].copy_from_slice(&inline[1..][..5]); + Ok(crate::wire::ipv6_from_octets(bytes)) + } + AddressMode::Multicast32bits(inline) => { + bytes[0] = 0xff; + bytes[1] = inline[0]; + bytes[13..].copy_from_slice(&inline[1..][..3]); + Ok(crate::wire::ipv6_from_octets(bytes)) + } + AddressMode::Multicast8bits(inline) => { + bytes[0] = 0xff; + bytes[1] = 0x02; + bytes[15] = inline[0]; + Ok(crate::wire::ipv6_from_octets(bytes)) + } + _ => Err(Error), + }, + UnresolvedAddress::WithContext(mode) => match mode { + (_, AddressMode::Unspecified) => Ok(ipv6::Address::UNSPECIFIED), + (index, AddressMode::InLine64bits(inline)) => { + copy_context(index, &mut bytes[..])?; + bytes[16 - inline.len()..].copy_from_slice(inline); + Ok(crate::wire::ipv6_from_octets(bytes)) + } + (index, AddressMode::InLine16bits(inline)) => { + copy_context(index, &mut bytes[..])?; + bytes[16 - inline.len()..].copy_from_slice(inline); + Ok(crate::wire::ipv6_from_octets(bytes)) + } + (index, AddressMode::FullyElided) => { + match ll_address { + Some(LlAddress::Short(ll)) => { + bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]); + bytes[14..].copy_from_slice(&ll); + } + Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() { + Some(addr) => bytes[8..].copy_from_slice(&addr), + None => return Err(Error), + }, + Some(LlAddress::Absent) => return Err(Error), + None => return Err(Error), + } + + copy_context(index, &mut bytes[..])?; + + Ok(crate::wire::ipv6_from_octets(bytes)) + } + _ => Err(Error), + }, + UnresolvedAddress::Reserved => Err(Error), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SixlowpanPacket { + FragmentHeader, + IphcHeader, +} + +const DISPATCH_FIRST_FRAGMENT_HEADER: u8 = 0b11000; +const DISPATCH_FRAGMENT_HEADER: u8 = 0b11100; +const DISPATCH_IPHC_HEADER: u8 = 0b011; +const DISPATCH_UDP_HEADER: u8 = 0b11110; +const DISPATCH_EXT_HEADER: u8 = 0b1110; + +impl SixlowpanPacket { + /// Returns the type of the 6LoWPAN header. + /// This can either be a fragment header or an IPHC header. + /// + /// # Errors + /// Returns `[Error::Unrecognized]` when neither the Fragment Header dispatch or the IPHC + /// dispatch is recognized. + pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result { + let raw = buffer.as_ref(); + + if raw.is_empty() { + return Err(Error); + } + + if raw[0] >> 3 == DISPATCH_FIRST_FRAGMENT_HEADER || raw[0] >> 3 == DISPATCH_FRAGMENT_HEADER + { + Ok(Self::FragmentHeader) + } else if raw[0] >> 5 == DISPATCH_IPHC_HEADER { + Ok(Self::IphcHeader) + } else { + Err(Error) + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum NextHeader { + Compressed, + Uncompressed(IpProtocol), +} + +impl core::fmt::Display for NextHeader { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + NextHeader::Compressed => write!(f, "compressed"), + NextHeader::Uncompressed(protocol) => write!(f, "{protocol}"), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for NextHeader { + fn format(&self, fmt: defmt::Formatter) { + match self { + NextHeader::Compressed => defmt::write!(fmt, "compressed"), + NextHeader::Uncompressed(protocol) => defmt::write!(fmt, "{}", protocol), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn sixlowpan_fragment_emit() { + let repr = frag::Repr::FirstFragment { + size: 0xff, + tag: 0xabcd, + }; + let buffer = [0u8; 4]; + let mut packet = frag::Packet::new_unchecked(buffer); + + assert_eq!(repr.buffer_len(), 4); + repr.emit(&mut packet); + + assert_eq!(packet.datagram_size(), 0xff); + assert_eq!(packet.datagram_tag(), 0xabcd); + assert_eq!(packet.into_inner(), [0xc0, 0xff, 0xab, 0xcd]); + + let repr = frag::Repr::Fragment { + size: 0xff, + tag: 0xabcd, + offset: 0xcc, + }; + let buffer = [0u8; 5]; + let mut packet = frag::Packet::new_unchecked(buffer); + + assert_eq!(repr.buffer_len(), 5); + repr.emit(&mut packet); + + assert_eq!(packet.datagram_size(), 0xff); + assert_eq!(packet.datagram_tag(), 0xabcd); + assert_eq!(packet.into_inner(), [0xe0, 0xff, 0xab, 0xcd, 0xcc]); + } + + #[test] + fn sixlowpan_three_fragments() { + use crate::wire::Ieee802154Address; + use crate::wire::ieee802154::Frame as Ieee802154Frame; + use crate::wire::ieee802154::Repr as Ieee802154Repr; + + let key = frag::Key { + ll_src_addr: Ieee802154Address::Extended([50, 147, 130, 47, 40, 8, 62, 217]), + ll_dst_addr: Ieee802154Address::Extended([26, 11, 66, 66, 66, 66, 66, 66]), + datagram_size: 307, + datagram_tag: 63, + }; + + let frame1: &[u8] = &[ + 0x41, 0xcc, 0x92, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9, + 0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xc1, 0x33, 0x00, 0x3f, 0x6e, 0x33, 0x02, + 0x35, 0x3d, 0xf0, 0xd2, 0x5f, 0x1b, 0x39, 0xb4, 0x6b, 0x4c, 0x6f, 0x72, 0x65, 0x6d, + 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x73, + 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65, + 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63, + 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x41, 0x6c, 0x69, 0x71, + 0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64, 0x69, 0x6f, 0x2c, 0x20, + 0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65, 0x6c, 0x20, 0x72, + ]; + + let ieee802154_frame = Ieee802154Frame::new_checked(frame1).unwrap(); + let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap(); + + let sixlowpan_frame = + SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap(); + + let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame { + frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap() + } else { + unreachable!() + }; + + assert_eq!(frag.datagram_size(), 307); + assert_eq!(frag.datagram_tag(), 0x003f); + assert_eq!(frag.datagram_offset(), 0); + + assert_eq!(frag.get_key(&ieee802154_repr), key); + + let frame2: &[u8] = &[ + 0x41, 0xcc, 0x93, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9, + 0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x11, 0x75, 0x74, + 0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74, 0x72, 0x69, 0x73, 0x74, 0x69, + 0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6e, 0x63, 0x20, 0x65, + 0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65, 0x2e, 0x20, 0x4c, 0x6f, 0x72, + 0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72, + 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e, + 0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, + 0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, + ]; + + let ieee802154_frame = Ieee802154Frame::new_checked(frame2).unwrap(); + let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap(); + + let sixlowpan_frame = + SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap(); + + let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame { + frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap() + } else { + unreachable!() + }; + + assert_eq!(frag.datagram_size(), 307); + assert_eq!(frag.datagram_tag(), 0x003f); + assert_eq!(frag.datagram_offset(), 136 / 8); + + assert_eq!(frag.get_key(&ieee802154_repr), key); + + let frame3: &[u8] = &[ + 0x41, 0xcc, 0x94, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9, + 0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x1d, 0x2e, 0x20, + 0x41, 0x6c, 0x69, 0x71, 0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64, + 0x69, 0x6f, 0x2c, 0x20, 0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65, + 0x6c, 0x20, 0x72, 0x75, 0x74, 0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74, + 0x72, 0x69, 0x73, 0x74, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e, + 0x75, 0x6e, 0x63, 0x20, 0x65, 0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65, + 0x2e, 0x20, 0x0a, + ]; + + let ieee802154_frame = Ieee802154Frame::new_checked(frame3).unwrap(); + let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap(); + + let sixlowpan_frame = + SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap(); + + let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame { + frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap() + } else { + unreachable!() + }; + + assert_eq!(frag.datagram_size(), 307); + assert_eq!(frag.datagram_tag(), 0x003f); + assert_eq!(frag.datagram_offset(), 232 / 8); + + assert_eq!(frag.get_key(&ieee802154_repr), key); + } +} diff --git a/vendor/smoltcp/src/wire/sixlowpan/nhc.rs b/vendor/smoltcp/src/wire/sixlowpan/nhc.rs new file mode 100644 index 00000000..2ee59b57 --- /dev/null +++ b/vendor/smoltcp/src/wire/sixlowpan/nhc.rs @@ -0,0 +1,885 @@ +//! Implementation of Next Header Compression from [RFC 6282 § 4]. +//! +//! [RFC 6282 § 4]: https://datatracker.ietf.org/doc/html/rfc6282#section-4 +use super::{DISPATCH_EXT_HEADER, DISPATCH_UDP_HEADER, Error, NextHeader, Result}; +use crate::{ + phy::ChecksumCapabilities, + wire::{IpProtocol, ip::checksum, ipv6, udp::Repr as UdpRepr}, +}; +use byteorder::{ByteOrder, NetworkEndian}; +use ipv6::Address; + +macro_rules! get_field { + ($name:ident, $mask:expr, $shift:expr) => { + fn $name(&self) -> u8 { + let data = self.buffer.as_ref(); + let raw = &data[0]; + ((raw >> $shift) & $mask) as u8 + } + }; +} + +macro_rules! set_field { + ($name:ident, $mask:expr, $shift:expr) => { + fn $name(&mut self, val: u8) { + let data = self.buffer.as_mut(); + let mut raw = data[0]; + raw = (raw & !($mask << $shift)) | (val << $shift); + data[0] = raw; + } + }; +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// A read/write wrapper around a 6LoWPAN_NHC Header. +/// [RFC 6282 § 4.2] specifies the format of the header. +/// +/// The header has the following format: +/// ```txt +/// 0 1 2 3 4 5 6 7 +/// +---+---+---+---+---+---+---+---+ +/// | 1 | 1 | 1 | 0 | EID |NH | +/// +---+---+---+---+---+---+---+---+ +/// ``` +/// +/// With: +/// - EID: the extension header ID +/// - NH: Next Header +/// +/// [RFC 6282 § 4.2]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.2 +pub enum NhcPacket { + ExtHeader, + UdpHeader, +} + +impl NhcPacket { + /// Returns the type of the Next Header header. + /// This can either be an Extension header or an 6LoWPAN Udp header. + /// + /// # Errors + /// Returns `[Error::Unrecognized]` when neither the Extension Header dispatch or the Udp + /// dispatch is recognized. + pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result { + let raw = buffer.as_ref(); + if raw.is_empty() { + return Err(Error); + } + + if raw[0] >> 4 == DISPATCH_EXT_HEADER { + // We have a compressed IPv6 Extension Header. + Ok(Self::ExtHeader) + } else if raw[0] >> 3 == DISPATCH_UDP_HEADER { + // We have a compressed UDP header. + Ok(Self::UdpHeader) + } else { + Err(Error) + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ExtHeaderId { + HopByHopHeader, + RoutingHeader, + FragmentHeader, + DestinationOptionsHeader, + MobilityHeader, + Header, + Reserved, +} + +impl From for IpProtocol { + fn from(val: ExtHeaderId) -> Self { + match val { + ExtHeaderId::HopByHopHeader => Self::HopByHop, + ExtHeaderId::RoutingHeader => Self::Ipv6Route, + ExtHeaderId::FragmentHeader => Self::Ipv6Frag, + ExtHeaderId::DestinationOptionsHeader => Self::Ipv6Opts, + ExtHeaderId::MobilityHeader => Self::Unknown(0), + ExtHeaderId::Header => Self::Unknown(0), + ExtHeaderId::Reserved => Self::Unknown(0), + } + } +} + +/// A read/write wrapper around a 6LoWPAN NHC Extension header. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ExtHeaderPacket> { + buffer: T, +} + +impl> ExtHeaderPacket { + /// Input a raw octet buffer with a 6LoWPAN NHC Extension Header structure. + pub const fn new_unchecked(buffer: T) -> Self { + ExtHeaderPacket { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + + if packet.eid_field() > 7 { + return Err(Error); + } + + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let buffer = self.buffer.as_ref(); + + if buffer.is_empty() { + return Err(Error); + } + + let mut len = 2; + len += self.next_header_size(); + + if len <= buffer.len() { + Ok(()) + } else { + Err(Error) + } + } + + /// Consumes the frame, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + get_field!(dispatch_field, 0b1111, 4); + get_field!(eid_field, 0b111, 1); + get_field!(nh_field, 0b1, 0); + + /// Return the Extension Header ID. + pub fn extension_header_id(&self) -> ExtHeaderId { + match self.eid_field() { + 0 => ExtHeaderId::HopByHopHeader, + 1 => ExtHeaderId::RoutingHeader, + 2 => ExtHeaderId::FragmentHeader, + 3 => ExtHeaderId::DestinationOptionsHeader, + 4 => ExtHeaderId::MobilityHeader, + 5 | 6 => ExtHeaderId::Reserved, + 7 => ExtHeaderId::Header, + _ => unreachable!(), + } + } + + /// Return the length field. + pub fn length(&self) -> u8 { + self.buffer.as_ref()[1 + self.next_header_size()] + } + + /// Parse the next header field. + pub fn next_header(&self) -> NextHeader { + if self.nh_field() == 1 { + NextHeader::Compressed + } else { + // The full 8 bits for Next Header are carried in-line. + NextHeader::Uncompressed(IpProtocol::from(self.buffer.as_ref()[1])) + } + } + + /// Return the size of the Next Header field. + fn next_header_size(&self) -> usize { + // If nh is set, then the Next Header is compressed using LOWPAN_NHC + match self.nh_field() { + 0 => 1, + 1 => 0, + _ => unreachable!(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> ExtHeaderPacket<&'a T> { + /// Return a pointer to the payload. + pub fn payload(&self) -> &'a [u8] { + let start = 2 + self.next_header_size(); + let len = self.length() as usize; + &self.buffer.as_ref()[start..][..len] + } +} + +impl + AsMut<[u8]>> ExtHeaderPacket { + /// Return a mutable pointer to the payload. + pub fn payload_mut(&mut self) -> &mut [u8] { + let start = 2 + self.next_header_size(); + let len = self.length() as usize; + &mut self.buffer.as_mut()[start..][..len] + } + + /// Set the dispatch field to `0b1110`. + fn set_dispatch_field(&mut self) { + let data = self.buffer.as_mut(); + data[0] = (data[0] & !(0b1111 << 4)) | (DISPATCH_EXT_HEADER << 4); + } + + set_field!(set_eid_field, 0b111, 1); + set_field!(set_nh_field, 0b1, 0); + + /// Set the Extension Header ID field. + fn set_extension_header_id(&mut self, ext_header_id: ExtHeaderId) { + let id = match ext_header_id { + ExtHeaderId::HopByHopHeader => 0, + ExtHeaderId::RoutingHeader => 1, + ExtHeaderId::FragmentHeader => 2, + ExtHeaderId::DestinationOptionsHeader => 3, + ExtHeaderId::MobilityHeader => 4, + ExtHeaderId::Reserved => 5, + ExtHeaderId::Header => 7, + }; + + self.set_eid_field(id); + } + + /// Set the Next Header. + fn set_next_header(&mut self, next_header: NextHeader) { + match next_header { + NextHeader::Compressed => self.set_nh_field(0b1), + NextHeader::Uncompressed(nh) => { + self.set_nh_field(0b0); + + let start = 1; + let data = self.buffer.as_mut(); + data[start] = nh.into(); + } + } + } + + /// Set the length. + fn set_length(&mut self, length: u8) { + let start = 1 + self.next_header_size(); + + let data = self.buffer.as_mut(); + data[start] = length; + } +} + +/// A high-level representation of an 6LoWPAN NHC Extension header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ExtHeaderRepr { + pub ext_header_id: ExtHeaderId, + pub next_header: NextHeader, + pub length: u8, +} + +impl ExtHeaderRepr { + /// Parse a 6LoWPAN NHC Extension Header packet and return a high-level representation. + pub fn parse + ?Sized>(packet: &ExtHeaderPacket<&T>) -> Result { + // Ensure basic accessors will work. + packet.check_len()?; + + if packet.dispatch_field() != DISPATCH_EXT_HEADER { + return Err(Error); + } + + Ok(Self { + ext_header_id: packet.extension_header_id(), + next_header: packet.next_header(), + length: packet.length(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub fn buffer_len(&self) -> usize { + let mut len = 1; // The minimal header size + + if self.next_header != NextHeader::Compressed { + len += 1; + } + + len += 1; // The length + + len + } + + /// Emit a high-level representation into a 6LoWPAN NHC Extension Header packet. + pub fn emit + AsMut<[u8]>>(&self, packet: &mut ExtHeaderPacket) { + packet.set_dispatch_field(); + packet.set_extension_header_id(self.ext_header_id); + packet.set_next_header(self.next_header); + packet.set_length(self.length); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::wire::{Ipv6RoutingHeader, Ipv6RoutingRepr}; + + #[cfg(feature = "proto-rpl")] + use crate::wire::{ + Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, RplHopByHopRepr, RplInstanceId, + }; + + #[cfg(feature = "proto-rpl")] + const RPL_HOP_BY_HOP_PACKET: [u8; 9] = [0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00, 0x1e, 0x03, 0x00]; + + const ROUTING_SR_PACKET: [u8; 32] = [ + 0xe3, 0x1e, 0x03, 0x03, 0x99, 0x30, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0x00, + ]; + + #[test] + #[cfg(feature = "proto-rpl")] + fn test_rpl_hop_by_hop_option_deconstruct() { + let header = ExtHeaderPacket::new_checked(&RPL_HOP_BY_HOP_PACKET).unwrap(); + assert_eq!( + header.next_header(), + NextHeader::Uncompressed(IpProtocol::Icmpv6) + ); + assert_eq!(header.extension_header_id(), ExtHeaderId::HopByHopHeader); + + let options = header.payload(); + let mut options = Ipv6OptionsIterator::new(options); + let rpl_repr = options.next().unwrap(); + let rpl_repr = rpl_repr.unwrap(); + + match rpl_repr { + Ipv6OptionRepr::Rpl(rpl) => { + assert_eq!( + rpl, + RplHopByHopRepr { + down: false, + rank_error: false, + forwarding_error: false, + instance_id: RplInstanceId::from(0x1e), + sender_rank: 0x0300, + } + ); + } + _ => unreachable!(), + } + } + + #[test] + #[cfg(feature = "proto-rpl")] + fn test_rpl_hop_by_hop_option_emit() { + let repr = Ipv6OptionRepr::Rpl(RplHopByHopRepr { + down: false, + rank_error: false, + forwarding_error: false, + instance_id: RplInstanceId::from(0x1e), + sender_rank: 0x0300, + }); + + let ext_hdr = ExtHeaderRepr { + ext_header_id: ExtHeaderId::HopByHopHeader, + next_header: NextHeader::Uncompressed(IpProtocol::Icmpv6), + length: repr.buffer_len() as u8, + }; + + let mut buffer = vec![0u8; ext_hdr.buffer_len() + repr.buffer_len()]; + ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked( + &mut buffer[..ext_hdr.buffer_len()], + )); + repr.emit(&mut Ipv6Option::new_unchecked( + &mut buffer[ext_hdr.buffer_len()..], + )); + + assert_eq!(&buffer[..], RPL_HOP_BY_HOP_PACKET); + } + + #[test] + fn test_source_routing_deconstruct() { + let header = ExtHeaderPacket::new_checked(&ROUTING_SR_PACKET).unwrap(); + assert_eq!(header.next_header(), NextHeader::Compressed); + assert_eq!(header.extension_header_id(), ExtHeaderId::RoutingHeader); + + let routing_hdr = Ipv6RoutingHeader::new_checked(header.payload()).unwrap(); + let repr = Ipv6RoutingRepr::parse(&routing_hdr).unwrap(); + assert_eq!( + repr, + Ipv6RoutingRepr::Rpl { + segments_left: 3, + cmpr_i: 9, + cmpr_e: 9, + pad: 3, + addresses: &[ + 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, + 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00 + ], + } + ); + } + + #[test] + fn test_source_routing_emit() { + let routing_hdr = Ipv6RoutingRepr::Rpl { + segments_left: 3, + cmpr_i: 9, + cmpr_e: 9, + pad: 3, + addresses: &[ + 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + ], + }; + + let ext_hdr = ExtHeaderRepr { + ext_header_id: ExtHeaderId::RoutingHeader, + next_header: NextHeader::Compressed, + length: routing_hdr.buffer_len() as u8, + }; + + let mut buffer = vec![0u8; ext_hdr.buffer_len() + routing_hdr.buffer_len()]; + ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked( + &mut buffer[..ext_hdr.buffer_len()], + )); + routing_hdr.emit(&mut Ipv6RoutingHeader::new_unchecked( + &mut buffer[ext_hdr.buffer_len()..], + )); + + assert_eq!(&buffer[..], ROUTING_SR_PACKET); + } +} + +/// A read/write wrapper around a 6LoWPAN_NHC UDP frame. +/// [RFC 6282 § 4.3] specifies the format of the header. +/// +/// The base header has the following format: +/// ```txt +/// 0 1 2 3 4 5 6 7 +/// +---+---+---+---+---+---+---+---+ +/// | 1 | 1 | 1 | 1 | 0 | C | P | +/// +---+---+---+---+---+---+---+---+ +/// With: +/// - C: checksum, specifies if the checksum is elided. +/// - P: ports, specifies if the ports are elided. +/// ``` +/// +/// [RFC 6282 § 4.3]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.3 +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UdpNhcPacket> { + buffer: T, +} + +impl> UdpNhcPacket { + /// Input a raw octet buffer with a LOWPAN_NHC frame structure for UDP. + pub const fn new_unchecked(buffer: T) -> Self { + Self { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error::Truncated)` if the buffer is too short. + pub fn check_len(&self) -> Result<()> { + let buffer = self.buffer.as_ref(); + + if buffer.is_empty() { + return Err(Error); + } + + let index = 1 + self.ports_size() + self.checksum_size(); + if index > buffer.len() { + return Err(Error); + } + + Ok(()) + } + + /// Consumes the frame, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + get_field!(dispatch_field, 0b11111, 3); + get_field!(checksum_field, 0b1, 2); + get_field!(ports_field, 0b11, 0); + + /// Returns the index of the start of the next header compressed fields. + const fn nhc_fields_start(&self) -> usize { + 1 + } + + /// Return the source port number. + pub fn src_port(&self) -> u16 { + match self.ports_field() { + 0b00 | 0b01 => { + // The full 16 bits are carried in-line. + let data = self.buffer.as_ref(); + let start = self.nhc_fields_start(); + + NetworkEndian::read_u16(&data[start..start + 2]) + } + 0b10 => { + // The first 8 bits are elided. + let data = self.buffer.as_ref(); + let start = self.nhc_fields_start(); + + 0xf000 + data[start] as u16 + } + 0b11 => { + // The first 12 bits are elided. + let data = self.buffer.as_ref(); + let start = self.nhc_fields_start(); + + 0xf0b0 + (data[start] >> 4) as u16 + } + _ => unreachable!(), + } + } + + /// Return the destination port number. + pub fn dst_port(&self) -> u16 { + match self.ports_field() { + 0b00 => { + // The full 16 bits are carried in-line. + let data = self.buffer.as_ref(); + let idx = self.nhc_fields_start(); + + NetworkEndian::read_u16(&data[idx + 2..idx + 4]) + } + 0b01 => { + // The first 8 bits are elided. + let data = self.buffer.as_ref(); + let idx = self.nhc_fields_start(); + + 0xf000 + data[idx] as u16 + } + 0b10 => { + // The full 16 bits are carried in-line. + let data = self.buffer.as_ref(); + let idx = self.nhc_fields_start(); + + NetworkEndian::read_u16(&data[idx + 1..idx + 1 + 2]) + } + 0b11 => { + // The first 12 bits are elided. + let data = self.buffer.as_ref(); + let start = self.nhc_fields_start(); + + 0xf0b0 + (data[start] & 0xff) as u16 + } + _ => unreachable!(), + } + } + + /// Return the checksum. + pub fn checksum(&self) -> Option { + if self.checksum_field() == 0b0 { + // The first 12 bits are elided. + let data = self.buffer.as_ref(); + let start = self.nhc_fields_start() + self.ports_size(); + Some(NetworkEndian::read_u16(&data[start..start + 2])) + } else { + // The checksum is elided and needs to be recomputed on the 6LoWPAN termination point. + None + } + } + + // Return the size of the checksum field. + pub(crate) fn checksum_size(&self) -> usize { + match self.checksum_field() { + 0b0 => 2, + 0b1 => 0, + _ => unreachable!(), + } + } + + /// Returns the total size of both port numbers. + pub(crate) fn ports_size(&self) -> usize { + match self.ports_field() { + 0b00 => 4, // 16 bits + 16 bits + 0b01 => 3, // 16 bits + 8 bits + 0b10 => 3, // 8 bits + 16 bits + 0b11 => 1, // 4 bits + 4 bits + _ => unreachable!(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> UdpNhcPacket<&'a T> { + /// Return a pointer to the payload. + pub fn payload(&self) -> &'a [u8] { + let start = 1 + self.ports_size() + self.checksum_size(); + &self.buffer.as_ref()[start..] + } +} + +impl + AsMut<[u8]>> UdpNhcPacket { + /// Return a mutable pointer to the payload. + pub fn payload_mut(&mut self) -> &mut [u8] { + let start = 1 + self.ports_size() + 2; // XXX(thvdveld): we assume we put the checksum inlined. + &mut self.buffer.as_mut()[start..] + } + + /// Set the dispatch field to `0b11110`. + fn set_dispatch_field(&mut self) { + let data = self.buffer.as_mut(); + data[0] = (data[0] & !(0b11111 << 3)) | (DISPATCH_UDP_HEADER << 3); + } + + set_field!(set_checksum_field, 0b1, 2); + set_field!(set_ports_field, 0b11, 0); + + fn set_ports(&mut self, src_port: u16, dst_port: u16) { + let mut idx = 1; + + match (src_port, dst_port) { + (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => { + // We can compress both the source and destination ports. + self.set_ports_field(0b11); + let data = self.buffer.as_mut(); + data[idx] = (((src_port - 0xf0b0) as u8) << 4) & ((dst_port - 0xf0b0) as u8); + } + (0xf000..=0xf0ff, _) => { + // We can compress the source port, but not the destination port. + self.set_ports_field(0b10); + let data = self.buffer.as_mut(); + data[idx] = (src_port - 0xf000) as u8; + idx += 1; + + NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port); + } + (_, 0xf000..=0xf0ff) => { + // We can compress the destination port, but not the source port. + self.set_ports_field(0b01); + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port); + idx += 2; + data[idx] = (dst_port - 0xf000) as u8; + } + (_, _) => { + // We cannot compress any port. + self.set_ports_field(0b00); + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port); + idx += 2; + NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port); + } + }; + } + + fn set_checksum(&mut self, checksum: u16) { + self.set_checksum_field(0b0); + let idx = 1 + self.ports_size(); + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[idx..idx + 2], checksum); + } +} + +/// A high-level representation of a 6LoWPAN NHC UDP header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UdpNhcRepr(pub UdpRepr); + +impl<'a> UdpNhcRepr { + /// Parse a 6LoWPAN NHC UDP packet and return a high-level representation. + pub fn parse + ?Sized>( + packet: &UdpNhcPacket<&'a T>, + src_addr: &ipv6::Address, + dst_addr: &ipv6::Address, + checksum_caps: &ChecksumCapabilities, + ) -> Result { + packet.check_len()?; + + if packet.dispatch_field() != DISPATCH_UDP_HEADER { + return Err(Error); + } + + if checksum_caps.udp.rx() { + let payload_len = packet.payload().len(); + let chk_sum = !checksum::combine(&[ + checksum::pseudo_header_v6( + src_addr, + dst_addr, + crate::wire::ip::Protocol::Udp, + payload_len as u32 + 8, + ), + packet.src_port(), + packet.dst_port(), + payload_len as u16 + 8, + checksum::data(packet.payload()), + ]); + + if let Some(checksum) = packet.checksum() { + if chk_sum != checksum { + return Err(Error); + } + } + } + + Ok(Self(UdpRepr { + src_port: packet.src_port(), + dst_port: packet.dst_port(), + })) + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub fn header_len(&self) -> usize { + let mut len = 1; // The minimal header size + + len += 2; // XXX We assume we will add the checksum at the end + + // Check if we can compress the source and destination ports + match (self.src_port, self.dst_port) { + (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => len + 1, + (0xf000..=0xf0ff, _) | (_, 0xf000..=0xf0ff) => len + 3, + (_, _) => len + 4, + } + } + + /// Emit a high-level representation into a LOWPAN_NHC UDP header. + pub fn emit + AsMut<[u8]>>( + &self, + packet: &mut UdpNhcPacket, + src_addr: &Address, + dst_addr: &Address, + payload_len: usize, + emit_payload: impl FnOnce(&mut [u8]), + checksum_caps: &ChecksumCapabilities, + ) { + packet.set_dispatch_field(); + packet.set_ports(self.src_port, self.dst_port); + emit_payload(packet.payload_mut()); + + if checksum_caps.udp.tx() { + let chk_sum = !checksum::combine(&[ + checksum::pseudo_header_v6( + src_addr, + dst_addr, + crate::wire::ip::Protocol::Udp, + payload_len as u32 + 8, + ), + self.src_port, + self.dst_port, + payload_len as u16 + 8, + checksum::data(packet.payload_mut()), + ]); + + packet.set_checksum(chk_sum); + } + } +} + +impl core::ops::Deref for UdpNhcRepr { + type Target = UdpRepr; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for UdpNhcRepr { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn ext_header_nh_inlined() { + let bytes = [0xe2, 0x3a, 0x6, 0x3, 0x0, 0xff, 0x0, 0x0, 0x0]; + + let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap(); + assert_eq!(packet.next_header_size(), 1); + assert_eq!(packet.length(), 6); + assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER); + assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader); + assert_eq!( + packet.next_header(), + NextHeader::Uncompressed(IpProtocol::Icmpv6) + ); + + assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]); + } + + #[test] + fn ext_header_nh_elided() { + let bytes = [0xe3, 0x06, 0x03, 0x00, 0xff, 0x00, 0x00, 0x00]; + + let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap(); + assert_eq!(packet.next_header_size(), 0); + assert_eq!(packet.length(), 6); + assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER); + assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader); + assert_eq!(packet.next_header(), NextHeader::Compressed); + + assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]); + } + + #[test] + fn ext_header_emit() { + let ext_header = ExtHeaderRepr { + ext_header_id: ExtHeaderId::RoutingHeader, + next_header: NextHeader::Compressed, + length: 6, + }; + + let len = ext_header.buffer_len(); + let mut buffer = [0u8; 127]; + let mut packet = ExtHeaderPacket::new_unchecked(&mut buffer[..len]); + ext_header.emit(&mut packet); + + assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER); + assert_eq!(packet.next_header(), NextHeader::Compressed); + assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader); + } + + #[test] + fn udp_nhc_fields() { + let bytes = [0xf0, 0x16, 0x2e, 0x22, 0x3d, 0x28, 0xc4]; + + let packet = UdpNhcPacket::new_checked(&bytes[..]).unwrap(); + assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER); + assert_eq!(packet.checksum(), Some(0x28c4)); + assert_eq!(packet.src_port(), 5678); + assert_eq!(packet.dst_port(), 8765); + } + + #[test] + fn udp_emit() { + let udp = UdpNhcRepr(UdpRepr { + src_port: 0xf0b1, + dst_port: 0xf001, + }); + + let payload = b"Hello World!"; + + let src_addr = ipv6::Address::UNSPECIFIED; + let dst_addr = ipv6::Address::UNSPECIFIED; + + let len = udp.header_len() + payload.len(); + let mut buffer = [0u8; 127]; + let mut packet = UdpNhcPacket::new_unchecked(&mut buffer[..len]); + udp.emit( + &mut packet, + &src_addr, + &dst_addr, + payload.len(), + |buf| buf.copy_from_slice(&payload[..]), + &ChecksumCapabilities::default(), + ); + + assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER); + assert_eq!(packet.src_port(), 0xf0b1); + assert_eq!(packet.dst_port(), 0xf001); + assert_eq!(packet.payload_mut(), b"Hello World!"); + } +} diff --git a/vendor/smoltcp/src/wire/tcp.rs b/vendor/smoltcp/src/wire/tcp.rs new file mode 100644 index 00000000..ad347b17 --- /dev/null +++ b/vendor/smoltcp/src/wire/tcp.rs @@ -0,0 +1,1415 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::{cmp, fmt, ops}; + +use super::{Error, Result}; +use crate::phy::ChecksumCapabilities; +use crate::wire::ip::checksum; +use crate::wire::{IpAddress, IpProtocol}; + +/// A TCP sequence number. +/// +/// A sequence number is a monotonically advancing integer modulo 232. +/// Sequence numbers do not have a discontiguity when compared pairwise across a signed overflow. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct SeqNumber(pub i32); + +impl SeqNumber { + pub fn max(self, rhs: Self) -> Self { + if self > rhs { self } else { rhs } + } + + pub fn min(self, rhs: Self) -> Self { + if self < rhs { self } else { rhs } + } +} + +impl fmt::Display for SeqNumber { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0 as u32) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for SeqNumber { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{}", self.0 as u32); + } +} + +impl ops::Add for SeqNumber { + type Output = SeqNumber; + + fn add(self, rhs: usize) -> SeqNumber { + if rhs > i32::MAX as usize { + panic!("attempt to add to sequence number with unsigned overflow") + } + SeqNumber(self.0.wrapping_add(rhs as i32)) + } +} + +impl ops::Sub for SeqNumber { + type Output = SeqNumber; + + fn sub(self, rhs: usize) -> SeqNumber { + if rhs > i32::MAX as usize { + panic!("attempt to subtract to sequence number with unsigned overflow") + } + SeqNumber(self.0.wrapping_sub(rhs as i32)) + } +} + +impl ops::AddAssign for SeqNumber { + fn add_assign(&mut self, rhs: usize) { + *self = *self + rhs; + } +} + +impl ops::Sub for SeqNumber { + type Output = usize; + + fn sub(self, rhs: SeqNumber) -> usize { + let result = self.0.wrapping_sub(rhs.0); + if result < 0 { + panic!("attempt to subtract sequence numbers with underflow") + } + result as usize + } +} + +impl cmp::PartialOrd for SeqNumber { + fn partial_cmp(&self, other: &SeqNumber) -> Option { + self.0.wrapping_sub(other.0).partial_cmp(&0) + } +} + +/// A read/write wrapper around a Transmission Control Protocol packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet> { + buffer: T, +} + +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + pub const SRC_PORT: Field = 0..2; + pub const DST_PORT: Field = 2..4; + pub const SEQ_NUM: Field = 4..8; + pub const ACK_NUM: Field = 8..12; + pub const FLAGS: Field = 12..14; + pub const WIN_SIZE: Field = 14..16; + pub const CHECKSUM: Field = 16..18; + pub const URGENT: Field = 18..20; + + pub const fn OPTIONS(length: u8) -> Field { + URGENT.end..(length as usize) + } + + pub const FLG_FIN: u16 = 0x001; + pub const FLG_SYN: u16 = 0x002; + pub const FLG_RST: u16 = 0x004; + pub const FLG_PSH: u16 = 0x008; + pub const FLG_ACK: u16 = 0x010; + pub const FLG_URG: u16 = 0x020; + pub const FLG_ECE: u16 = 0x040; + pub const FLG_CWR: u16 = 0x080; + pub const FLG_NS: u16 = 0x100; + + pub const OPT_END: u8 = 0x00; + pub const OPT_NOP: u8 = 0x01; + pub const OPT_MSS: u8 = 0x02; + pub const OPT_WS: u8 = 0x03; + pub const OPT_SACKPERM: u8 = 0x04; + pub const OPT_SACKRNG: u8 = 0x05; + pub const OPT_TSTAMP: u8 = 0x08; +} + +pub const HEADER_LEN: usize = field::URGENT.end; + +impl> Packet { + /// Imbue a raw octet buffer with TCP packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// Returns `Err(Error)` if the header length field has a value smaller + /// than the minimal header length. + /// + /// The result of this check is invalidated by calling [set_header_len]. + /// + /// [set_header_len]: #method.set_header_len + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::URGENT.end { + Err(Error) + } else { + let header_len = self.header_len() as usize; + if len < header_len || header_len < field::URGENT.end { + Err(Error) + } else { + Ok(()) + } + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the source port field. + #[inline] + pub fn src_port(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::SRC_PORT]) + } + + /// Return the destination port field. + #[inline] + pub fn dst_port(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::DST_PORT]) + } + + /// Return the sequence number field. + #[inline] + pub fn seq_number(&self) -> SeqNumber { + let data = self.buffer.as_ref(); + SeqNumber(NetworkEndian::read_i32(&data[field::SEQ_NUM])) + } + + /// Return the acknowledgement number field. + #[inline] + pub fn ack_number(&self) -> SeqNumber { + let data = self.buffer.as_ref(); + SeqNumber(NetworkEndian::read_i32(&data[field::ACK_NUM])) + } + + /// Return the FIN flag. + #[inline] + pub fn fin(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_FIN != 0 + } + + /// Return the SYN flag. + #[inline] + pub fn syn(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_SYN != 0 + } + + /// Return the RST flag. + #[inline] + pub fn rst(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_RST != 0 + } + + /// Return the PSH flag. + #[inline] + pub fn psh(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_PSH != 0 + } + + /// Return the ACK flag. + #[inline] + pub fn ack(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_ACK != 0 + } + + /// Return the URG flag. + #[inline] + pub fn urg(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_URG != 0 + } + + /// Return the ECE flag. + #[inline] + pub fn ece(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_ECE != 0 + } + + /// Return the CWR flag. + #[inline] + pub fn cwr(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_CWR != 0 + } + + /// Return the NS flag. + #[inline] + pub fn ns(&self) -> bool { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + raw & field::FLG_NS != 0 + } + + /// Return the header length, in octets. + #[inline] + pub fn header_len(&self) -> u8 { + let data = self.buffer.as_ref(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + ((raw >> 12) * 4) as u8 + } + + /// Return the window size field. + #[inline] + pub fn window_len(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::WIN_SIZE]) + } + + /// Return the checksum field. + #[inline] + pub fn checksum(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::CHECKSUM]) + } + + /// Return the urgent pointer field. + #[inline] + pub fn urgent_at(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::URGENT]) + } + + /// Return the length of the segment, in terms of sequence space. + pub fn segment_len(&self) -> usize { + let data = self.buffer.as_ref(); + let mut length = data.len() - self.header_len() as usize; + if self.syn() { + length += 1 + } + if self.fin() { + length += 1 + } + length + } + + /// Returns whether the selective acknowledgement SYN flag is set or not. + pub fn selective_ack_permitted(&self) -> Result { + let data = self.buffer.as_ref(); + let mut options = &data[field::OPTIONS(self.header_len())]; + while !options.is_empty() { + let (next_options, option) = TcpOption::parse(options)?; + if option == TcpOption::SackPermitted { + return Ok(true); + } + options = next_options; + } + Ok(false) + } + + /// Return the selective acknowledgement ranges, if any. If there are none in the packet, an + /// array of ``None`` values will be returned. + /// + pub fn selective_ack_ranges(&self) -> Result<[Option<(u32, u32)>; 3]> { + let data = self.buffer.as_ref(); + let mut options = &data[field::OPTIONS(self.header_len())]; + while !options.is_empty() { + let (next_options, option) = TcpOption::parse(options)?; + if let TcpOption::SackRange(slice) = option { + return Ok(slice); + } + options = next_options; + } + Ok([None, None, None]) + } + + /// Validate the partial checksum. + /// + /// # Panics + /// This function panics unless `src_addr` and `dst_addr` belong to the same family, + /// and that family is IPv4 or IPv6. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_partial_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool { + if cfg!(fuzzing) { + return true; + } + + let data = self.buffer.as_ref(); + + checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32) + == self.checksum() + } + + /// Validate the packet checksum. + /// + /// # Panics + /// This function panics unless `src_addr` and `dst_addr` belong to the same family, + /// and that family is IPv4 or IPv6. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool { + if cfg!(fuzzing) { + return true; + } + + let data = self.buffer.as_ref(); + checksum::combine(&[ + checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32), + checksum::data(data), + ]) == !0 + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the options. + #[inline] + pub fn options(&self) -> &'a [u8] { + let header_len = self.header_len(); + let data = self.buffer.as_ref(); + &data[field::OPTIONS(header_len)] + } + + /// Return a pointer to the payload. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let header_len = self.header_len() as usize; + let data = self.buffer.as_ref(); + &data[header_len..] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the source port field. + #[inline] + pub fn set_src_port(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::SRC_PORT], value) + } + + /// Set the destination port field. + #[inline] + pub fn set_dst_port(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::DST_PORT], value) + } + + /// Set the sequence number field. + #[inline] + pub fn set_seq_number(&mut self, value: SeqNumber) { + let data = self.buffer.as_mut(); + NetworkEndian::write_i32(&mut data[field::SEQ_NUM], value.0) + } + + /// Set the acknowledgement number field. + #[inline] + pub fn set_ack_number(&mut self, value: SeqNumber) { + let data = self.buffer.as_mut(); + NetworkEndian::write_i32(&mut data[field::ACK_NUM], value.0) + } + + /// Clear the entire flags field. + #[inline] + pub fn clear_flags(&mut self) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = raw & !0x0fff; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the FIN flag. + #[inline] + pub fn set_fin(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_FIN + } else { + raw & !field::FLG_FIN + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the SYN flag. + #[inline] + pub fn set_syn(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_SYN + } else { + raw & !field::FLG_SYN + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the RST flag. + #[inline] + pub fn set_rst(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_RST + } else { + raw & !field::FLG_RST + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the PSH flag. + #[inline] + pub fn set_psh(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_PSH + } else { + raw & !field::FLG_PSH + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the ACK flag. + #[inline] + pub fn set_ack(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_ACK + } else { + raw & !field::FLG_ACK + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the URG flag. + #[inline] + pub fn set_urg(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_URG + } else { + raw & !field::FLG_URG + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the ECE flag. + #[inline] + pub fn set_ece(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_ECE + } else { + raw & !field::FLG_ECE + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the CWR flag. + #[inline] + pub fn set_cwr(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_CWR + } else { + raw & !field::FLG_CWR + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the NS flag. + #[inline] + pub fn set_ns(&mut self, value: bool) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = if value { + raw | field::FLG_NS + } else { + raw & !field::FLG_NS + }; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the header length, in octets. + #[inline] + pub fn set_header_len(&mut self, value: u8) { + let data = self.buffer.as_mut(); + let raw = NetworkEndian::read_u16(&data[field::FLAGS]); + let raw = (raw & !0xf000) | ((value as u16) / 4) << 12; + NetworkEndian::write_u16(&mut data[field::FLAGS], raw) + } + + /// Set the window size field. + #[inline] + pub fn set_window_len(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::WIN_SIZE], value) + } + + /// Set the checksum field. + #[inline] + pub fn set_checksum(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) + } + + /// Set the urgent pointer field. + #[inline] + pub fn set_urgent_at(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::URGENT], value) + } + + /// Compute and fill in the header checksum. + /// + /// # Panics + /// This function panics unless `src_addr` and `dst_addr` belong to the same family, + /// and that family is IPv4 or IPv6. + pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) { + self.set_checksum(0); + let checksum = { + let data = self.buffer.as_ref(); + !checksum::combine(&[ + checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Tcp, data.len() as u32), + checksum::data(data), + ]) + }; + self.set_checksum(checksum) + } + + /// Return a pointer to the options. + #[inline] + pub fn options_mut(&mut self) -> &mut [u8] { + let header_len = self.header_len(); + let data = self.buffer.as_mut(); + &mut data[field::OPTIONS(header_len)] + } + + /// Return a mutable pointer to the payload data. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let header_len = self.header_len() as usize; + let data = self.buffer.as_mut(); + &mut data[header_len..] + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A representation of a single TCP option. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TcpOption<'a> { + EndOfList, + NoOperation, + MaxSegmentSize(u16), + WindowScale(u8), + SackPermitted, + SackRange([Option<(u32, u32)>; 3]), + TimeStamp { tsval: u32, tsecr: u32 }, + Unknown { kind: u8, data: &'a [u8] }, +} + +impl<'a> TcpOption<'a> { + pub fn parse(buffer: &'a [u8]) -> Result<(&'a [u8], TcpOption<'a>)> { + let (length, option); + match *buffer.first().ok_or(Error)? { + field::OPT_END => { + length = 1; + option = TcpOption::EndOfList; + } + field::OPT_NOP => { + length = 1; + option = TcpOption::NoOperation; + } + kind => { + length = *buffer.get(1).ok_or(Error)? as usize; + let data = buffer.get(2..length).ok_or(Error)?; + match (kind, length) { + (field::OPT_END, _) | (field::OPT_NOP, _) => unreachable!(), + (field::OPT_MSS, 4) => { + option = TcpOption::MaxSegmentSize(NetworkEndian::read_u16(data)) + } + (field::OPT_MSS, _) => return Err(Error), + (field::OPT_WS, 3) => option = TcpOption::WindowScale(data[0]), + (field::OPT_WS, _) => return Err(Error), + (field::OPT_SACKPERM, 2) => option = TcpOption::SackPermitted, + (field::OPT_SACKPERM, _) => return Err(Error), + (field::OPT_SACKRNG, n) => { + if n < 10 || (n - 2) % 8 != 0 { + return Err(Error); + } + if n > 26 { + // It's possible for a remote to send 4 SACK blocks, but extremely rare. + // Better to "lose" that 4th block and save the extra RAM and CPU + // cycles in the vastly more common case. + // + // RFC 2018: SACK option that specifies n blocks will have a length of + // 8*n+2 bytes, so the 40 bytes available for TCP options can specify a + // maximum of 4 blocks. It is expected that SACK will often be used in + // conjunction with the Timestamp option used for RTTM [...] thus a + // maximum of 3 SACK blocks will be allowed in this case. + net_debug!("sACK with >3 blocks, truncating to 3"); + } + let mut sack_ranges: [Option<(u32, u32)>; 3] = [None; 3]; + + // RFC 2018: Each contiguous block of data queued at the data receiver is + // defined in the SACK option by two 32-bit unsigned integers in network + // byte order[...] + sack_ranges.iter_mut().enumerate().for_each(|(i, nmut)| { + let left = i * 8; + *nmut = if left < data.len() { + let mid = left + 4; + let right = mid + 4; + let range_left = NetworkEndian::read_u32(&data[left..mid]); + let range_right = NetworkEndian::read_u32(&data[mid..right]); + Some((range_left, range_right)) + } else { + None + }; + }); + option = TcpOption::SackRange(sack_ranges); + } + (field::OPT_TSTAMP, 10) => { + let tsval = NetworkEndian::read_u32(&data[0..4]); + let tsecr = NetworkEndian::read_u32(&data[4..8]); + option = TcpOption::TimeStamp { tsval, tsecr }; + } + (_, _) => option = TcpOption::Unknown { kind, data }, + } + } + } + Ok((&buffer[length..], option)) + } + + pub fn buffer_len(&self) -> usize { + match *self { + TcpOption::EndOfList => 1, + TcpOption::NoOperation => 1, + TcpOption::MaxSegmentSize(_) => 4, + TcpOption::WindowScale(_) => 3, + TcpOption::SackPermitted => 2, + TcpOption::SackRange(s) => s.iter().filter(|s| s.is_some()).count() * 8 + 2, + TcpOption::TimeStamp { tsval: _, tsecr: _ } => 10, + TcpOption::Unknown { data, .. } => 2 + data.len(), + } + } + + pub fn emit<'b>(&self, buffer: &'b mut [u8]) -> &'b mut [u8] { + let length; + match *self { + TcpOption::EndOfList => { + length = 1; + // There may be padding space which also should be initialized. + for p in buffer.iter_mut() { + *p = field::OPT_END; + } + } + TcpOption::NoOperation => { + length = 1; + buffer[0] = field::OPT_NOP; + } + _ => { + length = self.buffer_len(); + buffer[1] = length as u8; + match self { + &TcpOption::EndOfList | &TcpOption::NoOperation => unreachable!(), + &TcpOption::MaxSegmentSize(value) => { + buffer[0] = field::OPT_MSS; + NetworkEndian::write_u16(&mut buffer[2..], value) + } + &TcpOption::WindowScale(value) => { + buffer[0] = field::OPT_WS; + buffer[2] = value; + } + &TcpOption::SackPermitted => { + buffer[0] = field::OPT_SACKPERM; + } + &TcpOption::SackRange(slice) => { + buffer[0] = field::OPT_SACKRNG; + slice + .iter() + .filter(|s| s.is_some()) + .enumerate() + .for_each(|(i, s)| { + let (first, second) = *s.as_ref().unwrap(); + let pos = i * 8 + 2; + NetworkEndian::write_u32(&mut buffer[pos..], first); + NetworkEndian::write_u32(&mut buffer[pos + 4..], second); + }); + } + &TcpOption::TimeStamp { tsval, tsecr } => { + buffer[0] = field::OPT_TSTAMP; + NetworkEndian::write_u32(&mut buffer[2..], tsval); + NetworkEndian::write_u32(&mut buffer[6..], tsecr); + } + &TcpOption::Unknown { + kind, + data: provided, + } => { + buffer[0] = kind; + buffer[2..].copy_from_slice(provided) + } + } + } + } + &mut buffer[length..] + } +} + +/// The possible control flags of a Transmission Control Protocol packet. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Control { + None, + Psh, + Syn, + Fin, + Rst, +} + +#[allow(clippy::len_without_is_empty)] +impl Control { + /// Return the length of a control flag, in terms of sequence space. + pub const fn len(self) -> usize { + match self { + Control::Syn | Control::Fin => 1, + _ => 0, + } + } + + /// Turn the PSH flag into no flag, and keep the rest as-is. + pub const fn quash_psh(self) -> Control { + match self { + Control::Psh => Control::None, + _ => self, + } + } +} + +/// A high-level representation of a Transmission Control Protocol packet. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Repr<'a> { + pub src_port: u16, + pub dst_port: u16, + pub control: Control, + pub seq_number: SeqNumber, + pub ack_number: Option, + pub window_len: u16, + pub window_scale: Option, + pub max_seg_size: Option, + pub sack_permitted: bool, + pub sack_ranges: [Option<(u32, u32)>; 3], + pub timestamp: Option, + pub payload: &'a [u8], +} + +pub type TcpTimestampGenerator = fn() -> u32; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct TcpTimestampRepr { + pub tsval: u32, + pub tsecr: u32, +} + +impl TcpTimestampRepr { + pub fn new(tsval: u32, tsecr: u32) -> Self { + Self { tsval, tsecr } + } + + pub fn generate_reply(&self, generator: Option) -> Option { + Self::generate_reply_with_tsval(generator, self.tsval) + } + + pub fn generate_reply_with_tsval( + generator: Option, + tsval: u32, + ) -> Option { + Some(Self::new(generator?(), tsval)) + } +} + +impl<'a> Repr<'a> { + /// Parse a Transmission Control Protocol packet and return a high-level representation. + pub fn parse( + packet: &Packet<&'a T>, + src_addr: &IpAddress, + dst_addr: &IpAddress, + checksum_caps: &ChecksumCapabilities, + ) -> Result> + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + + // Source and destination ports must be present. + if packet.src_port() == 0 { + return Err(Error); + } + if packet.dst_port() == 0 { + return Err(Error); + } + // Valid checksum is expected. + if checksum_caps.tcp.rx() && !packet.verify_checksum(src_addr, dst_addr) { + return Err(Error); + } + + let control = match (packet.syn(), packet.fin(), packet.rst(), packet.psh()) { + (false, false, false, false) => Control::None, + (false, false, false, true) => Control::Psh, + (true, false, false, _) => Control::Syn, + (false, true, false, _) => Control::Fin, + (false, false, true, _) => Control::Rst, + _ => return Err(Error), + }; + let ack_number = match packet.ack() { + true => Some(packet.ack_number()), + false => None, + }; + // The PSH flag is ignored. + // The URG flag and the urgent field is ignored. This behavior is standards-compliant, + // however, most deployed systems (e.g. Linux) are *not* standards-compliant, and would + // cut the byte at the urgent pointer from the stream. + + let mut max_seg_size = None; + let mut window_scale = None; + let mut options = packet.options(); + let mut sack_permitted = false; + let mut sack_ranges = [None, None, None]; + let mut timestamp = None; + while !options.is_empty() { + let (next_options, option) = TcpOption::parse(options)?; + match option { + TcpOption::EndOfList => break, + TcpOption::NoOperation => (), + TcpOption::MaxSegmentSize(value) => max_seg_size = Some(value), + TcpOption::WindowScale(value) => { + // RFC 1323: Thus, the shift count must be limited to 14 (which allows windows + // of 2**30 = 1 Gigabyte). If a Window Scale option is received with a shift.cnt + // value exceeding 14, the TCP should log the error but use 14 instead of the + // specified value. + window_scale = if value > 14 { + net_debug!( + "{}:{}:{}:{}: parsed window scaling factor >14, setting to 14", + src_addr, + packet.src_port(), + dst_addr, + packet.dst_port() + ); + Some(14) + } else { + Some(value) + }; + } + TcpOption::SackPermitted => sack_permitted = true, + TcpOption::SackRange(slice) => sack_ranges = slice, + TcpOption::TimeStamp { tsval, tsecr } => { + timestamp = Some(TcpTimestampRepr::new(tsval, tsecr)); + } + _ => (), + } + options = next_options; + } + + Ok(Repr { + src_port: packet.src_port(), + dst_port: packet.dst_port(), + control: control, + seq_number: packet.seq_number(), + ack_number: ack_number, + window_len: packet.window_len(), + window_scale: window_scale, + max_seg_size: max_seg_size, + sack_permitted: sack_permitted, + sack_ranges: sack_ranges, + timestamp: timestamp, + payload: packet.payload(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + /// + /// This should be used for buffer space calculations. + /// The TCP header length is a multiple of 4. + pub fn header_len(&self) -> usize { + let mut length = field::URGENT.end; + if self.max_seg_size.is_some() { + length += 4 + } + if self.window_scale.is_some() { + length += 3 + } + if self.sack_permitted { + length += 2; + } + if self.timestamp.is_some() { + length += 10; + } + let sack_range_len: usize = self + .sack_ranges + .iter() + .map(|o| o.map(|_| 8).unwrap_or(0)) + .sum(); + if sack_range_len > 0 { + length += sack_range_len + 2; + } + if length % 4 != 0 { + length += 4 - length % 4; + } + length + } + + /// Return the length of a packet that will be emitted from this high-level representation. + pub fn buffer_len(&self) -> usize { + self.header_len() + self.payload.len() + } + + /// Emit a high-level representation into a Transmission Control Protocol packet. + pub fn emit( + &self, + packet: &mut Packet<&mut T>, + src_addr: &IpAddress, + dst_addr: &IpAddress, + checksum_caps: &ChecksumCapabilities, + ) where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + packet.set_src_port(self.src_port); + packet.set_dst_port(self.dst_port); + packet.set_seq_number(self.seq_number); + packet.set_ack_number(self.ack_number.unwrap_or(SeqNumber(0))); + packet.set_window_len(self.window_len); + packet.set_header_len(self.header_len() as u8); + packet.clear_flags(); + match self.control { + Control::None => (), + Control::Psh => packet.set_psh(true), + Control::Syn => packet.set_syn(true), + Control::Fin => packet.set_fin(true), + Control::Rst => packet.set_rst(true), + } + packet.set_ack(self.ack_number.is_some()); + { + let mut options = packet.options_mut(); + if let Some(value) = self.max_seg_size { + let tmp = options; + options = TcpOption::MaxSegmentSize(value).emit(tmp); + } + if let Some(value) = self.window_scale { + let tmp = options; + options = TcpOption::WindowScale(value).emit(tmp); + } + if self.sack_permitted { + let tmp = options; + options = TcpOption::SackPermitted.emit(tmp); + } else if self.ack_number.is_some() && self.sack_ranges.iter().any(|s| s.is_some()) { + let tmp = options; + options = TcpOption::SackRange(self.sack_ranges).emit(tmp); + } + if let Some(timestamp) = self.timestamp { + let tmp = options; + options = TcpOption::TimeStamp { + tsval: timestamp.tsval, + tsecr: timestamp.tsecr, + } + .emit(tmp); + } + + if !options.is_empty() { + TcpOption::EndOfList.emit(options); + } + } + packet.set_urgent_at(0); + packet.payload_mut()[..self.payload.len()].copy_from_slice(self.payload); + + if checksum_caps.tcp.tx() { + packet.fill_checksum(src_addr, dst_addr) + } else { + // make sure we get a consistently zeroed checksum, + // since implementations might rely on it + packet.set_checksum(0); + } + } + + /// Return the length of the segment, in terms of sequence space. + pub const fn segment_len(&self) -> usize { + self.payload.len() + self.control.len() + } + + /// Return whether the segment has no flags set (except PSH) and no data. + pub const fn is_empty(&self) -> bool { + match self.control { + _ if !self.payload.is_empty() => false, + Control::Syn | Control::Fin | Control::Rst => false, + Control::None | Control::Psh => true, + } + } +} + +impl + ?Sized> fmt::Display for Packet<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Cannot use Repr::parse because we don't have the IP addresses. + write!(f, "TCP src={} dst={}", self.src_port(), self.dst_port())?; + if self.syn() { + write!(f, " syn")? + } + if self.fin() { + write!(f, " fin")? + } + if self.rst() { + write!(f, " rst")? + } + if self.psh() { + write!(f, " psh")? + } + if self.ece() { + write!(f, " ece")? + } + if self.cwr() { + write!(f, " cwr")? + } + if self.ns() { + write!(f, " ns")? + } + write!(f, " seq={}", self.seq_number())?; + if self.ack() { + write!(f, " ack={}", self.ack_number())?; + } + write!(f, " win={}", self.window_len())?; + if self.urg() { + write!(f, " urg={}", self.urgent_at())?; + } + write!(f, " len={}", self.payload().len())?; + + let mut options = self.options(); + while !options.is_empty() { + let (next_options, option) = match TcpOption::parse(options) { + Ok(res) => res, + Err(err) => return write!(f, " ({err})"), + }; + match option { + TcpOption::EndOfList => break, + TcpOption::NoOperation => (), + TcpOption::MaxSegmentSize(value) => write!(f, " mss={value}")?, + TcpOption::WindowScale(value) => write!(f, " ws={value}")?, + TcpOption::SackPermitted => write!(f, " sACK")?, + TcpOption::SackRange(slice) => write!(f, " sACKr{slice:?}")?, // debug print conveniently includes the []s + TcpOption::TimeStamp { tsval, tsecr } => { + write!(f, " tsval {tsval:08x} tsecr {tsecr:08x}")? + } + TcpOption::Unknown { kind, .. } => write!(f, " opt({kind})")?, + } + options = next_options; + } + Ok(()) + } +} + +impl<'a> fmt::Display for Repr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TCP src={} dst={}", self.src_port, self.dst_port)?; + match self.control { + Control::Syn => write!(f, " syn")?, + Control::Fin => write!(f, " fin")?, + Control::Rst => write!(f, " rst")?, + Control::Psh => write!(f, " psh")?, + Control::None => (), + } + write!(f, " seq={}", self.seq_number)?; + if let Some(ack_number) = self.ack_number { + write!(f, " ack={ack_number}")?; + } + write!(f, " win={}", self.window_len)?; + write!(f, " len={}", self.payload.len())?; + if let Some(max_seg_size) = self.max_seg_size { + write!(f, " mss={max_seg_size}")?; + } + Ok(()) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Repr<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "TCP src={} dst={}", self.src_port, self.dst_port); + match self.control { + Control::Syn => defmt::write!(fmt, " syn"), + Control::Fin => defmt::write!(fmt, " fin"), + Control::Rst => defmt::write!(fmt, " rst"), + Control::Psh => defmt::write!(fmt, " psh"), + Control::None => (), + } + defmt::write!(fmt, " seq={}", self.seq_number); + if let Some(ack_number) = self.ack_number { + defmt::write!(fmt, " ack={}", ack_number); + } + defmt::write!(fmt, " win={}", self.window_len); + defmt::write!(fmt, " len={}", self.payload.len()); + if let Some(max_seg_size) = self.max_seg_size { + defmt::write!(fmt, " mss={}", max_seg_size); + } + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + match Packet::new_checked(buffer) { + Err(err) => write!(f, "{indent}({err})"), + Ok(packet) => write!(f, "{indent}{packet}"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + #[cfg(feature = "proto-ipv4")] + use crate::wire::Ipv4Address; + + #[cfg(feature = "proto-ipv4")] + const SRC_ADDR: Ipv4Address = Ipv4Address::new(192, 168, 1, 1); + #[cfg(feature = "proto-ipv4")] + const DST_ADDR: Ipv4Address = Ipv4Address::new(192, 168, 1, 2); + + #[cfg(feature = "proto-ipv4")] + static PACKET_BYTES: [u8; 28] = [ + 0xbf, 0x00, 0x00, 0x50, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x60, 0x35, 0x01, + 0x23, 0x01, 0xb6, 0x02, 0x01, 0x03, 0x03, 0x0c, 0x01, 0xaa, 0x00, 0x00, 0xff, + ]; + + #[cfg(feature = "proto-ipv4")] + static OPTION_BYTES: [u8; 4] = [0x03, 0x03, 0x0c, 0x01]; + + #[cfg(feature = "proto-ipv4")] + static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(packet.src_port(), 48896); + assert_eq!(packet.dst_port(), 80); + assert_eq!(packet.seq_number(), SeqNumber(0x01234567)); + assert_eq!(packet.ack_number(), SeqNumber(0x89abcdefu32 as i32)); + assert_eq!(packet.header_len(), 24); + assert!(packet.fin()); + assert!(!packet.syn()); + assert!(packet.rst()); + assert!(!packet.psh()); + assert!(packet.ack()); + assert!(packet.urg()); + assert_eq!(packet.window_len(), 0x0123); + assert_eq!(packet.urgent_at(), 0x0201); + assert_eq!(packet.checksum(), 0x01b6); + assert_eq!(packet.options(), &OPTION_BYTES[..]); + assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]); + assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into())); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_construct() { + let mut bytes = vec![0xa5; PACKET_BYTES.len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_src_port(48896); + packet.set_dst_port(80); + packet.set_seq_number(SeqNumber(0x01234567)); + packet.set_ack_number(SeqNumber(0x89abcdefu32 as i32)); + packet.set_header_len(24); + packet.clear_flags(); + packet.set_fin(true); + packet.set_syn(false); + packet.set_rst(true); + packet.set_psh(false); + packet.set_ack(true); + packet.set_urg(true); + packet.set_window_len(0x0123); + packet.set_urgent_at(0x0201); + packet.set_checksum(0xEEEE); + packet.options_mut().copy_from_slice(&OPTION_BYTES[..]); + packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); + packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into()); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_truncated() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..23]); + assert_eq!(packet.check_len(), Err(Error)); + } + + #[test] + fn test_impossible_len() { + let mut bytes = vec![0; 20]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_header_len(10); + assert_eq!(packet.check_len(), Err(Error)); + } + + #[cfg(feature = "proto-ipv4")] + static SYN_PACKET_BYTES: [u8; 24] = [ + 0xbf, 0x00, 0x00, 0x50, 0x01, 0x23, 0x45, 0x67, 0x00, 0x00, 0x00, 0x00, 0x50, 0x02, 0x01, + 0x23, 0x7a, 0x8d, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff, + ]; + + #[cfg(feature = "proto-ipv4")] + fn packet_repr() -> Repr<'static> { + Repr { + src_port: 48896, + dst_port: 80, + seq_number: SeqNumber(0x01234567), + ack_number: None, + window_len: 0x0123, + window_scale: None, + control: Control::Syn, + max_seg_size: None, + sack_permitted: false, + sack_ranges: [None, None, None], + timestamp: None, + payload: &PAYLOAD_BYTES, + } + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_parse() { + let packet = Packet::new_unchecked(&SYN_PACKET_BYTES[..]); + let repr = Repr::parse( + &packet, + &SRC_ADDR.into(), + &DST_ADDR.into(), + &ChecksumCapabilities::default(), + ) + .unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_emit() { + let repr = packet_repr(); + let mut bytes = vec![0xa5; repr.buffer_len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit( + &mut packet, + &SRC_ADDR.into(), + &DST_ADDR.into(), + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &SYN_PACKET_BYTES[..]); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_header_len_multiple_of_4() { + let mut repr = packet_repr(); + repr.window_scale = Some(0); // This TCP Option needs 3 bytes. + assert_eq!(repr.header_len() % 4, 0); // Should e.g. be 28 instead of 27. + } + + macro_rules! assert_option_parses { + ($opt:expr, $data:expr) => {{ + assert_eq!(TcpOption::parse($data), Ok((&[][..], $opt))); + let buffer = &mut [0; 40][..$opt.buffer_len()]; + assert_eq!($opt.emit(buffer), &mut []); + assert_eq!(&*buffer, $data); + }}; + } + + #[test] + fn test_tcp_options() { + assert_option_parses!(TcpOption::EndOfList, &[0x00]); + assert_option_parses!(TcpOption::NoOperation, &[0x01]); + assert_option_parses!(TcpOption::MaxSegmentSize(1500), &[0x02, 0x04, 0x05, 0xdc]); + assert_option_parses!(TcpOption::WindowScale(12), &[0x03, 0x03, 0x0c]); + assert_option_parses!(TcpOption::SackPermitted, &[0x4, 0x02]); + assert_option_parses!( + TcpOption::SackRange([Some((500, 1500)), None, None]), + &[0x05, 0x0a, 0x00, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x05, 0xdc] + ); + assert_option_parses!( + TcpOption::SackRange([Some((875, 1225)), Some((1500, 2500)), None]), + &[ + 0x05, 0x12, 0x00, 0x00, 0x03, 0x6b, 0x00, 0x00, 0x04, 0xc9, 0x00, 0x00, 0x05, 0xdc, + 0x00, 0x00, 0x09, 0xc4 + ] + ); + assert_option_parses!( + TcpOption::SackRange([ + Some((875000, 1225000)), + Some((1500000, 2500000)), + Some((876543210, 876654320)) + ]), + &[ + 0x05, 0x1a, 0x00, 0x0d, 0x59, 0xf8, 0x00, 0x12, 0xb1, 0x28, 0x00, 0x16, 0xe3, 0x60, + 0x00, 0x26, 0x25, 0xa0, 0x34, 0x3e, 0xfc, 0xea, 0x34, 0x40, 0xae, 0xf0 + ] + ); + assert_option_parses!( + TcpOption::TimeStamp { + tsval: 5000000, + tsecr: 7000000 + }, + &[ + 0x08, // data length + 0x0a, // type + 0x00, 0x4c, 0x4b, 0x40, //tsval + 0x00, 0x6a, 0xcf, 0xc0 //tsecr + ] + ); + assert_option_parses!( + TcpOption::Unknown { + kind: 12, + data: &[1, 2, 3][..] + }, + &[0x0c, 0x05, 0x01, 0x02, 0x03] + ) + } + + #[test] + fn test_malformed_tcp_options() { + assert_eq!(TcpOption::parse(&[]), Err(Error)); + assert_eq!(TcpOption::parse(&[0xc]), Err(Error)); + assert_eq!(TcpOption::parse(&[0xc, 0x05, 0x01, 0x02]), Err(Error)); + assert_eq!(TcpOption::parse(&[0xc, 0x01]), Err(Error)); + assert_eq!(TcpOption::parse(&[0x2, 0x02]), Err(Error)); + assert_eq!(TcpOption::parse(&[0x3, 0x02]), Err(Error)); + } +} diff --git a/vendor/smoltcp/src/wire/udp.rs b/vendor/smoltcp/src/wire/udp.rs new file mode 100644 index 00000000..27f61750 --- /dev/null +++ b/vendor/smoltcp/src/wire/udp.rs @@ -0,0 +1,501 @@ +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use crate::phy::ChecksumCapabilities; +use crate::wire::ip::checksum; +use crate::wire::{IpAddress, IpProtocol}; + +/// A read/write wrapper around an User Datagram Protocol packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Packet> { + buffer: T, +} + +mod field { + #![allow(non_snake_case)] + + use crate::wire::field::*; + + pub const SRC_PORT: Field = 0..2; + pub const DST_PORT: Field = 2..4; + pub const LENGTH: Field = 4..6; + pub const CHECKSUM: Field = 6..8; + + pub const fn PAYLOAD(length: u16) -> Field { + CHECKSUM.end..(length as usize) + } +} + +pub const HEADER_LEN: usize = field::CHECKSUM.end; + +#[allow(clippy::len_without_is_empty)] +impl> Packet { + /// Imbue a raw octet buffer with UDP packet structure. + pub const fn new_unchecked(buffer: T) -> Packet { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + pub fn new_checked(buffer: T) -> Result> { + let packet = Self::new_unchecked(buffer); + packet.check_len()?; + Ok(packet) + } + + /// Ensure that no accessor method will panic if called. + /// Returns `Err(Error)` if the buffer is too short. + /// Returns `Err(Error)` if the length field has a value smaller + /// than the header length. + /// + /// The result of this check is invalidated by calling [set_len]. + /// + /// [set_len]: #method.set_len + pub fn check_len(&self) -> Result<()> { + let buffer_len = self.buffer.as_ref().len(); + if buffer_len < HEADER_LEN { + Err(Error) + } else { + let field_len = self.len() as usize; + if buffer_len < field_len || field_len < HEADER_LEN { + Err(Error) + } else { + Ok(()) + } + } + } + + /// Consume the packet, returning the underlying buffer. + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the source port field. + #[inline] + pub fn src_port(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::SRC_PORT]) + } + + /// Return the destination port field. + #[inline] + pub fn dst_port(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::DST_PORT]) + } + + /// Return the length field. + #[inline] + pub fn len(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::LENGTH]) + } + + /// Return the checksum field. + #[inline] + pub fn checksum(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::CHECKSUM]) + } + + /// Validate the partial packet checksum. + /// + /// # Panics + /// This function panics unless `src_addr` and `dst_addr` belong to the same family, + /// and that family is IPv4 or IPv6. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_partial_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool { + if cfg!(fuzzing) { + return true; + } + + checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32) + == self.checksum() + } + + /// Validate the packet checksum. + /// + /// # Panics + /// This function panics unless `src_addr` and `dst_addr` belong to the same family, + /// and that family is IPv4 or IPv6. + /// + /// # Fuzzing + /// This function always returns `true` when fuzzing. + pub fn verify_checksum(&self, src_addr: &IpAddress, dst_addr: &IpAddress) -> bool { + if cfg!(fuzzing) { + return true; + } + + // From the RFC: + // > An all zero transmitted checksum value means that the transmitter + // > generated no checksum (for debugging or for higher level protocols + // > that don't care). + if self.checksum() == 0 { + return true; + } + + let data = self.buffer.as_ref(); + checksum::combine(&[ + checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32), + checksum::data(&data[..self.len() as usize]), + ]) == !0 + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the payload. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let length = self.len(); + let data = self.buffer.as_ref(); + &data[field::PAYLOAD(length)] + } +} + +impl + AsMut<[u8]>> Packet { + /// Set the source port field. + #[inline] + pub fn set_src_port(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::SRC_PORT], value) + } + + /// Set the destination port field. + #[inline] + pub fn set_dst_port(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::DST_PORT], value) + } + + /// Set the length field. + #[inline] + pub fn set_len(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::LENGTH], value) + } + + /// Set the checksum field. + #[inline] + pub fn set_checksum(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::CHECKSUM], value) + } + + /// Compute and fill in the header checksum. + /// + /// # Panics + /// This function panics unless `src_addr` and `dst_addr` belong to the same family, + /// and that family is IPv4 or IPv6. + pub fn fill_checksum(&mut self, src_addr: &IpAddress, dst_addr: &IpAddress) { + self.set_checksum(0); + let checksum = { + let data = self.buffer.as_ref(); + !checksum::combine(&[ + checksum::pseudo_header(src_addr, dst_addr, IpProtocol::Udp, self.len() as u32), + checksum::data(&data[..self.len() as usize]), + ]) + }; + // UDP checksum value of 0 means no checksum; if the checksum really is zero, + // use all-ones, which indicates that the remote end must verify the checksum. + // Arithmetically, RFC 1071 checksums of all-zeroes and all-ones behave identically, + // so no action is necessary on the remote end. + self.set_checksum(if checksum == 0 { 0xffff } else { checksum }) + } + + /// Return a mutable pointer to the payload. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let length = self.len(); + let data = self.buffer.as_mut(); + &mut data[field::PAYLOAD(length)] + } +} + +impl> AsRef<[u8]> for Packet { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an User Datagram Protocol packet. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Repr { + pub src_port: u16, + pub dst_port: u16, +} + +impl Repr { + /// Parse an User Datagram Protocol packet and return a high-level representation. + pub fn parse( + packet: &Packet<&T>, + src_addr: &IpAddress, + dst_addr: &IpAddress, + checksum_caps: &ChecksumCapabilities, + ) -> Result + where + T: AsRef<[u8]> + ?Sized, + { + packet.check_len()?; + + // Destination port cannot be omitted (but source port can be). + if packet.dst_port() == 0 { + return Err(Error); + } + // Valid checksum is expected... + if checksum_caps.udp.rx() && !packet.verify_checksum(src_addr, dst_addr) { + match (src_addr, dst_addr) { + // ... except on UDP-over-IPv4, where it can be omitted. + #[cfg(feature = "proto-ipv4")] + (&IpAddress::Ipv4(_), &IpAddress::Ipv4(_)) if packet.checksum() == 0 => (), + _ => return Err(Error), + } + } + + Ok(Repr { + src_port: packet.src_port(), + dst_port: packet.dst_port(), + }) + } + + /// Return the length of the packet header that will be emitted from this high-level representation. + pub const fn header_len(&self) -> usize { + HEADER_LEN + } + + /// Emit a high-level representation into an User Datagram Protocol packet. + /// + /// This never calculates the checksum, and is intended for internal-use only, + /// not for packets that are going to be actually sent over the network. For + /// example, when decompressing 6lowpan. + pub(crate) fn emit_header(&self, packet: &mut Packet<&mut T>, payload_len: usize) + where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + packet.set_src_port(self.src_port); + packet.set_dst_port(self.dst_port); + packet.set_len((HEADER_LEN + payload_len) as u16); + packet.set_checksum(0); + } + + /// Emit a high-level representation into an User Datagram Protocol packet. + pub fn emit( + &self, + packet: &mut Packet<&mut T>, + src_addr: &IpAddress, + dst_addr: &IpAddress, + payload_len: usize, + emit_payload: impl FnOnce(&mut [u8]), + checksum_caps: &ChecksumCapabilities, + ) where + T: AsRef<[u8]> + AsMut<[u8]> + ?Sized, + { + packet.set_src_port(self.src_port); + packet.set_dst_port(self.dst_port); + packet.set_len((HEADER_LEN + payload_len) as u16); + emit_payload(packet.payload_mut()); + + if checksum_caps.udp.tx() { + packet.fill_checksum(src_addr, dst_addr) + } else { + // make sure we get a consistently zeroed checksum, + // since implementations might rely on it + packet.set_checksum(0); + } + } +} + +impl + ?Sized> fmt::Display for Packet<&T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Cannot use Repr::parse because we don't have the IP addresses. + write!( + f, + "UDP src={} dst={} len={}", + self.src_port(), + self.dst_port(), + self.payload().len() + ) + } +} + +#[cfg(feature = "defmt")] +impl<'a, T: AsRef<[u8]> + ?Sized> defmt::Format for Packet<&'a T> { + fn format(&self, fmt: defmt::Formatter) { + // Cannot use Repr::parse because we don't have the IP addresses. + defmt::write!( + fmt, + "UDP src={} dst={} len={}", + self.src_port(), + self.dst_port(), + self.payload().len() + ); + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "UDP src={} dst={}", self.src_port, self.dst_port) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Repr { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "UDP src={} dst={}", self.src_port, self.dst_port); + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +impl> PrettyPrint for Packet { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + match Packet::new_checked(buffer) { + Err(err) => write!(f, "{indent}({err})"), + Ok(packet) => write!(f, "{indent}{packet}"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + #[cfg(feature = "proto-ipv4")] + use crate::wire::Ipv4Address; + + #[cfg(feature = "proto-ipv4")] + const SRC_ADDR: Ipv4Address = Ipv4Address::new(192, 168, 1, 1); + #[cfg(feature = "proto-ipv4")] + const DST_ADDR: Ipv4Address = Ipv4Address::new(192, 168, 1, 2); + + #[cfg(feature = "proto-ipv4")] + static PACKET_BYTES: [u8; 12] = [ + 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x12, 0x4d, 0xaa, 0x00, 0x00, 0xff, + ]; + + #[cfg(feature = "proto-ipv4")] + static NO_CHECKSUM_PACKET: [u8; 12] = [ + 0xbf, 0x00, 0x00, 0x35, 0x00, 0x0c, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xff, + ]; + + #[cfg(feature = "proto-ipv4")] + static PAYLOAD_BYTES: [u8; 4] = [0xaa, 0x00, 0x00, 0xff]; + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_deconstruct() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + assert_eq!(packet.src_port(), 48896); + assert_eq!(packet.dst_port(), 53); + assert_eq!(packet.len(), 12); + assert_eq!(packet.checksum(), 0x124d); + assert_eq!(packet.payload(), &PAYLOAD_BYTES[..]); + assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into())); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_construct() { + let mut bytes = vec![0xa5; 12]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_src_port(48896); + packet.set_dst_port(53); + packet.set_len(12); + packet.set_checksum(0xffff); + packet.payload_mut().copy_from_slice(&PAYLOAD_BYTES[..]); + packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into()); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } + + #[test] + fn test_impossible_len() { + let mut bytes = vec![0; 12]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_len(4); + assert_eq!(packet.check_len(), Err(Error)); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_zero_checksum() { + let mut bytes = vec![0; 8]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_src_port(1); + packet.set_dst_port(31881); + packet.set_len(8); + packet.fill_checksum(&SRC_ADDR.into(), &DST_ADDR.into()); + assert_eq!(packet.checksum(), 0xffff); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_no_checksum() { + let mut bytes = vec![0; 8]; + let mut packet = Packet::new_unchecked(&mut bytes); + packet.set_src_port(1); + packet.set_dst_port(31881); + packet.set_len(8); + packet.set_checksum(0); + assert!(packet.verify_checksum(&SRC_ADDR.into(), &DST_ADDR.into())); + } + + #[cfg(feature = "proto-ipv4")] + fn packet_repr() -> Repr { + Repr { + src_port: 48896, + dst_port: 53, + } + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_parse() { + let packet = Packet::new_unchecked(&PACKET_BYTES[..]); + let repr = Repr::parse( + &packet, + &SRC_ADDR.into(), + &DST_ADDR.into(), + &ChecksumCapabilities::default(), + ) + .unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_emit() { + let repr = packet_repr(); + let mut bytes = vec![0xa5; repr.header_len() + PAYLOAD_BYTES.len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit( + &mut packet, + &SRC_ADDR.into(), + &DST_ADDR.into(), + PAYLOAD_BYTES.len(), + |payload| payload.copy_from_slice(&PAYLOAD_BYTES), + &ChecksumCapabilities::default(), + ); + assert_eq!(&*packet.into_inner(), &PACKET_BYTES[..]); + } + + #[test] + #[cfg(feature = "proto-ipv4")] + fn test_checksum_omitted() { + let packet = Packet::new_unchecked(&NO_CHECKSUM_PACKET[..]); + let repr = Repr::parse( + &packet, + &SRC_ADDR.into(), + &DST_ADDR.into(), + &ChecksumCapabilities::default(), + ) + .unwrap(); + assert_eq!(repr, packet_repr()); + } +} diff --git a/vendor/smoltcp/tests/netsim.rs b/vendor/smoltcp/tests/netsim.rs new file mode 100644 index 00000000..3f37e283 --- /dev/null +++ b/vendor/smoltcp/tests/netsim.rs @@ -0,0 +1,364 @@ +use std::cell::RefCell; +use std::collections::BinaryHeap; +use std::fmt::Write as _; +use std::io::Write as _; +use std::sync::Mutex; + +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy::Tracer; +use smoltcp::phy::{self, ChecksumCapabilities, Device, DeviceCapabilities, Medium}; +use smoltcp::socket::tcp; +use smoltcp::time::{Duration, Instant}; +use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress, IpCidr}; + +const MAC_A: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([2, 0, 0, 0, 0, 1])); +const MAC_B: HardwareAddress = HardwareAddress::Ethernet(EthernetAddress([2, 0, 0, 0, 0, 2])); +const IP_A: IpAddress = IpAddress::v4(10, 0, 0, 1); +const IP_B: IpAddress = IpAddress::v4(10, 0, 0, 2); + +const BYTES: usize = 10 * 1024 * 1024; + +static CLOCK: Mutex<(Instant, char)> = Mutex::new((Instant::ZERO, ' ')); + +#[test] +fn netsim() { + setup_logging(); + + let buffers = [128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]; + let losses = [0.0, 0.001, 0.01, 0.02, 0.05, 0.10, 0.20, 0.30]; + + let mut s = String::new(); + + write!(&mut s, "buf\\loss").unwrap(); + for loss in losses { + write!(&mut s, "{loss:9.3} ").unwrap(); + } + writeln!(&mut s).unwrap(); + + for buffer in buffers { + write!(&mut s, "{buffer:7}").unwrap(); + for loss in losses { + let r = run_test(TestCase { + rtt: Duration::from_millis(100), + buffer, + loss, + }); + write!(&mut s, " {r:9.2}").unwrap(); + } + writeln!(&mut s).unwrap(); + } + + insta::assert_snapshot!(s); +} + +struct TestCase { + rtt: Duration, + loss: f64, + buffer: usize, +} + +fn run_test(case: TestCase) -> f64 { + let mut time = Instant::ZERO; + + let params = QueueParams { + latency: case.rtt / 2, + loss: case.loss, + }; + let queue_a_to_b = RefCell::new(PacketQueue::new(params.clone(), 0)); + let queue_b_to_a = RefCell::new(PacketQueue::new(params.clone(), 1)); + let device_a = QueueDevice::new(&queue_a_to_b, &queue_b_to_a, Medium::Ethernet); + let device_b = QueueDevice::new(&queue_b_to_a, &queue_a_to_b, Medium::Ethernet); + + let mut device_a = Tracer::new(device_a, |_timestamp, _printer| log::trace!("{}", _printer)); + let mut device_b = Tracer::new(device_b, |_timestamp, _printer| log::trace!("{}", _printer)); + + let mut iface_a = Interface::new(Config::new(MAC_A), &mut device_a, time); + iface_a.update_ip_addrs(|a| a.push(IpCidr::new(IP_A, 8)).unwrap()); + let mut iface_b = Interface::new(Config::new(MAC_B), &mut device_b, time); + iface_b.update_ip_addrs(|a| a.push(IpCidr::new(IP_B, 8)).unwrap()); + + // Create sockets + let socket_a = { + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; case.buffer]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; case.buffer]); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let socket_b = { + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; case.buffer]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; case.buffer]); + tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + }; + + let mut sockets_a: [_; 2] = Default::default(); + let mut sockets_a = SocketSet::new(&mut sockets_a[..]); + let socket_a_handle = sockets_a.add(socket_a); + + let mut sockets_b: [_; 2] = Default::default(); + let mut sockets_b = SocketSet::new(&mut sockets_b[..]); + let socket_b_handle = sockets_b.add(socket_b); + + let mut did_listen = false; + let mut did_connect = false; + let mut processed = 0; + while processed < BYTES { + *CLOCK.lock().unwrap() = (time, ' '); + log::info!("loop"); + //println!("t = {}", time); + + *CLOCK.lock().unwrap() = (time, 'A'); + + iface_a.poll(time, &mut device_a, &mut sockets_a); + + let socket = sockets_a.get_mut::(socket_a_handle); + if !socket.is_active() && !socket.is_listening() && !did_listen { + //println!("listening"); + socket.listen(1234).unwrap(); + did_listen = true; + } + + while socket.can_recv() { + let received = socket.recv(|buffer| (buffer.len(), buffer.len())).unwrap(); + //println!("got {:?}", received,); + processed += received; + } + + *CLOCK.lock().unwrap() = (time, 'B'); + iface_b.poll(time, &mut device_b, &mut sockets_b); + let socket = sockets_b.get_mut::(socket_b_handle); + let cx = iface_b.context(); + if !socket.is_open() && !did_connect { + //println!("connecting"); + socket.connect(cx, (IP_A, 1234), 65000).unwrap(); + did_connect = true; + } + + while socket.can_send() { + //println!("sending"); + socket.send(|buffer| (buffer.len(), ())).unwrap(); + } + + *CLOCK.lock().unwrap() = (time, ' '); + + let mut next_time = queue_a_to_b.borrow_mut().next_expiration(); + next_time = next_time.min(queue_b_to_a.borrow_mut().next_expiration()); + if let Some(t) = iface_a.poll_at(time, &sockets_a) { + next_time = next_time.min(t); + } + if let Some(t) = iface_b.poll_at(time, &sockets_b) { + next_time = next_time.min(t); + } + assert!(next_time.total_micros() != i64::MAX); + time = time.max(next_time); + } + + let duration = time - Instant::ZERO; + processed as f64 / duration.total_micros() as f64 * 1e6 +} + +struct Packet { + timestamp: Instant, + id: u64, + data: Vec, +} + +impl PartialEq for Packet { + fn eq(&self, other: &Self) -> bool { + (other.timestamp, other.id) == (self.timestamp, self.id) + } +} + +impl Eq for Packet {} + +impl PartialOrd for Packet { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Packet { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + (other.timestamp, other.id).cmp(&(self.timestamp, self.id)) + } +} + +#[derive(Clone)] +struct QueueParams { + latency: Duration, + loss: f64, +} + +struct PacketQueue { + queue: BinaryHeap, + next_id: u64, + params: QueueParams, + rng: ChaCha20Rng, +} + +impl PacketQueue { + pub fn new(params: QueueParams, seed: u64) -> Self { + Self { + queue: BinaryHeap::new(), + next_id: 0, + params, + rng: ChaCha20Rng::seed_from_u64(seed), + } + } + + pub fn next_expiration(&self) -> Instant { + self.queue + .peek() + .map(|p| p.timestamp) + .unwrap_or(Instant::from_micros(i64::MAX)) + } + + pub fn push(&mut self, data: Vec, timestamp: Instant) { + if self.rng.r#gen::() < self.params.loss { + log::info!("PACKET LOST!"); + return; + } + + self.queue.push(Packet { + data, + id: self.next_id, + timestamp: timestamp + self.params.latency, + }); + self.next_id += 1; + } + + pub fn pop(&mut self, timestamp: Instant) -> Option> { + let p = self.queue.peek()?; + if p.timestamp > timestamp { + return None; + } + Some(self.queue.pop().unwrap().data) + } +} + +pub struct QueueDevice<'a> { + tx_queue: &'a RefCell, + rx_queue: &'a RefCell, + medium: Medium, +} + +impl<'a> QueueDevice<'a> { + fn new( + tx_queue: &'a RefCell, + rx_queue: &'a RefCell, + medium: Medium, + ) -> Self { + Self { + tx_queue, + rx_queue, + medium, + } + } +} + +impl Device for QueueDevice<'_> { + type RxToken<'a> + = RxToken + where + Self: 'a; + type TxToken<'a> + = TxToken<'a> + where + Self: 'a; + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; + caps.medium = self.medium; + caps.checksum = ChecksumCapabilities::ignored(); + caps + } + + fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.rx_queue + .borrow_mut() + .pop(timestamp) + .map(move |buffer| { + let rx = RxToken { buffer }; + let tx = TxToken { + queue: self.tx_queue, + timestamp, + }; + (rx, tx) + }) + } + + fn transmit(&mut self, timestamp: Instant) -> Option> { + Some(TxToken { + queue: self.tx_queue, + timestamp, + }) + } +} + +pub struct RxToken { + buffer: Vec, +} + +impl phy::RxToken for RxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(&self.buffer) + } +} + +pub struct TxToken<'a> { + queue: &'a RefCell, + timestamp: Instant, +} + +impl<'a> phy::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + self.queue.borrow_mut().push(buffer, self.timestamp); + result + } +} + +pub fn setup_logging() { + env_logger::Builder::new() + .format(move |buf, record| { + let (elapsed, side) = *CLOCK.lock().unwrap(); + + let timestamp = format!("[{elapsed} {side}]"); + if record.target().starts_with("smoltcp::") { + writeln!( + buf, + "{} ({}): {}", + timestamp, + record.target().replace("smoltcp::", ""), + record.args() + ) + } else if record.level() == log::Level::Trace { + let message = format!("{}", record.args()); + writeln!( + buf, + "{} {}", + timestamp, + message.replace('\n', "\n ") + ) + } else { + writeln!( + buf, + "{} ({}): {}", + timestamp, + record.target(), + record.args() + ) + } + }) + .parse_env("RUST_LOG") + .init(); +} diff --git a/vendor/smoltcp/tests/snapshots/netsim__netsim.snap b/vendor/smoltcp/tests/snapshots/netsim__netsim.snap new file mode 100644 index 00000000..533fd37d --- /dev/null +++ b/vendor/smoltcp/tests/snapshots/netsim__netsim.snap @@ -0,0 +1,15 @@ +--- +source: tests/netsim.rs +expression: s +snapshot_kind: text +--- +buf\loss 0.000 0.001 0.010 0.020 0.050 0.100 0.200 0.300 + 128 1279.98 1255.76 1054.15 886.36 538.66 227.84 33.99 7.18 + 256 2559.91 2507.27 2100.03 1770.30 1070.71 468.24 66.71 14.35 + 512 5119.63 5011.95 4172.36 3531.57 2098.73 942.38 144.73 29.45 + 1024 10238.50 10023.19 8340.90 7084.25 4003.34 1869.94 290.74 60.92 + 2048 17535.11 17171.82 14093.50 12063.90 7205.27 3379.12 824.76 131.54 + 4096 35062.41 33852.31 27011.08 22073.09 13680.70 7631.11 1617.81 302.65 + 8192 77374.28 72409.99 58428.68 48310.75 29123.30 14314.36 2880.39 551.60 + 16384 161842.28 159448.56 141467.31 127073.06 78239.08 39828.70 6729.92 1225.58 + 32768 322944.88 314313.90 266384.37 245985.29 138762.29 74125.53 8032.12 2112.09 diff --git a/vendor/smoltcp/utils/packet2pcap.rs b/vendor/smoltcp/utils/packet2pcap.rs new file mode 100644 index 00000000..7d06c6f1 --- /dev/null +++ b/vendor/smoltcp/utils/packet2pcap.rs @@ -0,0 +1,74 @@ +use getopts::Options; +use smoltcp::phy::{PcapLinkType, PcapSink}; +use smoltcp::time::Instant; +use std::env; +use std::fs::File; +use std::io::{self, Read}; +use std::path::Path; +use std::process::exit; + +fn convert( + packet_filename: &Path, + pcap_filename: &Path, + link_type: PcapLinkType, +) -> io::Result<()> { + let mut packet_file = File::open(packet_filename)?; + let mut packet = Vec::new(); + packet_file.read_to_end(&mut packet)?; + + let mut pcap_file = File::create(pcap_filename)?; + PcapSink::global_header(&mut pcap_file, link_type); + PcapSink::packet(&mut pcap_file, Instant::from_millis(0), &packet[..]); + + Ok(()) +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {program} [options] INPUT OUTPUT"); + print!("{}", opts.usage(&brief)); +} + +fn main() { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optflag("h", "help", "print this help menu"); + opts.optopt( + "t", + "link-type", + "set link type (one of: ethernet ip)", + "TYPE", + ); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + let link_type = match matches.opt_str("t").as_ref().map(|s| &s[..]) { + Some("ethernet") => Some(PcapLinkType::Ethernet), + Some("ip") => Some(PcapLinkType::Ip), + _ => None, + }; + + if matches.opt_present("h") || matches.free.len() != 2 || link_type.is_none() { + print_usage(&program, opts); + return; + } + + match convert( + Path::new(&matches.free[0]), + Path::new(&matches.free[1]), + link_type.unwrap(), + ) { + Ok(()) => (), + Err(e) => { + eprintln!("Cannot convert packet to pcap: {e}"); + exit(1); + } + } +}