Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
16d0294
Upgrade protocol to v1.44.0
ladvoc Jan 31, 2026
cb8f8a8
Integrate
ladvoc Jan 31, 2026
ca66c69
generated protobuf
github-actions[bot] Jan 31, 2026
2ccb534
Data tracks core module
ladvoc Jan 31, 2026
fd1a92e
Merge remote-tracking branch 'origin/main' into ladvoc/data-tracks-core
ladvoc Feb 2, 2026
7a90118
Use workspace dependencies
ladvoc Feb 2, 2026
afc10b4
Add to workspace
ladvoc Feb 3, 2026
c320670
Format
ladvoc Feb 3, 2026
ab8bb25
Add release-plz configuration
ladvoc Feb 3, 2026
d7b07a5
Used checked add
ladvoc Feb 3, 2026
7fb9c8f
Fix naming typo
ladvoc Feb 3, 2026
e951f4a
Fix inverted logic
ladvoc Feb 3, 2026
9452218
Fix potential integer overflow
ladvoc Feb 3, 2026
1347840
Add crate as workspace dependency
ladvoc Feb 3, 2026
da85847
Fix typo
ladvoc Feb 4, 2026
8239631
Apply Clippy suggestions
ladvoc Feb 4, 2026
9e1b6c7
Detect duplicate packets
ladvoc Feb 7, 2026
b4862cf
Compare with >=
ladvoc Feb 7, 2026
5ec6439
Calculate payload length during finalize
ladvoc Feb 7, 2026
01c550a
Cast earlier in expression
ladvoc Feb 7, 2026
8d65bbe
Handle edge case in packet reassembly
ladvoc Feb 7, 2026
c117c1c
Format
ladvoc Feb 7, 2026
b920d42
Refactor extension deserialization
ladvoc Feb 7, 2026
301aedf
Use wrapping add in tests
ladvoc Feb 7, 2026
d2afd21
Merge remote-tracking branch 'origin/main' into ladvoc/data-tracks-core
ladvoc Feb 9, 2026
8e4f0bc
generated protobuf
github-actions[bot] Feb 9, 2026
ca40c9f
Address comment
ladvoc Feb 11, 2026
037fafc
Add packet specification
ladvoc Feb 12, 2026
cf1f9be
Format
ladvoc Feb 12, 2026
1438639
Implement drop for manager input
ladvoc Feb 12, 2026
2d7d219
Increment frame number after fallible ops
ladvoc Feb 12, 2026
ccdc012
Handle publisher side full reconnect
ladvoc Feb 12, 2026
ad94670
Merge remote-tracking branch 'origin/main' into ladvoc/data-tracks-core
ladvoc Feb 13, 2026
15f3359
Update protobuf
ladvoc Feb 13, 2026
1ce2601
generated protobuf
github-actions[bot] Feb 13, 2026
e2583ab
Add missing clone derive
ladvoc Feb 14, 2026
cc4f741
Rename error reason case
ladvoc Feb 14, 2026
ba3636b
Unpublish on unknown handle
ladvoc Feb 14, 2026
3432333
Update protocol
ladvoc Feb 14, 2026
586dc9d
Format
ladvoc Feb 14, 2026
8529950
generated protobuf
github-actions[bot] Feb 14, 2026
9923c14
Add missing await
ladvoc Feb 14, 2026
e5d9549
Drop guard for manager input
ladvoc Feb 14, 2026
fc7e146
Remove timeout
ladvoc Feb 14, 2026
86924dd
Add doc comment
ladvoc Feb 14, 2026
38eba78
Avoid hardcoded constants
ladvoc Feb 14, 2026
0aa9412
Increase buffer counts
ladvoc Feb 14, 2026
45531ef
Merge remote-tracking branch 'origin' into ladvoc/data-tracks-core
ladvoc Feb 19, 2026
dac3f02
Add invalid name error case
ladvoc Feb 20, 2026
ac7b3f8
Ignore extensions with unknown length
ladvoc Feb 20, 2026
bef4a10
Format
ladvoc Feb 20, 2026
211313e
Merge remote-tracking branch 'origin/main' into ladvoc/data-tracks-core
ladvoc Mar 12, 2026
20439ea
Add knope configuration
ladvoc Mar 12, 2026
7efd0a8
Two byte TLV packet format
ladvoc Mar 17, 2026
296bfbe
Merge remote-tracking branch 'origin/main' into ladvoc/data-tracks-core
ladvoc Mar 17, 2026
c513c70
Add changeset
ladvoc Mar 17, 2026
eb1d7d8
Remove comment
ladvoc Mar 17, 2026
920544c
Use workspace props, add description
ladvoc Mar 17, 2026
4337555
Use actual bytes for extension TLV encoding
ladvoc Mar 17, 2026
0faf7c3
Skip extra bytes for future extension versions
ladvoc Mar 17, 2026
5ba7822
User customizable frame buffer size for subscriptions
ladvoc Mar 18, 2026
61f8f29
Format
ladvoc Mar 18, 2026
a7181f3
Add new method
ladvoc Mar 18, 2026
41b25dd
Additional unit tests for track managers
ladvoc Mar 18, 2026
37030fe
Fix publish/unpublish detection
ladvoc Mar 18, 2026
de06a2d
Concrete stream type for subscription
ladvoc Mar 19, 2026
7fa0bc6
Consistent public type prefixing
ladvoc Mar 19, 2026
d5339be
Avoid intermediate collect
ladvoc Mar 19, 2026
f615772
Format
ladvoc Mar 19, 2026
fff3d9d
Rename event
ladvoc Mar 19, 2026
ab51dc5
Add track unpublished event
ladvoc Mar 19, 2026
7b07b1d
Test track unpublished event
ladvoc Mar 19, 2026
bb358f3
Clamp invalid buffer size instead of panic
ladvoc Mar 19, 2026
49ed111
Do not consume track on unpublish
ladvoc Mar 20, 2026
e81eb60
Merge remote-tracking branch 'origin/main' into ladvoc/data-tracks-core
ladvoc Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ bytes = "1.10"
clap = "4.5"
console-subscriber = "0.1"
env_logger = "0.11"
from_variants = "1.0.2"
futures = "0.3"
futures-core = "0.3"
futures-util = "0.3"
lazy_static = "1.4"
log = "0.4"
Expand All @@ -68,6 +70,7 @@ serde = "1"
serde_json = "1.0"
thiserror = "1"
tokio = "1"
tokio-stream = "0.1"

