Skip to content

Commit

Permalink
wip: rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
passcod committed May 18, 2024
1 parent 770bceb commit 564ffbc
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 68 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/improv-wifi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ tracing = { version = "0.1.40", features = ["attributes"] }
miette = { version = "7.2.0", optional = true }
bluer = { version = "0.17.1", features = ["bluetoothd"] }
tokio = { version = "1.37.0", features = ["sync", "rt"] }
winnow = "0.6.8"

[features]
miette = ["dep:miette"]
Expand Down
178 changes: 112 additions & 66 deletions crates/improv-wifi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use bluer::{
gatt::local::{
characteristic_control, service_control, Application, ApplicationHandle, Characteristic,
CharacteristicControl, CharacteristicNotify, CharacteristicNotifyMethod,
CharacteristicRead, Service, ServiceControl,
CharacteristicRead, CharacteristicWrite, CharacteristicWriteMethod, Service,
ServiceControl,
},
Adapter, Result, Uuid,
};
Expand All @@ -37,6 +38,7 @@ use tokio::sync::{
};

mod error;
mod rpc;
mod status;

const SERVICE_UUID: Uuid = Uuid::from_u128(0x00467768_6228_2272_4663_277478268000);
Expand Down Expand Up @@ -68,79 +70,60 @@ impl InnerState {
}

#[derive(Clone, Debug)]
pub struct State(Arc<RwLock<InnerState>>);

