Skip to main content

mock_igd/responder/
mod.rs

1//! Response generation for UPnP IGD actions.
2
3mod builder;
4mod templates;
5
6pub use builder::SuccessResponseBuilder;
7pub(crate) use templates::generate_soap_fault;
8use templates::generate_success_response;
9
10use crate::matcher::SoapRequest;
11use std::net::IpAddr;
12use std::sync::Arc;
13
14/// A responder that generates responses for matched requests.
15#[derive(Clone)]
16pub struct Responder {
17    inner: Arc<ResponderInner>,
18}
19
20enum ResponderInner {
21    Success(SuccessResponse),
22    Error { code: u16, description: String },
23    Custom(Arc<dyn Fn(&SoapRequest) -> ResponseBody + Send + Sync>),
24}
25
26/// The body of a response.
27#[derive(Debug, Clone)]
28pub enum ResponseBody {
29    /// A successful SOAP response.
30    Soap(String),
31    /// An error SOAP response.
32    SoapFault { code: u16, description: String },
33    /// A raw HTTP response body.
34    Raw { content_type: String, body: String },
35}
36
37/// Data for successful responses.
38#[derive(Debug, Clone, Default)]
39pub(crate) struct SuccessResponse {
40    // GetExternalIPAddress
41    pub(crate) external_ip: Option<IpAddr>,
42
43    // GetStatusInfo
44    pub(crate) connection_status: Option<String>,
45    pub(crate) last_connection_error: Option<String>,
46    pub(crate) uptime: Option<u32>,
47
48    // GetGenericPortMappingEntry / GetSpecificPortMappingEntry
49    pub(crate) remote_host: Option<String>,
50    pub(crate) external_port: Option<u16>,
51    pub(crate) protocol: Option<String>,
52    pub(crate) internal_port: Option<u16>,
53    pub(crate) internal_client: Option<String>,
54    pub(crate) enabled: Option<bool>,
55    pub(crate) description: Option<String>,
56    pub(crate) lease_duration: Option<u32>,
57
58    // GetCommonLinkProperties
59    pub(crate) wan_access_type: Option<String>,
60    pub(crate) layer1_upstream_max_bit_rate: Option<u32>,
61    pub(crate) layer1_downstream_max_bit_rate: Option<u32>,
62    pub(crate) physical_link_status: Option<String>,
63
64    // GetTotalBytesReceived / GetTotalBytesSent
65    pub(crate) total_bytes: Option<u64>,
66}
67
68impl Responder {
69    /// Create a successful response.
70    pub fn success() -> SuccessResponseBuilder {
71        SuccessResponseBuilder::default()
72    }
73
74    /// Create an error response with UPnP error code.
75    pub fn error(code: u16, description: impl Into<String>) -> Self {
76        Responder {
77            inner: Arc::new(ResponderInner::Error {
78                code,
79                description: description.into(),
80            }),
81        }
82    }
83
84    /// Create a custom responder with a closure.
85    pub fn custom<F>(f: F) -> Self
86    where
87        F: Fn(&SoapRequest) -> ResponseBody + Send + Sync + 'static,
88    {
89        Responder {
90            inner: Arc::new(ResponderInner::Custom(Arc::new(f))),
91        }
92    }
93
94    /// Generate a response for the given request.
95    pub fn respond(&self, request: &SoapRequest) -> ResponseBody {
96        match self.inner.as_ref() {
97            ResponderInner::Success(data) => {
98                let xml = generate_success_response(&request.action_name, data);
99                ResponseBody::Soap(xml)
100            }
101            ResponderInner::Error { code, description } => ResponseBody::SoapFault {
102                code: *code,
103                description: description.clone(),
104            },
105            ResponderInner::Custom(f) => f(request),
106        }
107    }
108}
109
110impl std::fmt::Debug for Responder {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self.inner.as_ref() {
113            ResponderInner::Success(data) => f.debug_tuple("Responder::Success").field(data).finish(),
114            ResponderInner::Error { code, description } => f
115                .debug_struct("Responder::Error")
116                .field("code", code)
117                .field("description", description)
118                .finish(),
119            ResponderInner::Custom(_) => f.debug_tuple("Responder::Custom").finish(),
120        }
121    }
122}