[profile.release]
opt-level = "z"
Expand Down
25 changes: 25 additions & 0 deletions livekit-datatrack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "livekit-datatrack"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/livekit/rust-sdks"
readme = "README.md"
Comment thread
ladvoc marked this conversation as resolved.

[dependencies]
livekit-protocol = { workspace = true }
livekit-runtime = { workspace = true }
log = { workspace = true }
thiserror = "2.0.17" # TODO: upgrade workspace version to 2.x.x
Comment thread
ladvoc marked this conversation as resolved.
tokio = { workspace = true, default-features = false, features = ["sync"] }
futures-util = { workspace = true, default-features = false, features = ["sink"] }
futures-core = { workspace = true }
bytes = { workspace = true }
from_variants = { workspace = true }
tokio-stream = { workspace = true, features = ["sync"] }
anyhow = { workspace = true } # For internal error handling only
rand = { workspace = true }

[dev-dependencies]
test-case = "3.3"
fake = { version = "4.4", features = ["derive"] }
6 changes: 6 additions & 0 deletions livekit-datatrack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# LiveKit Data Track

**Important**:
This is an internal crate that powers the data tracks feature in LiveKit client SDKs (including [Rust](https://crates.io/crates/livekit) and others) and is not usable directly.

To use data tracks in your application, please use the public APIs provided by the client SDKs.
57 changes: 57 additions & 0 deletions livekit-datatrack/src/e2ee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2025 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use bytes::Bytes;
use core::fmt::Debug;
use thiserror::Error;

// TODO: If a core module for end-to-end encryption is created in the future
// (livekit-e2ee), these traits should be moved to there.

/// Encrypted payload and metadata required for decryption.
pub struct EncryptedPayload {
pub payload: Bytes,
pub iv: [u8; 12],
pub key_index: u8,
}

/// An error indicating a payload could not be encrypted.
#[derive(Debug, Error)]
#[error("Encryption failed")]
pub struct EncryptionError;

/// An error indicating a payload could not be decrypted.
#[derive(Debug, Error)]
#[error("Decryption failed")]
pub struct DecryptionError;

/// Provider for encrypting payloads for E2EE.
pub trait EncryptionProvider: Send + Sync + Debug {
/// Encrypts the given payload being sent by the local participant.
fn encrypt(&self, payload: Bytes) -> Result<EncryptedPayload, EncryptionError>;
}

/// Provider for decrypting payloads for E2EE.
pub trait DecryptionProvider: Send + Sync + Debug {
/// Decrypts the given payload received from a remote participant.
///
/// Sender identity is required in order for the proper key to be used
/// for decryption.
///
fn decrypt(
&self,
payload: EncryptedPayload,
sender_identity: &str,
) -> Result<Bytes, DecryptionError>;
}
24 changes: 24 additions & 0 deletions livekit-datatrack/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2025 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use thiserror::Error;

/// Internal data track error.
///
/// Occurrences of this error type are unexpected and likely indicate
/// a bug. If encountered, please report on GitHub and include the full error description.
///
#[derive(Debug, Error)]
#[error(transparent)]
pub struct InternalError(#[from] anyhow::Error);
122 changes: 122 additions & 0 deletions livekit-datatrack/src/frame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright 2025 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use bytes::Bytes;
use core::fmt;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

/// A frame published on a data track, consisting of a payload and optional metadata.
///
/// # Examples
///
/// Create a frame from a [`Vec`] payload:
///
/// ```
/// # use livekit_datatrack::api::DataTrackFrame;
/// let some_payload = vec![0xFA; 256];
/// let frame: DataTrackFrame = some_payload.into();
///
/// assert_eq!(frame.payload().len(), 256);
/// ```
///
#[derive(Clone, Default)]
pub struct DataTrackFrame {
pub(crate) payload: Bytes,
pub(crate) user_timestamp: Option<u64>,
}

impl DataTrackFrame {
/// Returns the frame's payload.
pub fn payload(&self) -> Bytes {
self.payload.clone() // Cheap clone
}

/// Returns the frame's user timestamp, if one is associated.
pub fn user_timestamp(&self) -> Option<u64> {
self.user_timestamp
}

/// If the frame has a user timestamp, calculate how long has passed
/// relative to the current system time.
///
/// If a timestamp is present, it is assumed it is a UNIX timestamp in milliseconds
/// (as can be set with [`Self::with_user_timestamp_now`] on the publisher side).
/// If the timestamp is invalid or not present, the result is none.
///
pub fn duration_since_timestamp(&self) -> Option<Duration> {
SystemTime::now()
.duration_since(UNIX_EPOCH + Duration::from_millis(self.user_timestamp?))
.inspect_err(|err| log::error!("Failed to calculate duration: {err}"))
.ok()
}
Comment thread
ladvoc marked this conversation as resolved.
}

impl DataTrackFrame {
/// Creates a frame from the given payload.
pub fn new(payload: impl Into<Bytes>) -> Self {
Self { payload: payload.into(), ..Default::default() }
}

/// Associates a user timestamp with the frame.
pub fn with_user_timestamp(mut self, value: u64) -> Self {
self.user_timestamp = Some(value);
self
}

/// Associates the current Unix timestamp (in milliseconds) with the frame.
pub fn with_user_timestamp_now(mut self) -> Self {
let timestamp = SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.inspect_err(|err| log::error!("Failed to get system time: {err}"))
.ok();
self.user_timestamp = timestamp;
self
}
}

impl fmt::Debug for DataTrackFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataTrackFrame")
.field("payload_len", &self.payload.len())
.field("user_timestamp", &self.user_timestamp)
.finish()
}
}

// MARK: - From implementations

impl From<Bytes> for DataTrackFrame {
fn from(bytes: Bytes) -> Self {
Self { payload: bytes, ..Default::default() }
}
}

impl From<&'static [u8]> for DataTrackFrame {
fn from(slice: &'static [u8]) -> Self {
Self { payload: slice.into(), ..Default::default() }
}
}

impl From<Vec<u8>> for DataTrackFrame {
fn from(vec: Vec<u8>) -> Self {
Self { payload: vec.into(), ..Default::default() }
}
}

impl From<Box<[u8]>> for DataTrackFrame {
fn from(slice: Box<[u8]>) -> Self {
Self { payload: slice.into(), ..Default::default() }
}
}
Loading
Loading