ConnectionManager service stub
This commit is contained in:
parent
15b254c47c
commit
96eb5fec73
4 changed files with 170 additions and 5 deletions
|
|
@ -5,7 +5,7 @@ use axum::{
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
};
|
};
|
||||||
use http::{header::CONTENT_TYPE, StatusCode};
|
use http::header::CONTENT_TYPE;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -69,6 +69,17 @@ pub fn make_router(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let connection_manager_sub_handler = {
|
||||||
|
let state = state.clone();
|
||||||
|
move |request: axum::extract::Request| async move {
|
||||||
|
crate::services::connection_manager::subscribe_http_handler(
|
||||||
|
State(state.clone()),
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let app = axum::Router::new()
|
let app = axum::Router::new()
|
||||||
.route("/description.xml", get(description_xml))
|
.route("/description.xml", get(description_xml))
|
||||||
.route(
|
.route(
|
||||||
|
|
@ -85,15 +96,15 @@ pub fn make_router(
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/control/ConnectionManager",
|
"/control/ConnectionManager",
|
||||||
post(|| async { (StatusCode::NOT_IMPLEMENTED, "") }),
|
post(crate::services::connection_manager::http_handler),
|
||||||
)
|
)
|
||||||
.route_service(
|
.route_service(
|
||||||
"/subscribe/ContentDirectory",
|
"/subscribe/ContentDirectory",
|
||||||
content_dir_sub_handler.into_service(),
|
content_dir_sub_handler.into_service(),
|
||||||
)
|
)
|
||||||
.route(
|
.route_service(
|
||||||
"/subscribe/ConnectionManager",
|
"/subscribe/ConnectionManager",
|
||||||
post(|| async { (StatusCode::NOT_IMPLEMENTED, "") }),
|
connection_manager_sub_handler.into_service(),
|
||||||
)
|
)
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
use axum::{body::Bytes, extract::State, response::IntoResponse};
|
||||||
|
use bstr::BStr;
|
||||||
|
use http::{HeaderMap, StatusCode};
|
||||||
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
|
use crate::{state::UnpnServerState, subscriptions::SubscribeRequest};
|
||||||
|
|
||||||
|
pub const SOAP_ACTION_GET_PROTOCOL_INFO: &[u8] =
|
||||||
|
b"\"urn:schemas-upnp-org:service:ConnectionManager:1#GetProtocolInfo\"";
|
||||||
|
|
||||||
|
pub const SOAP_ACTION_CONNECTION_COMPLETE: &[u8] =
|
||||||
|
b"\"urn:schemas-upnp-org:service:ConnectionManager:1#ConnectionComplete\"";
|
||||||
|
|
||||||
|
pub const SOAP_ACTION_GET_CURRENT_CONNECTION_IDS: &[u8] =
|
||||||
|
b"\"urn:schemas-upnp-org:service:ConnectionManager:1#GetCurrentConnectionIDs\"";
|
||||||
|
|
||||||
|
pub const SOAP_ACTION_GET_CURRENT_CONNECTION_INFO: &[u8] =
|
||||||
|
b"\"urn:schemas-upnp-org:service:ConnectionManager:1#GetCurrentConnectionInfo\"";
|
||||||
|
|
||||||
|
pub const SOAP_ACTION_PREPARE_FOR_CONNECTION: &[u8] =
|
||||||
|
b"\"urn:schemas-upnp-org:service:ConnectionManager:1#PrepareForConnection\"";
|
||||||
|
|
||||||
|
pub(crate) async fn http_handler(
|
||||||
|
headers: HeaderMap,
|
||||||
|
State(_state): State<UnpnServerState>,
|
||||||
|
body: Bytes,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let body = BStr::new(&body);
|
||||||
|
let action = headers.get("soapaction").map(|v| BStr::new(v.as_bytes()));
|
||||||
|
trace!(?body, ?action, "received control request");
|
||||||
|
let action = match action {
|
||||||
|
Some(action) => action,
|
||||||
|
None => {
|
||||||
|
debug!("missing SOAPACTION header");
|
||||||
|
return (StatusCode::BAD_REQUEST, "").into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let not_implemented = StatusCode::NOT_IMPLEMENTED.into_response();
|
||||||
|
|
||||||
|
match action.as_ref() {
|
||||||
|
SOAP_ACTION_GET_PROTOCOL_INFO => not_implemented,
|
||||||
|
SOAP_ACTION_CONNECTION_COMPLETE => not_implemented,
|
||||||
|
SOAP_ACTION_GET_CURRENT_CONNECTION_INFO => not_implemented,
|
||||||
|
SOAP_ACTION_GET_CURRENT_CONNECTION_IDS => not_implemented,
|
||||||
|
SOAP_ACTION_PREPARE_FOR_CONNECTION => not_implemented,
|
||||||
|
_ => StatusCode::BAD_REQUEST.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn subscribe_http_handler(
|
||||||
|
State(state): State<UnpnServerState>,
|
||||||
|
request: axum::extract::Request,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let SubscribeRequest {
|
||||||
|
callback,
|
||||||
|
subscription_id,
|
||||||
|
timeout,
|
||||||
|
} = match SubscribeRequest::parse(request) {
|
||||||
|
Ok(sub) => sub,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(sid) = subscription_id {
|
||||||
|
match state.renew_connection_manager_subscription(&sid, timeout) {
|
||||||
|
Ok(()) => (
|
||||||
|
StatusCode::OK,
|
||||||
|
[
|
||||||
|
("SID", sid.to_owned()),
|
||||||
|
("TIMEOUT", format!("Second-{}", timeout.as_secs())),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
Err(e) => {
|
||||||
|
warn!(sid, error=?e, "error renewing subscription");
|
||||||
|
StatusCode::NOT_FOUND.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match state.new_connection_manager_subscription(callback, timeout) {
|
||||||
|
Ok(sid) => (
|
||||||
|
StatusCode::OK,
|
||||||
|
[
|
||||||
|
("SID", sid),
|
||||||
|
("TIMEOUT", format!("Second-{}", timeout.as_secs())),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
Err(e) => {
|
||||||
|
warn!(error=?e, "error creating subscription");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ pub struct UpnpServerStateInner {
|
||||||
pub(crate) provider: Box<dyn ContentDirectoryBrowseProvider>,
|
pub(crate) provider: Box<dyn ContentDirectoryBrowseProvider>,
|
||||||
pub(crate) system_update_id: AtomicU64,
|
pub(crate) system_update_id: AtomicU64,
|
||||||
pub(crate) content_directory_subscriptions: Subscriptions,
|
pub(crate) content_directory_subscriptions: Subscriptions,
|
||||||
|
pub(crate) connection_manager_subscriptions: Subscriptions,
|
||||||
|
|
||||||
pub(crate) span: Span,
|
pub(crate) span: Span,
|
||||||
pub(crate) system_update_bcast_tx: tokio::sync::broadcast::Sender<u64>,
|
pub(crate) system_update_bcast_tx: tokio::sync::broadcast::Sender<u64>,
|
||||||
|
|
@ -45,6 +46,7 @@ impl UpnpServerStateInner {
|
||||||
provider,
|
provider,
|
||||||
system_update_id: AtomicU64::new(new_system_update_id()?),
|
system_update_id: AtomicU64::new(new_system_update_id()?),
|
||||||
content_directory_subscriptions: Default::default(),
|
content_directory_subscriptions: Default::default(),
|
||||||
|
connection_manager_subscriptions: Default::default(),
|
||||||
system_update_bcast_tx: btx,
|
system_update_bcast_tx: btx,
|
||||||
_drop_guard: drop_guard,
|
_drop_guard: drop_guard,
|
||||||
span: span.clone(),
|
span: span.clone(),
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,15 @@ impl UpnpServerStateInner {
|
||||||
.update_timeout(sid, new_timeout)
|
.update_timeout(sid, new_timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn renew_connection_manager_subscription(
|
||||||
|
&self,
|
||||||
|
sid: &str,
|
||||||
|
new_timeout: Duration,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.content_directory_subscriptions
|
||||||
|
.update_timeout(sid, new_timeout)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_content_directory_subscription(
|
pub fn new_content_directory_subscription(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
url: url::Url,
|
url: url::Url,
|
||||||
|
|
@ -157,7 +166,55 @@ impl UpnpServerStateInner {
|
||||||
};
|
};
|
||||||
|
|
||||||
spawn_with_cancel(
|
spawn_with_cancel(
|
||||||
error_span!(parent: pspan, "subscription-manager", sid, %url),
|
error_span!(parent: pspan, "subscription-manager", sid, %url, service="ContentDirectory"),
|
||||||
|
token,
|
||||||
|
subscription_manager,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_connection_manager_subscription(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
url: url::Url,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> anyhow::Result<String> {
|
||||||
|
let (sid, refresh_notify) = self
|
||||||
|
.content_directory_subscriptions
|
||||||
|
.add(url.clone(), timeout);
|
||||||
|
let token = self.cancel_token.child_token();
|
||||||
|
|
||||||
|
// Spawn a task that will notify it of system id changes.
|
||||||
|
// Spawn a task that will wait for timeout or subscription refreshes.
|
||||||
|
// When it times out, kill all of them.
|
||||||
|
|
||||||
|
let pspan = self.span.clone();
|
||||||
|
let subscription_manager = {
|
||||||
|
let state = Arc::downgrade(self);
|
||||||
|
let sid = sid.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let timeout_notifier = async {
|
||||||
|
let mut timeout = timeout;
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = refresh_notify.notified() => {
|
||||||
|
timeout = state.upgrade().context("upnp server dead")?.connection_manager_subscriptions.get_timeout(&sid)?;
|
||||||
|
},
|
||||||
|
_ = tokio::time::sleep(timeout) => {
|
||||||
|
state.upgrade().context("upnp server dead")?.connection_manager_subscriptions.remove(&sid)?;
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.instrument(error_span!("timeout-killer"));
|
||||||
|
|
||||||
|
timeout_notifier.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
spawn_with_cancel(
|
||||||
|
error_span!(parent: pspan, "subscription-manager", sid, %url, service="ConnectionManager"),
|
||||||
token,
|
token,
|
||||||
subscription_manager,
|
subscription_manager,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue