Merge pull request #9 from ferrous-systems/puzzle-ch25

puzzle hints, recv_timeout, etc.
This commit is contained in:
Jorge Aparicio 2020-06-25 11:40:21 +00:00 committed by GitHub
commit ea2fc91029
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 2743 additions and 1888 deletions

View file

@ -321,7 +321,89 @@ deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
received 5 bytes (LQI=49)
```
The program broadcasts a radio packet that contains the 5-byte string `Hello` over channel 20 (which has a center frequency of 2450 MHz). The `loopback` program running on the Dongle is listening to all packets sent over channel 20; every time it receives a new packet it reports its length and the Link Quality Indicator (LQI) metric of the transmission over the USB/serial interface. As the name implies the LQI metric indicates how good the connection between the sender and the device is.
The program broadcasts a radio packet that contains the 5-byte string `Hello` over channel 20 (which has a center frequency of 2450 MHz). The `loopback` program running on the Dongle is listening to all packets sent over channel 20; every time it receives a new packet it reports its length and the Link Quality Indicator (LQI) metric of the transmission over the USB/serial interface. As the name implies the LQI metric indicates how good the connection between the sender and the receiver is.
### Slices
The `send` method takes a *reference* -- in Rust, a reference (`&`) is a non-null pointer that's compile-time known to point into valid (e.g. non-freed) memory -- to a `Packet` as argument. A `Packet` is a stack-allocated, fixed-size buffer. You can fill the `Packet` (buffer) with data using the `copy_from_slice` method -- this will overwrite previously stored data.
This `copy_from_slice` method takes a *slice of bytes* (`&[u8]`). A slice is a reference into a list of elements stored in contiguous memory. One way to create a slice is to take a reference to an *array*, a fixed-size list of elements stored in contiguous memory.
``` rust
// stack allocated array
let array: [u8; 3] = [0, 1, 2];
let ref_to_array: &[u8; 3] = &array;
let slice: &[u8] = &array;
```
`slice` and `ref_to_array` are constructed in the same way but have different types. `ref_to_array` is represented in memory as a single pointer (1 word / 4 bytes); `slice` is represented as a pointer + length (2 words / 8 bytes).
Because slices track length at runtime rather than in their type they can point to chunks of memory of any length.
``` rust
let array1: [u8; 3] = [0, 1, 2];
let array2: [u8; 4] = [0, 1, 2, 3];
let mut slice: &[u8] = &array1;
log::info!("{:?}", slice); // length = 3
// now point to the other array
slice = &array2;
log::info!("{:?}", slice); // length = 4
```
### Byte literals
In the example we sent the list of bytes: `[72, 101, 108, 108, 111]`, which can be interpreted as the string `"Hello"`. To see why this is the case check this [list of printable ASCII characters][ascii]. You'll see that letter `H` is represented by the (single-byte) value `72`, `e` by `101`, etc.
[ascii]: https://en.wikipedia.org/wiki/ASCII#Printable_characters
Rust provides a more convenient way to write ASCII characters: byte literals. `b'H'` is syntactic sugar for the literal `72u8`, `b'e'` is equivalent to `101u8`, etc.. So we can rewrite `[72, 101, 108, 108, 111]` as `[b'H', b'e', b'l', b'l', b'o']`. Note that byte literals can also represent `u8` values that are not printable ASCII characters: those values are written using escaped sequences like `b'\x7F'`, which is equivalent to `0x7F`.
### Byte string literals
`[b'H', b'e', b'l', b'l', b'o']` can be further rewritten as `b"Hello"`. This is called a *byte* string literal (note that unlike a string literal like `"Hello"` this one has a `b` before the opening double quote). A byte string literal is a series of byte literals (`u8` values); these literals have type `&[u8; N]` where `N` is the number of byte literals in the string.
Because byte string literals are references you need to *dereference* them to get an array type.
``` rust
let reftoarray: &[u8; 2] = b"Hi";
// these two are equivalent
let array1: [u8; 2] = [b'H', 'i'];
let array2: [u8; 2] = *b"Hi";
// ^ ^ dereference
```
Or if you want to go the other way around: you need to take a reference to an array to get the same type as a byte string literal.
``` rust
// these two are equivalent
let reftoarray1: &[u8; 2] = b"Hi";
let reftoarray2: &[u8; 2] = &[b'H', 'i'];
// ^ ^
```
### Character constrains in byte string vs. string literals
You can encode text as `b"Hello"` or as `"Hello"`.
`b"Hello"` is by definition a string (series) of byte literals so each character has to be a byte literal like `b'A'` or `b'\x7f'`. You cannot use "Unicode characters" (`char` type) like emoji or CJK (Chinese Japanese Korean) in byte string literals.
On the other hand, `"Hello"` is a string literal with type `&str`. `str` strings in Rust contain UTF-8 data so these string literals can contain CJK characters, emoji, Greek letters, Cyrillic script, etc.
### Printing strings and characters
In this workshop we'll work with ASCII strings so byte string literals that contain no escaped characters are OK to use as packet payloads.
You'll note that `log::info!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
To print this you'll need to convert the slice `&[u8]` into a string (`&str`) using the `str::from_utf8` function. This function will verify that the slice contains well formed UTF-8 data and interpret it as a UTF-8 string (`&str`). As long as we use ASCII data (printable ASCII characters) this conversion will not fail.
Something similar will happen with byte literals: `log::info!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `log::info!("{}", b'A' as char)`.
### Link Quality Indicator (LQI)
Now run the `radio-send` program several times with different variations:
@ -331,61 +413,98 @@ Now run the `radio-send` program several times with different variations:
- change the length of the packet
- different combinations of all of the above
> NOTE if you decide to send many packets in a single program then you should use the `Timer` API to insert a delay of at least five milliseconds between the transmissions. This is required because the Dongle will use the radio medium right after it receives a packet. Not including the delay will result in the Dongle missing packets
Take note of how LQI changes with these changes. Do packet loss occur in any of these configurations?
The radio interface we are using follows the IEEE 802.15.4 specification but it's missing MAC level features like addressing (each device gets its own address), opt-in acknowledgment (a transmitted packet must be acknowledged with a response acknowledgment packet; the packet is re-transmitted if the packet is not acknowledged in time). These MAC level features are not implemented *in hardware* (in the nRF52840 Radio peripheral) so they would need to be implemented in software to be fully IEEE 802.15.4 compliant.
> NOTE if you decide to send many packets in a single program then you should use the `Timer` API to insert a delay of at least five milliseconds between the transmissions. This is required because the Dongle will use the radio medium right after it receives a packet. Not including the delay will result in the Dongle missing packets
802.15.4 radios are often used in mesh networks like Wireless Sensors Networks (WSN). The devices, or *nodes*, in these networks can be mobile so the distance between nodes can change in time. To prevent a link between two nodes getting broken due to mobility the LQI metric is used to decide the transmission power -- if the metric degrades power should be increased, etc. At the same time, the nodes in these networks often need to be power efficient (e.g. are battery powered) so the transmission power is often set as low as possible -- again the LQI metric is used to pick an adequate transmission power.
### 802.15.4 compatibility
The radio API we are using follows the PHY layer of the IEEE 802.15.4 specification but it's missing MAC level features like addressing (each device gets its own address), opt-in acknowledgment (a transmitted packet must be acknowledged with a response acknowledgment packet; the packet is re-transmitted if the packet is not acknowledged in time). These MAC level features are not implemented *in hardware* (in the nRF52840 Radio peripheral) so they would need to be implemented in software to be fully IEEE 802.15.4 compliant.
This is not an issue for the workshop exercises but it's something to consider if you would like to continue from here and build a 802.15.4 compliant network API.
## Radio in
In this section we'll explore the `recv` method of the Radio API. As the name implies, this is used to listen for packets. The method will block the program execution until a packet is received. We'll continue to use the Dongle in this section; it should be running the `loopback` application; and the `serial-term` application should also be running in the background.
In this section we'll explore the `recv_timeout` method of the Radio API. As the name implies, this is used to listen for packets. The method will block the program execution until a packet is received or the specified timeout has expired. We'll continue to use the Dongle in this section; it should be running the `loopback` application; and the `serial-term` application should also be running in the background.
The `loopback` application running on the Dongle will broadcast a radio packet after receiving one over channel 20. The contents of this outgoing packet will be the contents of the received one but reversed.
Open the `src/bin/radio-recv.rs` file and click the "Run" button.
The Dongle will response as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response.
The Dongle expects the packet to contain only ASCII characters and will not respond to packets that contain non-ASCII data. If you only send packets that contain byte string literals *with no escaped characters* (e.g. `b"hello"`) then this requirement will be satisfied. At the same time the Dongle will always respond with ASCII data so calling `str::from_utf8` on the response should never fail, unless the packet contents got corrupted in the transmission but the CRC should detect this scenario.
In a fully IEEE 802.15.4 compliant implementation one can mark a packet as "requires acknowledgment". The recipient must respond to these packets with an acknowledgment packet; if the sender doesn't receive the acknowledgment packet it will re-transmit the packet. This feature is part of the MAC layer and not implemented in the `Radio` API we are using so packet loss is possible even when the radios are close enough to communicate.
The Dongle will respond as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response. So try this: add a `timer.delay(x)` call before the `recv_timeout` call; try different values of `x` and observe what happens.
Having log statements between `send` and `recv_timeout` can also cause packets to be missed so try to keep those two calls as close to each other as possible and with as little code in between as possible.
> NOTE Packet loss can always occur in wireless networks, even if the radios are close to each other. The `Radio` API we are using will not detect lost packets because it does not implement IEEE 802.15.4 Acknowledgement Requests. If you are having trouble with lost packets, consider adding a retry loop.
## Radio puzzle
> TODO(japaric) before this section maybe cover collision avoidance
For this section you'll need to flash the `puzzle.hex` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle.hex` program instead of the `loopback.hex` one -- don't forget to put the Dongle in bootloader mode before invoking `dongle-flash`.
Like in the previous sections the Dongle will listen for radio packets over channel 20 while also logging messages over a USB/serial interface.
Like in the previous sections the Dongle will listen for radio packets -- this time over *channel 25* -- while also logging messages over a USB/serial interface.
Open the `beginner/apps` folder in VS Code; then open the `src/bin/radio-puzzle.rs` file.
Your task in this section is to decrypt the [substitution cipher] encrypted *ascii* string stored in the Dongle. The string has been encrypted using *simple substitution*.
Your task in this section is to decrypt the [substitution cipher] encrypted *ASCII* string stored in the Dongle. The string has been encrypted using *simple substitution*.
[substitution cipher]: https://en.wikipedia.org/wiki/Substitution_cipher
The Dongle will respond differently depending on the length of the incoming packet:
- On zero-sized packets it will respond with the encrypted string.
- On one-byte sized packets it will respond with the *direct* mapping from a *plaintext* letter -- the letter contained in the packet -- to the *ciphertext* letter.
- On one-byte sized packets it will respond with the *direct* mapping from a *plaintext* letter (single `u8` value) -- the letter contained in the packet -- to the *ciphertext* letter (`u8` value).
- On packets of any other length the Dongle will respond with the string `correct` if it received the decrypted string, otherwise it will respond with the `incorrect` string.
The Dongle will always respond with packets that are valid UTF-8.
The Dongle will always respond with packets that are valid UTF-8 so you can use `str::from_utf8` on the response packets.
Our suggestion is to use a dictionary / map. `std::collections::HashMap` is not available in `no_std` code (without linking to a global allocator) but you can use one of the maps in the [`heapless`] crate. To make this crate available in your application get the latest version from [crates.io] and add it to the `beginner/apps/Cargo.toml` file, for example:
Our suggestion is to use a dictionary / map. `std::collections::HashMap` is not available in `no_std` code (without linking to a global allocator) but you can use one of the stack-allocated maps in the [`heapless`] crate. A `Vec`-like buffer may also come in handy; `heapless` provides a stack-allocated, fixed-capacity version of the `std::Vec` type.
`heapless` is already declared as a dependency in the Cargo.toml of the project so you can directly import it into the application code using a `use` statement.
[`heapless`]: https://docs.rs/heapless
[crates.io]: https://crates.io/crates/heapless
``` toml
# Cargo.toml
[dependencies]
heapless = "0.5.0"
``` rust
use heapless::IndexMap; // a dictionary / map
use heapless::Vec; // like `std::Vec` but stack-allocated
```
If you haven't use a stack-allocated collection before note that you'll need to specify the capacity of the collection as a type parameter using one of the "type-level values" in the `heapless::consts` module. The crate level documentation of the `heapless` crate has some examples.
If you haven't use a stack-allocated collection before note that you'll need to specify the capacity of the collection as a type parameter using one of the "type-level values" in the `heapless::consts` module. The [crate level documentation][`heapless`] of the `heapless` crate has some examples.
Something you will likely run into while solving this exercise are *character* literals (`'c'`) and *byte* literals (`b'c'`). The former has type [`char`] and represent a single Unicode "scalar value". The latter has type `u8` (1-byte integer) and it's mainly a convenience for getting the value of ASCII characters, for instance `b'A'` is the same as the `65u8` literal.
[`char`]: https://doc.rust-lang.org/std/primitive.char.html
*IMPORTANT* you do not need to use the `str` or `char` API to solve this problem, other than for printing purposes. Work directly with slices of bytes (`[u8]`) and bytes (`u8`); and only convert those to `str` or `char` when you are about to print them.
P.S. The plaintext string is *not* stored in `puzzle.hex` so running `strings` on it will not give you the answer.
These are our recommended steps to tackle the problem. Each step is demonstrated in a separate example so if for example you only need a quick reference of how to use the map API you can step / example number 2.
1. Send a one letter packet (e.g. `A`) to the radio to get a feel for how the mapping works. Then do a few more letters. Check out example `radio-puzzle-1`
2. Get familiar with the dictionary API. Do some insertions and look ups. What happens if the dictionary gets full? See `radio-puzzle-2`
3. Next, get mappings from the radio and insert them into the dictionary. See `radio-puzzle-3`
4. You'll probably want a buffer to place the plaintext in. We suggest using `heapless::Vec` for this. See `radio-puzzle-4` (NB It is also possible to decrypt the packet in place)
5. Simulate decryption: fetch the encrypted string and "process" each of its bytes. See `radio-puzzle-5`
6. Now merge steps 3 and 5: build a dictionary, retrieve the secret string and do the reverse mapping to decrypt the message. See `radio-puzzle-6`
7. As a final step, send the decrypted string to the Dongle and check if it was correct or not. See `radio-puzzle-7`
For your reference, we have provided a complete solution in the `src/bin/radio-puzzle-solution.rs` file. That solution is based on the seven steps outlined above. Did you solve the puzzle in a different way?
If you solved the puzzle using a `Vec` buffer you can try solving it without the buffer as a stretch goal. You may find the [slice methods][slice] that let you mutate its data useful. A solution that does not use the `Vec` buffer can be found in the `radio-puzzle-solution-2` file.
[slice]: https://doc.rust-lang.org/std/primitive.slice.html#methods
## Starting a project from scratch
So far we have been using a pre-made Cargo project to work with the nRF52840 DK. In this section we'll see how to create a new embedded project for any microcontroller.

View file

@ -7,10 +7,22 @@ dependencies = [
"cortex-m",
"cortex-m-rt",
"dk",
"heapless",
"log",
"panic-log",
]
[[package]]
name = "as-slice"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70"
dependencies = [
"generic-array 0.12.3",
"generic-array 0.13.2",
"stable_deref_trait",
]
[[package]]
name = "bare-metal"
version = "0.2.5"
@ -26,6 +38,12 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cast"
version = "0.2.3"
@ -104,6 +122,45 @@ dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd"
dependencies = [
"typenum",
]
[[package]]
name = "hash32"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73a8a2391a3bc70b31f60e7a90daa5755a360559c0b6b9c5cfc0fee482362dc0"
dependencies = [
"as-slice",
"generic-array 0.13.2",
"hash32",
"stable_deref_trait",
]
[[package]]
name = "log"
version = "0.4.8"
@ -122,7 +179,7 @@ checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
[[package]]
name = "nrf-hal-common"
version = "0.10.0"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#d624e80e5724e4709081ed65abaf63271fe1eca7"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#7076639891fd2493e1c61a7aca71944b48ae0458"
dependencies = [
"cast",
"cortex-m",
@ -137,7 +194,7 @@ dependencies = [
[[package]]
name = "nrf52840-hal"
version = "0.10.0"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#d624e80e5724e4709081ed65abaf63271fe1eca7"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#7076639891fd2493e1c61a7aca71944b48ae0458"
dependencies = [
"cast",
"cortex-m",
@ -233,6 +290,12 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
[[package]]
name = "syn"
version = "1.0.30"

View file

@ -8,6 +8,7 @@ version = "0.1.0"
cortex-m = "0.6.2"
cortex-m-rt = "0.6.12"
dk = { path = "../../boards/dk", features = ["beginner"] }
heapless = "0.5.5"
log = "0.4.8"
panic-log = { path = "../../common/panic-log" }

View file

@ -0,0 +1,60 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
let mut packet = Packet::new();
// first exchange a single packet with the Dongle
// letter 'A' (uppercase)
let source = 65;
// let source = b'A'; // this is the same as above
// TODO try other letters
// single letter (byte) packet
packet.copy_from_slice(&[source]);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let destination = packet[0];
log::info!("{} -> {}", source, destination);
// or cast to `char` for a more readable output
log::info!("{:?} -> {:?}", source as char, destination as char);
} else {
log::error!("response packet was not a single byte");
dk::exit()
}
} else {
log::error!("no response or response packet was corrupted");
dk::exit()
}
// TODO next do the whole ASCII range [0, 127]
// start small: just 'A' and 'B' at first
// NOTE: `a..=b` means inclusive range; `a` and `b` are included in the range
// `a..b` means open-ended range; `a` is included in the range but `b` isn't
for _source in b'A'..=b'B' {
// TODO similar procedure as above
}
dk::exit()
}

View file

@ -0,0 +1,37 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use cortex_m_rt::entry;
// NOTE you can use `FnvIndexMap` instead of `LinearMap`; the former may have better
// lookup performance when the dictionary contains a large number of items but performance is
// not important for this exercise
use heapless::{consts, LinearMap};
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
dk::init().unwrap();
// a dictionary with capacity for 2 elements
let mut dict = LinearMap::<_, _, consts::U2>::new();
// ^^ capacity; this is a type not a value
// do some insertions
dict.insert(b'A', b'*').expect("dictionary full");
dict.insert(b'B', b'/').expect("dictionary full");
// do some lookups
let key = b'A';
let value = dict[&key]; // the key needs to be passed by reference
log::info!("{} -> {}", key, value);
// more readable
log::info!("{:?} -> {:?}", key as char, value as char);
// TODO try another insertion
// TODO try looking up a key not contained in the dictionary
// TODO try changing the capacity
dk::exit()
}

View file

@ -0,0 +1,51 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
// capacity (128) should be large enough for the ASCII range
let dict = LinearMap::<u8, u8, consts::U128>::new();
let mut packet = Packet::new();
// TODO do the whole ASCII range [0, 127]
for source in b'A'..=b'B' {
packet.copy_from_slice(&[source]);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let _destination = packet[0];
// TODO insert the key-value pair
// dict.insert(/* ? */, /* ? */).expect("dictionary full");
} else {
log::error!("response packet was not a single byte");
dk::exit()
}
} else {
log::error!("no response or response packet was corrupted");
dk::exit()
}
}
log::info!("{:?}", dict);
dk::exit()
}

View file

@ -0,0 +1,33 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use heapless::{consts, Vec};
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
dk::init().unwrap();
// a buffer with capacity for 2 bytes
let mut buffer = Vec::<u8, consts::U2>::new();
// ^^ capacity; this is a type not a value
// do some insertions
buffer.push(b'H').expect("buffer full");
buffer.push(b'i').expect("buffer full");
// look into the contents so far
log::info!("{:?}", buffer);
// or more readable
// NOTE as long as you only push bytes in the ASCII range (0..=127) the conversion should work
log::info!(
"{}",
str::from_utf8(&buffer).expect("content was not UTF-8")
);
dk::exit()
}

View file

@ -0,0 +1,56 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, Vec};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
let mut packet = Packet::new();
/* # Retrieve the secret string */
packet.copy_from_slice(&[]); // empty packet
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"ciphertext: {}",
str::from_utf8(&packet).expect("packet was not valid UTF-8")
);
/* # Decrypt the string */
let mut buf = Vec::<u8, consts::U128>::new();
// iterate over the bytes
for input in packet.iter() {
// process each byte
// here we should do the reverse mapping; instead we'll do a shift for illustrative purposes
let output = input + 1;
buf.push(output).expect("buffer full");
}
log::info!(
"plaintext: {}",
str::from_utf8(&buf).expect("buffer contains non-UTF-8 data")
);
dk::exit()
}

View file

@ -0,0 +1,80 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap, Vec};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let dict = LinearMap::<u8, u8, consts::U128>::new();
let mut packet = Packet::new();
for source in 0..=127 {
packet.copy_from_slice(&[source]);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let _destination = packet[0];
// TODO insert the key-value pair
// dict.insert(/* ? */, /* ? */).expect("dictionary full");
} else {
log::error!("response packet was not a single byte");
dk::exit()
}
} else {
log::error!("no response or response packet was corrupted");
dk::exit()
}
}
/* # Retrieve the secret string */
packet.copy_from_slice(&[]); // empty packet
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"ciphertext: {}",
str::from_utf8(&packet).expect("packet was not valid UTF-8")
);
/* # Decrypt the string */
let mut buffer = Vec::<u8, consts::U128>::new();
// iterate over the bytes
for byte in packet.iter() {
// NOTE this should map from the encrypted letter to the plaintext letter
let key = byte;
let value = dict[key];
buffer.push(value).expect("buffer full");
}
log::info!(
"plaintext: {}",
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
);
dk::exit()
}

View file

@ -0,0 +1,95 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap, Vec};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let dict = LinearMap::<u8, u8, consts::U128>::new();
let mut packet = Packet::new();
for source in 0..=127 {
packet.copy_from_slice(&[source]);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let _destination = packet[0];
// TODO insert the key-value pair
// dict.insert(/* ? */, /* ? */).expect("dictionary full");
} else {
log::error!("response packet was not a single byte");
dk::exit()
}
} else {
log::error!("no response or response packet was corrupted");
dk::exit()
}
}
/* # Retrieve the secret string */
packet.copy_from_slice(&[]); // empty packet
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"ciphertext: {}",
str::from_utf8(&packet).expect("packet was not valid UTF-8")
);
/* # Decrypt the string */
let mut buffer = Vec::<u8, consts::U128>::new();
// iterate over the bytes
for byte in packet.iter() {
// NOTE this should map from the encrypted letter to the plaintext letter
let key = byte;
let value = dict[key];
buffer.push(value).expect("buffer full");
}
log::info!(
"plaintext: {}",
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
);
/* # (NEW) Verify decrypted text */
packet.copy_from_slice(&buffer);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"Dongle response: {}",
str::from_utf8(&packet).expect("response was not UTF-8")
);
dk::exit()
}

View file

@ -0,0 +1,95 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let mut dict = LinearMap::<u8, u8, consts::U128>::new();
let mut packet = Packet::new();
for plainletter in 0..=127 {
packet.copy_from_slice(&[plainletter]);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let cipherletter = packet[0];
dict.insert(cipherletter, plainletter)
.expect("dictionary full");
} else {
log::error!("response packet was not a single byte");
dk::exit()
}
} else {
log::error!("no response or response packet was corrupted");
dk::exit()
}
}
/* # Retrieve the secret string */
packet.copy_from_slice(&[]); // empty packet
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"ciphertext: {}",
str::from_utf8(&packet).expect("packet was not valid UTF-8")
);
/* # Decrypt the string *in place* */
// NOTE *mutably* iterate over the bytes
for spot in packet.iter_mut() {
// `spot` has type `&mut u8` and lets you modify the contents of the packet
let cipherletter = *spot; // make a copy of the byte
let key = cipherletter;
let value = dict[&key];
let plainletter = value;
// overwrite the old value with the plainletter
*spot = plainletter;
}
log::info!(
"plaintext: {}",
str::from_utf8(&packet).expect("buffer contains non-UTF-8 data")
);
/* # Verify decrypted text */
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"Dongle response: {}",
str::from_utf8(&packet).expect("response was not UTF-8")
);
dk::exit()
}

View file

@ -0,0 +1,99 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap, Vec};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let mut dict = LinearMap::<u8, u8, consts::U128>::new();
// ^^^^^^^^^^^^ NOTE larger capacity
let mut packet = Packet::new();
for plainletter in 0..=127 {
// ^^^^^^^ NOTE complete ASCII range
packet.copy_from_slice(&[plainletter]);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
// response should be one byte large
if packet.len() == 1 {
let cipherletter = packet[0];
// NOTE we want to map in reverse: from cipherletter to plainletter
dict.insert(cipherletter, plainletter)
.expect("dictionary full");
} else {
log::error!("response packet was not a single byte");
dk::exit()
}
} else {
log::error!("no response or response packet was corrupted");
dk::exit()
}
}
/* # Retrieve the secret string */
packet.copy_from_slice(&[]); // empty packet
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"ciphertext: {}",
str::from_utf8(&packet).expect("packet was not valid UTF-8")
);
/* # Decrypt the string */
let mut buffer = Vec::<u8, consts::U128>::new();
// iterate over the bytes
for cipherletter in packet.iter() {
let key = cipherletter;
let value = dict[key];
let plainletter = value;
buffer.push(plainletter).expect("buffer full");
}
log::info!(
"plaintext: {}",
str::from_utf8(&buffer).expect("buffer contains non-UTF-8 data")
);
/* # Verify decrypted text */
packet.copy_from_slice(&buffer);
radio.send(&packet);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_err() {
log::error!("no response or response packet was corrupted");
dk::exit()
}
log::info!(
"Dongle response: {}",
str::from_utf8(&packet).expect("response was not UTF-8")
);
dk::exit()
}

View file

@ -5,29 +5,46 @@
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::Packet;
use dk::ieee802154::{Channel, Packet};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
let mut packet = Packet::new();
// try these
let msg = "";
// let msg = "A";
// let msg = "Hello?";
// try one of these 3 options
let msg = b"";
// these 3 lines are equivalent
// let msg: &[u8; 1] = b"A";
// let msg: &[u8; 1] = &[b'A'];
// let msg: &[u8; 1] = &[65];
// let msg = b"Hello?";
packet.copy_from_slice(msg);
log::info!(
"sending: {}",
str::from_utf8(msg).expect("msg was not valid UTF-8 data")
);
packet.copy_from_slice(msg.as_bytes());
radio.send(&packet);
log::info!("sent: {:?}", msg);
// listen for a response packet and ensure it is not corrupted
if radio.recv(&mut packet).is_ok() {
// convert the packet contents to str or print error message on failure
let response = str::from_utf8(&*packet).expect("could not convert response to str");
log::info!("received: {}", response);
if radio.recv_timeout(&mut packet, &mut timer, TEN_MS).is_ok() {
log::info!(
"received: {}",
str::from_utf8(&packet).expect("response was not valid UTF-8 data")
);
} else {
log::error!("no response or response packet was corrupted");
}
dk::exit()
}

View file

@ -4,27 +4,45 @@
use core::str;
use cortex_m::asm;
use cortex_m_rt::entry;
use dk::ieee802154::Packet;
use dk::ieee802154::{Error, Packet};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
#[entry]
fn main() -> ! {
// initializes the peripherals
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut timer = board.timer;
let mut packet = Packet::new();
let msg = b"olleh";
packet.copy_from_slice(msg);
radio.send(&packet);
log::info!("sent: {:?}", msg);
let crc = radio.recv(&mut packet);
let s = str::from_utf8(&*packet).expect("response is not valid UTF-8");
log::info!("received: {} (CRC={:?})", s, crc);
loop {
asm::bkpt();
log::info!(
"sending: {}",
str::from_utf8(msg).expect("message is not valid UTF-8")
);
radio.send(&packet);
// TODO try uncommenting this line
// timer.delay(1_000);
let res = radio.recv_timeout(&mut packet, &mut timer, TEN_MS);
match res {
Ok(crc) => {
log::info!(
"received: {} (CRC={})",
str::from_utf8(&*packet).expect("response is not valid UTF-8"),
crc
);
}
Err(Error::Crc(crc)) => log::error!("invalid CRC: {:06x}", crc),
Err(Error::Timeout) => log::error!("no response within {} ms", TEN_MS / 1_000),
}
dk::exit()
}

View file

@ -2,6 +2,8 @@
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet, TxPower};
use panic_log as _; // the panicking behavior
@ -16,9 +18,20 @@ fn main() -> ! {
radio.set_txpower(TxPower::Pos8dBm);
let mut packet = Packet::new();
packet.copy_from_slice(b"Hello");
let res = radio.try_send(&packet);
log::info!("{:?}", res);
// these three are equivalent
let msg: &[u8; 5] = &[72, 101, 108, 108, 111];
// let msg: &[u8; 5] = &[b'H', b'e', b'l', b'l', b'o'];
// let msg: &[u8; 5] = b"Hello";
log::info!(
"sending: {}",
str::from_utf8(msg).expect("msg is not valid UTF-8 data")
);
packet.copy_from_slice(msg);
radio.send(&packet);
dk::exit();
}

View file

@ -5,7 +5,8 @@
#![no_std]
use core::{
sync::atomic::{AtomicU32, Ordering},
ops,
sync::atomic::{self, AtomicU32, Ordering},
time::Duration,
};
@ -118,7 +119,7 @@ impl Led {
}
}
/// A timer for blocking delay
/// A timer for creating blocking delays
pub struct Timer {
inner: hal::Timer<hal::target::TIMER0, OneShot>,
}
@ -157,7 +158,19 @@ impl Timer {
}
}
// add Instant API
impl ops::Deref for Timer {
type Target = hal::Timer<hal::target::TIMER0, OneShot>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ops::DerefMut for Timer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
/// Initializes the board
///
@ -291,6 +304,9 @@ fn RTC0() {
/// Exits the application and prints a backtrace when the program is executed through the `dk-run`
/// Cargo runner
pub fn exit() -> ! {
log::info!("`dk::exit() called; exiting ...`");
// force any pending memory operation to complete before the BKPT instruction that follows
atomic::compiler_fence(Ordering::SeqCst);
loop {
asm::bkpt()
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff