bt_obex/server/
put.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 log::trace;
6
7use crate::error::Error;
8use crate::header::{Header, HeaderSet, SingleResponseMode};
9use crate::operation::{OpCode, RequestPacket, ResponseCode, ResponsePacket};
10use crate::server::handler::ObexOperationError;
11use crate::server::{ApplicationResponse, OperationRequest, ServerOperation};
12
13/// The current state of the PUT operation.
14#[derive(Debug)]
15enum State {
16    /// Receiving informational headers and data packets.
17    Request { headers: HeaderSet, staged_data: Option<Vec<u8>> },
18    /// The final request packet has been received.
19    RequestPhaseComplete,
20    /// The operation is complete.
21    Complete,
22}
23
24/// An in-progress PUT operation.
25pub struct PutOperation {
26    /// Whether SRM is locally supported.
27    srm_supported: bool,
28    /// The current SRM status for this operation. This is None if it has not been negotiated
29    /// and Some<T> when negotiated.
30    /// Defaults to disabled if never negotiated.
31    srm: Option<SingleResponseMode>,
32    state: State,
33}
34
35impl PutOperation {
36    pub fn new(srm_supported: bool) -> Self {
37        Self {
38            srm_supported,
39            srm: None,
40            state: State::Request { headers: HeaderSet::new(), staged_data: None },
41        }
42    }
43
44    #[cfg(test)]
45    fn new_at_state(state: State) -> Self {
46        Self { srm_supported: false, srm: None, state }
47    }
48}
49
50impl ServerOperation for PutOperation {
51    fn srm_status(&self) -> SingleResponseMode {
52        self.srm.unwrap_or(SingleResponseMode::Disable)
53    }
54
55    fn is_complete(&self) -> bool {
56        matches!(self.state, State::Complete)
57    }
58
59    fn handle_peer_request(&mut self, request: RequestPacket) -> Result<OperationRequest, Error> {
60        let code = *request.code();
61        let mut request_headers = HeaderSet::from(request);
62        match &mut self.state {
63            State::Request { ref mut headers, ref mut staged_data } if code == OpCode::Put => {
64                // A non-final PUT request may contain a Body header specifying user data (among
65                // other informational headers).
66                if let Ok(mut data) = request_headers.remove_body(/*final= */ false) {
67                    let staged = staged_data.get_or_insert(Vec::new());
68                    staged.append(&mut data);
69                }
70                headers.try_append(request_headers)?;
71
72                // The response to the `request` depends on the current SRM status.
73                // If SRM is enabled, then no response is needed.
74                // If SRM is disabled, then we must acknowledge the `request`.
75                // If SRM hasn't been negotiated yet, we check to see if the peer requests it
76                // and reply with the negotiated SRM header.
77                let response_headers = match self.srm {
78                    Some(SingleResponseMode::Enable) => return Ok(OperationRequest::None),
79                    Some(SingleResponseMode::Disable) => HeaderSet::new(),
80                    None => {
81                        self.srm = Self::check_headers_for_srm(self.srm_supported, &headers);
82                        // If SRM was just negotiated then we need to include it in the response.
83                        self.srm.as_ref().map_or_else(HeaderSet::new, |srm| {
84                            HeaderSet::from_header(Header::SingleResponseMode(*srm))
85                        })
86                    }
87                };
88                let response =
89                    ResponsePacket::new_no_data(ResponseCode::Continue, response_headers);
90                Ok(OperationRequest::SendPackets(vec![response]))
91            }
92            State::Request { ref mut headers, ref mut staged_data } if code == OpCode::PutFinal => {
93                // A final PUT request may contain an EndOfBody header specifying user data (among
94                // other informational headers).
95                if let Ok(mut data) = request_headers.remove_body(/*final= */ true) {
96                    let staged = staged_data.get_or_insert(Vec::new());
97                    staged.append(&mut data);
98                }
99                headers.try_append(request_headers)?;
100                let request_headers = std::mem::replace(headers, HeaderSet::new());
101                let request_data = std::mem::take(staged_data);
102                self.state = State::RequestPhaseComplete;
103                // If data is provided, then we are placing data in the application. Otherwise, we
104                // are deleting data identified by the provided `request_headers`.
105                // See OBEX 1.5 Section 3.4.3.6.
106                if let Some(data) = request_data {
107                    Ok(OperationRequest::PutApplicationData(data, request_headers))
108                } else {
109                    Ok(OperationRequest::DeleteApplicationData(request_headers))
110                }
111            }
112            _ => Err(Error::operation(OpCode::Put, "received invalid request")),
113        }
114    }
115
116    fn handle_application_response(
117        &mut self,
118        response: Result<ApplicationResponse, ObexOperationError>,
119    ) -> Result<Vec<ResponsePacket>, Error> {
120        // Only expect a response when all request packets have been received.
121        if !matches!(self.state, State::RequestPhaseComplete) {
122            return Err(Error::operation(OpCode::Put, "invalid state"));
123        }
124
125        let response = match response {
126            Ok(ApplicationResponse::Put) => {
127                ResponsePacket::new_no_data(ResponseCode::Ok, HeaderSet::new())
128            }
129            Err((code, response_headers)) => {
130                trace!("Application rejected PUT request: {code:?}");
131                ResponsePacket::new_no_data(code, response_headers)
132            }
133            _ => {
134                return Err(Error::operation(
135                    OpCode::Put,
136                    "invalid application response to PUT request",
137                ));
138            }
139        };
140        self.state = State::Complete;
141        Ok(vec![response])
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    use assert_matches::assert_matches;
150
151    use crate::header::HeaderIdentifier;
152    use crate::server::test_utils::expect_single_packet;
153
154    #[fuchsia::test]
155    fn single_stage_put_success() {
156        let mut operation = PutOperation::new(/*srm_supported=*/ false);
157        assert!(!operation.is_complete());
158
159        let body = (1..10).collect::<Vec<u8>>();
160        let eob = Header::EndOfBody(body.clone());
161        let name = Header::name("foo".into());
162        let type_ = Header::Type("text".into());
163        let headers = HeaderSet::from_headers(vec![eob, name, type_]).unwrap();
164        let request = RequestPacket::new_put_final(headers);
165        let response = operation.handle_peer_request(request).expect("valid request");
166        assert_matches!(response,
167            OperationRequest::PutApplicationData(data, headers)
168            if headers.contains_header(&HeaderIdentifier::Name)
169            && headers.contains_header(&HeaderIdentifier::Type)
170            && data == body
171        );
172        assert!(!operation.is_complete());
173
174        // Application accepts.
175        let responses = operation
176            .handle_application_response(ApplicationResponse::accept_put())
177            .expect("valid response");
178        assert_eq!(*responses[0].code(), ResponseCode::Ok);
179        assert!(operation.is_complete());
180    }
181
182    #[fuchsia::test]
183    fn multi_packet_put() {
184        let mut operation = PutOperation::new(/*srm_supported=*/ false);
185        assert!(!operation.is_complete());
186
187        // First request to provide informational headers describing the PUT payload. We will ack
188        // with an empty Continue packet.
189        let headers1 = HeaderSet::from_header(Header::name("random file".into()));
190        let request1 = RequestPacket::new_put(headers1);
191        let response1 = operation.handle_peer_request(request1).expect("valid request");
192        let response_packet1 = expect_single_packet(response1);
193        assert_eq!(*response_packet1.code(), ResponseCode::Continue);
194        assert!(!operation.is_complete());
195
196        // Second request just contains a part of the payload. We will ack with an empty Continue
197        // packet.
198        let body2 = (0..50).collect::<Vec<u8>>();
199        let headers2 = HeaderSet::from_header(Header::Body(body2));
200        let request2 = RequestPacket::new_put(headers2);
201        let response2 = operation.handle_peer_request(request2).expect("valid request");
202        let response_packet2 = expect_single_packet(response2);
203        assert_eq!(*response_packet2.code(), ResponseCode::Continue);
204        assert!(!operation.is_complete());
205
206        // Third and final request contains the remaining payload. We will ask the application to
207        // accept or reject with the complete reassembled data payload.
208        let body3 = (50..100).collect::<Vec<u8>>();
209        let headers3 = HeaderSet::from_header(Header::EndOfBody(body3));
210        let request3 = RequestPacket::new_put_final(headers3);
211        let response3 = operation.handle_peer_request(request3).expect("valid request");
212        let expected_payload = (0..100).collect::<Vec<u8>>();
213        assert_matches!(response3,
214            OperationRequest::PutApplicationData(data, headers)
215            if headers.contains_header(&HeaderIdentifier::Name)
216            && data == expected_payload
217        );
218        assert!(!operation.is_complete());
219
220        // Simulate application accepting - expect a single outgoing packet acknowledging the PUT.
221        let responses4 = operation
222            .handle_application_response(ApplicationResponse::accept_put())
223            .expect("valid response");
224        assert_eq!(responses4.len(), 1);
225        assert_eq!(*responses4[0].code(), ResponseCode::Ok);
226        assert!(operation.is_complete());
227    }
228
229    #[fuchsia::test]
230    fn multi_packet_put_with_duplicate_headers_is_ok() {
231        let mut operation = PutOperation::new(/*srm_supported=*/ false);
232
233        // Peer's first request provides informational headers about the payload.
234        let type_header = Header::Type("foo".into());
235        let headers1 =
236            HeaderSet::from_headers(vec![Header::name("random file".into()), type_header.clone()])
237                .unwrap();
238        let request1 = RequestPacket::new_put(headers1);
239        let response1 = operation.handle_peer_request(request1).expect("valid request");
240        let response_packet1 = expect_single_packet(response1);
241        assert_eq!(*response_packet1.code(), ResponseCode::Continue);
242        assert!(!operation.is_complete());
243
244        // Peer's second and final request contains a duplicate informational header & payload.
245        let body = (0..10).collect::<Vec<u8>>();
246        let headers2 =
247            HeaderSet::from_headers(vec![type_header, Header::EndOfBody(body.clone())]).unwrap();
248        let request2 = RequestPacket::new_put_final(headers2);
249        let response2 = operation.handle_peer_request(request2).expect("valid request");
250        assert_matches!(response2,
251            OperationRequest::PutApplicationData(data, headers)
252            if headers.contains_header(&HeaderIdentifier::Name) && headers.contains_header(&HeaderIdentifier::Type)
253            && data == body
254        );
255        assert!(!operation.is_complete());
256
257        // Simulate application accepting - expect a single outgoing packet acknowledging the PUT.
258        let responses3 = operation
259            .handle_application_response(ApplicationResponse::accept_put())
260            .expect("valid response");
261        assert_eq!(responses3.len(), 1);
262        assert_eq!(*responses3[0].code(), ResponseCode::Ok);
263        assert!(operation.is_complete());
264    }
265
266    #[fuchsia::test]
267    fn multi_packet_put_srm_enabled() {
268        let mut operation = PutOperation::new(/*srm_supported=*/ true);
269        assert!(!operation.is_complete());
270        assert_eq!(operation.srm_status(), SingleResponseMode::Disable);
271
272        // First request provides informational headers & SRM enable request. Expect to reply
273        // positively and enable SRM.
274        let headers1 = HeaderSet::from_headers(vec![
275            Header::name("random file".into()),
276            SingleResponseMode::Enable.into(),
277        ])
278        .unwrap();
279        let request1 = RequestPacket::new_put(headers1);
280        let response1 = operation.handle_peer_request(request1).expect("valid request");
281        let response_packet1 = expect_single_packet(response1);
282        assert_eq!(*response_packet1.code(), ResponseCode::Continue);
283        let received_srm = response_packet1
284            .headers()
285            .get(&HeaderIdentifier::SingleResponseMode)
286            .expect("contains SRM header");
287        assert_eq!(*received_srm, Header::SingleResponseMode(SingleResponseMode::Enable));
288        assert_eq!(operation.srm_status(), SingleResponseMode::Enable);
289        assert!(!operation.is_complete());
290
291        // Second request just contains a part of the payload - since SRM is enabled, no ack.
292        let body2 = (0..50).collect::<Vec<u8>>();
293        let request2 = RequestPacket::new_put(HeaderSet::from_header(Header::Body(body2)));
294        let response2 = operation.handle_peer_request(request2).expect("valid request");
295        assert_matches!(response2, OperationRequest::None);
296        assert!(!operation.is_complete());
297
298        // Third and final request contains the remaining payload. We will ask the application to
299        // accept or reject with the complete reassembled data payload.
300        let body3 = (50..100).collect::<Vec<u8>>();
301        let request3 =
302            RequestPacket::new_put_final(HeaderSet::from_header(Header::EndOfBody(body3)));
303        let response3 = operation.handle_peer_request(request3).expect("valid request");
304        let expected_payload = (0..100).collect::<Vec<u8>>();
305        assert_matches!(response3,
306            OperationRequest::PutApplicationData(data, headers)
307            if headers.contains_header(&HeaderIdentifier::Name)
308            && data == expected_payload
309        );
310        assert!(!operation.is_complete());
311
312        // Simulate application accepting - expect a single outbound packet acknowledging the PUT.
313        let responses4 = operation
314            .handle_application_response(ApplicationResponse::accept_put())
315            .expect("valid response");
316        assert_eq!(responses4.len(), 1);
317        assert_eq!(*responses4[0].code(), ResponseCode::Ok);
318        assert!(operation.is_complete());
319    }
320
321    #[fuchsia::test]
322    fn application_reject_is_ok() {
323        let mut operation = PutOperation::new_at_state(State::RequestPhaseComplete);
324
325        let headers = HeaderSet::from_header(Header::Description("not allowed".into()));
326        let reject = Err((ResponseCode::Forbidden, headers));
327        let response_packets =
328            operation.handle_application_response(reject).expect("valid response");
329        assert_eq!(*response_packets[0].code(), ResponseCode::Forbidden);
330        assert!(response_packets[0].headers().contains_header(&HeaderIdentifier::Description));
331        assert!(operation.is_complete());
332    }
333
334    #[fuchsia::test]
335    fn non_put_request_is_error() {
336        let mut operation = PutOperation::new(/*srm_supported=*/ false);
337        let random_request1 = RequestPacket::new_get(HeaderSet::new());
338        assert_matches!(
339            operation.handle_peer_request(random_request1),
340            Err(Error::OperationError { .. })
341        );
342
343        let random_request2 = RequestPacket::new_disconnect(HeaderSet::new());
344        assert_matches!(
345            operation.handle_peer_request(random_request2),
346            Err(Error::OperationError { .. })
347        );
348    }
349
350    #[fuchsia::test]
351    fn invalid_application_response_is_error() {
352        // Receiving a response before the request phase is complete is an Error.
353        let mut operation = PutOperation::new(/*srm_supported=*/ false);
354        assert_matches!(
355            operation.handle_application_response(ApplicationResponse::accept_put()),
356            Err(Error::OperationError { .. })
357        );
358
359        // We don't expect a GET response from the application.
360        let mut operation = PutOperation::new_at_state(State::RequestPhaseComplete);
361        let invalid = ApplicationResponse::accept_get(vec![], HeaderSet::new());
362        assert_matches!(
363            operation.handle_application_response(invalid),
364            Err(Error::OperationError { .. })
365        );
366    }
367
368    #[fuchsia::test]
369    fn delete_request_success() {
370        let mut operation = PutOperation::new(/*srm_supported=*/ false);
371
372        let headers = HeaderSet::from_header(Header::name("randomfile.txt"));
373        let request = RequestPacket::new_put_final(headers);
374        let response = operation.handle_peer_request(request).expect("valid request");
375        assert_matches!(response, OperationRequest::DeleteApplicationData(headers) if headers.contains_header(&HeaderIdentifier::Name));
376        assert!(!operation.is_complete());
377
378        // Application accepts delete request.
379        let final_responses = operation
380            .handle_application_response(ApplicationResponse::accept_put())
381            .expect("valid application response");
382        let final_response = final_responses.first().expect("one response");
383        assert_eq!(*final_response.code(), ResponseCode::Ok);
384        assert!(final_response.headers().is_empty());
385        assert!(operation.is_complete());
386    }
387}