Skip to content

Commit

Permalink
Merge pull request #179 from geky/mcu-client
Browse files Browse the repository at this point in the history
Add MCU client
  • Loading branch information
dreemkiller authored Aug 12, 2021
2 parents 5067c53 + 61a11df commit c0ca927
Show file tree
Hide file tree
Showing 45 changed files with 4,281 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@
path = docker
url = https://github.com/veracruz-project/veracruz-docker-image.git
branch = main
[submodule "veracruz-mcu-client/nanopb"]
path = veracruz-mcu-client/nanopb
url = https://github.com/nanopb/nanopb
16 changes: 16 additions & 0 deletions sdk/rust-examples/audio-event-triangulation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "audio-event-triangulation"
version = "0.3.0"
authors = ["The Veracruz Development Team"]
edition = "2018"
description = "Triangulation via audio events"

[dependencies]
anyhow = "1.0.14"
thiserror = "1.0.26"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1

26 changes: 26 additions & 0 deletions sdk/rust-examples/audio-event-triangulation/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Example Makefile
#
# AUTHORS
#
# The Veracruz Development Team.
#
# COPYRIGHT AND LICENSING
#
# See the `LICENSING.markdown` file in the Veracruz root directory for
# licensing and copyright information.

.PHONY: all doc clean fmt

all:
cargo build --target wasm32-wasi --release

doc:
cargo doc

fmt:
cargo fmt

clean:
cargo clean
rm -f Cargo.lock

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
#
# Poll for audio event triangulation results
#
# AUTHORS
#
# The Veracruz Development Team.
#
# COPYRIGHT AND LICENSING
#
# See the `LICENSING.markdown` file in the Veracruz root directory for
# licensing and copyright information.

import argparse
import json
import re
import struct
import subprocess as sp
import sys
import time

# Convert pair of signed 32-bit latitude+longitude coordinates into
# a human readable representation
def dump_gps(location):
absy = abs(location[0])
absx = abs(location[1])
return '%d°%02d\'%02d.%02d"%c %d°%02d\'%02d.%02d"%c' % (
absy / (1024*1024),
(absy / (1024*1024/60)) % 60,
(absy / (1024*1024/60/60)) % 60,
(absy / (1024*1024/60/60/100)) % 100,
'N' if location[0] >= 0 else 'S',
absx / (1024*1024),
(absx / (1024*1024/60)) % 60,
(absx / (1024*1024/60/60)) % 60,
(absx / (1024*1024/60/60/100)) % 100,
'E' if location[1] >= 0 else 'W')

def main(args):
# grab server addresses from policy file
with open(args.policy) as f:
policy_json = json.load(f)

print('\033[0;32mstarting audio event triangulation service\033[m')
print('veracruz_server: %s' % policy_json['veracruz_server_url'])
print('proxy_attestation_server: %s' % policy_json['proxy_attestation_server_url'])
print('waiting for triangulation results...')

# poll until Veracruz returns a successful computation
while True:
try:
output = sp.check_output([
'vc-client',
args.policy,
'--identity', args.identity,
'--key', args.key,
'--program', 'audio-event-triangulation.wasm=' + args.program,
'--output', 'audio-event-triangulation.wasm=-'])
except sp.CalledProcessError:
time.sleep(5)
continue

# strip debug info
output = re.sub(rb'post.*?\n', b'', output)

# decode found coordinates
location = struct.unpack('<ii', output)
print()
print('\033[1;33maudio event detected!\033[0m')
print('\033[1;33mlocation:\033[0m %s' % dump_gps(location))
print('\033[1;33mtimestamp:\033[0m %u' % int(time.time()))
print()

break

if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='Poll for audio event triangulation results')
parser.add_argument('policy',
help='Veracruz policy file (.json)')
parser.add_argument('--identity', required=True,
help='Identity of client (.pem)')
parser.add_argument('--key', required=True,
help='Private key of client (.pem)')
parser.add_argument('--program', required=True,
help='Path to audio event triangulation binary (.wasm)')
args = parser.parse_args()
main(args)
177 changes: 177 additions & 0 deletions sdk/rust-examples/audio-event-triangulation/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//! An implementation of triangulation of audio event data
//!
//! This demo takes in audio events in the form of a timestamp,
//! GPS coordinates, and an analog rich audio sample, calculates the
//! best effort triangulated location, returning the estimated location
//!
//! ## Authors
//!
//! The Veracruz Development Team.
//!
//! ## Copyright
//!
//! See the file `LICENSING.markdown` in the Veracruz root directory for licensing
//! and copyright information.
use anyhow;
use std::{
convert::TryInto,
fs,
io,
};
use thiserror::Error;

/// An individual audio event
struct AudioEvent {
/// Timestamp of when even occured (currently unused)
#[allow(dead_code)]
timestamp: u32,
/// Location in 32-bit signed GPS coordinates
location: (i32, i32),
/// Samples in signed 16-bit PCM, bitrate is assumed to be
/// consistent for all samples
samples: Vec<i16>,
}

#[derive(Error, Debug)]
pub enum AudioEventError {
#[error("Unable to decode audio event")]
InvalidAudioEvent,
}

