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}