bt_obex/server/
handler.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use async_trait::async_trait;
6
7use crate::header::{Header, HeaderSet};
8use crate::operation::ResponseCode;
9
10/// An operation can be rejected with a `ResponseCode` and optional headers describing the
11/// reason for rejection.w
12pub type ObexOperationError = (ResponseCode, HeaderSet);
13
14/// Helper function for creating an [ObexOperationError] with the specified [ResponseCode]
15/// and a [Header::Description] with the reason.
16pub fn new_operation_error(code: ResponseCode, reason: &str) -> ObexOperationError {
17    (code, HeaderSet::from_header(Header::Description(reason.into())))
18}
19
20pub type ObexResult<T> = Result<T, ObexOperationError>;
21
22/// An interface that implements the OBEX Server role.
23/// This interface roughly corresponds to the operations defined in OBEX v1.5.
24#[async_trait]
25pub trait ObexServerHandler {
26    /// A request to initiate the CONNECT operation.
27    /// `headers` are the informational headers provided by the remote OBEX client.
28    ///
29    /// Directed OBEX connections are automatically supported by the `ObexServer` and needn't be
30    /// handled in this response. Specifically, the `ConnectionIdentifier` is automatically
31    /// added and shouldn't be included here.
32    ///
33    /// Returns `Ok` with any response headers if the CONNECT request is accepted.
34    /// Returns `Err` with a rejection code and headers if the CONNECT request is rejected.
35    async fn connect(&mut self, headers: HeaderSet) -> ObexResult<HeaderSet>;
36
37    /// A request to disconnect the OBEX connection.
38    /// `headers` are the informational headers provided by the remote OBEX client.
39    /// Returns informational headers in response to the request.
40    async fn disconnect(&mut self, headers: HeaderSet) -> HeaderSet;
41
42    /// A request to set the current working folder on the device.
43    /// `headers` are the informational headers provided by the remote OBEX client.
44    /// If `backup` is `true`, then the remote requests to backup one directory before setting the
45    /// path.
46    /// If `create` is `true`, then the remote requests to create the path if it does not exist.
47    /// If `create` is `false` and the path doesn't exist, `Err` should be returned.
48    /// Returns `Ok` with any response headers if the SET_PATH request is accepted.
49    /// Returns `Err` with a rejection code and headers if the SET_PATH request is rejected.
50    async fn set_path(
51        &mut self,
52        headers: HeaderSet,
53        backup: bool,
54        create: bool,
55    ) -> ObexResult<HeaderSet>;
56
57    /// A request to get information about a data payload from the local OBEX server.
58    /// `headers` are the headers provided by the remote OBEX client that specify the desired
59    /// information.
60    /// Returns `Ok` with headers if accepted.
61    /// Returns `Err` with a rejection code and optional headers if rejected.
62    async fn get_info(&mut self, headers: HeaderSet) -> ObexResult<HeaderSet>;
63
64    /// A request to get data from the local OBEX server.
65    /// `headers` are the informational headers provided by the remote OBEX client that identify
66    /// the payload to be retrieved.
67    /// Returns `Ok` with the payload and optional informational headers if accepted.
68    /// Returns `Err` with a rejection code and optional headers if rejected.
69    async fn get_data(&mut self, headers: HeaderSet) -> ObexResult<(Vec<u8>, HeaderSet)>;
70
71    /// A request to put data in the local OBEX server.
72    /// `data` is the payload to be written.
73    /// `headers` are the informational headers provided by the remote OBEX client that describe
74    /// the payload.
75    /// Returns `Ok` if accepted.
76    /// Returns `Err` with a rejection code and optional headers if rejected.
77    async fn put(&mut self, data: Vec<u8>, headers: HeaderSet) -> ObexResult<()>;
78
79    /// A request to delete data in the local OBEX server.
80    /// `headers` are the informational headers provided by the remote OBEX client that describe
81    /// the delete request.
82    /// Returns `Ok` if accepted.
83    /// Returns `Err` with a rejection code and optional headers if rejected.
84    async fn delete(&mut self, headers: HeaderSet) -> ObexResult<()>;
85}
86
87#[cfg(test)]
88pub(crate) mod test_utils {
89    use super::*;
90
91    use fuchsia_sync::Mutex;
92    use std::sync::Arc;
93
94    #[derive(Default)]
95    struct TestApplicationProfileInner {
96        generic_response: Option<ObexResult<HeaderSet>>,
97        get_response: Option<Result<(Vec<u8>, HeaderSet), ObexOperationError>>,
98        put_response: Option<Result<(), ObexOperationError>>,
99        received_put_data: Option<(Vec<u8>, HeaderSet)>,
100    }
101
102    #[derive(Clone)]
103    pub(crate) struct TestApplicationProfile {
104        inner: Arc<Mutex<TestApplicationProfileInner>>,
105    }
106
107    impl TestApplicationProfile {
108        pub fn new() -> Self {
109            Self { inner: Arc::new(Mutex::new(Default::default())) }
110        }
111
112        pub fn set_response(&self, response: ObexResult<HeaderSet>) {
113            (*self.inner.lock()).generic_response = Some(response);
114        }
115
116        pub fn set_get_response(&self, response: (Vec<u8>, HeaderSet)) {
117            (*self.inner.lock()).get_response = Some(Ok(response));
118        }
119
120        pub fn set_put_response(&self, response: ObexResult<()>) {
121            (*self.inner.lock()).put_response = Some(response);
122        }
123
124        pub fn put_data(&self) -> (Vec<u8>, HeaderSet) {
125            (*self.inner.lock()).received_put_data.take().expect("expect data")
126        }
127    }
128
129    #[async_trait]
130    impl ObexServerHandler for TestApplicationProfile {
131        async fn connect(&mut self, _headers: HeaderSet) -> ObexResult<HeaderSet> {
132            // Defaults to rejecting with `MethodNotAllowed`.
133            self.inner
134                .lock()
135                .generic_response
136                .take()
137                .unwrap_or(Err((ResponseCode::MethodNotAllowed, HeaderSet::new())))
138        }
139
140        async fn disconnect(&mut self, _headers: HeaderSet) -> HeaderSet {
141            // Disconnect cannot be rejected so just take the response headers if they exist or
142            // default to returning an empty HeaderSet.
143            match self.inner.lock().generic_response.take() {
144                Some(Ok(headers)) => headers,
145                _ => HeaderSet::new(),
146            }
147        }
148
149        async fn set_path(
150            &mut self,
151            _headers: HeaderSet,
152            _backup: bool,
153            _create: bool,
154        ) -> ObexResult<HeaderSet> {
155            // Defaults to rejecting with `Forbidden`.
156            self.inner
157                .lock()
158                .generic_response
159                .take()
160                .unwrap_or(Err((ResponseCode::Forbidden, HeaderSet::new())))
161        }
162
163        async fn get_info(&mut self, _headers: HeaderSet) -> ObexResult<HeaderSet> {
164            self.inner
165                .lock()
166                .generic_response
167                .take()
168                .unwrap_or(Err((ResponseCode::NotFound, HeaderSet::new())))
169        }
170
171        async fn get_data(&mut self, _headers: HeaderSet) -> ObexResult<(Vec<u8>, HeaderSet)> {
172            self.inner
173                .lock()
174                .get_response
175                .take()
176                .unwrap_or(Err((ResponseCode::NotImplemented, HeaderSet::new())))
177        }
178
179        async fn put(&mut self, data: Vec<u8>, headers: HeaderSet) -> ObexResult<()> {
180            let mut inner = self.inner.lock();
181            inner.received_put_data = Some((data, headers));
182            inner
183                .put_response
184                .take()
185                .unwrap_or(Err((ResponseCode::NotImplemented, HeaderSet::new())))
186        }
187
188        async fn delete(&mut self, _headers: HeaderSet) -> ObexResult<()> {
189            self.inner
190                .lock()
191                .put_response
192                .take()
193                .unwrap_or(Err((ResponseCode::NotImplemented, HeaderSet::new())))
194        }
195    }
196}