diff --git a/services/tunnelbroker/src/notifs/wns/error.rs b/services/tunnelbroker/src/notifs/wns/error.rs --- a/services/tunnelbroker/src/notifs/wns/error.rs +++ b/services/tunnelbroker/src/notifs/wns/error.rs @@ -12,4 +12,18 @@ ReadLock, #[display(fmt = "Failed to acquire write lock")] WriteLock, + #[display(fmt = "WNS Notification Error: {}", _0)] + WNSNotification(WNSNotificationError), + #[display(fmt = "Missing WNS ID")] + MissingWNSID, } + +#[derive(Debug, Display)] +pub enum WNSNotificationError { + #[display(fmt = "HTTP Error: {}", _0)] + Http(reqwest::Error), + #[display(fmt = "Unknown Error: {}", _0)] + Unknown(String), +} + +impl std::error::Error for WNSNotificationError {} diff --git a/services/tunnelbroker/src/notifs/wns/mod.rs b/services/tunnelbroker/src/notifs/wns/mod.rs --- a/services/tunnelbroker/src/notifs/wns/mod.rs +++ b/services/tunnelbroker/src/notifs/wns/mod.rs @@ -13,6 +13,12 @@ expires: SystemTime, } +#[derive(Debug, Clone)] +pub struct WNSNotif { + pub device_token: String, + pub payload: String, +} + #[derive(Clone)] pub struct WNSClient { http_client: reqwest::Client, @@ -30,9 +36,51 @@ }) } - pub async fn get_wns_token( - &mut self, - ) -> Result, error::Error> { + pub async fn send(&self, notif: WNSNotif) -> Result<(), error::Error> { + let token = self.get_wns_token().await?.ok_or( + error::WNSNotificationError::Unknown( + "Failed to get WNS token".to_string(), + ), + )?; + + let url = format!( + "https://dm3p.notify.windows.com/?token={}", + notif.device_token + ); + + // Send the notification + let response = self + .http_client + .post(&url) + .header("Content-Type", "application/octet-stream") + .header("X-WNS-Type", "wns/raw") + .bearer_auth(token) + .body(notif.payload) + .send() + .await?; + + if !response.status().is_success() { + return Err( + error::WNSNotificationError::Unknown( + response + .text() + .await + .unwrap_or_else(|_| "Unknown error".to_string()), + ) + .into(), + ); + } + + response + .headers() + .get("X-WNS-MSG-ID") + .and_then(|val| val.to_str().ok()) + .ok_or(error::Error::MissingWNSID)?; + + Ok(()) + } + + pub async fn get_wns_token(&self) -> Result, error::Error> { let expiry_window_in_secs = 10; { diff --git a/services/tunnelbroker/src/websockets/session.rs b/services/tunnelbroker/src/websockets/session.rs --- a/services/tunnelbroker/src/websockets/session.rs +++ b/services/tunnelbroker/src/websockets/session.rs @@ -34,6 +34,7 @@ AndroidConfig, AndroidMessagePriority, FCMMessage, }; use crate::notifs::web_push::WebPushNotif; +use crate::notifs::wns::WNSNotif; use crate::notifs::NotifClient; pub struct DeviceInfo { @@ -70,6 +71,7 @@ MissingAPNsClient, MissingFCMClient, MissingWebPushClient, + MissingWNSClient, MissingDeviceToken, } @@ -496,6 +498,43 @@ self.get_message_to_device_status(¬if.client_message_id, result), ) } + DeviceToTunnelbrokerMessage::WNSNotif(notif) => { + if !self.device_info.is_authenticated { + debug!( + "Unauthenticated device {} tried to send WNS notif. Aborting.", + self.device_info.device_id + ); + return Some(MessageSentStatus::Unauthenticated); + } + debug!("Received WNS notif for {}", notif.device_id); + + let Some(wns_client) = self.notif_client.wns.clone() else { + return Some(self.get_message_to_device_status( + ¬if.client_message_id, + Err(SessionError::MissingWNSClient), + )); + }; + + let device_token = match self.get_device_token(notif.device_id).await { + Ok(token) => token, + Err(e) => { + return Some( + self + .get_message_to_device_status(¬if.client_message_id, Err(e)), + ) + } + }; + + let wns_notif = WNSNotif { + device_token, + payload: notif.payload, + }; + + let result = wns_client.send(wns_notif).await; + Some( + self.get_message_to_device_status(¬if.client_message_id, result), + ) + } _ => { error!("Client sent invalid message type"); Some(MessageSentStatus::InvalidRequest) diff --git a/shared/tunnelbroker_messages/src/messages/mod.rs b/shared/tunnelbroker_messages/src/messages/mod.rs --- a/shared/tunnelbroker_messages/src/messages/mod.rs +++ b/shared/tunnelbroker_messages/src/messages/mod.rs @@ -44,6 +44,7 @@ APNsNotif(APNsNotif), FCMNotif(FCMNotif), WebPushNotif(WebPushNotif), + WNSNotif(WNSNotif), MessageToDeviceRequest(MessageToDeviceRequest), MessageReceiveConfirmation(MessageReceiveConfirmation), MessageToTunnelbrokerRequest(MessageToTunnelbrokerRequest), diff --git a/shared/tunnelbroker_messages/src/messages/notif.rs b/shared/tunnelbroker_messages/src/messages/notif.rs --- a/shared/tunnelbroker_messages/src/messages/notif.rs +++ b/shared/tunnelbroker_messages/src/messages/notif.rs @@ -35,3 +35,14 @@ pub device_id: String, pub payload: String, } + +/// WNS notif built on client. +#[derive(Serialize, Deserialize, TagAwareDeserialize, PartialEq, Debug)] +#[serde(tag = "type", remote = "Self", rename_all = "camelCase")] +pub struct WNSNotif { + #[serde(rename = "clientMessageID")] + pub client_message_id: String, + #[serde(rename = "deviceID")] + pub device_id: String, + pub payload: String, +}