diff --git a/Cargo.toml b/Cargo.toml index b69fd7c..81a1fb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ authors = [ "David De Jesus Duarte ", "Henri Chataing ", ] -default-run = "pica-server" +default-run = "pica" exclude = [ "res/*", "scripts/*" @@ -28,8 +28,13 @@ name = "pica" path = "src/lib.rs" [[bin]] -name = "pica-server" -path = "src/bin/server/mod.rs" +name = "pica" +path = "src/bin/main.rs" + +[[bin]] +name = "pica-http" +path = "src/bin/http-server/main.rs" +features = ["web"] [features] default = ["web"] diff --git a/src/bin/http-server/main.rs b/src/bin/http-server/main.rs new file mode 100644 index 0000000..86f0730 --- /dev/null +++ b/src/bin/http-server/main.rs @@ -0,0 +1,557 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::Result; +use clap::Parser; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{body, Body, Request, Response, Server, StatusCode as HttpStatusCode}; +use serde::{Deserialize, Serialize}; +use serde_json::error::Category as SerdeErrorCategory; +use std::collections::HashMap; +use std::convert::Infallible; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; +use tokio::net::TcpListener; +use tokio::sync::{broadcast, mpsc, oneshot}; +use tokio::try_join; +use tokio_stream::{wrappers::BroadcastStream, StreamExt}; + +use pica::{Category, MacAddress, Pica, PicaCommand, PicaCommandError, PicaEvent}; + +mod position; +use position::Position; + +const DEFAULT_UCI_PORT: u16 = 7000; +const DEFAULT_WEB_PORT: u16 = 3000; + +const STATIC_FILES: &[(&str, &str, &str)] = &[ + ("/", "text/html", include_str!("../../../static/index.html")), + ( + "/openapi", + "text/html", + include_str!("../../../static/openapi.html"), + ), + ( + "/openapi.yaml", + "text/yaml", + include_str!("../../../static/openapi.yaml"), + ), + ( + "/src/components/Map.js", + "application/javascript", + include_str!("../../../static/src/components/Map.js"), + ), + ( + "/src/components/DeviceInfo.js", + "application/javascript", + include_str!("../../../static/src/components/DeviceInfo.js"), + ), + ( + "/src/components/Orientation.js", + "application/javascript", + include_str!("../../../static/src/components/Orientation.js"), + ), +]; + +/// Record information about an active device. +#[derive(Debug, Serialize, Clone)] +struct DeviceInformation { + category: pica::Category, + mac_address: MacAddress, + #[serde(flatten)] + position: Position, +} + +/// Record information about an active device. +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum Event { + DeviceAdded { + category: Category, + mac_address: MacAddress, + #[serde(flatten)] + position: Position, + }, + DeviceRemoved { + category: Category, + mac_address: MacAddress, + }, + DeviceUpdated { + category: Category, + mac_address: MacAddress, + #[serde(flatten)] + position: Position, + }, + NeighborUpdated { + source_category: Category, + source_mac_address: MacAddress, + destination_category: Category, + destination_mac_address: MacAddress, + distance: u16, + azimuth: i16, + elevation: i8, + }, +} + +/// Record the position of active devices for reference by the +/// ranging estimator. +#[derive(Clone)] +struct Context { + devices: Arc>>, + events: broadcast::Sender, +} + +impl Context { + fn new() -> Self { + let (events, _) = broadcast::channel(1024); + Context { + devices: Arc::new(Mutex::new(HashMap::new())), + events, + } + } + + async fn handle_connection_events( + self, + mut events: broadcast::Receiver, + ) -> Result<()> { + loop { + match events.recv().await { + Ok(PicaEvent::Connected { + mac_address, + handle, + }) => { + let mut devices = self.devices.lock().unwrap(); + devices.insert( + handle, + DeviceInformation { + category: Category::Uci, + mac_address, + position: Default::default(), + }, + ); + self.events + .send(Event::DeviceAdded { + category: Category::Uci, + mac_address, + position: Default::default(), + }) + .unwrap(); + } + Ok(PicaEvent::Disconnected { + mac_address, + handle, + }) => { + let mut devices = self.devices.lock().unwrap(); + devices.remove(&handle); + self.events + .send(Event::DeviceRemoved { + category: Category::Uci, + mac_address, + }) + .unwrap(); + } + Err(err) => anyhow::bail!(err), + } + } + } + + fn http_events(&self) -> Response { + let stream = BroadcastStream::new(self.events.subscribe()).map(|result| { + result.map(|event| { + format!( + "event: {}\ndata: {}\n\n", + event.name(), + serde_json::to_string(&event).unwrap() + ) + }) + }); + Response::builder() + .header("content-type", "text/event-stream") + .body(Body::wrap_stream(stream)) + .unwrap() + } + + fn http_set_position(&self, mac_address: MacAddress, position: Position) -> Response { + log::info!("set-position({}, {})", mac_address, position); + + let mut devices = self.devices.lock().unwrap(); + let mut found_device = None; + for (_, device) in devices.iter_mut() { + if device.mac_address == mac_address { + device.position = position; + found_device = Some(device.clone()); + break; + } + } + + let Some(device) = found_device else { + return Response::builder() + .status(HttpStatusCode::NOT_FOUND) + .body("".into()) + .unwrap(); + }; + + self.events + .send(Event::DeviceUpdated { + category: device.category, + mac_address, + position, + }) + .unwrap(); + + for other in devices.values() { + if other.mac_address != device.mac_address { + let local = device + .position + .compute_range_azimuth_elevation(&other.position); + let remote = other + .position + .compute_range_azimuth_elevation(&device.position); + + assert!(local.0 == remote.0); + + self.events + .send(Event::NeighborUpdated { + source_category: device.category, + source_mac_address: device.mac_address, + destination_category: other.category, + destination_mac_address: other.mac_address, + distance: local.0, + azimuth: local.1, + elevation: local.2, + }) + .unwrap(); + + let _ = self + .events + .send(Event::NeighborUpdated { + source_category: other.category, + source_mac_address: other.mac_address, + destination_category: device.category, + destination_mac_address: device.mac_address, + distance: remote.0, + azimuth: remote.1, + elevation: remote.2, + }) + .unwrap(); + } + } + + Response::builder() + .status(HttpStatusCode::OK) + .body("".into()) + .unwrap() + } + + async fn http_create_anchor( + &self, + mac_address: MacAddress, + position: Position, + cmd_tx: mpsc::Sender, + ) -> Response { + log::info!("create-anchor({}, {})", mac_address, position); + + let (rsp_tx, rsp_rx) = oneshot::channel::>(); + cmd_tx + .send(PicaCommand::CreateAnchor(mac_address, rsp_tx)) + .await + .unwrap(); + + let status = match rsp_rx.await { + Ok(Ok(handle)) => { + let mut devices = self.devices.lock().unwrap(); + devices.insert( + handle, + DeviceInformation { + position, + mac_address, + category: Category::Anchor, + }, + ); + self.events + .send(Event::DeviceAdded { + category: Category::Anchor, + mac_address, + position, + }) + .unwrap(); + HttpStatusCode::OK + } + Ok(Err(PicaCommandError::DeviceAlreadyExists(_))) => HttpStatusCode::CONFLICT, + Ok(Err(PicaCommandError::DeviceNotFound(_))) => HttpStatusCode::NOT_FOUND, + Err(_) => HttpStatusCode::INTERNAL_SERVER_ERROR, + }; + + Response::builder().status(status).body("".into()).unwrap() + } + + async fn http_destroy_anchor( + &self, + mac_address: MacAddress, + cmd_tx: mpsc::Sender, + ) -> Response { + log::info!("destroy-anchor({})", mac_address); + + let (rsp_tx, rsp_rx) = oneshot::channel::>(); + cmd_tx + .send(PicaCommand::DestroyAnchor(mac_address, rsp_tx)) + .await + .unwrap(); + + let status = match rsp_rx.await { + Ok(Ok(handle)) => { + let mut devices = self.devices.lock().unwrap(); + devices.remove(&handle); + self.events + .send(Event::DeviceRemoved { + category: Category::Anchor, + mac_address, + }) + .unwrap(); + HttpStatusCode::OK + } + Ok(Err(PicaCommandError::DeviceAlreadyExists(_))) => HttpStatusCode::CONFLICT, + Ok(Err(PicaCommandError::DeviceNotFound(_))) => HttpStatusCode::NOT_FOUND, + Err(_) => HttpStatusCode::INTERNAL_SERVER_ERROR, + }; + + Response::builder().status(status).body("".into()).unwrap() + } + + fn http_get_state(&self) -> Response { + log::info!("get-state()"); + + #[derive(Serialize)] + struct GetStateResponse { + devices: Vec, + } + + let devices = self.devices.lock().unwrap(); + let response = GetStateResponse { + devices: devices.values().cloned().collect::>(), + }; + let body = serde_json::to_string(&response).unwrap(); + Response::builder() + .status(HttpStatusCode::OK) + .body(body.into()) + .unwrap() + } +} + +impl pica::RangingEstimator for Context { + fn estimate( + &self, + left: &pica::Handle, + right: &pica::Handle, + ) -> anyhow::Result { + let devices = self + .devices + .lock() + .map_err(|_| anyhow::anyhow!("cannot take lock"))?; + let left_pos = devices + .get(left) + .ok_or(anyhow::anyhow!("unknown position"))? + .position; + let right_pos = devices + .get(right) + .ok_or(anyhow::anyhow!("unknown position"))? + .position; + let (range, azimuth, elevation) = left_pos.compute_range_azimuth_elevation(&right_pos); + Ok(pica::RangingMeasurement { + range, + azimuth, + elevation, + }) + } +} + +#[derive(Deserialize)] +struct PositionBody { + x: i16, + y: i16, + z: i16, + yaw: i16, + pitch: i8, + roll: i16, +} + +macro_rules! position { + ($body: ident) => { + position!($body, false) + }; + ($body: ident, $mandatory: ident) => { + match serde_json::from_slice::(&$body) { + Ok(body) => Position::new(body.x, body.y, body.z, body.yaw, body.pitch, body.roll), + Err(err) => { + if !$mandatory && err.classify() == SerdeErrorCategory::Eof { + Position::default() + } else { + let reason = format!("Error while deserializing position: {}", err); + log::error!("{}", reason); + return Ok(Response::builder().status(406).body(reason.into()).unwrap()); + } + } + } + }; +} + +macro_rules! mac_address { + ($mac_address: ident) => { + match MacAddress::new($mac_address.to_string()) { + Ok(mac_address) => mac_address, + Err(err) => { + let reason = format!("Error mac_address: {}", err); + log::error!("{}", reason); + return Ok(Response::builder().status(406).body(reason.into()).unwrap()); + } + } + }; +} + +impl Event { + fn name(&self) -> &'static str { + match self { + Event::DeviceAdded { .. } => "device-added", + Event::DeviceRemoved { .. } => "device-removed", + Event::DeviceUpdated { .. } => "device-updated", + Event::NeighborUpdated { .. } => "neighbor-updated", + } + } +} + +async fn http_request( + mut req: Request, + cmd_tx: mpsc::Sender, + context: Context, +) -> Result, Infallible> { + let static_file = STATIC_FILES + .iter() + .find(|(path, _, _)| req.uri().path() == *path); + + if let Some((_, mime, content)) = static_file { + return Ok(Response::builder() + .header("content-type", *mime) + .body((*content).into()) + .unwrap()); + } + + let body = body::to_bytes(req.body_mut()).await.unwrap(); + let response = match req + .uri_mut() + .path() + .trim_start_matches('/') + .split('/') + .collect::>()[..] + { + ["events"] => context.http_events(), + ["init-uci-device", mac_address] => { + context.http_set_position(mac_address!(mac_address), position!(body)) + } + ["set-position", mac_address] => { + context.http_set_position(mac_address!(mac_address), position!(body)) + } + ["create-anchor", mac_address] => { + context + .http_create_anchor(mac_address!(mac_address), position!(body), cmd_tx) + .await + } + ["destroy-anchor", mac_address] => { + context + .http_destroy_anchor(mac_address!(mac_address), cmd_tx) + .await + } + ["get-state"] => context.http_get_state(), + + _ => Response::builder() + .status(HttpStatusCode::NOT_FOUND) + .body("".into()) + .unwrap(), + }; + + Ok(response) +} + +async fn serve(context: Context, tx: mpsc::Sender, web_port: u16) -> Result<()> { + let addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, web_port); + let make_svc = make_service_fn(move |_conn| { + let tx = tx.clone(); + let local_context = context.clone(); + async move { + Ok::<_, Infallible>(service_fn(move |req| { + http_request(req, tx.clone(), local_context.clone()) + })) + } + }); + + let server = Server::bind(&addr.into()).serve(make_svc); + + log::info!("Pica: Web server started on http://0.0.0.0:{}", web_port); + + server.await?; + Ok(()) +} + +async fn listen(tx: mpsc::Sender, uci_port: u16) -> Result<()> { + let uci_socket = SocketAddrV4::new(Ipv4Addr::LOCALHOST, uci_port); + let uci_listener = TcpListener::bind(uci_socket).await?; + log::info!("Pica: Listening on: {}", uci_port); + + loop { + let (socket, addr) = uci_listener.accept().await?; + log::info!("Uwb host addr: {}", addr); + tx.send(PicaCommand::Connect(socket)).await? + } +} + +#[derive(Parser, Debug)] +#[command(name = "pica", about = "Virtual UWB subsystem")] +struct Args { + /// Output directory for storing .pcapng traces. + /// If provided, .pcapng traces of client connections are automatically + /// saved under the name `device-{handle}.pcapng`. + #[arg(short, long, value_name = "DIR")] + pcapng_dir: Option, + /// Configure the TCP port for the UCI server. + #[arg(short, long, value_name = "PORT", default_value_t = DEFAULT_UCI_PORT)] + uci_port: u16, + /// Configure the HTTP port for the web interface. + #[arg(short, long, value_name = "PORT", default_value_t = DEFAULT_WEB_PORT)] + web_port: u16, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Args::parse(); + assert_ne!( + args.uci_port, args.web_port, + "UCI port and WEB port must be different." + ); + + let context = Context::new(); + + let mut pica = Pica::new(Box::new(context.clone()), args.pcapng_dir); + let cmd_tx = pica.commands(); + let events_rx = pica.events(); + + try_join!( + pica.run(), + listen(cmd_tx.clone(), args.uci_port), + serve(context.clone(), cmd_tx.clone(), args.web_port), + context.handle_connection_events(events_rx), + )?; + + Ok(()) +} diff --git a/src/position.rs b/src/bin/http-server/position.rs similarity index 100% rename from src/position.rs rename to src/bin/http-server/position.rs diff --git a/src/bin/server/mod.rs b/src/bin/main.rs similarity index 65% rename from src/bin/server/mod.rs rename to src/bin/main.rs index 7cf015d..f3ac9b2 100644 --- a/src/bin/server/mod.rs +++ b/src/bin/main.rs @@ -12,14 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate bytes; -extern crate num_derive; -extern crate num_traits; -extern crate thiserror; - -#[cfg(feature = "web")] -mod web; - use anyhow::Result; use clap::Parser; use env_logger::Env; @@ -31,9 +23,8 @@ use tokio::sync::mpsc; use tokio::try_join; const DEFAULT_UCI_PORT: u16 = 7000; -const DEFAULT_WEB_PORT: u16 = 3000; -async fn accept_incoming(tx: mpsc::Sender, uci_port: u16) -> Result<()> { +async fn accept_incoming(cmd_tx: mpsc::Sender, uci_port: u16) -> Result<()> { let uci_socket = SocketAddrV4::new(Ipv4Addr::LOCALHOST, uci_port); let uci_listener = TcpListener::bind(uci_socket).await?; log::info!("Pica: Listening on: {}", uci_port); @@ -41,7 +32,7 @@ async fn accept_incoming(tx: mpsc::Sender, uci_port: u16) -> Result loop { let (socket, addr) = uci_listener.accept().await?; log::info!("Uwb host addr: {}", addr); - tx.send(PicaCommand::Connect(socket)).await? + cmd_tx.send(PicaCommand::Connect(socket)).await? } } @@ -56,9 +47,21 @@ struct Args { /// Configure the TCP port for the UCI server. #[arg(short, long, value_name = "UCI_PORT", default_value_t = DEFAULT_UCI_PORT)] uci_port: u16, - /// Configure the HTTP port for the web interface. - #[arg(short, long, value_name = "WEB_PORT", default_value_t = DEFAULT_WEB_PORT)] - web_port: u16, +} + +struct MockRangingEstimator(); + +/// The position cannot be communicated to the pica environment when +/// using the default binary (HTTP interface not available). +/// Thus the ranging estimator cannot produce any result. +impl pica::RangingEstimator for MockRangingEstimator { + fn estimate( + &self, + _left: &pica::Handle, + _right: &pica::Handle, + ) -> Result { + Err(anyhow::anyhow!("position not available")) + } } #[tokio::main] @@ -66,24 +69,11 @@ async fn main() -> Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); let args = Args::parse(); - assert_ne!( - args.uci_port, args.web_port, - "UCI port and Web port shall be different." - ); - - let mut pica = Pica::new(args.pcapng_dir); - let pica_tx = pica.tx(); - let pica_events = pica.events(); - #[cfg(feature = "web")] - try_join!( - accept_incoming(pica_tx.clone(), args.uci_port), - pica.run(), - web::serve(pica_tx, pica_events, args.web_port) - )?; + let mut pica = Pica::new(Box::new(MockRangingEstimator()), args.pcapng_dir); + let commands = pica.commands(); - #[cfg(not(feature = "web"))] - try_join!(accept_incoming(pica_tx.clone(), args.uci_port), pica.run(),)?; + try_join!(accept_incoming(commands.clone(), args.uci_port), pica.run(),)?; Ok(()) } diff --git a/src/bin/server/web.rs b/src/bin/server/web.rs deleted file mode 100644 index d43ab9d..0000000 --- a/src/bin/server/web.rs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::convert::Infallible; -use std::net::{Ipv4Addr, SocketAddrV4}; - -use anyhow::{Context, Result}; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{body, Body, Request, Response, Server, StatusCode as HttpStatusCode}; -use serde::{Deserialize, Serialize}; -use serde_json::error::Category as SerdeErrorCategory; -use tokio::sync::{broadcast, mpsc, oneshot}; -use tokio_stream::{wrappers::BroadcastStream, StreamExt}; - -use pica::{ - Category, MacAddress, PicaCommand, PicaCommandError, PicaCommandStatus, PicaEvent, Position, -}; -use PicaEvent::{DeviceAdded, DeviceRemoved, DeviceUpdated, NeighborUpdated}; - -const STATIC_FILES: &[(&str, &str, &str)] = &[ - ("/", "text/html", include_str!("../../../static/index.html")), - ( - "/openapi", - "text/html", - include_str!("../../../static/openapi.html"), - ), - ( - "/openapi.yaml", - "text/yaml", - include_str!("../../../static/openapi.yaml"), - ), - ( - "/src/components/Map.js", - "application/javascript", - include_str!("../../../static/src/components/Map.js"), - ), - ( - "/src/components/DeviceInfo.js", - "application/javascript", - include_str!("../../../static/src/components/DeviceInfo.js"), - ), - ( - "/src/components/Orientation.js", - "application/javascript", - include_str!("../../../static/src/components/Orientation.js"), - ), -]; - -#[derive(Deserialize)] -struct PositionBody { - x: i16, - y: i16, - z: i16, - yaw: i16, - pitch: i8, - roll: i16, -} - -macro_rules! position { - ($body: ident) => { - position!($body, false) - }; - ($body: ident, $mandatory: ident) => { - match serde_json::from_slice::(&$body) { - Ok(body) => Position::new(body.x, body.y, body.z, body.yaw, body.pitch, body.roll), - Err(err) => { - if !$mandatory && err.classify() == SerdeErrorCategory::Eof { - Position::default() - } else { - let reason = format!("Error while deserializing position: {}", err); - log::error!("{}", reason); - return Ok(Response::builder().status(406).body(reason.into()).unwrap()); - } - } - } - }; -} - -macro_rules! mac_address { - ($mac_address: ident) => { - match MacAddress::new($mac_address.to_string()) { - Ok(mac_address) => mac_address, - Err(err) => { - let reason = format!("Error mac_address: {}", err); - log::error!("{}", reason); - return Ok(Response::builder().status(406).body(reason.into()).unwrap()); - } - } - }; -} - -#[derive(Debug, Serialize, Clone)] -struct Device { - pub category: Category, - pub mac_address: String, - #[serde(flatten)] - pub position: Position, -} - -fn event_name(event: &PicaEvent) -> &'static str { - match event { - DeviceAdded { .. } => "device-added", - DeviceRemoved { .. } => "device-removed", - DeviceUpdated { .. } => "device-updated", - NeighborUpdated { .. } => "neighbor-updated", - } -} - -async fn handle( - mut req: Request, - tx: mpsc::Sender, - events: broadcast::Receiver, -) -> Result, Infallible> { - let static_file = STATIC_FILES - .iter() - .find(|(path, _, _)| req.uri().path() == *path); - - if let Some((_, mime, content)) = static_file { - return Ok(Response::builder() - .header("content-type", *mime) - .body((*content).into()) - .unwrap()); - } - - let body = body::to_bytes(req.body_mut()).await.unwrap(); - let (pica_cmd_rsp_tx, pica_cmd_rsp_rx) = oneshot::channel::(); - - let send_cmd = |pica_cmd| async { - log::debug!("PicaCommand: {}", pica_cmd); - tx.send(pica_cmd).await.unwrap(); - let (status, description) = match pica_cmd_rsp_rx.await { - Ok(Ok(_)) => (HttpStatusCode::OK, "success".into()), - Ok(Err(err)) => ( - match err { - PicaCommandError::DeviceAlreadyExists(_) => HttpStatusCode::CONFLICT, - PicaCommandError::DeviceNotFound(_) => HttpStatusCode::NOT_FOUND, - }, - format!("{}", err), - ), - Err(err) => ( - HttpStatusCode::INTERNAL_SERVER_ERROR, - format!("Error getting command response: {}", err), - ), - }; - log::debug!(" status: {}, {}", status, description); - Response::builder() - .status(status) - .body(description.into()) - .unwrap() - }; - - match req - .uri_mut() - .path() - .trim_start_matches('/') - .split('/') - .collect::>()[..] - { - ["events"] => { - let stream = BroadcastStream::new(events).map(|result| { - result.map(|event| { - format!( - "event: {}\ndata: {}\n\n", - event_name(&event), - serde_json::to_string(&event).unwrap() - ) - }) - }); - return Ok(Response::builder() - .header("content-type", "text/event-stream") - .body(Body::wrap_stream(stream)) - .unwrap()); - } - ["init-uci-device", mac_address] => { - return Ok(send_cmd(PicaCommand::InitUciDevice( - mac_address!(mac_address), - position!(body), - pica_cmd_rsp_tx, - )) - .await); - } - ["set-position", mac_address] => { - return Ok(send_cmd(PicaCommand::SetPosition( - mac_address!(mac_address), - position!(body), - pica_cmd_rsp_tx, - )) - .await); - } - ["create-anchor", mac_address] => { - return Ok(send_cmd(PicaCommand::CreateAnchor( - mac_address!(mac_address), - position!(body), - pica_cmd_rsp_tx, - )) - .await); - } - ["destroy-anchor", mac_address] => { - return Ok(send_cmd(PicaCommand::DestroyAnchor( - mac_address!(mac_address), - pica_cmd_rsp_tx, - )) - .await); - } - ["get-state"] => { - #[derive(Serialize)] - struct GetStateResponse { - devices: Vec, - } - log::debug!("PicaCommand: GetState"); - let (state_tx, state_rx) = oneshot::channel::>(); - tx.send(PicaCommand::GetState(state_tx)).await.unwrap(); - let devices = match state_rx.await { - Ok(devices) => GetStateResponse { - devices: devices - .into_iter() - .map(|(category, mac_address, position)| Device { - category, - mac_address: mac_address.into(), - position, - }) - .collect(), - }, - Err(_) => GetStateResponse { devices: vec![] }, - }; - let body = serde_json::to_string(&devices).unwrap(); - return Ok(Response::builder().status(200).body(body.into()).unwrap()); - } - - _ => (), - } - - Ok(Response::builder().status(404).body("".into()).unwrap()) -} - -pub async fn serve( - tx: mpsc::Sender, - events: broadcast::Sender, - web_port: u16, -) -> Result<()> { - let addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, web_port); - - let make_svc = make_service_fn(move |_conn| { - let tx = tx.clone(); - let events = events.clone(); - async move { - Ok::<_, Infallible>(service_fn(move |req| { - handle(req, tx.clone(), events.subscribe()) - })) - } - }); - - let server = Server::bind(&addr.into()).serve(make_svc); - - log::debug!("Pica: Web server started on http://0.0.0.0:{}", web_port); - - server.await.context("Web Server Error") -} diff --git a/src/device.rs b/src/device.rs index 131b55a..0a0c539 100644 --- a/src/device.rs +++ b/src/device.rs @@ -13,7 +13,6 @@ // limitations under the License. use crate::packets::uci::*; -use crate::position::Position; use crate::MacAddress; use crate::PicaCommand; @@ -74,9 +73,8 @@ pub const DEFAULT_CAPS_INFO: &[(CapTlvType, &[u8])] = &[ ]; pub struct Device { - handle: usize, + pub handle: usize, pub mac_address: MacAddress, - pub position: Position, /// [UCI] 5. UWBS Device State Machine state: DeviceState, sessions: HashMap, @@ -101,7 +99,6 @@ impl Device { Device { handle: device_handle, mac_address, - position: Position::default(), state: DeviceState::DeviceStateError, // Will be overwitten sessions: Default::default(), tx, diff --git a/src/lib.rs b/src/lib.rs index e8c6f01..af280b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,12 +23,8 @@ use thiserror::Error; use tokio::net::TcpStream; use tokio::sync::{broadcast, mpsc, oneshot}; -mod pcapng; - -mod position; -pub use position::Position; - mod packets; +mod pcapng; use packets::uci::StatusCode as UciStatusCode; use packets::uci::*; @@ -44,9 +40,47 @@ pub use mac_address::MacAddress; use crate::session::RangeDataNtfConfig; -pub type PicaCommandStatus = Result<(), PicaCommandError>; pub type UciPacket = Vec; +/// Handle allocated for created devices or anchors. +/// The handle is unique across the lifetime of the Pica context +/// and callers may assume that one handle is never reused. +pub type Handle = usize; + +/// Ranging measurement produced by a ranging estimator. +#[derive(Clone, Copy, Default, Debug)] +pub struct RangingMeasurement { + pub range: u16, + pub azimuth: i16, + pub elevation: i8, +} + +/// Trait matching the capabilities of a ranging estimator. +/// The estimator manages the position of the devices, and chooses +/// the algorithm used to generate the ranging measurements. +pub trait RangingEstimator { + /// Evaluate the ranging measurement for the two input devices + /// identified by their respective handle. The result is a triplet + /// containing the range, azimuth, and elevation of the right device + /// relative to the left device. + fn estimate(&self, left: &Handle, right: &Handle) -> Result; +} + +/// Pica emulation environment. +/// All the devices added to this environment are emulated as if they were +/// from the same physical space. +pub struct Pica { + counter: usize, + devices: HashMap, + anchors: HashMap, + command_rx: mpsc::Receiver, + command_tx: mpsc::Sender, + event_tx: broadcast::Sender, + #[allow(unused)] + ranging_estimator: Box, + pcapng_dir: Option, +} + #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum PicaCommandError { #[error("Device already exists: {0}")] @@ -69,16 +103,16 @@ pub enum PicaCommand { UciData(usize, DataPacket), // Execute UCI command received for selected device. UciCommand(usize, UciCommand), - // Init Uci Device - InitUciDevice(MacAddress, Position, oneshot::Sender), - // Set Position - SetPosition(MacAddress, Position, oneshot::Sender), // Create Anchor - CreateAnchor(MacAddress, Position, oneshot::Sender), + CreateAnchor( + MacAddress, + oneshot::Sender>, + ), // Destroy Anchor - DestroyAnchor(MacAddress, oneshot::Sender), - // Get State - GetState(oneshot::Sender>), + DestroyAnchor( + MacAddress, + oneshot::Sender>, + ), } impl Display for PicaCommand { @@ -90,11 +124,8 @@ impl Display for PicaCommand { PicaCommand::StopRanging(_, _) => "StopRanging", PicaCommand::UciData(_, _) => "UciData", PicaCommand::UciCommand(_, _) => "UciCommand", - PicaCommand::InitUciDevice(_, _, _) => "InitUciDevice", - PicaCommand::SetPosition(_, _, _) => "SetPosition", - PicaCommand::CreateAnchor(_, _, _) => "CreateAnchor", + PicaCommand::CreateAnchor(_, _) => "CreateAnchor", PicaCommand::DestroyAnchor(_, _) => "DestroyAnchor", - PicaCommand::GetState(_) => "GetState", }; write!(f, "{}", cmd) } @@ -103,34 +134,16 @@ impl Display for PicaCommand { #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum PicaEvent { - // A Device was added - DeviceAdded { - category: Category, + // A UCI connection was added + Connected { + handle: Handle, mac_address: MacAddress, - #[serde(flatten)] - position: Position, }, - // A Device was removed - DeviceRemoved { - category: Category, + // A UCI connection was lost + Disconnected { + handle: Handle, mac_address: MacAddress, }, - // A Device position has changed - DeviceUpdated { - category: Category, - mac_address: MacAddress, - #[serde(flatten)] - position: Position, - }, - NeighborUpdated { - source_category: Category, - source_mac_address: MacAddress, - destination_category: Category, - destination_mac_address: MacAddress, - distance: u16, - azimuth: i16, - elevation: i8, - }, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -141,18 +154,9 @@ pub enum Category { #[derive(Debug, Clone, Copy)] struct Anchor { + handle: Handle, + #[allow(unused)] mac_address: MacAddress, - position: Position, -} - -pub struct Pica { - devices: HashMap, - anchors: HashMap, - counter: usize, - rx: mpsc::Receiver, - tx: mpsc::Sender, - event_tx: broadcast::Sender, - pcapng_dir: Option, } /// Result of UCI packet parsing. @@ -218,22 +222,22 @@ fn parse_uci_packet(bytes: &[u8]) -> UciParseResult { fn make_measurement( mac_address: &MacAddress, - local: (u16, i16, i8), - remote: (u16, i16, i8), + local: RangingMeasurement, + remote: RangingMeasurement, ) -> ShortAddressTwoWayRangingMeasurement { if let MacAddress::Short(address) = mac_address { ShortAddressTwoWayRangingMeasurement { mac_address: u16::from_le_bytes(*address), status: UciStatusCode::UciStatusOk, nlos: 0, // in Line Of Sight - distance: local.0, - aoa_azimuth: local.1 as u16, + distance: local.range, + aoa_azimuth: local.azimuth as u16, aoa_azimuth_fom: 100, // Yup, pretty sure about this - aoa_elevation: local.2 as u16, + aoa_elevation: local.elevation as u16, aoa_elevation_fom: 100, // Yup, pretty sure about this - aoa_destination_azimuth: remote.1 as u16, + aoa_destination_azimuth: remote.azimuth as u16, aoa_destination_azimuth_fom: 100, - aoa_destination_elevation: remote.2 as u16, + aoa_destination_elevation: remote.elevation as u16, aoa_destination_elevation_fom: 100, slot_index: 0, rssi: u8::MAX, @@ -244,26 +248,27 @@ fn make_measurement( } impl Pica { - pub fn new(pcapng_dir: Option) -> Self { - let (tx, rx) = mpsc::channel(MAX_SESSION * MAX_DEVICE); + pub fn new(ranging_estimator: Box, pcapng_dir: Option) -> Self { + let (command_tx, command_rx) = mpsc::channel(MAX_SESSION * MAX_DEVICE); let (event_tx, _) = broadcast::channel(16); Pica { devices: HashMap::new(), anchors: HashMap::new(), counter: 0, - rx, - tx, + command_rx, + command_tx, event_tx, + ranging_estimator, pcapng_dir, } } - pub fn events(&self) -> broadcast::Sender { - self.event_tx.clone() + pub fn events(&self) -> broadcast::Receiver { + self.event_tx.subscribe() } - pub fn tx(&self) -> mpsc::Sender { - self.tx.clone() + pub fn commands(&self) -> mpsc::Sender { + self.command_tx.clone() } fn get_device_mut(&mut self, device_handle: usize) -> Option<&mut Device> { @@ -309,19 +314,18 @@ impl Pica { async fn connect(&mut self, stream: TcpStream) { let (packet_tx, mut packet_rx) = mpsc::channel(MAX_SESSION); let device_handle = self.counter; - let pica_tx = self.tx.clone(); + let pica_tx = self.command_tx.clone(); let pcapng_dir = self.pcapng_dir.clone(); log::debug!("[{}] Connecting device", device_handle); self.counter += 1; - let mut device = Device::new(device_handle, packet_tx, self.tx.clone()); + let mut device = Device::new(device_handle, packet_tx, self.command_tx.clone()); device.init(); - self.send_event(PicaEvent::DeviceAdded { - category: Category::Uci, + self.send_event(PicaEvent::Connected { + handle: device_handle, mac_address: device.mac_address, - position: device.position, }); self.devices.insert(device_handle, device); @@ -386,8 +390,8 @@ impl Pica { .ok_or_else(|| PicaCommandError::DeviceNotFound(device_handle.into())) { Ok(device) => { - self.send_event(PicaEvent::DeviceRemoved { - category: Category::Uci, + self.send_event(PicaEvent::Disconnected { + handle: device_handle, mac_address: device.mac_address, }); self.devices.remove(&device_handle); @@ -409,37 +413,38 @@ impl Pica { .get_dst_mac_addresses() .iter() .for_each(|mac_address| { - if let Some(anchor) = self.anchors.get(mac_address) { - let local = device - .position - .compute_range_azimuth_elevation(&anchor.position); - let remote = anchor - .position - .compute_range_azimuth_elevation(&device.position); - - assert!(local.0 == remote.0); + if let Some(other) = self.anchors.get(mac_address) { + let local = self + .ranging_estimator + .estimate(&device.handle, &other.handle) + .unwrap_or(Default::default()); + let remote = self + .ranging_estimator + .estimate(&other.handle, &device.handle) + .unwrap_or(Default::default()); measurements.push(make_measurement(mac_address, local, remote)); } - if let Some(peer_device) = self.get_device_by_mac(mac_address) { - if peer_device.can_start_ranging(session, session_id) { - let local: (u16, i16, i8) = device - .position - .compute_range_azimuth_elevation(&peer_device.position); - let remote = peer_device - .position - .compute_range_azimuth_elevation(&device.position); - - assert!(local.0 == remote.0); + if let Some(other) = self.get_device_by_mac(mac_address) { + if other.can_start_ranging(session, session_id) { + let local = self + .ranging_estimator + .estimate(&device.handle, &other.handle) + .unwrap_or(Default::default()); + let remote = self + .ranging_estimator + .estimate(&other.handle, &device.handle) + .unwrap_or(Default::default()); measurements.push(make_measurement(mac_address, local, remote)); } if device.can_start_data_transfer(session_id) - && peer_device.can_receive_data_transfer(session_id) + && other.can_receive_data_transfer(session_id) { - peer_device_data_transfer.push(peer_device); + peer_device_data_transfer.push(other); } } }); + // TODO: Data transfer should be limited in size for // each round of ranging for peer_device in peer_device_data_transfer.iter() { @@ -528,7 +533,7 @@ impl Pica { pub async fn run(&mut self) -> Result<()> { loop { use PicaCommand::*; - match self.rx.recv().await { + match self.command_rx.recv().await { Some(Connect(stream)) => { self.connect(stream).await; } @@ -541,19 +546,12 @@ impl Pica { } Some(UciData(device_handle, data)) => self.uci_data(device_handle, data).await, Some(UciCommand(device_handle, cmd)) => self.command(device_handle, cmd).await, - Some(SetPosition(mac_address, position, pica_cmd_rsp_tx)) => { - self.set_position(mac_address, position, pica_cmd_rsp_tx) - } - Some(CreateAnchor(mac_address, position, pica_cmd_rsp_tx)) => { - self.create_anchor(mac_address, position, pica_cmd_rsp_tx) + Some(CreateAnchor(mac_address, pica_cmd_rsp_tx)) => { + self.create_anchor(mac_address, pica_cmd_rsp_tx) } Some(DestroyAnchor(mac_address, pica_cmd_rsp_tx)) => { self.destroy_anchor(mac_address, pica_cmd_rsp_tx) } - Some(GetState(state_tx)) => self.get_state(state_tx), - Some(InitUciDevice(mac_address, position, pica_cmd_rsp_tx)) => { - self.init_uci_device(mac_address, position, pica_cmd_rsp_tx); - } None => (), }; } @@ -581,141 +579,36 @@ impl Pica { } } - // TODO: Assign a reserved range of mac addresses for UCI devices - // to protect against conflicts with user defined Anchor addresses - // b/246000641 - fn init_uci_device( - &mut self, - mac_address: MacAddress, - position: Position, - pica_cmd_rsp_tx: oneshot::Sender, - ) { - log::debug!("[_] Init device"); - log::debug!(" mac_address: {}", mac_address); - log::debug!(" position={:?}", position); - - let status = self - .get_device_mut_by_mac(&mac_address) - .ok_or(PicaCommandError::DeviceNotFound(mac_address)) - .map(|uci_device| { - uci_device.mac_address = mac_address; - uci_device.position = position; - }); - - pica_cmd_rsp_tx.send(status).unwrap_or_else(|err| { - log::error!("Failed to send init-uci-device command response: {:?}", err) - }); - } - - fn set_position( - &mut self, - mac_address: MacAddress, - position: Position, - pica_cmd_rsp_tx: oneshot::Sender, - ) { - let mut status = if let Some(uci_device) = self.get_device_mut_by_mac(&mac_address) { - uci_device.position = position; - Ok(()) - } else if let Some(anchor) = self.anchors.get_mut(&mac_address) { - anchor.position = position; - Ok(()) - } else { - Err(PicaCommandError::DeviceNotFound(mac_address)) - }; - - if status.is_ok() { - status = self.update_position(mac_address, position) - } - - pica_cmd_rsp_tx.send(status).unwrap_or_else(|err| { - log::error!("Failed to send set-position command response: {:?}", err) - }); - } - - fn update_position( - &self, - mac_address: MacAddress, - position: Position, - ) -> Result<(), PicaCommandError> { - let category = match self.get_category(&mac_address) { - Some(category) => category, - None => { - return Err(PicaCommandError::DeviceNotFound(mac_address)); - } - }; - self.send_event(PicaEvent::DeviceUpdated { - category, - mac_address, - position, - }); - - let devices = self.devices.values().map(|d| (d.mac_address, d.position)); - let anchors = self.anchors.values().map(|b| (b.mac_address, b.position)); - - let update_neighbors = |device_category, device_mac_address, device_position| { - if mac_address != device_mac_address { - let local = position.compute_range_azimuth_elevation(&device_position); - let remote = device_position.compute_range_azimuth_elevation(&position); - - assert!(local.0 == remote.0); - - self.send_event(PicaEvent::NeighborUpdated { - source_category: category, - source_mac_address: mac_address, - destination_category: device_category, - destination_mac_address: device_mac_address, - distance: local.0, - azimuth: local.1, - elevation: local.2, - }); - - self.send_event(PicaEvent::NeighborUpdated { - source_category: device_category, - source_mac_address: device_mac_address, - destination_category: category, - destination_mac_address: mac_address, - distance: remote.0, - azimuth: remote.1, - elevation: remote.2, - }); - } - }; - - devices.for_each(|device| update_neighbors(Category::Uci, device.0, device.1)); - anchors.for_each(|anchor| update_neighbors(Category::Anchor, anchor.0, anchor.1)); - Ok(()) - } - #[allow(clippy::map_entry)] fn create_anchor( &mut self, mac_address: MacAddress, - position: Position, - pica_cmd_rsp_tx: oneshot::Sender, + rsp_tx: oneshot::Sender>, ) { - log::debug!("Create anchor: {} {}", mac_address, position); + log::debug!("[_] Create anchor"); + log::debug!(" mac_address: {}", mac_address); + let status = if self.get_category(&mac_address).is_some() { Err(PicaCommandError::DeviceAlreadyExists(mac_address)) } else { - self.send_event(PicaEvent::DeviceAdded { - category: Category::Anchor, - mac_address, - position, - }); + let handle = self.counter; + self.counter += 1; + assert!(self .anchors .insert( mac_address, Anchor { + handle, mac_address, - position, }, ) .is_none()); - Ok(()) + + Ok(handle) }; - pica_cmd_rsp_tx.send(status).unwrap_or_else(|err| { + rsp_tx.send(status).unwrap_or_else(|err| { log::error!("Failed to send create-anchor command response: {:?}", err) }) } @@ -723,40 +616,18 @@ impl Pica { fn destroy_anchor( &mut self, mac_address: MacAddress, - pica_cmd_rsp_tx: oneshot::Sender, + rsp_tx: oneshot::Sender>, ) { log::debug!("[_] Destroy anchor"); log::debug!(" mac_address: {}", mac_address); - let status = if self.anchors.remove(&mac_address).is_none() { - Err(PicaCommandError::DeviceNotFound(mac_address)) - } else { - self.send_event(PicaEvent::DeviceRemoved { - category: Category::Anchor, - mac_address, - }); - Ok(()) + let status = match self.anchors.remove(&mac_address) { + None => Err(PicaCommandError::DeviceNotFound(mac_address)), + Some(anchor) => Ok(anchor.handle), }; - pica_cmd_rsp_tx.send(status).unwrap_or_else(|err| { + + rsp_tx.send(status).unwrap_or_else(|err| { log::error!("Failed to send destroy-anchor command response: {:?}", err) }) } - - fn get_state(&self, state_tx: oneshot::Sender>) { - log::debug!("[_] Get State"); - - state_tx - .send( - self.anchors - .values() - .map(|anchor| (Category::Anchor, anchor.mac_address, anchor.position)) - .chain( - self.devices - .values() - .map(|device| (Category::Uci, device.mac_address, device.position)), - ) - .collect(), - ) - .unwrap(); - } } diff --git a/src/mac_address.rs b/src/mac_address.rs index 115c64e..6559175 100644 --- a/src/mac_address.rs +++ b/src/mac_address.rs @@ -19,21 +19,22 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; const SHORT_MAC_ADDRESS_SIZE: usize = 2; -const STRING_SHORT_MAC_ADDRESS_SIZE: usize = 2 * SHORT_MAC_ADDRESS_SIZE; +const SHORT_MAC_ADDRESS_STR_SIZE: usize = 2 * SHORT_MAC_ADDRESS_SIZE; -const EXTEND_MAC_ADDRESS_SIZE: usize = 8; -const STRING_EXTEND_MAC_ADDRESS_SIZE: usize = 2 * EXTEND_MAC_ADDRESS_SIZE; +const EXTENDED_MAC_ADDRESS_SIZE: usize = 8; +const EXTENDED_MAC_ADDRESS_STR_SIZE: usize = 2 * EXTENDED_MAC_ADDRESS_SIZE; #[derive(Error, Debug)] pub enum Error { #[error("MacAddress has the wrong format: 0")] MacAddressWrongFormat(String), } + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(try_from = "String", into = "String")] pub enum MacAddress { Short([u8; SHORT_MAC_ADDRESS_SIZE]), - Extend([u8; EXTEND_MAC_ADDRESS_SIZE]), + Extended([u8; EXTENDED_MAC_ADDRESS_SIZE]), } impl MacAddress { @@ -45,7 +46,7 @@ impl MacAddress { impl From for MacAddress { fn from(device_handle: usize) -> Self { let handle = device_handle as u64; - MacAddress::Extend(handle.to_be_bytes()) + MacAddress::Extended(handle.to_be_bytes()) } } @@ -53,7 +54,7 @@ impl From for u64 { fn from(mac_address: MacAddress) -> Self { match mac_address { MacAddress::Short(addr) => u16::from_le_bytes(addr) as u64, - MacAddress::Extend(addr) => u64::from_le_bytes(addr), + MacAddress::Extended(addr) => u64::from_le_bytes(addr), } } } @@ -61,20 +62,18 @@ impl From for u64 { impl TryFrom for MacAddress { type Error = Error; fn try_from(mac_address: String) -> std::result::Result { - let mac_address = mac_address.replace(':', ""); - let mac_address = mac_address.replace("%3A", ""); - let uwb_mac_address = match mac_address.len() { - STRING_SHORT_MAC_ADDRESS_SIZE => MacAddress::Short( + let mac_address = mac_address.replace(':', "").replace("%3A", ""); + Ok(match mac_address.len() { + SHORT_MAC_ADDRESS_STR_SIZE => MacAddress::Short( <[u8; SHORT_MAC_ADDRESS_SIZE]>::from_hex(mac_address) .map_err(|err| Error::MacAddressWrongFormat(err.to_string()))?, ), - STRING_EXTEND_MAC_ADDRESS_SIZE => MacAddress::Extend( - <[u8; EXTEND_MAC_ADDRESS_SIZE]>::from_hex(mac_address) + EXTENDED_MAC_ADDRESS_STR_SIZE => MacAddress::Extended( + <[u8; EXTENDED_MAC_ADDRESS_SIZE]>::from_hex(mac_address) .map_err(|err| Error::MacAddressWrongFormat(err.to_string()))?, ), _ => return Err(Error::MacAddressWrongFormat(mac_address)), - }; - Ok(uwb_mac_address) + }) } } @@ -90,7 +89,7 @@ impl From<&MacAddress> for String { }; match mac_address { MacAddress::Short(address) => to_string(address), - MacAddress::Extend(address) => to_string(address), + MacAddress::Extended(address) => to_string(address), } } } @@ -121,7 +120,7 @@ mod tests { let valid_mac_address = "FF:77:AA:DD:EE:BB:CC:10"; assert_eq!( MacAddress::new(valid_mac_address.into()).unwrap(), - MacAddress::Extend([0xFF, 0x77, 0xAA, 0xDD, 0xEE, 0xBB, 0xCC, 0x10]) + MacAddress::Extended([0xFF, 0x77, 0xAA, 0xDD, 0xEE, 0xBB, 0xCC, 0x10]) ); } @@ -141,17 +140,17 @@ mod tests { #[test] fn display_mac_address() { - let extend_mac_address = "00:FF:77:AA:DD:EE:CC:45"; + let extended_mac_address = "00:FF:77:AA:DD:EE:CC:45"; let short_mac_address = "00:FF"; assert_eq!( - format!("{}", MacAddress::new(extend_mac_address.into()).unwrap()), - extend_mac_address + format!("{}", MacAddress::new(extended_mac_address.into()).unwrap()), + extended_mac_address ); assert_eq!( format!("{}", MacAddress::new(short_mac_address.into()).unwrap()), short_mac_address ); - assert_eq!(extend_mac_address.to_string(), extend_mac_address); + assert_eq!(extended_mac_address.to_string(), extended_mac_address); } #[test] @@ -163,9 +162,9 @@ mod tests { } #[test] - fn test_extend_mac_to_u64() { - let extend_mac = MacAddress::Extend([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); - let result: u64 = extend_mac.into(); + fn test_extended_mac_to_u64() { + let extended_mac = MacAddress::Extended([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + let result: u64 = extended_mac.into(); let expected: u64 = 0x0807060504030201; assert_eq!(result, expected); } diff --git a/src/session.rs b/src/session.rs index 9dd2f34..9d7e027 100644 --- a/src/session.rs +++ b/src/session.rs @@ -465,7 +465,7 @@ impl AppConfig { MacAddress::Short(value[..].try_into().unwrap()) } MacAddressMode::AddressMode2 => { - MacAddress::Extend(value[..].try_into().unwrap()) + MacAddress::Extended(value[..].try_into().unwrap()) } _ => panic!("Unexpected MAC Address Mode"), }; @@ -489,7 +489,7 @@ impl AppConfig { .chunks(mac_address_size) .map(|c| match self.mac_address_mode { MacAddressMode::AddressMode0 => MacAddress::Short(c.try_into().unwrap()), - MacAddressMode::AddressMode2 => MacAddress::Extend(c.try_into().unwrap()), + MacAddressMode::AddressMode2 => MacAddress::Extended(c.try_into().unwrap()), _ => panic!("Unexpected MAC Address Mode"), }) .collect(); @@ -1037,7 +1037,7 @@ impl Session { controlee_status.push(ControleeStatus { mac_address: match controlee.short_address { MacAddress::Short(address) => address, - MacAddress::Extend(_) => panic!("Extended address is not supported!"), + MacAddress::Extended(_) => panic!("Extended address is not supported!"), }, subsession_id: controlee.sub_session_id, status: update_status, @@ -1073,7 +1073,7 @@ impl Session { controlee_status.push(ControleeStatus { mac_address: match address { MacAddress::Short(addr) => addr, - MacAddress::Extend(_) => panic!("Extended address is not supported!"), + MacAddress::Extended(_) => panic!("Extended address is not supported!"), }, subsession_id: controlee.sub_session_id, status: update_status, diff --git a/tests/test_runner.py b/tests/test_runner.py index 72f4d3b..83c5d5a 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -10,7 +10,7 @@ from . import ranging, data_transfer -PICA_BIN = Path("./target/debug/pica-server") +PICA_BIN = Path("./target/debug/pica") DATA_FILE = Path("README.md") PICA_LOCALHOST = "127.0.0.1"