26 Commits
develop ... 2.0

Author SHA1 Message Date
kaczmarczyck
7ab1b568fb Updated README to reflect maintenance status (#660)
* Updated README to reflect maintenance status

This PR is to be merged after the branches are reworked.

* FIDO -> CTAP

* Wording change to default branch
2023-11-09 14:28:39 +01:00
kaczmarczyck
c85a01579f Updates the metadata to MDS 3 (#654) 2023-11-08 11:05:32 +01:00
kaczmarczyck
bc0b30bc9e Removes broken workflows (#656)
We have problems compiling, and therefore workflows are deleted.
2023-11-08 10:05:46 +01:00
kaczmarczyck
5b67f3f9a8 Adds Bibtex for our paper to the README (#646)
* Adds Bibtex for our paper to the README

* Reference to proceedings instead of eprint

* Add date to News section
2023-08-24 12:51:56 +02:00
kaczmarczyck
244a199316 Links PQC work on stable README (#638) 2023-07-14 15:50:07 +02:00
Jean-Michel Picod
1b70583243 Fix nRF52 boards with bootloader (#560)
Backporting patch from develop branch which saves and restores
the bootloader parameters in case we need to erase UICR at startup.
2022-10-18 17:31:10 +02:00
kaczmarczyck
3b9274e93e updates libfido to version 1 (#558) 2022-10-11 01:36:02 +02:00
kaczmarczyck
a969faaaa0 Submit Cargo.lock (#555)
* submits Cargo.lock files

* removes the unused test runner from libtock-rs for less depepdencies
2022-10-10 15:34:05 +02:00
kaczmarczyck
a79abc209a uses latest in workflows (#528) 2022-08-24 15:16:46 +02:00
kaczmarczyck
b396fc0f36 Set bumpalo version for crypto library (#533) 2022-08-24 15:16:46 +02:00
kaczmarczyck
f2496a8e6d Ported documentation (#413)
* ported documentation from develop

* adapted instructions

* fix broken English
2021-11-19 17:55:12 +01:00
kaczmarczyck
e4d82087a8 Fix desktop tests for bugfix and stable (#395)
* fix build and lint problems

* fix coveralls workflow by setting a working toolchain
2021-11-19 17:55:12 +01:00
Fabian Kaczmarczyck
c847e7060a use f-strings and test with 3.9 2021-11-19 17:55:12 +01:00
Minjun
52343ed86f Added one python package required by one of the tockloader dependencies; (#376)
Signed-off-by: minjun <xi.minjun@gmail.com>
2021-11-19 17:55:12 +01:00
kaczmarczyck
57ffafaa24 Update bugfix with changes from stable (#377)
* Add Feitian OpenSK USB Dongle (#257) (#258)

Co-authored-by: superskybird <skybird.le@gmail.com>

Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>

* Bugfix (#304)

* Add Feitian OpenSK USB Dongle (#257)

Co-authored-by: superskybird <skybird.le@gmail.com>

* Fix `config.py` tool according to the new API of fido2 python package (#284)

* Fix fido2 API update.

Since fido2 0.8.1 the device descriptor moved to NamedTuple, breaking
our configuration tool.
Code is now updated accordingly and the setup script ensure we're
using the correct version for fido2 package.

* Make Yapf happy

* Fix missing update for fido2 0.9.1

Also split the comment into 2 lines so that the touch is not hidden
at the end of the screen.

* adds README changes, logo and certificate (#285)

Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>
Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>

* Compare all timestamps in UTC timezone. (#309)

* Merge bugfix into stable (#324)

* Add Feitian OpenSK USB Dongle (#257)

Co-authored-by: superskybird <skybird.le@gmail.com>

* Fix `config.py` tool according to the new API of fido2 python package (#284)

* Fix fido2 API update.

Since fido2 0.8.1 the device descriptor moved to NamedTuple, breaking
our configuration tool.
Code is now updated accordingly and the setup script ensure we're
using the correct version for fido2 package.

* Make Yapf happy

* Fix missing update for fido2 0.9.1

Also split the comment into 2 lines so that the touch is not hidden
at the end of the screen.

* adds README changes, logo and certificate (#285)

* Fix broken parsing. (#317)

* Fix broken parsing.

By setting the default value before pre-parsing we ensure that the item
can't be None. As an extra safety the custom action also checks for
None.

Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>
Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>

* Coveralls workflow applied also to stable (#342)

* Coveralls (#339)

* Add code coverage report as part of the workflows

* Remove -Clink-dead-code which seems to be problematic

* Manually set features to avoid debug_* failing unit tests.

* Update badges

* Add libraries directory to trigger code coverage reporting.

* Fix coveralls badge not pointing to the branch

* Badges to stable branch

* adds and links new security policy

* Add erase_storage application example (#352)

* Fix coveralls workflow (#356)

* Return error instead of debug assert (#363)

With dirty storage we hit the assert. Returning an error permits to continue to
catch if the invariant is broken for normal operation while being able to
continue fuzzing with dirty storage.

* Remove elf2tab dev-dependency (#366)

We don't use it anymore. Not sure when we used to use it.

Fixes #364

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>

* Install Rust tools with stable compiler

We only need the frozen nightly for Tock (and maybe the app).

* fix python lint with encoding, see commit 7418196

* more encoding

Co-authored-by: Jean-Michel Picod <jmichel@google.com>
Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>
Co-authored-by: Julien Cretin <cretin@google.com>
2021-11-19 17:55:12 +01:00
Julien Cretin
420f062a21 Install Rust tools with stable compiler
We only need the frozen nightly for Tock (and maybe the app).
2021-09-09 07:04:24 +02:00
Julien Cretin
e02eaa2cac Remove elf2tab dev-dependency (#366)
We don't use it anymore. Not sure when we used to use it.

Fixes #364

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2021-08-10 13:20:33 +02:00
Julien Cretin
5c7df89198 Return error instead of debug assert (#363)
With dirty storage we hit the assert. Returning an error permits to continue to
catch if the invariant is broken for normal operation while being able to
continue fuzzing with dirty storage.
2021-08-10 13:09:22 +02:00
Jean-Michel Picod
42050f96af Fix coveralls workflow (#356) 2021-08-02 18:05:07 +02:00
Julien Cretin
40d6040d41 Add erase_storage application example (#352) 2021-08-02 09:51:57 +02:00
Fabian Kaczmarczyck
eb65c4f07f adds and links new security policy 2021-07-09 13:05:18 +02:00
Jean-Michel Picod
2de660ae4d Coveralls workflow applied also to stable (#342)
* Coveralls (#339)

* Add code coverage report as part of the workflows

* Remove -Clink-dead-code which seems to be problematic

* Manually set features to avoid debug_* failing unit tests.

* Update badges

* Add libraries directory to trigger code coverage reporting.

* Fix coveralls badge not pointing to the branch

* Badges to stable branch
2021-07-06 11:22:05 +02:00
Jean-Michel Picod
7e5e2a665f Merge bugfix into stable (#324)
* Add Feitian OpenSK USB Dongle (#257)

Co-authored-by: superskybird <skybird.le@gmail.com>

* Fix `config.py` tool according to the new API of fido2 python package (#284)

* Fix fido2 API update.

Since fido2 0.8.1 the device descriptor moved to NamedTuple, breaking
our configuration tool.
Code is now updated accordingly and the setup script ensure we're
using the correct version for fido2 package.

* Make Yapf happy

* Fix missing update for fido2 0.9.1

Also split the comment into 2 lines so that the touch is not hidden
at the end of the screen.

* adds README changes, logo and certificate (#285)

* Fix broken parsing. (#317)

* Fix broken parsing.

By setting the default value before pre-parsing we ensure that the item
can't be None. As an extra safety the custom action also checks for
None.

Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>
Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2021-06-09 16:50:00 +02:00
Jean-Michel Picod
5e682d9e17 Compare all timestamps in UTC timezone. (#309) 2021-04-15 17:22:54 +02:00
Jean-Michel Picod
748b7e7fb8 Bugfix (#304)
* Add Feitian OpenSK USB Dongle (#257)

Co-authored-by: superskybird <skybird.le@gmail.com>

* Fix `config.py` tool according to the new API of fido2 python package (#284)

* Fix fido2 API update.

Since fido2 0.8.1 the device descriptor moved to NamedTuple, breaking
our configuration tool.
Code is now updated accordingly and the setup script ensure we're
using the correct version for fido2 package.

* Make Yapf happy

* Fix missing update for fido2 0.9.1

Also split the comment into 2 lines so that the touch is not hidden
at the end of the screen.

* adds README changes, logo and certificate (#285)

Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>
Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2021-04-13 14:59:47 +02:00
Jean-Michel Picod
b0c1b73897 Add Feitian OpenSK USB Dongle (#257) (#258)
Co-authored-by: superskybird <skybird.le@gmail.com>

Co-authored-by: Geoffrey <geoffrey@ftsafe.com>
Co-authored-by: superskybird <skybird.le@gmail.com>
2021-01-14 12:32:28 +01:00
273 changed files with 16384 additions and 35664 deletions

View File

@@ -2,10 +2,5 @@ Fixes #<issue_number_goes_here>
> It's a good idea to open an issue first for discussion. > It's a good idea to open an issue first for discussion.
- [ ] Local tests pass (running `run_desktop_tests.sh`) - [ ] Tests pass
- [ ] Tested against boards - [ ] Appropriate changes to README are included in PR
- [ ] Nordic nRF52840 DK
- [ ] Nordic nRF52840 Dongle (JTAG programmed)
- [ ] Nordic nRF52840 Dongle (DFU programmed)
- [ ] Makerdiary nRF52840 MDK USB Dongle
- [ ] Appropriate changes to README are included in PR

9
.github/actions-rs/grcov.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
branch: true
ignore-not-existing: true
llvm: true
filter: covered
output-type: lcov
output-path: ./lcov.info
ignore:
- "third_party/*"
- "/*"

View File

@@ -1,46 +0,0 @@
#!/bin/bash
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cd "$(dirname "$0")"
# New output file is $1
# Old output file is $2
TMP=comment.md
WARNING="Note: numbers above are a result of guesswork. They are not 100% correct and never will be."
NEW_SIZE=$(cat "$1" | sed -nr 's/.*100.0% (.*)KiB .text.*/\1/p')
OLD_SIZE=$(cat "$2" | sed -nr 's/.*100.0% (.*)KiB .text.*/\1/p')
echo "
OLD $OLD_SIZE kiB
NEW $NEW_SIZE kiB" > "$TMP"
echo "
Output of cargo bloat
======================
" >> "$TMP"
echo "Including PR" >> "$TMP"
cat "$1" >> "$TMP"
echo "Base branch" >> "$TMP"
cat "$2" >> "$TMP"
COMMENT="$(cat $TMP | sed "s/$WARNING//g" | sed 's/%/%25/g' | sed -z 's/\n/%0A/g')"
# No output for equality is intentional.
if (( $(echo "$NEW_SIZE > $OLD_SIZE" | bc -l) )); then
echo "::warning file=.github/workflows/cargo_bloat.yml,title=Binary size::$COMMENT"
fi
if (( $(echo "$NEW_SIZE < $OLD_SIZE" | bc -l) )); then
echo "::notice file=.github/workflows/cargo_bloat.yml,title=Binary size::$COMMENT"
fi

View File

@@ -1,21 +0,0 @@
name: Security audit
on:
schedule:
- cron: '0 0 * * *'
jobs:
audit:
runs-on: ubuntu-latest
if: github.repository == 'google/OpenSK'
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set up OpenSK
run: ./setup.sh
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,40 +0,0 @@
name: Binary size report
on: pull_request
jobs:
cargo_bloat:
runs-on: ubuntu-latest
steps:
# Setup
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: actions-rs/cargo@v1
with:
command: install
args: cargo-bloat
# First run: PR
- uses: actions/checkout@v4
with:
submodules: true
- name: Set up OpenSK
run: ./setup.sh
- name: Run bloat on the PR
run: RUSTFLAGS="-C link-arg=-icf=all -C force-frame-pointers=no -C link-arg=-Tnrf52840_layout.ld" cargo bloat --release --target=thumbv7em-none-eabi --features=config_command,with_ctap1 --crates >> .github/workflows/bloat_output_new.txt
# Second run: PR
- uses: actions/checkout@v4
with:
submodules: true
ref: ${{ github.base_ref }}
path: OpenSK_base
- name: Set up OpenSK
working-directory: ./OpenSK_base
run: ./setup.sh
- name: Run bloat on base
working-directory: ./OpenSK_base
run: RUSTFLAGS="-C link-arg=-icf=all -C force-frame-pointers=no -C link-arg=-Tnrf52840_layout.ld" cargo bloat --release --target=thumbv7em-none-eabi --features=config_command,with_ctap1 --crates >> "$GITHUB_WORKSPACE/.github/workflows/bloat_output_old.txt"
- name: Run output formatter to echo workflow command
run: ./.github/workflows/bloat_formatter.sh bloat_output_new.txt bloat_output_old.txt bloat_comment.md

View File

@@ -1,31 +0,0 @@
name: Continuous Integration
on:
push:
branches:
- develop
pull_request:
branches:
- develop
schedule:
- cron: 30 1 * * 2 # every Tuesday at 1:30 UTC
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
runtests:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: ./setup.sh
- run: ./run_desktop_tests.sh

View File

@@ -1,28 +0,0 @@
name: CIFuzz
on:
push:
branches:
- develop
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'opensk'
dry-run: false
language: rust
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'opensk'
fuzz-seconds: 600
dry-run: false
- name: Upload Crash
uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View File

@@ -1,47 +0,0 @@
---
name: OpenSK code coverage report
on:
push:
paths:
- 'libraries/**/*.rs'
pull_request:
types: [opened, synchronize, reopened]
jobs:
coveralls:
name: OpenSK code coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install Rust toolchain
run: rustup show
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set up OpenSK
run: ./setup.sh
- name: Install llvm tools
run: rustup +nightly component add llvm-tools-preview
- name: Install grcov
run: if [[ ! -e ~/.cargo/bin/grcov ]]; then cargo +stable install grcov; fi
- uses: actions-rs/cargo@v1
with:
toolchain: nightly
command: test
args: --manifest-path libraries/opensk/Cargo.toml --features "std,with_ctap1,vendor_hid,ed25519" --no-fail-fast
env:
RUSTFLAGS: "-Cinstrument-coverage"
LLVM_PROFILE_FILE: "opensk-%p-%m.profraw"
- name: Run grcov
run: RUSTUP_TOOLCHAIN=nightly grcov . --binary-path ./libraries/opensk/target/debug/ --source-dir libraries/opensk/ --output-type lcov --ignore-not-existing --output-path ./lcov.info --ignore "/*" --ignore "examples/*" --ignore "third_party/*"
- uses: coverallsapp/github-action@1.1.3
name: upload report to coveralls
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: "./lcov.info"
base-path: "libraries/opensk"

View File

@@ -12,10 +12,10 @@ jobs:
mdlint: mdlint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: markdownlint-cli - name: markdownlint-cli
uses: nosborn/github-action-markdown-cli@v3 uses: nosborn/github-action-markdown-cli@v1.1.1
with: with:
files: '**/*.md' files: '**/*.md'
config_file: '.markdownlint.json' ignore_files: "third_party/*"
ignore_files: 'third_party/*' config_file: ".markdownlint.json"

View File

@@ -1,43 +0,0 @@
---
name: Check that binaries are reproducible
on:
push:
pull_request:
types: [opened, synchronize, reopened]
jobs:
check_hashes:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
submodules: "true"
- name: Install Rust toolchain
run: rustup show
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Set up OpenSK
run: ./setup.sh
- name: Use sample cryptographic material
run: rm -R crypto_data/ && cp -r reproducible/sample_crypto_data crypto_data
- name: Computing cryptographic hashes
run: ./maintainers/reproduce_hashes.sh
- name: Upload reproduced binaries
uses: actions/upload-artifact@v4
with:
name: reproduced-${{ matrix.os }}
path: reproducible/reproduced.tar
- name: Comparing binary sizes
if: always()
run: git diff --no-index reproducible/reference_elf2tab_${{ matrix.os }}.txt reproducible/elf2tab.txt || true
- name: Comparing cryptographic hashes
if: always()
run: git diff --no-index reproducible/reference_binaries_${{ matrix.os }}.sha256sum reproducible/binaries.sha256sum || true

3
.gitignore vendored
View File

@@ -1,7 +1,4 @@
libraries/**/Cargo.lock
target/ target/
/build/
/py_virtual_env/
# Local installation of elf2tab. # Local installation of elf2tab.
/elf2tab/ /elf2tab/

View File

@@ -1,22 +1,35 @@
{ {
"default": true, "default": true,
"MD003": { "heading-style": {
"style": "atx" "style": "atx"
}, },
"MD007": { "no-trailing-spaces": {
"indent": 4
},
"MD009": {
"br_spaces": 0, "br_spaces": 0,
"strict": true "strict": true
}, },
"MD013": { "ul-indent": {
"indent": 4
},
"line-length": {
"line_length": 80, "line_length": 80,
"code_blocks": false "code_blocks": false
}, },
"MD033": { "list-marker-space": {
"ol_single": 2,
"ol_multi": 2,
"ul_single": 3,
"ul_multi": 3
},
"no-inline-html": {
"allowed_elements": [ "allowed_elements": [
"img" "img"
] ]
},
"fenced-code-language": true,
"code-block-style": {
"style": "fenced"
},
"code-fence-style": {
"style": "backtick"
} }
} }

543
.pylintrc
View File

@@ -1,318 +1,135 @@
# This Pylint rcfile contains a best-effort configuration to uphold the # File taken from Tensor2Tensor project
# best-practices and style described in the Google Python style guide: # https://github.com/tensorflow/tensor2tensor/blob/master/pylintrc
# https://google.github.io/styleguide/pyguide.html
#
# Its canonical open-source location is:
# https://google.github.io/styleguide/pylintrc
[MAIN] [MASTER]
# Files or directories to be skipped. They should be base names, not paths.
ignore=third_party
# Files or directories matching the regex patterns are skipped. The regex
# matches against base names, not paths.
ignore-patterns=
# Pickle collected data for later comparisons. # Pickle collected data for later comparisons.
persistent=no persistent=no
# List of plugins (as comma separated values of python modules names) to load, # Set the cache size for astng objects.
# usually to register additional checkers. cache-size=500
# Ignore Py3 files
ignore=get_references_web.py,get_references_web_single_group.py
load-plugins= load-plugins=
pylint.extensions.bad_builtin,
# Use multiple processes to speed up Pylint. pylint.extensions.docparams,
jobs=4 pylint.extensions.docstyle,
pylint.extensions.redefined_variable_type,
# Allow loading of arbitrary C extensions. Extensions are imported into the pylint.extensions.overlapping_exceptions,
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=abstract-method,
apply-builtin,
arguments-differ,
attribute-defined-outside-init,
backtick,
bad-option-value,
basestring-builtin,
buffer-builtin,
c-extension-no-member,
consider-using-enumerate,
cmp-builtin,
cmp-method,
coerce-builtin,
coerce-method,
delslice-method,
div-method,
duplicate-code,
eq-without-hash,
execfile-builtin,
file-builtin,
filter-builtin-not-iterating,
fixme,
getslice-method,
global-statement,
hex-method,
idiv-method,
implicit-str-concat-in-sequence,
import-error,
import-self,
import-star-module-level,
inconsistent-return-statements,
input-builtin,
intern-builtin,
invalid-str-codec,
locally-disabled,
long-builtin,
long-suffix,
map-builtin-not-iterating,
misplaced-comparison-constant,
missing-function-docstring,
metaclass-assignment,
next-method-called,
next-method-defined,
no-absolute-import,
no-else-break,
no-else-continue,
no-else-raise,
no-else-return,
no-init, # added
no-member,
no-name-in-module,
no-self-use,
nonzero-method,
oct-method,
old-division,
old-ne-operator,
old-octal-literal,
old-raise-syntax,
parameter-unpacking,
print-statement,
raising-string,
range-builtin-not-iterating,
raw_input-builtin,
rdiv-method,
reduce-builtin,
relative-import,
reload-builtin,
round-builtin,
setslice-method,
signature-differs,
standarderror-builtin,
suppressed-message,
sys-max-int,
too-few-public-methods,
too-many-ancestors,
too-many-arguments,
too-many-boolean-expressions,
too-many-branches,
too-many-instance-attributes,
too-many-locals,
too-many-nested-blocks,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
trailing-newlines,
unichr-builtin,
unicode-builtin,
unnecessary-pass,
unpacking-in-except,
unrecognized-option,
useless-else-on-loop,
useless-object-inheritance,
useless-suppression,
using-cmp-argument,
wrong-import-order,
xrange-builtin,
zip-builtin-not-iterating,
[REPORTS] [REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs # Set the output format.
# (visual studio) and html. You can also give a reporter class, eg # output-format=sorted-text
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the # Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be # command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]". This option is deprecated # written in a file name "pylint_global.[txt|html]".
# and it will be removed in Pylint 2.0.
files-output=no files-output=no
# Tells whether to display a full report or only the messages # Tells whether to display a full report or only the messages.
reports=no reports=no
# Python expression which should return a note less than 10 (10 is the highest # Disable the report(s) with the given id(s).
# note). You have access to the variables errors warning, statement which disable-report=R0001,R0002,R0003,R0004,R0101,R0102,R0201,R0202,R0220,R0401,R0402,R0701,R0801,R0901,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,R0921,R0922,R0923
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string # Error message template (continued on second line)
# used to format the message information. See doc for all details msg-template={msg_id}:{line:3} {obj}: {msg} [{symbol}]
#msg-template=
# We don't need evaluation score
score=no
[MESSAGES CONTROL]
# List of checkers and warnings to enable.
enable=indexing-exception,old-raise-syntax
# List of checkers and warnings to disable.
disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,file-ignored,multiple-imports,c-extension-no-member,trailing-newlines,unsubscriptable-object,misplaced-comparison-constant,no-member,abstract-method,no-else-return,missing-docstring,wrong-import-order,protected-access,inconsistent-return-statements,invalid-unary-operand-type,import-error,no-name-in-module,arguments-differ,not-context-manager,unused-argument
[BASIC] [BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# Regular expression which should only match the name
# of functions or classes which do not require a docstring.
no-docstring-rgx=(__.*__|main)
# Min length in lines of a function that requires a docstring.
docstring-min-length=10
# Regular expression which should only match correct module names. The
# leading underscore is sanctioned for private modules by Google's style
# guide.
#
# There are exceptions to the basic rule (_?[a-z][a-z0-9_]*) to cover
# requirements of Python's module system.
module-rgx=^(_?[a-z][a-z0-9_]*)|__init__$
# Regular expression which should only match correct module level names
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression which should only match correct class attribute
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression which should only match correct class names
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
# Regular expression which should only match correct function names.
# 'camel_case' and 'snake_case' group names are used for consistency of naming
# styles across functions and methods.
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
# Regular expression which should only match correct method names.
# 'camel_case' and 'snake_case' group names are used for consistency of naming
# styles across functions and methods. 'exempt' indicates a name which is
# consistent with all naming styles.
method-rgx=(?x)
^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase
|tearDownTestCase|setupSelf|tearDownClass|setUpClass
|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)
|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)
|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
# Regular expression which should only match correct instance attribute names
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
# Regular expression which should only match correct argument names
argument-rgx=^[a-z][a-z0-9_]*$
# Regular expression which should only match correct variable names
variable-rgx=^[a-z][a-z0-9_]*$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=^[a-z][a-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma # Good variable names which should always be accepted, separated by a comma
good-names=main,_ good-names=main,_
# Bad variable names which should always be refused, separated by a comma # Bad variable names which should always be refused, separated by a comma
bad-names= bad-names=
# Colon-delimited sets of names that determine each other's naming style when # List of builtins function names that should not be used, separated by a comma
# the name regexes allow several styles. bad-functions=input,apply,reduce
name-group=
# Include a hint for the correct naming format with invalid-name # List of decorators that define properties, such as abc.abstractproperty.
include-naming-hint=no property-classes=abc.abstractproperty
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
# Regular expression matching correct function names
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
# Regular expression matching correct variable names
variable-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct constant names
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct attribute names
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
# Regular expression matching correct argument names
argument-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class attribute names
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
# Regular expression matching correct inline iteration names
inlinevar-rgx=^[a-z][a-z0-9_]*$
# Regular expression matching correct class names
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
# Regular expression matching correct module names
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
# Regular expression matching correct method names
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=10
[TYPECHECK] [TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
# Tells whether missing members accessed in mixin class should be ignored. A # Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive). # mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes ignore-mixin-members=yes
# List of module names for which member attributes should not be checked # List of decorators that create context managers from functions, such as
# (useful for modules/projects where namespaces are manipulated during runtime # contextlib.contextmanager.
# and thus existing member attributes cannot be deduced by static analysis. It contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
# lines made too long by directives to pytype.
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=(?x)(
^\s*(\#\ )?<?https?://\S+>?$|
^\s*(from\s+\S+\s+)?import\s+.+$)
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=yes
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=
# Maximum number of lines in a module
max-module-lines=99999
# String used as indentation unit. The internal Google style guide mandates 2
# spaces. Google's externaly-published style guide says 4, consistent with
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
# projects (like TensorFlow).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=TODO
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=yes
[VARIABLES] [VARIABLES]
@@ -320,123 +137,99 @@ check-quote-consistency=yes
# Tells whether we should check for unused import in __init__ files. # Tells whether we should check for unused import in __init__ files.
init-import=no init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly # A regular expression matching names used for dummy variables (i.e. not used).
# not used).
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
# List of additional names supposed to be defined in builtins. Remember that # List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible. # you should avoid to define new builtins when possible.
additional-builtins= additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine [CLASSES]
# builtins.
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools # List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# "class_" is also a valid for the first argument to a class method.
valid-classmethod-first-arg=cls,class_
[LOGGING] [EXCEPTIONS]
# Logging modules to check that the string format arguments are in logging overgeneral-exceptions=StandardError,Exception,BaseException
# function parameter format
logging-modules=logging,absl.logging,tensorflow.io.logging
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[IMPORTS] [IMPORTS]
# Deprecated modules which should not be used, separated by a comma # Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub, deprecated-modules=regsub,TERMIOS,Bastion,rexec,sets
TERMIOS,
Bastion,
rexec,
sets
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant, absl
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
[CLASSES] [FORMAT]
# List of method names used to declare (i.e. assign) instance attributes. # Maximum number of characters on a single line.
defining-attr-methods=__init__, max-line-length=80
__new__,
setUp
# List of member names, which should be excluded from the protected access # Regexp for a line that is allowed to be longer than the limit.
# warning. # This "ignore" regex is today composed of several independent parts:
exclude-protected=_asdict, # (1) Long import lines
_fields, # (2) URLs in comments or pydocs. Detecting URLs by regex is a hard problem and
_replace, # no amount of tweaking will make a perfect regex AFAICT. This one is a good
_source, # compromise.
_make # (3) Constant string literals at the start of files don't need to be broken
# across lines. Allowing long paths and urls to be on a single
# line. Also requires that the string not be a triplequoted string.
ignore-long-lines=(?x)
(^\s*(import|from)\s
|^\s*(\#\ )?<?(https?|ftp):\/\/[^\s\/$.?#].[^\s]*>?$
|^[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*("[^"]\S+"|'[^']\S+')
)
# List of valid names for the first argument in a class method. # Maximum number of lines in a module
valid-classmethod-first-arg=cls, max-module-lines=99999
class_
# List of valid names for the first argument in a metaclass class method. # String used as indentation unit. We differ from PEP8's normal 4 spaces.
valid-metaclass-classmethod-first-arg=mcs indent-string=' '
# Do not warn about multiple statements on a single line for constructs like
# if test: stmt
single-line-if-stmt=y
# Make sure : in dicts and trailing commas are checked for whitespace.
no-space-check=
[EXCEPTIONS] [LOGGING]
# Exceptions that will emit a warning when being caught. Defaults to # Add logging modules.
# "Exception" logging-modules=logging,absl.logging
overgeneral-exceptions=builtins.StandardError,
builtins.Exception,
builtins.BaseException [MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=
# Maximum line length for lambdas
short-func-length=1
# List of module members that should be marked as deprecated.
# All of the string functions are listed in 4.1.4 Deprecated string functions
# in the Python 2.4 docs.
deprecated-members=string.atof,string.atoi,string.atol,string.capitalize,string.expandtabs,string.find,string.rfind,string.index,string.rindex,string.count,string.lower,string.split,string.rsplit,string.splitfields,string.join,string.joinfields,string.lstrip,string.rstrip,string.strip,string.swapcase,string.translate,string.upper,string.ljust,string.rjust,string.center,string.zfill,string.replace,sys.exitfunc,sys.maxint
# List of exceptions that do not need to be mentioned in the Raises section of
# a docstring.
ignore-exceptions=AssertionError,NotImplementedError,StopIteration,TypeError
# Number of spaces of indent required when the last token on the preceding line
# is an open (, [, or {.
indent-after-paren=4
# Set the linting for string quotes
string-quote=double
triple-quote=double
docstring-quote=double

View File

@@ -1,27 +0,0 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
repository-code: "https://github.com/google/OpenSK"
license: "Apache-2.0"
preferred-citation:
type: article
authors:
- family-names: "Ghinea"
given-names: "Diana"
- family-names: "Kaczmarczyck"
given-names: "Fabian"
- family-names: "Pullman"
given-names: "Jennifer"
- family-names: "Cretin"
given-names: "Julien"
- family-names: "Kölbl"
given-names: "Stefan"
- family-names: "Invernizzi"
given-names: "Luca"
- family-names: "Bursztein"
given-names: "Elie"
- family-names: "Picod"
given-names: "Jean-Michel"
title: "Hybrid Post-Quantum Signatures in Hardware Security Keys"
journal: "4th ACNS Workshop on Secure Cryptographic Implementation"
year: 2023
month: 6

796
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,54 +5,36 @@ authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>", "Fabian Kaczmarczyck <kaczmarczyck@google.com>",
"Guillaume Endignoux <guillaumee@google.com>", "Guillaume Endignoux <guillaumee@google.com>",
"Jean-Michel Picod <jmichel@google.com>", "Jean-Michel Picod <jmichel@google.com>",
"Julien Cretin <cretin@google.com>",
] ]
license = "Apache-2.0" license = "Apache-2.0"
edition = "2018" edition = "2018"
[target.'cfg(any(target_arch = "arm", target_arch = "riscv32"))'.dependencies.libtock_runtime]
path = "third_party/libtock-rs/runtime"
default-features = false
features = ["no_auto_layout", "no_debug_memop"]
[dependencies] [dependencies]
libtock_buttons = { path = "third_party/libtock-rs/apis/buttons" } libtock_core = { path = "third_party/libtock-rs/core" }
libtock_platform = { path = "third_party/libtock-rs/platform" }
libtock_drivers = { path = "third_party/libtock-drivers" } libtock_drivers = { path = "third_party/libtock-drivers" }
libtock_alarm = { path = "third_party/libtock-rs/apis/alarm" }
libtock_console = { path = "third_party/libtock-rs/apis/console" }
libtock_leds = { path = "third_party/libtock-rs/apis/leds" }
lang_items = { path = "third_party/lang-items" } lang_items = { path = "third_party/lang-items" }
opensk = { path = "libraries/opensk", default-features = false } cbor = { path = "libraries/cbor" }
sk-cbor = { path = "libraries/cbor" }
crypto = { path = "libraries/crypto" } crypto = { path = "libraries/crypto" }
persistent_store = { path = "libraries/persistent_store" } persistent_store = { path = "libraries/persistent_store" }
libtock_unittest = { path = "third_party/libtock-rs/unittest", optional = true }
byteorder = { version = "1", default-features = false } byteorder = { version = "1", default-features = false }
arrayref = "0.3.6" arrayref = "0.3.6"
rand_core = "0.6.4" subtle = { version = "2.2", default-features = false, features = ["nightly"] }
ed25519-compact = { version = "1", default-features = false, optional = true }
[features] [features]
config_command = ["opensk/config_command"]
debug_allocations = ["lang_items/debug_allocations"] debug_allocations = ["lang_items/debug_allocations"]
debug_ctap = ["libtock_drivers/debug_ctap", "opensk/debug_ctap"] debug_ctap = ["crypto/derive_debug", "libtock_drivers/debug_ctap"]
panic_console = ["lang_items/panic_console"] panic_console = ["lang_items/panic_console"]
std = ["crypto/std", "lang_items/std", "persistent_store/std", "opensk/std", "libtock_unittest"] std = ["cbor/std", "crypto/std", "crypto/derive_debug", "lang_items/std", "persistent_store/std"]
verbose = ["debug_ctap", "libtock_drivers/verbose_usb"] verbose = ["debug_ctap", "libtock_drivers/verbose_usb"]
with_ctap1 = ["opensk/with_ctap1"] with_ctap1 = ["crypto/with_ctap1"]
with_ctap2_1 = []
with_nfc = ["libtock_drivers/with_nfc"] with_nfc = ["libtock_drivers/with_nfc"]
vendor_hid = ["opensk/vendor_hid"]
ed25519 = ["ed25519-compact", "opensk/ed25519"]
rust_crypto = ["opensk/rust_crypto"]
[dev-dependencies] [dev-dependencies]
enum-iterator = "0.6.0" enum-iterator = "0.6.0"
[build-dependencies] [build-dependencies]
sk-cbor = { path = "libraries/cbor" }
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
openssl = "0.10.55"
[profile.dev] [profile.dev]
panic = "abort" panic = "abort"
@@ -61,10 +43,3 @@ lto = true # Link Time Optimization usually reduces size of binaries and static
[profile.release] [profile.release]
panic = "abort" panic = "abort"
lto = true # Link Time Optimization usually reduces size of binaries and static libraries lto = true # Link Time Optimization usually reduces size of binaries and static libraries
opt-level = "z"
codegen-units = 1
[profile.release.package]
aes = { opt-level = 3 }
sha2 = { opt-level = 3 }
p256 = { opt-level = 3 }

View File

@@ -1,63 +1,42 @@
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px"> # <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
![markdownlint](https://github.com/google/OpenSK/workflows/markdownlint/badge.svg?branch=develop) This branch is unmaintained. It implements the CTAP 2.0 version of OpenSK.
![pylint](https://github.com/google/OpenSK/workflows/pylint/badge.svg?branch=develop) Please check out the default branch for maintained code.
![Cargo check](https://github.com/google/OpenSK/workflows/Cargo%20check/badge.svg?branch=develop) If you are a developer, go to the
![Cargo format](https://github.com/google/OpenSK/workflows/Cargo%20format/badge.svg?branch=develop) [develop branch](https://github.com/google/OpenSK/tree/develop).
[![Coverage Status](https://coveralls.io/repos/github/google/OpenSK/badge.svg?branch=develop)](https://coveralls.io/github/google/OpenSK?branch=develop)
*News:*
- 2023-08-24: [PQC paper reference](#Research)
## OpenSK ## OpenSK
This repository contains a Rust implementation of a This repository contains a Rust implementation of a
[FIDO2](https://fidoalliance.org/fido2/) security key. [FIDO2](https://fidoalliance.org/fido2/) authenticator.
Security keys are external devices that can be used for signing in on websites. We developed OpenSK as a [Tock OS](https://tockos.org) application.
You can see OpenSK in action in this
[video on YouTube](https://www.youtube.com/watch?v=klEozvpw0xg)!
We intend to bring a full open source experience to security keys, from We intend to bring a full open source experience to security keys, from
application to operating system. You can even 3D print your own open source application to operating system. You can even 3D print your own open source
enclosure! enclosure!
You can see OpenSK in action in this
<img src="docs/img/enclosure.jpg" alt="OpenSK Enclosure" width="200"/> [video on YouTube](https://www.youtube.com/watch?v=klEozvpw0xg)!
You can run OpenSK as a [Tock OS](https://tockos.org) application, or use the
library to bring OpenSK to your own hardware.
You are viewing the branch for developers. New features are developed here.
Go to the default branch for a more stable version of OpenSK.
### FIDO2 ### FIDO2
OpenSK's version that implemented CTAP 2.0 was certified by the FIDO Alliance. This branch implements the
[CTAP2.0 specification](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html)
The develop branch tracks the latest release version of the and is FIDO certified. OpenSK supports U2F, and non-discoverable credentials
[CTAP specification](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html). created with either protocol are compatible with the other.
This branch is not FIDO certified.
OpenSK supports U2F, and non-discoverable credentials created with either
protocol are compatible with the other.
### :warning: Disclaimer ### :warning: Disclaimer
This project is **proof-of-concept and a research platform**. It is **NOT** This project is **proof-of-concept and a research platform**. It is **NOT**
meant for a daily usage. This branch is under development, and therefore less meant for a daily usage. The cryptography implementations are not resistent
rigorously tested than the numbered branches. against side-channel attacks.
We're still in the process of integrating the We're still in the process of integrating the
[ARM&reg; CryptoCell-310](https://developer.arm.com/ip-products/security-ip/cryptocell-300-family) [ARM&reg; CryptoCell-310](https://developer.arm.com/ip-products/security-ip/cryptocell-300-family)
embedded in the embedded in the
[Nordic nRF52840 chip](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fcryptocell.html) [Nordic nRF52840 chip](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fcryptocell.html)
to enable hardware-accelerated cryptography. to enable hardware-accelerated cryptography. Our placeholder implementations of required
In the meantime, there are 2 options for cryptography implementations: cryptography algorithms (ECDSA, ECC secp256r1, HMAC-SHA256 and AES256) in Rust are research-quality
code. They haven't been reviewed and don't provide constant-time guarantees.
* Our own placeholder implementation. The code is research quality and doesn't
provide constant-time guarantees.
* The [RustCrypto](https://github.com/RustCrypto) interface. Deploy with
`--rust-crypto`. Note that our own ECC implementation is faster and has
smaller binary size, so not all boards support RustCrypto yet.
## Hardware ## Hardware
@@ -87,28 +66,6 @@ Please check our [Troubleshooting and Debugging](docs/debugging.md) section if y
have problems with the installation process or during development. To find out what have problems with the installation process or during development. To find out what
else you can do with your OpenSK, see [Customization](docs/customization.md). else you can do with your OpenSK, see [Customization](docs/customization.md).
## Research
We implemented post-quantum cryptography on OpenSK. The code is released under
the [hybrid-pqc tag](https://github.com/google/OpenSK/releases/tag/hybrid-pqc).
Our [paper](https://eprint.iacr.org/2022/1225) was published in the ACNS
Secure Cryptographic Implementation workshop 2023 and won the best paper award.
<details>
<summary>Bibtex reference</summary>
```
@InProceedings{Ghinea2023hybrid,
author= {Diana Ghinea and Fabian Kaczmarczyck and Jennifer Pullman and Julien Cretin and Rafael Misoczki and Stefan Kölbl and Luca Invernizzi and Elie Bursztein and Jean-Michel Picod},
title= {{Hybrid Post-Quantum Signatures in Hardware Security Keys}},
booktitle= {{4th ACNS Workshop on Secure Cryptographic Implementation, Kyoto, Japan}},
month= {June},
year= {2023},
}
```
</details>
## Contributing ## Contributing
See [Contributing.md](docs/contributing.md). See [Contributing.md](docs/contributing.md).

View File

@@ -6,7 +6,7 @@ build = "build.rs"
edition = "2018" edition = "2018"
[[bin]] [[bin]]
path = "../nrf52840_dongle_opensk/src/main.rs" path = "../nrf52840_dongle/src/main.rs"
name = "nrf52840_dongle_dfu" name = "nrf52840_dongle_dfu"
[dependencies] [dependencies]
@@ -16,6 +16,3 @@ capsules = { path = "../../../capsules" }
kernel = { path = "../../../kernel" } kernel = { path = "../../../kernel" }
nrf52840 = { path = "../../../chips/nrf52840" } nrf52840 = { path = "../../../chips/nrf52840" }
nrf52_components = { path = "../nrf52_components" } nrf52_components = { path = "../nrf52_components" }
[features]
vendor_hid = ["capsules/vendor_hid"]

View File

@@ -1,17 +0,0 @@
[package]
name = "nrf52840_dongle_opensk"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
build = "build.rs"
edition = "2018"
[dependencies]
components = { path = "../../components" }
cortexm4 = { path = "../../../arch/cortex-m4" }
capsules = { path = "../../../capsules" }
kernel = { path = "../../../kernel" }
nrf52840 = { path = "../../../chips/nrf52840" }
nrf52_components = { path = "../nrf52_components" }
[features]
vendor_hid = ["capsules/vendor_hid"]

View File

@@ -1,28 +0,0 @@
# Makefile for building the tock kernel for the nRF development kit
TARGET=thumbv7em-none-eabi
PLATFORM=nrf52840_dongle_opensk
include ../../Makefile.common
TOCKLOADER=tockloader
# Where in the nrf52 flash to load the kernel with `tockloader`
KERNEL_ADDRESS=0x00000
# Upload programs over uart with tockloader
ifdef PORT
TOCKLOADER_GENERAL_FLAGS += --port $(PORT)
endif
TOCKLOADER_JTAG_FLAGS = --jlink --board nrf52dk
# Upload the kernel over JTAG
.PHONY: flash
flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $<
# Upload the kernel over serial/bootloader
.PHONY: program
program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex
$(error Cannot program nRF52840-Dongle over USB. Use \`make flash\` and JTAG)

View File

@@ -1,41 +0,0 @@
Platform-Specific Instructions: nRF52840-Dongle
===================================
This is an adapted nrf52840\_dongle made to work with OpenSK.
The [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle)
is a platform based around the nRF52840, an SoC with an ARM Cortex-M4 and a BLE radio.
The kit is uses a USB key form factor and includes 1 button, 1 red LED and 1 RGB LED.
## Getting Started
To program the nRF52840 Dongle with Tock, you will need a JLink JTAG device and the
appropriate cables. An example setup is:
- [JLink JTAG Device](https://www.digikey.com/product-detail/en/segger-microcontroller-systems/8.08.90-J-LINK-EDU/899-1008-ND/2263130)
- [ARM to TagConnect Adapter](https://www.digikey.com/product-detail/en/tag-connect-llc/TC2050-ARM2010/TC2050-ARM2010-ND/3528170)
- [10pin TagConnect Cable](https://www.digikey.com/product-detail/en/tag-connect-llc/TC2050-IDC-NL/TC2050-IDC-NL-ND/2605367)
Then, follow the [Tock Getting Started guide](../../../doc/Getting_Started.md)
JTAG is the preferred method to program. The development kit has the JTAG pins exposed either
through the half-moons pads or, below the PCB, on a Tag-Connect TC2050 connector footprint.
You need to [install JTAG software](../../../doc/Getting_Started.md#optional-requirements).
## Programming the kernel
Once you have all software installed, you should be able to simply run
make flash in this directory to install a fresh kernel.
## Programming user-level applications
You can program an application via JTAG using `tockloader`:
```shell
$ cd libtock-c/examples/<app>
$ make
$ tockloader install --jlink --board nrf52dk
```
## Debugging
See the [nrf52dk README](../nrf52dk/README.md) for information about debugging
the nRF52840 Dongle.

View File

@@ -1,4 +0,0 @@
fn main() {
println!("cargo:rerun-if-changed=layout.ld");
println!("cargo:rerun-if-changed=../../kernel_layout.ld");
}

View File

@@ -1,25 +0,0 @@
#
#
#
# J-LINK GDB SERVER initialization
#
# This connects to a GDB Server listening
# for commands on localhost at tcp port 2331
target remote localhost:2331
monitor speed 30
file ../../../../target/thumbv7em-none-eabi/release/nrf52840_dongle
monitor reset
#
# CPU core initialization (to be done by user)
#
# Set the processor mode
# monitor reg cpsr = 0xd3
# Set auto JTAG speed
monitor speed auto
# Setup GDB FOR FASTER DOWNLOADS
set remote memory-write-packet-size 1024
set remote memory-write-packet-size fixed
# tui enable
# layout split
# layout service_pending_interrupts
b reset_handler

View File

@@ -1 +0,0 @@
JLinkGDBServer -device nRF52840_xxAA -speed 1200 -if swd -AutoConnect 1 -port 2331

View File

@@ -1,2 +0,0 @@
INCLUDE ../nrf52840_chip_layout.ld
INCLUDE ../../kernel_layout.ld

View File

@@ -1,68 +0,0 @@
use core::fmt::Write;
use core::panic::PanicInfo;
use cortexm4;
use kernel::debug;
use kernel::debug::IoWrite;
use kernel::hil::led;
use kernel::hil::uart::{self, Configure};
use nrf52840::gpio::Pin;
use crate::{CHIP, PROCESSES, PROCESS_PRINTER};
struct Writer {
initialized: bool,
}
static mut WRITER: Writer = Writer { initialized: false };
impl Write for Writer {
fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
self.write(s.as_bytes());
Ok(())
}
}
impl IoWrite for Writer {
fn write(&mut self, buf: &[u8]) {
// Here, we create a second instance of the Uarte struct.
// This is okay because we only call this during a panic, and
// we will never actually process the interrupts
let uart = nrf52840::uart::Uarte::new();
if !self.initialized {
self.initialized = true;
let _ = uart.configure(uart::Parameters {
baud_rate: 115200,
stop_bits: uart::StopBits::One,
parity: uart::Parity::None,
hw_flow_control: false,
width: uart::Width::Eight,
});
}
for &c in buf {
unsafe {
uart.send_byte(c);
}
while !uart.tx_ready() {}
}
}
}
#[cfg(not(test))]
#[no_mangle]
#[panic_handler]
/// Panic handler
pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! {
// The nRF52840 Dongle LEDs (see back of board)
let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_06);
let led = &mut led::LedLow::new(led_kernel_pin);
let writer = &mut WRITER;
debug::panic(
&mut [led],
writer,
pi,
&cortexm4::support::nop,
&PROCESSES,
&CHIP,
&PROCESS_PRINTER,
)
}

View File

@@ -1,497 +0,0 @@
//! Tock kernel for the Nordic Semiconductor nRF52840 dongle.
//!
//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with
//! many exported I/O and peripherals.
#![no_std]
// Disable this attribute when documenting, as a workaround for
// https://github.com/rust-lang/rust/issues/62184.
#![cfg_attr(not(doc), no_main)]
#![deny(missing_docs)]
use capsules::virtual_alarm::VirtualMuxAlarm;
use kernel::component::Component;
use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState};
use kernel::hil::led::LedLow;
use kernel::hil::time::Counter;
use kernel::platform::{KernelResources, SyscallDriverLookup, SyscallFilter};
use kernel::scheduler::round_robin::RoundRobinSched;
#[allow(unused_imports)]
use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init};
use kernel::{StorageLocation, StorageType};
use nrf52840::gpio::Pin;
use nrf52840::interrupt_service::Nrf52840DefaultPeripherals;
use nrf52_components::{self, UartChannel, UartPins};
// The nRF52840 Dongle LEDs
const LED1_PIN: Pin = Pin::P0_06;
const LED2_R_PIN: Pin = Pin::P0_08;
const LED2_G_PIN: Pin = Pin::P1_09;
const LED2_B_PIN: Pin = Pin::P0_12;
// The nRF52840 Dongle button
const BUTTON_PIN: Pin = Pin::P1_06;
const BUTTON_RST_PIN: Pin = Pin::P0_18;
const UART_RTS: Option<Pin> = Some(Pin::P0_13);
const UART_TXD: Pin = Pin::P0_15;
const UART_CTS: Option<Pin> = Some(Pin::P0_17);
const UART_RXD: Pin = Pin::P0_20;
// SPI pins not currently in use, but left here for convenience
const _SPI_MOSI: Pin = Pin::P1_01;
const _SPI_MISO: Pin = Pin::P1_02;
const _SPI_CLK: Pin = Pin::P1_04;
/// UART Writer
pub mod io;
const VENDOR_ID: u16 = 0x1915; // Nordic Semiconductor
const PRODUCT_ID: u16 = 0x521f; // nRF52840 Dongle (PCA10059)
static STRINGS: &'static [&'static str] = &[
// Manufacturer
"Nordic Semiconductor ASA",
// Product
"OpenSK",
// Serial number
"v1.0",
// Interface description + main HID string
"FIDO2",
// vendor HID string
"Vendor HID",
];
// State for loading and holding applications.
// How should the kernel respond when a process faults.
const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {};
// Number of concurrent processes this platform supports.
const NUM_PROCS: usize = 8;
static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] =
[None; NUM_PROCS];
static mut STORAGE_LOCATIONS: [StorageLocation; 2] = [
// We implement NUM_PAGES = 20 as 16 + 4 to satisfy the MPU.
StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: StorageType::Store,
},
StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: StorageType::Store,
},
];
// Static reference to chip for panic dumps
static mut CHIP: Option<&'static nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>> = None;
// Static reference to process printer for panic dumps
static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None;
/// Flash buffer for the custom nvmc driver
static mut APP_FLASH_BUFFER: [u8; 0x1000] = [0; 0x1000];
/// Dummy buffer that causes the linker to reserve enough space for the stack.
#[no_mangle]
#[link_section = ".stack_buffer"]
pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000];
/// Supported drivers by the platform
pub struct Platform {
button: &'static capsules::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>,
pconsole: &'static capsules::process_console::ProcessConsole<
'static,
VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
components::process_console::Capability,
>,
console: &'static capsules::console::Console<'static>,
gpio: &'static capsules::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>,
led: &'static capsules::led::LedDriver<
'static,
LedLow<'static, nrf52840::gpio::GPIOPin<'static>>,
4,
>,
rng: &'static capsules::rng::RngDriver<'static>,
ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>,
analog_comparator: &'static capsules::analog_comparator::AnalogComparator<
'static,
nrf52840::acomp::Comparator<'static>,
>,
alarm: &'static capsules::alarm::AlarmDriver<
'static,
capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
>,
scheduler: &'static RoundRobinSched<'static>,
systick: cortexm4::systick::SysTick,
nvmc: &'static nrf52840::nvmc::SyscallDriver,
usb: &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver<
'static,
'static,
nrf52840::usbd::Usbd<'static>,
>,
}
impl SyscallDriverLookup for Platform {
fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
where
F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R,
{
match driver_num {
capsules::console::DRIVER_NUM => f(Some(self.console)),
capsules::gpio::DRIVER_NUM => f(Some(self.gpio)),
capsules::alarm::DRIVER_NUM => f(Some(self.alarm)),
capsules::led::DRIVER_NUM => f(Some(self.led)),
capsules::button::DRIVER_NUM => f(Some(self.button)),
capsules::rng::DRIVER_NUM => f(Some(self.rng)),
capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)),
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
_ => f(None),
}
}
}
impl SyscallFilter for Platform {
fn filter_syscall(
&self,
process: &dyn kernel::process::Process,
syscall: &kernel::syscall::Syscall,
) -> Result<(), kernel::errorcode::ErrorCode> {
use kernel::syscall::Syscall;
match *syscall {
Syscall::Command {
driver_number: nrf52840::nvmc::DRIVER_NUM,
subdriver_number: cmd,
arg0: ptr,
arg1: len,
} if (cmd == 2 || cmd == 3) && !process.fits_in_storage_location(ptr, len) => {
Err(kernel::ErrorCode::INVAL)
}
_ => Ok(()),
}
}
}
impl KernelResources<nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>>
for Platform
{
type SyscallDriverLookup = Self;
type SyscallFilter = Self;
type ProcessFault = ();
type Scheduler = RoundRobinSched<'static>;
type SchedulerTimer = cortexm4::systick::SysTick;
type WatchDog = ();
type ContextSwitchCallback = ();
fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup {
&self
}
fn syscall_filter(&self) -> &Self::SyscallFilter {
&self
}
fn process_fault(&self) -> &Self::ProcessFault {
&()
}
fn scheduler(&self) -> &Self::Scheduler {
self.scheduler
}
fn scheduler_timer(&self) -> &Self::SchedulerTimer {
&self.systick
}
fn watchdog(&self) -> &Self::WatchDog {
&()
}
fn context_switch_callback(&self) -> &Self::ContextSwitchCallback {
&()
}
}
/// This is in a separate, inline(never) function so that its stack frame is
/// removed when this function returns. Otherwise, the stack space used for
/// these static_inits is wasted.
#[inline(never)]
unsafe fn get_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> {
// Initialize chip peripheral drivers
let nrf52840_peripherals = static_init!(
Nrf52840DefaultPeripherals,
Nrf52840DefaultPeripherals::new()
);
nrf52840_peripherals
}
/// Main function called after RAM initialized.
#[no_mangle]
pub unsafe fn main() {
nrf52840::init();
let nrf52840_peripherals = get_peripherals();
// set up circular peripheral dependencies
nrf52840_peripherals.init();
let base_peripherals = &nrf52840_peripherals.nrf52;
let board_kernel = static_init!(
kernel::Kernel,
kernel::Kernel::new_with_storage(&PROCESSES, &STORAGE_LOCATIONS)
);
// GPIOs
let gpio = components::gpio::GpioComponent::new(
board_kernel,
capsules::gpio::DRIVER_NUM,
components::gpio_component_helper!(
nrf52840::gpio::GPIOPin,
// left side of the USB plug
0 => &nrf52840_peripherals.gpio_port[Pin::P0_13],
1 => &nrf52840_peripherals.gpio_port[Pin::P0_15],
2 => &nrf52840_peripherals.gpio_port[Pin::P0_17],
3 => &nrf52840_peripherals.gpio_port[Pin::P0_20],
4 => &nrf52840_peripherals.gpio_port[Pin::P0_22],
5 => &nrf52840_peripherals.gpio_port[Pin::P0_24],
6 => &nrf52840_peripherals.gpio_port[Pin::P1_00],
7 => &nrf52840_peripherals.gpio_port[Pin::P0_09],
8 => &nrf52840_peripherals.gpio_port[Pin::P0_10],
// right side of the USB plug
9 => &nrf52840_peripherals.gpio_port[Pin::P0_31],
10 => &nrf52840_peripherals.gpio_port[Pin::P0_29],
11 => &nrf52840_peripherals.gpio_port[Pin::P0_02],
12 => &nrf52840_peripherals.gpio_port[Pin::P1_15],
13 => &nrf52840_peripherals.gpio_port[Pin::P1_13],
14 => &nrf52840_peripherals.gpio_port[Pin::P1_10],
// Below the PCB
15 => &nrf52840_peripherals.gpio_port[Pin::P0_26],
16 => &nrf52840_peripherals.gpio_port[Pin::P0_04],
17 => &nrf52840_peripherals.gpio_port[Pin::P0_11],
18 => &nrf52840_peripherals.gpio_port[Pin::P0_14],
19 => &nrf52840_peripherals.gpio_port[Pin::P1_11],
20 => &nrf52840_peripherals.gpio_port[Pin::P1_07],
21 => &nrf52840_peripherals.gpio_port[Pin::P1_01],
22 => &nrf52840_peripherals.gpio_port[Pin::P1_04],
23 => &nrf52840_peripherals.gpio_port[Pin::P1_02]
),
)
.finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin));
let button = components::button::ButtonComponent::new(
board_kernel,
capsules::button::DRIVER_NUM,
components::button_component_helper!(
nrf52840::gpio::GPIOPin,
(
&nrf52840_peripherals.gpio_port[BUTTON_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow,
kernel::hil::gpio::FloatingState::PullUp
)
),
)
.finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin));
let led = components::led::LedsComponent::new().finalize(components::led_component_helper!(
LedLow<'static, nrf52840::gpio::GPIOPin>,
LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]),
LedLow::new(&nrf52840_peripherals.gpio_port[LED2_R_PIN]),
LedLow::new(&nrf52840_peripherals.gpio_port[LED2_G_PIN]),
LedLow::new(&nrf52840_peripherals.gpio_port[LED2_B_PIN]),
));
let chip = static_init!(
nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>,
nrf52840::chip::NRF52::new(nrf52840_peripherals)
);
CHIP = Some(chip);
nrf52_components::startup::NrfStartupComponent::new(
false,
BUTTON_RST_PIN,
nrf52840::uicr::Regulator0Output::V3_0,
&base_peripherals.nvmc,
)
.finalize(());
// Create capabilities that the board needs to call certain protected kernel
// functions.
let process_management_capability =
create_capability!(capabilities::ProcessManagementCapability);
let main_loop_capability = create_capability!(capabilities::MainLoopCapability);
let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability);
let gpio_port = &nrf52840_peripherals.gpio_port;
// Configure kernel debug gpios as early as possible
kernel::debug::assign_gpios(
Some(&gpio_port[LED2_R_PIN]),
Some(&gpio_port[LED2_G_PIN]),
Some(&gpio_port[LED2_B_PIN]),
);
let rtc = &base_peripherals.rtc;
let _ = rtc.start();
let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc)
.finalize(components::alarm_mux_component_helper!(nrf52840::rtc::Rtc));
let alarm = components::alarm::AlarmDriverComponent::new(
board_kernel,
capsules::alarm::DRIVER_NUM,
mux_alarm,
)
.finalize(components::alarm_component_helper!(nrf52840::rtc::Rtc));
let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD));
let channel = nrf52_components::UartChannelComponent::new(
uart_channel,
mux_alarm,
&base_peripherals.uarte0,
)
.finalize(());
let dynamic_deferred_call_clients =
static_init!([DynamicDeferredCallClientState; 3], Default::default());
let dynamic_deferred_caller = static_init!(
DynamicDeferredCall,
DynamicDeferredCall::new(dynamic_deferred_call_clients)
);
DynamicDeferredCall::set_global_instance(dynamic_deferred_caller);
let process_printer =
components::process_printer::ProcessPrinterTextComponent::new().finalize(());
PROCESS_PRINTER = Some(process_printer);
// Create a shared UART channel for the console and for kernel debug.
let uart_mux =
components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller)
.finalize(());
let pconsole = components::process_console::ProcessConsoleComponent::new(
board_kernel,
uart_mux,
mux_alarm,
process_printer,
)
.finalize(components::process_console_component_helper!(
nrf52840::rtc::Rtc<'static>
));
// Setup the console.
let console = components::console::ConsoleComponent::new(
board_kernel,
capsules::console::DRIVER_NUM,
uart_mux,
)
.finalize(components::console_component_helper!());
// Create the debugger object that handles calls to `debug!()`.
components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(());
let rng = components::rng::RngComponent::new(
board_kernel,
capsules::rng::DRIVER_NUM,
&base_peripherals.trng,
)
.finalize(());
// Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02)
// These are hardcoded pin assignments specified in the driver
let analog_comparator = components::analog_comparator::AcComponent::new(
&base_peripherals.acomp,
components::acomp_component_helper!(
nrf52840::acomp::Channel,
&nrf52840::acomp::CHANNEL_AC0
),
board_kernel,
capsules::analog_comparator::DRIVER_NUM,
)
.finalize(components::acomp_component_buf!(
nrf52840::acomp::Comparator
));
let nvmc = static_init!(
nrf52840::nvmc::SyscallDriver,
nrf52840::nvmc::SyscallDriver::new(
&base_peripherals.nvmc,
board_kernel.create_grant(nrf52840::nvmc::DRIVER_NUM, &memory_allocation_capability),
dynamic_deferred_caller,
&mut APP_FLASH_BUFFER,
)
);
nvmc.set_deferred_handle(
dynamic_deferred_caller
.register(nvmc)
.expect("no deferred call slot available for nvmc"),
);
// Configure USB controller
let usb = components::usb_ctap::UsbCtapComponent::new(
board_kernel,
capsules::usb::usb_ctap::DRIVER_NUM,
&nrf52840_peripherals.usbd,
capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840,
VENDOR_ID,
PRODUCT_ID,
STRINGS,
)
.finalize(components::usb_ctap_component_helper!(nrf52840::usbd::Usbd));
nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(());
let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES)
.finalize(components::rr_component_helper!(NUM_PROCS));
let platform = Platform {
button,
pconsole,
console,
led,
gpio,
rng,
alarm,
analog_comparator,
nvmc,
usb,
ipc: kernel::ipc::IPC::new(
board_kernel,
kernel::ipc::DRIVER_NUM,
&memory_allocation_capability,
),
scheduler,
systick: cortexm4::systick::SysTick::new_with_calibration(6400_0000),
};
let _ = platform.pconsole.start();
debug!("Initialization complete. Entering main loop\r");
debug!("{}", &nrf52840::ficr::FICR_INSTANCE);
// These symbols are defined in the linker script.
extern "C" {
/// Beginning of the ROM region containing app images.
static _sapps: u8;
/// End of the ROM region containing app images.
static _eapps: u8;
/// Beginning of the RAM region for app memory.
static mut _sappmem: u8;
/// End of the RAM region for app memory.
static _eappmem: u8;
}
kernel::process::load_processes(
board_kernel,
chip,
core::slice::from_raw_parts(
&_sapps as *const u8,
&_eapps as *const u8 as usize - &_sapps as *const u8 as usize,
),
core::slice::from_raw_parts_mut(
&mut _sappmem as *mut u8,
&_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize,
),
&mut PROCESSES,
&FAULT_RESPONSE,
&process_management_capability,
)
.unwrap_or_else(|err| {
debug!("Error loading processes!");
debug!("{:?}", err);
});
board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability);
}

View File

@@ -12,6 +12,3 @@ capsules = { path = "../../../capsules" }
kernel = { path = "../../../kernel" } kernel = { path = "../../../kernel" }
nrf52840 = { path = "../../../chips/nrf52840" } nrf52840 = { path = "../../../chips/nrf52840" }
nrf52_components = { path = "../nrf52_components" } nrf52_components = { path = "../nrf52_components" }
[features]
vendor_hid = ["capsules/vendor_hid"]

View File

@@ -7,7 +7,8 @@ use kernel::hil::led;
use kernel::hil::uart::{self, Configure}; use kernel::hil::uart::{self, Configure};
use nrf52840::gpio::Pin; use nrf52840::gpio::Pin;
use crate::{CHIP, PROCESSES, PROCESS_PRINTER}; use crate::CHIP;
use crate::PROCESSES;
struct Writer { struct Writer {
initialized: bool, initialized: bool,
@@ -24,13 +25,10 @@ impl Write for Writer {
impl IoWrite for Writer { impl IoWrite for Writer {
fn write(&mut self, buf: &[u8]) { fn write(&mut self, buf: &[u8]) {
// Here, we create a second instance of the Uarte struct. let uart = unsafe { &mut nrf52840::uart::UARTE0 };
// This is okay because we only call this during a panic, and
// we will never actually process the interrupts
let uart = nrf52840::uart::Uarte::new();
if !self.initialized { if !self.initialized {
self.initialized = true; self.initialized = true;
let _ = uart.configure(uart::Parameters { uart.configure(uart::Parameters {
baud_rate: 115200, baud_rate: 115200,
stop_bits: uart::StopBits::One, stop_bits: uart::StopBits::One,
parity: uart::Parity::None, parity: uart::Parity::None,
@@ -53,8 +51,8 @@ impl IoWrite for Writer {
/// Panic handler /// Panic handler
pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! {
// The nRF52840 Dongle LEDs (see back of board) // The nRF52840 Dongle LEDs (see back of board)
let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_23); const LED1_PIN: Pin = Pin::P0_23;
let led = &mut led::LedLow::new(led_kernel_pin); let led = &mut led::LedLow::new(&mut nrf52840::gpio::PORT[LED1_PIN]);
let writer = &mut WRITER; let writer = &mut WRITER;
debug::panic( debug::panic(
&mut [led], &mut [led],
@@ -63,6 +61,5 @@ pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! {
&cortexm4::support::nop, &cortexm4::support::nop,
&PROCESSES, &PROCESSES,
&CHIP, &CHIP,
&PROCESS_PRINTER,
) )
} }

View File

@@ -7,19 +7,16 @@
// Disable this attribute when documenting, as a workaround for // Disable this attribute when documenting, as a workaround for
// https://github.com/rust-lang/rust/issues/62184. // https://github.com/rust-lang/rust/issues/62184.
#![cfg_attr(not(doc), no_main)] #![cfg_attr(not(doc), no_main)]
#![feature(const_in_array_repeat_expressions)]
#![deny(missing_docs)] #![deny(missing_docs)]
use capsules::virtual_alarm::VirtualMuxAlarm; use capsules::virtual_alarm::VirtualMuxAlarm;
use kernel::common::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState};
use kernel::component::Component; use kernel::component::Component;
use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState};
use kernel::hil::led::LedLow;
use kernel::hil::time::Counter;
use kernel::platform::{KernelResources, SyscallDriverLookup, SyscallFilter};
use kernel::scheduler::round_robin::RoundRobinSched;
#[allow(unused_imports)] #[allow(unused_imports)]
use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init};
use kernel::hil::usb::UsbController;
use nrf52840::gpio::Pin; use nrf52840::gpio::Pin;
use nrf52840::interrupt_service::Nrf52840DefaultPeripherals;
use nrf52_components::{self, UartChannel, UartPins}; use nrf52_components::{self, UartChannel, UartPins};
// The nRF52840 MDK USB Dongle LEDs // The nRF52840 MDK USB Dongle LEDs
@@ -29,13 +26,17 @@ const LED1_B_PIN: Pin = Pin::P0_24;
// The nRF52840 Dongle button // The nRF52840 Dongle button
const BUTTON_PIN: Pin = Pin::P0_18; const BUTTON_PIN: Pin = Pin::P0_18;
const _BUTTON_RST_PIN: Pin = Pin::P0_02; const BUTTON_RST_PIN: Pin = Pin::P0_02;
const UART_RTS: Option<Pin> = Some(Pin::P0_21); const UART_RTS: Option<Pin> = Some(Pin::P0_21);
const UART_TXD: Pin = Pin::P0_20; const UART_TXD: Pin = Pin::P0_20;
const UART_CTS: Option<Pin> = Some(Pin::P0_03); const UART_CTS: Option<Pin> = Some(Pin::P0_03);
const UART_RXD: Pin = Pin::P0_19; const UART_RXD: Pin = Pin::P0_19;
// Constants related to the configuration of the 15.4 network stack
const SRC_MAC: u16 = 0xf00f;
const PAN_ID: u16 = 0xABCD;
/// UART Writer /// UART Writer
pub mod io; pub mod io;
@@ -48,41 +49,25 @@ static STRINGS: &'static [&'static str] = &[
"OpenSK", "OpenSK",
// Serial number // Serial number
"v1.0", "v1.0",
// Interface description + main HID string
"FIDO2",
// vendor HID string
"Vendor HID",
]; ];
// State for loading and holding applications. // State for loading and holding applications.
// How should the kernel respond when a process faults. // How should the kernel respond when a process faults.
const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; const FAULT_RESPONSE: kernel::procs::FaultResponse = kernel::procs::FaultResponse::Panic;
// Number of concurrent processes this platform supports. // Number of concurrent processes this platform supports.
const NUM_PROCS: usize = 8; const NUM_PROCS: usize = 8;
static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROCS] =
[None; NUM_PROCS]; [None; NUM_PROCS];
static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 2] = [ static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 1] = [kernel::StorageLocation {
// We implement NUM_PAGES = 20 as 16 + 4 to satisfy the MPU. address: 0xC0000,
kernel::StorageLocation { size: 0x40000,
address: 0xC0000, }];
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
},
];
// Static reference to chip for panic dumps // Static reference to chip for panic dumps
static mut CHIP: Option<&'static nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>> = None; static mut CHIP: Option<&'static nrf52840::chip::Chip> = None;
static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None;
/// Flash buffer for the custom nvmc driver
static mut APP_FLASH_BUFFER: [u8; 0x1000] = [0; 0x1000];
/// Dummy buffer that causes the linker to reserve enough space for the stack. /// Dummy buffer that causes the linker to reserve enough space for the stack.
#[no_mangle] #[no_mangle]
@@ -91,21 +76,23 @@ pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000];
/// Supported drivers by the platform /// Supported drivers by the platform
pub struct Platform { pub struct Platform {
ble_radio: &'static capsules::ble_advertising_driver::BLE<
'static,
nrf52840::ble_radio::Radio<'static>,
VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
>,
ieee802154_radio: &'static capsules::ieee802154::RadioDriver<'static>,
button: &'static capsules::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, button: &'static capsules::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>,
pconsole: &'static capsules::process_console::ProcessConsole< pconsole: &'static capsules::process_console::ProcessConsole<
'static, 'static,
VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
components::process_console::Capability, components::process_console::Capability,
>, >,
console: &'static capsules::console::Console<'static>, console: &'static capsules::console::Console<'static>,
gpio: &'static capsules::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, gpio: &'static capsules::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>,
led: &'static capsules::led::LedDriver< led: &'static capsules::led::LED<'static, nrf52840::gpio::GPIOPin<'static>>,
'static,
kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>,
3,
>,
rng: &'static capsules::rng::RngDriver<'static>, rng: &'static capsules::rng::RngDriver<'static>,
ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, temp: &'static capsules::temperature::TemperatureSensor<'static>,
ipc: kernel::ipc::IPC,
analog_comparator: &'static capsules::analog_comparator::AnalogComparator< analog_comparator: &'static capsules::analog_comparator::AnalogComparator<
'static, 'static,
nrf52840::acomp::Comparator<'static>, nrf52840::acomp::Comparator<'static>,
@@ -114,8 +101,6 @@ pub struct Platform {
'static, 'static,
capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
>, >,
scheduler: &'static RoundRobinSched<'static>,
systick: cortexm4::systick::SysTick,
nvmc: &'static nrf52840::nvmc::SyscallDriver, nvmc: &'static nrf52840::nvmc::SyscallDriver,
usb: &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver< usb: &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver<
'static, 'static,
@@ -124,10 +109,10 @@ pub struct Platform {
>, >,
} }
impl SyscallDriverLookup for Platform { impl kernel::Platform for Platform {
fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
where where
F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, F: FnOnce(Option<&dyn kernel::Driver>) -> R,
{ {
match driver_num { match driver_num {
capsules::console::DRIVER_NUM => f(Some(self.console)), capsules::console::DRIVER_NUM => f(Some(self.console)),
@@ -136,6 +121,9 @@ impl SyscallDriverLookup for Platform {
capsules::led::DRIVER_NUM => f(Some(self.led)), capsules::led::DRIVER_NUM => f(Some(self.led)),
capsules::button::DRIVER_NUM => f(Some(self.button)), capsules::button::DRIVER_NUM => f(Some(self.button)),
capsules::rng::DRIVER_NUM => f(Some(self.rng)), capsules::rng::DRIVER_NUM => f(Some(self.rng)),
capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)),
capsules::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)),
capsules::temperature::DRIVER_NUM => f(Some(self.temp)),
capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)),
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)), nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)), capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
@@ -143,87 +131,33 @@ impl SyscallDriverLookup for Platform {
_ => f(None), _ => f(None),
} }
} }
}
impl SyscallFilter for Platform {
fn filter_syscall( fn filter_syscall(
&self, &self,
process: &dyn kernel::process::Process, process: &dyn kernel::procs::ProcessType,
syscall: &kernel::syscall::Syscall, syscall: &kernel::syscall::Syscall,
) -> Result<(), kernel::errorcode::ErrorCode> { ) -> Result<(), kernel::ReturnCode> {
use kernel::syscall::Syscall; use kernel::syscall::Syscall;
match *syscall { match *syscall {
Syscall::Command { Syscall::COMMAND {
driver_number: nrf52840::nvmc::DRIVER_NUM, driver_number: nrf52840::nvmc::DRIVER_NUM,
subdriver_number: cmd, subdriver_number: cmd,
arg0: ptr, arg0: ptr,
arg1: len, arg1: len,
} if (cmd == 2 || cmd == 3) && !process.fits_in_storage_location(ptr, len) => { } if (cmd == 2 || cmd == 3) && !process.fits_in_storage_location(ptr, len) => {
Err(kernel::ErrorCode::INVAL) Err(kernel::ReturnCode::EINVAL)
} }
_ => Ok(()), _ => Ok(()),
} }
} }
} }
/// This is in a separate, inline(never) function so that its stack frame is /// Entry point in the vector table called on hard reset.
/// removed when this function returns. Otherwise, the stack space used for
/// these static_inits is wasted.
#[inline(never)]
unsafe fn get_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> {
// Initialize chip peripheral drivers
let nrf52840_peripherals = static_init!(
Nrf52840DefaultPeripherals,
Nrf52840DefaultPeripherals::new()
);
nrf52840_peripherals
}
impl KernelResources<nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>>
for Platform
{
type SyscallDriverLookup = Self;
type SyscallFilter = Self;
type ProcessFault = ();
type Scheduler = RoundRobinSched<'static>;
type SchedulerTimer = cortexm4::systick::SysTick;
type WatchDog = ();
type ContextSwitchCallback = ();
fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup {
&self
}
fn syscall_filter(&self) -> &Self::SyscallFilter {
&self
}
fn process_fault(&self) -> &Self::ProcessFault {
&()
}
fn scheduler(&self) -> &Self::Scheduler {
self.scheduler
}
fn scheduler_timer(&self) -> &Self::SchedulerTimer {
&self.systick
}
fn watchdog(&self) -> &Self::WatchDog {
&()
}
fn context_switch_callback(&self) -> &Self::ContextSwitchCallback {
&()
}
}
/// Main function called after RAM initialized.
#[no_mangle] #[no_mangle]
pub unsafe fn main() { pub unsafe fn reset_handler() {
// Loads relocations and clears BSS // Loads relocations and clears BSS
nrf52840::init(); nrf52840::init();
// Initialize chip peripheral drivers
let nrf52840_peripherals = get_peripherals();
// set up circular peripheral dependencies
nrf52840_peripherals.init();
let base_peripherals = &nrf52840_peripherals.nrf52;
let board_kernel = static_init!( let board_kernel = static_init!(
kernel::Kernel, kernel::Kernel,
kernel::Kernel::new_with_storage(&PROCESSES, &STORAGE_LOCATIONS) kernel::Kernel::new_with_storage(&PROCESSES, &STORAGE_LOCATIONS)
@@ -231,26 +165,23 @@ pub unsafe fn main() {
// GPIOs // GPIOs
let gpio = components::gpio::GpioComponent::new( let gpio = components::gpio::GpioComponent::new(
board_kernel, board_kernel,
capsules::gpio::DRIVER_NUM,
components::gpio_component_helper!( components::gpio_component_helper!(
nrf52840::gpio::GPIOPin, nrf52840::gpio::GPIOPin,
// left side of the USB plug. Right side is used for UART // left side of the USB plug. Right side is used for UART
0 => &nrf52840_peripherals.gpio_port[Pin::P0_04], 0 => &nrf52840::gpio::PORT[Pin::P0_04],
1 => &nrf52840_peripherals.gpio_port[Pin::P0_05], 1 => &nrf52840::gpio::PORT[Pin::P0_05],
2 => &nrf52840_peripherals.gpio_port[Pin::P0_06], 2 => &nrf52840::gpio::PORT[Pin::P0_06],
3 => &nrf52840_peripherals.gpio_port[Pin::P0_07], 3 => &nrf52840::gpio::PORT[Pin::P0_07],
4 => &nrf52840_peripherals.gpio_port[Pin::P0_08] 4 => &nrf52840::gpio::PORT[Pin::P0_08]
), ),
) ).finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin));
.finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin));
let button = components::button::ButtonComponent::new( let button = components::button::ButtonComponent::new(
board_kernel, board_kernel,
capsules::button::DRIVER_NUM,
components::button_component_helper!( components::button_component_helper!(
nrf52840::gpio::GPIOPin, nrf52840::gpio::GPIOPin,
( (
&nrf52840_peripherals.gpio_port[BUTTON_PIN], &nrf52840::gpio::PORT[BUTTON_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow, kernel::hil::gpio::ActivationMode::ActiveLow,
kernel::hil::gpio::FloatingState::PullUp kernel::hil::gpio::FloatingState::PullUp
) )
@@ -258,19 +189,33 @@ pub unsafe fn main() {
) )
.finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin)); .finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin));
let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( let led = components::led::LedsComponent::new(components::led_component_helper!(
LedLow<'static, nrf52840::gpio::GPIOPin>, nrf52840::gpio::GPIOPin,
LedLow::new(&nrf52840_peripherals.gpio_port[LED1_R_PIN]), (
LedLow::new(&nrf52840_peripherals.gpio_port[LED1_G_PIN]), &nrf52840::gpio::PORT[LED1_R_PIN],
LedLow::new(&nrf52840_peripherals.gpio_port[LED1_B_PIN]), kernel::hil::gpio::ActivationMode::ActiveLow
)); ),
(
&nrf52840::gpio::PORT[LED1_G_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow
),
(
&nrf52840::gpio::PORT[LED1_B_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow
)
))
.finalize(components::led_component_buf!(nrf52840::gpio::GPIOPin));
let chip = static_init!( let chip = static_init!(nrf52840::chip::Chip, nrf52840::chip::new());
nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>,
nrf52840::chip::NRF52::new(nrf52840_peripherals)
);
CHIP = Some(chip); CHIP = Some(chip);
nrf52_components::startup::NrfStartupComponent::new(
false,
BUTTON_RST_PIN,
nrf52840::uicr::Regulator0Output::V3_0,
)
.finalize(());
// Create capabilities that the board needs to call certain protected kernel // Create capabilities that the board needs to call certain protected kernel
// functions. // functions.
let process_management_capability = let process_management_capability =
@@ -278,7 +223,7 @@ pub unsafe fn main() {
let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let main_loop_capability = create_capability!(capabilities::MainLoopCapability);
let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability);
let gpio_port = &nrf52840_peripherals.gpio_port; let gpio_port = &nrf52840::gpio::PORT;
// Configure kernel debug gpios as early as possible // Configure kernel debug gpios as early as possible
kernel::debug::assign_gpios( kernel::debug::assign_gpios(
@@ -287,23 +232,14 @@ pub unsafe fn main() {
Some(&gpio_port[LED1_B_PIN]), Some(&gpio_port[LED1_B_PIN]),
); );
let rtc = &base_peripherals.rtc; let rtc = &nrf52840::rtc::RTC;
let _ = rtc.start(); rtc.start();
let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc)
.finalize(components::alarm_mux_component_helper!(nrf52840::rtc::Rtc)); .finalize(components::alarm_mux_component_helper!(nrf52840::rtc::Rtc));
let alarm = components::alarm::AlarmDriverComponent::new( let alarm = components::alarm::AlarmDriverComponent::new(board_kernel, mux_alarm)
board_kernel, .finalize(components::alarm_component_helper!(nrf52840::rtc::Rtc));
capsules::alarm::DRIVER_NUM,
mux_alarm,
)
.finalize(components::alarm_component_helper!(nrf52840::rtc::Rtc));
let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD));
let channel = nrf52_components::UartChannelComponent::new( let channel = nrf52_components::UartChannelComponent::new(uart_channel, mux_alarm).finalize(());
uart_channel,
mux_alarm,
&base_peripherals.uarte0,
)
.finalize(());
let dynamic_deferred_call_clients = let dynamic_deferred_call_clients =
static_init!([DynamicDeferredCallClientState; 2], Default::default()); static_init!([DynamicDeferredCallClientState; 2], Default::default());
@@ -312,51 +248,53 @@ pub unsafe fn main() {
DynamicDeferredCall::new(dynamic_deferred_call_clients) DynamicDeferredCall::new(dynamic_deferred_call_clients)
); );
DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); DynamicDeferredCall::set_global_instance(dynamic_deferred_caller);
let process_printer =
components::process_printer::ProcessPrinterTextComponent::new().finalize(());
PROCESS_PRINTER = Some(process_printer);
// Create a shared UART channel for the console and for kernel debug. // Create a shared UART channel for the console and for kernel debug.
let uart_mux = let uart_mux =
components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller) components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller)
.finalize(()); .finalize(());
let pconsole = components::process_console::ProcessConsoleComponent::new( let pconsole =
board_kernel, components::process_console::ProcessConsoleComponent::new(board_kernel, uart_mux)
uart_mux, .finalize(());
mux_alarm,
process_printer,
)
.finalize(components::process_console_component_helper!(
nrf52840::rtc::Rtc<'static>
));
// Setup the console. // Setup the console.
let console = components::console::ConsoleComponent::new( let console = components::console::ConsoleComponent::new(board_kernel, uart_mux).finalize(());
board_kernel,
capsules::console::DRIVER_NUM,
uart_mux,
)
.finalize(components::console_component_helper!());
// Create the debugger object that handles calls to `debug!()`. // Create the debugger object that handles calls to `debug!()`.
components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(());
let rng = components::rng::RngComponent::new(
let ble_radio =
nrf52_components::BLEComponent::new(board_kernel, &nrf52840::ble_radio::RADIO, mux_alarm)
.finalize(());
let (ieee802154_radio, _mux_mac) = components::ieee802154::Ieee802154Component::new(
board_kernel, board_kernel,
capsules::rng::DRIVER_NUM, &nrf52840::ieee802154_radio::RADIO,
&base_peripherals.trng, &nrf52840::aes::AESECB,
PAN_ID,
SRC_MAC,
)
.finalize(components::ieee802154_component_helper!(
nrf52840::ieee802154_radio::Radio,
nrf52840::aes::AesECB<'static>
));
let temp = components::temperature::TemperatureComponent::new(
board_kernel,
&nrf52840::temperature::TEMP,
) )
.finalize(()); .finalize(());
let rng = components::rng::RngComponent::new(board_kernel, &nrf52840::trng::TRNG).finalize(());
// Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02) // Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02)
// These are hardcoded pin assignments specified in the driver // These are hardcoded pin assignments specified in the driver
let analog_comparator = components::analog_comparator::AcComponent::new( let analog_comparator = components::analog_comparator::AcComponent::new(
&base_peripherals.acomp, &nrf52840::acomp::ACOMP,
components::acomp_component_helper!( components::acomp_component_helper!(
nrf52840::acomp::Channel, nrf52840::acomp::Channel,
&nrf52840::acomp::CHANNEL_AC0 &nrf52840::acomp::CHANNEL_AC0
), ),
board_kernel,
capsules::analog_comparator::DRIVER_NUM,
) )
.finalize(components::acomp_component_buf!( .finalize(components::acomp_component_buf!(
nrf52840::acomp::Comparator nrf52840::acomp::Comparator
@@ -365,60 +303,78 @@ pub unsafe fn main() {
let nvmc = static_init!( let nvmc = static_init!(
nrf52840::nvmc::SyscallDriver, nrf52840::nvmc::SyscallDriver,
nrf52840::nvmc::SyscallDriver::new( nrf52840::nvmc::SyscallDriver::new(
&base_peripherals.nvmc, &nrf52840::nvmc::NVMC,
board_kernel.create_grant(nrf52840::nvmc::DRIVER_NUM, &memory_allocation_capability), board_kernel.create_grant(&memory_allocation_capability),
dynamic_deferred_caller,
&mut APP_FLASH_BUFFER,
) )
); );
nvmc.set_deferred_handle(
dynamic_deferred_caller
.register(nvmc)
.expect("no deferred call slot available for nvmc"),
);
// Configure USB controller // Configure USB controller
let usb = components::usb_ctap::UsbCtapComponent::new( let usb:
board_kernel, &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver<
capsules::usb::usb_ctap::DRIVER_NUM, 'static,
&nrf52840_peripherals.usbd, 'static,
capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840, nrf52840::usbd::Usbd<'static>,
VENDOR_ID, > = {
PRODUCT_ID, let usb_ctap = static_init!(
STRINGS, capsules::usb::usbc_ctap_hid::ClientCtapHID<
) 'static,
.finalize(components::usb_ctap_component_helper!(nrf52840::usbd::Usbd)); 'static,
nrf52840::usbd::Usbd<'static>,
>,
capsules::usb::usbc_ctap_hid::ClientCtapHID::new(
&nrf52840::usbd::USBD,
capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840,
VENDOR_ID,
PRODUCT_ID,
STRINGS,
)
);
nrf52840::usbd::USBD.set_client(usb_ctap);
nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); // Enable power events to be sent to USB controller
nrf52840::power::POWER.set_usb_client(&nrf52840::usbd::USBD);
nrf52840::power::POWER.enable_interrupts();
let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) // Configure the USB userspace driver
.finalize(components::rr_component_helper!(NUM_PROCS)); let usb_driver = static_init!(
capsules::usb::usb_ctap::CtapUsbSyscallDriver<
'static,
'static,
nrf52840::usbd::Usbd<'static>,
>,
capsules::usb::usb_ctap::CtapUsbSyscallDriver::new(
usb_ctap,
board_kernel.create_grant(&memory_allocation_capability)
)
);
usb_ctap.set_client(usb_driver);
usb_driver as &'static _
};
nrf52_components::NrfClockComponent::new().finalize(());
let platform = Platform { let platform = Platform {
button, button,
ble_radio,
ieee802154_radio,
pconsole, pconsole,
console, console,
led, led,
gpio, gpio,
rng, rng,
temp,
alarm, alarm,
analog_comparator, analog_comparator,
nvmc, nvmc,
usb, usb,
ipc: kernel::ipc::IPC::new( ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
board_kernel,
kernel::ipc::DRIVER_NUM,
&memory_allocation_capability,
),
scheduler,
systick: cortexm4::systick::SysTick::new_with_calibration(64000000),
}; };
let _ = platform.pconsole.start(); platform.pconsole.start();
debug!("Initialization complete. Entering main loop\r"); debug!("Initialization complete. Entering main loop\r");
debug!("{}", &nrf52840::ficr::FICR_INSTANCE); debug!("{}", &nrf52840::ficr::FICR_INSTANCE);
// These symbols are defined in the linker script. /// These symbols are defined in the linker script.
extern "C" { extern "C" {
/// Beginning of the ROM region containing app images. /// Beginning of the ROM region containing app images.
static _sapps: u8; static _sapps: u8;
@@ -430,7 +386,7 @@ pub unsafe fn main() {
static _eappmem: u8; static _eappmem: u8;
} }
kernel::process::load_processes( kernel::procs::load_processes(
board_kernel, board_kernel,
chip, chip,
core::slice::from_raw_parts( core::slice::from_raw_parts(
@@ -442,7 +398,7 @@ pub unsafe fn main() {
&_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize,
), ),
&mut PROCESSES, &mut PROCESSES,
&FAULT_RESPONSE, FAULT_RESPONSE,
&process_management_capability, &process_management_capability,
) )
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
@@ -450,5 +406,13 @@ pub unsafe fn main() {
debug!("{:?}", err); debug!("{:?}", err);
}); });
board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES)
.finalize(components::rr_component_helper!(NUM_PROCS));
board_kernel.kernel_loop(
&platform,
chip,
Some(&platform.ipc),
scheduler,
&main_loop_capability,
);
} }

View File

@@ -1,17 +0,0 @@
[package]
name = "nrf52840dk_opensk"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
build = "build.rs"
edition = "2018"
[dependencies]
components = { path = "../../components" }
cortexm4 = { path = "../../../arch/cortex-m4" }
capsules = { path = "../../../capsules" }
kernel = { path = "../../../kernel" }
nrf52840 = { path = "../../../chips/nrf52840" }
nrf52_components = { path = "../nrf52_components" }
[features]
vendor_hid = ["capsules/vendor_hid"]

View File

@@ -1,31 +0,0 @@
# Makefile for building the tock kernel for the nRF development kit
TARGET=thumbv7em-none-eabi
PLATFORM=nrf52840dk_opensk
include ../../Makefile.common
TOCKLOADER=tockloader
# Where in the SAM4L flash to load the kernel with `tockloader`
KERNEL_ADDRESS=0x00000
# Upload programs over uart with tockloader
ifdef PORT
TOCKLOADER_GENERAL_FLAGS += --port $(PORT)
endif
# Upload the kernel over JTAG
.PHONY: flash
flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --jlink $<
# Upload the kernel over JTAG using OpenOCD
.PHONY: flash-openocd
flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --openocd $<
# Upload the kernel over serial/bootloader
.PHONY: program
program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex
$(error Cannot program nRF52840DK over USB. Use \`make flash\` and JTAG)

View File

@@ -1,65 +0,0 @@
Platform-Specific Instructions: nRF52840-DK
===================================
This is an adapted nrf52840dk made to work with OpenSK.
The [nRF52840 Development
Kit](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) is a platform
based around the nRF52840, an SoC with an ARM Cortex-M4 and a BLE
radio. The kit is Arduino shield compatible and includes several
buttons.
## Getting Started
First, follow the [Tock Getting Started guide](../../../doc/Getting_Started.md)
JTAG is the preferred method to program. The development kit has an
integrated JTAG debugger, you simply need to [install JTAG
software](../../../doc/Getting_Started.md#loading-the-kernel-onto-a-board).
## Programming the kernel
Once you have all software installed, you should be able to simply run
make flash in this directory to install a fresh kernel.
## Programming user-level applications
You can program an application over USB using the integrated JTAG and `tockloader`:
```bash
$ cd libtock-c/examples/<app>
$ make
$ tockloader install --jlink --board nrf52dk
```
The same options (`--jlink --board nrf52dk`) must be passed for other tockloader commands
such as `erase-apps` or `list`.
Viewing console output on the nrf52840dk is slightly different from other boards. You must use
```bash
$ tockloader listen
```
**followed by a press of the reset button** in order to view console output starting from the boot
sequence. Notably, you should not
pass the `--jlink` option to `tockloader listen`.
## Console output
This board supports two methods for writing messages to a console interface
(console driver for applications as well as debug statements in the kernel).
By default, messages are written to a UART interface over the GPIO pins `P0.05`
to `P0.08` (see the [main.rs](src/main.rs) file).
If you don't have any UART cables or want to use a different interface, there is
also a console over the Segger RTT protocol. This only requires a micro-USB
cable on the USB debugging port (the same used to flash Tock on the board), and
is enabled by setting the `USB_DEBUGGING` constant to `true` in the
[main.rs](src/main.rs) file.
This disables the UART interface.
For instructions about how to receive RTT messages on the host, see the
[corresponding capsule](../../../capsules/src/segger_rtt.rs).
## Debugging
See the [nrf52dk README](../nrf52dk/README.md) for information about debugging
the nRF52840dk.

View File

@@ -1,29 +0,0 @@
use std::env;
use std::fs;
use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=layout.ld");
println!("cargo:rerun-if-changed=../../kernel_layout.ld");
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("locations.rs");
fs::write(
&dest_path,
"
static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 2] = [
// We implement NUM_PAGES = 20 as 16 + 4 to satisfy the MPU.
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
},
];
"
).unwrap();
}

View File

@@ -1,25 +0,0 @@
#
#
#
# J-LINK GDB SERVER initialization
#
# This connects to a GDB Server listening
# for commands on localhost at tcp port 2331
target remote localhost:2331
monitor speed 30
file ../../../../target/thumbv7em-none-eabi/release/nrf52dk
monitor reset
#
# CPU core initialization (to be done by user)
#
# Set the processor mode
# monitor reg cpsr = 0xd3
# Set auto JTAG speed
monitor speed auto
# Setup GDB FOR FASTER DOWNLOADS
set remote memory-write-packet-size 1024
set remote memory-write-packet-size fixed
# tui enable
# layout split
# layout service_pending_interrupts
b reset_handler

View File

@@ -1 +0,0 @@
JLinkGDBServer -device nrf52 -speed 1200 -if swd -AutoConnect 1 -port 2331

View File

@@ -1,2 +0,0 @@
INCLUDE ../nrf52840_chip_layout.ld
INCLUDE ../../kernel_layout.ld

View File

@@ -1,108 +0,0 @@
use core::fmt::Write;
use core::panic::PanicInfo;
use cortexm4;
use kernel::debug;
use kernel::debug::IoWrite;
use kernel::hil::led;
use kernel::hil::uart;
use kernel::hil::uart::Configure;
use nrf52840::gpio::Pin;
use crate::CHIP;
use crate::PROCESSES;
use crate::PROCESS_PRINTER;
enum Writer {
WriterUart(/* initialized */ bool),
WriterRtt(&'static capsules::segger_rtt::SeggerRttMemory<'static>),
}
static mut WRITER: Writer = Writer::WriterUart(false);
fn wait() {
for _ in 0..100 {
cortexm4::support::nop();
}
}
/// Set the RTT memory buffer used to output panic messages.
pub unsafe fn set_rtt_memory(
rtt_memory: &'static mut capsules::segger_rtt::SeggerRttMemory<'static>,
) {
WRITER = Writer::WriterRtt(rtt_memory);
}
impl Write for Writer {
fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
self.write(s.as_bytes());
Ok(())
}
}
impl IoWrite for Writer {
fn write(&mut self, buf: &[u8]) {
match self {
Writer::WriterUart(ref mut initialized) => {
// Here, we create a second instance of the Uarte struct.
// This is okay because we only call this during a panic, and
// we will never actually process the interrupts
let uart = nrf52840::uart::Uarte::new();
if !*initialized {
*initialized = true;
let _ = uart.configure(uart::Parameters {
baud_rate: 115200,
stop_bits: uart::StopBits::One,
parity: uart::Parity::None,
hw_flow_control: false,
width: uart::Width::Eight,
});
}
for &c in buf {
unsafe {
uart.send_byte(c);
}
while !uart.tx_ready() {}
}
}
Writer::WriterRtt(rtt_memory) => {
let up_buffer = unsafe { &*rtt_memory.get_up_buffer_ptr() };
let buffer_len = up_buffer.length.get();
let buffer = unsafe {
core::slice::from_raw_parts_mut(
up_buffer.buffer.get() as *mut u8,
buffer_len as usize,
)
};
let mut write_position = up_buffer.write_position.get();
for &c in buf {
buffer[write_position as usize] = c;
write_position = (write_position + 1) % buffer_len;
up_buffer.write_position.set(write_position);
wait();
}
}
};
}
}
#[cfg(not(test))]
#[no_mangle]
#[panic_handler]
/// Panic handler
pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! {
// The nRF52840DK LEDs (see back of board)
let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13);
let led = &mut led::LedLow::new(led_kernel_pin);
let writer = &mut WRITER;
debug::panic(
&mut [led],
writer,
pi,
&cortexm4::support::nop,
&PROCESSES,
&CHIP,
&PROCESS_PRINTER,
)
}

View File

@@ -1,569 +0,0 @@
//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK).
//!
//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with
//! many exported I/O and peripherals.
//!
//! Pin Configuration
//! -------------------
//!
//! ### `GPIO`
//!
//! | # | Pin | Ix | Header | Arduino |
//! |----|-------|----|--------|---------|
//! | 0 | P1.01 | 33 | P3 1 | D0 |
//! | 1 | P1.02 | 34 | P3 2 | D1 |
//! | 2 | P1.03 | 35 | P3 3 | D2 |
//! | 3 | P1.04 | 36 | P3 4 | D3 |
//! | 4 | P1.05 | 37 | P3 5 | D4 |
//! | 5 | P1.06 | 38 | P3 6 | D5 |
//! | 6 | P1.07 | 39 | P3 7 | D6 |
//! | 7 | P1.08 | 40 | P3 8 | D7 |
//! | 8 | P1.10 | 42 | P4 1 | D8 |
//! | 9 | P1.11 | 43 | P4 2 | D9 |
//! | 10 | P1.12 | 44 | P4 3 | D10 |
//! | 11 | P1.13 | 45 | P4 4 | D11 |
//! | 12 | P1.14 | 46 | P4 5 | D12 |
//! | 13 | P1.15 | 47 | P4 6 | D13 |
//! | 14 | P0.26 | 26 | P4 9 | D14 |
//! | 15 | P0.27 | 27 | P4 10 | D15 |
//!
//! ### `GPIO` / Analog Inputs
//!
//! | # | Pin | Header | Arduino |
//! |----|------------|--------|---------|
//! | 16 | P0.03 AIN1 | P2 1 | A0 |
//! | 17 | P0.04 AIN2 | P2 2 | A1 |
//! | 18 | P0.28 AIN4 | P2 3 | A2 |
//! | 19 | P0.29 AIN5 | P2 4 | A3 |
//! | 20 | P0.30 AIN6 | P2 5 | A4 |
//! | 21 | P0.31 AIN7 | P2 6 | A5 |
//! | 22 | P0.02 AIN0 | P4 8 | AVDD |
//!
//! ### Onboard Functions
//!
//! | Pin | Header | Function |
//! |-------|--------|----------|
//! | P0.05 | P6 3 | UART RTS |
//! | P0.06 | P6 4 | UART TXD |
//! | P0.07 | P6 5 | UART CTS |
//! | P0.08 | P6 6 | UART RXT |
//! | P0.11 | P24 1 | Button 1 |
//! | P0.12 | P24 2 | Button 2 |
//! | P0.13 | P24 3 | LED 1 |
//! | P0.14 | P24 4 | LED 2 |
//! | P0.15 | P24 5 | LED 3 |
//! | P0.16 | P24 6 | LED 4 |
//! | P0.18 | P24 8 | Reset |
//! | P0.19 | P24 9 | SPI CLK |
//! | P0.20 | P24 10 | SPI MOSI |
//! | P0.21 | P24 11 | SPI MISO |
//! | P0.24 | P24 14 | Button 3 |
//! | P0.25 | P24 15 | Button 4 |
#![no_std]
// Disable this attribute when documenting, as a workaround for
// https://github.com/rust-lang/rust/issues/62184.
#![cfg_attr(not(doc), no_main)]
#![deny(missing_docs)]
use capsules::virtual_alarm::VirtualMuxAlarm;
use core::env;
use kernel::component::Component;
use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState};
use kernel::hil::led::LedLow;
use kernel::hil::time::Counter;
use kernel::platform::{KernelResources, SyscallDriverLookup, SyscallFilter};
use kernel::scheduler::round_robin::RoundRobinSched;
#[allow(unused_imports)]
use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init};
use nrf52840::gpio::Pin;
use nrf52840::interrupt_service::Nrf52840DefaultPeripherals;
use nrf52_components::{self, UartChannel, UartPins};
// The nRF52840DK LEDs (see back of board)
const LED1_PIN: Pin = Pin::P0_13;
const LED2_PIN: Pin = Pin::P0_14;
const LED3_PIN: Pin = Pin::P0_15;
const LED4_PIN: Pin = Pin::P0_16;
// The nRF52840DK buttons (see back of board)
const BUTTON1_PIN: Pin = Pin::P0_11;
const BUTTON2_PIN: Pin = Pin::P0_12;
const BUTTON3_PIN: Pin = Pin::P0_24;
const BUTTON4_PIN: Pin = Pin::P0_25;
const BUTTON_RST_PIN: Pin = Pin::P0_18;
const UART_RTS: Option<Pin> = Some(Pin::P0_05);
const UART_TXD: Pin = Pin::P0_06;
const UART_CTS: Option<Pin> = Some(Pin::P0_07);
const UART_RXD: Pin = Pin::P0_08;
const SPI_MOSI: Pin = Pin::P0_20;
const SPI_MISO: Pin = Pin::P0_21;
const SPI_CLK: Pin = Pin::P0_19;
/// Debug Writer
pub mod io;
// Whether to use UART debugging or Segger RTT (USB) debugging.
// - Set to false to use UART.
// - Set to true to use Segger RTT over USB.
const USB_DEBUGGING: bool = true;
const VENDOR_ID: u16 = 0x1915; // Nordic Semiconductor
const PRODUCT_ID: u16 = 0x521f; // nRF52840 Dongle (PCA10059)
static STRINGS: &'static [&'static str] = &[
// Manufacturer
"Nordic Semiconductor ASA",
// Product
"OpenSK",
// Serial number
"v1.0",
// Interface description + main HID string
"FIDO2",
// vendor HID string
"Vendor HID",
];
// State for loading and holding applications.
// How should the kernel respond when a process faults.
const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {};
// Number of concurrent processes this platform supports.
const NUM_PROCS: usize = 8;
static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] =
[None; NUM_PROCS];
include!(concat!(env!("OUT_DIR"), "/locations.rs"));
static mut CHIP: Option<&'static nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>> = None;
static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None;
/// Flash buffer for the custom nvmc driver
static mut APP_FLASH_BUFFER: [u8; 0x1000] = [0; 0x1000];
/// Dummy buffer that causes the linker to reserve enough space for the stack.
#[no_mangle]
#[link_section = ".stack_buffer"]
pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000];
/// Supported drivers by the platform
pub struct Platform {
button: &'static capsules::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>,
pconsole: &'static capsules::process_console::ProcessConsole<
'static,
VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
components::process_console::Capability,
>,
console: &'static capsules::console::Console<'static>,
gpio: &'static capsules::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>,
led: &'static capsules::led::LedDriver<
'static,
kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>,
4,
>,
rng: &'static capsules::rng::RngDriver<'static>,
ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>,
analog_comparator: &'static capsules::analog_comparator::AnalogComparator<
'static,
nrf52840::acomp::Comparator<'static>,
>,
alarm: &'static capsules::alarm::AlarmDriver<
'static,
capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>,
>,
nvmc: &'static nrf52840::nvmc::SyscallDriver,
usb: &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver<
'static,
'static,
nrf52840::usbd::Usbd<'static>,
>,
scheduler: &'static RoundRobinSched<'static>,
systick: cortexm4::systick::SysTick,
}
impl SyscallDriverLookup for Platform {
fn with_driver<F, R>(&self, driver_num: usize, f: F) -> R
where
F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R,
{
match driver_num {
capsules::console::DRIVER_NUM => f(Some(self.console)),
capsules::gpio::DRIVER_NUM => f(Some(self.gpio)),
capsules::alarm::DRIVER_NUM => f(Some(self.alarm)),
capsules::led::DRIVER_NUM => f(Some(self.led)),
capsules::button::DRIVER_NUM => f(Some(self.button)),
capsules::rng::DRIVER_NUM => f(Some(self.rng)),
capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)),
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
_ => f(None),
}
}
}
impl SyscallFilter for Platform {
fn filter_syscall(
&self,
process: &dyn kernel::process::Process,
syscall: &kernel::syscall::Syscall,
) -> Result<(), kernel::errorcode::ErrorCode> {
use kernel::syscall::Syscall;
match *syscall {
Syscall::Command {
driver_number: nrf52840::nvmc::DRIVER_NUM,
subdriver_number: cmd,
arg0: ptr,
arg1: len,
} if (cmd == 2 || cmd == 3) && !process.fits_in_storage_location(ptr, len) => {
Err(kernel::ErrorCode::INVAL)
}
_ => Ok(()),
}
}
}
/// This is in a separate, inline(never) function so that its stack frame is
/// removed when this function returns. Otherwise, the stack space used for
/// these static_inits is wasted.
#[inline(never)]
unsafe fn get_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> {
// Initialize chip peripheral drivers
let nrf52840_peripherals = static_init!(
Nrf52840DefaultPeripherals,
Nrf52840DefaultPeripherals::new()
);
nrf52840_peripherals
}
impl KernelResources<nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>>
for Platform
{
type SyscallDriverLookup = Self;
type SyscallFilter = Self;
type ProcessFault = ();
type Scheduler = RoundRobinSched<'static>;
type SchedulerTimer = cortexm4::systick::SysTick;
type WatchDog = ();
type ContextSwitchCallback = ();
fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup {
&self
}
fn syscall_filter(&self) -> &Self::SyscallFilter {
&self
}
fn process_fault(&self) -> &Self::ProcessFault {
&()
}
fn scheduler(&self) -> &Self::Scheduler {
self.scheduler
}
fn scheduler_timer(&self) -> &Self::SchedulerTimer {
&self.systick
}
fn watchdog(&self) -> &Self::WatchDog {
&()
}
fn context_switch_callback(&self) -> &Self::ContextSwitchCallback {
&()
}
}
/// Main function called after RAM initialized.
#[no_mangle]
pub unsafe fn main() {
// Loads relocations and clears BSS
nrf52840::init();
// Initialize chip peripheral drivers
let nrf52840_peripherals = get_peripherals();
// set up circular peripheral dependencies
nrf52840_peripherals.init();
let base_peripherals = &nrf52840_peripherals.nrf52;
let uart_channel = if USB_DEBUGGING {
// Initialize early so any panic beyond this point can use the RTT memory object.
let mut rtt_memory_refs =
components::segger_rtt::SeggerRttMemoryComponent::new().finalize(());
// XXX: This is inherently unsafe as it aliases the mutable reference to rtt_memory. This
// aliases reference is only used inside a panic handler, which should be OK, but maybe we
// should use a const reference to rtt_memory and leverage interior mutability instead.
self::io::set_rtt_memory(&mut *rtt_memory_refs.get_rtt_memory_ptr());
UartChannel::Rtt(rtt_memory_refs)
} else {
UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD))
};
let board_kernel = static_init!(
kernel::Kernel,
kernel::Kernel::new_with_storage(&PROCESSES, &STORAGE_LOCATIONS)
);
let gpio = components::gpio::GpioComponent::new(
board_kernel,
capsules::gpio::DRIVER_NUM,
components::gpio_component_helper!(
nrf52840::gpio::GPIOPin,
0 => &nrf52840_peripherals.gpio_port[Pin::P1_01],
1 => &nrf52840_peripherals.gpio_port[Pin::P1_02],
2 => &nrf52840_peripherals.gpio_port[Pin::P1_03],
3 => &nrf52840_peripherals.gpio_port[Pin::P1_04],
4 => &nrf52840_peripherals.gpio_port[Pin::P1_05],
5 => &nrf52840_peripherals.gpio_port[Pin::P1_06],
6 => &nrf52840_peripherals.gpio_port[Pin::P1_07],
7 => &nrf52840_peripherals.gpio_port[Pin::P1_08],
8 => &nrf52840_peripherals.gpio_port[Pin::P1_10],
9 => &nrf52840_peripherals.gpio_port[Pin::P1_11],
10 => &nrf52840_peripherals.gpio_port[Pin::P1_12],
11 => &nrf52840_peripherals.gpio_port[Pin::P1_13],
12 => &nrf52840_peripherals.gpio_port[Pin::P1_14],
13 => &nrf52840_peripherals.gpio_port[Pin::P1_15],
14 => &nrf52840_peripherals.gpio_port[Pin::P0_26],
15 => &nrf52840_peripherals.gpio_port[Pin::P0_27]
),
)
.finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin));
let button = components::button::ButtonComponent::new(
board_kernel,
capsules::button::DRIVER_NUM,
components::button_component_helper!(
nrf52840::gpio::GPIOPin,
(
&nrf52840_peripherals.gpio_port[BUTTON1_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow,
kernel::hil::gpio::FloatingState::PullUp
), //13
(
&nrf52840_peripherals.gpio_port[BUTTON2_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow,
kernel::hil::gpio::FloatingState::PullUp
), //14
(
&nrf52840_peripherals.gpio_port[BUTTON3_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow,
kernel::hil::gpio::FloatingState::PullUp
), //15
(
&nrf52840_peripherals.gpio_port[BUTTON4_PIN],
kernel::hil::gpio::ActivationMode::ActiveLow,
kernel::hil::gpio::FloatingState::PullUp
) //16
),
)
.finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin));
let led = components::led::LedsComponent::new().finalize(components::led_component_helper!(
LedLow<'static, nrf52840::gpio::GPIOPin>,
LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]),
LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]),
LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]),
LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]),
));
let chip = static_init!(
nrf52840::chip::NRF52<Nrf52840DefaultPeripherals>,
nrf52840::chip::NRF52::new(nrf52840_peripherals)
);
CHIP = Some(chip);
nrf52_components::startup::NrfStartupComponent::new(
false,
BUTTON_RST_PIN,
nrf52840::uicr::Regulator0Output::DEFAULT,
&base_peripherals.nvmc,
)
.finalize(());
// Create capabilities that the board needs to call certain protected kernel
// functions.
let process_management_capability =
create_capability!(capabilities::ProcessManagementCapability);
let main_loop_capability = create_capability!(capabilities::MainLoopCapability);
let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability);
let gpio_port = &nrf52840_peripherals.gpio_port;
// Configure kernel debug gpios as early as possible
kernel::debug::assign_gpios(
Some(&gpio_port[LED1_PIN]),
Some(&gpio_port[LED2_PIN]),
Some(&gpio_port[LED3_PIN]),
);
let rtc = &base_peripherals.rtc;
let _ = rtc.start();
let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc)
.finalize(components::alarm_mux_component_helper!(nrf52840::rtc::Rtc));
let alarm = components::alarm::AlarmDriverComponent::new(
board_kernel,
capsules::alarm::DRIVER_NUM,
mux_alarm,
)
.finalize(components::alarm_component_helper!(nrf52840::rtc::Rtc));
let channel = nrf52_components::UartChannelComponent::new(
uart_channel,
mux_alarm,
&base_peripherals.uarte0,
)
.finalize(());
let dynamic_deferred_call_clients =
static_init!([DynamicDeferredCallClientState; 3], Default::default());
let dynamic_deferred_caller = static_init!(
DynamicDeferredCall,
DynamicDeferredCall::new(dynamic_deferred_call_clients)
);
DynamicDeferredCall::set_global_instance(dynamic_deferred_caller);
let process_printer =
components::process_printer::ProcessPrinterTextComponent::new().finalize(());
PROCESS_PRINTER = Some(process_printer);
// Create a shared UART channel for the console and for kernel debug.
let uart_mux =
components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller)
.finalize(());
let pconsole = components::process_console::ProcessConsoleComponent::new(
board_kernel,
uart_mux,
mux_alarm,
process_printer,
)
.finalize(components::process_console_component_helper!(
nrf52840::rtc::Rtc<'static>
));
// Setup the console.
let console = components::console::ConsoleComponent::new(
board_kernel,
capsules::console::DRIVER_NUM,
uart_mux,
)
.finalize(components::console_component_helper!());
// Create the debugger object that handles calls to `debug!()`.
components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(());
let rng = components::rng::RngComponent::new(
board_kernel,
capsules::rng::DRIVER_NUM,
&base_peripherals.trng,
)
.finalize(());
base_peripherals.spim0.configure(
nrf52840::pinmux::Pinmux::new(SPI_MOSI as u32),
nrf52840::pinmux::Pinmux::new(SPI_MISO as u32),
nrf52840::pinmux::Pinmux::new(SPI_CLK as u32),
);
// Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02)
// These are hardcoded pin assignments specified in the driver
let analog_comparator = components::analog_comparator::AcComponent::new(
&base_peripherals.acomp,
components::acomp_component_helper!(
nrf52840::acomp::Channel,
&nrf52840::acomp::CHANNEL_AC0
),
board_kernel,
capsules::analog_comparator::DRIVER_NUM,
)
.finalize(components::acomp_component_buf!(
nrf52840::acomp::Comparator
));
let nvmc = static_init!(
nrf52840::nvmc::SyscallDriver,
nrf52840::nvmc::SyscallDriver::new(
&base_peripherals.nvmc,
board_kernel.create_grant(nrf52840::nvmc::DRIVER_NUM, &memory_allocation_capability),
dynamic_deferred_caller,
&mut APP_FLASH_BUFFER,
)
);
nvmc.set_deferred_handle(
dynamic_deferred_caller
.register(nvmc)
.expect("no deferred call slot available for nvmc"),
);
// Configure USB controller
let usb = components::usb_ctap::UsbCtapComponent::new(
board_kernel,
capsules::usb::usb_ctap::DRIVER_NUM,
&nrf52840_peripherals.usbd,
capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840,
VENDOR_ID,
PRODUCT_ID,
STRINGS,
)
.finalize(components::usb_ctap_component_helper!(nrf52840::usbd::Usbd));
let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES)
.finalize(components::rr_component_helper!(NUM_PROCS));
nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(());
let platform = Platform {
button,
pconsole,
console,
led,
gpio,
rng,
alarm,
analog_comparator,
nvmc,
usb,
ipc: kernel::ipc::IPC::new(
board_kernel,
kernel::ipc::DRIVER_NUM,
&memory_allocation_capability,
),
scheduler,
systick: cortexm4::systick::SysTick::new_with_calibration(64000000),
};
let _ = platform.pconsole.start();
debug!("Initialization complete. Entering main loop\r");
debug!("{}", &nrf52840::ficr::FICR_INSTANCE);
// These symbols are defined in the linker script.
extern "C" {
/// Beginning of the ROM region containing app images.
static _sapps: u8;
/// End of the ROM region containing app images.
static _eapps: u8;
/// Beginning of the RAM region for app memory.
static mut _sappmem: u8;
/// End of the RAM region for app memory.
static _eappmem: u8;
}
kernel::process::load_processes(
board_kernel,
chip,
core::slice::from_raw_parts(
&_sapps as *const u8,
&_eapps as *const u8 as usize - &_sapps as *const u8 as usize,
),
core::slice::from_raw_parts_mut(
&mut _sappmem as *mut u8,
&_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize,
),
&mut PROCESSES,
&FAULT_RESPONSE,
&process_management_capability,
)
.unwrap_or_else(|err| {
debug!("Error loading processes!");
debug!("{:?}", err);
});
board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability);
}

View File

@@ -1,21 +0,0 @@
[package]
name = "nrf52840dk_opensk_a"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
build = "build.rs"
edition = "2018"
[[bin]]
path = "../nrf52840dk_opensk/src/main.rs"
name = "nrf52840dk_opensk_a"
[dependencies]
components = { path = "../../components" }
cortexm4 = { path = "../../../arch/cortex-m4" }
capsules = { path = "../../../capsules" }
kernel = { path = "../../../kernel" }
nrf52840 = { path = "../../../chips/nrf52840" }
nrf52_components = { path = "../nrf52_components" }
[features]
vendor_hid = ["capsules/vendor_hid"]

View File

@@ -1,31 +0,0 @@
# Makefile for building the tock kernel for the nRF development kit
TARGET=thumbv7em-none-eabi
PLATFORM=nrf52840dk_opensk_a
include ../../Makefile.common
TOCKLOADER=tockloader
# Where in the SAM4L flash to load the kernel with `tockloader`
KERNEL_ADDRESS=0x20000
# Upload programs over uart with tockloader
ifdef PORT
TOCKLOADER_GENERAL_FLAGS += --port $(PORT)
endif
# Upload the kernel over JTAG
.PHONY: flash
flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --jlink $<
# Upload the kernel over JTAG using OpenOCD
.PHONY: flash-openocd
flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --openocd $<
# Upload the kernel over serial/bootloader
.PHONY: program
program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex
$(error Cannot program nRF52840DK over USB. Use \`make flash\` and JTAG)

View File

@@ -1,11 +0,0 @@
Platform-Specific Instructions: nRF52840-DK, partition A
===================================
This is an upgrade partition for the adapted nrf52840dk in `../nrf52840dk_opensk`.
Compared to our regular board definition for the nrf52840dk, changes are:
- a `layout.ld` with 128 kB for kernel and app
- the matching kernel address in the `Makefile`
- different `StorageLocation`s in `build.rs`
For everything else, please check the README in `../nrf52840dk_opensk`.

View File

@@ -1,45 +0,0 @@
use std::env;
use std::fs;
use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=layout.ld");
println!("cargo:rerun-if-changed=../../kernel_layout.ld");
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("locations.rs");
fs::write(
&dest_path,
"
static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 5] = [
// We implement NUM_PAGES = 20 as 16 + 4 to satisfy the MPU.
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
},
// Partitions can also be split to maximize MPU happiness.
kernel::StorageLocation {
address: 0x4000,
size: 0x2000,
storage_type: kernel::StorageType::Partition,
},
kernel::StorageLocation {
address: 0x60000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
},
kernel::StorageLocation {
address: 0x80000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
},
];
"
).unwrap();
}

View File

@@ -1,13 +0,0 @@
/* Memory Space Definitions, 1M flash, 256K ram */
MEMORY
{
rom (rx) : ORIGIN = 0x00020000, LENGTH = 128K
prog (rx) : ORIGIN = 0x00040000, LENGTH = 128K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
}
MPU_MIN_ALIGN = 8K;
PAGE_SIZE = 4K;
INCLUDE ../../kernel_layout.ld

View File

@@ -1,21 +0,0 @@
[package]
name = "nrf52840dk_opensk_b"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
build = "build.rs"
edition = "2018"
[[bin]]
path = "../nrf52840dk_opensk/src/main.rs"
name = "nrf52840dk_opensk_b"
[dependencies]
components = { path = "../../components" }
cortexm4 = { path = "../../../arch/cortex-m4" }
capsules = { path = "../../../capsules" }
kernel = { path = "../../../kernel" }
nrf52840 = { path = "../../../chips/nrf52840" }
nrf52_components = { path = "../nrf52_components" }
[features]
vendor_hid = ["capsules/vendor_hid"]

View File

@@ -1,31 +0,0 @@
# Makefile for building the tock kernel for the nRF development kit
TARGET=thumbv7em-none-eabi
PLATFORM=nrf52840dk_opensk_b
include ../../Makefile.common
TOCKLOADER=tockloader
# Where in the SAM4L flash to load the kernel with `tockloader`
KERNEL_ADDRESS=0x60000
# Upload programs over uart with tockloader
ifdef PORT
TOCKLOADER_GENERAL_FLAGS += --port $(PORT)
endif
# Upload the kernel over JTAG
.PHONY: flash
flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --jlink $<
# Upload the kernel over JTAG using OpenOCD
.PHONY: flash-openocd
flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --openocd $<
# Upload the kernel over serial/bootloader
.PHONY: program
program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex
$(error Cannot program nRF52840DK over USB. Use \`make flash\` and JTAG)

View File

@@ -1,11 +0,0 @@
Platform-Specific Instructions: nRF52840-DK, partition B
===================================
This is an upgrade partition for the adapted nrf52840dk in `../nrf52840dk_opensk`.
Compared to our regular board definition for the nrf52840dk, changes are:
- a `layout.ld` with 128 kB for kernel and app
- the matching kernel address in the `Makefile`
- different `StorageLocation`s in `build.rs`
For everything else, please check the README in `../nrf52840dk_opensk`.

View File

@@ -1,45 +0,0 @@
use std::env;
use std::fs;
use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=layout.ld");
println!("cargo:rerun-if-changed=../../kernel_layout.ld");
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("locations.rs");
fs::write(
&dest_path,
"
static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 5] = [
// We implement NUM_PAGES = 20 as 16 + 4 to satisfy the MPU.
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
},
// Partitions can also be split to maximize MPU happiness.
kernel::StorageLocation {
address: 0x4000,
size: 0x2000,
storage_type: kernel::StorageType::Partition,
},
kernel::StorageLocation {
address: 0x20000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
},
kernel::StorageLocation {
address: 0x40000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
},
];
"
).unwrap();
}

View File

@@ -1,13 +0,0 @@
/* Memory Space Definitions, 1M flash, 256K ram */
MEMORY
{
rom (rx) : ORIGIN = 0x00060000, LENGTH = 128K
prog (rx) : ORIGIN = 0x00080000, LENGTH = 128K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
}
MPU_MIN_ALIGN = 8K;
PAGE_SIZE = 4K;
INCLUDE ../../kernel_layout.ld

View File

@@ -1,2 +0,0 @@
[target.thumbv7em-none-eabi]
linker = "arm-none-eabi-gcc"

282
bootloader/Cargo.lock generated
View File

@@ -1,282 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aligned"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a785a543aea40f5e4e2e93bb2655d31bc21bb391fff65697150973e383f16bb"
dependencies = [
"as-slice",
]
[[package]]
name = "as-slice"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0"
dependencies = [
"generic-array 0.12.4",
"generic-array 0.13.3",
"generic-array 0.14.7",
"stable_deref_trait",
]
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version",
]
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bootloader"
version = "0.1.0"
dependencies = [
"byteorder",
"cortex-m 0.6.7",
"cortex-m-rt",
"cortex-m-rt-macros",
"panic-abort",
"rtt-target",
"tock-registers",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cortex-m"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9075300b07c6a56263b9b582c214d0ff037b00d45ec9fde1cc711490c56f1bb9"
dependencies = [
"aligned",
"bare-metal",
"bitfield",
"cortex-m 0.7.7",
"volatile-register",
]
[[package]]
name = "cortex-m"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [
"bare-metal",
"bitfield",
"embedded-hal",
"volatile-register",
]
[[package]]
name = "cortex-m-rt"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "panic-abort"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e20e6499bbbc412f280b04a42346b356c6fa0753d5fd22b7bd752ff34c778ee"
[[package]]
name = "proc-macro2"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rtt-target"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "065d6058bb1204f51a562a67209e1817cf714759d5cf845aa45c75fa7b0b9d9b"
dependencies = [
"cortex-m 0.7.7",
"ufmt-write",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tock-registers"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee8fba06c1f4d0b396ef61a54530bb6b28f0dc61c38bc8bc5a5a48161e6282e"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ufmt-write"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
dependencies = [
"vcell",
]

View File

@@ -1,29 +0,0 @@
[package]
name = "bootloader"
version = "0.1.0"
authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
]
build = "build.rs"
license = "Apache-2.0"
edition = "2018"
[dependencies]
byteorder = { version = "1", default-features = false }
cortex-m = "^0.6.0"
cortex-m-rt = "*"
cortex-m-rt-macros = "*"
panic-abort = "0.3.2"
rtt-target = { version = "*", features = ["cortex-m"] }
tock-registers = "0.7.0"
[profile.dev]
panic = "abort"
lto = true
opt-level = 3
[profile.release]
panic = "abort"
lto = true
# Level "z" may decrease the binary size more of necessary.
opt-level = 3

View File

@@ -1,36 +0,0 @@
# OpenSK Bootloader
This bootloader supports upgradability for OpenSK. Its functionality is to
- check images on A/B partitions,
- boot the most recent valid partition.
## How to use
The bootloader is built and deployed by OpenSK's `deploy.py`. If your board
defines a metadata address, it is detected as an upgradable board and this
bootloader is flashed to memory address 0.
## How to debug
The bootloader prints debug message over RTT when compiled in debug mode. Using
`nrfjprog` for flashing and inspecting memory is recommended for debugging.
```shell
RUSTFLAGS="-C link-arg=-Wl,-Tlink.x -C link-arg=-nostartfiles" \
cargo build --target thumbv7em-none-eabi
llvm-objcopy -O ihex target/thumbv7em-none-eabi/debug/bootloader \
target/thumbv7em-none-eabi/debug/bootloader.hex
nrfjprog --program target/thumbv7em-none-eabi/debug/bootloader.hex \
--sectorerase -f nrf52 --reset
```
To read the debug messages, open two terminals for:
```shell
JLinkRTTLogger -device NRF52840_XXAA -if swd -speed 1000 -RTTchannel 0
JLinkRTTClient
```
The first command also logs the output to a file. The second shows all output in
real time.

View File

@@ -1,21 +0,0 @@
/* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 16K
RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 128K
}

View File

@@ -1,118 +0,0 @@
// Copyright 2020-2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use tock_registers::register_bitfields;
register_bitfields! [u32,
// Generic or shared bitfields
pub Task [
ENABLE OFFSET(0) NUMBITS(1)
],
pub Byte [
VALUE OFFSET(0) NUMBITS(8)
],
pub Busy [
/// Asserted when AES_BUSY or DES_BUSY or HASH_BUSY are asserted or when the DIN FIFO is not empty
BUSY OFFSET(0) NUMBITS(1) [
Ready = 0,
Busy = 1
]
],
// CC_CTL register bitfields
pub CryptoMode [
/// Determines the active cryptographic engine
MODE OFFSET(0) NUMBITS(5) [
Bypass = 0,
Aes = 1,
AesToHash = 2,
AesAndHash = 3,
Des = 4,
DesToHash = 5,
DesAndHash = 6,
Hash = 7,
AesMacAndBypass = 9,
AesToHashAndDout = 10
]
],
// HOST_RGF register bitfields
pub Interrupts [
/// This interrupt is asserted when all data was delivered to DIN buffer from SRAM
SRAM_TO_DIN OFFSET(4) NUMBITS(1),
/// This interrupt is asserted when all data was delivered to SRAM buffer from DOUT
DOUT_TO_SRAM OFFSET(5) NUMBITS(1),
/// This interrupt is asserted when all data was delivered to DIN buffer from memory
MEM_TO_DIN OFFSET(6) NUMBITS(1),
/// This interrupt is asserted when all data was delivered to memory buffer from DOUT
DOUT_TO_MEM OFFSET(7) NUMBITS(1),
AXI_ERROR OFFSET(8) NUMBITS(1),
/// The PKA end of operation interrupt status
PKA_EXP OFFSET(9) NUMBITS(1),
/// The RNG interrupt status
RNG OFFSET(10) NUMBITS(1),
/// The GPR interrupt status
SYM_DMA_COMPLETED OFFSET(11) NUMBITS(1)
],
pub RgfEndianness [
/// DOUT write endianness
DOUT_WR_BG OFFSET(3) NUMBITS(1) [
LittleEndian = 0,
BigEndian = 1
],
/// DIN write endianness
DIN_RD_BG OFFSET(7) NUMBITS(1) [
LittleEndian = 0,
BigEndian = 1
],
/// DOUT write word endianness
DOUT_WR_WBG OFFSET(11) NUMBITS(1) [
LittleEndian = 0,
BigEndian = 1
],
/// DIN write word endianness
DIN_RD_WBG OFFSET(15) NUMBITS(1) [
LittleEndian = 0,
BigEndian = 1
]
],
// DIN and DOUT register bitfields
pub LliWord1 [
/// Total number of bytes to read using DMA in this entry
BYTES_NUM OFFSET(0) NUMBITS(30),
/// Indicates the first LLI entry
FIRST OFFSET(30) NUMBITS(1),
/// Indicates the last LLI entry
LAST OFFSET(31) NUMBITS(1)
],
pub HashControl [
// bit 2 is reserved but to simplify the logic we include it in the bitfield.
MODE OFFSET(0) NUMBITS(4) [
MD5 = 0,
SHA1 = 1,
SHA256 = 2,
SHA224 = 10
]
],
pub PaddingConfig [
/// Enable Padding generation. must be reset upon completion of padding.
DO_PAD OFFSET(2) NUMBITS(1)
]
];

View File

@@ -1,284 +0,0 @@
// Copyright 2019-2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! CryptoCell 310
//!
//! Author
//! -------------------
//!
//! * Author: Jean-Michel Picod <jmichel@google.com>
//! * Date: October 1 2019
use super::bitfields;
use super::registers::{CryptoCellRegisters, NordicCC310Registers};
use super::static_ref::StaticRef;
use core::cell::Cell;
#[cfg(debug_assertions)]
use rtt_target::rprintln;
use tock_registers::interfaces::{Readable, Writeable};
const SHA256_INIT_VALUE: [u32; 8] = [
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
];
#[derive(Copy, Clone)]
enum DigestAlgorithm {
Sha256 = 2,
}
#[derive(Copy, Clone)]
enum OperationMode {
Idle,
Hash,
}
pub struct CryptoCell310 {
registers: StaticRef<CryptoCellRegisters>,
power: StaticRef<NordicCC310Registers>,
current_op: Cell<OperationMode>,
hash_ctx: Cell<[u32; 8]>,
hash_total_size: Cell<u64>,
}
const CC310_BASE: StaticRef<CryptoCellRegisters> =
unsafe { StaticRef::new(0x5002B000 as *const CryptoCellRegisters) };
const CC310_POWER: StaticRef<NordicCC310Registers> =
unsafe { StaticRef::new(0x5002A500 as *const NordicCC310Registers) };
// Identification "signature" for CryptoCell. According to the documentation, the value
// held by this register is a fixed value, used by Host driver to verify CryptoCell presence
// at this address.
// This value was read from a CryptoCell-310 on a nRF52840-dongle kit.
const CC310_SIGNATURE: u32 = 0x20E00000;
impl CryptoCell310 {
/// Creates a new instance of cryptocell state.
pub const fn new() -> Self {
CryptoCell310 {
registers: CC310_BASE,
power: CC310_POWER,
current_op: Cell::new(OperationMode::Idle),
hash_ctx: Cell::new(SHA256_INIT_VALUE),
hash_total_size: Cell::new(0),
}
}
fn enable(&self) {
self.power.enable.write(bitfields::Task::ENABLE::SET);
for _i in 1..10 {
let read_signature = self.registers.host_rgf.signature.get();
if read_signature != CC310_SIGNATURE {
#[cfg(debug_assertions)]
rprintln!(
"[loop {}] Invalid CC310 signature. Expected {}, got {}\n",
_i,
CC310_SIGNATURE,
read_signature
);
} else {
break;
}
}
if self.registers.host_rgf.signature.get() != CC310_SIGNATURE {
panic!("Failed to initialize CC310");
}
// Make sure everything is set to little endian
self.registers.host_rgf.endian.write(
bitfields::RgfEndianness::DOUT_WR_BG::LittleEndian
+ bitfields::RgfEndianness::DIN_RD_BG::LittleEndian
+ bitfields::RgfEndianness::DOUT_WR_WBG::LittleEndian
+ bitfields::RgfEndianness::DIN_RD_WBG::LittleEndian,
);
// Always start the clock for DMA engine. It's too hard to keep
// track of which submodule needs DMA otherwise.
self.registers
.misc
.dma_clk_enable
.write(bitfields::Task::ENABLE::SET);
self.registers.host_rgf.interrupt_mask.write(
bitfields::Interrupts::SRAM_TO_DIN::CLEAR
+ bitfields::Interrupts::DOUT_TO_SRAM::CLEAR
+ bitfields::Interrupts::MEM_TO_DIN::CLEAR
+ bitfields::Interrupts::DOUT_TO_MEM::CLEAR
+ bitfields::Interrupts::AXI_ERROR::SET
+ bitfields::Interrupts::PKA_EXP::SET
+ bitfields::Interrupts::RNG::SET
+ bitfields::Interrupts::SYM_DMA_COMPLETED::CLEAR,
);
}
fn disable(&self) {
self.registers.host_rgf.interrupt_mask.set(0);
self.power.enable.write(bitfields::Task::ENABLE::CLEAR);
self.registers
.misc
.dma_clk_enable
.write(bitfields::Task::ENABLE::CLEAR);
}
fn clear_data(&self) {
let mut ctx = self.hash_ctx.get();
ctx.iter_mut().for_each(|b| *b = 0);
self.hash_ctx.set(ctx);
self.hash_total_size.set(0);
}
/// Adds data to the current hash computation.
///
/// You have to know in advance if is this is going to be the last block, and indicate that
/// correctly. Sizes of chunks before the last need to be multiples of 64.
pub fn update(&self, data: &[u8], is_last_block: bool) {
// Start CryptoCell
self.enable();
while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {}
while self
.registers
.ctrl
.crypto_busy
.is_set(bitfields::Busy::BUSY)
{}
while self
.registers
.din
.mem_dma_busy
.is_set(bitfields::Busy::BUSY)
{}
// Start HASH module and configure it
self.current_op.set(OperationMode::Hash);
self.registers
.misc
.hash_clk_enable
.write(bitfields::Task::ENABLE::SET);
self.registers
.ctrl
.crypto_ctl
.write(bitfields::CryptoMode::MODE::Hash);
self.registers
.hash
.padding
.write(bitfields::Task::ENABLE::SET);
let size = self.hash_total_size.get();
self.registers.hash.hash_len_lsb.set(size as u32);
self.registers
.hash
.hash_len_msb
.set(size.wrapping_shr(32) as u32);
self.registers
.hash
.control
.set(DigestAlgorithm::Sha256 as u32);
// Digest must be set backwards because writing to HASH[0]
// starts computation
let mut digest = self.hash_ctx.get();
for i in (0..digest.len()).rev() {
self.registers.hash.hash[i].set(digest[i]);
}
while self.registers.ctrl.hash_busy.is_set(bitfields::Busy::BUSY) {}
// Process data
if !data.is_empty() {
if is_last_block {
self.registers
.hash
.auto_hw_padding
.write(bitfields::Task::ENABLE::SET);
}
self.registers.din.src_lli_word0.set(data.as_ptr() as u32);
self.registers
.din
.src_lli_word1
.write(bitfields::LliWord1::BYTES_NUM.val(data.len() as u32));
while !self
.registers
.host_rgf
.interrupts
.is_set(bitfields::Interrupts::MEM_TO_DIN)
{}
self.registers
.host_rgf
.interrupt_clear
.write(bitfields::Interrupts::MEM_TO_DIN::SET);
} else {
// use DO_PAD to complete padding of previous operation
self.registers
.hash
.pad_config
.write(bitfields::PaddingConfig::DO_PAD::SET);
}
while self
.registers
.ctrl
.crypto_busy
.is_set(bitfields::Busy::BUSY)
{}
while self
.registers
.din
.mem_dma_busy
.is_set(bitfields::Busy::BUSY)
{}
// Update context and total size
for i in (0..digest.len()).rev() {
digest[i] = self.registers.hash.hash[i].get();
}
self.hash_ctx.set(digest);
let new_size: u64 = ((self.registers.hash.hash_len_msb.get() as u64) << 32)
+ (self.registers.hash.hash_len_lsb.get() as u64);
self.hash_total_size.set(new_size);
// Disable HASH module
self.registers
.hash
.padding
.write(bitfields::Task::ENABLE::SET);
self.registers
.hash
.auto_hw_padding
.write(bitfields::Task::ENABLE::CLEAR);
self.registers
.hash
.pad_config
.write(bitfields::PaddingConfig::DO_PAD::CLEAR);
while self
.registers
.ctrl
.crypto_busy
.is_set(bitfields::Busy::BUSY)
{}
self.registers
.misc
.hash_clk_enable
.write(bitfields::Task::ENABLE::CLEAR);
self.disable();
}
/// Clears the data for potential reuse, and returns the result.
pub fn finalize_and_clear(&self) -> [u8; 32] {
use byteorder::{BigEndian, ByteOrder};
let words = self.hash_ctx.get();
let mut bytes = [0u8; 32];
for (i, word) in words.iter().enumerate() {
BigEndian::write_u32(&mut bytes[4 * i..4 * i + 4], *word);
}
self.clear_data();
bytes
}
}

View File

@@ -1,178 +0,0 @@
// Copyright 2021-2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![no_main]
#![no_std]
mod bitfields;
mod crypto_cell;
mod registers;
mod static_ref;
extern crate cortex_m;
extern crate cortex_m_rt as rt;
use byteorder::{ByteOrder, LittleEndian};
use core::convert::TryInto;
use core::ptr;
use cortex_m::asm;
use panic_abort as _;
use rt::entry;
#[cfg(debug_assertions)]
use rtt_target::{rprintln, rtt_init_print};
/// Size of a flash page in bytes.
const PAGE_SIZE: usize = 0x1000;
const METADATA_SIGN_OFFSET: usize = 0x800;
/// A flash page.
type Page = [u8; PAGE_SIZE];
/// Reads a page of memory.
unsafe fn read_page(address: usize) -> Page {
debug_assert!(address % PAGE_SIZE == 0);
let address_pointer = address as *const Page;
ptr::read(address_pointer)
}
/// Parsed metadata for a firmware partition.
struct Metadata {
checksum: [u8; 32],
_signature: [u8; 64],
version: u64,
address: u32,
}
/// Reads the metadata from a flash page.
impl From<Page> for Metadata {
fn from(page: Page) -> Self {
Metadata {
checksum: page[0..32].try_into().unwrap(),
_signature: page[32..96].try_into().unwrap(),
version: LittleEndian::read_u64(&page[METADATA_SIGN_OFFSET..][..8]),
address: LittleEndian::read_u32(&page[METADATA_SIGN_OFFSET + 8..][..4]),
}
}
}
/// Location of a firmware partition's data.
struct BootPartition {
firmware_address: usize,
metadata_address: usize,
}
impl BootPartition {
const FIRMWARE_LENGTH: usize = 0x00040000;
/// Reads the metadata, returns the firmware version if all checks pass.
pub fn read_version(&self) -> Result<u64, ()> {
let metadata_page = unsafe { read_page(self.metadata_address) };
let hash_value = self.compute_upgrade_hash(&metadata_page);
let metadata = Metadata::from(metadata_page);
if self.firmware_address != metadata.address as usize {
#[cfg(debug_assertions)]
rprintln!(
"Partition address mismatch: expected 0x{:08X}, metadata 0x{:08X}",
self.firmware_address,
metadata.address as usize
);
return Err(());
}
if hash_value != metadata.checksum {
#[cfg(debug_assertions)]
rprintln!("Hash mismatch");
return Err(());
}
Ok(metadata.version)
}
/// Computes the SHA256 of metadata information and partition data.
///
/// Assumes that firmware address and length are divisible by the page size.
/// This is the hardware implementation on the cryptocell.
#[allow(clippy::assertions_on_constants)]
fn compute_upgrade_hash(&self, metadata_page: &[u8]) -> [u8; 32] {
debug_assert!(self.firmware_address % PAGE_SIZE == 0);
debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0);
let cc310 = crypto_cell::CryptoCell310::new();
cc310.update(&metadata_page[METADATA_SIGN_OFFSET..], false);
for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) {
let page = unsafe { read_page(self.firmware_address + page_offset) };
cc310.update(
&page,
page_offset + PAGE_SIZE == BootPartition::FIRMWARE_LENGTH,
);
}
cc310.finalize_and_clear()
}
/// Jump to the firmware.
pub fn boot(&self) -> ! {
let address = self.firmware_address;
// Clear any pending Cryptocell interrupt in NVIC
let peripherals = cortex_m::Peripherals::take().unwrap();
unsafe {
// We could only clear cryptocell interrupts, but let's clean up before booting.
// Example code to clear more specifically:
// const CC310_IRQ: u16 = 42;
// peripherals.NVIC.icpr[usize::from(CC310_IRQ / 32)].write(1 << (CC310_IRQ % 32));
peripherals.NVIC.icer[0].write(0xffff_ffff);
peripherals.NVIC.icpr[0].write(0xffff_ffff);
peripherals.NVIC.icer[1].write(0xffff_ffff);
peripherals.NVIC.icpr[1].write(0xffff_ffff);
}
#[cfg(debug_assertions)]
rprintln!("Boot jump to {:08X}", address);
let address_pointer = address as *const u32;
// https://docs.rs/cortex-m/0.7.2/cortex_m/asm/fn.bootload.html
unsafe { asm::bootload(address_pointer) };
}
}
#[entry]
fn main() -> ! {
#[cfg(debug_assertions)]
rtt_init_print!();
#[cfg(debug_assertions)]
rprintln!("Starting bootloader");
let partition_a = BootPartition {
firmware_address: 0x20000,
metadata_address: 0x4000,
};
let partition_b = BootPartition {
firmware_address: 0x60000,
metadata_address: 0x5000,
};
#[cfg(debug_assertions)]
rprintln!("Reading partition A");
let version_a = partition_a.read_version();
#[cfg(debug_assertions)]
rprintln!("Reading partition B");
let version_b = partition_b.read_version();
match (version_a, version_b) {
(Ok(t1), Ok(t2)) => {
if t1 >= t2 {
partition_a.boot()
} else {
partition_b.boot()
}
}
(Ok(_), Err(_)) => partition_a.boot(),
(Err(_), Ok(_)) => partition_b.boot(),
(Err(_), Err(_)) => panic!(),
}
}

View File

@@ -1,137 +0,0 @@
// Copyright 2020-2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::bitfields::{
Busy, CryptoMode, HashControl, Interrupts, LliWord1, PaddingConfig, RgfEndianness, Task,
};
use tock_registers::register_structs;
use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly};
register_structs! {
pub CryptoCellControlRegisters {
/// Defines the cryptographic flow
(0x0000 => pub crypto_ctl: WriteOnly<u32, CryptoMode::Register>),
(0x0004 => _reserved0),
/// This register is set whent the cryptographic core is busy
(0x0010 => pub crypto_busy: ReadOnly<u32, Busy::Register>),
(0x0014 => _reserved1),
/// This register is set when the Hash engine is busy
(0x001C => pub hash_busy: ReadOnly<u32, Busy::Register>),
(0x0020 => @END),
}
}
register_structs! {
pub CryptoCellDinRegisters {
(0x0000 => _reserved0),
/// Indicates whether memoty (AXI) source DMA (DIN) is busy
(0x0020 => pub mem_dma_busy: ReadOnly<u32, Busy::Register>),
(0x0024 => _reserved1),
/// This register is used in direct LLI mode - holds the location of the data source
/// in the memory (AXI)
(0x0028 => pub src_lli_word0: WriteOnly<u32>),
/// This register is used in direct LLI mode - holds the number of bytes to be read
/// from the memory (AXI).
/// Writing to this register triggers the DMA.
(0x002C => pub src_lli_word1: WriteOnly<u32, LliWord1::Register>),
(0x0030 => @END),
}
}
register_structs! {
pub CryptoCellHashRegisters {
/// Write initial hash value or read final hash value
(0x0000 => pub hash: [ReadWrite<u32>; 9]),
(0x0024 => _reserved0),
/// HW padding automatically activated by engine.
/// For the special case of ZERO bytes data vector this register should not be used! instead use HASH_PAD_CFG
(0x0044 => pub auto_hw_padding: WriteOnly<u32, Task::Register>),
(0x0048 => _reserved1),
/// Selects which HASH mode to run
(0x0180 => pub control: ReadWrite<u32, HashControl::Register>),
/// This register enables the hash hw padding.
(0x0184 => pub padding: ReadWrite<u32, Task::Register>),
/// HASH_PAD_CFG Register.
(0x0188 => pub pad_config: ReadWrite<u32, PaddingConfig::Register>),
/// This register hold the length of current hash operation
(0x018C => pub hash_len_lsb: ReadWrite<u32>),
/// This register hold the length of current hash operation
(0x0190 => pub hash_len_msb: ReadWrite<u32>),
(0x0194 => @END),
}
}
register_structs! {
pub CryptoCellHostRgfRegisters {
/// The Interrupt Request register.
/// Each bit of this register holds the interrupt status of a single interrupt source.
(0x0000 => pub interrupts: ReadOnly<u32, Interrupts::Register>),
/// The Interrupt Mask register. Each bit of this register holds the mask of a single
/// interrupt source.
(0x0004 => pub interrupt_mask: ReadWrite<u32, Interrupts::Register>),
/// Interrupt Clear Register
(0x0008 => pub interrupt_clear: WriteOnly<u32, Interrupts::Register>),
/// This register defines the endianness of the Host-accessible registers.
(0x000C => pub endian: ReadWrite<u32, RgfEndianness::Register>),
(0x0010 => _reserved0),
/// This register holds the CryptoCell product signature.
(0x0024 => pub signature: ReadOnly<u32>),
(0x0028 => @END),
}
}
register_structs! {
pub CryptoCellMiscRegisters {
(0x0000 => _reserved0),
/// The HASH clock enable register
(0x0018 => pub hash_clk_enable: ReadWrite<u32, Task::Register>),
/// The PKA clock enable register
(0x001C => _reserved1),
/// The DMA clock enable register
(0x0020 => pub dma_clk_enable: ReadWrite<u32, Task::Register>),
/// the CryptoCell clocks' status register
(0x0024 => @END),
}
}
register_structs! {
pub NordicCC310Registers {
(0x0000 => pub enable: ReadWrite<u32, Task::Register>),
(0x0004 => @END),
},
pub CryptoCellRegisters {
(0x0000 => _reserved0),
/// HASH registers
/// - Base address: 0x0640
(0x0640 => pub hash: CryptoCellHashRegisters),
(0x07D4 => _reserved1),
/// Misc registers
/// - Base address: 0x0800
(0x0800 => pub misc: CryptoCellMiscRegisters),
(0x0824 => _reserved2),
/// CryptoCell control registers
/// - Base address: 0x0900
(0x0900 => pub ctrl: CryptoCellControlRegisters),
(0x0920 => _reserved3),
/// HOST_RGF registers
/// - Base address: 0x0A00
(0x0A00 => pub host_rgf: CryptoCellHostRgfRegisters),
(0x0A28 => _reserved4),
/// DIN registers
/// - Base address: 0x0C00
(0x0C00 => pub din: CryptoCellDinRegisters),
(0x0C30 => @END),
}
}

View File

@@ -1,46 +0,0 @@
//! Wrapper type for safe pointers to static memory.
//!
//! Imported from:
//! https://github.com/tock/tock/blob/master/kernel/src/utilities/static_ref.rs
use core::ops::Deref;
/// A pointer to statically allocated mutable data such as memory mapped I/O
/// registers.
///
/// This is a simple wrapper around a raw pointer that encapsulates an unsafe
/// dereference in a safe manner. It serve the role of creating a `&'static T`
/// given a raw address and acts similarly to `extern` definitions, except
/// `StaticRef` is subject to module and crate boundaries, while `extern`
/// definitions can be imported anywhere.
#[derive(Debug)]
pub struct StaticRef<T> {
ptr: *const T,
}
impl<T> StaticRef<T> {
/// Create a new `StaticRef` from a raw pointer
///
/// ## Safety
///
/// Callers must pass in a reference to statically allocated memory which
/// does not overlap with other values.
pub const unsafe fn new(ptr: *const T) -> StaticRef<T> {
StaticRef { ptr }
}
}
impl<T> Clone for StaticRef<T> {
fn clone(&self) -> Self {
StaticRef { ptr: self.ptr }
}
}
impl<T> Copy for StaticRef<T> {}
impl<T> Deref for StaticRef<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.ptr }
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2019-2021 Google LLC // Copyright 2019 Google LLC
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -12,46 +12,24 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
extern crate alloc; use std::env;
use openssl::{bn, ec, nid};
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::Read;
use std::io::Write;
use std::path::Path; use std::path::Path;
use std::{env, fs};
use uuid::Uuid; use uuid::Uuid;
fn main() { fn main() {
const UPGRADE_FILE: &str = "crypto_data/opensk_upgrade_pub.pem";
println!("cargo:rerun-if-changed=crypto_data/aaguid.txt"); println!("cargo:rerun-if-changed=crypto_data/aaguid.txt");
println!("cargo:rerun-if-changed={UPGRADE_FILE}");
println!("cargo:rerun-if-changed=layout.ld");
println!("cargo:rerun-if-changed=nrf52840_layout.ld");
println!("cargo:rerun-if-changed=nrf52840_layout_a.ld");
println!("cargo:rerun-if-changed=nrf52840_layout_b.ld");
let out_dir = env::var_os("OUT_DIR").unwrap(); let out_dir = env::var_os("OUT_DIR").unwrap();
let aaguid_bin_path = Path::new(&out_dir).join("opensk_aaguid.bin"); let aaguid_bin_path = Path::new(&out_dir).join("opensk_aaguid.bin");
let mut aaguid_bin_file = File::create(aaguid_bin_path).unwrap(); let mut aaguid_bin_file = File::create(&aaguid_bin_path).unwrap();
let mut aaguid_txt_file = File::open("crypto_data/aaguid.txt").unwrap(); let mut aaguid_txt_file = File::open("crypto_data/aaguid.txt").unwrap();
let mut content = String::new(); let mut content = String::new();
aaguid_txt_file.read_to_string(&mut content).unwrap(); aaguid_txt_file.read_to_string(&mut content).unwrap();
content.truncate(36); content.truncate(36);
let aaguid = Uuid::parse_str(&content).unwrap(); let aaguid = Uuid::parse_str(&content).unwrap();
aaguid_bin_file.write_all(aaguid.as_bytes()).unwrap(); aaguid_bin_file.write_all(aaguid.as_bytes()).unwrap();
// COSE encoding the public key, then write it out.
let pem_bytes = fs::read(UPGRADE_FILE).unwrap();
let ec_key = ec::EcKey::public_key_from_pem(&pem_bytes).ok().unwrap();
let group = ec::EcGroup::from_curve_name(nid::Nid::X9_62_PRIME256V1).unwrap();
let conversion_form = ec::PointConversionForm::UNCOMPRESSED;
let mut ctx = bn::BigNumContext::new().unwrap();
let raw_bytes = ec_key
.public_key()
.to_bytes(&group, conversion_form, &mut ctx)
.unwrap();
let upgrade_pubkey_path = Path::new(&out_dir).join("opensk_upgrade_pubkey.bin");
let mut upgrade_pub_bin_file = File::create(upgrade_pubkey_path).unwrap();
upgrade_pub_bin_file.write_all(&raw_bytes).unwrap();
} }

705
deploy.py

File diff suppressed because it is too large Load Diff

View File

@@ -19,9 +19,6 @@ customize it.
### Flashing using DFU (preferred method) ### Flashing using DFU (preferred method)
You need `nrfutil` version 6. The [install manual](../install.md) has
setup instructions.
To flash the firmware, run: To flash the firmware, run:
```shell ```shell
@@ -62,48 +59,14 @@ Follow these steps:
![Nordic dongle retainer clip](../img/dongle_clip.jpg) ![Nordic dongle retainer clip](../img/dongle_clip.jpg)
#### JLink 1. Depending on the programmer you're using, you may have to adapt the next
command line. Run our script for compiling/flashing Tock OS on your device:
Run our script for compiling/flashing Tock OS on your device:
```shell
$ ./deploy.py --board=nrf52840_dongle --programmer=jlink
```
#### OpenOCD
1. Create your openocd config, named `nordic_nrf52840_dongle.cfg` in the
appropriate location:
```shell
mkdir -p ${HOME}/.openocd/board
touch ${HOME}/.openocd/board/nordic_nrf52840_dongle.cfg
```
Paste the following st-link example and edit the specific setup to your needs:
```
# Specific setup
source [find interface/stlink-dap.cfg]
transport select dapdirect_swd
# The rest should be kept the same
set CHIPNAME nrf52840
source [find target/nrf52.cfg]
```
1. Test your config:
```shell ```shell
openocd -f board/nordic_nrf52840_dongle.cfg $ ./deploy.py --board=nrf52840_dongle --programmer=jlink
``` ```
1. Run the deploy script with the appropriate options, i.e.: 1. Remove the programming cable and the USB-A extension cable.
```shell
./deploy.py --board=nrf52840_dongle --opensk --programmer=openocd
```
Finally, remove the programming cable and the USB-A extension cable.
### Buttons and LEDs ### Buttons and LEDs

View File

@@ -19,7 +19,7 @@ After general setup, you still need these steps:
1. Run the script: 1. Run the script:
```shell ```shell
py_virtual_env/bin/python3 uf2conv.py -c -f 0xada52840 -o target/opensk.uf2 target/nrf52840_mdk_dfu_merged.hex python3 uf2conv.py -c -f 0xada52840 -o target/opensk.uf2 target/nrf52840_mdk_dfu_merged.hex
``` ```
1. Boot into DFU mode. Keep the user button pressed on your hardware while 1. Boot into DFU mode. Keep the user button pressed on your hardware while

View File

@@ -14,7 +14,7 @@ is the easiest and most convenient. You can flash OpenSK with these steps:
1. Run our script for compiling/flashing Tock OS and OpenSK on your device: 1. Run our script for compiling/flashing Tock OS and OpenSK on your device:
```shell ```shell
./deploy.py --board=nrf52840dk_opensk --opensk ./deploy.py --board=nrf52840dk --opensk
``` ```
1. Connect a micro USB cable to the device USB port. 1. Connect a micro USB cable to the device USB port.
@@ -48,38 +48,3 @@ There are 3 switches that need to be in the correct position:
* Power (bottom left): On * Power (bottom left): On
* nRF power source (center left): VDD * nRF power source (center left): VDD
* SW6 (top right): DEFAULT * SW6 (top right): DEFAULT
### Upgradability
There are variants of the board that introduce A/B partitions for upgrading the
firmware. You can bootstrap an upgradable board using one of the two commands:
```shell
./deploy.py --board=nrf52840dk_opensk_a --opensk --version=0
./deploy.py --board=nrf52840dk_opensk_b --opensk --version=0
```
Afterwards, you can upgrade the other partition with
```shell
# Board A -> B
./deploy.py --board=nrf52840dk_opensk_b --opensk --programmer=none --version=1
py_virtual_env/bin/python3 -m tools.deploy_partition --board=nrf52840dk_opensk_b --version=1
# Board B -> A
./deploy.py --board=nrf52840dk_opensk_a --opensk --programmer=none --version=1
py_virtual_env/bin/python3 -m tools.deploy_partition --board=nrf52840dk_opensk_a --version=1
```
respectively. You can only upgrade the partition that is not currently running,
otherwise your deploy attempts will fail. You can call `deploy_partition` after
you locked down your device, to deploy changes to your development board.
Upgrades only apply after a reboot.
If you want to use Vendor HID, add the `--vendor-hid` flag to all calls,
for example:
```shell
./deploy.py --board=nrf52840dk_opensk_a --opensk --version=0 --vendor-hid
./deploy.py --board=nrf52840dk_opensk_b --opensk --programmer=none --version=1 --vendor-hid
py_virtual_env/bin/python3 -m tools.deploy_partition --board=nrf52840dk_opensk_b --version=1 --vendor-hid
```

View File

@@ -7,18 +7,16 @@
All the generated certificates and private keys are stored in the directory All the generated certificates and private keys are stored in the directory
`crypto_data/`. The expected content after running our `setup.sh` script is: `crypto_data/`. The expected content after running our `setup.sh` script is:
| File | Purpose | File | Purpose
| ------------------------ | ----------------------------------------------- | ------------------------ | --------------------------------------------------------
| `aaguid.txt` | Text file containaing the AAGUID value | `aaguid.txt` | Text file containaing the AAGUID value
| `opensk_ca.csr` | Certificate sign request for the Root CA | `opensk_ca.csr` | Certificate sign request for the Root CA
| `opensk_ca.key` | ECC secp256r1 private key used for the Root CA | `opensk_ca.key` | ECC secp256r1 private key used for the Root CA
| `opensk_ca.pem` | PEM encoded certificate of the Root CA | `opensk_ca.pem` | PEM encoded certificate of the Root CA
| `opensk_ca.srl` | File generated by OpenSSL | `opensk_ca.srl` | File generated by OpenSSL
| `opensk_cert.csr` | CSR for attestation certificate | `opensk_cert.csr` | Certificate sign request for the attestation certificate
| `opensk_cert.pem` | PEM encoded certificate for the authenticator | `opensk_cert.pem` | PEM encoded certificate used for the authenticator
| `opensk.key` | ECC secp256r1 private key for the autenticator | `opensk.key` | ECC secp256r1 private key used for the autenticator
| `opensk_upgrade.key` | Private key for signing upgrades through CTAP |
| `opensk_upgrade_pub.pem` | Public key for verifying upgrades |
If you want to use your own attestation certificate and private key, If you want to use your own attestation certificate and private key,
replace the `opensk_cert.pem` and `opensk.key` files. The script at replace the `opensk_cert.pem` and `opensk.key` files. The script at
@@ -50,46 +48,31 @@ carefully if you want to take this privacy risk.
If you build your own security key, depending on the hardware you use, there are If you build your own security key, depending on the hardware you use, there are
a few things you can personalize: a few things you can personalize:
1. If you have multiple buttons, choose the buttons responsible for user 1. If you have multiple buttons, choose the buttons responsible for user
presence in `src/main.rs`. presence in `main.rs`.
1. If you have colored LEDs, like different blinking patterns and want to play 2. Decide whether you want to use batch attestation. There is a boolean flag in
around with the code in `src/main.rs` more, take a look at e.g. `wink_leds`. `ctap/mod.rs`. It is mandatory for U2F, and you can create your own
1. You find more options and documentation in `src/ctap/customization.rs`, self-signed certificate. The flag is used for FIDO2 and has some privacy
including: implications. Please check
* The default level for the credProtect extension. [WebAuthn](https://www.w3.org/TR/webauthn/#attestation) for more
* The default minimum PIN length, and what relying parties can set it. information.
* Whether you want to enforce alwaysUv. 3. Decide whether you want to use signature counters. Currently, only global
* Settings for enterprise attestation. signature counters are implemented, as they are the default option for U2F.
* The maximum PIN retries. The flag in `ctap/mod.rs` only turns them off for FIDO2. The most privacy
* Whether you want to use batch attestation. preserving solution is individual or no signature counters. Again, please
* Whether you want to use signature counters. check [WebAuthn](https://www.w3.org/TR/webauthn/#signature-counter) for
* Various constants to adapt to different hardware. documentation.
4. Depending on your available flash storage, choose an appropriate maximum
### Testing and Fuzzing number of supported resident keys and number of pages in
`ctap/storage.rs`.
You might want to test your changes before deploying them. To run unit tests, 5. Change the default level for the credProtect extension in `ctap/mod.rs`.
make sure that at least the `std` feature is included, e.g.: When changing the default, resident credentials become undiscoverable without
user verification. This helps privacy, but can make usage less comfortable
```shell for credentials that need less protection.
cargo test --features=std,with_ctap1 6. Increase the default minimum length for PINs in `ctap/storage.rs`.
``` The current minimum is 4. Values from 4 to 63 are allowed. Requiring longer
PINs can help establish trust between users and relying parties. It makes
Alternatively, you can simply call our test script to also test all libraries, user verification harder to break, but less convenient.
run clippy, check formatting and more: NIST recommends at least 6-digit PINs in section 5.1.9.1 of their
[Digital Identity Guidelines](https://pages.nist.gov/800-63-3/sp800-63b.html).
```shell You can add relying parties to the list of readers of the minimum PIN length.
./run_desktop_tests.sh
```
OpenSK is fuzzed with the [OSS-Fuzz](https://github.com/google/oss-fuzz)
project. You can also run fuzzing locally. First install:
```shell
./fuzzing_setup.sh
```
Then choose a fuzz target from `fuzz/fuzz_targets/`, e.g.:
```shell
cargo fuzz run fuzz_target_process_ctap1
```

View File

@@ -59,15 +59,15 @@ JLinkRTTClient
You can enhance the debug output by adding flags to the deploy command (see You can enhance the debug output by adding flags to the deploy command (see
below for details): below for details):
* `--debug`: more debug messages * `--debug`: more debug messages
* `--panic-console`: add panic messages * `--panic-console`: add panic messages
* `--debug-allocations`: print information about the used heap * `--debug-allocations`: print information about the used heap
Adding debugging to your firmware increases resource usage, including Adding debugging to your firmware increases resource usage, including
* USB communication speed * USB communication speed
* RAM usage * RAM usage
* binary size * binary size
Depending on your choice of board, you may have to increase the available stack Depending on your choice of board, you may have to increase the available stack
for kernel or app, or disable features so that the binary fits the flash. Also for kernel or app, or disable features so that the binary fits the flash. Also
@@ -83,7 +83,7 @@ driver, before faulting the app, you can use the `--panic-console` flag of the
```shell ```shell
# Example on Nordic nRF52840-DK board # Example on Nordic nRF52840-DK board
./deploy.py --board=nrf52840dk_opensk --opensk --panic-console ./deploy.py --board=nrf52840dk --opensk --panic-console
``` ```
### Memory allocations ### Memory allocations
@@ -107,3 +107,31 @@ alloc[256, 1] = 0x2002401c (2 ptrs, 384 bytes)
# After this operation, 1 pointers are allocated, totalling 512 bytes. # After this operation, 1 pointers are allocated, totalling 512 bytes.
dealloc[64, 1] = 0x2002410c (1 ptrs, 512 bytes) dealloc[64, 1] = 0x2002410c (1 ptrs, 512 bytes)
``` ```
A tool is provided to analyze such reports, in `tools/heapviz`. This tool
parses the console output, identifies the lines corresponding to (de)allocation
operations, and first computes some statistics:
* Address range used by the heap over this run of the program,
* Peak heap usage (how many useful bytes are allocated),
* Peak heap consumption (how many bytes are used by the heap, including
unavailable bytes between allocated blocks, due to alignment constraints and
memory fragmentation),
* Fragmentation overhead (difference between heap consumption and usage).
Then, the `heapviz` tool displays an animated "movie" of the allocated bytes in
heap memory. Each frame in this "movie" shows bytes that are currently
allocated, that were allocated but are now freed, and that have never been
allocated. A new frame is generated for each (de)allocation operation. This tool
uses the `ncurses` library, that you may have to install beforehand.
You can control the tool with the following parameters:
* `--logfile` (required) to provide the file which contains the console output
to parse,
* `--fps` (optional) to customize the number of frames per second in the movie
animation.
```shell
cargo run --manifest-path tools/heapviz/Cargo.toml -- --logfile console.log --fps 50
```

View File

@@ -8,27 +8,26 @@ This document lists required steps to start build your own OpenSK.
OpenSK supports different ways to flash your board: OpenSK supports different ways to flash your board:
* [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) * [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/)
(default method). (default method).
* [OpenOCD](http://openocd.org/). * [OpenOCD](http://openocd.org/).
* [pyOCD](https://pypi.org/project/pyocd/). * [pyOCD](https://pypi.org/project/pyocd/).
* [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that * [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that
support it, which allows you to directly flash a working board over USB support it, which allows you to directly flash a working board over USB
without additional hardware. without additional hardware.
### Software requirements ### Software requirements
In order to compile and flash a working OpenSK firmware, you will need the In order to compile and flash a working OpenSK firmware, you will need the
following: following:
* rustup (can be installed with [Rustup](https://rustup.rs/)) * rustup (can be installed with [Rustup](https://rustup.rs/))
* python3 and pip (can be installed with the `python3-pip` package on Debian) * python3 and pip (can be installed with the `python3-pip` package on Debian)
* the OpenSSL command line tool (can be installed and configured with the * the OpenSSL command line tool (can be installed and configured with the
`libssl-dev` and `pkg-config` packages on Debian) `libssl-dev` and `pkg-config` packages on Debian)
* `nrfutil` (pip package of the same name), if you want to flash * `nrfutil` (can be installed using `pip3 install nrfutil`) if you want to flash
a device with DFU. Read the disclaimer below. a device with DFU
* `uuid-runtime` if you are missing the `uuidgen` command. * `uuid-runtime` if you are missing the `uuidgen` command.
* `llvm` and `gcc-arm-none-eabi` if you want to use the upgradability feature.
The proprietary software to use the default programmer can be found on the The proprietary software to use the default programmer can be found on the
[Segger website](https://www.segger.com/downloads/jlink). Please follow their [Segger website](https://www.segger.com/downloads/jlink). Please follow their
@@ -37,73 +36,35 @@ instructions to appropriate binaries for your system.
The scripts provided in this project have been tested under Linux and OS X. We The scripts provided in this project have been tested under Linux and OS X. We
haven't tested them on Windows and other platforms. haven't tested them on Windows and other platforms.
You need `nrfutil` version 6, if you want to flash over DFU.
The tool doesn't support Python newer than 3.10. Therefore, we don't officially
support DFU for other versions. If you want to try regardless,
[Nordic's new tool](https://www.nordicsemi.com/Products/Development-tools/nrf-util)
might work for you.
### Compiling the firmware ### Compiling the firmware
If this is your first time installing OpenSK, please skip directly to If you are switching branches or used an old version of OpenSK before, we have
[Initial setup](#initial-setup). Else, see tools to help you migrate to our develop branch. You find more information on
[Updating your setup](#updating-your-setup) below. how to update your setup or reset your storage in its
[install instructions](https://github.com/google/OpenSK/blob/develop/docs/install.md).
#### Updating your setup To clone and setup the repository for the stable branch, run the following
Depending on the difference to your last state, you may need some of the
following steps:
* If you are not just testing minor changes, reset and redo the setup. This
will delete all uncommited changes.
```shell
./reset.sh
./setup.sh
```
* Flash your board according to the [instructions below](#flashing-a-firmware).
If you come from an OpenSK version before the 2.0 certified one, your credential
storage is not backwards compatible and you have to reset it. :warning: You will
lose logins to all websites that you registered with OpenSK. To erase your
persistent storage, run the deploy script twice: Once with the application
parameter `--erase_storage`, and once with `--opensk` as usual.
This reset also clears the certificate. For a privacy discussion, see the
[certificate section in Customization](customization.md#Certificate-considerations).
If you want to reinstall it, you also need to rerun:
```shell
./tools/configure.py \
--certificate=crypto_data/opensk_cert.pem \
--private-key=crypto_data/opensk.key
```
#### Initial setup
To clone and setup the repository for the develop branch, run the following
commands: commands:
```shell ```shell
git clone -b develop https://github.com/google/OpenSK.git git clone https://github.com/google/OpenSK.git
cd OpenSK cd OpenSK
./setup.sh ./setup.sh
``` ```
The setup script performs the following steps: The setup script performs the following steps:
1. Make sure that the git submodules are checked out. 1. Make sure that the git submodules are checked out.
1. Apply our patches that haven't yet been merged upstream to both 1. Apply our patches that haven't yet been merged upstream to both
[Tock](https://github.com/tock/tock) and [Tock](https://github.com/tock/tock) and
[libtock-rs](https://github.com/tock/libtock-rs). [libtock-rs](https://github.com/tock/libtock-rs).
1. Generate crypto material, see [Customization](customization.md) for details. 1. Generate crypto material, see [Customization](customization.md) for details.
1. Install the correct Rust toolchain for ARM devices. 1. Install the correct Rust toolchain for ARM devices.
1. Install [tockloader](https://github.com/tock/tockloader). 1. Install [tockloader](https://github.com/tock/tockloader).
Additionally on Linux, you need to install a `udev` rule file to allow non-root Additionally on Linux, you need to install a `udev` rule file to allow non-root
users to interact with OpenSK devices. To install it, execute: users to interact with OpenSK devices. To install it, execute:
@@ -132,47 +93,25 @@ for understand privacy tradeoffs.
From here on, please follow the instructions for your hardware: From here on, please follow the instructions for your hardware:
* [Nordic nRF52840-DK](boards/nrf52840dk.md) * [Nordic nRF52840-DK](boards/nrf52840dk.md)
* [Nordic nRF52840 Dongle](boards/nrf52840_dongle.md) * [Nordic nRF52840 Dongle](boards/nrf52840_dongle.md)
* [Makerdiary nRF52840-MDK USB dongle](boards/nrf52840_mdk.md) * [Makerdiary nRF52840-MDK USB dongle](boards/nrf52840_mdk.md)
* [Feitian OpenSK dongle](boards/nrf52840_feitian.md) * [Feitian OpenSK dongle](boards/nrf52840_feitian.md)
### Advanced installation ### Advanced installation
We recommend that you flash your development board with JTAG and dongles with We recommend that you flash your development board with JTAG and dongles with
DFU, as described in the [board documentation](#flashing-a-firmware) linked DFU, as described in the [board documentation](#Flashing-a-firmware) linked
above. However, we support other programmers: above. However, we support other programmers:
* OpenOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk * OpenOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=openocd`
--programmer=openocd` * pyOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=pyocd`
* pyOCD: `./deploy.py --board=nrf52840_dongle_opensk --opensk * Custom: `./deploy.py --board=nrf52840_dongle --opensk --programmer=none`.
--programmer=pyocd` In this case, an IntelHex file will be created and how to program a board is
* Custom: `./deploy.py --board=nrf52840_dongle_opensk --opensk left to the user.
--programmer=none`. In this case, an IntelHex file will be created and how
to program a board is left to the user.
If your board is already flashed with Tock OS, you may skip installing it: If your board is already flashed with Tock OS, you may skip installing it:
`./deploy.py --board=nrf52840dk_opensk --opensk --no-tockos` `./deploy.py --board=nrf52840dk --opensk --no-tockos`
For more options, we invite you to read the help of our `deploy.py` script by For more options, we invite you to read the help of our `deploy.py` script by
running `./deploy.py --help`. running `./deploy.py --help`.
### Upgradability
We experiment with a new CTAP command to allow upgrading your device without
access to its debugging port. For that purpose, the flash storage is split into
4 parts:
* the bootloader to decide with partition to boot
* firmware partition A
* firmware partition B
* the persistent storage for credentials
The storage is backward compatible to non-upgradable boards. Deploying an
upgradable board automatically installs the bootloader. Please keep in mind that
you have to safely store your private signing key for upgrades if you want to
use this feature. For more information on the cryptographic material, see
[Customization](customization.md).
So far, upgradability is only supported for the development board. See the
instructions on the [board specific page](boards/nrf52840dk.md).

View File

@@ -12,32 +12,22 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_main]
#![no_std] #![no_std]
extern crate lang_items; extern crate lang_items;
use libtock_console::Console; use libtock_drivers::console::{Console, BUFFER_SIZE};
use libtock_drivers::result::FlexUnwrap;
use libtock_runtime::{set_main, stack_size, TockSyscalls};
stack_size! {0x800}
set_main! {main}
type Syscalls = TockSyscalls;
fn main() { fn main() {
// Write messages of length up to the console driver's buffer size. // Write messages of length up to the console driver's buffer size.
let mut buf = [0; 1024]; let mut buf = [0; BUFFER_SIZE];
loop { loop {
for i in 1..buf.len() { for i in 1..buf.len() {
for byte in buf.iter_mut().take(i) { for byte in buf.iter_mut().take(i) {
*byte = b'0' + ((i % 10) as u8); *byte = b'0' + ((i % 10) as u8);
} }
buf[i] = b'\n'; buf[i] = b'\n';
Console::<Syscalls>::write(&buf[..(i + 1)]) Console::write_unbuffered(&mut buf[..(i + 1)]);
.map_err(|e| e.into())
.flex_unwrap();
} }
} }
} }

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_main]
#![no_std] #![no_std]
extern crate alloc; extern crate alloc;
@@ -21,72 +20,83 @@ extern crate lang_items;
use alloc::format; use alloc::format;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::Write; use core::fmt::Write;
use core::hint::black_box; use crypto::{
use ctap2::env::tock::{TockEnv, TockRng}; aes256, cbc, ecdsa, rng256, sha256, Decrypt16BytesBlock, Encrypt16BytesBlock, Hash256,
use libtock_console::{Console, ConsoleWriter}; };
use libtock_drivers::console::Console;
use libtock_drivers::result::FlexUnwrap; use libtock_drivers::result::FlexUnwrap;
use libtock_drivers::timer; use libtock_drivers::timer;
use libtock_drivers::timer::{Timer, Timestamp}; use libtock_drivers::timer::Timer;
use libtock_runtime::{set_main, stack_size, TockSyscalls}; use libtock_drivers::timer::Timestamp;
use opensk::api::crypto::aes256::Aes256;
use opensk::api::crypto::ecdsa::SecretKey as _;
use opensk::api::crypto::sha256::Sha256;
use opensk::env::{AesKey, EcdsaSk, Sha};
stack_size! {0x2000}
set_main! {main}
type Syscalls = TockSyscalls;
fn main() { fn main() {
let mut console = Console::<Syscalls>::writer(); let mut console = Console::new();
// Setup the timer with a dummy callback (we only care about reading the current time, but the // Setup the timer with a dummy callback (we only care about reading the current time, but the
// API forces us to set an alarm callback too). // API forces us to set an alarm callback too).
let mut with_callback = timer::with_callback(|_| {}); let mut with_callback = timer::with_callback(|_, _| {});
let timer = with_callback.init().flex_unwrap(); let timer = with_callback.init().flex_unwrap();
let mut rng = TockRng::<Syscalls>::default(); let mut rng = rng256::TockRng256 {};
writeln!(console, "****************************************").unwrap(); writeln!(console, "****************************************").unwrap();
writeln!(console, "Clock frequency: {:?} Hz", timer.clock_frequency()).unwrap(); writeln!(
console,
"Clock frequency: {} Hz",
timer.clock_frequency().hz()
)
.unwrap();
// AES // AES
bench(&mut console, &timer, "Aes256::new", || { bench(&mut console, &timer, "aes256::EncryptionKey::new", || {
black_box(AesKey::<TockEnv<Syscalls>>::new(&[0; 32])); aes256::EncryptionKey::new(&[0; 32]);
}); });
let aes_key = AesKey::<TockEnv<Syscalls>>::new(&[0; 32]); let ek = aes256::EncryptionKey::new(&[0; 32]);
bench(&mut console, &timer, "aes256::DecryptionKey::new", || {
aes256::DecryptionKey::new(&ek);
});
let dk = aes256::DecryptionKey::new(&ek);
bench(&mut console, &timer, "Aes256::encrypt_block", || { bench(
aes_key.encrypt_block(&mut [0; 16]); &mut console,
}); &timer,
bench(&mut console, &timer, "Aes256::decrypt_block", || { "aes256::EncryptionKey::encrypt_block",
aes_key.decrypt_block(&mut [0; 16]); || {
}); ek.encrypt_block(&mut [0; 16]);
},
);
bench(
&mut console,
&timer,
"aes256::DecryptionKey::decrypt_block",
|| {
dk.decrypt_block(&mut [0; 16]);
},
);
// CBC // CBC
let mut blocks = Vec::new(); let mut blocks = Vec::new();
for i in 0..6 { for i in 0..8 {
blocks.resize(1 << (i + 4), 0); blocks.resize(1 << i, [0; 16]);
bench( bench(
&mut console, &mut console,
&timer, &timer,
&format!("Aes256::encrypt_cbc({} bytes)", blocks.len()), &format!("cbc::cbc_encrypt({} bytes)", blocks.len() * 16),
|| { || {
aes_key.encrypt_cbc(&[0; 16], &mut blocks); cbc::cbc_encrypt(&ek, [0; 16], &mut blocks);
}, },
); );
} }
drop(blocks); drop(blocks);
let mut blocks = Vec::new(); let mut blocks = Vec::new();
for i in 0..6 { for i in 0..8 {
blocks.resize(1 << (i + 4), 0); blocks.resize(1 << i, [0; 16]);
bench( bench(
&mut console, &mut console,
&timer, &timer,
&format!("Aes256::decrypt_cbc({} bytes)", blocks.len()), &format!("cbc::cbc_decrypt({} bytes)", blocks.len() * 16),
|| { || {
aes_key.decrypt_cbc(&[0; 16], &mut blocks); cbc::cbc_decrypt(&dk, [0; 16], &mut blocks);
}, },
); );
} }
@@ -94,37 +104,52 @@ fn main() {
// SHA-256 // SHA-256
let mut contents = Vec::new(); let mut contents = Vec::new();
for i in 0..6 { for i in 0..8 {
contents.resize(16 << i, 0); contents.resize(16 << i, 0);
bench( bench(
&mut console, &mut console,
&timer, &timer,
&format!("Sha256::digest({} bytes)", contents.len()), &format!("sha256::Sha256::update({} bytes)", contents.len()),
|| { || {
Sha::<TockEnv<Syscalls>>::digest(&contents); let mut sha = sha256::Sha256::new();
sha.update(&contents);
sha.finalize();
}, },
); );
} }
drop(contents); drop(contents);
// ECDSA // ECDSA
bench(&mut console, &timer, "Ecdsa::SecretKey::random", || { bench(&mut console, &timer, "ecdsa::SecKey::gensk", || {
EcdsaSk::<TockEnv<Syscalls>>::random(&mut rng); ecdsa::SecKey::gensk(&mut rng);
}); });
let sk = EcdsaSk::<TockEnv<Syscalls>>::random(&mut rng); let k = ecdsa::SecKey::gensk(&mut rng);
bench(&mut console, &timer, "Ecdsa::SecretKey::public_key", || { bench(&mut console, &timer, "ecdsa::SecKey::genpk", || {
black_box(sk.public_key()); k.genpk();
});
bench(&mut console, &timer, "Ecdsa::SecretKey::sign", || {
sk.sign(&[]);
}); });
bench(
&mut console,
&timer,
"ecdsa::SecKey::sign_rng::<sha256::Sha256, _>",
|| {
k.sign_rng::<sha256::Sha256, _>(&[], &mut rng);
},
);
bench(
&mut console,
&timer,
"ecdsa::SecKey::sign_rfc6979::<sha256::Sha256>",
|| {
k.sign_rfc6979::<sha256::Sha256>(&[]);
},
);
writeln!(console, "****************************************").unwrap(); writeln!(console, "****************************************").unwrap();
writeln!(console, "All the benchmarks are done.\nHave a nice day!").unwrap(); writeln!(console, "All the benchmarks are done.\nHave a nice day!").unwrap();
writeln!(console, "****************************************").unwrap(); writeln!(console, "****************************************").unwrap();
} }
fn bench<F>(console: &mut ConsoleWriter<Syscalls>, timer: &Timer<Syscalls>, title: &str, mut f: F) fn bench<F>(console: &mut Console, timer: &Timer, title: &str, mut f: F)
where where
F: FnMut(), F: FnMut(),
{ {
@@ -133,13 +158,11 @@ where
writeln!(console, "----------------------------------------").unwrap(); writeln!(console, "----------------------------------------").unwrap();
let mut count = 1; let mut count = 1;
for _ in 0..30 { for _ in 0..30 {
let start = let start = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
Timestamp::<f64>::from_clock_value(timer.get_current_counter_ticks().flex_unwrap());
for _ in 0..count { for _ in 0..count {
f(); f();
} }
let end = let end = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
Timestamp::<f64>::from_clock_value(timer.get_current_counter_ticks().flex_unwrap());
let elapsed = (end - start).ms(); let elapsed = (end - start).ms();
writeln!( writeln!(
console, console,
@@ -149,6 +172,7 @@ where
elapsed / (count as f64) elapsed / (count as f64)
) )
.unwrap(); .unwrap();
console.flush();
if elapsed > 1000.0 { if elapsed > 1000.0 {
break; break;
} }

View File

@@ -12,25 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_main]
#![no_std] #![no_std]
extern crate lang_items; extern crate lang_items;
use core::fmt::Write; use core::fmt::Write;
use ctap2::env::tock::take_storage; use ctap2::embedded_flash::new_storage;
use libtock_console::Console; use libtock_drivers::console::Console;
use libtock_drivers::led;
use libtock_drivers::result::FlexUnwrap; use libtock_drivers::result::FlexUnwrap;
use libtock_leds::Leds;
use libtock_platform as platform;
use libtock_runtime::{set_main, stack_size, TockSyscalls};
use persistent_store::{Storage, StorageIndex}; use persistent_store::{Storage, StorageIndex};
use platform::DefaultConfig;
stack_size! {0x800}
set_main! {main}
type Syscalls = TockSyscalls;
fn is_page_erased(storage: &dyn Storage, page: usize) -> bool { fn is_page_erased(storage: &dyn Storage, page: usize) -> bool {
let index = StorageIndex { page, byte: 0 }; let index = StorageIndex { page, byte: 0 };
@@ -43,21 +34,20 @@ fn is_page_erased(storage: &dyn Storage, page: usize) -> bool {
} }
fn main() { fn main() {
Leds::<Syscalls>::on(1).map_err(|e| e.into()).flex_unwrap(); // red on dongle led::get(1).flex_unwrap().on().flex_unwrap(); // red on dongle
let mut storage = take_storage::<Syscalls, DefaultConfig>().unwrap(); const NUM_PAGES: usize = 20; // should be at least ctap::storage::NUM_PAGES
let num_pages = storage.num_pages(); let mut storage = new_storage(NUM_PAGES);
let mut console = Console::<Syscalls>::writer(); writeln!(Console::new(), "Erase {} pages of storage:", NUM_PAGES).unwrap();
writeln!(console, "Erase {} pages of storage:", num_pages).unwrap(); for page in 0..NUM_PAGES {
for page in 0..num_pages { write!(Console::new(), "- Page {} ", page).unwrap();
write!(console, "- Page {} ", page).unwrap();
if is_page_erased(&storage, page) { if is_page_erased(&storage, page) {
writeln!(console, "skipped (was already erased).").unwrap(); writeln!(Console::new(), "skipped (was already erased).").unwrap();
} else { } else {
storage.erase_page(page).unwrap(); storage.erase_page(page).unwrap();
writeln!(console, "erased.").unwrap(); writeln!(Console::new(), "erased.").unwrap();
} }
} }
writeln!(console, "Done.").unwrap(); writeln!(Console::new(), "Done.").unwrap();
Leds::<Syscalls>::on(1).map_err(|e| e.into()).flex_unwrap(); led::get(1).flex_unwrap().off().flex_unwrap();
Leds::<Syscalls>::off(0).map_err(|e| e.into()).flex_unwrap(); // green on dongle led::get(0).flex_unwrap().on().flex_unwrap(); // green on dongle
} }

View File

@@ -1,4 +1,3 @@
#![no_main]
#![no_std] #![no_std]
extern crate alloc; extern crate alloc;
@@ -6,35 +5,34 @@ extern crate lang_items;
extern crate libtock_drivers; extern crate libtock_drivers;
use core::fmt::Write; use core::fmt::Write;
use libtock_console::{Console, ConsoleWriter}; use libtock_drivers::console::Console;
use libtock_runtime::{set_main, stack_size, TockSyscalls};
stack_size! {0x4000}
set_main! {main}
type Syscalls = TockSyscalls;
#[cfg(not(feature = "with_nfc"))] #[cfg(not(feature = "with_nfc"))]
mod example { mod example {
use super::{ConsoleWriter, Syscalls, Write}; use super::Console;
use super::Write;
pub fn nfc(console: &mut ConsoleWriter<Syscalls>) { pub fn nfc(console: &mut Console) {
writeln!(console, "NFC feature flag is missing!").unwrap(); writeln!(console, "NFC feature flag is missing!").unwrap();
} }
} }
#[cfg(feature = "with_nfc")] #[cfg(feature = "with_nfc")]
mod example { mod example {
use super::{Console, ConsoleWriter, Write}; use super::Console;
use crate::Syscalls; use super::Write;
use libtock_drivers::nfc::{NfcTag, RecvOp}; use libtock_core::result::CommandError;
use libtock_drivers::result::{FlexUnwrap, TockError}; use libtock_drivers::nfc::NfcTag;
use libtock_drivers::nfc::RecvOp;
use libtock_drivers::result::FlexUnwrap;
use libtock_drivers::result::TockError;
use libtock_drivers::timer; use libtock_drivers::timer;
use libtock_drivers::timer::{Timer, Timestamp}; use libtock_drivers::timer::Timer;
use libtock_platform::{DefaultConfig, ErrorCode}; use libtock_drivers::timer::Timestamp;
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
#[allow(clippy::upper_case_acronyms)] // The actual lint upper_case_acronyms is not supported in all toolchains.
#[allow(clippy::all)]
enum ReturnCode { enum ReturnCode {
/// Operation completed successfully /// Operation completed successfully
SUCCESS, SUCCESS,
@@ -54,15 +52,16 @@ mod example {
ENOSUPPORT, ENOSUPPORT,
} }
impl From<ErrorCode> for ReturnCode { impl From<isize> for ReturnCode {
fn from(original: ErrorCode) -> ReturnCode { fn from(original: isize) -> ReturnCode {
match original { match original {
ErrorCode::Fail => ReturnCode::FAIL, 0 => ReturnCode::SUCCESS,
ErrorCode::Busy => ReturnCode::EBUSY, -1 => ReturnCode::FAIL,
ErrorCode::Off => ReturnCode::EOFF, -2 => ReturnCode::EBUSY,
ErrorCode::Invalid => ReturnCode::EINVAL, -4 => ReturnCode::EOFF,
ErrorCode::Cancel => ReturnCode::ECANCEL, -6 => ReturnCode::EINVAL,
ErrorCode::NoMem => ReturnCode::ENOMEM, -8 => ReturnCode::ECANCEL,
-9 => ReturnCode::ENOMEM,
_ => ReturnCode::ENOSUPPORT, _ => ReturnCode::ENOSUPPORT,
} }
} }
@@ -71,32 +70,34 @@ mod example {
/// Helper function to write on console the received packet. /// Helper function to write on console the received packet.
fn print_rx_buffer(buf: &mut [u8]) { fn print_rx_buffer(buf: &mut [u8]) {
if let Some((last, bytes)) = buf.split_last() { if let Some((last, bytes)) = buf.split_last() {
let mut console = Console::<Syscalls>::writer(); let mut console = Console::new();
write!(console, "RX:").unwrap(); write!(console, "RX:").unwrap();
for byte in bytes { for byte in bytes {
write!(console, " {:02x?}", byte).unwrap(); write!(console, " {:02x?}", byte).unwrap();
} }
writeln!(console, " {:02x?}", last).unwrap(); writeln!(console, " {:02x?}", last).unwrap();
console.flush();
} }
} }
/// Function to identify the time elapsed for a transmission request. /// Function to identify the time elapsed for a transmission request.
fn bench_transmit( fn bench_transmit(
console: &mut ConsoleWriter<Syscalls>, console: &mut Console,
timer: &Timer<Syscalls>, timer: &Timer,
title: &str, title: &str,
buf: &mut [u8], mut buf: &mut [u8],
) -> ReturnCode { ) -> ReturnCode {
let amount = buf.len(); let amount = buf.len();
let start = let start = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
Timestamp::<f64>::from_clock_value(timer.get_current_counter_ticks().flex_unwrap()); match NfcTag::transmit(&mut buf, amount) {
match NfcTag::<Syscalls, DefaultConfig>::transmit(buf, amount as u32) {
Ok(_) => (), Ok(_) => (),
Err(TockError::Command(ErrorCode::Cancel)) => return ReturnCode::ECANCEL, Err(TockError::Command(CommandError {
Err(_) => writeln!(console, " -- tx error!").unwrap(), return_code: -8, /* ECANCEL: No Field*/
..
})) => return ReturnCode::ECANCEL,
Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(),
} }
let end = let end = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
Timestamp::<f64>::from_clock_value(timer.get_current_counter_ticks().flex_unwrap());
let elapsed = (end - start).ms(); let elapsed = (end - start).ms();
writeln!( writeln!(
console, console,
@@ -107,21 +108,21 @@ mod example {
(amount as f64) / elapsed * 8. (amount as f64) / elapsed * 8.
) )
.unwrap(); .unwrap();
console.flush();
ReturnCode::SUCCESS ReturnCode::SUCCESS
} }
fn receive_packet(console: &mut ConsoleWriter<Syscalls>, buf: &mut [u8; 256]) -> ReturnCode { fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) -> ReturnCode {
match NfcTag::<Syscalls, DefaultConfig>::receive(buf) { match NfcTag::receive(&mut buf) {
Ok(RecvOp { Ok(RecvOp {
recv_amount: amount, recv_amount: amount,
.. ..
}) => { }) => {
if amount <= buf.len() as u32 { if amount <= buf.len() {
print_rx_buffer(&mut buf[..amount as usize]); print_rx_buffer(&mut buf[..amount]);
} }
} }
Err(TockError::Command(code)) => return code.into(), Err(TockError::Command(CommandError { return_code, .. })) => return return_code.into(),
Err(_) => { Err(_) => {
writeln!(console, " -- RX Err").unwrap(); writeln!(console, " -- RX Err").unwrap();
return ReturnCode::ECANCEL; return ReturnCode::ECANCEL;
@@ -130,58 +131,54 @@ mod example {
ReturnCode::SUCCESS ReturnCode::SUCCESS
} }
fn transmit_reply( fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> ReturnCode {
console: &mut ConsoleWriter<Syscalls>,
timer: &Timer<Syscalls>,
buf: &[u8],
) -> ReturnCode {
let mut return_code = ReturnCode::SUCCESS; let mut return_code = ReturnCode::SUCCESS;
match buf[0] { match buf[0] {
0xe0 /* RATS */=> { 0xe0 /* RATS */=> {
let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00]; let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00];
return_code = bench_transmit(console, timer, "TX: ATS", &mut answer_to_select); return_code = bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select);
} }
0xc2 /* DESELECT */ => { 0xc2 /* DESELECT */ => {
// Ignore the request // Ignore the request
let mut command_error = [0x6A, 0x81]; let mut command_error = [0x6A, 0x81];
return_code = bench_transmit(console, timer, "TX: DESELECT", &mut command_error); return_code = bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error);
} }
0x02 | 0x03 /* APDU Prefix */ => match buf[2] { 0x02 | 0x03 /* APDU Prefix */ => match buf[2] {
// If the received packet is applet selection command (FIDO 2) // If the received packet is applet selection command (FIDO 2)
0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 { 0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 {
// Vesion: "FIDO_2_0" // Vesion: "FIDO_2_0"
let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,]; let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,];
return_code = bench_transmit(console, timer, "TX: Version Str", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply);
} else if (buf[6] == 0xd2 && buf[7] == 0x76) || (buf[6] == 0xe1 && (buf[7] == 0x03 || buf[7] == 0x04)){ } else if (buf[6] == 0xd2 && buf[7] == 0x76) || (buf[6] == 0xe1 && (buf[7] == 0x03 || buf[7] == 0x04)){
let mut reply = [buf[0], 0x90, 0x00]; let mut reply = [buf[0], 0x90, 0x00];
return_code = bench_transmit(console, timer, "TX: 0x9000", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
} else /* Unknown file */ { } else /* Unknown file */ {
let mut reply = [buf[0], 0x6a, 0x82]; let mut reply = [buf[0], 0x6a, 0x82];
return_code = bench_transmit(console, timer, "TX: 0x6A82", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: 0x6A82", &mut reply);
} }
0xb0 /* READ */ => match buf[5] { 0xb0 /* READ */ => match buf[5] {
0x02 => { 0x02 => {
let mut reply = [buf[0], 0x12, 0x90, 0x00,]; let mut reply = [buf[0], 0x12, 0x90, 0x00,];
return_code = bench_transmit(console, timer, "TX: File Size", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: File Size", &mut reply);
} }
0x12 => { 0x12 => {
let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,]; 0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,];
return_code = bench_transmit(console, timer, "TX: NDEF", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply);
} }
0x0f => { 0x0f => {
let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04, let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04,
0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,]; 0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,];
return_code = bench_transmit(console, timer, "TX: CC", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: CC", &mut reply);
} }
_ => { _ => {
let mut reply = [buf[0], 0x90, 0x00]; let mut reply = [buf[0], 0x90, 0x00];
return_code = bench_transmit(console, timer, "TX: 0x9000", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
} }
} }
_ => { _ => {
let mut reply = [buf[0], 0x90, 0x00]; let mut reply = [buf[0], 0x90, 0x00];
return_code = bench_transmit(console, timer, "TX: 0x9000", &mut reply); return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
} }
} }
0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => { 0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => {
@@ -192,27 +189,31 @@ mod example {
return_code return_code
} }
pub fn nfc(console: &mut ConsoleWriter<Syscalls>) { pub fn nfc(mut console: &mut Console) {
// Setup the timer with a dummy callback (we only care about reading the current time, but the // Setup the timer with a dummy callback (we only care about reading the current time, but the
// API forces us to set an alarm callback too). // API forces us to set an alarm callback too).
let mut with_callback = timer::with_callback(|_| {}); let mut with_callback = timer::with_callback(|_, _| {});
let timer = with_callback.init().flex_unwrap(); let timer = with_callback.init().flex_unwrap();
writeln!(console, "Clock frequency: {:?} Hz", timer.clock_frequency()).unwrap(); writeln!(
console,
"Clock frequency: {} Hz",
timer.clock_frequency().hz()
)
.unwrap();
let mut state_change_counter = 0; let mut state_change_counter = 0;
loop { loop {
let mut rx_buf = [0; 256]; let mut rx_buf = [0; 256];
match receive_packet(console, &mut rx_buf) { match receive_packet(&mut console, &mut rx_buf) {
ReturnCode::EOFF => { ReturnCode::EOFF => {
// Not configured // Not configured
while !NfcTag::<Syscalls, DefaultConfig>::enable_emulation() {} while !NfcTag::enable_emulation() {}
// Configure Type 4 tag // Configure Type 4 tag
while !NfcTag::<Syscalls, DefaultConfig>::configure(4) {} while !NfcTag::configure(4) {}
} }
ReturnCode::ECANCEL /* field lost */ => { ReturnCode::ECANCEL /* field lost */ => {
NfcTag::<Syscalls, DefaultConfig>::disable_emulation(); NfcTag::disable_emulation();
} }
ReturnCode::EBUSY /* awaiting select*/ => (), ReturnCode::EBUSY /* awaiting select*/ => (),
ReturnCode::ENOMEM => { ReturnCode::ENOMEM => {
@@ -223,9 +224,9 @@ mod example {
ReturnCode::ENOSUPPORT => (), ReturnCode::ENOSUPPORT => (),
ReturnCode::SUCCESS => { ReturnCode::SUCCESS => {
// If the reader restarts the communication then disable the tag. // If the reader restarts the communication then disable the tag.
match transmit_reply(console, &timer, &rx_buf) { match transmit_reply(&mut console, &timer, &rx_buf) {
ReturnCode::ECANCEL | ReturnCode::EOFF => { ReturnCode::ECANCEL | ReturnCode::EOFF => {
if NfcTag::<Syscalls, DefaultConfig>::disable_emulation() { if NfcTag::disable_emulation() {
writeln!(console, " -- TAG DISABLED").unwrap(); writeln!(console, " -- TAG DISABLED").unwrap();
} }
state_change_counter += 1; state_change_counter += 1;
@@ -242,7 +243,7 @@ mod example {
} }
fn main() { fn main() {
let mut console = Console::<Syscalls>::writer(); let mut console = Console::new();
writeln!(console, "****************************************").unwrap(); writeln!(console, "****************************************").unwrap();
writeln!(console, "nfct_test application is installed").unwrap(); writeln!(console, "nfct_test application is installed").unwrap();
example::nfc(&mut console); example::nfc(&mut console);

View File

@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_main]
#![no_std] #![no_std]
extern crate alloc; extern crate alloc;
@@ -20,22 +19,15 @@ extern crate lang_items;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt::Write; use core::fmt::Write;
use libtock_console::Console; use libtock_drivers::console::Console;
use libtock_runtime::{set_main, stack_size, TockSyscalls};
stack_size! {0x800}
set_main! {main}
type Syscalls = TockSyscalls;
fn main() { fn main() {
let mut console = Console::<Syscalls>::writer(); writeln!(Console::new(), "****************************************").unwrap();
writeln!(console, "****************************************").unwrap();
for i in 0.. { for i in 0.. {
writeln!(console, "Allocating {} bytes...", 1 << i).unwrap(); writeln!(Console::new(), "Allocating {} bytes...", 1 << i).unwrap();
let x: Vec<u8> = Vec::with_capacity(1 << i); let x: Vec<u8> = Vec::with_capacity(1 << i);
writeln!(console, "Allocated!").unwrap(); writeln!(Console::new(), "Allocated!").unwrap();
drop(x); drop(x);
writeln!(console, "Dropped!").unwrap(); writeln!(Console::new(), "Dropped!").unwrap();
} }
} }

View File

@@ -12,16 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_main]
#![no_std] #![no_std]
extern crate lang_items; extern crate lang_items;
use libtock_runtime::{set_main, stack_size};
stack_size! {0x800}
set_main! {main}
fn main() { fn main() {
panic!("Bye world!") panic!("Bye world!")
} }

View File

@@ -12,96 +12,51 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_main]
#![no_std] #![no_std]
extern crate alloc; extern crate alloc;
extern crate lang_items; extern crate lang_items;
use alloc::string::{String, ToString}; use alloc::vec;
use alloc::vec::Vec;
use alloc::{format, vec};
use core::fmt::Write; use core::fmt::Write;
use ctap2::env::tock::{take_storage, Storage}; use ctap2::embedded_flash::{new_storage, Storage};
use libtock_console::Console; use libtock_drivers::console::Console;
use libtock_drivers::result::FlexUnwrap;
use libtock_drivers::timer::{self, Duration, Timer, Timestamp}; use libtock_drivers::timer::{self, Duration, Timer, Timestamp};
use libtock_platform::DefaultConfig; use persistent_store::Store;
use libtock_runtime::{set_main, stack_size, TockSyscalls};
use persistent_store::{Storage as _, Store};
stack_size! {0x800} fn timestamp(timer: &Timer) -> Timestamp<f64> {
set_main! {main} Timestamp::<f64>::from_clock_value(timer.get_current_clock().ok().unwrap())
type Syscalls = TockSyscalls;
fn timestamp(timer: &Timer<Syscalls>) -> Timestamp<f64> {
Timestamp::<f64>::from_clock_value(timer.get_current_counter_ticks().ok().unwrap())
} }
fn measure<T>(timer: &Timer<Syscalls>, operation: impl FnOnce() -> T) -> (T, Duration<f64>) { fn measure<T>(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration<f64>) {
let before = timestamp(timer); let before = timestamp(timer);
let result = operation(); let result = operation();
let after = timestamp(timer); let after = timestamp(timer);
(result, after - before) (result, after - before)
} }
fn boot_store( // Only use one store at a time.
mut storage: Storage<Syscalls, DefaultConfig>, unsafe fn boot_store(num_pages: usize, erase: bool) -> Store<Storage> {
erase: bool, let mut storage = new_storage(num_pages);
) -> Store<Storage<Syscalls, DefaultConfig>> {
let num_pages = storage.num_pages();
if erase { if erase {
for page in 0..num_pages { for page in 0..num_pages {
use persistent_store::Storage;
storage.erase_page(page).unwrap(); storage.erase_page(page).unwrap();
} }
} }
Store::new(storage).ok().unwrap() Store::new(storage).ok().unwrap()
} }
#[derive(Debug)] fn compute_latency(timer: &Timer, num_pages: usize, key_increment: usize, word_length: usize) {
struct StorageConfig { let mut console = Console::new();
num_pages: usize,
}
fn storage_config(storage: &Storage<Syscalls, DefaultConfig>) -> StorageConfig {
StorageConfig {
num_pages: storage.num_pages(),
}
}
#[derive(Default)]
struct Stat {
key_increment: usize,
entry_length: usize, // words
boot_ms: f64,
compaction_ms: f64,
insert_ms: f64,
remove_ms: f64,
}
fn compute_latency(
storage: Storage<Syscalls, DefaultConfig>,
timer: &Timer<Syscalls>,
num_pages: usize,
key_increment: usize,
word_length: usize,
) -> (Storage<Syscalls, DefaultConfig>, Stat) {
let mut stat = Stat {
key_increment,
entry_length: word_length,
..Default::default()
};
let mut console = Console::<Syscalls>::writer();
writeln!( writeln!(
console, console,
"\nLatency for key_increment={} word_length={}.", "\nLatency for num_pages={} key_increment={} word_length={}.",
key_increment, word_length num_pages, key_increment, word_length
) )
.unwrap(); .unwrap();
let mut store = boot_store(storage, true); let mut store = unsafe { boot_store(num_pages, true) };
let total_capacity = store.capacity().unwrap().total(); let total_capacity = store.capacity().unwrap().total();
assert_eq!(store.capacity().unwrap().used(), 0); assert_eq!(store.capacity().unwrap().used(), 0);
assert_eq!(store.lifetime().unwrap().used(), 0); assert_eq!(store.lifetime().unwrap().used(), 0);
@@ -117,33 +72,33 @@ fn compute_latency(
let ((), time) = measure(timer, || { let ((), time) = measure(timer, || {
for i in 0..count { for i in 0..count {
let key = 1 + key_increment * i; let key = 1 + key_increment * i;
store.insert(key, &vec![0; 4 * word_length]).unwrap(); // For some reason the kernel sometimes fails.
while store.insert(key, &vec![0; 4 * word_length]).is_err() {
// We never enter this loop in practice, but we still need it for the kernel.
writeln!(console, "Retry insert.").unwrap();
}
} }
}); });
writeln!(console, "Setup: {:.1}ms for {} entries.", time.ms(), count).unwrap(); writeln!(console, "Setup: {:.1}ms for {} entries.", time.ms(), count).unwrap();
// Measure latency of insert. // Measure latency of insert.
let key = 1 + key_increment * count; let key = 1 + key_increment * count;
let ((), time) = measure(timer, || { let ((), time) = measure(&timer, || {
store.insert(key, &vec![0; 4 * word_length]).unwrap() store.insert(key, &vec![0; 4 * word_length]).unwrap()
}); });
writeln!(console, "Insert: {:.1}ms.", time.ms()).unwrap(); writeln!(console, "Insert: {:.1}ms.", time.ms()).unwrap();
stat.insert_ms = time.ms();
assert_eq!( assert_eq!(
store.lifetime().unwrap().used(), store.lifetime().unwrap().used(),
num_pages + (1 + count) * (1 + word_length) num_pages + (1 + count) * (1 + word_length)
); );
// Measure latency of boot. // Measure latency of boot.
let storage = store.extract_storage(); let (mut store, time) = measure(&timer, || unsafe { boot_store(num_pages, false) });
let (mut store, time) = measure(timer, || boot_store(storage, false));
writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap(); writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap();
stat.boot_ms = time.ms();
// Measure latency of remove. // Measure latency of remove.
let ((), time) = measure(timer, || store.remove(key).unwrap()); let ((), time) = measure(&timer, || store.remove(key).unwrap());
writeln!(console, "Remove: {:.1}ms.", time.ms()).unwrap(); writeln!(console, "Remove: {:.1}ms.", time.ms()).unwrap();
stat.remove_ms = time.ms();
// Measure latency of compaction. // Measure latency of compaction.
let length = total_capacity + num_pages - store.lifetime().unwrap().used(); let length = total_capacity + num_pages - store.lifetime().unwrap().used();
@@ -156,84 +111,28 @@ fn compute_latency(
assert_eq!(store.lifetime().unwrap().used(), num_pages + total_capacity); assert_eq!(store.lifetime().unwrap().used(), num_pages + total_capacity);
let ((), time) = measure(timer, || store.prepare(1).unwrap()); let ((), time) = measure(timer, || store.prepare(1).unwrap());
writeln!(console, "Compaction: {:.1}ms.", time.ms()).unwrap(); writeln!(console, "Compaction: {:.1}ms.", time.ms()).unwrap();
stat.compaction_ms = time.ms();
assert!(store.lifetime().unwrap().used() > total_capacity + num_pages); assert!(store.lifetime().unwrap().used() > total_capacity + num_pages);
(store.extract_storage(), stat)
} }
fn main() { fn main() {
let mut with_callback = timer::with_callback::<Syscalls, DefaultConfig, _>(|_| {}); let mut with_callback = timer::with_callback(|_, _| {});
let timer = with_callback.init().ok().unwrap();
let timer = with_callback.init().flex_unwrap(); writeln!(Console::new(), "\nRunning 4 tests...").unwrap();
let storage = take_storage::<Syscalls, DefaultConfig>().unwrap(); // Those non-overwritten 50 words entries simulate credentials.
let config = storage_config(&storage); compute_latency(&timer, 3, 1, 50);
let mut stats = Vec::new(); compute_latency(&timer, 20, 1, 50);
let mut console = Console::<Syscalls>::writer(); // Those overwritten 1 word entries simulate counters.
compute_latency(&timer, 3, 0, 1);
compute_latency(&timer, 6, 0, 1);
writeln!(Console::new(), "\nDone.").unwrap();
writeln!(console, "\nRunning 2 tests...").unwrap(); // Results on nrf52840dk:
// Simulate a store full of credentials (of 50 words). //
let (storage, stat) = compute_latency(storage, &timer, config.num_pages, 1, 50); // | Pages | Overwrite | Length | Boot | Compaction | Insert | Remove |
stats.push(stat); // | ----- | --------- | --------- | ------- | ---------- | ------ | ------- |
// Simulate a store full of increments of a single counter. // | 3 | no | 50 words | 2.0 ms | 132.5 ms | 4.8 ms | 1.2 ms |
let (_storage, stat) = compute_latency(storage, &timer, config.num_pages, 0, 1); // | 20 | no | 50 words | 7.4 ms | 135.5 ms | 10.2 ms | 3.9 ms |
stats.push(stat); // | 3 | yes | 1 word | 21.9 ms | 94.5 ms | 12.4 ms | 5.9 ms |
writeln!(console, "\nDone.\n").unwrap(); // | 6 | yes | 1 word | 55.2 ms | 100.8 ms | 24.8 ms | 12.1 ms |
const HEADERS: &[&str] = &[
"Overwrite",
"Length",
"Boot",
"Compaction",
"Insert",
"Remove",
];
let mut matrix = vec![HEADERS.iter().map(|x| x.to_string()).collect()];
for stat in stats {
matrix.push(vec![
if stat.key_increment == 0 { "yes" } else { "no" }.to_string(),
format!("{} words", stat.entry_length),
format!("{:.1} ms", stat.boot_ms),
format!("{:.1} ms", stat.compaction_ms),
format!("{:.1} ms", stat.insert_ms),
format!("{:.1} ms", stat.remove_ms),
]);
}
writeln!(console, "Copy to examples/store_latency.rs:\n").unwrap();
writeln!(console, "{:?}", config).unwrap();
write_matrix(matrix);
// Results for nrf52840dk_opensk:
// StorageConfig { num_pages: 20 }
// Overwrite Length Boot Compaction Insert Remove
// no 50 words 18.6 ms 145.8 ms 21.0 ms 9.8 ms
// yes 1 words 335.8 ms 100.6 ms 11.7 ms 5.7 ms
}
fn align(x: &str, n: usize) {
let mut console = Console::<Syscalls>::writer();
for _ in 0..n.saturating_sub(x.len()) {
write!(console, " ").unwrap();
}
write!(console, "{}", x).unwrap();
}
fn write_matrix(mut m: Vec<Vec<String>>) {
if m.is_empty() {
return;
}
let num_cols = m.iter().map(|r| r.len()).max().unwrap();
let mut col_len = vec![0; num_cols];
for row in &mut m {
row.resize(num_cols, String::new());
for col in 0..num_cols {
col_len[col] = core::cmp::max(col_len[col], row[col].len());
}
}
for row in m {
for col in 0..num_cols {
align(&row[col], col_len[col] + 2 * (col > 0) as usize);
}
writeln!(Console::<Syscalls>::writer()).unwrap();
}
} }

589
fuzz/Cargo.lock generated Normal file
View File

@@ -0,0 +1,589 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "arbitrary"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cbor"
version = "0.1.0"
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "crypto"
version = "0.1.0"
dependencies = [
"arrayref",
"byteorder",
"cbor",
"hex",
"libtock_drivers",
"rand",
"regex",
"ring",
"serde",
"serde_json",
"subtle",
"untrusted",
]
[[package]]
name = "ctap2"
version = "1.0.0"
dependencies = [
"arrayref",
"byteorder",
"cbor",
"crypto",
"lang_items",
"libtock_core",
"libtock_drivers",
"persistent_store",
"subtle",
"uuid",
]
[[package]]
name = "ctap2-fuzz"
version = "0.0.0"
dependencies = [
"fuzz_helper",
"libfuzzer-sys",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "fuzz_helper"
version = "0.1.0"
dependencies = [
"arrayref",
"cbor",
"crypto",
"ctap2",
"lang_items",
"libtock_drivers",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
[[package]]
name = "itoa"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lang_items"
version = "0.1.0"
dependencies = [
"libtock_core",
"libtock_drivers",
"linked_list_allocator",
]
[[package]]
name = "libc"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "libfuzzer-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf184a4b6b274f82a5df6b357da6055d3e82272327bba281c28bbba6f1664ef"
dependencies = [
"arbitrary",
"cc",
]
[[package]]
name = "libtock_codegen"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "libtock_core"
version = "0.1.0"
dependencies = [
"libtock_codegen",
]
[[package]]
name = "libtock_drivers"
version = "0.1.0"
dependencies = [
"libtock_core",
]
[[package]]
name = "linked_list_allocator"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822add9edb1860698b79522510da17bef885171f75aa395cff099d770c609c24"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "persistent_store"
version = "0.1.0"
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha",
"rand_core 0.4.2",
"rand_hc",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "opensk-fuzz" name = "ctap2-fuzz"
version = "0.0.0" version = "0.0.0"
authors = ["Automatically generated"] authors = ["Automatically generated"]
publish = false publish = false
@@ -9,7 +9,7 @@ edition = "2018"
cargo-fuzz = true cargo-fuzz = true
[dependencies] [dependencies]
libfuzzer-sys = { version = "0.3" } libfuzzer-sys = { version = "0.3"}
fuzz_helper = { path = "fuzz_helper" } fuzz_helper = { path = "fuzz_helper" }
# Prevent this from interfering with workspaces # Prevent this from interfering with workspaces
@@ -34,36 +34,18 @@ path = "fuzz_targets/fuzz_target_process_ctap2_client_pin.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "fuzz_target_process_ctap2_client_pin_structured"
path = "fuzz_targets/fuzz_target_process_ctap2_client_pin_structured.rs"
test = false
doc = false
[[bin]] [[bin]]
name = "fuzz_target_process_ctap2_get_assertion" name = "fuzz_target_process_ctap2_get_assertion"
path = "fuzz_targets/fuzz_target_process_ctap2_get_assertion.rs" path = "fuzz_targets/fuzz_target_process_ctap2_get_assertion.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "fuzz_target_process_ctap2_get_assertion_structured"
path = "fuzz_targets/fuzz_target_process_ctap2_get_assertion_structured.rs"
test = false
doc = false
[[bin]] [[bin]]
name = "fuzz_target_process_ctap2_make_credential" name = "fuzz_target_process_ctap2_make_credential"
path = "fuzz_targets/fuzz_target_process_ctap2_make_credential.rs" path = "fuzz_targets/fuzz_target_process_ctap2_make_credential.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "fuzz_target_process_ctap2_make_credential_structured"
path = "fuzz_targets/fuzz_target_process_ctap2_make_credential_structured.rs"
test = false
doc = false
[[bin]] [[bin]]
name = "fuzz_target_split_assemble" name = "fuzz_target_split_assemble"
path = "fuzz_targets/fuzz_target_split_assemble.rs" path = "fuzz_targets/fuzz_target_split_assemble.rs"

View File

@@ -0,0 +1,14 @@
[package]
name = "fuzz_helper"
version = "0.1.0"
authors = ["Mingxiao Guo <mingxguo@google.com>"]
license = "Apache-2.0"
edition = "2018"
[dependencies]
arrayref = "0.3.6"
libtock_drivers = { path = "../../third_party/libtock-drivers" }
crypto = { path = "../../libraries/crypto", features = ['std'] }
cbor = { path = "../../libraries/cbor", features = ['std'] }
ctap2 = { path = "../..", features = ['std'] }
lang_items = { path = "../../third_party/lang-items", features = ['std'] }

View File

@@ -12,24 +12,31 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use arbitrary::{Arbitrary, Unstructured}; // This explicit "extern crate" is needed to make the linker aware of the
// `libtock_alloc_init` symbol.
extern crate lang_items;
use arrayref::array_ref; use arrayref::array_ref;
use core::convert::TryFrom; use core::convert::TryFrom;
use opensk::api::customization::is_valid; use crypto::rng256::ThreadRng256;
use opensk::ctap::command::{ use ctap2::ctap::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, Command, AuthenticatorMakeCredentialParameters,
}; };
use opensk::ctap::data_formats::EnterpriseAttestationMode; use ctap2::ctap::hid::receive::MessageAssembler;
use opensk::ctap::hid::{ use ctap2::ctap::hid::send::HidPacketIterator;
ChannelID, CtapHidCommand, HidPacket, HidPacketIterator, Message, MessageAssembler, use ctap2::ctap::hid::{ChannelID, CtapHid, HidPacket, Message};
}; use ctap2::ctap::status_code::Ctap2StatusCode;
use opensk::ctap::{cbor_read, Channel, CtapState}; use ctap2::ctap::CtapState;
use opensk::env::test::customization::TestCustomization; use libtock_drivers::timer::{ClockValue, Timestamp};
use opensk::env::test::TestEnv;
use opensk::{test_helpers, Ctap, Transport};
const COMMAND_INIT: u8 = 0x06;
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
const PACKET_TYPE_MASK: u8 = 0x80;
const CLOCK_FREQUENCY_HZ: usize = 32768;
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum InputType { pub enum InputType {
@@ -39,17 +46,8 @@ pub enum InputType {
Ctap1, Ctap1,
} }
pub enum FuzzError { fn user_immediately_present(_: ChannelID) -> Result<(), Ctap2StatusCode> {
ArbitraryError(arbitrary::Error), Ok(())
InvalidCustomization,
}
pub type FuzzResult<T> = Result<T, FuzzError>;
impl From<arbitrary::Error> for FuzzError {
fn from(err: arbitrary::Error) -> Self {
Self::ArbitraryError(err)
}
} }
// Converts a byte slice into Message // Converts a byte slice into Message
@@ -59,14 +57,13 @@ fn raw_to_message(data: &[u8]) -> Message {
cid[..data.len()].copy_from_slice(data); cid[..data.len()].copy_from_slice(data);
Message { Message {
cid, cid,
// Arbitrary command. cmd: 0,
cmd: CtapHidCommand::Cbor,
payload: vec![], payload: vec![],
} }
} else { } else {
Message { Message {
cid: array_ref!(data, 0, 4).clone(), cid: array_ref!(data, 0, 4).clone(),
cmd: CtapHidCommand::from(data[4]), cmd: data[4],
payload: data[5..].to_vec(), payload: data[5..].to_vec(),
} }
} }
@@ -74,18 +71,24 @@ fn raw_to_message(data: &[u8]) -> Message {
// Returns an initialized ctap state, hid and the allocated cid // Returns an initialized ctap state, hid and the allocated cid
// after processing the init command. // after processing the init command.
fn initialize(ctap: &mut Ctap<TestEnv>) -> ChannelID { fn initialize<CheckUserPresence>(
ctap_state: &mut CtapState<ThreadRng256, CheckUserPresence>,
ctap_hid: &mut CtapHid,
) -> ChannelID
where
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
{
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let message = Message { let message = Message {
cid: CHANNEL_BROADCAST, cid: CHANNEL_BROADCAST,
cmd: CtapHidCommand::Init, cmd: COMMAND_INIT,
payload: nonce, payload: nonce,
}; };
let mut assembler_reply = MessageAssembler::default(); let mut assembler_reply = MessageAssembler::new();
let mut result_cid: ChannelID = Default::default(); let mut result_cid: ChannelID = Default::default();
for pkt_request in HidPacketIterator::new(message).unwrap() { for pkt_request in HidPacketIterator::new(message).unwrap() {
for pkt_reply in ctap.process_hid_packet(&pkt_request, Transport::MainHid) { for pkt_reply in ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, ctap_state) {
if let Ok(Some(result)) = assembler_reply.parse_packet(ctap.env(), &pkt_reply, None) { if let Ok(Some(result)) = assembler_reply.parse_packet(&pkt_reply, DUMMY_TIMESTAMP) {
result_cid.copy_from_slice(&result.payload[8..12]); result_cid.copy_from_slice(&result.payload[8..12]);
} }
} }
@@ -98,7 +101,7 @@ fn is_type(data: &[u8], input_type: InputType) -> bool {
if input_type == InputType::Ctap1 { if input_type == InputType::Ctap1 {
return true; return true;
} }
match cbor_read(data) { match cbor::read(data) {
Err(_) => false, Err(_) => false,
Ok(decoded_cbor) => match input_type { Ok(decoded_cbor) => match input_type {
InputType::CborMakeCredentialParameter => { InputType::CborMakeCredentialParameter => {
@@ -117,14 +120,22 @@ fn is_type(data: &[u8], input_type: InputType) -> bool {
// Interprets the raw data as a complete message (with channel id, command type and payload) and // Interprets the raw data as a complete message (with channel id, command type and payload) and
// invokes message splitting, packet processing at CTAP HID level and response assembling. // invokes message splitting, packet processing at CTAP HID level and response assembling.
fn process_message(data: &[u8], ctap: &mut Ctap<TestEnv>) { fn process_message<CheckUserPresence>(
data: &[u8],
ctap_state: &mut CtapState<ThreadRng256, CheckUserPresence>,
ctap_hid: &mut CtapHid,
) where
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
{
let message = raw_to_message(data); let message = raw_to_message(data);
if let Some(hid_packet_iterator) = HidPacketIterator::new(message) { if let Some(hid_packet_iterator) = HidPacketIterator::new(message) {
let mut assembler_reply = MessageAssembler::default(); let mut assembler_reply = MessageAssembler::new();
for pkt_request in hid_packet_iterator { for pkt_request in hid_packet_iterator {
for pkt_reply in ctap.process_hid_packet(&pkt_request, Transport::MainHid) { for pkt_reply in
ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, ctap_state)
{
// Only checks for assembling crashes, not for semantics. // Only checks for assembling crashes, not for semantics.
let _ = assembler_reply.parse_packet(ctap.env(), &pkt_reply, None); let _ = assembler_reply.parse_packet(&pkt_reply, DUMMY_TIMESTAMP);
} }
} }
} }
@@ -133,66 +144,30 @@ fn process_message(data: &[u8], ctap: &mut Ctap<TestEnv>) {
// Interprets the raw data as any ctap command (including the command byte) and // Interprets the raw data as any ctap command (including the command byte) and
// invokes message splitting, packet processing at CTAP HID level and response assembling // invokes message splitting, packet processing at CTAP HID level and response assembling
// using an initialized and allocated channel. // using an initialized and allocated channel.
pub fn process_ctap_any_type(data: &[u8]) -> arbitrary::Result<()> { pub fn process_ctap_any_type(data: &[u8]) {
let mut unstructured = Unstructured::new(data);
let mut env = TestEnv::default();
env.seed_rng_from_u64(u64::arbitrary(&mut unstructured)?);
let data = unstructured.take_rest();
// Initialize ctap state and hid and get the allocated cid. // Initialize ctap state and hid and get the allocated cid.
let mut ctap = Ctap::new(env); let mut rng = ThreadRng256 {};
let cid = initialize(&mut ctap); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new();
let cid = initialize(&mut ctap_state, &mut ctap_hid);
// Wrap input as message with the allocated cid. // Wrap input as message with the allocated cid.
let mut command = cid.to_vec(); let mut command = cid.to_vec();
command.extend(data); command.extend(data);
process_message(&command, &mut ctap); process_message(&command, &mut ctap_state, &mut ctap_hid);
Ok(())
}
fn setup_customization(
unstructured: &mut Unstructured,
customization: &mut TestCustomization,
) -> FuzzResult<()> {
customization.setup_enterprise_attestation(
Option::<EnterpriseAttestationMode>::arbitrary(unstructured)?,
// TODO: Generate arbitrary rp_id_list (but with some dummies because content doesn't
// matter), and use the rp ids in commands.
None,
);
if !is_valid(customization) {
return Err(FuzzError::InvalidCustomization);
}
Ok(())
}
fn setup_state(
unstructured: &mut Unstructured,
state: &mut CtapState<TestEnv>,
env: &mut TestEnv,
) -> FuzzResult<()> {
if bool::arbitrary(unstructured)? {
test_helpers::enable_enterprise_attestation(state, env).ok();
}
Ok(())
} }
// Interprets the raw data as of the given input type and // Interprets the raw data as of the given input type and
// invokes message splitting, packet processing at CTAP HID level and response assembling // invokes message splitting, packet processing at CTAP HID level and response assembling
// using an initialized and allocated channel. // using an initialized and allocated channel.
pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) -> arbitrary::Result<()> { pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
let mut unstructured = Unstructured::new(data);
let mut env = TestEnv::default();
env.seed_rng_from_u64(u64::arbitrary(&mut unstructured)?);
let data = unstructured.take_rest();
if !is_type(data, input_type) { if !is_type(data, input_type) {
return Ok(()); return;
} }
// Initialize ctap state and hid and get the allocated cid. // Initialize ctap state and hid and get the allocated cid.
let mut ctap = Ctap::new(env); let mut rng = ThreadRng256 {};
let cid = initialize(&mut ctap); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new();
let cid = initialize(&mut ctap_state, &mut ctap_hid);
// Wrap input as message with allocated cid and command type. // Wrap input as message with allocated cid and command type.
let mut command = cid.to_vec(); let mut command = cid.to_vec();
match input_type { match input_type {
@@ -210,67 +185,24 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) -> arbitra
} }
} }
command.extend(data); command.extend(data);
process_message(&command, &mut ctap); process_message(&command, &mut ctap_state, &mut ctap_hid);
Ok(())
}
pub fn process_ctap_structured(data: &[u8], input_type: InputType) -> FuzzResult<()> {
let unstructured = &mut Unstructured::new(data);
let mut env = TestEnv::default();
env.seed_rng_from_u64(u64::arbitrary(unstructured)?);
setup_customization(unstructured, env.customization_mut())?;
let mut state = CtapState::new(&mut env);
setup_state(unstructured, &mut state, &mut env)?;
let command = match input_type {
InputType::CborMakeCredentialParameter => Command::AuthenticatorMakeCredential(
AuthenticatorMakeCredentialParameters::arbitrary(unstructured)?,
),
InputType::CborGetAssertionParameter => Command::AuthenticatorGetAssertion(
AuthenticatorGetAssertionParameters::arbitrary(unstructured)?,
),
InputType::CborClientPinParameter => Command::AuthenticatorClientPin(
AuthenticatorClientPinParameters::arbitrary(unstructured)?,
),
InputType::Ctap1 => {
unimplemented!()
}
};
state
.process_parsed_command(
&mut env,
command,
Channel::MainHid(ChannelID::arbitrary(unstructured)?),
)
.ok();
Ok(())
} }
// Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed. // Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed.
pub fn split_assemble_hid_packets(data: &[u8]) -> arbitrary::Result<()> { pub fn split_assemble_hid_packets(data: &[u8]) {
let mut unstructured = Unstructured::new(data); let mut message = raw_to_message(data);
let mut env = TestEnv::default();
env.seed_rng_from_u64(u64::arbitrary(&mut unstructured)?);
let data = unstructured.take_rest();
let message = raw_to_message(data);
if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) { if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) {
let mut assembler = MessageAssembler::default(); let mut assembler = MessageAssembler::new();
let packets: Vec<HidPacket> = hid_packet_iterator.collect(); let packets: Vec<HidPacket> = hid_packet_iterator.collect();
if let Some((last_packet, first_packets)) = packets.split_last() { if let Some((last_packet, first_packets)) = packets.split_last() {
for packet in first_packets { for packet in first_packets {
assert_eq!(assembler.parse_packet(&mut env, packet, None), Ok(None)); assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None));
} }
message.cmd &= !PACKET_TYPE_MASK;
assert_eq!( assert_eq!(
assembler.parse_packet(&mut env, last_packet, None), assembler.parse_packet(last_packet, DUMMY_TIMESTAMP),
Ok(Some(message)) Ok(Some(message))
); );
} }
} }
Ok(())
} }

View File

@@ -7,5 +7,5 @@ use libfuzzer_sys::fuzz_target;
// For a more generic fuzz target including all CTAP commands, you can use // For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command. // fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::Ctap1).ok(); process_ctap_specific_type(data, InputType::Ctap1);
}); });

View File

@@ -7,5 +7,5 @@ use libfuzzer_sys::fuzz_target;
// For a more generic fuzz target including all CTAP commands, you can use // For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command. // fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::CborClientPinParameter).ok(); process_ctap_specific_type(data, InputType::CborClientPinParameter);
}); });

View File

@@ -7,5 +7,5 @@ use libfuzzer_sys::fuzz_target;
// For a more generic fuzz target including all CTAP commands, you can use // For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command. // fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::CborGetAssertionParameter).ok(); process_ctap_specific_type(data, InputType::CborGetAssertionParameter);
}); });

View File

@@ -7,5 +7,5 @@ use libfuzzer_sys::fuzz_target;
// For a more generic fuzz target including all CTAP commands, you can use // For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command. // fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::CborMakeCredentialParameter).ok(); process_ctap_specific_type(data, InputType::CborMakeCredentialParameter);
}); });

View File

@@ -5,5 +5,5 @@ use libfuzzer_sys::fuzz_target;
// Generically fuzz inputs as CTAP commands. // Generically fuzz inputs as CTAP commands.
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
process_ctap_any_type(data).ok(); process_ctap_any_type(data);
}); });

View File

@@ -5,5 +5,5 @@ use libfuzzer_sys::fuzz_target;
// Fuzzing HID packets splitting and assembling functions. // Fuzzing HID packets splitting and assembling functions.
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
split_assemble_hid_packets(data).ok(); split_assemble_hid_packets(data);
}); });

View File

@@ -1,24 +1,24 @@
"""Creates a directory containing seed inputs from a json file having
the following structure:
[
{
"hex": "a901a1182a182a02a3626964781a6d616b655f6261645f7...",
"cbor": "{1: h'42', 2: {\"id\": \"make.example.com\", ...",
"description": "make credential parameters"
},
...
]
Usage:
- pass the resulting corpus directory path as the first argument
- pass the json file path to make the corpus from as the second argument
Example:
python make_corpus.py ./corpus ./corpus_file.json
"""
import argparse import argparse
import json import json
import os.path import os.path
# Creates a directory containing seed inputs from a json file having
# the following structure:
# [
# {
# "hex": "a901a1182a182a02a3626964781a6d616b655f6261645f7...",
# "cbor": "{1: h'42', 2: {\"id\": \"make.example.com\", ...",
# "description": "make credential parameters"
# },
# ...
# ]
#
# Usage:
# - pass the resulting corpus directory path as the first argument
# - pass the json file path to make the corpus from as the second argument
# Example:
# python make_corpus.py ./corpus ./corpus_file.json
# Creates a corpus directory to the given path from the given json file. # Creates a corpus directory to the given path from the given json file.
def make_corpus(corpus_dir, corpus_json): def make_corpus(corpus_dir, corpus_json):

158
layout.ld Normal file
View File

@@ -0,0 +1,158 @@
/* Userland Generic Layout
*
* Currently, due to incomplete ROPI-RWPI support in rustc (see
* https://github.com/tock/libtock-rs/issues/28), this layout implements static
* linking. An application init script must define the FLASH and SRAM address
* ranges as well as MPU_MIN_ALIGN before including this layout file.
*
* Here is a an example application linker script to get started:
* MEMORY {
* /* FLASH memory region must start immediately *after* the Tock
* * Binary Format headers, which means you need to offset the
* * beginning of FLASH memory region relative to where the
* * application is loaded.
* FLASH (rx) : ORIGIN = 0x10030, LENGTH = 0x0FFD0
* SRAM (RWX) : ORIGIN = 0x20000, LENGTH = 0x10000
* }
* STACK_SIZE = 2048;
* MPU_MIN_ALIGN = 8K;
* INCLUDE ../libtock-rs/layout.ld
*/
ENTRY(_start)
SECTIONS {
/* Section for just the app crt0 header.
* This must be first so that the app can find it.
*/
.crt0_header :
{
_beginning = .; /* Start of the app in flash. */
/**
* Populate the header expected by `crt0`:
*
* struct hdr {
* uint32_t got_sym_start;
* uint32_t got_start;
* uint32_t got_size;
* uint32_t data_sym_start;
* uint32_t data_start;
* uint32_t data_size;
* uint32_t bss_start;
* uint32_t bss_size;
* uint32_t reldata_start;
* uint32_t stack_size;
* };
*/
/* Offset of GOT symbols in flash */
LONG(LOADADDR(.got) - _beginning);
/* Offset of GOT section in memory */
LONG(_got);
/* Size of GOT section */
LONG(SIZEOF(.got));
/* Offset of data symbols in flash */
LONG(LOADADDR(.data) - _beginning);
/* Offset of data section in memory */
LONG(_data);
/* Size of data section */
LONG(SIZEOF(.data));
/* Offset of BSS section in memory */
LONG(_bss);
/* Size of BSS section */
LONG(SIZEOF(.bss));
/* First address offset after program flash, where elf2tab places
* .rel.data section */
LONG(LOADADDR(.endflash) - _beginning);
/* The size of the stack requested by this application */
LONG(STACK_SIZE);
/* Pad the header out to a multiple of 32 bytes so there is not a gap
* between the header and subsequent .data section. It's unclear why,
* but LLD is aligning sections to a multiple of 32 bytes. */
. = ALIGN(32);
} > FLASH =0xFF
/* Text section, Code! */
.text :
{
. = ALIGN(4);
_text = .;
KEEP (*(.start))
*(.text*)
*(.rodata*)
KEEP (*(.syscalls))
*(.ARM.extab*)
. = ALIGN(4); /* Make sure we're word-aligned here */
_etext = .;
} > FLASH =0xFF
/* Application stack */
.stack :
{
. = . + STACK_SIZE;
_stack_top_unaligned = .;
. = ALIGN(8);
_stack_top_aligned = .;
} > SRAM
/* Data section, static initialized variables
* Note: This is placed in Flash after the text section, but needs to be
* moved to SRAM at runtime
*/
.data : AT (_etext)
{
. = ALIGN(4); /* Make sure we're word-aligned here */
_data = .;
KEEP(*(.data*))
. = ALIGN(4); /* Make sure we're word-aligned at the end of flash */
} > SRAM
/* Global Offset Table */
.got :
{
. = ALIGN(4); /* Make sure we're word-aligned here */
_got = .;
*(.got*)
*(.got.plt*)
. = ALIGN(4);
} > SRAM
/* BSS section, static uninitialized variables */
.bss :
{
. = ALIGN(4); /* Make sure we're word-aligned here */
_bss = .;
KEEP(*(.bss*))
*(COMMON)
. = ALIGN(4);
} > SRAM
/* End of flash. */
.endflash :
{
} > FLASH
/* ARM Exception support
*
* This contains compiler-generated support for unwinding the stack,
* consisting of key-value pairs of function addresses and information on
* how to unwind stack frames.
* https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf
*
* .ARM.exidx is sorted, so has to go in its own output section.
*
* __NOTE__: It's at the end because we currently don't actually serialize
* it to the binary in elf2tbf. If it was before the RAM sections, it would
* through off our calculations of the header.
*/
PROVIDE_HIDDEN (__exidx_start = .);
.ARM.exidx :
{
/* (C++) Index entries for section unwinding */
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
PROVIDE_HIDDEN (__exidx_end = .);
}
ASSERT((_stack_top_aligned - _stack_top_unaligned) == 0, "
STACK_SIZE must be 8 byte multiple")

5
libraries/cbor/Cargo.lock generated Normal file
View File

@@ -0,0 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "cbor"
version = "0.1.0"

View File

@@ -1,22 +1,15 @@
[package] [package]
name = "sk-cbor" name = "cbor"
version = "0.1.2" version = "0.1.0"
authors = [ authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>", "Fabian Kaczmarczyck <kaczmarczyck@google.com>",
"Guillaume Endignoux <guillaumee@google.com>", "Guillaume Endignoux <guillaumee@google.com>",
"Jean-Michel Picod <jmichel@google.com>", "Jean-Michel Picod <jmichel@google.com>",
"David Drysdale <drysdale@google.com>",
] ]
license = "Apache-2.0" license = "Apache-2.0"
edition = "2018" edition = "2018"
description = "CBOR parsing library"
homepage = "https://github.com/google/OpenSK"
repository = "https://github.com/google/OpenSK"
keywords = ["cbor", "serialization", "no_std"]
categories = ["encoding"]
readme = "README.md"
[badges]
maintenance = { status = "passively-maintained" }
[dependencies] [dependencies]
[features]
std = []

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,54 +0,0 @@
# CBOR Parsing Library
[![crates.io](https://img.shields.io/crates/d/sk-cbor.svg)](https://crates.io/crates/sk-cbor)
[![crates.io](https://img.shields.io/crates/v/sk-cbor.svg)](https://crates.io/crates/sk-cbor)
[![docs.rs](https://docs.rs/sk-cbor/badge.svg)](https://docs.rs/sk-cbor)
[![License](https://img.shields.io/crates/l/sk-cbor.svg)](https://crates.io/crates/sk-cbor)
[![Maintenance](https://img.shields.io/maintenance/yes/2021)](https://crates.io/crates/sk-cbor)
This crate implements Concise Binary Object Representation (CBOR) from [RFC
8949](https://datatracker.ietf.org/doc/html/rfc8949).
## Usage
```rust
fn main() {
// Build a CBOR object with the crate's convenience macros. Note that this
// object is not built in canonical order.
let map_object = cbor_map! {
1 => cbor_array![2, 3],
"tstr" => cbor_bytes!(vec![1, 2, 3]),
-2 => cbor_null!(),
3 => cbor_true!(),
};
println!("Object {:?}", map_object);
// Serialize to bytes.
let mut map_data = vec![];
sk_cbor::writer::write(map_object, &mut map_data).unwrap();
let hex_map_data = hex::encode(&map_data);
// Serialized version is in canonical order.
println!("Serializes to {}", hex_map_data);
assert_eq!(
hex_map_data,
concat!(
"a4", // 4-map
"01", // int(1) =>
"820203", // 2-array [2, 3],
"03", // int(3) =>
"f5", // true,
"21", // nint(-2) =>
"f6", // null,
"6474737472", // 4-tstr "tstr" =>
"43010203" // 3-bstr
)
);
// Convert back to an object. This is different than the original object,
// because the map is now in canonical order.
let recovered_object = sk_cbor::reader::read(&map_data).unwrap();
println!("Deserializes to {:?}", recovered_object);
}
```

View File

@@ -1,87 +0,0 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
//! Example program demonstrating cbor usage.
extern crate alloc;
use sk_cbor::values::Value;
use sk_cbor::{cbor_array, cbor_bytes, cbor_map, cbor_null, cbor_true};
fn hexify(data: &[u8]) -> String {
let mut s = String::new();
for b in data {
s.push_str(&format!("{:02x}", b));
}
s
}
fn main() {
// Build a CBOR object with various different types included. Note that this
// object is not built in canonical order.
let manual_object = Value::map(vec![
(
Value::from(1),
Value::array(vec![Value::from(2), Value::from(3)]),
),
(Value::from("tstr".to_owned()), Value::from(vec![1, 2, 3])),
(Value::from(-2), Value::null_value()),
(Value::from(3), Value::bool_value(true)),
]);
// Build the same object using the crate's convenience macros.
let macro_object = cbor_map! {
1 => cbor_array![2, 3],
"tstr" => cbor_bytes!(vec![1, 2, 3]),
-2 => cbor_null!(),
3 => cbor_true!(),
};
assert_eq!(manual_object, macro_object);
println!("Object {:?}", manual_object);
// Serialize to bytes.
let mut manual_data = vec![];
sk_cbor::writer::write(manual_object, &mut manual_data).unwrap();
let hex_manual_data = hexify(&manual_data);
let mut macro_data = vec![];
sk_cbor::writer::write(macro_object, &mut macro_data).unwrap();
let hex_macro_data = hexify(&macro_data);
assert_eq!(hex_manual_data, hex_macro_data);
// Serialized version is in canonical order.
println!("Serializes to {}", hex_manual_data);
assert_eq!(
hex_manual_data,
concat!(
"a4", // 4-map
"01", // int(1) =>
"820203", // 2-array [2, 3],
"03", // int(3) =>
"f5", // true,
"21", // nint(-2) =>
"f6", // null,
"6474737472", // 4-tstr "tstr" =>
"43010203" // 3-bstr
)
);
// Convert back to an object. This is different than the original object,
// because the map is now in canonical order.
let recovered_object = sk_cbor::reader::read(&manual_data).unwrap();
println!("Deserializes to {:?}", recovered_object);
}

View File

@@ -1,3 +0,0 @@
/artifacts/
/corpus/
/target/

35
libraries/cbor/fuzz/Cargo.lock generated Normal file
View File

@@ -0,0 +1,35 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arbitrary"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
[[package]]
name = "cbor"
version = "0.1.0"
[[package]]
name = "cbor-fuzz"
version = "0.0.0"
dependencies = [
"cbor",
"libfuzzer-sys",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "libfuzzer-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf184a4b6b274f82a5df6b357da6055d3e82272327bba281c28bbba6f1664ef"
dependencies = [
"arbitrary",
"cc",
]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "sk-cbor-fuzz" name = "cbor-fuzz"
version = "0.0.0" version = "0.0.0"
authors = ["Automatically generated"] authors = ["Automatically generated"]
publish = false publish = false
@@ -12,7 +12,7 @@ cargo-fuzz = true
[dependencies] [dependencies]
libfuzzer-sys = "0.3" libfuzzer-sys = "0.3"
[dependencies.sk-cbor] [dependencies.cbor]
path = ".." path = ".."
# Prevent this from interfering with workspaces # Prevent this from interfering with workspaces

View File

@@ -3,12 +3,11 @@ extern crate alloc;
use alloc::vec::Vec; use alloc::vec::Vec;
use libfuzzer_sys::fuzz_target; use libfuzzer_sys::fuzz_target;
use sk_cbor as cbor;
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
if let Ok(value) = cbor::read(data) { if let Ok(value) = cbor::read(data) {
let mut result = Vec::new(); let mut result = Vec::new();
assert!(cbor::write(value, &mut result).is_ok()); assert!(cbor::write(value, &mut result));
assert_eq!(result, data); assert_eq!(result, data);
}; };
}); });

View File

@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![no_std] #![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc; extern crate alloc;
#[cfg(feature = "std")]
extern crate core;
pub mod macros; pub mod macros;
pub mod reader; pub mod reader;
@@ -22,5 +24,5 @@ pub mod values;
pub mod writer; pub mod writer;
pub use self::reader::read; pub use self::reader::read;
pub use self::values::Value; pub use self::values::{KeyType, SimpleValue, Value};
pub use self::writer::write; pub use self::writer::write;

View File

@@ -12,17 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Convenience macros for working with CBOR values. use crate::values::{KeyType, Value};
use alloc::collections::btree_map;
use crate::values::Value;
use alloc::vec;
use core::cmp::Ordering; use core::cmp::Ordering;
use core::iter::Peekable; use core::iter::Peekable;
/// This macro generates code to extract multiple values from a `Vec<(Value, Value)>` at once /// This macro generates code to extract multiple values from a `BTreeMap<KeyType, Value>` at once
/// in an optimized manner, consuming the input vector. /// in an optimized manner, consuming the input map.
/// ///
/// It takes as input a `Vec` as well as a list of identifiers and keys, and generates code /// It takes as input a `BTreeMap` as well as a list of identifiers and keys, and generates code
/// that assigns the corresponding values to new variables using the given identifiers. Each of /// that assigns the corresponding values to new variables using the given identifiers. Each of
/// these variables has type `Option<Value>`, to account for the case where keys aren't found. /// these variables has type `Option<Value>`, to account for the case where keys aren't found.
/// ///
@@ -34,14 +32,16 @@ use core::iter::Peekable;
/// the keys are indeed sorted. This macro is therefore **not suitable for dynamic keys** that can /// the keys are indeed sorted. This macro is therefore **not suitable for dynamic keys** that can
/// change at runtime. /// change at runtime.
/// ///
/// Example usage: /// Semantically, provided that the keys are sorted as specified above, the following two snippets
/// of code are equivalent, but the `destructure_cbor_map!` version is more optimized, as it doesn't
/// re-balance the `BTreeMap` for each key, contrary to the `BTreeMap::remove` operations.
/// ///
/// ```rust /// ```rust
/// # extern crate alloc; /// # extern crate alloc;
/// # use sk_cbor::destructure_cbor_map; /// # use cbor::destructure_cbor_map;
/// # /// #
/// # fn main() { /// # fn main() {
/// # let map = alloc::vec::Vec::new(); /// # let map = alloc::collections::BTreeMap::new();
/// destructure_cbor_map! { /// destructure_cbor_map! {
/// let { /// let {
/// 1 => x, /// 1 => x,
@@ -50,6 +50,17 @@ use core::iter::Peekable;
/// } /// }
/// # } /// # }
/// ``` /// ```
///
/// ```rust
/// # extern crate alloc;
/// #
/// # fn main() {
/// # let mut map = alloc::collections::BTreeMap::<cbor::KeyType, _>::new();
/// use cbor::values::IntoCborKey;
/// let x: Option<cbor::Value> = map.remove(&1.into_cbor_key());
/// let y: Option<cbor::Value> = map.remove(&"key".into_cbor_key());
/// # }
/// ```
#[macro_export] #[macro_export]
macro_rules! destructure_cbor_map { macro_rules! destructure_cbor_map {
( let { $( $key:expr => $variable:ident, )+ } = $map:expr; ) => { ( let { $( $key:expr => $variable:ident, )+ } = $map:expr; ) => {
@@ -59,7 +70,7 @@ macro_rules! destructure_cbor_map {
#[cfg(test)] #[cfg(test)]
$crate::assert_sorted_keys!($( $key, )+); $crate::assert_sorted_keys!($( $key, )+);
use $crate::values::{IntoCborValue, Value}; use $crate::values::{IntoCborKey, Value};
use $crate::macros::destructure_cbor_map_peek_value; use $crate::macros::destructure_cbor_map_peek_value;
// This algorithm first converts the map into a peekable iterator - whose items are sorted // This algorithm first converts the map into a peekable iterator - whose items are sorted
@@ -72,7 +83,7 @@ macro_rules! destructure_cbor_map {
// to come in the same order (i.e. sorted). // to come in the same order (i.e. sorted).
let mut it = $map.into_iter().peekable(); let mut it = $map.into_iter().peekable();
$( $(
let $variable: Option<Value> = destructure_cbor_map_peek_value(&mut it, $key.into_cbor_value()); let $variable: Option<Value> = destructure_cbor_map_peek_value(&mut it, $key.into_cbor_key());
)+ )+
}; };
} }
@@ -89,14 +100,14 @@ macro_rules! destructure_cbor_map {
/// would be inlined for every use case. As of June 2020, this saves ~40KB of binary size for the /// would be inlined for every use case. As of June 2020, this saves ~40KB of binary size for the
/// CTAP2 application of OpenSK. /// CTAP2 application of OpenSK.
pub fn destructure_cbor_map_peek_value( pub fn destructure_cbor_map_peek_value(
it: &mut Peekable<vec::IntoIter<(Value, Value)>>, it: &mut Peekable<btree_map::IntoIter<KeyType, Value>>,
needle: Value, needle: KeyType,
) -> Option<Value> { ) -> Option<Value> {
loop { loop {
match it.peek() { match it.peek() {
None => return None, None => return None,
Some(item) => { Some(item) => {
let key: &Value = &item.0; let key: &KeyType = &item.0;
match key.cmp(&needle) { match key.cmp(&needle) {
Ordering::Less => { Ordering::Less => {
it.next(); it.next();
@@ -112,7 +123,6 @@ pub fn destructure_cbor_map_peek_value(
} }
} }
/// Assert that the keys in a vector of key-value pairs are in canonical order.
#[macro_export] #[macro_export]
macro_rules! assert_sorted_keys { macro_rules! assert_sorted_keys {
// Last key // Last key
@@ -121,9 +131,9 @@ macro_rules! assert_sorted_keys {
( $key1:expr, $key2:expr, $( $keys:expr, )* ) => { ( $key1:expr, $key2:expr, $( $keys:expr, )* ) => {
{ {
use $crate::values::{IntoCborValue, Value}; use $crate::values::{IntoCborKey, KeyType};
let k1: Value = $key1.into_cbor_value(); let k1: KeyType = $key1.into_cbor_key();
let k2: Value = $key2.into_cbor_value(); let k2: KeyType = $key2.into_cbor_key();
assert!( assert!(
k1 < k2, k1 < k2,
"{:?} < {:?} failed. The destructure_cbor_map! macro requires keys in sorted order.", "{:?} < {:?} failed. The destructure_cbor_map! macro requires keys in sorted order.",
@@ -135,26 +145,6 @@ macro_rules! assert_sorted_keys {
}; };
} }
/// Creates a CBOR Value of type Map with the specified key-value pairs.
///
/// Keys and values are expressions and converted into CBOR Keys and Values.
/// The syntax for these pairs is `key_expression => value_expression,`.
/// Keys do not have to be sorted.
///
/// # Panics
///
/// You may not call this function with identical keys in its argument.
///
/// Example usage:
///
/// ```rust
/// # extern crate alloc;
/// # use sk_cbor::cbor_map;
/// let map = cbor_map! {
/// 0x01 => false,
/// "02" => -3,
/// };
/// ```
#[macro_export] #[macro_export]
macro_rules! cbor_map { macro_rules! cbor_map {
// trailing comma case // trailing comma case
@@ -166,36 +156,16 @@ macro_rules! cbor_map {
{ {
// The import is unused if the list is empty. // The import is unused if the list is empty.
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::values::IntoCborValue; use $crate::values::{IntoCborKey, IntoCborValue};
let mut _map = ::alloc::vec::Vec::new(); let mut _map = ::alloc::collections::BTreeMap::new();
$( $(
_map.push(($key.into_cbor_value(), $value.into_cbor_value())); _map.insert($key.into_cbor_key(), $value.into_cbor_value());
)* )*
$crate::values::Value::map(_map) $crate::values::Value::Map(_map)
} }
}; };
} }
/// Creates a CBOR Value of type Map with key-value pairs where values can be Options.
///
/// Keys and values are expressions and converted into CBOR Keys and Value Options.
/// The map entry is included iff the Value is not an Option or Option is Some.
/// The syntax for these pairs is `key_expression => value_expression,`.
/// Duplicate keys will lead to invalid CBOR, i.e. writing these values fails.
/// Keys do not have to be sorted.
///
/// Example usage:
///
/// ```rust
/// # extern crate alloc;
/// # use sk_cbor::cbor_map_options;
/// let missing_value: Option<bool> = None;
/// let map = cbor_map_options! {
/// 0x01 => Some(false),
/// "02" => -3,
/// "not in map" => missing_value,
/// };
/// ```
#[macro_export] #[macro_export]
macro_rules! cbor_map_options { macro_rules! cbor_map_options {
// trailing comma case // trailing comma case
@@ -207,40 +177,28 @@ macro_rules! cbor_map_options {
{ {
// The import is unused if the list is empty. // The import is unused if the list is empty.
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::values::{IntoCborValue, IntoCborValueOption}; use $crate::values::{IntoCborKey, IntoCborValueOption};
let mut _map = ::alloc::vec::Vec::<(_, $crate::values::Value)>::new(); let mut _map = ::alloc::collections::BTreeMap::<_, $crate::values::Value>::new();
$( $(
{ {
let opt: Option<$crate::values::Value> = $value.into_cbor_value_option(); let opt: Option<$crate::values::Value> = $value.into_cbor_value_option();
if let Some(val) = opt { if let Some(val) = opt {
_map.push(($key.into_cbor_value(), val)); _map.insert($key.into_cbor_key(), val);
} }
} }
)* )*
$crate::values::Value::map(_map) $crate::values::Value::Map(_map)
} }
}; };
} }
/// Creates a CBOR Value of type Map from a Vec<(Value, Value)>.
#[macro_export] #[macro_export]
macro_rules! cbor_map_collection { macro_rules! cbor_map_btree {
( $tree:expr ) => {{ ( $tree:expr ) => {
$crate::values::Value::map($tree) $crate::values::Value::Map($tree)
}}; };
} }
/// Creates a CBOR Value of type Array with the given elements.
///
/// Elements are expressions and converted into CBOR Values. Elements are comma-separated.
///
/// Example usage:
///
/// ```rust
/// # extern crate alloc;
/// # use sk_cbor::cbor_array;
/// let array = cbor_array![1, "2"];
/// ```
#[macro_export] #[macro_export]
macro_rules! cbor_array { macro_rules! cbor_array {
// trailing comma case // trailing comma case
@@ -253,53 +211,47 @@ macro_rules! cbor_array {
// The import is unused if the list is empty. // The import is unused if the list is empty.
#[allow(unused_imports)] #[allow(unused_imports)]
use $crate::values::IntoCborValue; use $crate::values::IntoCborValue;
$crate::values::Value::array(vec![ $( $value.into_cbor_value(), )* ]) $crate::values::Value::Array(vec![ $( $value.into_cbor_value(), )* ])
} }
}; };
} }
/// Creates a CBOR Value of type Array from a Vec<Value>.
#[macro_export] #[macro_export]
macro_rules! cbor_array_vec { macro_rules! cbor_array_vec {
( $vec:expr ) => {{ ( $vec:expr ) => {{
use $crate::values::IntoCborValue; use $crate::values::IntoCborValue;
$crate::values::Value::array($vec.into_iter().map(|x| x.into_cbor_value()).collect()) $crate::values::Value::Array($vec.into_iter().map(|x| x.into_cbor_value()).collect())
}}; }};
} }
/// Creates a CBOR Value of type Simple with value true.
#[macro_export] #[macro_export]
macro_rules! cbor_true { macro_rules! cbor_true {
( ) => { ( ) => {
$crate::values::Value::bool_value(true) $crate::values::Value::Simple($crate::values::SimpleValue::TrueValue)
}; };
} }
/// Creates a CBOR Value of type Simple with value false.
#[macro_export] #[macro_export]
macro_rules! cbor_false { macro_rules! cbor_false {
( ) => { ( ) => {
$crate::values::Value::bool_value(false) $crate::values::Value::Simple($crate::values::SimpleValue::FalseValue)
}; };
} }
/// Creates a CBOR Value of type Simple with value null.
#[macro_export] #[macro_export]
macro_rules! cbor_null { macro_rules! cbor_null {
( ) => { ( ) => {
$crate::values::Value::null_value() $crate::values::Value::Simple($crate::values::SimpleValue::NullValue)
}; };
} }
/// Creates a CBOR Value of type Simple with the undefined value.
#[macro_export] #[macro_export]
macro_rules! cbor_undefined { macro_rules! cbor_undefined {
( ) => { ( ) => {
$crate::values::Value::undefined() $crate::values::Value::Simple($crate::values::SimpleValue::Undefined)
}; };
} }
/// Creates a CBOR Value of type Simple with the given bool value.
#[macro_export] #[macro_export]
macro_rules! cbor_bool { macro_rules! cbor_bool {
( $x:expr ) => { ( $x:expr ) => {
@@ -307,55 +259,37 @@ macro_rules! cbor_bool {
}; };
} }
/// Creates a CBOR Value of type Unsigned with the given numeric value. // For key types, we construct a KeyType and call .into(), which will automatically convert it to a
// KeyType or a Value depending on the context.
#[macro_export] #[macro_export]
macro_rules! cbor_unsigned { macro_rules! cbor_unsigned {
( $x:expr ) => { ( $x:expr ) => {
$crate::values::Value::unsigned($x as u64) $crate::cbor_key_unsigned!($x).into()
}; };
} }
/// Creates a CBOR Value of type Unsigned or Negative with the given numeric value.
#[macro_export] #[macro_export]
macro_rules! cbor_int { macro_rules! cbor_int {
( $x:expr ) => { ( $x:expr ) => {
$crate::values::Value::integer($x as i64) $crate::cbor_key_int!($x).into()
}; };
} }
/// Creates a CBOR Value of type Text String with the given string.
#[macro_export] #[macro_export]
macro_rules! cbor_text { macro_rules! cbor_text {
( $x:expr ) => { ( $x:expr ) => {
$crate::values::Value::text_string($x.into()) $crate::cbor_key_text!($x).into()
}; };
} }
/// Creates a CBOR Value of type Byte String with the given slice or vector.
#[macro_export] #[macro_export]
macro_rules! cbor_bytes { macro_rules! cbor_bytes {
( $x:expr ) => { ( $x:expr ) => {
$crate::values::Value::byte_string($x) $crate::cbor_key_bytes!($x).into()
}; };
} }
/// Creates a CBOR Value of type Tag with the given tag and object. // Macro to use with a literal, e.g. cbor_bytes_lit!(b"foo")
#[macro_export]
macro_rules! cbor_tagged {
( $t:expr, $x: expr ) => {
$crate::values::Value::tag($t, $x)
};
}
/// Creates a CBOR Value of type Byte String with the given byte string literal.
///
/// Example usage:
///
/// ```rust
/// # extern crate alloc;
/// # use sk_cbor::cbor_bytes_lit;
/// let byte_array = cbor_bytes_lit!(b"foo");
/// ```
#[macro_export] #[macro_export]
macro_rules! cbor_bytes_lit { macro_rules! cbor_bytes_lit {
( $x:expr ) => { ( $x:expr ) => {
@@ -363,70 +297,100 @@ macro_rules! cbor_bytes_lit {
}; };
} }
// Some explicit macros are also available for contexts where the type is not explicit.
#[macro_export]
macro_rules! cbor_key_unsigned {
( $x:expr ) => {
$crate::values::KeyType::Unsigned($x)
};
}
#[macro_export]
macro_rules! cbor_key_int {
( $x:expr ) => {
$crate::values::KeyType::integer($x)
};
}
#[macro_export]
macro_rules! cbor_key_text {
( $x:expr ) => {
$crate::values::KeyType::TextString($x.into())
};
}
#[macro_export]
macro_rules! cbor_key_bytes {
( $x:expr ) => {
$crate::values::KeyType::ByteString($x)
};
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::super::values::Value; use super::super::values::{KeyType, SimpleValue, Value};
use alloc::string::String; use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
#[test] #[test]
fn test_cbor_simple_values() { fn test_cbor_simple_values() {
assert_eq!(cbor_true!(), Value::bool_value(true)); assert_eq!(cbor_true!(), Value::Simple(SimpleValue::TrueValue));
assert_eq!(cbor_false!(), Value::bool_value(false)); assert_eq!(cbor_false!(), Value::Simple(SimpleValue::FalseValue));
assert_eq!(cbor_null!(), Value::null_value()); assert_eq!(cbor_null!(), Value::Simple(SimpleValue::NullValue));
assert_eq!(cbor_undefined!(), Value::undefined()); assert_eq!(cbor_undefined!(), Value::Simple(SimpleValue::Undefined));
} }
#[test] #[test]
fn test_cbor_bool() { fn test_cbor_bool() {
assert_eq!(cbor_bool!(true), Value::bool_value(true)); assert_eq!(cbor_bool!(true), Value::Simple(SimpleValue::TrueValue));
assert_eq!(cbor_bool!(false), Value::bool_value(false)); assert_eq!(cbor_bool!(false), Value::Simple(SimpleValue::FalseValue));
} }
#[test] #[test]
fn test_cbor_int_unsigned() { fn test_cbor_int_unsigned() {
assert_eq!(cbor_int!(0), Value::from(0)); assert_eq!(cbor_key_int!(0), KeyType::Unsigned(0));
assert_eq!(cbor_int!(1), Value::from(1)); assert_eq!(cbor_key_int!(1), KeyType::Unsigned(1));
assert_eq!(cbor_int!(123456), Value::from(123456)); assert_eq!(cbor_key_int!(123456), KeyType::Unsigned(123456));
assert_eq!( assert_eq!(
cbor_int!(core::i64::MAX), cbor_key_int!(std::i64::MAX),
Value::from(core::i64::MAX as u64) KeyType::Unsigned(std::i64::MAX as u64)
); );
} }
#[test] #[test]
fn test_cbor_int_negative() { fn test_cbor_int_negative() {
assert_eq!(cbor_int!(-1), Value::from(-1)); assert_eq!(cbor_key_int!(-1), KeyType::Negative(-1));
assert_eq!(cbor_int!(-123456), Value::from(-123456)); assert_eq!(cbor_key_int!(-123456), KeyType::Negative(-123456));
assert_eq!(cbor_int!(core::i64::MIN), Value::from(core::i64::MIN)); assert_eq!(
cbor_key_int!(std::i64::MIN),
KeyType::Negative(std::i64::MIN)
);
} }
#[test] #[test]
fn test_cbor_int_literals() { fn test_cbor_int_literals() {
let a = cbor_array![ let a = cbor_array![
core::i64::MIN, std::i64::MIN,
core::i32::MIN, std::i32::MIN,
-123456, -123456,
-1, -1,
0, 0,
1, 1,
123456, 123456,
core::i32::MAX, std::i32::MAX,
core::i64::MAX, std::i64::MAX,
core::u64::MAX, std::u64::MAX,
]; ];
let b = Value::array(vec![ let b = Value::Array(vec![
Value::from(core::i64::MIN), Value::KeyValue(KeyType::Negative(std::i64::MIN)),
Value::from(core::i32::MIN as i64), Value::KeyValue(KeyType::Negative(std::i32::MIN as i64)),
Value::from(-123456), Value::KeyValue(KeyType::Negative(-123456)),
Value::from(-1), Value::KeyValue(KeyType::Negative(-1)),
Value::from(0), Value::KeyValue(KeyType::Unsigned(0)),
Value::from(1), Value::KeyValue(KeyType::Unsigned(1)),
Value::from(123456), Value::KeyValue(KeyType::Unsigned(123456)),
Value::from(core::i32::MAX as u64), Value::KeyValue(KeyType::Unsigned(std::i32::MAX as u64)),
Value::from(core::i64::MAX as u64), Value::KeyValue(KeyType::Unsigned(std::i64::MAX as u64)),
Value::from(core::u64::MAX), Value::KeyValue(KeyType::Unsigned(std::u64::MAX)),
]); ]);
assert_eq!(a, b); assert_eq!(a, b);
} }
@@ -445,17 +409,25 @@ mod test {
cbor_map! {}, cbor_map! {},
cbor_map! {2 => 3}, cbor_map! {2 => 3},
]; ];
let b = Value::array(vec![ let b = Value::Array(vec![
Value::from(-123), Value::KeyValue(KeyType::Negative(-123)),
Value::from(456), Value::KeyValue(KeyType::Unsigned(456)),
Value::bool_value(true), Value::Simple(SimpleValue::TrueValue),
Value::null_value(), Value::Simple(SimpleValue::NullValue),
Value::from(String::from("foo")), Value::KeyValue(KeyType::TextString(String::from("foo"))),
Value::from(b"bar".to_vec()), Value::KeyValue(KeyType::ByteString(b"bar".to_vec())),
Value::array(Vec::new()), Value::Array(Vec::new()),
Value::array(vec![Value::from(0), Value::from(1)]), Value::Array(vec![
Value::map(Vec::new()), Value::KeyValue(KeyType::Unsigned(0)),
Value::map([(Value::from(2), Value::from(3))].iter().cloned().collect()), Value::KeyValue(KeyType::Unsigned(1)),
]),
Value::Map(BTreeMap::new()),
Value::Map(
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
.iter()
.cloned()
.collect(),
),
]); ]);
assert_eq!(a, b); assert_eq!(a, b);
} }
@@ -463,18 +435,18 @@ mod test {
#[test] #[test]
fn test_cbor_array_vec_empty() { fn test_cbor_array_vec_empty() {
let a = cbor_array_vec!(Vec::<bool>::new()); let a = cbor_array_vec!(Vec::<bool>::new());
let b = Value::array(Vec::new()); let b = Value::Array(Vec::new());
assert_eq!(a, b); assert_eq!(a, b);
} }
#[test] #[test]
fn test_cbor_array_vec_int() { fn test_cbor_array_vec_int() {
let a = cbor_array_vec!(vec![1, 2, 3, 4]); let a = cbor_array_vec!(vec![1, 2, 3, 4]);
let b = Value::array(vec![ let b = Value::Array(vec![
Value::from(1), Value::KeyValue(KeyType::Unsigned(1)),
Value::from(2), Value::KeyValue(KeyType::Unsigned(2)),
Value::from(3), Value::KeyValue(KeyType::Unsigned(3)),
Value::from(4), Value::KeyValue(KeyType::Unsigned(4)),
]); ]);
assert_eq!(a, b); assert_eq!(a, b);
} }
@@ -482,10 +454,10 @@ mod test {
#[test] #[test]
fn test_cbor_array_vec_text() { fn test_cbor_array_vec_text() {
let a = cbor_array_vec!(vec!["a", "b", "c"]); let a = cbor_array_vec!(vec!["a", "b", "c"]);
let b = Value::array(vec![ let b = Value::Array(vec![
Value::from(String::from("a")), Value::KeyValue(KeyType::TextString(String::from("a"))),
Value::from(String::from("b")), Value::KeyValue(KeyType::TextString(String::from("b"))),
Value::from(String::from("c")), Value::KeyValue(KeyType::TextString(String::from("c"))),
]); ]);
assert_eq!(a, b); assert_eq!(a, b);
} }
@@ -493,10 +465,10 @@ mod test {
#[test] #[test]
fn test_cbor_array_vec_bytes() { fn test_cbor_array_vec_bytes() {
let a = cbor_array_vec!(vec![b"a", b"b", b"c"]); let a = cbor_array_vec!(vec![b"a", b"b", b"c"]);
let b = Value::array(vec![ let b = Value::Array(vec![
Value::from(b"a".to_vec()), Value::KeyValue(KeyType::ByteString(b"a".to_vec())),
Value::from(b"b".to_vec()), Value::KeyValue(KeyType::ByteString(b"b".to_vec())),
Value::from(b"c".to_vec()), Value::KeyValue(KeyType::ByteString(b"c".to_vec())),
]); ]);
assert_eq!(a, b); assert_eq!(a, b);
} }
@@ -504,35 +476,58 @@ mod test {
#[test] #[test]
fn test_cbor_map() { fn test_cbor_map() {
let a = cbor_map! { let a = cbor_map! {
-1 => -23,
4 => 56, 4 => 56,
"foo" => true,
b"bar" => cbor_null!(),
5 => "foo", 5 => "foo",
6 => b"bar", 6 => b"bar",
7 => cbor_array![], 7 => cbor_array![],
8 => cbor_array![0, 1], 8 => cbor_array![0, 1],
9 => cbor_map!{}, 9 => cbor_map!{},
10 => cbor_map!{2 => 3}, 10 => cbor_map!{2 => 3},
-1 => -23,
b"bar" => cbor_null!(),
"foo" => true,
}; };
let b = Value::map( let b = Value::Map(
[ [
(Value::from(4), Value::from(56)),
(Value::from(5), Value::from(String::from("foo"))),
(Value::from(6), Value::from(b"bar".to_vec())),
(Value::from(7), Value::array(Vec::new())),
( (
Value::from(8), KeyType::Negative(-1),
Value::array(vec![Value::from(0), Value::from(1)]), Value::KeyValue(KeyType::Negative(-23)),
), ),
(Value::from(9), Value::map(Vec::new())), (KeyType::Unsigned(4), Value::KeyValue(KeyType::Unsigned(56))),
( (
Value::from(10), KeyType::TextString(String::from("foo")),
Value::map([(Value::from(2), Value::from(3))].iter().cloned().collect()), Value::Simple(SimpleValue::TrueValue),
),
(
KeyType::ByteString(b"bar".to_vec()),
Value::Simple(SimpleValue::NullValue),
),
(
KeyType::Unsigned(5),
Value::KeyValue(KeyType::TextString(String::from("foo"))),
),
(
KeyType::Unsigned(6),
Value::KeyValue(KeyType::ByteString(b"bar".to_vec())),
),
(KeyType::Unsigned(7), Value::Array(Vec::new())),
(
KeyType::Unsigned(8),
Value::Array(vec![
Value::KeyValue(KeyType::Unsigned(0)),
Value::KeyValue(KeyType::Unsigned(1)),
]),
),
(KeyType::Unsigned(9), Value::Map(BTreeMap::new())),
(
KeyType::Unsigned(10),
Value::Map(
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
.iter()
.cloned()
.collect(),
),
), ),
(Value::from(-1), Value::from(-23)),
(Value::from(b"bar".to_vec()), Value::null_value()),
(Value::from(String::from("foo")), Value::bool_value(true)),
] ]
.iter() .iter()
.cloned() .cloned()
@@ -544,43 +539,66 @@ mod test {
#[test] #[test]
fn test_cbor_map_options() { fn test_cbor_map_options() {
let a = cbor_map_options! { let a = cbor_map_options! {
4 => Some(56),
5 => "foo",
6 => Some(b"bar" as &[u8]),
7 => cbor_array![],
8 => Some(cbor_array![0, 1]),
9 => cbor_map!{},
10 => Some(cbor_map!{2 => 3}),
11 => None::<String>,
12 => None::<&str>,
13 => None::<Vec<u8>>,
14 => None::<&[u8]>,
15 => None::<bool>,
16 => None::<i32>,
17 => None::<i64>,
18 => None::<u64>,
-1 => -23, -1 => -23,
b"bar" => Some(cbor_null!()), 4 => Some(56),
11 => None::<String>,
"foo" => true, "foo" => true,
12 => None::<&str>,
b"bar" => Some(cbor_null!()),
13 => None::<Vec<u8>>,
5 => "foo",
14 => None::<&[u8]>,
6 => Some(b"bar" as &[u8]),
15 => None::<bool>,
7 => cbor_array![],
16 => None::<i32>,
8 => Some(cbor_array![0, 1]),
17 => None::<i64>,
9 => cbor_map!{},
18 => None::<u64>,
10 => Some(cbor_map!{2 => 3}),
}; };
let b = Value::map( let b = Value::Map(
[ [
(Value::from(4), Value::from(56)),
(Value::from(5), Value::from(String::from("foo"))),
(Value::from(6), Value::from(b"bar".to_vec())),
(Value::from(7), Value::array(Vec::new())),
( (
Value::from(8), KeyType::Negative(-1),
Value::array(vec![Value::from(0), Value::from(1)]), Value::KeyValue(KeyType::Negative(-23)),
), ),
(Value::from(9), Value::map(Vec::new())), (KeyType::Unsigned(4), Value::KeyValue(KeyType::Unsigned(56))),
( (
Value::from(10), KeyType::TextString(String::from("foo")),
Value::map([(Value::from(2), Value::from(3))].iter().cloned().collect()), Value::Simple(SimpleValue::TrueValue),
),
(
KeyType::ByteString(b"bar".to_vec()),
Value::Simple(SimpleValue::NullValue),
),
(
KeyType::Unsigned(5),
Value::KeyValue(KeyType::TextString(String::from("foo"))),
),
(
KeyType::Unsigned(6),
Value::KeyValue(KeyType::ByteString(b"bar".to_vec())),
),
(KeyType::Unsigned(7), Value::Array(Vec::new())),
(
KeyType::Unsigned(8),
Value::Array(vec![
Value::KeyValue(KeyType::Unsigned(0)),
Value::KeyValue(KeyType::Unsigned(1)),
]),
),
(KeyType::Unsigned(9), Value::Map(BTreeMap::new())),
(
KeyType::Unsigned(10),
Value::Map(
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
.iter()
.cloned()
.collect(),
),
), ),
(Value::from(-1), Value::from(-23)),
(Value::from(b"bar".to_vec()), Value::null_value()),
(Value::from(String::from("foo")), Value::bool_value(true)),
] ]
.iter() .iter()
.cloned() .cloned()
@@ -590,21 +608,34 @@ mod test {
} }
#[test] #[test]
fn test_cbor_map_collection_empty() { fn test_cbor_map_btree_empty() {
let a = cbor_map_collection!(Vec::<(_, _)>::new()); let a = cbor_map_btree!(BTreeMap::new());
let b = Value::map(Vec::new()); let b = Value::Map(BTreeMap::new());
assert_eq!(a, b); assert_eq!(a, b);
} }
#[test] #[test]
fn test_cbor_map_collection_foo() { fn test_cbor_map_btree_foo() {
let a = cbor_map_collection!(vec![(Value::from(2), Value::from(3))]); let a = cbor_map_btree!(
let b = Value::map(vec![(Value::from(2), Value::from(3))]); [(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
.iter()
.cloned()
.collect()
);
let b = Value::Map(
[(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))]
.iter()
.cloned()
.collect(),
);
assert_eq!(a, b); assert_eq!(a, b);
} }
fn extract_map(cbor_value: Value) -> Vec<(Value, Value)> { fn extract_map(cbor_value: Value) -> BTreeMap<KeyType, Value> {
cbor_value.extract_map().unwrap() match cbor_value {
Value::Map(map) => map,
_ => panic!("Expected CBOR map."),
}
} }
#[test] #[test]
@@ -694,22 +725,4 @@ mod test {
assert_eq!(x4, Some(cbor_unsigned!(40))); assert_eq!(x4, Some(cbor_unsigned!(40)));
assert_eq!(x5, None); assert_eq!(x5, None);
} }
#[test]
fn test_destructure_unsorted_cbor_map() {
let map = cbor_map! {
2 => 20,
1 => 10,
};
destructure_cbor_map! {
let {
1 => x1,
2 => x2,
} = extract_map(map);
}
assert_eq!(x1, Some(cbor_unsigned!(10)));
assert_eq!(x2, Some(cbor_unsigned!(20)));
}
} }

View File

@@ -12,24 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Functionality for deserializing CBOR data into values. use super::values::{Constants, KeyType, SimpleValue, Value};
use crate::{cbor_array_vec, cbor_bytes_lit, cbor_map_btree, cbor_text, cbor_unsigned};
use super::values::{Constants, SimpleValue, Value, ValueImpl}; use alloc::collections::BTreeMap;
use crate::{
cbor_array_vec, cbor_bytes_lit, cbor_map_collection, cbor_tagged, cbor_text, cbor_unsigned,
};
use alloc::str; use alloc::str;
use alloc::vec::Vec; use alloc::vec::Vec;
/// Possible errors from a deserialization operation.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DecoderError { pub enum DecoderError {
UnsupportedMajorType, UnsupportedMajorType,
UnknownAdditionalInfo, UnknownAdditionalInfo,
IncompleteCborData, IncompleteCborData,
IncorrectMapKeyType,
TooMuchNesting, TooMuchNesting,
InvalidUtf8, InvalidUtf8,
ExtraneousData, ExtranousData,
OutOfOrderKey, OutOfOrderKey,
NonMinimalCborEncoding, NonMinimalCborEncoding,
UnsupportedSimpleValue, UnsupportedSimpleValue,
@@ -37,21 +34,11 @@ pub enum DecoderError {
OutOfRangeIntegerValue, OutOfRangeIntegerValue,
} }
/// Deserialize CBOR binary data to produce a single [`Value`], expecting that there is no additional data.
/// Maximum level of nesting supported is 127; more deeply nested structures will fail with
/// [`DecoderError::TooMuchNesting`].
pub fn read(encoded_cbor: &[u8]) -> Result<Value, DecoderError> { pub fn read(encoded_cbor: &[u8]) -> Result<Value, DecoderError> {
read_nested(encoded_cbor, Some(i8::MAX))
}
/// Deserialize CBOR binary data to produce a single [`Value`], expecting that there is no additional data. If
/// `max_nest` is `Some(max)`, then nested structures are only supported up to the given limit (returning
/// [`DecoderError::TooMuchNesting`] if the limit is hit).
pub fn read_nested(encoded_cbor: &[u8], max_nest: Option<i8>) -> Result<Value, DecoderError> {
let mut reader = Reader::new(encoded_cbor); let mut reader = Reader::new(encoded_cbor);
let value = reader.decode_complete_data_item(max_nest)?; let value = reader.decode_complete_data_item(Reader::MAX_NESTING_DEPTH)?;
if !reader.remaining_cbor.is_empty() { if !reader.remaining_cbor.is_empty() {
return Err(DecoderError::ExtraneousData); return Err(DecoderError::ExtranousData);
} }
Ok(value) Ok(value)
} }
@@ -61,6 +48,8 @@ struct Reader<'a> {
} }
impl<'a> Reader<'a> { impl<'a> Reader<'a> {
const MAX_NESTING_DEPTH: i8 = 4;
pub fn new(cbor: &'a [u8]) -> Reader<'a> { pub fn new(cbor: &'a [u8]) -> Reader<'a> {
Reader { Reader {
remaining_cbor: cbor, remaining_cbor: cbor,
@@ -69,9 +58,9 @@ impl<'a> Reader<'a> {
pub fn decode_complete_data_item( pub fn decode_complete_data_item(
&mut self, &mut self,
remaining_depth: Option<i8>, remaining_depth: i8,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
if remaining_depth.map_or(false, |d| d < 0) { if remaining_depth < 0 {
return Err(DecoderError::TooMuchNesting); return Err(DecoderError::TooMuchNesting);
} }
@@ -80,17 +69,19 @@ impl<'a> Reader<'a> {
// Unsigned byte means logical shift, so only zeros get shifted in. // Unsigned byte means logical shift, so only zeros get shifted in.
let major_type_value = first_byte >> Constants::MAJOR_TYPE_BIT_SHIFT; let major_type_value = first_byte >> Constants::MAJOR_TYPE_BIT_SHIFT;
let additional_info = first_byte & Constants::ADDITIONAL_INFORMATION_MASK; let additional_info = first_byte & Constants::ADDITIONAL_INFORMATION_MASK;
let size_value = self.read_variadic_length_integer(additional_info)?; let size_result = self.read_variadic_length_integer(additional_info);
match major_type_value { match size_result {
0 => self.decode_value_to_unsigned(size_value), Ok(size_value) => match major_type_value {
1 => self.decode_value_to_negative(size_value), 0 => self.decode_value_to_unsigned(size_value),
2 => self.read_byte_string_content(size_value), 1 => self.decode_value_to_negative(size_value),
3 => self.read_text_string_content(size_value), 2 => self.read_byte_string_content(size_value),
4 => self.read_array_content(size_value, remaining_depth), 3 => self.read_text_string_content(size_value),
5 => self.read_map_content(size_value, remaining_depth), 4 => self.read_array_content(size_value, remaining_depth),
6 => self.read_tagged_content(size_value, remaining_depth), 5 => self.read_map_content(size_value, remaining_depth),
7 => self.decode_to_simple_value(size_value, additional_info), 7 => self.decode_to_simple_value(size_value, additional_info),
_ => Err(DecoderError::UnsupportedMajorType), _ => Err(DecoderError::UnsupportedMajorType),
},
Err(decode_error) => Err(decode_error),
} }
} }
_ => Err(DecoderError::IncompleteCborData), _ => Err(DecoderError::IncompleteCborData),
@@ -144,7 +135,7 @@ impl<'a> Reader<'a> {
if signed_size < 0 { if signed_size < 0 {
Err(DecoderError::OutOfRangeIntegerValue) Err(DecoderError::OutOfRangeIntegerValue)
} else { } else {
Ok(Value(ValueImpl::Negative(-(size_value as i64) - 1))) Ok(Value::KeyValue(KeyType::Negative(-(size_value as i64) - 1)))
} }
} }
@@ -168,12 +159,12 @@ impl<'a> Reader<'a> {
fn read_array_content( fn read_array_content(
&mut self, &mut self,
size_value: u64, size_value: u64,
remaining_depth: Option<i8>, remaining_depth: i8,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
// Don't set the capacity already, it is an unsanitized input. // Don't set the capacity already, it is an unsanitized input.
let mut value_array = Vec::new(); let mut value_array = Vec::new();
for _ in 0..size_value { for _ in 0..size_value {
value_array.push(self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?); value_array.push(self.decode_complete_data_item(remaining_depth - 1)?);
} }
Ok(cbor_array_vec!(value_array)) Ok(cbor_array_vec!(value_array))
} }
@@ -181,31 +172,25 @@ impl<'a> Reader<'a> {
fn read_map_content( fn read_map_content(
&mut self, &mut self,
size_value: u64, size_value: u64,
remaining_depth: Option<i8>, remaining_depth: i8,
) -> Result<Value, DecoderError> { ) -> Result<Value, DecoderError> {
let mut value_map = Vec::<(Value, Value)>::new(); let mut value_map = BTreeMap::new();
let mut last_key_option = None;
for _ in 0..size_value { for _ in 0..size_value {
let key = self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?; let key_value = self.decode_complete_data_item(remaining_depth - 1)?;
if let Some(last_item) = value_map.last() { if let Value::KeyValue(key) = key_value {
if last_item.0 >= key { if let Some(last_key) = last_key_option {
return Err(DecoderError::OutOfOrderKey); if last_key >= key {
return Err(DecoderError::OutOfOrderKey);
}
} }
last_key_option = Some(key.clone());
value_map.insert(key, self.decode_complete_data_item(remaining_depth - 1)?);
} else {
return Err(DecoderError::IncorrectMapKeyType);
} }
value_map.push((
key,
self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?,
));
} }
Ok(cbor_map_collection!(value_map)) Ok(cbor_map_btree!(value_map))
}
fn read_tagged_content(
&mut self,
tag_value: u64,
remaining_depth: Option<i8>,
) -> Result<Value, DecoderError> {
let inner_value = self.decode_complete_data_item(remaining_depth.map(|d| d - 1))?;
Ok(cbor_tagged!(tag_value, inner_value))
} }
fn decode_to_simple_value( fn decode_to_simple_value(
@@ -221,7 +206,7 @@ impl<'a> Reader<'a> {
return Err(DecoderError::UnsupportedFloatingPointValue); return Err(DecoderError::UnsupportedFloatingPointValue);
} }
match SimpleValue::from_integer(size_value) { match SimpleValue::from_integer(size_value) {
Some(simple_value) => Ok(Value(ValueImpl::Simple(simple_value))), Some(simple_value) => Ok(Value::Simple(simple_value)),
None => Err(DecoderError::UnsupportedSimpleValue), None => Err(DecoderError::UnsupportedSimpleValue),
} }
} }
@@ -234,7 +219,6 @@ mod test {
cbor_array, cbor_bytes, cbor_false, cbor_int, cbor_map, cbor_null, cbor_true, cbor_array, cbor_bytes, cbor_false, cbor_int, cbor_map, cbor_null, cbor_true,
cbor_undefined, cbor_undefined,
}; };
use alloc::vec;
#[test] #[test]
fn test_read_unsigned() { fn test_read_unsigned() {
@@ -254,14 +238,14 @@ mod test {
vec![0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], vec![0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
), ),
( (
core::i64::MAX, std::i64::MAX,
vec![0x1B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], vec![0x1B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
), ),
]; ];
for (unsigned, mut cbor) in cases { for (unsigned, mut cbor) in cases {
assert_eq!(read(&cbor), Ok(cbor_int!(unsigned))); assert_eq!(read(&cbor), Ok(cbor_int!(unsigned)));
cbor.push(0x01); cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -316,14 +300,14 @@ mod test {
(-1000000, vec![0x3A, 0x00, 0x0F, 0x42, 0x3F]), (-1000000, vec![0x3A, 0x00, 0x0F, 0x42, 0x3F]),
(-4294967296, vec![0x3A, 0xFF, 0xFF, 0xFF, 0xFF]), (-4294967296, vec![0x3A, 0xFF, 0xFF, 0xFF, 0xFF]),
( (
core::i64::MIN, std::i64::MIN,
vec![0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], vec![0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
), ),
]; ];
for (negative, mut cbor) in cases { for (negative, mut cbor) in cases {
assert_eq!(read(&cbor), Ok(cbor_int!(negative))); assert_eq!(read(&cbor), Ok(cbor_int!(negative)));
cbor.push(0x01); cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -339,7 +323,7 @@ mod test {
for (byte_string, mut cbor) in cases { for (byte_string, mut cbor) in cases {
assert_eq!(read(&cbor), Ok(cbor_bytes!(byte_string))); assert_eq!(read(&cbor), Ok(cbor_bytes!(byte_string)));
cbor.push(0x01); cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -353,7 +337,7 @@ mod test {
("\"\\", vec![0x62, 0x22, 0x5C]), ("\"\\", vec![0x62, 0x22, 0x5C]),
("ü", vec![0x62, 0xC3, 0xBC]), ("ü", vec![0x62, 0xC3, 0xBC]),
( (
core::str::from_utf8(&unicode_3byte).unwrap(), std::str::from_utf8(&unicode_3byte).unwrap(),
vec![0x63, 0xE6, 0xB0, 0xB4], vec![0x63, 0xE6, 0xB0, 0xB4],
), ),
("𐅑", vec![0x64, 0xF0, 0x90, 0x85, 0x91]), ("𐅑", vec![0x64, 0xF0, 0x90, 0x85, 0x91]),
@@ -361,7 +345,7 @@ mod test {
for (text_string, mut cbor) in cases { for (text_string, mut cbor) in cases {
assert_eq!(read(&cbor), Ok(cbor_text!(text_string))); assert_eq!(read(&cbor), Ok(cbor_text!(text_string)));
cbor.push(0x01); cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -399,7 +383,7 @@ mod test {
for (text_string, mut cbor) in cases { for (text_string, mut cbor) in cases {
assert_eq!(read(&cbor), Ok(cbor_text!(text_string))); assert_eq!(read(&cbor), Ok(cbor_text!(text_string)));
cbor.push(0x01); cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -421,7 +405,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor.clone()), Ok(cbor_array_vec!(value_vec))); assert_eq!(read(&test_cbor.clone()), Ok(cbor_array_vec!(value_vec)));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -445,7 +429,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -469,7 +453,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -490,7 +474,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -508,7 +492,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -525,7 +509,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -542,7 +526,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
} }
#[test] #[test]
@@ -564,36 +548,7 @@ mod test {
]; ];
assert_eq!(read(&test_cbor), Ok(value_map)); assert_eq!(read(&test_cbor), Ok(value_map));
test_cbor.push(0x01); test_cbor.push(0x01);
assert_eq!(read(&test_cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData));
}
#[test]
fn test_read_tagged() {
let cases = vec![
(cbor_tagged!(6, cbor_int!(0x42)), vec![0xc6, 0x18, 0x42]),
(cbor_tagged!(1, cbor_true!()), vec![0xc1, 0xf5]),
(
cbor_tagged!(
1000,
cbor_map! {
"a" => 1,
"b" => cbor_array![2, 3],
}
),
vec![
0xd9, 0x03, 0xe8, 0xa2, // map of 2 pairs
0x61, 0x61, // "a"
0x01, 0x61, 0x62, // "b"
0x82, // array with 2 elements
0x02, 0x03,
],
),
];
for (value, mut cbor) in cases {
assert_eq!(read(&cbor), Ok(value));
cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData));
}
} }
#[test] #[test]
@@ -619,7 +574,7 @@ mod test {
for (simple, mut cbor) in cases { for (simple, mut cbor) in cases {
assert_eq!(read(&cbor.clone()), Ok(simple)); assert_eq!(read(&cbor.clone()), Ok(simple));
cbor.push(0x01); cbor.push(0x01);
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -660,6 +615,19 @@ mod test {
} }
} }
#[test]
fn test_read_unsupported_map_key_format_error() {
// While CBOR can handle all types as map keys, we only support a subset.
let bad_map_cbor = vec![
0xa2, // map of 2 pairs
0x82, 0x01, 0x02, // invalid key : [1, 2]
0x02, // value : 2
0x61, 0x64, // key : "d"
0x03, // value : 3
];
assert_eq!(read(&bad_map_cbor), Err(DecoderError::IncorrectMapKeyType));
}
#[test] #[test]
fn test_read_unknown_additional_info_error() { fn test_read_unknown_additional_info_error() {
let cases = vec![ let cases = vec![
@@ -688,7 +656,7 @@ mod test {
]; ];
for cbor in cases { for cbor in cases {
let mut reader = Reader::new(&cbor); let mut reader = Reader::new(&cbor);
assert!(reader.decode_complete_data_item(Some(0)).is_ok()); assert!(reader.decode_complete_data_item(0).is_ok());
} }
let map_cbor = vec![ let map_cbor = vec![
0xa2, // map of 2 pairs 0xa2, // map of 2 pairs
@@ -699,11 +667,11 @@ mod test {
]; ];
let mut reader = Reader::new(&map_cbor); let mut reader = Reader::new(&map_cbor);
assert_eq!( assert_eq!(
reader.decode_complete_data_item(Some(1)), reader.decode_complete_data_item(1),
Err(DecoderError::TooMuchNesting) Err(DecoderError::TooMuchNesting)
); );
reader = Reader::new(&map_cbor); reader = Reader::new(&map_cbor);
assert!(reader.decode_complete_data_item(Some(2)).is_ok()); assert!(reader.decode_complete_data_item(2).is_ok());
} }
#[test] #[test]
@@ -788,7 +756,7 @@ mod test {
} }
#[test] #[test]
fn test_read_extraneous_cbor_data_error() { fn test_read_extranous_cbor_data_error() {
let cases = vec![ let cases = vec![
vec![0x19, 0x03, 0x05, 0x00], vec![0x19, 0x03, 0x05, 0x00],
vec![0x44, 0x01, 0x02, 0x03, 0x04, 0x00], vec![0x44, 0x01, 0x02, 0x03, 0x04, 0x00],
@@ -797,7 +765,7 @@ mod test {
vec![0xa1, 0x61, 0x63, 0x02, 0x61, 0x64, 0x03], vec![0xa1, 0x61, 0x63, 0x02, 0x61, 0x64, 0x03],
]; ];
for cbor in cases { for cbor in cases {
assert_eq!(read(&cbor), Err(DecoderError::ExtraneousData)); assert_eq!(read(&cbor), Err(DecoderError::ExtranousData));
} }
} }
@@ -829,4 +797,20 @@ mod test {
assert_eq!(read(&cbor), Err(DecoderError::IncompleteCborData)); assert_eq!(read(&cbor), Err(DecoderError::IncompleteCborData));
} }
} }
#[test]
fn test_read_unsupported_major_type() {
let cases = vec![
vec![0xC0],
vec![0xD8, 0xFF],
// multi-dimensional array example using tags
vec![
0x82, 0x82, 0x02, 0x03, 0xd8, 0x41, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00,
0x03, 0x00, 0x04, 0x00, 0x05,
],
];
for cbor in cases {
assert_eq!(read(&cbor), Err(DecoderError::UnsupportedMajorType));
}
}
} }

View File

@@ -12,242 +12,93 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Types for expressing CBOR values. use alloc::collections::BTreeMap;
use alloc::boxed::Box;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use alloc::vec::Vec; use alloc::vec::Vec;
use core::cmp::Ordering; use core::cmp::Ordering;
/// The CBOR data structure. #[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Value {
pub struct Value(pub(crate) ValueImpl); KeyValue(KeyType),
/// Possible CBOR values.
#[derive(Clone, Debug)]
pub(crate) enum ValueImpl {
/// Unsigned integer value (uint).
Unsigned(u64),
/// Signed integer value (nint). Only 63 bits of information are used here.
Negative(i64),
/// Byte string (bstr).
ByteString(Vec<u8>),
/// Text string (tstr).
TextString(String),
/// Array/tuple of values.
Array(Vec<Value>), Array(Vec<Value>),
/// Map of key-value pairs. Map(BTreeMap<KeyType, Value>),
Map(Vec<(Value, Value)>), // TAG is omitted
/// Tagged value.
Tag(u64, Box<Value>),
/// Simple value.
Simple(SimpleValue), Simple(SimpleValue),
} }
/// Specific simple CBOR values. // The specification recommends to limit the available keys.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] // Currently supported are both integer and string types.
pub(crate) enum SimpleValue { #[derive(Clone, Debug, Eq, PartialEq)]
pub enum KeyType {
Unsigned(u64),
// We only use 63 bits of information here.
Negative(i64),
ByteString(Vec<u8>),
TextString(String),
}
#[derive(Clone, Debug, PartialEq)]
pub enum SimpleValue {
FalseValue = 20, FalseValue = 20,
TrueValue = 21, TrueValue = 21,
NullValue = 22, NullValue = 22,
Undefined = 23, Undefined = 23,
} }
/// Constant values required for CBOR encoding.
pub struct Constants {} pub struct Constants {}
impl Constants { impl Constants {
/// Number of bits used to shift left the major type of a CBOR type byte.
pub const MAJOR_TYPE_BIT_SHIFT: u8 = 5; pub const MAJOR_TYPE_BIT_SHIFT: u8 = 5;
/// Mask to retrieve the additional information held in a CBOR type bytes,
/// ignoring the major type.
pub const ADDITIONAL_INFORMATION_MASK: u8 = 0x1F; pub const ADDITIONAL_INFORMATION_MASK: u8 = 0x1F;
/// Additional information value that indicates the largest inline value.
pub const ADDITIONAL_INFORMATION_MAX_INT: u8 = 23; pub const ADDITIONAL_INFORMATION_MAX_INT: u8 = 23;
/// Additional information value indicating that a 1-byte length follows.
pub const ADDITIONAL_INFORMATION_1_BYTE: u8 = 24; pub const ADDITIONAL_INFORMATION_1_BYTE: u8 = 24;
/// Additional information value indicating that a 2-byte length follows.
pub const ADDITIONAL_INFORMATION_2_BYTES: u8 = 25; pub const ADDITIONAL_INFORMATION_2_BYTES: u8 = 25;
/// Additional information value indicating that a 4-byte length follows.
pub const ADDITIONAL_INFORMATION_4_BYTES: u8 = 26; pub const ADDITIONAL_INFORMATION_4_BYTES: u8 = 26;
/// Additional information value indicating that an 8-byte length follows.
pub const ADDITIONAL_INFORMATION_8_BYTES: u8 = 27; pub const ADDITIONAL_INFORMATION_8_BYTES: u8 = 27;
} }
impl Value { impl Value {
/// Creates a CBOR unsigned value.
pub fn unsigned(int: u64) -> Value {
Value(ValueImpl::Unsigned(int))
}
/// Create an appropriate CBOR integer value (uint/nint).
/// For simplicity, this only takes i64. Construct directly for the last bit.
pub fn integer(int: i64) -> Value {
if int >= 0 {
Value(ValueImpl::Unsigned(int as u64))
} else {
Value(ValueImpl::Negative(int))
}
}
/// Creates a CBOR byte string value.
pub fn byte_string(bytes: Vec<u8>) -> Value {
Value(ValueImpl::ByteString(bytes))
}
/// Creates a CBOR text string value.
pub fn text_string(text: String) -> Value {
Value(ValueImpl::TextString(text))
}
/// Create a CBOR array value.
pub fn array(a: Vec<Value>) -> Value {
Value(ValueImpl::Array(a))
}
/// Create a CBOR map value.
///
/// Keys do not have to be sorted.
///
/// # Panics
///
/// You may not call this function with identical keys in its argument.
pub fn map(mut m: Vec<(Value, Value)>) -> Value {
m.sort_by(|a, b| a.0.cmp(&b.0));
let map_len = m.len();
m.dedup_by(|a, b| a.0.eq(&b.0));
if map_len != m.len() {
panic!();
}
Value(ValueImpl::Map(m))
}
/// Create a CBOR tagged value.
pub fn tag(int: u64, value: Value) -> Value {
Value(ValueImpl::Tag(int, Box::new(value)))
}
/// Create a CBOR boolean simple value.
pub fn bool_value(b: bool) -> Value { pub fn bool_value(b: bool) -> Value {
if b { if b {
Value(ValueImpl::Simple(SimpleValue::TrueValue)) Value::Simple(SimpleValue::TrueValue)
} else { } else {
Value(ValueImpl::Simple(SimpleValue::FalseValue)) Value::Simple(SimpleValue::FalseValue)
} }
} }
/// Creates a null value.
pub fn null_value() -> Value {
Value(ValueImpl::Simple(SimpleValue::NullValue))
}
/// Creates an undefined value.
pub fn undefined() -> Value {
Value(ValueImpl::Simple(SimpleValue::Undefined))
}
pub fn extract_unsigned(self) -> Option<u64> {
match self {
Value(ValueImpl::Unsigned(unsigned)) => Some(unsigned),
_ => None,
}
}
pub fn extract_integer(self) -> Option<i64> {
match self {
Value(ValueImpl::Unsigned(unsigned)) => {
if unsigned <= core::i64::MAX as u64 {
Some(unsigned as i64)
} else {
None
}
}
Value(ValueImpl::Negative(signed)) => Some(signed),
_ => None,
}
}
pub fn extract_byte_string(self) -> Option<Vec<u8>> {
match self {
Value(ValueImpl::ByteString(byte_string)) => Some(byte_string),
_ => None,
}
}
pub fn extract_text_string(self) -> Option<String> {
match self {
Value(ValueImpl::TextString(text_string)) => Some(text_string),
_ => None,
}
}
pub fn extract_array(self) -> Option<Vec<Value>> {
match self {
Value(ValueImpl::Array(array)) => Some(array),
_ => None,
}
}
pub fn extract_map(self) -> Option<Vec<(Value, Value)>> {
match self {
Value(ValueImpl::Map(map)) => Some(map),
_ => None,
}
}
pub fn extract_tag(self) -> Option<(u64, Value)> {
match self {
Value(ValueImpl::Tag(tag, value)) => Some((tag, *value)),
_ => None,
}
}
pub fn extract_bool(self) -> Option<bool> {
match self {
Value(ValueImpl::Simple(SimpleValue::FalseValue)) => Some(false),
Value(ValueImpl::Simple(SimpleValue::TrueValue)) => Some(true),
_ => None,
}
}
pub fn extract_null(self) -> Option<()> {
match self {
Value(ValueImpl::Simple(SimpleValue::NullValue)) => Some(()),
_ => None,
}
}
pub fn extract_undefined(self) -> Option<()> {
match self {
Value(ValueImpl::Simple(SimpleValue::Undefined)) => Some(()),
_ => None,
}
}
}
impl ValueImpl {
/// Return the major type for the [`ValueImpl`].
pub fn type_label(&self) -> u8 { pub fn type_label(&self) -> u8 {
// TODO use enum discriminant instead when stable
// https://github.com/rust-lang/rust/issues/60553
match self { match self {
ValueImpl::Unsigned(_) => 0, Value::KeyValue(key) => key.type_label(),
ValueImpl::Negative(_) => 1, Value::Array(_) => 4,
ValueImpl::ByteString(_) => 2, Value::Map(_) => 5,
ValueImpl::TextString(_) => 3, Value::Simple(_) => 7,
ValueImpl::Array(_) => 4,
ValueImpl::Map(_) => 5,
ValueImpl::Tag(_, _) => 6,
ValueImpl::Simple(_) => 7,
} }
} }
} }
impl Ord for ValueImpl { impl KeyType {
fn cmp(&self, other: &ValueImpl) -> Ordering { // For simplicity, this only takes i64. Construct directly for the last bit.
use super::values::ValueImpl::{ pub fn integer(int: i64) -> KeyType {
Array, ByteString, Map, Negative, Simple, Tag, TextString, Unsigned, if int >= 0 {
}; KeyType::Unsigned(int as u64)
} else {
KeyType::Negative(int)
}
}
pub fn type_label(&self) -> u8 {
match self {
KeyType::Unsigned(_) => 0,
KeyType::Negative(_) => 1,
KeyType::ByteString(_) => 2,
KeyType::TextString(_) => 3,
}
}
}
impl Ord for KeyType {
fn cmp(&self, other: &KeyType) -> Ordering {
use super::values::KeyType::{ByteString, Negative, TextString, Unsigned};
let self_type_value = self.type_label(); let self_type_value = self.type_label();
let other_type_value = other.type_label(); let other_type_value = other.type_label();
if self_type_value != other_type_value { if self_type_value != other_type_value {
@@ -258,56 +109,18 @@ impl Ord for ValueImpl {
(Negative(n1), Negative(n2)) => n1.cmp(n2).reverse(), (Negative(n1), Negative(n2)) => n1.cmp(n2).reverse(),
(ByteString(b1), ByteString(b2)) => b1.len().cmp(&b2.len()).then(b1.cmp(b2)), (ByteString(b1), ByteString(b2)) => b1.len().cmp(&b2.len()).then(b1.cmp(b2)),
(TextString(t1), TextString(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)), (TextString(t1), TextString(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)),
(Array(a1), Array(a2)) if a1.len() != a2.len() => a1.len().cmp(&a2.len()), _ => unreachable!(),
(Array(a1), Array(a2)) => {
// Arrays of same length.
let mut ordering = Ordering::Equal;
for (e1, e2) in a1.iter().zip(a2.iter()) {
ordering = e1.cmp(e2);
if !matches!(ordering, Ordering::Equal) {
break;
}
}
ordering
}
(Map(m1), Map(m2)) if m1.len() != m2.len() => m1.len().cmp(&m2.len()),
(Map(m1), Map(m2)) => {
// Maps of same length.
let mut ordering = Ordering::Equal;
for ((k1, v1), (k2, v2)) in m1.iter().zip(m2.iter()) {
ordering = k1.cmp(k2).then_with(|| v1.cmp(v2));
if !matches!(ordering, Ordering::Equal) {
break;
}
}
ordering
}
(Tag(t1, v1), Tag(t2, v2)) => t1.cmp(t2).then(v1.cmp(v2)),
(Simple(s1), Simple(s2)) => s1.cmp(s2),
(_, _) => {
// The case of different major types is caught above.
unreachable!();
}
} }
} }
} }
impl PartialOrd for ValueImpl { impl PartialOrd for KeyType {
fn partial_cmp(&self, other: &ValueImpl) -> Option<Ordering> { fn partial_cmp(&self, other: &KeyType) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Eq for ValueImpl {}
impl PartialEq for ValueImpl {
fn eq(&self, other: &ValueImpl) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl SimpleValue { impl SimpleValue {
/// Create a simple value from its encoded value.
pub fn from_integer(int: u64) -> Option<SimpleValue> { pub fn from_integer(int: u64) -> Option<SimpleValue> {
match int { match int {
20 => Some(SimpleValue::FalseValue), 20 => Some(SimpleValue::FalseValue),
@@ -319,93 +132,54 @@ impl SimpleValue {
} }
} }
impl From<u64> for Value { impl From<u64> for KeyType {
fn from(u: u64) -> Self { fn from(unsigned: u64) -> Self {
Value::unsigned(u) KeyType::Unsigned(unsigned)
} }
} }
impl From<u32> for Value { impl From<i64> for KeyType {
fn from(u: u32) -> Self {
Value::unsigned(u as u64)
}
}
impl From<u16> for Value {
fn from(u: u16) -> Self {
Value::unsigned(u as u64)
}
}
impl From<u8> for Value {
fn from(u: u8) -> Self {
Value::unsigned(u as u64)
}
}
impl From<i64> for Value {
fn from(i: i64) -> Self { fn from(i: i64) -> Self {
Value::integer(i) KeyType::integer(i)
} }
} }
impl From<i32> for Value { impl From<i32> for KeyType {
fn from(i: i32) -> Self { fn from(i: i32) -> Self {
Value::integer(i as i64) KeyType::integer(i as i64)
} }
} }
impl From<i16> for Value { impl From<Vec<u8>> for KeyType {
fn from(i: i16) -> Self {
Value::integer(i as i64)
}
}
impl From<i8> for Value {
fn from(i: i8) -> Self {
Value::integer(i as i64)
}
}
impl From<Vec<u8>> for Value {
fn from(bytes: Vec<u8>) -> Self { fn from(bytes: Vec<u8>) -> Self {
Value(ValueImpl::ByteString(bytes)) KeyType::ByteString(bytes)
} }
} }
impl From<&[u8]> for Value { impl From<&[u8]> for KeyType {
fn from(bytes: &[u8]) -> Self { fn from(bytes: &[u8]) -> Self {
Value(ValueImpl::ByteString(bytes.to_vec())) KeyType::ByteString(bytes.to_vec())
} }
} }
impl From<&[u8; 0]> for Value { impl From<String> for KeyType {
fn from(bytes: &[u8; 0]) -> Self {
Value(ValueImpl::ByteString(bytes.to_vec()))
}
}
impl From<String> for Value {
fn from(text: String) -> Self { fn from(text: String) -> Self {
Value(ValueImpl::TextString(text)) KeyType::TextString(text)
} }
} }
impl From<&str> for Value { impl From<&str> for KeyType {
fn from(text: &str) -> Self { fn from(text: &str) -> Self {
Value(ValueImpl::TextString(text.to_string())) KeyType::TextString(text.to_string())
} }
} }
impl From<Vec<Value>> for Value { impl<T> From<T> for Value
fn from(array: Vec<Value>) -> Self { where
Value(ValueImpl::Array(array)) KeyType: From<T>,
} {
} fn from(t: T) -> Self {
Value::KeyValue(KeyType::from(t))
impl From<Vec<(Value, Value)>> for Value {
fn from(map: Vec<(Value, Value)>) -> Self {
Value::map(map)
} }
} }
@@ -415,9 +189,20 @@ impl From<bool> for Value {
} }
} }
/// Trait that indicates that a type can be converted to a CBOR [`Value`]. pub trait IntoCborKey {
fn into_cbor_key(self) -> KeyType;
}
impl<T> IntoCborKey for T
where
KeyType: From<T>,
{
fn into_cbor_key(self) -> KeyType {
KeyType::from(self)
}
}
pub trait IntoCborValue { pub trait IntoCborValue {
/// Convert `self` into a CBOR [`Value`], consuming it along the way.
fn into_cbor_value(self) -> Value; fn into_cbor_value(self) -> Value;
} }
@@ -430,9 +215,7 @@ where
} }
} }
/// Trait that indicates that a type can be converted to a CBOR [`Option<Value>`].
pub trait IntoCborValueOption { pub trait IntoCborValueOption {
/// Convert `self` into a CBOR [`Option<Value>`], consuming it along the way.
fn into_cbor_value_option(self) -> Option<Value>; fn into_cbor_value_option(self) -> Option<Value>;
} }
@@ -456,283 +239,32 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use crate::{cbor_key_bytes, cbor_key_int, cbor_key_text};
use crate::{
cbor_array, cbor_bool, cbor_bytes, cbor_bytes_lit, cbor_int, cbor_map, cbor_null,
cbor_tagged, cbor_text, cbor_undefined, cbor_unsigned,
};
use alloc::vec;
#[test] #[test]
#[should_panic] fn test_key_type_ordering() {
fn test_duplicate_map_key() { assert!(cbor_key_int!(0) < cbor_key_int!(23));
let _map = cbor_map! { assert!(cbor_key_int!(23) < cbor_key_int!(24));
0 => "a", assert!(cbor_key_int!(24) < cbor_key_int!(1000));
-1 => "c", assert!(cbor_key_int!(1000) < cbor_key_int!(1000000));
b"a" => "e", assert!(cbor_key_int!(1000000) < cbor_key_int!(std::i64::MAX));
"c" => "g", assert!(cbor_key_int!(std::i64::MAX) < cbor_key_int!(-1));
0 => "b", assert!(cbor_key_int!(-1) < cbor_key_int!(-23));
}; assert!(cbor_key_int!(-23) < cbor_key_int!(-24));
} assert!(cbor_key_int!(-24) < cbor_key_int!(-1000));
assert!(cbor_key_int!(-1000) < cbor_key_int!(-1000000));
#[test] assert!(cbor_key_int!(-1000000) < cbor_key_int!(std::i64::MIN));
fn test_extract_unsigned() { assert!(cbor_key_int!(std::i64::MIN) < cbor_key_bytes!(vec![]));
assert_eq!(cbor_int!(1).extract_unsigned(), Some(1)); assert!(cbor_key_bytes!(vec![]) < cbor_key_bytes!(vec![0x00]));
assert_eq!(cbor_int!(-1).extract_unsigned(), None); assert!(cbor_key_bytes!(vec![0x00]) < cbor_key_bytes!(vec![0x01]));
assert_eq!(cbor_bytes!(vec![]).extract_unsigned(), None); assert!(cbor_key_bytes!(vec![0x01]) < cbor_key_bytes!(vec![0xFF]));
assert_eq!(cbor_text!("").extract_unsigned(), None); assert!(cbor_key_bytes!(vec![0xFF]) < cbor_key_bytes!(vec![0x00, 0x00]));
assert_eq!(cbor_array![].extract_unsigned(), None); assert!(cbor_key_bytes!(vec![0x00, 0x00]) < cbor_key_text!(""));
assert_eq!(cbor_map! {}.extract_unsigned(), None); assert!(cbor_key_text!("") < cbor_key_text!("a"));
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_unsigned(), None); assert!(cbor_key_text!("a") < cbor_key_text!("b"));
assert_eq!(cbor_bool!(false).extract_unsigned(), None); assert!(cbor_key_text!("b") < cbor_key_text!("aa"));
} assert!(cbor_key_int!(1) < cbor_key_bytes!(vec![0x00]));
assert!(cbor_key_int!(1) < cbor_key_text!("s"));
#[test] assert!(cbor_key_int!(-1) < cbor_key_text!("s"));
fn test_extract_unsigned_limits() {
assert_eq!(
cbor_unsigned!(core::u64::MAX).extract_unsigned(),
Some(core::u64::MAX)
);
assert_eq!(
cbor_unsigned!((core::i64::MAX as u64) + 1).extract_unsigned(),
Some((core::i64::MAX as u64) + 1)
);
assert_eq!(
cbor_int!(core::i64::MAX).extract_unsigned(),
Some(core::i64::MAX as u64)
);
assert_eq!(cbor_int!(123).extract_unsigned(), Some(123));
assert_eq!(cbor_int!(0).extract_unsigned(), Some(0));
assert_eq!(cbor_int!(-123).extract_unsigned(), None);
assert_eq!(cbor_int!(core::i64::MIN).extract_unsigned(), None);
}
#[test]
fn test_extract_integer() {
assert_eq!(cbor_int!(1).extract_integer(), Some(1));
assert_eq!(cbor_int!(-1).extract_integer(), Some(-1));
assert_eq!(cbor_bytes!(vec![]).extract_integer(), None);
assert_eq!(cbor_text!("").extract_integer(), None);
assert_eq!(cbor_array![].extract_integer(), None);
assert_eq!(cbor_map! {}.extract_integer(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_integer(), None);
assert_eq!(cbor_bool!(false).extract_integer(), None);
}
#[test]
fn test_extract_integer_limits() {
assert_eq!(cbor_unsigned!(core::u64::MAX).extract_integer(), None);
assert_eq!(
cbor_unsigned!((core::i64::MAX as u64) + 1).extract_integer(),
None
);
assert_eq!(
cbor_int!(core::i64::MAX).extract_integer(),
Some(core::i64::MAX)
);
assert_eq!(cbor_int!(123).extract_integer(), Some(123));
assert_eq!(cbor_int!(0).extract_integer(), Some(0));
assert_eq!(cbor_int!(-123).extract_integer(), Some(-123));
assert_eq!(
cbor_int!(core::i64::MIN).extract_integer(),
Some(core::i64::MIN)
);
}
#[test]
fn test_extract_byte_string() {
assert_eq!(cbor_int!(1).extract_byte_string(), None);
assert_eq!(cbor_int!(-1).extract_byte_string(), None);
assert_eq!(cbor_bytes!(vec![]).extract_byte_string(), Some(Vec::new()));
assert_eq!(
cbor_bytes_lit!(b"bar").extract_byte_string(),
Some(b"bar".to_vec())
);
assert_eq!(cbor_text!("").extract_byte_string(), None);
assert_eq!(cbor_array![].extract_byte_string(), None);
assert_eq!(cbor_map! {}.extract_byte_string(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_byte_string(), None);
assert_eq!(cbor_bool!(false).extract_byte_string(), None);
}
#[test]
fn test_extract_text_string() {
assert_eq!(cbor_int!(1).extract_text_string(), None);
assert_eq!(cbor_int!(-1).extract_text_string(), None);
assert_eq!(cbor_bytes!(vec![]).extract_text_string(), None);
assert_eq!(cbor_text!("").extract_text_string(), Some(String::new()));
assert_eq!(cbor_text!("s").extract_text_string(), Some("s".to_string()));
assert_eq!(cbor_array![].extract_text_string(), None);
assert_eq!(cbor_map! {}.extract_text_string(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_text_string(), None);
assert_eq!(cbor_bool!(false).extract_text_string(), None);
}
#[test]
fn test_extract_array() {
assert_eq!(cbor_int!(1).extract_array(), None);
assert_eq!(cbor_int!(-1).extract_array(), None);
assert_eq!(cbor_bytes!(vec![]).extract_array(), None);
assert_eq!(cbor_text!("").extract_array(), None);
assert_eq!(cbor_array![].extract_array(), Some(Vec::new()));
assert_eq!(
cbor_array![cbor_int!(1)].extract_array(),
Some(vec![cbor_int!(1)])
);
assert_eq!(cbor_map! {}.extract_array(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_array(), None);
assert_eq!(cbor_bool!(false).extract_array(), None);
}
#[test]
fn test_extract_map() {
assert_eq!(cbor_int!(1).extract_map(), None);
assert_eq!(cbor_int!(-1).extract_map(), None);
assert_eq!(cbor_bytes!(vec![]).extract_map(), None);
assert_eq!(cbor_text!("").extract_map(), None);
assert_eq!(cbor_array![].extract_map(), None);
assert_eq!(cbor_map! {}.extract_map(), Some(Vec::new()));
assert_eq!(
cbor_map! {0 => 1}.extract_map(),
Some(vec![(cbor_int!(0), cbor_int!(1))])
);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_map(), None);
assert_eq!(cbor_bool!(false).extract_map(), None);
}
#[test]
fn test_extract_tag() {
assert_eq!(cbor_int!(1).extract_tag(), None);
assert_eq!(cbor_int!(-1).extract_tag(), None);
assert_eq!(cbor_bytes!(vec![]).extract_tag(), None);
assert_eq!(cbor_text!("").extract_tag(), None);
assert_eq!(cbor_array![].extract_tag(), None);
assert_eq!(cbor_map! {}.extract_tag(), None);
assert_eq!(
cbor_tagged!(1, cbor_text!("s")).extract_tag(),
Some((1, cbor_text!("s")))
);
assert_eq!(cbor_bool!(false).extract_tag(), None);
}
#[test]
fn test_extract_bool() {
assert_eq!(cbor_int!(1).extract_bool(), None);
assert_eq!(cbor_int!(-1).extract_bool(), None);
assert_eq!(cbor_bytes!(vec![]).extract_bool(), None);
assert_eq!(cbor_text!("").extract_bool(), None);
assert_eq!(cbor_array![].extract_bool(), None);
assert_eq!(cbor_map! {}.extract_bool(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_bool(), None);
assert_eq!(cbor_bool!(false).extract_bool(), Some(false));
assert_eq!(cbor_bool!(true).extract_bool(), Some(true));
assert_eq!(cbor_null!().extract_bool(), None);
assert_eq!(cbor_undefined!().extract_bool(), None);
}
#[test]
fn test_extract_null() {
assert_eq!(cbor_int!(1).extract_null(), None);
assert_eq!(cbor_int!(-1).extract_null(), None);
assert_eq!(cbor_bytes!(vec![]).extract_null(), None);
assert_eq!(cbor_text!("").extract_null(), None);
assert_eq!(cbor_array![].extract_null(), None);
assert_eq!(cbor_map! {}.extract_null(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_null(), None);
assert_eq!(cbor_bool!(false).extract_null(), None);
assert_eq!(cbor_bool!(true).extract_null(), None);
assert_eq!(cbor_null!().extract_null(), Some(()));
assert_eq!(cbor_undefined!().extract_null(), None);
}
#[test]
fn test_extract_undefined() {
assert_eq!(cbor_int!(1).extract_undefined(), None);
assert_eq!(cbor_int!(-1).extract_undefined(), None);
assert_eq!(cbor_bytes!(vec![]).extract_undefined(), None);
assert_eq!(cbor_text!("").extract_undefined(), None);
assert_eq!(cbor_array![].extract_undefined(), None);
assert_eq!(cbor_map! {}.extract_undefined(), None);
assert_eq!(cbor_tagged!(1, cbor_text!("s")).extract_undefined(), None);
assert_eq!(cbor_bool!(false).extract_undefined(), None);
assert_eq!(cbor_bool!(true).extract_undefined(), None);
assert_eq!(cbor_null!().extract_undefined(), None);
assert_eq!(cbor_undefined!().extract_undefined(), Some(()));
}
#[test]
fn test_value_ordering() {
assert!(cbor_int!(0) < cbor_int!(23));
assert!(cbor_int!(23) < cbor_int!(24));
assert!(cbor_int!(24) < cbor_int!(1000));
assert!(cbor_int!(1000) < cbor_int!(1000000));
assert!(cbor_int!(1000000) < cbor_int!(core::i64::MAX));
assert!(cbor_int!(core::i64::MAX) < cbor_int!(-1));
assert!(cbor_int!(-1) < cbor_int!(-23));
assert!(cbor_int!(-23) < cbor_int!(-24));
assert!(cbor_int!(-24) < cbor_int!(-1000));
assert!(cbor_int!(-1000) < cbor_int!(-1000000));
assert!(cbor_int!(-1000000) < cbor_int!(core::i64::MIN));
assert!(cbor_int!(core::i64::MIN) < cbor_bytes!(vec![]));
assert!(cbor_bytes!(vec![]) < cbor_bytes!(vec![0x00]));
assert!(cbor_bytes!(vec![0x00]) < cbor_bytes!(vec![0x01]));
assert!(cbor_bytes!(vec![0x01]) < cbor_bytes!(vec![0xFF]));
assert!(cbor_bytes!(vec![0xFF]) < cbor_bytes!(vec![0x00, 0x00]));
assert!(cbor_bytes!(vec![0x00, 0x00]) < cbor_text!(""));
assert!(cbor_text!("") < cbor_text!("a"));
assert!(cbor_text!("a") < cbor_text!("b"));
assert!(cbor_text!("b") < cbor_text!("aa"));
assert!(cbor_text!("aa") < cbor_array![]);
assert!(cbor_array![] < cbor_array![0]);
assert!(cbor_array![0] < cbor_array![-1]);
assert!(cbor_array![1] < cbor_array![b""]);
assert!(cbor_array![b""] < cbor_array![""]);
assert!(cbor_array![""] < cbor_array![cbor_array![]]);
assert!(cbor_array![cbor_array![]] < cbor_array![cbor_map! {}]);
assert!(cbor_array![cbor_map! {}] < cbor_array![false]);
assert!(cbor_array![false] < cbor_array![0, 0]);
assert!(cbor_array![0, 0] < cbor_map! {});
assert!(cbor_map! {} < cbor_map! {0 => 0});
assert!(cbor_map! {0 => 0} < cbor_map! {0 => 1});
assert!(cbor_map! {0 => 1} < cbor_map! {1 => 0});
assert!(cbor_map! {1 => 0} < cbor_map! {-1 => 0});
assert!(cbor_map! {-1 => 0} < cbor_map! {b"" => 0});
assert!(cbor_map! {b"" => 0} < cbor_map! {"" => 0});
assert!(cbor_map! {"" => 0} < cbor_map! {cbor_array![] => 0});
assert!(cbor_map! {cbor_array![] => 0} < cbor_map! {cbor_map!{} => 0});
assert!(cbor_map! {cbor_map!{} => 0} < cbor_map! {false => 0});
assert!(cbor_map! {false => 0} < cbor_map! {0 => 0, 1 => 0});
assert!(cbor_map! {0 => 0} < cbor_tagged!(2, cbor_int!(0)));
assert!(cbor_map! {0 => 0, 1 => 0} < cbor_bool!(false));
assert!(cbor_bool!(false) < cbor_bool!(true));
assert!(cbor_bool!(true) < cbor_null!());
assert!(cbor_null!() < cbor_undefined!());
assert!(cbor_tagged!(1, cbor_text!("s")) < cbor_tagged!(2, cbor_int!(0)));
assert!(cbor_int!(1) < cbor_int!(-1));
assert!(cbor_int!(1) < cbor_bytes!(vec![0x00]));
assert!(cbor_int!(1) < cbor_text!("s"));
assert!(cbor_int!(1) < cbor_array![]);
assert!(cbor_int!(1) < cbor_map! {});
assert!(cbor_int!(1) < cbor_tagged!(1, cbor_text!("s")));
assert!(cbor_int!(1) < cbor_bool!(false));
assert!(cbor_int!(-1) < cbor_bytes!(vec![0x00]));
assert!(cbor_int!(-1) < cbor_text!("s"));
assert!(cbor_int!(-1) < cbor_array![]);
assert!(cbor_int!(-1) < cbor_map! {});
assert!(cbor_int!(-1) < cbor_tagged!(1, cbor_text!("s")));
assert!(cbor_int!(-1) < cbor_bool!(false));
assert!(cbor_bytes!(vec![0x00]) < cbor_text!("s"));
assert!(cbor_bytes!(vec![0x00]) < cbor_array![]);
assert!(cbor_bytes!(vec![0x00]) < cbor_map! {});
assert!(cbor_bytes!(vec![0x00]) < cbor_tagged!(1, cbor_text!("s")));
assert!(cbor_bytes!(vec![0x00]) < cbor_bool!(false));
assert!(cbor_text!("s") < cbor_array![]);
assert!(cbor_text!("s") < cbor_map! {});
assert!(cbor_text!("s") < cbor_tagged!(1, cbor_text!("s")));
assert!(cbor_text!("s") < cbor_bool!(false));
assert!(cbor_array![] < cbor_map!(0 => 1));
assert!(cbor_array![] < cbor_tagged!(2, cbor_int!(0)));
assert!(cbor_array![] < cbor_bool!(false));
assert!(cbor_tagged!(1, cbor_text!("s")) < cbor_bool!(false));
} }
} }

Some files were not shown because too many files have changed in this diff Show More