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 {}