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(target_uuid: &[u8; 16]) -> Result<Bytes, ObexError> {
60 Ok(Packet {
61 opcode: OpCode::Connect,
62 extra: PacketExtra::Connect {
63 version: OBEX_VERSION,
64 flags: OBEX_FLAGS,
65 max_packet: OBEX_MAX_PACKET,
66 },
67 headers: vec![Header::Target(Bytes::copy_from_slice(target_uuid))],
68 }
69 .encode()?)
70 }
71
72 pub fn handle_connect_response(&mut self, data: &[u8]) -> Result<u32, ObexError> {
78 let packet = Packet::decode_connect_response(data)?;
79 if !packet.opcode.is_ok() {
80 return Err(ObexError::ConnectRejected(packet.opcode.to_byte()));
81 }
82 let conn_id = packet.header_connection_id().ok_or(ObexError::MissingConnectionId)?;
83 let max_packet = match &packet.extra {
84 PacketExtra::Connect { max_packet, .. } => *max_packet,
85 _ => OBEX_MAX_PACKET,
86 };
87 self.state = State::Connected { conn_id, max_packet };
88 Ok(conn_id)
89 }
90
91 pub fn setpath_request(&self, name: &str) -> Result<Bytes, ObexError> {
96 let conn_id = self.conn_id()?;
97 Ok(Packet {
98 opcode: OpCode::SetPath,
99 extra: PacketExtra::SetPath { flags: SETPATH_NAVIGATE, constants: 0x00 },
100 headers: vec![Header::ConnectionId(conn_id), Header::Name(name.to_owned())],
101 }
102 .encode()?)
103 }
104
105 pub fn setpath_backup_request(&self) -> Result<Bytes, ObexError> {
111 let conn_id = self.conn_id()?;
112 Ok(Packet {
113 opcode: OpCode::SetPath,
114 extra: PacketExtra::SetPath { flags: SETPATH_BACKUP, constants: 0x00 },
115 headers: vec![Header::ConnectionId(conn_id), Header::Name(String::new())],
116 }
117 .encode()?)
118 }
119
120 pub fn get_request(
125 &self,
126 type_: &[u8],
127 name: Option<&str>,
128 app_params: Option<Bytes>,
129 ) -> Result<Bytes, ObexError> {
130 let conn_id = self.conn_id()?;
131 let mut headers =
132 vec![Header::ConnectionId(conn_id), Header::Type(Bytes::copy_from_slice(type_))];
133 if let Some(n) = name {
134 headers.push(Header::Name(n.to_owned()));
135 }
136 if let Some(params) = app_params {
137 headers.push(Header::AppParams(params));
138 }
139 Ok(Packet { opcode: OpCode::GetFinal, extra: PacketExtra::None, headers }.encode()?)
140 }
141
142 pub fn get_continue_request(&self) -> Result<Bytes, ObexError> {
147 let conn_id = self.conn_id()?;
148 Ok(Packet {
149 opcode: OpCode::GetFinal,
150 extra: PacketExtra::None,
151 headers: vec![Header::ConnectionId(conn_id)],
152 }
153 .encode()?)
154 }
155
156 pub fn put_final_request(
163 &self,
164 type_: &[u8],
165 extra_headers: Vec<Header>,
166 ) -> Result<Bytes, ObexError> {
167 let conn_id = self.conn_id()?;
168 let mut headers =
169 vec![Header::ConnectionId(conn_id), Header::Type(Bytes::copy_from_slice(type_))];
170 headers.extend(extra_headers);
171 Ok(Packet { opcode: OpCode::PutFinal, extra: PacketExtra::None, headers }.encode()?)
172 }
173
174 pub fn disconnect_request(&self) -> Result<Bytes, ObexError> {
179 let conn_id = self.conn_id()?;
180 Ok(Packet {
181 opcode: OpCode::Disconnect,
182 extra: PacketExtra::None,
183 headers: vec![Header::ConnectionId(conn_id)],
184 }
185 .encode()?)
186 }
187
188 pub fn parse_response(data: &[u8]) -> Result<Packet, ObexError> {
193 Ok(Packet::decode(data)?)
194 }
195
196 pub const fn conn_id(&self) -> Result<u32, ObexError> {
202 match &self.state {
203 State::Connected { conn_id, .. } => Ok(*conn_id),
204 State::Disconnected => Err(ObexError::NotConnected),
205 }
206 }
207
208 #[must_use]
210 pub const fn is_connected(&self) -> bool {
211 matches!(self.state, State::Connected { .. })
212 }
213
214 #[must_use]
216 pub const fn max_packet(&self) -> u16 {
217 match &self.state {
218 State::Connected { max_packet, .. } => *max_packet,
219 State::Disconnected => 0,
220 }
221 }
222}
223
224impl Default for ObexClient {
225 fn default() -> Self {
226 Self::new()
227 }
228}