WIP: add puzzle hints

This commit is contained in:
Jorge Aparicio 2020-06-22 15:23:59 +02:00
parent c918a995d7
commit 515186020c
10 changed files with 496 additions and 0 deletions

View file

@ -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.

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"
@ -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,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()
}

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);
// 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()
}

View 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()
}

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,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()
}

View 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()
}