gpsd_json/protocol.rs
1//! GPSD JSON protocol trait definitions and implementations
2//!
3//! This module provides the core traits and types for implementing
4//! the GPSD JSON protocol. It defines how messages are encoded, decoded,
5//! and transmitted between client and server.
6//!
7//! The GPSD protocol uses newline-delimited JSON messages for communication.
8//! Each message is a complete JSON object terminated with a newline character.
9
10use std::{
11 pin::Pin,
12 task::{Context, Poll},
13};
14
15use crate::{Result, error::GpsdJsonError};
16
17/// Protocol version 3 implementation
18///
19/// This is the current stable version of the GPSD JSON protocol,
20/// supporting all standard GPS data types and control messages.
21pub mod v3;
22
23/// Trait for types that can be deserialized as GPSD response messages
24///
25/// All GPSD response message types must implement this trait,
26/// which ensures they can be properly deserialized from JSON.
27pub trait GpsdJsonResponse: serde::de::DeserializeOwned {}
28
29/// Extension trait for reading GPSD JSON responses from an async buffered reader
30///
31/// This trait provides functionality to asynchronously read and parse GPSD JSON
32/// messages from any type that implements `AsyncBufRead`. Messages are expected
33/// to be newline-delimited JSON objects.
34///
35/// This is the async equivalent of `GpsdJsonDecode`.
36pub trait GpsdJsonDecodeAsync: futures_io::AsyncBufRead {
37 /// Polls for the next GPSD response message
38 ///
39 /// This method attempts to read and deserialize a single GPSD response
40 /// message from the async stream. It accumulates data in the provided
41 /// buffer until a complete message is received (delimited by newline).
42 ///
43 /// # Arguments
44 /// * `self` - Pinned mutable reference to the async reader
45 /// * `cx` - The task context for waking
46 /// * `buf` - A reusable buffer for accumulating message data
47 ///
48 /// # Returns
49 /// * `Poll::Ready(Ok(Some(response)))` - Successfully parsed response message
50 /// * `Poll::Ready(Ok(None))` - End of stream reached
51 /// * `Poll::Ready(Err(_))` - I/O or parsing error occurred
52 /// * `Poll::Pending` - Not enough data available yet
53 ///
54 /// # Example
55 /// ```no_run
56 /// # use std::pin::Pin;
57 /// # use std::task::{Context, Poll};
58 /// # use futures::AsyncBufReadExt;
59 /// # use gpsd_json::protocol::GpsdJsonDecodeAsync;
60 /// # use gpsd_json::protocol::v3::ResponseMessage;
61 /// # async fn example(reader: &mut (impl AsyncBufReadExt + Unpin)) {
62 /// let mut buf = Vec::new();
63 /// let fut = futures::future::poll_fn(|cx| {
64 /// Pin::new(&mut *reader).poll_response::<ResponseMessage>(cx, &mut buf)
65 /// });
66 /// if let Ok(Some(response)) = fut.await {
67 /// // Process the response
68 /// }
69 /// # }
70 /// ```
71 fn poll_response<Response>(
72 mut self: Pin<&mut Self>,
73 cx: &mut Context<'_>,
74 buf: &mut Vec<u8>,
75 ) -> Poll<Result<Option<Response>>>
76 where
77 Response: GpsdJsonResponse,
78 {
79 loop {
80 match self.as_mut().poll_fill_buf(cx) {
81 Poll::Ready(Ok(in_buf)) => {
82 if in_buf.is_empty() {
83 return Poll::Ready(Ok(None)); // EOF reached
84 }
85
86 if let Some(pos) = in_buf.iter().position(|&b| b == b'\n') {
87 // Found a newline, we have a complete message
88 buf.extend_from_slice(&in_buf[..=pos]);
89 self.as_mut().consume(pos + 1); // Consume up to and including the newline
90
91 return match serde_json::from_slice(buf) {
92 Ok(msg) => {
93 buf.clear();
94 Poll::Ready(Ok(Some(msg)))
95 }
96 Err(e) if e.is_eof() => {
97 // Incomplete JSON, continue reading
98 Poll::Pending
99 }
100 Err(e) => {
101 buf.clear();
102 Poll::Ready(Err(GpsdJsonError::SerdeError(e)))
103 }
104 };
105 } else {
106 // No newline found, append all available data and continue
107 buf.extend_from_slice(in_buf);
108 let len = in_buf.len();
109 self.as_mut().consume(len);
110 }
111 }
112 Poll::Ready(Err(e)) => return Poll::Ready(Err(GpsdJsonError::IoError(e))),
113 Poll::Pending => return Poll::Pending,
114 }
115 }
116 }
117
118 /// Polls for raw GPSD message data without deserialization
119 ///
120 /// This method reads raw bytes from the async stream until a complete
121 /// message is received (delimited by newline), but returns the raw bytes
122 /// instead of deserializing them. This is useful when you need to process
123 /// the raw JSON data or handle messages that don't fit standard types.
124 ///
125 /// # Arguments
126 /// * `self` - Pinned mutable reference to the async reader
127 /// * `cx` - The task context for waking
128 /// * `buf` - A reusable buffer for accumulating message data
129 ///
130 /// # Returns
131 /// * `Poll::Ready(Ok(Some(bytes)))` - Complete raw message including newline
132 /// * `Poll::Ready(Ok(None))` - End of stream reached
133 /// * `Poll::Ready(Err(_))` - I/O error occurred
134 /// * `Poll::Pending` - Not enough data available yet
135 ///
136 /// # Example
137 /// ```no_run
138 /// # use std::pin::Pin;
139 /// # use std::task::{Context, Poll};
140 /// # use futures::AsyncBufReadExt;
141 /// # use gpsd_json::protocol::GpsdJsonDecodeAsync;
142 /// # async fn example(reader: &mut (impl AsyncBufReadExt + Unpin)) {
143 /// let mut buf = Vec::new();
144 /// let fut = futures::future::poll_fn(|cx| {
145 /// Pin::new(&mut *reader).poll_raw(cx, &mut buf)
146 /// });
147 /// if let Ok(Some(raw_msg)) = fut.await {
148 /// // Process raw JSON bytes
149 /// let json_str = String::from_utf8_lossy(&raw_msg);
150 /// println!("Raw message: {}", json_str);
151 /// }
152 /// # }
153 /// ```
154 fn poll_raw(
155 mut self: Pin<&mut Self>,
156 cx: &mut Context<'_>,
157 buf: &mut Vec<u8>,
158 ) -> Poll<Result<Option<Vec<u8>>>> {
159 loop {
160 match self.as_mut().poll_fill_buf(cx) {
161 Poll::Ready(Ok(in_buf)) => {
162 if in_buf.is_empty() {
163 return Poll::Ready(Ok(None)); // EOF reached
164 }
165
166 if let Some(pos) = in_buf.iter().position(|&b| b == b'\n') {
167 // Found a newline, we have a complete message
168 buf.extend_from_slice(&in_buf[..=pos]);
169 self.as_mut().consume(pos + 1); // Consume up to and including the newline
170
171 let msg = buf.clone();
172 buf.clear();
173 return Poll::Ready(Ok(Some(msg)));
174 } else {
175 // No newline found, append all available data and continue
176 buf.extend_from_slice(in_buf);
177 let len = in_buf.len();
178 self.as_mut().consume(len);
179 }
180 }
181 Poll::Ready(Err(e)) => return Poll::Ready(Err(GpsdJsonError::IoError(e))),
182 Poll::Pending => return Poll::Pending,
183 }
184 }
185 }
186}
187
188impl<R: futures_io::AsyncBufRead + Unpin + ?Sized> GpsdJsonDecodeAsync for R {}
189
190/// Extension trait for reading GPSD JSON responses from a buffered reader
191///
192/// This trait provides functionality to read and parse GPSD JSON messages
193/// from any type that implements `BufRead`. Messages are expected to be
194/// newline-delimited JSON objects.
195pub trait GpsdJsonDecode: std::io::BufRead {
196 /// Reads and deserializes a single GPSD response message
197 ///
198 /// # Arguments
199 /// * `buf` - A reusable string buffer for reading data
200 ///
201 /// # Returns
202 /// * `Ok(Some(response))` - Successfully parsed response message
203 /// * `Ok(None)` - End of stream reached
204 /// * `Err(_)` - I/O or parsing error occurred
205 ///
206 /// # Example
207 /// ```no_run
208 /// # use std::io::BufReader;
209 /// # use gpsd_json::protocol::GpsdJsonDecode;
210 /// # use gpsd_json::protocol::v3::ResponseMessage;
211 /// # fn example(reader: &mut BufReader<std::net::TcpStream>) {
212 /// let mut buf = Vec::new();
213 /// if let Ok(Some(response)) = reader.read_response::<ResponseMessage>(&mut buf) {
214 /// // Process the response
215 /// }
216 /// # }
217 /// ```
218 fn read_response<Response>(&mut self, buf: &mut Vec<u8>) -> Result<Option<Response>>
219 where
220 Response: GpsdJsonResponse,
221 {
222 let bytes_read = self
223 .read_until(b'\n', buf)
224 .map_err(GpsdJsonError::IoError)?;
225 if bytes_read == 0 {
226 return Ok(None); // EOF reached
227 }
228
229 match serde_json::from_slice(buf) {
230 Ok(msg) => {
231 buf.clear();
232 Ok(Some(msg))
233 }
234 Err(e) if e.is_eof() => {
235 // Incomplete JSON, continue reading
236 Ok(None)
237 }
238 Err(e) => {
239 buf.clear();
240 Err(GpsdJsonError::SerdeError(e))
241 }
242 }
243 }
244}
245
246impl<R: std::io::BufRead + ?Sized> GpsdJsonDecode for R {}
247
248/// Trait for types that can be serialized as GPSD request messages
249///
250/// Request messages in GPSD follow a specific command format,
251/// typically starting with '?' and ending with ';'.
252pub trait GpsdJsonRequest {
253 /// Converts the request into a GPSD command string
254 ///
255 /// The returned string should be a valid GPSD command that can be
256 /// sent directly to the server. Commands typically follow the format:
257 /// `?COMMAND[=JSON_PARAMS];`
258 ///
259 /// # Example
260 /// ```
261 /// # struct WatchRequest;
262 /// # impl gpsd_json::protocol::GpsdJsonRequest for WatchRequest {
263 /// fn to_command(&self) -> String {
264 /// "?WATCH={\"enable\":true};".to_string()
265 /// }
266 /// # }
267 /// ```
268 fn to_command(&self) -> String;
269}
270
271/// Extension trait for writing GPSD JSON requests to an async writer
272///
273/// This trait provides functionality to asynchronously encode and send GPSD
274/// request messages to any type that implements `AsyncWriteExt`. The messages
275/// are formatted as GPSD commands and sent to the stream.
276///
277/// This is the async equivalent of `GpsdJsonEncode`.
278pub trait GpsdJsonEncodeAsync: futures_io::AsyncWrite + Unpin {
279 /// Writes a request message to the async output stream
280 ///
281 /// This method converts the request to a GPSD command string and
282 /// asynchronously writes it to the underlying stream.
283 ///
284 /// # Arguments
285 /// * `request` - The request message to send
286 ///
287 /// # Returns
288 /// A future that resolves to:
289 /// * `Ok(())` - Request successfully written
290 /// * `Err(_)` - I/O error occurred during write
291 ///
292 /// # Example
293 /// ```no_run
294 /// # use gpsd_json::protocol::GpsdJsonEncodeAsync;
295 /// # use gpsd_json::protocol::v3::RequestMessage;
296 /// # async fn example(writer: &mut (impl futures::io::AsyncWriteExt + Unpin), request: &RequestMessage) {
297 /// writer.write_request(request).await.expect("Failed to send request");
298 /// # }
299 /// ```
300 fn write_request(
301 &mut self,
302 request: &impl GpsdJsonRequest,
303 ) -> impl std::future::Future<Output = Result<()>> {
304 let cmd = request.to_command();
305 async move {
306 use futures_util::io::AsyncWriteExt;
307 self.write_all(cmd.as_bytes())
308 .await
309 .map_err(GpsdJsonError::IoError)
310 }
311 }
312}
313
314impl<W: futures_io::AsyncWrite + Unpin + ?Sized> GpsdJsonEncodeAsync for W {}
315
316/// Extension trait for writing GPSD JSON requests to a writer
317///
318/// This trait provides functionality to encode and send GPSD request
319/// messages to any type that implements `Write`.
320pub trait GpsdJsonEncode: std::io::Write {
321 /// Writes a request message to the output stream
322 ///
323 /// # Arguments
324 /// * `request` - The request message to send
325 ///
326 /// # Returns
327 /// * `Ok(())` - Request successfully written
328 /// * `Err(_)` - I/O error occurred during write
329 ///
330 /// # Example
331 /// ```no_run
332 /// # use gpsd_json::protocol::GpsdJsonEncode;
333 /// # fn example(writer: &mut std::net::TcpStream, request: &impl gpsd_json::protocol::GpsdJsonRequest) {
334 /// writer.write_request(request).expect("Failed to send request");
335 /// # }
336 /// ```
337 fn write_request(&mut self, request: &impl GpsdJsonRequest) -> Result<()> {
338 let cmd = request.to_command();
339 self.write_all(cmd.as_bytes())
340 .map_err(GpsdJsonError::IoError)
341 }
342}
343
344impl<W: std::io::Write + ?Sized> GpsdJsonEncode for W {}