impl AudioEvent {
/// Compute the power of the audio event, larger+closer audio
/// events result in higher power
fn power(&self) -> f64 {
// compute as avg per single sample, assumes common bitrate
let mut p: f64 = 0.0;
for x in self.samples.iter() {
p += x.abs() as f64;
}

p.checked_div(self.samples.len() as f64).unwrap_or(0)
}
}

/// Decode an audio event from raw little-endian bytes
///
/// This uses a simple fixed encoding scheme:
/// [ u32 timestamp ]
/// [ i32 device X GPS coord ]
/// [ i32 device Y GPS coord ]
/// [ i16 PCM audio window ]
/// [ i16 ]
/// [ i16 ]
/// [ ... ]
///
fn decode_audio_event(event: &[u8]) -> anyhow::Result<AudioEvent> {
if event.len() < 12 || event.len() % 2 != 0 {
Err(AudioEventError::InvalidAudioEvent)?;
}

Ok(AudioEvent{
timestamp: u32::from_le_bytes(event[0..4].try_into().unwrap()),
location: (
i32::from_le_bytes(event[4.. 8].try_into().unwrap()),
i32::from_le_bytes(event[8..12].try_into().unwrap())
),
samples: (12..event.len()).step_by(2)
.map(|i| i16::from_le_bytes(event[i..i+2].try_into().unwrap()))
.collect::<Vec<_>>()
})
}

/// Find the best effort triangulation of audio events
/// using measured signal power to estimate distance
///
/// Note! This is a fairly naive solution, using only 3 events
/// leaves us with farily low confidence. We also don't take
/// things like the curvature of the earth into account, so
/// this should only be used for demo purposes
///
fn triangulate(events: &[AudioEvent]) -> (i32, i32) {
// solving
// (x−x1)^2 + (y−y1)^2 = d1^2
// (x−x2)^2 + (y−y2)^2 = d2^2
// (x−x3)^2 + (y−y3)^2 = d3^2
//
// let
// a = (-2x1 + 2x2)
// b = (-2y1 + 2y2)
// c = d1^2-d2^2 - x1^2+x2^2 - y1^2+y2^2
// d = (-2x2 + 2x3)
// e = (-2y2 + 2y3)
// f = d2^2-d3^2 - x2^2+x3^2 - y2^2+y3^2
//
// gives us
// x = (ce - fb) / (ea - bd)
// y = (cd - af) / (bd - ae)
//
let (y1, x1) = events[0].location;
let (y2, x2) = events[1].location;
let (y3, x3) = events[2].location;
let (y1, x1) = (y1 as f64, x1 as f64);
let (y2, x2) = (y2 as f64, x2 as f64);
let (y3, x3) = (y3 as f64, x3 as f64);
let d1 = events[0].power();
let d2 = events[1].power();
let d3 = events[2].power();

let a = -2.0*x1 + 2.0*x2;
let b = -2.0*y1 + 2.0*y2;
let c = d1.powf(2.0)-d2.powf(2.0) - x1.powf(2.0)+x2.powf(2.0) - y1.powf(2.0)+y2.powf(2.0);
let d = -2.0*x2 + 2.0*x3;
let e = -2.0*y2 + 2.0*y3;
let f = d2.powf(2.0)-d3.powf(2.0) - x2.powf(2.0)+x3.powf(2.0) - y2.powf(2.0)+y3.powf(2.0);

let x = (c*e - f*b) / (e*a - b*d);
let y = (c*d - a*f) / (b*d - a*e);

(y as i32, x as i32)
}

/// Encode a pair of latitude/longitude GPS coordinates into
/// little-endian bytes
fn encode_location(location: (i32, i32)) -> Vec<u8> {
location.0.to_le_bytes().iter()
.chain(location.1.to_le_bytes().iter())
.map(|x| *x)
.collect::<Vec<_>>()
}

/// entry point
fn main() -> anyhow::Result<()> {
// read all inputs, note we don't know
// how many there are, so we keep trying until
// an input errors
let mut raw_events = Vec::new();
for i in 0.. {
let filename = format!("/input-{}", i);
let event = match fs::read(filename) {
Ok(event) => event,
Err(err) => {
match err.kind() {
io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied => break,
_ => Err(err)?,
}
}
};

raw_events.push(event);
}

// decode
let events = raw_events.iter()
.map(|raw_event| decode_audio_event(&raw_event[..]))
.collect::<Result<Vec<_>, _>>()?;

// triangulate
let location = triangulate(&events);

// encode
let raw_location = encode_location(location);

// write our output through libveracruz
fs::write("/output", &raw_location)?;
Ok(())
}
1 change: 1 addition & 0 deletions veracruz-mcu-client/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
20 changes: 20 additions & 0 deletions veracruz-mcu-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# intermediary directories
build/
example/
test-data/
# this must be copied over from the transport-protocol crate
transport_protocol.proto
# these are all autogenerated
transport_protocol.pb.c
transport_protocol.pb.h
policy.c
policy.h
samples/audio-event-triangulation/clap.c
samples/audio-event-triangulation/clap.h
samples/shamir-secret-sharing/binary.h
samples/shamir-secret-sharing/binary.c
# output from reports
massif.out
rom_report.txt
static_ram_report.txt
dyn_ram_report.txt
3 changes: 3 additions & 0 deletions veracruz-mcu-client/.gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "nanopb"]
path = nanopb
url = https://github.com/nanopb/nanopb
Loading

0 comments on commit c0ca927

Please sign in to comment.