Skip to main content

oxigdal_ws/
stream.rs

1//! Data streaming utilities for WebSocket connections.
2use crate::error::{Error, Result};
3use crate::protocol::Message;
4use bytes::Bytes;
5use futures::stream::Stream;
6use std::pin::Pin;
7use std::task::{Context, Poll};
8use tokio::sync::mpsc;
9/// A stream of WebSocket messages.
10pub struct MessageStream {
11    receiver: mpsc::UnboundedReceiver<Message>,
12}
13impl MessageStream {
14    /// Create a new message stream.
15    pub fn new(receiver: mpsc::UnboundedReceiver<Message>) -> Self {
16        Self { receiver }
17    }
18    /// Receive the next message.
19    pub async fn next_message(&mut self) -> Option<Message> {
20        self.receiver.recv().await
21    }
22}
23impl Stream for MessageStream {
24    type Item = Message;
25    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
26        self.receiver.poll_recv(cx)
27    }
28}
29/// A stream of tile data.
30pub struct TileStream {
31    receiver: mpsc::UnboundedReceiver<TileData>,
32}
33impl TileStream {
34    /// Create a new tile stream.
35    pub fn new(receiver: mpsc::UnboundedReceiver<TileData>) -> Self {
36        Self { receiver }
37    }
38    /// Receive the next tile.
39    pub async fn next_tile(&mut self) -> Option<TileData> {
40        self.receiver.recv().await
41    }
42}
43impl Stream for TileStream {
44    type Item = TileData;
45    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
46        self.receiver.poll_recv(cx)
47    }
48}
49/// Tile data with metadata.
50#[derive(Debug, Clone)]
51pub struct TileData {
52    /// Tile X coordinate
53    pub x: u32,
54    /// Tile Y coordinate
55    pub y: u32,
56    /// Zoom level
57    pub zoom: u8,
58    /// Tile data
59    pub data: Bytes,
60    /// MIME type (e.g., "application/x-protobuf" for MVT)
61    pub mime_type: String,
62}
63impl TileData {
64    /// Create new tile data.
65    pub fn new(x: u32, y: u32, zoom: u8, data: Vec<u8>, mime_type: String) -> Self {
66        Self {
67            x,
68            y,
69            zoom,
70            data: Bytes::from(data),
71            mime_type,
72        }
73    }
74    /// Get tile coordinates as (x, y, zoom).
75    pub fn coords(&self) -> (u32, u32, u8) {
76        (self.x, self.y, self.zoom)
77    }
78    /// Get data size in bytes.
79    pub fn size(&self) -> usize {
80        self.data.len()
81    }
82}
83/// A stream of feature data.
84pub struct FeatureStream {
85    receiver: mpsc::UnboundedReceiver<FeatureData>,
86}
87impl FeatureStream {
88    /// Create a new feature stream.
89    pub fn new(receiver: mpsc::UnboundedReceiver<FeatureData>) -> Self {
90        Self { receiver }
91    }
92    /// Receive the next feature.
93    pub async fn next_feature(&mut self) -> Option<FeatureData> {
94        self.receiver.recv().await
95    }
96}
97impl Stream for FeatureStream {
98    type Item = FeatureData;
99    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
100        self.receiver.poll_recv(cx)
101    }
102}
103/// Feature data with metadata.
104#[derive(Debug, Clone)]
105pub struct FeatureData {
106    /// GeoJSON string
107    pub geojson: String,
108    /// Change type
109    pub change_type: crate::protocol::ChangeType,
110    /// Layer name
111    pub layer: Option<String>,
112}
113impl FeatureData {
114    /// Create new feature data.
115    pub fn new(
116        geojson: String,
117        change_type: crate::protocol::ChangeType,
118        layer: Option<String>,
119    ) -> Self {
120        Self {
121            geojson,
122            change_type,
123            layer,
124        }
125    }
126    /// Parse GeoJSON.
127    pub fn parse_json(&self) -> Result<serde_json::Value> {
128        serde_json::from_str(&self.geojson).map_err(Into::into)
129    }
130}
131/// A stream of events.
132pub struct EventStream {
133    receiver: mpsc::UnboundedReceiver<EventData>,
134}
135impl EventStream {
136    /// Create a new event stream.
137    pub fn new(receiver: mpsc::UnboundedReceiver<EventData>) -> Self {
138        Self { receiver }
139    }
140    /// Receive the next event.
141    pub async fn next_event(&mut self) -> Option<EventData> {
142        self.receiver.recv().await
143    }
144}
145impl Stream for EventStream {
146    type Item = EventData;
147    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
148        self.receiver.poll_recv(cx)
149    }
150}
151/// Event data with metadata.
152#[derive(Debug, Clone)]
153pub struct EventData {
154    /// Event type
155    pub event_type: crate::protocol::EventType,
156    /// Event payload
157    pub payload: serde_json::Value,
158    /// Event timestamp
159    pub timestamp: chrono::DateTime<chrono::Utc>,
160}
161impl EventData {
162    /// Create new event data.
163    pub fn new(event_type: crate::protocol::EventType, payload: serde_json::Value) -> Self {
164        Self {
165            event_type,
166            payload,
167            timestamp: chrono::Utc::now(),
168        }
169    }
170    /// Create event with explicit timestamp.
171    pub fn with_timestamp(
172        event_type: crate::protocol::EventType,
173        payload: serde_json::Value,
174        timestamp: chrono::DateTime<chrono::Utc>,
175    ) -> Self {
176        Self {
177            event_type,
178            payload,
179            timestamp,
180        }
181    }
182}
183/// Backpressure control for streams.
184pub struct BackpressureController {
185    /// Maximum buffer size
186    max_buffer_size: usize,
187    /// Current buffer size
188    current_buffer_size: usize,
189    /// High watermark (percentage of max)
190    high_watermark: f64,
191    /// Low watermark (percentage of max)
192    low_watermark: f64,
193    /// Current state
194    state: BackpressureState,
195}
196/// Backpressure state.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum BackpressureState {
199    /// Normal operation
200    Normal,
201    /// High pressure - slow down
202    High,
203    /// Critical - stop sending
204    Critical,
205}
206impl BackpressureController {
207    /// Create a new backpressure controller.
208    pub fn new(max_buffer_size: usize) -> Self {
209        Self {
210            max_buffer_size,
211            current_buffer_size: 0,
212            high_watermark: 0.7,
213            low_watermark: 0.3,
214            state: BackpressureState::Normal,
215        }
216    }
217    /// Update buffer size and return new state.
218    pub fn update(&mut self, buffer_size: usize) -> BackpressureState {
219        self.current_buffer_size = buffer_size;
220        let ratio = buffer_size as f64 / self.max_buffer_size as f64;
221        self.state = if ratio >= 0.9 {
222            BackpressureState::Critical
223        } else if ratio >= self.high_watermark {
224            BackpressureState::High
225        } else if ratio <= self.low_watermark {
226            BackpressureState::Normal
227        } else {
228            self.state
229        };
230        self.state
231    }
232    /// Get current state.
233    pub fn state(&self) -> BackpressureState {
234        self.state
235    }
236    /// Check if should throttle.
237    pub fn should_throttle(&self) -> bool {
238        matches!(
239            self.state,
240            BackpressureState::High | BackpressureState::Critical
241        )
242    }
243    /// Check if should drop messages.
244    pub fn should_drop(&self) -> bool {
245        self.state == BackpressureState::Critical
246    }
247}
248/// Format tag: payload is the raw (uncompressed) tile bytes.
249const DELTA_TAG_RAW: u8 = 0x00;
250/// Format tag: payload is a compact varint-encoded diff against the cached
251/// previous tile.
252const DELTA_TAG_DELTA: u8 = 0x01;
253/// Maximum number of bits a LEB128 varint may occupy in this codec
254/// (5 groups of 7 bits covers the full `u32` range).
255const MAX_VARINT_SHIFT: u32 = 35;
256
257/// Write `value` as an unsigned LEB128 varint into `buf`.
258fn write_varint(buf: &mut Vec<u8>, mut value: u32) {
259    loop {
260        let mut byte = (value & 0x7f) as u8;
261        value >>= 7;
262        if value != 0 {
263            byte |= 0x80;
264        }
265        buf.push(byte);
266        if value == 0 {
267            break;
268        }
269    }
270}
271
272/// Read an unsigned LEB128 varint from `data` starting at `*pos`, advancing
273/// `*pos` past the bytes consumed.
274fn read_varint(data: &[u8], pos: &mut usize) -> Result<u32> {
275    let mut result: u32 = 0;
276    let mut shift: u32 = 0;
277    loop {
278        let byte = *data
279            .get(*pos)
280            .ok_or_else(|| Error::Deserialization("truncated varint in delta payload".into()))?;
281        *pos += 1;
282        result |= ((byte & 0x7f) as u32) << shift;
283        if byte & 0x80 == 0 {
284            break;
285        }
286        shift += 7;
287        if shift >= MAX_VARINT_SHIFT {
288            return Err(Error::Deserialization(
289                "varint too long in delta payload".into(),
290            ));
291        }
292    }
293    Ok(result)
294}
295
296/// Delta encoder for efficient tile updates.
297///
298/// Encoded output is always tagged with a leading format byte so a decoder
299/// (see [`DeltaEncoder::apply_delta`]) can tell raw payloads apart from
300/// diffs: `DELTA_TAG_RAW` for the untouched tile bytes, or
301/// `DELTA_TAG_DELTA` for a compact varint diff against the previously
302/// cached tile. The diff format is only used when it is actually smaller
303/// than sending the tile raw; otherwise `encode` falls back to the raw
304/// payload so output can never expand beyond `1 + new.len()` bytes.
305pub struct DeltaEncoder {
306    /// Previous tile data cache
307    cache: dashmap::DashMap<(u32, u32, u8), Bytes>,
308}
309impl DeltaEncoder {
310    /// Create a new delta encoder.
311    pub fn new() -> Self {
312        Self {
313            cache: dashmap::DashMap::new(),
314        }
315    }
316    /// Encode tile data with delta compression.
317    ///
318    /// The result is always tag-prefixed (see [`DeltaEncoder::apply_delta`])
319    /// so it can be decoded without external knowledge of whether a diff or
320    /// a raw payload was chosen.
321    pub fn encode(&self, tile: &TileData) -> Result<Vec<u8>> {
322        let key = tile.coords();
323        // Compute the diff (if any) against the cached previous tile first,
324        // and let the `Ref` guard from `get` drop at the end of this block
325        // before we `insert`. DashMap's per-shard `RwLock` is not
326        // reentrant: holding a read guard while writing to the same shard
327        // (guaranteed here, since it's the same key) deadlocks the calling
328        // task instead of erroring.
329        let delta = match self.cache.get(&key) {
330            Some(prev_data) => Some(Self::compute_delta(&prev_data, &tile.data)?),
331            None => None,
332        };
333        self.cache.insert(key, tile.data.clone());
334        match delta {
335            Some(delta) => Ok(delta),
336            None => Ok(Self::tag_raw(&tile.data)),
337        }
338    }
339    /// Wrap `data` in the raw-payload tag.
340    fn tag_raw(data: &[u8]) -> Vec<u8> {
341        let mut out = Vec::with_capacity(1 + data.len());
342        out.push(DELTA_TAG_RAW);
343        out.extend_from_slice(data);
344        out
345    }
346    /// Build the varint diff payload (without the leading tag byte)
347    /// describing how to turn `old` into `new`.
348    fn encode_diff_payload(old: &[u8], new: &[u8]) -> Vec<u8> {
349        let mut payload = Vec::new();
350        write_varint(&mut payload, new.len() as u32);
351        let mut last_index: i64 = -1;
352        let common = old.len().min(new.len());
353        for i in 0..new.len() {
354            let changed = if i < common { old[i] != new[i] } else { true };
355            if changed {
356                // Gap since the previous changed index, so runs of changes
357                // close together cost a single zero byte instead of a full
358                // absolute index.
359                let gap = (i as i64 - last_index - 1) as u32;
360                write_varint(&mut payload, gap);
361                payload.push(new[i]);
362                last_index = i as i64;
363            }
364        }
365        payload
366    }
367    /// Compute delta between two byte arrays, falling back to a tagged raw
368    /// payload whenever the diff would not actually be smaller than `new`.
369    fn compute_delta(old: &[u8], new: &[u8]) -> Result<Vec<u8>> {
370        let diff_payload = Self::encode_diff_payload(old, new);
371        if diff_payload.len() < new.len() {
372            let mut out = Vec::with_capacity(1 + diff_payload.len());
373            out.push(DELTA_TAG_DELTA);
374            out.extend_from_slice(&diff_payload);
375            Ok(out)
376        } else {
377            Ok(Self::tag_raw(new))
378        }
379    }
380    /// Decode a payload produced by [`DeltaEncoder::encode`] (or
381    /// `DeltaEncoder::compute_delta`) back into the reconstructed tile
382    /// bytes.
383    ///
384    /// `old` must be the same "previous tile" bytes that were used to
385    /// produce a diff-tagged payload; it is ignored for raw-tagged
386    /// payloads.
387    pub fn apply_delta(old: &[u8], encoded: &[u8]) -> Result<Vec<u8>> {
388        let mut pos = 0usize;
389        let tag = *encoded
390            .first()
391            .ok_or_else(|| Error::Deserialization("empty delta payload".into()))?;
392        pos += 1;
393        match tag {
394            DELTA_TAG_RAW => Ok(encoded[pos..].to_vec()),
395            DELTA_TAG_DELTA => {
396                let new_len = read_varint(encoded, &mut pos)? as usize;
397                let mut out = vec![0u8; new_len];
398                let common = old.len().min(new_len);
399                out[..common].copy_from_slice(&old[..common]);
400                let mut index: i64 = -1;
401                while pos < encoded.len() {
402                    let gap = read_varint(encoded, &mut pos)?;
403                    let byte = *encoded.get(pos).ok_or_else(|| {
404                        Error::Deserialization("truncated delta value byte".into())
405                    })?;
406                    pos += 1;
407                    index += 1 + gap as i64;
408                    let idx = usize::try_from(index)
409                        .map_err(|_| Error::Deserialization("invalid delta index".into()))?;
410                    if idx >= new_len {
411                        return Err(Error::Deserialization("delta index out of range".into()));
412                    }
413                    out[idx] = byte;
414                }
415                Ok(out)
416            }
417            other => Err(Error::Deserialization(format!(
418                "unknown delta format tag: {other}"
419            ))),
420        }
421    }
422    /// Clear cache.
423    pub fn clear(&self) {
424        self.cache.clear();
425    }
426    /// Get cache size.
427    pub fn cache_size(&self) -> usize {
428        self.cache.len()
429    }
430}
431impl Default for DeltaEncoder {
432    fn default() -> Self {
433        Self::new()
434    }
435}
436#[cfg(test)]
437mod tests {
438    use super::*;
439    #[tokio::test]
440    async fn test_message_stream() {
441        let (tx, rx) = mpsc::unbounded_channel();
442        let mut stream = MessageStream::new(rx);
443        let send_result = tx.send(Message::Ping { id: 1 });
444        assert!(send_result.is_ok());
445        let msg = stream.next_message().await;
446        assert!(msg.is_some());
447        if let Some(Message::Ping { id }) = msg {
448            assert_eq!(id, 1);
449        }
450    }
451    #[tokio::test]
452    async fn test_tile_stream() {
453        let (tx, rx) = mpsc::unbounded_channel();
454        let mut stream = TileStream::new(rx);
455        let tile = TileData::new(0, 0, 5, vec![1, 2, 3], "application/x-protobuf".to_string());
456        let send_result = tx.send(tile.clone());
457        assert!(send_result.is_ok());
458        let received = stream.next_tile().await;
459        assert!(received.is_some());
460        if let Some(tile) = received {
461            assert_eq!(tile.coords(), (0, 0, 5));
462            assert_eq!(tile.size(), 3);
463        }
464    }
465    #[test]
466    fn test_backpressure_controller() {
467        let mut controller = BackpressureController::new(100);
468        assert_eq!(controller.update(30), BackpressureState::Normal);
469        assert!(!controller.should_throttle());
470        assert_eq!(controller.update(75), BackpressureState::High);
471        assert!(controller.should_throttle());
472        assert_eq!(controller.update(95), BackpressureState::Critical);
473        assert!(controller.should_drop());
474        assert_eq!(controller.update(25), BackpressureState::Normal);
475        assert!(!controller.should_throttle());
476    }
477    #[test]
478    fn test_delta_encoder() {
479        let encoder = DeltaEncoder::new();
480        let tile1 = TileData::new(
481            0,
482            0,
483            5,
484            vec![1, 2, 3, 4, 5],
485            "application/x-protobuf".to_string(),
486        );
487        let delta1 = encoder.encode(&tile1);
488        assert!(delta1.is_ok());
489        // First encode of a coordinate is a cache miss: 1 tag byte + the raw
490        // tile bytes, never larger than that.
491        if let Ok(data) = delta1 {
492            assert_eq!(data.len(), 6);
493            assert_eq!(data[0], DELTA_TAG_RAW);
494        }
495        let tile2 = TileData::new(
496            0,
497            0,
498            5,
499            vec![1, 2, 9, 4, 5],
500            "application/x-protobuf".to_string(),
501        );
502        let delta2 = encoder.encode(&tile2);
503        assert!(delta2.is_ok());
504        if let Ok(data) = delta2 {
505            // A single changed byte out of 5 must produce output smaller
506            // than the raw tile, not larger (this was the original bug).
507            assert!(data.len() < tile2.size());
508            assert_eq!(data[0], DELTA_TAG_DELTA);
509            // And it must decode back to the exact new tile bytes.
510            let restored = DeltaEncoder::apply_delta(&tile1.data, &data);
511            assert!(restored.is_ok());
512            if let Ok(restored) = restored {
513                assert_eq!(restored, tile2.data.to_vec());
514            }
515        }
516    }
517
518    #[test]
519    fn test_delta_encoder_raw_fallback_when_diff_would_expand() {
520        // Every byte differs, so the varint diff (>= 2 bytes/change) can
521        // never beat the raw payload: the encoder must fall back to raw
522        // instead of emitting something larger than the input.
523        let old = vec![0u8; 5];
524        let new = vec![9u8; 5];
525        let encoded = DeltaEncoder::compute_delta(&old, &new);
526        assert!(encoded.is_ok());
527        if let Ok(data) = encoded {
528            assert_eq!(data[0], DELTA_TAG_RAW);
529            assert_eq!(data.len(), 1 + new.len());
530            let restored = DeltaEncoder::apply_delta(&old, &data);
531            assert!(restored.is_ok());
532            if let Ok(restored) = restored {
533                assert_eq!(restored, new);
534            }
535        }
536    }
537
538    #[test]
539    fn test_delta_encoder_grow_and_shrink_round_trip() {
540        let old = vec![1, 2, 3];
541        let grown = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
542        let encoded = DeltaEncoder::compute_delta(&old, &grown);
543        assert!(encoded.is_ok());
544        if let Ok(data) = encoded {
545            let restored = DeltaEncoder::apply_delta(&old, &data);
546            assert!(restored.is_ok());
547            if let Ok(restored) = restored {
548                assert_eq!(restored, grown);
549            }
550        }
551        let shrunk = vec![1, 99];
552        let encoded = DeltaEncoder::compute_delta(&grown, &shrunk);
553        assert!(encoded.is_ok());
554        if let Ok(data) = encoded {
555            let restored = DeltaEncoder::apply_delta(&grown, &data);
556            assert!(restored.is_ok());
557            if let Ok(restored) = restored {
558                assert_eq!(restored, shrunk);
559            }
560        }
561    }
562
563    #[test]
564    fn test_delta_encoder_apply_delta_rejects_malformed_input() {
565        // Empty payload has no tag byte.
566        assert!(DeltaEncoder::apply_delta(&[], &[]).is_err());
567        // Unknown tag byte.
568        assert!(DeltaEncoder::apply_delta(&[], &[0xff]).is_err());
569        // Delta tag with a truncated varint length.
570        assert!(DeltaEncoder::apply_delta(&[], &[DELTA_TAG_DELTA]).is_err());
571        // Delta tag whose diff entry is missing its value byte.
572        assert!(DeltaEncoder::apply_delta(&[1, 2, 3], &[DELTA_TAG_DELTA, 3, 0]).is_err());
573    }
574}