Skip to main content

livekit_data_stream/
lib.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![doc = include_str!("../README.md")]
16
17use chrono::{DateTime, Utc};
18use livekit_common::EncryptionType;
19use livekit_protocol::data_stream as proto;
20use std::collections::HashMap;
21use thiserror::Error;
22
23mod incoming;
24mod outgoing;
25mod utf8_chunk;
26
27pub use incoming::{
28    AnyStreamReader, ByteStreamReader, IncomingStreamManager, StreamReader, TextStreamReader,
29};
30pub use outgoing::{
31    ByteStreamWriter, OutgoingStreamManager, StreamByteOptions, StreamTextOptions, StreamWriter,
32    TextStreamWriter,
33};
34
35/// Error returned by the packet transport when a data-stream packet fails to send.
36///
37/// The stream managers only need to know that a send failed (they map it to
38/// [`StreamError::SendFailed`]); the concrete engine error type stays in the `livekit` crate,
39/// which bridges the outgoing packet channel to the RTC engine.
40#[derive(Debug, Clone)]
41pub struct SendError;
42
43/// Generates a random stream identifier (UUID v4).
44pub(crate) fn create_random_uuid() -> String {
45    uuid::Uuid::new_v4().to_string()
46}
47
48/// Result type for data stream operations.
49pub type StreamResult<T> = Result<T, StreamError>;
50
51/// Error type for data stream operations.
52#[derive(Debug, Error)]
53pub enum StreamError {
54    // TODO(ladvoc): standardize error cases and expose over FFI.
55    #[error("stream has already been closed")]
56    AlreadyClosed,
57
58    #[error("stream closed abnormally: {0}")]
59    AbnormalEnd(String),
60
61    #[error("UTF-8 decoding error: {0}")]
62    Utf8(#[from] std::string::FromUtf8Error),
63
64    #[error("incoming header was invalid")]
65    InvalidHeader,
66
67    #[error("expected chunk index to be exactly one more than the previous")]
68    MissedChunk,
69
70    #[error("read length exceeded total length specified in stream header")]
71    LengthExceeded,
72
73    #[error("stream data is incomplete")]
74    Incomplete,
75
76    #[error("unable to send packet")]
77    SendFailed,
78
79    #[error("I/O error: {0}")]
80    Io(#[from] std::io::Error),
81
82    #[error("internal error")]
83    Internal,
84
85    #[error("encryption type mismatch")]
86    EncryptionTypeMismatch,
87}
88
89/// Progress of a data stream.
90#[derive(Clone, Copy, Default, Debug, Hash, Eq, PartialEq)]
91struct StreamProgress {
92    chunk_index: u64,
93    /// Number of bytes read or written so far.
94    bytes_processed: u64,
95    /// Total number of bytes expected to be read or written for finite streams.
96    bytes_total: Option<u64>,
97}
98
99impl StreamProgress {
100    /// Returns the completion percentage for finite streams.
101    #[allow(dead_code)]
102    fn percentage(&self) -> Option<f32> {
103        self.bytes_total.map(|total| self.bytes_processed as f32 / total as f32)
104    }
105}
106
107/// Information about a byte data stream.
108#[derive(Clone, Debug)]
109pub struct ByteStreamInfo {
110    /// Unique identifier of the stream.
111    pub id: String,
112    /// Topic name used to route the stream to the appropriate handler.
113    pub topic: String,
114    /// When the stream was created.
115    pub timestamp: DateTime<Utc>,
116    /// Total expected size in bytes, if known.
117    pub total_length: Option<u64>,
118    /// Additional attributes as needed for your application.
119    pub attributes: HashMap<String, String>,
120    /// The MIME type of the stream data.
121    pub mime_type: String,
122    /// The name of the file being sent.
123    pub name: String,
124    /// The encryption used
125    pub encryption_type: EncryptionType,
126}
127
128/// Information about a text data stream.
129#[derive(Clone, Debug)]
130pub struct TextStreamInfo {
131    /// Unique identifier of the stream.
132    pub id: String,
133    /// Topic name used to route the stream to the appropriate handler.
134    pub topic: String,
135    /// When the stream was created.
136    pub timestamp: DateTime<Utc>,
137    /// Total expected size in bytes, if known.
138    pub total_length: Option<u64>,
139    /// Additional attributes as needed for your application.
140    pub attributes: HashMap<String, String>,
141    /// The MIME type of the stream data.
142    pub mime_type: String,
143    pub operation_type: OperationType,
144    pub version: i32,
145    pub reply_to_stream_id: Option<String>,
146    pub attached_stream_ids: Vec<String>,
147    pub generated: bool,
148    /// The encryption used
149    pub encryption_type: EncryptionType,
150}
151
152/// Operation type for text streams.
153#[derive(Clone, Copy, Default, Debug, Hash, Eq, PartialEq)]
154pub enum OperationType {
155    #[default]
156    Create,
157    Update,
158    Delete,
159    Reaction,
160}
161
162// MARK: - Protocol type conversion
163
164impl TryFrom<proto::Header> for AnyStreamInfo {
165    type Error = StreamError;
166
167    fn try_from(mut header: proto::Header) -> Result<Self, Self::Error> {
168        Self::try_from_with_encryption(header, EncryptionType::None)
169    }
170}
171
172impl AnyStreamInfo {
173    pub fn try_from_with_encryption(
174        mut header: proto::Header,
175        encryption_type: EncryptionType,
176    ) -> Result<Self, StreamError> {
177        let Some(content_header) = header.content_header.take() else {
178            Err(StreamError::InvalidHeader)?
179        };
180        let info = match content_header {
181            proto::header::ContentHeader::ByteHeader(byte_header) => Self::Byte(
182                ByteStreamInfo::from_headers_with_encryption(header, byte_header, encryption_type),
183            ),
184            proto::header::ContentHeader::TextHeader(text_header) => Self::Text(
185                TextStreamInfo::from_headers_with_encryption(header, text_header, encryption_type),
186            ),
187        };
188        Ok(info)
189    }
190}
191
192impl ByteStreamInfo {
193    pub(crate) fn from_headers(header: proto::Header, byte_header: proto::ByteHeader) -> Self {
194        Self::from_headers_with_encryption(header, byte_header, EncryptionType::None)
195    }
196
197    pub(crate) fn from_headers_with_encryption(
198        header: proto::Header,
199        byte_header: proto::ByteHeader,
200        encryption_type: EncryptionType,
201    ) -> Self {
202        Self {
203            id: header.stream_id,
204            topic: header.topic,
205            timestamp: DateTime::<Utc>::from_timestamp_millis(header.timestamp)
206                .unwrap_or_else(|| Utc::now()),
207            total_length: header.total_length,
208            attributes: header.attributes,
209            mime_type: header.mime_type,
210            name: byte_header.name,
211            encryption_type,
212        }
213    }
214}
215
216impl TextStreamInfo {
217    pub(crate) fn from_headers(header: proto::Header, text_header: proto::TextHeader) -> Self {
218        Self::from_headers_with_encryption(header, text_header, EncryptionType::None)
219    }
220
221    pub(crate) fn from_headers_with_encryption(
222        header: proto::Header,
223        text_header: proto::TextHeader,
224        encryption_type: EncryptionType,
225    ) -> Self {
226        Self {
227            id: header.stream_id,
228            topic: header.topic,
229            timestamp: DateTime::<Utc>::from_timestamp_millis(header.timestamp)
230                .unwrap_or_else(|| Utc::now()),
231            total_length: header.total_length,
232            attributes: header.attributes,
233            mime_type: header.mime_type,
234            operation_type: text_header.operation_type().into(),
235            version: text_header.version,
236            reply_to_stream_id: (!text_header.reply_to_stream_id.is_empty())
237                .then_some(text_header.reply_to_stream_id),
238            attached_stream_ids: text_header.attached_stream_ids,
239            generated: text_header.generated,
240            encryption_type,
241        }
242    }
243}
244
245impl From<proto::OperationType> for OperationType {
246    fn from(op_type: proto::OperationType) -> Self {
247        match op_type {
248            proto::OperationType::Create => OperationType::Create,
249            proto::OperationType::Update => OperationType::Update,
250            proto::OperationType::Delete => OperationType::Delete,
251            proto::OperationType::Reaction => OperationType::Reaction,
252        }
253    }
254}
255// MARK: - Dispatch
256
257#[derive(Clone, Debug)]
258pub(crate) enum AnyStreamInfo {
259    Byte(ByteStreamInfo),
260    Text(TextStreamInfo),
261}
262
263impl AnyStreamInfo {
264    livekit_common::enum_dispatch!(
265        [Byte, Text];
266        pub fn id(self: &Self) -> &str;
267        pub fn total_length(self: &Self) -> Option<u64>;
268        pub fn encryption_type(self: &Self) -> EncryptionType;
269    );
270}
271
272#[rustfmt::skip]
273macro_rules! stream_info {
274    () => {
275        fn id(&self) -> &str { &self.id }
276        fn total_length(&self) -> Option<u64> { self.total_length }
277        fn encryption_type(&self) -> EncryptionType { self.encryption_type }
278    };
279}
280
281impl ByteStreamInfo {
282    stream_info!();
283}
284
285impl TextStreamInfo {
286    stream_info!();
287}
288
289impl From<ByteStreamInfo> for AnyStreamInfo {
290    fn from(info: ByteStreamInfo) -> Self {
291        Self::Byte(info)
292    }
293}
294
295impl From<TextStreamInfo> for AnyStreamInfo {
296    fn from(info: TextStreamInfo) -> Self {
297        Self::Text(info)
298    }
299}