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