impl State {
pub fn new(state: InnerState) -> Self {
Self(Arc::new(RwLock::new(state)))
}
pub struct State<T> {
inner: Arc<RwLock<InnerState>>,
status_change_notifier: Sender<()>,
error_change_notifier: Sender<()>,
timeout: Option<Duration>,
handler: T,
}

pub async fn status(&self) -> Status {
self.0.read().await.status
impl<T> State<T>
where
T: WifiConfigurator,
{
async fn status(&self) -> Status {
self.inner.read().await.status
}

pub async fn last_error(&self) -> Option<Error> {
self.0.read().await.last_error
async fn last_error(&self) -> Option<Error> {
self.inner.read().await.last_error
}

pub async fn set_status(&self, new: Status) {
self.0.write().await.status = new;
async fn set_status(&self, new: Status) {
self.inner.write().await.status = new;
}

pub async fn set_last_error(&self, new: Option<Error>) {
self.0.write().await.last_error = new;
async fn set_last_error(&self, new: Option<Error>) {
self.inner.write().await.last_error = new;
}
}

#[derive(Debug)]
pub struct ImprovWifi<T> {
timeout: Option<Duration>,
state: State,
handler: T,
status_change_notifier: Sender<()>,
error_change_notifier: Sender<()>,
app: ApplicationHandle,
service: ServiceControl,
capabilities: CharacteristicControl,
current_state: CharacteristicControl,
error_state: CharacteristicControl,
rpc_command: CharacteristicControl,
rpc_result: CharacteristicControl,
}

pub trait WifiConfigurator {
fn can_authorize() -> bool;
fn can_identify() -> bool;
async fn provision(&mut self) -> std::result::Result<(), Error>;
}

impl<T: WifiConfigurator> ImprovWifi<T> {
async fn modify_status(&mut self, status: Status) {
self.state.set_status(status).await;
async fn modify_status(&self, status: Status) {
self.set_status(status).await;
self.status_change_notifier.send(()).ok();
// TODO: pro-actively write to the client???
}

pub async fn set_error(&mut self, error: Error) {
self.state.set_last_error(Some(error)).await;
pub async fn set_error(&self, error: Error) {
self.set_last_error(Some(error)).await;
self.error_change_notifier.send(()).ok();
// TODO: pro-actively write to the client???
}

pub async fn clear_error(&mut self) {
self.state.set_last_error(None).await;
pub async fn clear_error(&self) {
self.set_last_error(None).await;
self.error_change_notifier.send(()).ok();
// TODO: pro-actively write to the client???
}

pub async fn set_authorized(&mut self) {
if self.state.status().await == Status::AuthorizationRequired {
pub async fn set_authorized(&self) {
if self.status().await == Status::AuthorizationRequired {
self.modify_status(Status::Authorized).await;
}
}

pub async fn provision(&mut self) {
if self.state.status().await != Status::Authorized {
if self.status().await != Status::Authorized {
self.set_error(Error::NotAuthorized).await;
return;
}
Expand All @@ -158,19 +141,80 @@ impl<T: WifiConfigurator> ImprovWifi<T> {
self.modify_status(Status::Provisioned).await;
}

#[tracing::instrument(level = "trace", skip(self))]
pub async fn handle_raw_rpc(&self, value: Vec<u8>) {
if let Err(error) = Self::inner_handle_raw_rpc(&self, value).await {
self.set_error(error).await;
} else {
self.clear_error().await;
}
}

async fn inner_handle_raw_rpc(&self, value: Vec<u8>) -> std::result::Result<(), Error> {
let rpc = rpc::Rpc::parse(&value).map_err(|err| {
tracing::error!("Failed to parse RPC: {}", err);
Error::InvalidRPC
})?;

todo!()
}

pub fn set_timeout(&mut self, timeout: Duration) {
if T::can_authorize() {
self.timeout = Some(timeout);
}
}
}

#[derive(Debug)]
pub struct ImprovWifi<T> {
state: State<T>,
app: ApplicationHandle,
service: ServiceControl,
capabilities: CharacteristicControl,
current_state: CharacteristicControl,
error_state: CharacteristicControl,
rpc_command: CharacteristicControl,
rpc_result: CharacteristicControl,
}

pub trait WifiConfigurator: Clone + Send + Sync + 'static {
fn can_authorize() -> bool;
fn can_identify() -> bool;
async fn provision(&mut self) -> std::result::Result<(), Error>;
}

impl<T> ImprovWifi<T>
where
T: WifiConfigurator,
{
pub fn set_timeout(&mut self, timeout: Duration) {
self.state.set_timeout(timeout);
}

pub async fn install(adapter: &Adapter, handler: T) -> Result<Self> {
let initial_status = if T::can_authorize() {
Status::AuthorizationRequired
} else {
Status::Authorized
};

let state = State::new(InnerState {
status: initial_status,
last_error: None,
});
let (status_change_notifier, _) = broadcast_channel(2);
let (error_change_notifier, _) = broadcast_channel(2);
let state = State {
inner: Arc::new(RwLock::new(InnerState {
status: initial_status,
last_error: None,
})),
status_change_notifier: status_change_notifier.clone(),
error_change_notifier: error_change_notifier.clone(),
timeout: if initial_status == Status::AuthorizationRequired {
Some(Duration::from_secs(60))
} else {
None
},
handler,
};

let (service, service_handle) = service_control();
let (capabilities_control, capabilities_handle) = characteristic_control();
Expand Down Expand Up @@ -289,6 +333,22 @@ impl<T: WifiConfigurator> ImprovWifi<T> {
},
Characteristic {
uuid: CHARACTERISTIC_UUID_RPC_COMMAND,
write: Some(CharacteristicWrite {
write: true,
write_without_response: true,
method: CharacteristicWriteMethod::Fun(Box::new({
let state = state.clone();
move |value, _req| {
let state = state.clone();
Box::pin(async move {
// not sure if the bluer interface here will stitch writes together, let's ignore that for now
state.handle_raw_rpc(value).await;
Ok(())
})
}
})),
..Default::default()
}),
control_handle: rpc_command_handle,
..Default::default()
},
Expand All @@ -305,15 +365,7 @@ impl<T: WifiConfigurator> ImprovWifi<T> {
};

Ok(ImprovWifi {
timeout: if initial_status == Status::AuthorizationRequired {
Some(Duration::from_secs(60))
} else {
None
},
state,
handler,
status_change_notifier,
error_change_notifier,
app: adapter.serve_gatt_application(app).await?,
service,
capabilities: capabilities_control,
Expand All @@ -323,10 +375,4 @@ impl<T: WifiConfigurator> ImprovWifi<T> {
rpc_result: rpc_result_control,
})
}

pub fn set_timeout(&mut self, timeout: Duration) {
if T::can_authorize() {
self.timeout = Some(timeout);
}
}
}
57 changes: 57 additions & 0 deletions crates/improv-wifi/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use winnow::{
binary,
combinator::{alt, fail},
token::take,
PResult, Parser,
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Rpc<'data> {
pub command: Command,
pub data: &'data [u8],
}

impl<'a> Rpc<'a> {
fn inner_parse_data(input: &mut &'a [u8]) -> PResult<(&'a [u8], u8)> {
let length = binary::u8(input)?;
let data = take(length).parse_next(input)?;
let checksum = binary::u8(input)?;
Ok((data, checksum))
}

fn parse_next(input: &mut &'a [u8]) -> PResult<Self> {
let command = Command::parse(input)?;
let data = Self::inner_parse_data
.verify_map(|(data, checksum)| {
if checksum == data.iter().fold(0, |acc, &x| acc.wrapping_add(x)) {
Some(data)
} else {
None
}
})
.parse_next(input)?;

Ok(Self { command, data })
}

pub fn parse(input: &'a [u8]) -> Result<Self, String> {
Self::parse_next.parse(input).map_err(|e| e.to_string())
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Command {
SendWifiSettings = 0x01,
Identify = 0x02,
}

impl Command {
fn parse(input: &mut &[u8]) -> PResult<Self> {
alt((
|input: &mut &[u8]| 0x01.parse_next(input).map(|_| Self::SendWifiSettings),
|input: &mut &[u8]| 0x02.parse_next(input).map(|_| Self::Identify),
fail,
))
.parse_next(input)
}
}

0 comments on commit 564ffbc

Please sign in to comment.