mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2025-02-09 22:22:19 +00:00
WIP: add puzzle hints
This commit is contained in:
parent
c918a995d7
commit
515186020c
10 changed files with 496 additions and 0 deletions
|
@ -386,6 +386,22 @@ If you haven't use a stack-allocated collection before note that you'll need to
|
|||
|
||||
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:
|
||||
|
||||
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`
|
||||
|
||||
## 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.
|
||||
|
|
63
beginner/apps/Cargo.lock
generated
63
beginner/apps/Cargo.lock
generated
|
@ -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"
|
||||
|
@ -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"
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
57
beginner/apps/src/bin/radio-puzzle-1.rs
Normal file
57
beginner/apps/src/bin/radio-puzzle-1.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
#![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();
|
||||
|
||||
// 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
|
||||
// OR for source in b'A'..=b'B' (NOTE: inclusive range)
|
||||
for source in 65..67 {
|
||||
// TODO similar procedure as above
|
||||
}
|
||||
|
||||
dk::exit()
|
||||
}
|
37
beginner/apps/src/bin/radio-puzzle-2.rs
Normal file
37
beginner/apps/src/bin/radio-puzzle-2.rs
Normal 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()
|
||||
}
|
51
beginner/apps/src/bin/radio-puzzle-3.rs
Normal file
51
beginner/apps/src/bin/radio-puzzle-3.rs
Normal 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);
|
||||
|
||||
// TODO increase capacity
|
||||
let mut dict = LinearMap::<u8, u8, consts::U2>::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()
|
||||
}
|
36
beginner/apps/src/bin/radio-puzzle-4.rs
Normal file
36
beginner/apps/src/bin/radio-puzzle-4.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
#![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")
|
||||
);
|
||||
|
||||
// TODO try another insertion
|
||||
// TODO try changing the capacity
|
||||
|
||||
dk::exit()
|
||||
}
|
56
beginner/apps/src/bin/radio-puzzle-5.rs
Normal file
56
beginner/apps/src/bin/radio-puzzle-5.rs
Normal 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()
|
||||
}
|
82
beginner/apps/src/bin/radio-puzzle-6.rs
Normal file
82
beginner/apps/src/bin/radio-puzzle-6.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
#![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;
|
||||
|
||||
/* # Build a dictionary */
|
||||
// TODO increase capacity
|
||||
let mut dict = LinearMap::<u8, u8, consts::U2>::new();
|
||||
|
||||
// puzzle.hex uses channel 25
|
||||
radio.set_channel(Channel::_25);
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/* # 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()
|
||||
}
|
97
beginner/apps/src/bin/radio-puzzle-7.rs
Normal file
97
beginner/apps/src/bin/radio-puzzle-7.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
#![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;
|
||||
|
||||
/* # Build a dictionary */
|
||||
// TODO increase capacity
|
||||
let mut dict = LinearMap::<u8, u8, consts::U2>::new();
|
||||
|
||||
// puzzle.hex uses channel 25
|
||||
radio.set_channel(Channel::_25);
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/* # 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")
|
||||
);
|
||||
|
||||
/* # 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()
|
||||
}
|
Loading…
Reference in a new issue