Merge pull request #53 from ferrous-systems/edit-book

didactic changes
This commit is contained in:
Jorge Aparicio 2020-07-15 13:06:32 +00:00 committed by GitHub
commit 606df17bd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 64 deletions

View file

@ -5,7 +5,7 @@
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Error, Packet};
use dk::ieee802154::{Channel, Error, Packet};
use panic_log as _; // the panicking behavior
const TEN_MS: u32 = 10_000;
@ -17,6 +17,8 @@ fn main() -> ! {
let mut radio = board.radio;
let mut timer = board.timer;
radio.set_channel(Channel::_20);
let mut packet = Packet::new();
let msg = b"olleh";
packet.copy_from_slice(msg);

View file

@ -20,6 +20,7 @@
- [Link Quality Indicator (LQI)](./link-quality.md)
- [Radio In ](./radio-in.md)
- [Radio Puzzle](./radio-puzzle.md)
- [Radio Puzzle Help](./radio-puzzle-help.md)
- [Starting a Project from Scratch](./from-scratch.md)
- [Next Steps](./beginner-next-steps.md)
- [Advanced Workbook](./advanced-workbook.md)

View file

@ -4,7 +4,7 @@ In this section we'll explore the `recv_timeout` method of the Radio API. As the
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.
✅ Open the `src/bin/radio-recv.rs` file. Make sure that the Dongle and the Radio are set to the same channel. Click the "Run" button.
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.

View file

@ -0,0 +1,67 @@
# Help
## Use a dictionary.
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. It supplies a stack-allocated, fixed-capacity version of the `std::Vec` type which will come in handy to store byte arrays. To store character mappings we recommend using a `heapless::IndexMap`.
`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
``` rust
use heapless::Vec; // like `std::Vec` but stack-allocated
use heapless::FnvIndexMap; // a dictionary / map
use heapless::consts::*; // defines U16, U32, U64... etc. to set the size of the IndexMap
fn main() {
// A hash map with a capacity of 16 key-value pairs allocated on the stack
let mut my_map = FnvIndexMap::<_, _, U16>::new();
my_map.insert(b'A', b'~').unwrap();
// A vector with a fixed capacity of 8 elements allocated on the stack
let mut my_vec = Vec::<_, U8>::new();
my_vec.push(b'A').unwrap();
}
```
If you haven't used 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 [`heapless::IndexMap` documentation][indexMap] of the `heapless` crate has some usage examples, as does the [`heapless::Vec` documentation][vec].
[indexMap]: https://docs.rs/heapless/0.5.5/heapless/struct.IndexMap.html
[vec]: https://docs.rs/heapless/0.5.5/heapless/struct.Vec.html
## Note the difference between character literals and byte literals!
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.
## Recommended Steps:
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

View file

@ -1,16 +1,20 @@
# Radio Puzzle
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`.
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
✅ 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`.
> Note: If you experienced USB issues with `loopback.hex` you use the `puzzle-nousb*.hex` variants.
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.
Open the `beginner/apps` folder in VS Code; then open the `src/bin/radio-puzzle.rs` file. Run the program.
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
## Dongle Responses
The Dongle will respond differently depending on the length of the incoming packet:
@ -20,61 +24,4 @@ The Dongle will respond differently depending on the length of the incoming pack
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 stack-allocated maps in the [`heapless`] crate. It supplies a stack-allocated, fixed-capacity version of the `std::Vec` type which will come in handy to store byte arrays. To store character mappings we recommend using a `heapless::IndexMap`.
`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
``` rust
use heapless::Vec; // like `std::Vec` but stack-allocated
use heapless::FnvIndexMap; // a dictionary / map
use heapless::consts::*; // defines U16, U32, U64... etc. to set the size of the IndexMap
fn main() {
// A hash map with a capacity of 16 key-value pairs allocated on the stack
let mut my_map = FnvIndexMap::<_, _, U16>::new();
my_map.insert(b'A', b'~').unwrap();
// A vector with a fixed capacity of 8 elements allocated on the stack
let mut my_vec = Vec::<_, U8>::new();
my_vec.push(b'A').unwrap();
}
```
If you haven't used 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 [`heapless::IndexMap` documentation][indexMap] of the `heapless` crate has some usage examples, as does the [`heapless::Vec` documentation][vec].
[indexMap]: https://docs.rs/heapless/0.5.5/heapless/struct.IndexMap.html
[vec]: https://docs.rs/heapless/0.5.5/heapless/struct.Vec.html
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
See the next chapter for solving stragies and help.