Skip to main content

network_device/
connector.rs

1//! Static construction and authentication boundary for device drivers.
2
3use crate::Driver;
4use std::error::Error;
5use std::future::Future;
6
7/// Constructs and authenticates a provider-specific device driver.
8///
9/// A connector owns the protocol-specific connection process. The caller
10/// supplies authentication material it has already resolved; the connector
11/// never fetches credentials from a vault or another credential store.
12///
13/// Each provider chooses its own target, authentication, driver, and
14/// connection error types. The returned driver remains statically typed.
15///
16/// # Example
17///
18/// ```
19/// use network_device::{Driver, DriverConnector};
20/// use std::convert::Infallible;
21/// use std::future::Future;
22///
23/// struct Target { address: String }
24/// struct Authentication { username: String, password: String }
25/// struct DeviceDriver;
26/// struct Connector;
27///
28/// impl Driver for DeviceDriver {
29///     type Error = Infallible;
30/// }
31///
32/// impl DriverConnector for Connector {
33///     type Driver = DeviceDriver;
34///     type Target = Target;
35///     type Authentication = Authentication;
36///     type Error = Infallible;
37///
38///     fn connect<'a>(
39///         &'a self,
40///         target: &'a Self::Target,
41///         authentication: Self::Authentication,
42///     ) -> impl Future<Output = Result<Self::Driver, Self::Error>> + Send + 'a {
43///         async move {
44///             let _endpoint = &target.address;
45///             let _username = authentication.username;
46///             let _password = authentication.password;
47///             Ok(DeviceDriver)
48///         }
49///     }
50/// }
51/// ```
52pub trait DriverConnector: Send + Sync {
53    /// Concrete driver returned after a successful connection.
54    type Driver: Driver;
55    /// Provider-defined information needed to locate the device.
56    type Target: Sync;
57    /// Provider-defined, already-resolved authentication material.
58    type Authentication: Send + 'static;
59    /// Error returned while connecting or authenticating.
60    type Error: Error + Send + Sync + 'static;
61
62    /// Connects to the target and authenticates using the supplied material.
63    ///
64    /// Authentication is passed by value so implementations can consume
65    /// secrets without requiring a clone or shared ownership wrapper.
66    fn connect<'a>(
67        &'a self,
68        target: &'a Self::Target,
69        authentication: Self::Authentication,
70    ) -> impl Future<Output = Result<Self::Driver, Self::Error>> + Send + 'a;
71}
72
73#[cfg(test)]
74mod tests {
75    use super::DriverConnector;
76    use crate::{
77        AdministrativeState, Driver, Execute, InterfaceName, Run, SetInterfaceState, operation,
78    };
79    use std::convert::Infallible;
80    use std::future::Future;
81    use std::pin::pin;
82    use std::task::{Context, Poll, Waker};
83
84    fn block_on<F: Future>(future: F) -> F::Output {
85        let mut future = pin!(future);
86        let mut context = Context::from_waker(Waker::noop());
87        match future.as_mut().poll(&mut context) {
88            Poll::Ready(output) => output,
89            Poll::Pending => panic!("test future unexpectedly yielded"),
90        }
91    }
92
93    struct DeviceTarget(&'static str);
94
95    struct DeviceAuthentication {
96        username: String,
97        password: String,
98    }
99
100    struct DeviceDriver {
101        endpoint: String,
102        authenticated_user: String,
103    }
104
105    impl Driver for DeviceDriver {
106        type Error = Infallible;
107    }
108
109    struct TestConnector;
110
111    impl DriverConnector for TestConnector {
112        type Driver = DeviceDriver;
113        type Target = DeviceTarget;
114        type Authentication = DeviceAuthentication;
115        type Error = Infallible;
116
117        #[allow(clippy::manual_async_fn)]
118        fn connect<'a>(
119            &'a self,
120            target: &'a Self::Target,
121            authentication: Self::Authentication,
122        ) -> impl Future<Output = Result<Self::Driver, Self::Error>> + Send + 'a {
123            async move {
124                let DeviceAuthentication { username, password } = authentication;
125                assert_eq!(password, "consumed-secret");
126                Ok(DeviceDriver {
127                    endpoint: target.0.to_owned(),
128                    authenticated_user: username,
129                })
130            }
131        }
132    }
133
134    impl Execute<SetInterfaceState> for DeviceDriver {
135        type Output = String;
136
137        #[allow(clippy::manual_async_fn)]
138        fn execute<'a>(
139            &'a self,
140            operation: SetInterfaceState,
141        ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + 'a {
142            async move {
143                let state = match operation.state {
144                    AdministrativeState::Enabled => "enabled",
145                    AdministrativeState::Disabled => "disabled",
146                };
147                Ok(format!(
148                    "{}:{}:{}:{}",
149                    self.endpoint, self.authenticated_user, operation.interface, state
150                ))
151            }
152        }
153    }
154
155    #[test]
156    fn connector_consumes_authentication_and_created_driver_runs_operation() {
157        let connector = TestConnector;
158        let target = DeviceTarget("device.example");
159        let authentication = DeviceAuthentication {
160            username: String::from("operator"),
161            password: String::from("consumed-secret"),
162        };
163        let driver = block_on(connector.connect(&target, authentication)).unwrap();
164        let interface = InterfaceName::try_from("Ethernet1/1").unwrap();
165
166        let result = block_on(
167            operation(SetInterfaceState::new(
168                interface,
169                AdministrativeState::Enabled,
170            ))
171            .run(&driver),
172        )
173        .unwrap();
174
175        assert_eq!(result, "device.example:operator:Ethernet1/1:enabled");
176    }
177}