1use bytes::Bytes;
2use thiserror::Error;
3
4use crate::{
5 headers::Header,
6 packet::{OpCode, Packet, PacketError, PacketExtra},
7};
8
9const OBEX_VERSION: u8 = 0x10;
10const OBEX_FLAGS: u8 = 0x00;
11const OBEX_MAX_PACKET: u16 = 0xFFFF;
12
13const SETPATH_NAVIGATE: u8 = 0x02;
15const SETPATH_BACKUP: u8 = 0x03;
17
18#[derive(Debug, Error)]
20pub enum ObexError {
21 #[error("not connected")]
23 NotConnected,
24 #[error("packet error: {0}")]
26 Packet(#[from] PacketError),
27 #[error("connect rejected with opcode {0:#04x}")]
29 ConnectRejected(u8),
30 #[error("connect response missing ConnectionId header")]
32 MissingConnectionId,
33 #[error("message body too large")]
35 BodyTooLarge,
36}
37
38enum State {
39 Disconnected,
40 Connected { conn_id: u32, max_packet: u16 },
41}
42
43pub struct ObexClient {
45 state: State,
46}
47
48impl ObexClient {
49 #[must_use]
51 pub const fn new() -> Self {
52 Self { state: State::Disconnected }
53 }
54
55 pub fn connect_request(
62 target_uuid: &[u8; 16],
63 app_params: Option<Bytes>,
64 ) -> Result<Bytes, ObexError> {
65 let mut headers = vec![Header::Target(Bytes::copy_from_slice(target_uuid))];
66 if let Some(params) = app_params {
67 headers.push(Header::AppParams(params));
68 }
69 Ok(Packet {
70 opcode: OpCode::Connect,
71 extra: PacketExtra::Connect {
72 version: OBEX_VERSION,
73 flags: OBEX_FLAGS,
74 max_packet: OBEX_MAX_PACKET,
75 },
76 headers,
77 }
78 .encode()?)
79 }
80
81 pub fn handle_connect_response(&mut self, data: &Bytes) -> Result<u32, ObexError> {
87 let packet = Packet::decode_connect_response(data)?;
88 if !packet.opcode.is_ok() {
89 return Err(ObexError::ConnectRejected(packet.opcode.to_byte()));
90 }
91 let conn_id = packet.header_connection_id().ok_or(ObexError::MissingConnectionId)?;
92 let max_packet = match &packet.extra {
93 PacketExtra::Connect { max_packet, .. } => *max_packet,
94 _ => OBEX_MAX_PACKET,
95 };
96 self.state = State::Connected { conn_id, max_packet };
97 Ok(conn_id)
98 }
99
100 pub fn setpath_request(&self, name: &str) -> Result<Bytes, ObexError> {
105 let conn_id = self.conn_id()?;
106 Ok(Packet {
107 opcode: OpCode::SetPath,
108 extra: PacketExtra::SetPath { flags: SETPATH_NAVIGATE, constants: 0x00 },
109 headers: vec![Header::ConnectionId(conn_id), Header::Name(name.to_owned())],
110 }
111 .encode()?)
112 }
113
114 pub fn setpath_backup_request(&self) -> Result<Bytes, ObexError> {
120 let conn_id = self.conn_id()?;
121 Ok(Packet {
122 opcode: OpCode::SetPath,
123 extra: PacketExtra::SetPath { flags: SETPATH_BACKUP, constants: 0x00 },
124 headers: vec![Header::ConnectionId(conn_id), Header::Name(String::new())],
125 }
126 .encode()?)
127 }
128
129 pub fn get_request(
134 &self,
135 type_: &[u8],
136 name: Option<&str>,
137 app_params: Option<Bytes>,
138 ) -> Result<Bytes, ObexError> {
139 let conn_id = self.conn_id()?;
140 let mut headers =
141 vec![Header::ConnectionId(conn_id), Header::Type(Bytes::copy_from_slice(type_))];
142 if let Some(n) = name {
143 headers.push(Header::Name(n.to_owned()));
144 }
145 if let Some(params) = app_params {
146 headers.push(Header::AppParams(params));
147 }
148 Ok(Packet { opcode: OpCode::GetFinal, extra: PacketExtra::None, headers }.encode()?)
149 }
150
151 pub fn get_continue_request(&self) -> Result<Bytes, ObexError> {
156 let conn_id = self.conn_id()?;
157 Ok(Packet {
158 opcode: OpCode::GetFinal,
159 extra: PacketExtra::None,
160 headers: vec![Header::ConnectionId(conn_id)],
161 }
162 .encode()?)
163 }
164
165 pub fn put_final_request(
172 &self,
173 type_: &[u8],
174 extra_headers: Vec<Header>,
175 ) -> Result<Bytes, ObexError> {
176 let conn_id = self.conn_id()?;
177 let mut headers =
178 vec![Header::ConnectionId(conn_id), Header::Type(Bytes::copy_from_slice(type_))];
179 headers.extend(extra_headers);
180 Ok(Packet { opcode: OpCode::PutFinal, extra: PacketExtra::None, headers }.encode()?)
181 }
182
183 pub fn disconnect_request(&self) -> Result<Bytes, ObexError> {
188 let conn_id = self.conn_id()?;
189 Ok(Packet {
190 opcode: OpCode::Disconnect,
191 extra: PacketExtra::None,
192 headers: vec![Header::ConnectionId(conn_id)],
193 }
194 .encode()?)
195 }
196
197 pub fn parse_response(data: &Bytes) -> Result<Packet, ObexError> {
202 Ok(Packet::decode(data)?)
203 }
204
205 pub const fn conn_id(&self) -> Result<u32, ObexError> {
211 match &self.state {
212 State::Connected { conn_id, .. } => Ok(*conn_id),
213 State::Disconnected => Err(ObexError::NotConnected),
214 }
215 }
216
217 #[must_use]
219 pub const fn is_connected(&self) -> bool {
220 matches!(self.state, State::Connected { .. })
221 }
222
223 #[must_use]
225 pub const fn max_packet(&self) -> u16 {
226 match &self.state {
227 State::Connected { max_packet, .. } => *max_packet,
228 State::Disconnected => 0,
229 }
230 }
231}
232
233impl Default for ObexClient {
234 fn default() -> Self {
235 Self::new()
236 }
237}