#![allow(missing_docs)]
use wolfcose::{
from_slice, to_vec, Algorithm, CborDeserialize, CborSerialize, CoseKeyBuilder, CoseMac0Message,
Encrypt0Builder, Mac0Builder, PayloadMode,
};
const DRONE_ID: &str = "drone-17";
#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct TelemetryFrame {
drone_id: String,
sequence: u64,
unix_ms: u64,
position: Position,
battery_percent: u8,
flight_mode: FlightMode,
}
#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct Position {
lat_e7: i32,
lon_e7: i32,
alt_cm: i32,
ground_speed_cm_s: u32,
}
#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
enum FlightMode {
Standby,
Mission,
ReturnHome,
}
#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct ControlFrame {
drone_id: String,
sequence: u64,
command: ControlCommand,
}
#[derive(Debug, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
enum ControlCommand {
Arm,
Disarm,
SetWaypoint {
lat_e7: i32,
lon_e7: i32,
alt_cm: i32,
},
ReturnHome,
}
fn main() -> wolfcose::Result<()> {
let telemetry_key = CoseKeyBuilder::symmetric([0x71u8; 16])
.algorithm(Algorithm::A128GCM)
.kid(b"drone-17-telemetry")
.build()?;
let control_key = CoseKeyBuilder::symmetric([0x91u8; 32])
.algorithm(Algorithm::HMAC256)
.kid(b"drone-17-control")
.build()?;
let telemetry = TelemetryFrame {
drone_id: DRONE_ID.to_owned(),
sequence: 42,
unix_ms: 1_725_000_000_123,
position: Position {
lat_e7: 377_749_000,
lon_e7: -1_224_194_000,
alt_cm: 12_340,
ground_speed_cm_s: 850,
},
battery_percent: 88,
flight_mode: FlightMode::Mission,
};
let telemetry_plaintext = to_vec(&telemetry)?;
let telemetry_aad = aad("telemetry", DRONE_ID, telemetry.sequence);
let telemetry_iv = iv_from_sequence(*b"TELM", telemetry.sequence);
let encrypted_telemetry = Encrypt0Builder::new()
.key(&telemetry_key)
.algorithm(Algorithm::A128GCM)
.iv(&telemetry_iv)
.external_aad(&telemetry_aad)
.payload(PayloadMode::Attached(&telemetry_plaintext))
.encrypt_to_vec()?;
let mut decrypted = vec![0; telemetry_plaintext.len() + 32];
let mut decryptor = Encrypt0Builder::new()
.key(&telemetry_key)
.external_aad(&telemetry_aad);
let output = decryptor.decrypt_into(&encrypted_telemetry, None, &mut decrypted)?;
let decoded_telemetry: TelemetryFrame = from_slice(&decrypted[..output.plaintext_len])?;
assert_eq!(decoded_telemetry, telemetry);
let command = ControlFrame {
drone_id: DRONE_ID.to_owned(),
sequence: 43,
command: ControlCommand::SetWaypoint {
lat_e7: 377_750_000,
lon_e7: -1_224_190_000,
alt_cm: 15_000,
},
};
let command_payload = to_vec(&command)?;
let command_aad = aad("control", DRONE_ID, command.sequence);
let signed_command = Mac0Builder::new()
.key(&control_key)
.algorithm(Algorithm::HMAC256)
.kid(b"drone-17-control")
.external_aad(&command_aad)
.payload(PayloadMode::Attached(&command_payload))
.mac_to_vec()?;
let parsed = CoseMac0Message::parse(&signed_command)?;
assert!(parsed.payload_attached());
assert_eq!(parsed.unprotected().kid(), Some(&b"drone-17-control"[..]));
let mut verifier = Mac0Builder::new()
.key(&control_key)
.external_aad(&command_aad);
let verified = verifier.verify(&signed_command, None)?;
let verified_payload = verified.payload.expect("attached control payload");
let decoded_command: ControlFrame = from_slice(verified_payload)?;
assert_eq!(decoded_command, command);
println!(
"encrypted_telemetry={} signed_command={} command_kid={:?}",
encrypted_telemetry.len(),
signed_command.len(),
parsed.unprotected().kid().map(String::from_utf8_lossy)
);
Ok(())
}
fn aad(channel: &str, drone_id: &str, sequence: u64) -> Vec<u8> {
format!("drone-protocol/v1:{channel}:{drone_id}:{sequence}").into_bytes()
}
fn iv_from_sequence(prefix: [u8; 4], sequence: u64) -> [u8; 12] {
let mut iv = [0u8; 12];
iv[..4].copy_from_slice(&prefix);
iv[4..].copy_from_slice(&sequence.to_be_bytes());
iv
}