Initial moq-transport-01 support (#115)
Co-authored-by: Mike English <mike.english@gmail.com>
This commit is contained in:
parent
d55c4a80d1
commit
ddfe7963e6
|
@ -1107,6 +1107,7 @@ dependencies = [
|
||||||
name = "moq-transport"
|
name = "moq-transport"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::cli::Config;
|
use crate::cli::Config;
|
||||||
use anyhow::{self, Context};
|
use anyhow::{self, Context};
|
||||||
use moq_transport::cache::{broadcast, segment, track};
|
use moq_transport::cache::{broadcast, fragment, segment, track};
|
||||||
use moq_transport::VarInt;
|
use moq_transport::VarInt;
|
||||||
use mp4::{self, ReadBox};
|
use mp4::{self, ReadBox};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -44,11 +44,17 @@ impl Media {
|
||||||
let mut init_track = broadcast.create_track("0.mp4")?;
|
let mut init_track = broadcast.create_track("0.mp4")?;
|
||||||
let mut init_segment = init_track.create_segment(segment::Info {
|
let mut init_segment = init_track.create_segment(segment::Info {
|
||||||
sequence: VarInt::ZERO,
|
sequence: VarInt::ZERO,
|
||||||
priority: i32::MAX,
|
priority: 0,
|
||||||
expires: None,
|
expires: None,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
init_segment.write_chunk(init.into())?;
|
// Create a single fragment, optionally setting the size
|
||||||
|
let mut init_fragment = init_segment.create_fragment(fragment::Info {
|
||||||
|
sequence: VarInt::ZERO,
|
||||||
|
size: None, // size is only needed when we have multiple fragments.
|
||||||
|
})?;
|
||||||
|
|
||||||
|
init_fragment.write_chunk(init.into())?;
|
||||||
|
|
||||||
let mut tracks = HashMap::new();
|
let mut tracks = HashMap::new();
|
||||||
|
|
||||||
|
@ -128,7 +134,7 @@ impl Media {
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let mut segment = track.create_segment(segment::Info {
|
let mut segment = track.create_segment(segment::Info {
|
||||||
sequence: VarInt::ZERO,
|
sequence: VarInt::ZERO,
|
||||||
priority: i32::MAX,
|
priority: 0,
|
||||||
expires: None,
|
expires: None,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -211,8 +217,14 @@ impl Media {
|
||||||
let catalog_str = serde_json::to_string_pretty(&catalog)?;
|
let catalog_str = serde_json::to_string_pretty(&catalog)?;
|
||||||
log::info!("catalog: {}", catalog_str);
|
log::info!("catalog: {}", catalog_str);
|
||||||
|
|
||||||
|
// Create a single fragment for the segment.
|
||||||
|
let mut fragment = segment.create_fragment(fragment::Info {
|
||||||
|
sequence: VarInt::ZERO,
|
||||||
|
size: None, // Size is only needed when we have multiple fragments.
|
||||||
|
})?;
|
||||||
|
|
||||||
// Add the segment and add the fragment.
|
// Add the segment and add the fragment.
|
||||||
segment.write_chunk(catalog_str.into())?;
|
fragment.write_chunk(catalog_str.into())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -260,7 +272,7 @@ struct Track {
|
||||||
track: track::Publisher,
|
track: track::Publisher,
|
||||||
|
|
||||||
// The current segment
|
// The current segment
|
||||||
segment: Option<segment::Publisher>,
|
current: Option<fragment::Publisher>,
|
||||||
|
|
||||||
// The number of units per second.
|
// The number of units per second.
|
||||||
timescale: u64,
|
timescale: u64,
|
||||||
|
@ -274,16 +286,16 @@ impl Track {
|
||||||
Self {
|
Self {
|
||||||
track,
|
track,
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
segment: None,
|
current: None,
|
||||||
timescale,
|
timescale,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header(&mut self, raw: Vec<u8>, fragment: Fragment) -> anyhow::Result<()> {
|
pub fn header(&mut self, raw: Vec<u8>, fragment: Fragment) -> anyhow::Result<()> {
|
||||||
if let Some(segment) = self.segment.as_mut() {
|
if let Some(current) = self.current.as_mut() {
|
||||||
if !fragment.keyframe {
|
if !fragment.keyframe {
|
||||||
// Use the existing segment
|
// Use the existing segment
|
||||||
segment.write_chunk(raw.into())?;
|
current.write_chunk(raw.into())?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,7 +304,7 @@ impl Track {
|
||||||
|
|
||||||
// Compute the timestamp in milliseconds.
|
// Compute the timestamp in milliseconds.
|
||||||
// Overflows after 583 million years, so we're fine.
|
// Overflows after 583 million years, so we're fine.
|
||||||
let timestamp: i32 = fragment
|
let timestamp: u32 = fragment
|
||||||
.timestamp(self.timescale)
|
.timestamp(self.timescale)
|
||||||
.as_millis()
|
.as_millis()
|
||||||
.try_into()
|
.try_into()
|
||||||
|
@ -301,26 +313,34 @@ impl Track {
|
||||||
// Create a new segment.
|
// Create a new segment.
|
||||||
let mut segment = self.track.create_segment(segment::Info {
|
let mut segment = self.track.create_segment(segment::Info {
|
||||||
sequence: VarInt::try_from(self.sequence).context("sequence too large")?,
|
sequence: VarInt::try_from(self.sequence).context("sequence too large")?,
|
||||||
priority: timestamp, // newer segments are higher priority
|
|
||||||
|
// Newer segments are higher priority
|
||||||
|
priority: u32::MAX.checked_sub(timestamp).context("priority too large")?,
|
||||||
|
|
||||||
// Delete segments after 10s.
|
// Delete segments after 10s.
|
||||||
expires: Some(time::Duration::from_secs(10)),
|
expires: Some(time::Duration::from_secs(10)),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Create a single fragment for the segment that we will keep appending.
|
||||||
|
let mut fragment = segment.create_fragment(fragment::Info {
|
||||||
|
sequence: VarInt::ZERO,
|
||||||
|
size: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
self.sequence += 1;
|
self.sequence += 1;
|
||||||
|
|
||||||
// Insert the raw atom into the segment.
|
// Insert the raw atom into the segment.
|
||||||
segment.write_chunk(raw.into())?;
|
fragment.write_chunk(raw.into())?;
|
||||||
|
|
||||||
// Save for the next iteration
|
// Save for the next iteration
|
||||||
self.segment = Some(segment);
|
self.current = Some(fragment);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data(&mut self, raw: Vec<u8>) -> anyhow::Result<()> {
|
pub fn data(&mut self, raw: Vec<u8>) -> anyhow::Result<()> {
|
||||||
let segment = self.segment.as_mut().context("missing segment")?;
|
let fragment = self.current.as_mut().context("missing current fragment")?;
|
||||||
segment.write_chunk(raw.into())?;
|
fragment.write_chunk(raw.into())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,15 +37,15 @@ impl moq_transport::MoqError for RelayError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reason(&self) -> &str {
|
fn reason(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Transport(err) => err.reason(),
|
Self::Transport(err) => format!("transport error: {}", err.reason()),
|
||||||
Self::Cache(err) => err.reason(),
|
Self::Cache(err) => format!("cache error: {}", err.reason()),
|
||||||
Self::MoqApi(_err) => "api error",
|
Self::MoqApi(err) => format!("api error: {}", err),
|
||||||
Self::Url(_) => "url error",
|
Self::Url(err) => format!("url error: {}", err),
|
||||||
Self::MissingNode => "missing node",
|
Self::MissingNode => "missing node".to_owned(),
|
||||||
Self::WebTransportServer(_) => "server error",
|
Self::WebTransportServer(err) => format!("upstream server error: {}", err),
|
||||||
Self::WebTransportClient(_) => "upstream error",
|
Self::WebTransportClient(err) => format!("upstream client error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,3 +24,5 @@ indexmap = "2"
|
||||||
quinn = "0.10"
|
quinn = "0.10"
|
||||||
webtransport-quinn = "0.6"
|
webtransport-quinn = "0.6"
|
||||||
#webtransport-quinn = { path = "../../webtransport-rs/webtransport-quinn" }
|
#webtransport-quinn = { path = "../../webtransport-rs/webtransport-quinn" }
|
||||||
|
|
||||||
|
async-trait = "0.1"
|
||||||
|
|
|
@ -136,12 +136,12 @@ impl Publisher {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the next track requested by a subscriber.
|
/// Block until the next track requested by a subscriber.
|
||||||
pub async fn next_track(&mut self) -> Result<Option<track::Publisher>, CacheError> {
|
pub async fn next_track(&mut self) -> Result<track::Publisher, CacheError> {
|
||||||
loop {
|
loop {
|
||||||
let notify = {
|
let notify = {
|
||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
if state.has_next()? {
|
if state.has_next()? {
|
||||||
return Ok(Some(state.into_mut().next()));
|
return Ok(state.into_mut().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
state.changed()
|
state.changed()
|
||||||
|
|
|
@ -39,13 +39,13 @@ impl MoqError for CacheError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reason that is sent over the wire.
|
/// A reason that is sent over the wire.
|
||||||
fn reason(&self) -> &str {
|
fn reason(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Closed => "closed",
|
Self::Closed => "closed".to_owned(),
|
||||||
Self::Reset(_) => "reset",
|
Self::Reset(code) => format!("reset code: {}", code),
|
||||||
Self::Stop => "stop",
|
Self::Stop => "stop".to_owned(),
|
||||||
Self::NotFound => "not found",
|
Self::NotFound => "not found".to_owned(),
|
||||||
Self::Duplicate => "duplicate",
|
Self::Duplicate => "duplicate".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
//! A fragment is a stream of bytes with a header, split into a [Publisher] and [Subscriber] handle.
|
||||||
|
//!
|
||||||
|
//! A [Publisher] writes an ordered stream of bytes in chunks.
|
||||||
|
//! There's no framing, so these chunks can be of any size or position, and won't be maintained over the network.
|
||||||
|
//!
|
||||||
|
//! A [Subscriber] reads an ordered stream of bytes in chunks.
|
||||||
|
//! These chunks are returned directly from the QUIC connection, so they may be of any size or position.
|
||||||
|
//! You can clone the [Subscriber] and each will read a copy of of all future chunks. (fanout)
|
||||||
|
//!
|
||||||
|
//! The fragment is closed with [CacheError::Closed] when all publishers or subscribers are dropped.
|
||||||
|
use core::fmt;
|
||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use crate::VarInt;
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
use super::{CacheError, Watch};
|
||||||
|
|
||||||
|
/// Create a new segment with the given info.
|
||||||
|
pub fn new(info: Info) -> (Publisher, Subscriber) {
|
||||||
|
let state = Watch::new(State::default());
|
||||||
|
let info = Arc::new(info);
|
||||||
|
|
||||||
|
let publisher = Publisher::new(state.clone(), info.clone());
|
||||||
|
let subscriber = Subscriber::new(state, info);
|
||||||
|
|
||||||
|
(publisher, subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static information about the segment.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Info {
|
||||||
|
// The sequence number of the fragment within the segment.
|
||||||
|
// NOTE: These may be received out of order or with gaps.
|
||||||
|
pub sequence: VarInt,
|
||||||
|
|
||||||
|
// The size of the fragment, optionally None if this is the last fragment in a segment.
|
||||||
|
// TODO enforce this size.
|
||||||
|
pub size: Option<VarInt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
// The data that has been received thus far.
|
||||||
|
chunks: Vec<Bytes>,
|
||||||
|
|
||||||
|
// Set when the publisher is dropped.
|
||||||
|
closed: Result<(), CacheError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn close(&mut self, err: CacheError) -> Result<(), CacheError> {
|
||||||
|
self.closed.clone()?;
|
||||||
|
self.closed = Err(err);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes(&self) -> usize {
|
||||||
|
self.chunks.iter().map(|f| f.len()).sum::<usize>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
chunks: Vec::new(),
|
||||||
|
closed: Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for State {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// We don't want to print out the contents, so summarize.
|
||||||
|
f.debug_struct("State")
|
||||||
|
.field("chunks", &self.chunks.len().to_string())
|
||||||
|
.field("bytes", &self.bytes().to_string())
|
||||||
|
.field("closed", &self.closed)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to write data to a segment and notify subscribers.
|
||||||
|
pub struct Publisher {
|
||||||
|
// Mutable segment state.
|
||||||
|
state: Watch<State>,
|
||||||
|
|
||||||
|
// Immutable segment state.
|
||||||
|
info: Arc<Info>,
|
||||||
|
|
||||||
|
// Closes the segment when all Publishers are dropped.
|
||||||
|
_dropped: Arc<Dropped>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Publisher {
|
||||||
|
fn new(state: Watch<State>, info: Arc<Info>) -> Self {
|
||||||
|
let _dropped = Arc::new(Dropped::new(state.clone()));
|
||||||
|
Self { state, info, _dropped }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a new chunk of bytes.
|
||||||
|
pub fn write_chunk(&mut self, chunk: Bytes) -> Result<(), CacheError> {
|
||||||
|
let mut state = self.state.lock_mut();
|
||||||
|
state.closed.clone()?;
|
||||||
|
state.chunks.push(chunk);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the segment with an error.
|
||||||
|
pub fn close(self, err: CacheError) -> Result<(), CacheError> {
|
||||||
|
self.state.lock_mut().close(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Publisher {
|
||||||
|
type Target = Info;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Publisher {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Publisher")
|
||||||
|
.field("state", &self.state)
|
||||||
|
.field("info", &self.info)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notified when a segment has new data available.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Subscriber {
|
||||||
|
// Modify the segment state.
|
||||||
|
state: Watch<State>,
|
||||||
|
|
||||||
|
// Immutable segment state.
|
||||||
|
info: Arc<Info>,
|
||||||
|
|
||||||
|
// The number of chunks that we've read.
|
||||||
|
// NOTE: Cloned subscribers inherit this index, but then run in parallel.
|
||||||
|
index: usize,
|
||||||
|
|
||||||
|
// Dropped when all Subscribers are dropped.
|
||||||
|
_dropped: Arc<Dropped>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Subscriber {
|
||||||
|
fn new(state: Watch<State>, info: Arc<Info>) -> Self {
|
||||||
|
let _dropped = Arc::new(Dropped::new(state.clone()));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
info,
|
||||||
|
index: 0,
|
||||||
|
_dropped,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block until the next chunk of bytes is available.
|
||||||
|
pub async fn read_chunk(&mut self) -> Result<Option<Bytes>, CacheError> {
|
||||||
|
loop {
|
||||||
|
let notify = {
|
||||||
|
let state = self.state.lock();
|
||||||
|
if self.index < state.chunks.len() {
|
||||||
|
let chunk = state.chunks[self.index].clone();
|
||||||
|
self.index += 1;
|
||||||
|
return Ok(Some(chunk));
|
||||||
|
}
|
||||||
|
|
||||||
|
match &state.closed {
|
||||||
|
Err(CacheError::Closed) => return Ok(None),
|
||||||
|
Err(err) => return Err(err.clone()),
|
||||||
|
Ok(()) => state.changed(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
notify.await; // Try again when the state changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Subscriber {
|
||||||
|
type Target = Info;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Subscriber {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Subscriber")
|
||||||
|
.field("state", &self.state)
|
||||||
|
.field("info", &self.info)
|
||||||
|
.field("index", &self.index)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Dropped {
|
||||||
|
// Modify the segment state.
|
||||||
|
state: Watch<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dropped {
|
||||||
|
fn new(state: Watch<State>) -> Self {
|
||||||
|
Self { state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Dropped {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.state.lock_mut().close(CacheError::Closed).ok();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,17 @@
|
||||||
//! Allows a publisher to push updates, automatically caching and fanning it out to any subscribers.
|
//! Allows a publisher to push updates, automatically caching and fanning it out to any subscribers.
|
||||||
//!
|
//!
|
||||||
//! The naming scheme doesn't match the spec because it's vague and confusing.
|
//! The hierarchy is: [broadcast] -> [track] -> [segment] -> [fragment] -> [Bytes](bytes::Bytes)
|
||||||
//! The hierarchy is: [broadcast] -> [track] -> [segment] -> [Bytes](bytes::Bytes)
|
//!
|
||||||
|
//! The naming scheme doesn't match the spec because it's more strict, and bikeshedding of course:
|
||||||
|
//!
|
||||||
|
//! - [broadcast] is kinda like "track namespace"
|
||||||
|
//! - [track] is "track"
|
||||||
|
//! - [segment] is "group" but MUST use a single stream.
|
||||||
|
//! - [fragment] is "object" but MUST have the same properties as the segment.
|
||||||
|
|
||||||
pub mod broadcast;
|
pub mod broadcast;
|
||||||
mod error;
|
mod error;
|
||||||
|
pub mod fragment;
|
||||||
pub mod segment;
|
pub mod segment;
|
||||||
pub mod track;
|
pub mod track;
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
//! A segment is a stream of bytes with a header, split into a [Publisher] and [Subscriber] handle.
|
//! A segment is a stream of fragments with a header, split into a [Publisher] and [Subscriber] handle.
|
||||||
//!
|
//!
|
||||||
//! A [Publisher] writes an ordered stream of bytes in chunks.
|
//! A [Publisher] writes an ordered stream of fragments.
|
||||||
//! There's no framing, so these chunks can be of any size or position, and won't be maintained over the network.
|
//! Each fragment can have a sequence number, allowing the subscriber to detect gaps fragments.
|
||||||
//!
|
//!
|
||||||
//! A [Subscriber] reads an ordered stream of bytes in chunks.
|
//! A [Subscriber] reads an ordered stream of fragments.
|
||||||
//! These chunks are returned directly from the QUIC connection, so they may be of any size or position.
|
//! The subscriber can be cloned, in which case each subscriber receives a copy of each fragment. (fanout)
|
||||||
//! A closed [Subscriber] will receive a copy of all future chunks. (fanout)
|
|
||||||
//!
|
//!
|
||||||
//! The segment is closed with [CacheError::Closed] when all publishers or subscribers are dropped.
|
//! The segment is closed with [CacheError::Closed] when all publishers or subscribers are dropped.
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::{ops::Deref, sync::Arc, time};
|
use std::{ops::Deref, sync::Arc, time};
|
||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
use bytes::Bytes;
|
|
||||||
|
|
||||||
use super::{CacheError, Watch};
|
use super::{fragment, CacheError, Watch};
|
||||||
|
|
||||||
/// Create a new segment with the given info.
|
/// Create a new segment with the given info.
|
||||||
pub fn new(info: Info) -> (Publisher, Subscriber) {
|
pub fn new(info: Info) -> (Publisher, Subscriber) {
|
||||||
|
@ -31,10 +29,11 @@ pub fn new(info: Info) -> (Publisher, Subscriber) {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Info {
|
pub struct Info {
|
||||||
// The sequence number of the segment within the track.
|
// The sequence number of the segment within the track.
|
||||||
|
// NOTE: These may be received out of order or with gaps.
|
||||||
pub sequence: VarInt,
|
pub sequence: VarInt,
|
||||||
|
|
||||||
// The priority of the segment within the BROADCAST.
|
// The priority of the segment within the BROADCAST.
|
||||||
pub priority: i32,
|
pub priority: u32,
|
||||||
|
|
||||||
// Cache the segment for at most this long.
|
// Cache the segment for at most this long.
|
||||||
pub expires: Option<time::Duration>,
|
pub expires: Option<time::Duration>,
|
||||||
|
@ -42,7 +41,7 @@ pub struct Info {
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
// The data that has been received thus far.
|
// The data that has been received thus far.
|
||||||
data: Vec<Bytes>,
|
fragments: Vec<fragment::Subscriber>,
|
||||||
|
|
||||||
// Set when the publisher is dropped.
|
// Set when the publisher is dropped.
|
||||||
closed: Result<(), CacheError>,
|
closed: Result<(), CacheError>,
|
||||||
|
@ -59,7 +58,7 @@ impl State {
|
||||||
impl Default for State {
|
impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Vec::new(),
|
fragments: Vec::new(),
|
||||||
closed: Ok(()),
|
closed: Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,12 +66,8 @@ impl Default for State {
|
||||||
|
|
||||||
impl fmt::Debug for State {
|
impl fmt::Debug for State {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
// We don't want to print out the contents, so summarize.
|
|
||||||
let size = self.data.iter().map(|chunk| chunk.len()).sum::<usize>();
|
|
||||||
let data = format!("size={} chunks={}", size, self.data.len());
|
|
||||||
|
|
||||||
f.debug_struct("State")
|
f.debug_struct("State")
|
||||||
.field("data", &data)
|
.field("fragments", &self.fragments)
|
||||||
.field("closed", &self.closed)
|
.field("closed", &self.closed)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
@ -96,14 +91,20 @@ impl Publisher {
|
||||||
Self { state, info, _dropped }
|
Self { state, info, _dropped }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a new chunk of bytes.
|
/// Write a fragment
|
||||||
pub fn write_chunk(&mut self, data: Bytes) -> Result<(), CacheError> {
|
pub fn push_fragment(&mut self, fragment: fragment::Subscriber) -> Result<(), CacheError> {
|
||||||
let mut state = self.state.lock_mut();
|
let mut state = self.state.lock_mut();
|
||||||
state.closed.clone()?;
|
state.closed.clone()?;
|
||||||
state.data.push(data);
|
state.fragments.push(fragment);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_fragment(&mut self, fragment: fragment::Info) -> Result<fragment::Publisher, CacheError> {
|
||||||
|
let (publisher, subscriber) = fragment::new(fragment);
|
||||||
|
self.push_fragment(subscriber)?;
|
||||||
|
Ok(publisher)
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the segment with an error.
|
/// Close the segment with an error.
|
||||||
pub fn close(self, err: CacheError) -> Result<(), CacheError> {
|
pub fn close(self, err: CacheError) -> Result<(), CacheError> {
|
||||||
self.state.lock_mut().close(err)
|
self.state.lock_mut().close(err)
|
||||||
|
@ -157,14 +158,14 @@ impl Subscriber {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the next chunk of bytes is available.
|
/// Block until the next chunk of bytes is available.
|
||||||
pub async fn read_chunk(&mut self) -> Result<Option<Bytes>, CacheError> {
|
pub async fn next_fragment(&mut self) -> Result<Option<fragment::Subscriber>, CacheError> {
|
||||||
loop {
|
loop {
|
||||||
let notify = {
|
let notify = {
|
||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
if self.index < state.data.len() {
|
if self.index < state.fragments.len() {
|
||||||
let chunk = state.data[self.index].clone();
|
let fragment = state.fragments[self.index].clone();
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
return Ok(Some(chunk));
|
return Ok(Some(fragment));
|
||||||
}
|
}
|
||||||
|
|
||||||
match &state.closed {
|
match &state.closed {
|
||||||
|
|
|
@ -206,7 +206,7 @@ impl Subscriber {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block until the next segment arrives, or return None if the track is [CacheError::Closed].
|
/// Block until the next segment arrives
|
||||||
pub async fn next_segment(&mut self) -> Result<Option<segment::Subscriber>, CacheError> {
|
pub async fn next_segment(&mut self) -> Result<Option<segment::Subscriber>, CacheError> {
|
||||||
loop {
|
loop {
|
||||||
let notify = {
|
let notify = {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{BoundsExceeded, VarInt};
|
use super::{BoundsExceeded, VarInt};
|
||||||
use std::str;
|
use std::{io, str};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -7,6 +7,13 @@ use thiserror::Error;
|
||||||
// TODO Use trait aliases when they're stable, or add these bounds to every method.
|
// TODO Use trait aliases when they're stable, or add these bounds to every method.
|
||||||
pub trait AsyncRead: tokio::io::AsyncRead + Unpin + Send {}
|
pub trait AsyncRead: tokio::io::AsyncRead + Unpin + Send {}
|
||||||
impl AsyncRead for webtransport_quinn::RecvStream {}
|
impl AsyncRead for webtransport_quinn::RecvStream {}
|
||||||
|
impl<T> AsyncRead for tokio::io::Take<&mut T> where T: AsyncRead {}
|
||||||
|
impl<T: AsRef<[u8]> + Unpin + Send> AsyncRead for io::Cursor<T> {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait Decode: Sized {
|
||||||
|
async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError>;
|
||||||
|
}
|
||||||
|
|
||||||
/// A decode error.
|
/// A decode error.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -17,12 +24,32 @@ pub enum DecodeError {
|
||||||
#[error("invalid string")]
|
#[error("invalid string")]
|
||||||
InvalidString(#[from] str::Utf8Error),
|
InvalidString(#[from] str::Utf8Error),
|
||||||
|
|
||||||
#[error("invalid type: {0:?}")]
|
#[error("invalid message: {0:?}")]
|
||||||
InvalidType(VarInt),
|
InvalidMessage(VarInt),
|
||||||
|
|
||||||
|
#[error("invalid role: {0:?}")]
|
||||||
|
InvalidRole(VarInt),
|
||||||
|
|
||||||
|
#[error("invalid subscribe location")]
|
||||||
|
InvalidSubscribeLocation,
|
||||||
|
|
||||||
#[error("varint bounds exceeded")]
|
#[error("varint bounds exceeded")]
|
||||||
BoundsExceeded(#[from] BoundsExceeded),
|
BoundsExceeded(#[from] BoundsExceeded),
|
||||||
|
|
||||||
|
// TODO move these to ParamError
|
||||||
|
#[error("duplicate parameter")]
|
||||||
|
DupliateParameter,
|
||||||
|
|
||||||
|
#[error("missing parameter")]
|
||||||
|
MissingParameter,
|
||||||
|
|
||||||
|
#[error("invalid parameter")]
|
||||||
|
InvalidParameter,
|
||||||
|
|
||||||
#[error("io error: {0}")]
|
#[error("io error: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
// Used to signal that the stream has ended.
|
||||||
|
#[error("no more messages")]
|
||||||
|
Final,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,12 @@ use thiserror::Error;
|
||||||
// TODO Use trait aliases when they're stable, or add these bounds to every method.
|
// TODO Use trait aliases when they're stable, or add these bounds to every method.
|
||||||
pub trait AsyncWrite: tokio::io::AsyncWrite + Unpin + Send {}
|
pub trait AsyncWrite: tokio::io::AsyncWrite + Unpin + Send {}
|
||||||
impl AsyncWrite for webtransport_quinn::SendStream {}
|
impl AsyncWrite for webtransport_quinn::SendStream {}
|
||||||
|
impl AsyncWrite for Vec<u8> {}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait Encode: Sized {
|
||||||
|
async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError>;
|
||||||
|
}
|
||||||
|
|
||||||
/// An encode error.
|
/// An encode error.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
mod decode;
|
mod decode;
|
||||||
mod encode;
|
mod encode;
|
||||||
|
mod params;
|
||||||
mod string;
|
mod string;
|
||||||
mod varint;
|
mod varint;
|
||||||
|
|
||||||
pub use decode::*;
|
pub use decode::*;
|
||||||
pub use encode::*;
|
pub use encode::*;
|
||||||
|
pub use params::*;
|
||||||
pub use string::*;
|
pub use string::*;
|
||||||
pub use varint::*;
|
pub use varint::*;
|
||||||
|
|
|
@ -1,69 +1,85 @@
|
||||||
use std::cmp::min;
|
use std::io::Cursor;
|
||||||
|
use std::{cmp::max, collections::HashMap};
|
||||||
|
|
||||||
use crate::VarInt;
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use super::{AsyncRead, AsyncWrite, DecodeError, EncodeError};
|
use crate::coding::{AsyncRead, AsyncWrite, Decode, Encode};
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
|
|
||||||
// I hate this parameter encoding so much.
|
use crate::{
|
||||||
// i hate it i hate it i hate it
|
coding::{DecodeError, EncodeError},
|
||||||
|
VarInt,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO Use #[async_trait] so we can do Param<VarInt> instead.
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct ParamInt(pub VarInt);
|
pub struct Params(pub HashMap<VarInt, Vec<u8>>);
|
||||||
|
|
||||||
impl ParamInt {
|
#[async_trait::async_trait]
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
impl Decode for Params {
|
||||||
// Why do we have a redundant size in front of each VarInt?
|
async fn decode<R: AsyncRead>(mut r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let size = VarInt::decode(r).await?;
|
let mut params = HashMap::new();
|
||||||
let mut take = r.take(size.into_inner());
|
|
||||||
let value = VarInt::decode(&mut take).await?;
|
|
||||||
|
|
||||||
// Like seriously why do I have to check if the VarInt length mismatches.
|
// I hate this shit so much; let me encode my role and get on with my life.
|
||||||
if take.limit() != 0 {
|
let count = VarInt::decode(r).await?;
|
||||||
return Err(DecodeError::InvalidSize);
|
for _ in 0..count.into_inner() {
|
||||||
|
let kind = VarInt::decode(r).await?;
|
||||||
|
if params.contains_key(&kind) {
|
||||||
|
return Err(DecodeError::DupliateParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = VarInt::decode(r).await?;
|
||||||
|
|
||||||
|
// Don't allocate the entire requested size to avoid a possible attack
|
||||||
|
// Instead, we allocate up to 1024 and keep appending as we read further.
|
||||||
|
let mut pr = r.take(size.into_inner());
|
||||||
|
let mut buf = Vec::with_capacity(max(1024, pr.limit() as usize));
|
||||||
|
pr.read_to_end(&mut buf).await?;
|
||||||
|
params.insert(kind, buf);
|
||||||
|
|
||||||
|
r = pr.into_inner();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(value))
|
Ok(Params(params))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
#[async_trait::async_trait]
|
||||||
// Seriously why do I have to compute the size.
|
impl Encode for Params {
|
||||||
let size = self.0.size();
|
async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
VarInt::try_from(size)?.encode(w).await?;
|
VarInt::try_from(self.0.len())?.encode(w).await?;
|
||||||
|
|
||||||
self.0.encode(w).await?;
|
for (kind, value) in self.0.iter() {
|
||||||
|
kind.encode(w).await?;
|
||||||
|
VarInt::try_from(value.len())?.encode(w).await?;
|
||||||
|
w.write_all(value).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ParamBytes(pub Vec<u8>);
|
impl Params {
|
||||||
|
pub fn new() -> Self {
|
||||||
impl ParamBytes {
|
Self::default()
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
|
||||||
let size = VarInt::decode(r).await?;
|
|
||||||
let mut take = r.take(size.into_inner());
|
|
||||||
let mut buf = Vec::with_capacity(min(take.limit() as usize, 1024));
|
|
||||||
take.read_to_end(&mut buf).await?;
|
|
||||||
|
|
||||||
Ok(Self(buf))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn set<P: Encode>(&mut self, kind: VarInt, p: P) -> Result<(), EncodeError> {
|
||||||
let size = VarInt::try_from(self.0.len())?;
|
let mut value = Vec::new();
|
||||||
size.encode(w).await?;
|
p.encode(&mut value).await?;
|
||||||
w.write_all(&self.0).await?;
|
self.0.insert(kind, value);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ParamUnknown {}
|
pub fn has(&self, kind: VarInt) -> bool {
|
||||||
|
self.0.contains_key(&kind)
|
||||||
|
}
|
||||||
|
|
||||||
impl ParamUnknown {
|
pub async fn get<P: Decode>(&mut self, kind: VarInt) -> Result<Option<P>, DecodeError> {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<(), DecodeError> {
|
if let Some(value) = self.0.remove(&kind) {
|
||||||
// Really? Is there no way to advance without reading?
|
let mut cursor = Cursor::new(value);
|
||||||
ParamBytes::decode(r).await?;
|
Ok(Some(P::decode(&mut cursor).await?))
|
||||||
Ok(())
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,25 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use crate::VarInt;
|
use crate::VarInt;
|
||||||
|
|
||||||
use super::{DecodeError, EncodeError};
|
use super::{Decode, DecodeError, Encode, EncodeError};
|
||||||
|
|
||||||
/// Encode a string with a varint length prefix.
|
#[async_trait::async_trait]
|
||||||
pub async fn encode_string<W: AsyncWrite>(s: &str, w: &mut W) -> Result<(), EncodeError> {
|
impl Encode for String {
|
||||||
let size = VarInt::try_from(s.len())?;
|
async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
size.encode(w).await?;
|
let size = VarInt::try_from(self.len())?;
|
||||||
w.write_all(s.as_ref()).await?;
|
size.encode(w).await?;
|
||||||
Ok(())
|
w.write_all(self.as_ref()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode a string with a varint length prefix.
|
#[async_trait::async_trait]
|
||||||
pub async fn decode_string<R: AsyncRead>(r: &mut R) -> Result<String, DecodeError> {
|
impl Decode for String {
|
||||||
let size = VarInt::decode(r).await?.into_inner();
|
/// Decode a string with a varint length prefix.
|
||||||
let mut str = String::with_capacity(min(1024, size) as usize);
|
async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
r.take(size).read_to_string(&mut str).await?;
|
let size = VarInt::decode(r).await?.into_inner();
|
||||||
Ok(str)
|
let mut str = String::with_capacity(min(1024, size) as usize);
|
||||||
|
r.take(size).read_to_string(&mut str).await?;
|
||||||
|
Ok(str)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use super::{DecodeError, EncodeError};
|
use super::{Decode, DecodeError, Encode, EncodeError};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
|
||||||
#[error("value out of range")]
|
#[error("value out of range")]
|
||||||
|
@ -164,14 +164,23 @@ impl fmt::Display for VarInt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VarInt {
|
#[async_trait::async_trait]
|
||||||
|
impl Decode for VarInt {
|
||||||
/// Decode a varint from the given reader.
|
/// Decode a varint from the given reader.
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let mut buf = [0u8; 8];
|
let b = r.read_u8().await?;
|
||||||
r.read_exact(buf[0..1].as_mut()).await?;
|
Self::decode_byte(b, r).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tag = buf[0] >> 6;
|
impl VarInt {
|
||||||
buf[0] &= 0b0011_1111;
|
/// Decode a varint given the first byte, reading the rest as needed.
|
||||||
|
/// This is silly but useful for determining if the stream has ended.
|
||||||
|
pub async fn decode_byte<R: AsyncRead>(b: u8, r: &mut R) -> Result<Self, DecodeError> {
|
||||||
|
let tag = b >> 6;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 8];
|
||||||
|
buf[0] = b & 0b0011_1111;
|
||||||
|
|
||||||
let x = match tag {
|
let x = match tag {
|
||||||
0b00 => u64::from(buf[0]),
|
0b00 => u64::from(buf[0]),
|
||||||
|
@ -192,9 +201,12 @@ impl VarInt {
|
||||||
|
|
||||||
Ok(Self(x))
|
Ok(Self(x))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Encode for VarInt {
|
||||||
/// Encode a varint to the given writer.
|
/// Encode a varint to the given writer.
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
let x = self.0;
|
let x = self.0;
|
||||||
if x < 2u64.pow(6) {
|
if x < 2u64.pow(6) {
|
||||||
w.write_u8(x as u8).await?;
|
w.write_u8(x as u8).await?;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
pub trait MoqError {
|
pub trait MoqError {
|
||||||
/// An integer code that is sent over the wire.
|
/// An integer code that is sent over the wire.
|
||||||
fn code(&self) -> u32;
|
fn code(&self) -> u32;
|
||||||
fn reason(&self) -> &str;
|
|
||||||
|
/// An optional reason sometimes sent over the wire.
|
||||||
|
fn reason(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
//! The specification is a work in progress and will change.
|
//! The specification is a work in progress and will change.
|
||||||
//! See the [specification](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/) and [github](https://github.com/moq-wg/moq-transport) for any updates.
|
//! See the [specification](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/) and [github](https://github.com/moq-wg/moq-transport) for any updates.
|
||||||
//!
|
//!
|
||||||
//! **FORKED**: This is implementation makes extensive changes to the protocol.
|
//! **FORKED**: This implementation makes some changes to the protocol.
|
||||||
//! See [KIXEL_00](crate::setup::Version::KIXEL_00) for a list of differences.
|
//! See [KIXEL_01](crate::setup::Version::KIXEL_01) for a list of differences.
|
||||||
//! Many of these will get merged into the specification, so don't panic.
|
//! Many of these will get merged into the specification, so don't panic.
|
||||||
mod coding;
|
mod coding;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
|
@ -1,22 +1,29 @@
|
||||||
use crate::coding::{decode_string, encode_string, DecodeError, EncodeError};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, Params};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
/// Sent by the publisher to announce the availability of a group of tracks.
|
/// Sent by the publisher to announce the availability of a group of tracks.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Announce {
|
pub struct Announce {
|
||||||
// The track namespace
|
/// The track namespace
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
|
|
||||||
|
/// Optional parameters
|
||||||
|
pub params: Params,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Announce {
|
impl Announce {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let namespace = decode_string(r).await?;
|
let namespace = String::decode(r).await?;
|
||||||
Ok(Self { namespace })
|
let params = Params::decode(r).await?;
|
||||||
|
|
||||||
|
Ok(Self { namespace, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
encode_string(&self.namespace, w).await?;
|
self.namespace.encode(w).await?;
|
||||||
|
self.params.encode(w).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::coding::{decode_string, encode_string, AsyncRead, AsyncWrite, DecodeError, EncodeError};
|
use crate::coding::{AsyncRead, AsyncWrite, Decode, DecodeError, Encode, EncodeError};
|
||||||
|
|
||||||
/// Sent by the subscriber to accept an Announce.
|
/// Sent by the subscriber to accept an Announce.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -10,11 +10,11 @@ pub struct AnnounceOk {
|
||||||
|
|
||||||
impl AnnounceOk {
|
impl AnnounceOk {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let namespace = decode_string(r).await?;
|
let namespace = String::decode(r).await?;
|
||||||
Ok(Self { namespace })
|
Ok(Self { namespace })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
encode_string(&self.namespace, w).await
|
self.namespace.encode(w).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::coding::{decode_string, encode_string, DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
/// Sent by the subscriber to reject an Announce.
|
/// Sent by the subscriber to reject an Announce.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AnnounceReset {
|
pub struct AnnounceError {
|
||||||
// Echo back the namespace that was reset
|
// Echo back the namespace that was reset
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ pub struct AnnounceReset {
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnnounceReset {
|
impl AnnounceError {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let namespace = decode_string(r).await?;
|
let namespace = String::decode(r).await?;
|
||||||
let code = VarInt::decode(r).await?.try_into()?;
|
let code = VarInt::decode(r).await?.try_into()?;
|
||||||
let reason = decode_string(r).await?;
|
let reason = String::decode(r).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
namespace,
|
namespace,
|
||||||
|
@ -29,9 +29,9 @@ impl AnnounceReset {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
encode_string(&self.namespace, w).await?;
|
self.namespace.encode(w).await?;
|
||||||
VarInt::from_u32(self.code).encode(w).await?;
|
VarInt::from_u32(self.code).encode(w).await?;
|
||||||
encode_string(&self.reason, w).await?;
|
self.reason.encode(w).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::coding::{decode_string, encode_string, DecodeError, EncodeError};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
@ -10,11 +10,11 @@ pub struct GoAway {
|
||||||
|
|
||||||
impl GoAway {
|
impl GoAway {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let url = decode_string(r).await?;
|
let url = String::decode(r).await?;
|
||||||
Ok(Self { url })
|
Ok(Self { url })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
encode_string(&self.url, w).await
|
self.url.encode(w).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,17 @@
|
||||||
//!
|
//!
|
||||||
//! Messages sent by the publisher:
|
//! Messages sent by the publisher:
|
||||||
//! - [Announce]
|
//! - [Announce]
|
||||||
//! - [AnnounceReset]
|
//! - [Unannounce]
|
||||||
//! - [SubscribeOk]
|
//! - [SubscribeOk]
|
||||||
|
//! - [SubscribeError]
|
||||||
//! - [SubscribeReset]
|
//! - [SubscribeReset]
|
||||||
//! - [Object]
|
//! - [Object]
|
||||||
//!
|
//!
|
||||||
//! Messages sent by the subscriber:
|
//! Messages sent by the subscriber:
|
||||||
//! - [Subscribe]
|
//! - [Subscribe]
|
||||||
//! - [SubscribeStop]
|
//! - [Unsubscribe]
|
||||||
//! - [AnnounceOk]
|
//! - [AnnounceOk]
|
||||||
//! - [AnnounceStop]
|
//! - [AnnounceError]
|
||||||
//!
|
//!
|
||||||
//! Example flow:
|
//! Example flow:
|
||||||
//! ```test
|
//! ```test
|
||||||
|
@ -32,26 +33,30 @@
|
||||||
mod announce;
|
mod announce;
|
||||||
mod announce_ok;
|
mod announce_ok;
|
||||||
mod announce_reset;
|
mod announce_reset;
|
||||||
mod announce_stop;
|
|
||||||
mod go_away;
|
mod go_away;
|
||||||
mod object;
|
mod object;
|
||||||
mod subscribe;
|
mod subscribe;
|
||||||
|
mod subscribe_error;
|
||||||
|
mod subscribe_fin;
|
||||||
mod subscribe_ok;
|
mod subscribe_ok;
|
||||||
mod subscribe_reset;
|
mod subscribe_reset;
|
||||||
mod subscribe_stop;
|
mod unannounce;
|
||||||
|
mod unsubscribe;
|
||||||
|
|
||||||
pub use announce::*;
|
pub use announce::*;
|
||||||
pub use announce_ok::*;
|
pub use announce_ok::*;
|
||||||
pub use announce_reset::*;
|
pub use announce_reset::*;
|
||||||
pub use announce_stop::*;
|
|
||||||
pub use go_away::*;
|
pub use go_away::*;
|
||||||
pub use object::*;
|
pub use object::*;
|
||||||
pub use subscribe::*;
|
pub use subscribe::*;
|
||||||
|
pub use subscribe_error::*;
|
||||||
|
pub use subscribe_fin::*;
|
||||||
pub use subscribe_ok::*;
|
pub use subscribe_ok::*;
|
||||||
pub use subscribe_reset::*;
|
pub use subscribe_reset::*;
|
||||||
pub use subscribe_stop::*;
|
pub use unannounce::*;
|
||||||
|
pub use unsubscribe::*;
|
||||||
|
|
||||||
use crate::coding::{DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
@ -76,7 +81,7 @@ macro_rules! message_types {
|
||||||
let msg = $name::decode(r).await?;
|
let msg = $name::decode(r).await?;
|
||||||
Ok(Self::$name(msg))
|
Ok(Self::$name(msg))
|
||||||
})*
|
})*
|
||||||
_ => Err(DecodeError::InvalidType(t)),
|
_ => Err(DecodeError::InvalidMessage(t)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,15 +132,28 @@ macro_rules! message_types {
|
||||||
message_types! {
|
message_types! {
|
||||||
// NOTE: Object and Setup are in other modules.
|
// NOTE: Object and Setup are in other modules.
|
||||||
// Object = 0x0
|
// Object = 0x0
|
||||||
// SetupClient = 0x1
|
// ObjectUnbounded = 0x2
|
||||||
// SetupServer = 0x2
|
// SetupClient = 0x40
|
||||||
|
// SetupServer = 0x41
|
||||||
|
|
||||||
|
// SUBSCRIBE family, sent by subscriber
|
||||||
Subscribe = 0x3,
|
Subscribe = 0x3,
|
||||||
|
Unsubscribe = 0xa,
|
||||||
|
|
||||||
|
// SUBSCRIBE family, sent by publisher
|
||||||
SubscribeOk = 0x4,
|
SubscribeOk = 0x4,
|
||||||
SubscribeReset = 0x5,
|
SubscribeError = 0x5,
|
||||||
SubscribeStop = 0x15,
|
SubscribeFin = 0xb,
|
||||||
|
SubscribeReset = 0xc,
|
||||||
|
|
||||||
|
// ANNOUNCE family, sent by publisher
|
||||||
Announce = 0x6,
|
Announce = 0x6,
|
||||||
|
Unannounce = 0x9,
|
||||||
|
|
||||||
|
// ANNOUNCE family, sent by subscriber
|
||||||
AnnounceOk = 0x7,
|
AnnounceOk = 0x7,
|
||||||
AnnounceReset = 0x8,
|
AnnounceError = 0x8,
|
||||||
AnnounceStop = 0x18,
|
|
||||||
|
// Misc
|
||||||
GoAway = 0x10,
|
GoAway = 0x10,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::time;
|
use std::{io, time};
|
||||||
|
|
||||||
use crate::coding::{DecodeError, EncodeError, VarInt};
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
/// Sent by the publisher as the header of each data stream.
|
/// Sent by the publisher as the header of each data stream.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -13,47 +13,75 @@ pub struct Object {
|
||||||
pub track: VarInt,
|
pub track: VarInt,
|
||||||
|
|
||||||
// The sequence number within the track.
|
// The sequence number within the track.
|
||||||
|
pub group: VarInt,
|
||||||
|
|
||||||
|
// The sequence number within the group.
|
||||||
pub sequence: VarInt,
|
pub sequence: VarInt,
|
||||||
|
|
||||||
// The priority, where **larger** values are sent first.
|
// The priority, where **smaller** values are sent first.
|
||||||
// Proposal: int32 instead of a varint.
|
pub priority: u32,
|
||||||
pub priority: i32,
|
|
||||||
|
|
||||||
// Cache the object for at most this many seconds.
|
// Cache the object for at most this many seconds.
|
||||||
// Zero means never expire.
|
// Zero means never expire.
|
||||||
pub expires: Option<time::Duration>,
|
pub expires: Option<time::Duration>,
|
||||||
|
|
||||||
|
/// An optional size, allowing multiple OBJECTs on the same stream.
|
||||||
|
pub size: Option<VarInt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let typ = VarInt::decode(r).await?;
|
// Try reading the first byte, returning a special error if the stream naturally ended.
|
||||||
if typ.into_inner() != 0 {
|
let typ = match r.read_u8().await {
|
||||||
return Err(DecodeError::InvalidType(typ));
|
Ok(b) => VarInt::decode_byte(b, r).await?,
|
||||||
}
|
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Err(DecodeError::Final),
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
// NOTE: size has been omitted
|
let size_present = match typ.into_inner() {
|
||||||
|
0 => false,
|
||||||
|
2 => true,
|
||||||
|
_ => return Err(DecodeError::InvalidMessage(typ)),
|
||||||
|
};
|
||||||
|
|
||||||
let track = VarInt::decode(r).await?;
|
let track = VarInt::decode(r).await?;
|
||||||
|
let group = VarInt::decode(r).await?;
|
||||||
let sequence = VarInt::decode(r).await?;
|
let sequence = VarInt::decode(r).await?;
|
||||||
let priority = r.read_i32().await?; // big-endian
|
let priority = VarInt::decode(r).await?.try_into()?;
|
||||||
|
|
||||||
let expires = match VarInt::decode(r).await?.into_inner() {
|
let expires = match VarInt::decode(r).await?.into_inner() {
|
||||||
0 => None,
|
0 => None,
|
||||||
secs => Some(time::Duration::from_secs(secs)),
|
secs => Some(time::Duration::from_secs(secs)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The presence of the size field depends on the type.
|
||||||
|
let size = match size_present {
|
||||||
|
true => Some(VarInt::decode(r).await?),
|
||||||
|
false => None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
track,
|
track,
|
||||||
|
group,
|
||||||
sequence,
|
sequence,
|
||||||
priority,
|
priority,
|
||||||
expires,
|
expires,
|
||||||
|
size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
VarInt::ZERO.encode(w).await?;
|
// The kind changes based on the presence of the size.
|
||||||
|
let kind = match self.size {
|
||||||
|
Some(_) => VarInt::from_u32(2),
|
||||||
|
None => VarInt::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
kind.encode(w).await?;
|
||||||
self.track.encode(w).await?;
|
self.track.encode(w).await?;
|
||||||
|
self.group.encode(w).await?;
|
||||||
self.sequence.encode(w).await?;
|
self.sequence.encode(w).await?;
|
||||||
w.write_i32(self.priority).await?;
|
VarInt::from_u32(self.priority).encode(w).await?;
|
||||||
|
|
||||||
// Round up if there's any decimal points.
|
// Round up if there's any decimal points.
|
||||||
let expires = match self.expires {
|
let expires = match self.expires {
|
||||||
|
@ -65,6 +93,10 @@ impl Object {
|
||||||
|
|
||||||
VarInt::try_from(expires)?.encode(w).await?;
|
VarInt::try_from(expires)?.encode(w).await?;
|
||||||
|
|
||||||
|
if let Some(size) = self.size {
|
||||||
|
size.encode(w).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::coding::{decode_string, encode_string, DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, Params, VarInt};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
@ -7,32 +7,119 @@ use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
/// Objects will use the provided ID instead of the full track name, to save bytes.
|
/// Objects will use the provided ID instead of the full track name, to save bytes.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Subscribe {
|
pub struct Subscribe {
|
||||||
// An ID we choose so we can map to the track_name.
|
/// An ID we choose so we can map to the track_name.
|
||||||
// Proposal: https://github.com/moq-wg/moq-transport/issues/209
|
// Proposal: https://github.com/moq-wg/moq-transport/issues/209
|
||||||
pub id: VarInt,
|
pub id: VarInt,
|
||||||
|
|
||||||
// The track namespace.
|
/// The track namespace.
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
|
|
||||||
// The track name.
|
/// The track name.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
|
/// The start/end group/object.
|
||||||
|
pub start_group: SubscribeLocation,
|
||||||
|
pub start_object: SubscribeLocation,
|
||||||
|
pub end_group: SubscribeLocation,
|
||||||
|
pub end_object: SubscribeLocation,
|
||||||
|
|
||||||
|
/// Optional parameters
|
||||||
|
pub params: Params,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subscribe {
|
impl Subscribe {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let id = VarInt::decode(r).await?;
|
let id = VarInt::decode(r).await?;
|
||||||
let namespace = decode_string(r).await?;
|
let namespace = String::decode(r).await?;
|
||||||
let name = decode_string(r).await?;
|
let name = String::decode(r).await?;
|
||||||
|
|
||||||
Ok(Self { id, namespace, name })
|
let start_group = SubscribeLocation::decode(r).await?;
|
||||||
|
let start_object = SubscribeLocation::decode(r).await?;
|
||||||
|
let end_group = SubscribeLocation::decode(r).await?;
|
||||||
|
let end_object = SubscribeLocation::decode(r).await?;
|
||||||
|
|
||||||
|
// You can't have a start object without a start group.
|
||||||
|
if start_group == SubscribeLocation::None && start_object != SubscribeLocation::None {
|
||||||
|
return Err(DecodeError::InvalidSubscribeLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can't have an end object without an end group.
|
||||||
|
if end_group == SubscribeLocation::None && end_object != SubscribeLocation::None {
|
||||||
|
return Err(DecodeError::InvalidSubscribeLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: There's some more location restrictions in the draft, but they're enforced at a higher level.
|
||||||
|
|
||||||
|
let params = Params::decode(r).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
start_group,
|
||||||
|
start_object,
|
||||||
|
end_group,
|
||||||
|
end_object,
|
||||||
|
params,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Subscribe {
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
self.id.encode(w).await?;
|
self.id.encode(w).await?;
|
||||||
encode_string(&self.namespace, w).await?;
|
self.namespace.encode(w).await?;
|
||||||
encode_string(&self.name, w).await?;
|
self.name.encode(w).await?;
|
||||||
|
|
||||||
|
self.start_group.encode(w).await?;
|
||||||
|
self.start_object.encode(w).await?;
|
||||||
|
self.end_group.encode(w).await?;
|
||||||
|
self.end_object.encode(w).await?;
|
||||||
|
|
||||||
|
self.params.encode(w).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signal where the subscription should begin, relative to the current cache.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum SubscribeLocation {
|
||||||
|
None,
|
||||||
|
Absolute(VarInt),
|
||||||
|
Latest(VarInt),
|
||||||
|
Future(VarInt),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubscribeLocation {
|
||||||
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
|
let kind = VarInt::decode(r).await?;
|
||||||
|
|
||||||
|
match kind.into_inner() {
|
||||||
|
0 => Ok(Self::None),
|
||||||
|
1 => Ok(Self::Absolute(VarInt::decode(r).await?)),
|
||||||
|
2 => Ok(Self::Latest(VarInt::decode(r).await?)),
|
||||||
|
3 => Ok(Self::Future(VarInt::decode(r).await?)),
|
||||||
|
_ => Err(DecodeError::InvalidSubscribeLocation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
|
match self {
|
||||||
|
Self::None => {
|
||||||
|
VarInt::from_u32(0).encode(w).await?;
|
||||||
|
}
|
||||||
|
Self::Absolute(val) => {
|
||||||
|
VarInt::from_u32(1).encode(w).await?;
|
||||||
|
val.encode(w).await?;
|
||||||
|
}
|
||||||
|
Self::Latest(val) => {
|
||||||
|
VarInt::from_u32(2).encode(w).await?;
|
||||||
|
val.encode(w).await?;
|
||||||
|
}
|
||||||
|
Self::Future(val) => {
|
||||||
|
VarInt::from_u32(3).encode(w).await?;
|
||||||
|
val.encode(w).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
|
/// Sent by the publisher to reject a Subscribe.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SubscribeError {
|
||||||
|
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
||||||
|
|
||||||
|
// The ID for this subscription.
|
||||||
|
pub id: VarInt,
|
||||||
|
|
||||||
|
// An error code.
|
||||||
|
pub code: u32,
|
||||||
|
|
||||||
|
// An optional, human-readable reason.
|
||||||
|
pub reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubscribeError {
|
||||||
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
|
let id = VarInt::decode(r).await?;
|
||||||
|
let code = VarInt::decode(r).await?.try_into()?;
|
||||||
|
let reason = String::decode(r).await?;
|
||||||
|
|
||||||
|
Ok(Self { id, code, reason })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
|
self.id.encode(w).await?;
|
||||||
|
VarInt::from_u32(self.code).encode(w).await?;
|
||||||
|
self.reason.encode(w).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
|
/// Sent by the publisher to cleanly terminate a Subscribe.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SubscribeFin {
|
||||||
|
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
||||||
|
/// The ID for this subscription.
|
||||||
|
pub id: VarInt,
|
||||||
|
|
||||||
|
/// The final group/object sent on this subscription.
|
||||||
|
pub final_group: VarInt,
|
||||||
|
pub final_object: VarInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubscribeFin {
|
||||||
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
|
let id = VarInt::decode(r).await?;
|
||||||
|
let final_group = VarInt::decode(r).await?;
|
||||||
|
let final_object = VarInt::decode(r).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
final_group,
|
||||||
|
final_object,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
|
self.id.encode(w).await?;
|
||||||
|
self.final_group.encode(w).await?;
|
||||||
|
self.final_object.encode(w).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::coding::{DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
@ -6,21 +6,25 @@ use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SubscribeOk {
|
pub struct SubscribeOk {
|
||||||
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
||||||
|
/// The ID for this track.
|
||||||
// The ID for this track.
|
|
||||||
pub id: VarInt,
|
pub id: VarInt,
|
||||||
|
|
||||||
|
/// The subscription will expire in this many milliseconds.
|
||||||
|
pub expires: VarInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscribeOk {
|
impl SubscribeOk {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let id = VarInt::decode(r).await?;
|
let id = VarInt::decode(r).await?;
|
||||||
Ok(Self { id })
|
let expires = VarInt::decode(r).await?;
|
||||||
|
Ok(Self { id, expires })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscribeOk {
|
impl SubscribeOk {
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
self.id.encode(w).await?;
|
self.id.encode(w).await?;
|
||||||
|
self.expires.encode(w).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,48 @@
|
||||||
use crate::coding::{decode_string, encode_string, DecodeError, EncodeError, VarInt};
|
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
/// Sent by the publisher to reject a Subscribe.
|
/// Sent by the publisher to terminate a Subscribe.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SubscribeReset {
|
pub struct SubscribeReset {
|
||||||
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
||||||
|
/// The ID for this subscription.
|
||||||
// The ID for this subscription.
|
|
||||||
pub id: VarInt,
|
pub id: VarInt,
|
||||||
|
|
||||||
// An error code.
|
/// An error code.
|
||||||
pub code: u32,
|
pub code: u32,
|
||||||
|
|
||||||
// An optional, human-readable reason.
|
/// An optional, human-readable reason.
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
|
|
||||||
|
/// The final group/object sent on this subscription.
|
||||||
|
pub final_group: VarInt,
|
||||||
|
pub final_object: VarInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscribeReset {
|
impl SubscribeReset {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let id = VarInt::decode(r).await?;
|
let id = VarInt::decode(r).await?;
|
||||||
let code = VarInt::decode(r).await?.try_into()?;
|
let code = VarInt::decode(r).await?.try_into()?;
|
||||||
let reason = decode_string(r).await?;
|
let reason = String::decode(r).await?;
|
||||||
|
let final_group = VarInt::decode(r).await?;
|
||||||
|
let final_object = VarInt::decode(r).await?;
|
||||||
|
|
||||||
Ok(Self { id, code, reason })
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
code,
|
||||||
|
reason,
|
||||||
|
final_group,
|
||||||
|
final_object,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
self.id.encode(w).await?;
|
self.id.encode(w).await?;
|
||||||
VarInt::from_u32(self.code).encode(w).await?;
|
VarInt::from_u32(self.code).encode(w).await?;
|
||||||
encode_string(&self.reason, w).await?;
|
self.reason.encode(w).await?;
|
||||||
|
|
||||||
|
self.final_group.encode(w).await?;
|
||||||
|
self.final_object.encode(w).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
use crate::coding::{decode_string, encode_string, DecodeError, EncodeError};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
/// Sent by the publisher to terminate an Announce.
|
/// Sent by the publisher to terminate an Announce.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AnnounceStop {
|
pub struct Unannounce {
|
||||||
// Echo back the namespace that was reset
|
// Echo back the namespace that was reset
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnnounceStop {
|
impl Unannounce {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let namespace = decode_string(r).await?;
|
let namespace = String::decode(r).await?;
|
||||||
|
|
||||||
Ok(Self { namespace })
|
Ok(Self { namespace })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
encode_string(&self.namespace, w).await?;
|
self.namespace.encode(w).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
use crate::coding::{DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
/// Sent by the subscriber to terminate a Subscribe.
|
/// Sent by the subscriber to terminate a Subscribe.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SubscribeStop {
|
pub struct Unsubscribe {
|
||||||
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
// NOTE: No full track name because of this proposal: https://github.com/moq-wg/moq-transport/issues/209
|
||||||
|
|
||||||
// The ID for this subscription.
|
// The ID for this subscription.
|
||||||
pub id: VarInt,
|
pub id: VarInt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscribeStop {
|
impl Unsubscribe {
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let id = VarInt::decode(r).await?;
|
let id = VarInt::decode(r).await?;
|
||||||
Ok(Self { id })
|
Ok(Self { id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscribeStop {
|
impl Unsubscribe {
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
self.id.encode(w).await?;
|
self.id.encode(w).await?;
|
||||||
Ok(())
|
Ok(())
|
|
@ -34,20 +34,19 @@ impl Client {
|
||||||
|
|
||||||
let client = setup::Client {
|
let client = setup::Client {
|
||||||
role,
|
role,
|
||||||
versions: vec![setup::Version::KIXEL_00].into(),
|
versions: vec![setup::Version::KIXEL_01].into(),
|
||||||
|
params: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
client.encode(&mut control.0).await?;
|
client.encode(&mut control.0).await?;
|
||||||
|
|
||||||
let server = setup::Server::decode(&mut control.1).await?;
|
let server = setup::Server::decode(&mut control.1).await?;
|
||||||
|
|
||||||
if server.version != setup::Version::KIXEL_00 {
|
if server.version != setup::Version::KIXEL_01 {
|
||||||
return Err(SessionError::Version(Some(server.version)));
|
return Err(SessionError::Version(
|
||||||
}
|
vec![setup::Version::KIXEL_01].into(),
|
||||||
|
vec![server.version].into(),
|
||||||
// Make sure the server replied with the
|
));
|
||||||
if !client.role.is_compatible(server.role) {
|
|
||||||
return Err(SessionError::RoleIncompatible(client.role, server.role));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(control)
|
Ok(control)
|
||||||
|
|
|
@ -14,8 +14,8 @@ pub enum SessionError {
|
||||||
#[error("decode error: {0}")]
|
#[error("decode error: {0}")]
|
||||||
Decode(#[from] coding::DecodeError),
|
Decode(#[from] coding::DecodeError),
|
||||||
|
|
||||||
#[error("unsupported version: {0:?}")]
|
#[error("unsupported versions: client={0:?} server={1:?}")]
|
||||||
Version(Option<setup::Version>),
|
Version(setup::Versions, setup::Versions),
|
||||||
|
|
||||||
#[error("incompatible roles: client={0:?} server={1:?}")]
|
#[error("incompatible roles: client={0:?} server={1:?}")]
|
||||||
RoleIncompatible(setup::Role, setup::Role),
|
RoleIncompatible(setup::Role, setup::Role),
|
||||||
|
@ -32,6 +32,18 @@ pub enum SessionError {
|
||||||
#[error("role violation: msg={0}")]
|
#[error("role violation: msg={0}")]
|
||||||
RoleViolation(VarInt),
|
RoleViolation(VarInt),
|
||||||
|
|
||||||
|
/// Our enforced stream mapping was disrespected.
|
||||||
|
#[error("stream mapping conflict")]
|
||||||
|
StreamMapping,
|
||||||
|
|
||||||
|
/// The priority was invalid.
|
||||||
|
#[error("invalid priority: {0}")]
|
||||||
|
InvalidPriority(VarInt),
|
||||||
|
|
||||||
|
/// The size was invalid.
|
||||||
|
#[error("invalid size: {0}")]
|
||||||
|
InvalidSize(VarInt),
|
||||||
|
|
||||||
/// An unclassified error because I'm lazy. TODO classify these errors
|
/// An unclassified error because I'm lazy. TODO classify these errors
|
||||||
#[error("unknown error: {0}")]
|
#[error("unknown error: {0}")]
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
|
@ -44,29 +56,40 @@ impl MoqError for SessionError {
|
||||||
Self::Cache(err) => err.code(),
|
Self::Cache(err) => err.code(),
|
||||||
Self::RoleIncompatible(..) => 406,
|
Self::RoleIncompatible(..) => 406,
|
||||||
Self::RoleViolation(..) => 405,
|
Self::RoleViolation(..) => 405,
|
||||||
|
Self::StreamMapping => 409,
|
||||||
Self::Unknown(_) => 500,
|
Self::Unknown(_) => 500,
|
||||||
Self::Write(_) => 501,
|
Self::Write(_) => 501,
|
||||||
Self::Read(_) => 502,
|
Self::Read(_) => 502,
|
||||||
Self::Session(_) => 503,
|
Self::Session(_) => 503,
|
||||||
Self::Version(_) => 406,
|
Self::Version(..) => 406,
|
||||||
Self::Encode(_) => 500,
|
Self::Encode(_) => 500,
|
||||||
Self::Decode(_) => 500,
|
Self::Decode(_) => 500,
|
||||||
|
Self::InvalidPriority(_) => 400,
|
||||||
|
Self::InvalidSize(_) => 400,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reason that is sent over the wire.
|
/// A reason that is sent over the wire.
|
||||||
fn reason(&self) -> &str {
|
fn reason(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Cache(err) => err.reason(),
|
Self::Cache(err) => err.reason(),
|
||||||
Self::RoleViolation(_) => "role violation",
|
Self::RoleViolation(kind) => format!("role violation for message type {:?}", kind),
|
||||||
Self::RoleIncompatible(..) => "role incompatible",
|
Self::RoleIncompatible(client, server) => {
|
||||||
Self::Read(_) => "read error",
|
format!(
|
||||||
Self::Write(_) => "write error",
|
"role incompatible: client wanted {:?} but server wanted {:?}",
|
||||||
Self::Session(_) => "session error",
|
client, server
|
||||||
Self::Unknown(_) => "unknown",
|
)
|
||||||
Self::Version(_) => "unsupported version",
|
}
|
||||||
Self::Encode(_) => "encode error",
|
Self::Read(err) => format!("read error: {}", err),
|
||||||
Self::Decode(_) => "decode error",
|
Self::Write(err) => format!("write error: {}", err),
|
||||||
|
Self::Session(err) => format!("session error: {}", err),
|
||||||
|
Self::Unknown(err) => format!("unknown error: {}", err),
|
||||||
|
Self::Version(client, server) => format!("unsupported versions: client={:?} server={:?}", client, server),
|
||||||
|
Self::Encode(err) => format!("encode error: {}", err),
|
||||||
|
Self::Decode(err) => format!("decode error: {}", err),
|
||||||
|
Self::StreamMapping => "streaming mapping conflict".to_owned(),
|
||||||
|
Self::InvalidPriority(priority) => format!("invalid priority: {}", priority),
|
||||||
|
Self::InvalidSize(size) => format!("invalid size: {}", size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,9 +85,9 @@ impl Publisher {
|
||||||
async fn recv_message(&mut self, msg: &Message) -> Result<(), SessionError> {
|
async fn recv_message(&mut self, msg: &Message) -> Result<(), SessionError> {
|
||||||
match msg {
|
match msg {
|
||||||
Message::AnnounceOk(msg) => self.recv_announce_ok(msg).await,
|
Message::AnnounceOk(msg) => self.recv_announce_ok(msg).await,
|
||||||
Message::AnnounceStop(msg) => self.recv_announce_stop(msg).await,
|
Message::AnnounceError(msg) => self.recv_announce_error(msg).await,
|
||||||
Message::Subscribe(msg) => self.recv_subscribe(msg).await,
|
Message::Subscribe(msg) => self.recv_subscribe(msg).await,
|
||||||
Message::SubscribeStop(msg) => self.recv_subscribe_stop(msg).await,
|
Message::Unsubscribe(msg) => self.recv_unsubscribe(msg).await,
|
||||||
_ => Err(SessionError::RoleViolation(msg.id())),
|
_ => Err(SessionError::RoleViolation(msg.id())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ impl Publisher {
|
||||||
Err(CacheError::NotFound.into())
|
Err(CacheError::NotFound.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_announce_stop(&mut self, _msg: &message::AnnounceStop) -> Result<(), SessionError> {
|
async fn recv_announce_error(&mut self, _msg: &message::AnnounceError) -> Result<(), SessionError> {
|
||||||
// We didn't send an announce.
|
// We didn't send an announce.
|
||||||
Err(CacheError::NotFound.into())
|
Err(CacheError::NotFound.into())
|
||||||
}
|
}
|
||||||
|
@ -115,14 +115,24 @@ impl Publisher {
|
||||||
hash_map::Entry::Vacant(entry) => entry.insert(abort),
|
hash_map::Entry::Vacant(entry) => entry.insert(abort),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.control.send(message::SubscribeOk { id: msg.id }).await
|
self.control
|
||||||
|
.send(message::SubscribeOk {
|
||||||
|
id: msg.id,
|
||||||
|
expires: VarInt::ZERO,
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reset_subscribe<E: MoqError>(&mut self, id: VarInt, err: E) -> Result<(), SessionError> {
|
async fn reset_subscribe<E: MoqError>(&mut self, id: VarInt, err: E) -> Result<(), SessionError> {
|
||||||
let msg = message::SubscribeReset {
|
let msg = message::SubscribeReset {
|
||||||
id,
|
id,
|
||||||
code: err.code(),
|
code: err.code(),
|
||||||
reason: err.reason().to_string(),
|
reason: err.reason(),
|
||||||
|
|
||||||
|
// TODO properly populate these
|
||||||
|
// But first: https://github.com/moq-wg/moq-transport/issues/313
|
||||||
|
final_group: VarInt::ZERO,
|
||||||
|
final_object: VarInt::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.control.send(msg).await
|
self.control.send(msg).await
|
||||||
|
@ -176,31 +186,42 @@ impl Publisher {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_segment(&self, id: VarInt, segment: &mut segment::Subscriber) -> Result<(), SessionError> {
|
async fn run_segment(&self, id: VarInt, segment: &mut segment::Subscriber) -> Result<(), SessionError> {
|
||||||
let object = message::Object {
|
log::trace!("serving group: {:?}", segment);
|
||||||
track: id,
|
|
||||||
sequence: segment.sequence,
|
|
||||||
priority: segment.priority,
|
|
||||||
expires: segment.expires,
|
|
||||||
};
|
|
||||||
|
|
||||||
log::trace!("serving object: {:?}", object);
|
|
||||||
|
|
||||||
let mut stream = self.webtransport.open_uni().await?;
|
let mut stream = self.webtransport.open_uni().await?;
|
||||||
stream.set_priority(object.priority).ok();
|
|
||||||
|
|
||||||
object
|
// Convert the u32 to a i32, since the Quinn set_priority is signed.
|
||||||
.encode(&mut stream)
|
let priority = (segment.priority as i64 - i32::MAX as i64) as i32;
|
||||||
.await
|
stream.set_priority(priority).ok();
|
||||||
.map_err(|e| SessionError::Unknown(e.to_string()))?;
|
|
||||||
|
|
||||||
while let Some(data) = segment.read_chunk().await? {
|
while let Some(mut fragment) = segment.next_fragment().await? {
|
||||||
stream.write_chunk(data).await?;
|
let object = message::Object {
|
||||||
|
track: id,
|
||||||
|
|
||||||
|
// Properties of the segment
|
||||||
|
group: segment.sequence,
|
||||||
|
priority: segment.priority,
|
||||||
|
expires: segment.expires,
|
||||||
|
|
||||||
|
// Properties of the fragment
|
||||||
|
sequence: fragment.sequence,
|
||||||
|
size: fragment.size,
|
||||||
|
};
|
||||||
|
|
||||||
|
object
|
||||||
|
.encode(&mut stream)
|
||||||
|
.await
|
||||||
|
.map_err(|e| SessionError::Unknown(e.to_string()))?;
|
||||||
|
|
||||||
|
while let Some(chunk) = fragment.read_chunk().await? {
|
||||||
|
stream.write_all(&chunk).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_subscribe_stop(&mut self, msg: &message::SubscribeStop) -> Result<(), SessionError> {
|
async fn recv_unsubscribe(&mut self, msg: &message::Unsubscribe) -> Result<(), SessionError> {
|
||||||
let abort = self
|
let abort = self
|
||||||
.subscribes
|
.subscribes
|
||||||
.lock()
|
.lock()
|
||||||
|
|
|
@ -18,8 +18,8 @@ impl Server {
|
||||||
client
|
client
|
||||||
.versions
|
.versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|version| **version == setup::Version::KIXEL_00)
|
.find(|version| **version == setup::Version::KIXEL_01)
|
||||||
.ok_or_else(|| SessionError::Version(client.versions.last().cloned()))?;
|
.ok_or_else(|| SessionError::Version(client.versions.clone(), vec![setup::Version::KIXEL_01].into()))?;
|
||||||
|
|
||||||
Ok(Request {
|
Ok(Request {
|
||||||
session,
|
session,
|
||||||
|
@ -63,7 +63,8 @@ impl Request {
|
||||||
async fn send_setup(&mut self, role: setup::Role) -> Result<(), SessionError> {
|
async fn send_setup(&mut self, role: setup::Role) -> Result<(), SessionError> {
|
||||||
let server = setup::Server {
|
let server = setup::Server {
|
||||||
role,
|
role,
|
||||||
version: setup::Version::KIXEL_00,
|
version: setup::Version::KIXEL_01,
|
||||||
|
params: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to sure we support the opposite of the client's role.
|
// We need to sure we support the opposite of the client's role.
|
||||||
|
|
|
@ -6,7 +6,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cache::{broadcast, segment, track, CacheError},
|
cache::{broadcast, fragment, segment, track, CacheError},
|
||||||
|
coding::DecodeError,
|
||||||
message,
|
message,
|
||||||
message::Message,
|
message::Message,
|
||||||
session::{Control, SessionError},
|
session::{Control, SessionError},
|
||||||
|
@ -64,28 +65,28 @@ impl Subscriber {
|
||||||
let msg = self.control.recv().await?;
|
let msg = self.control.recv().await?;
|
||||||
|
|
||||||
log::info!("message received: {:?}", msg);
|
log::info!("message received: {:?}", msg);
|
||||||
if let Err(err) = self.recv_message(&msg).await {
|
if let Err(err) = self.recv_message(&msg) {
|
||||||
log::warn!("message error: {:?} {:?}", err, msg);
|
log::warn!("message error: {:?} {:?}", err, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_message(&mut self, msg: &Message) -> Result<(), SessionError> {
|
fn recv_message(&mut self, msg: &Message) -> Result<(), SessionError> {
|
||||||
match msg {
|
match msg {
|
||||||
Message::Announce(_) => Ok(()), // don't care
|
Message::Announce(_) => Ok(()), // don't care
|
||||||
Message::AnnounceReset(_) => Ok(()), // also don't care
|
Message::Unannounce(_) => Ok(()), // also don't care
|
||||||
Message::SubscribeOk(_) => Ok(()), // guess what, don't care
|
Message::SubscribeOk(_msg) => Ok(()), // don't care
|
||||||
Message::SubscribeReset(msg) => self.recv_subscribe_reset(msg).await,
|
Message::SubscribeReset(msg) => self.recv_subscribe_error(msg.id, CacheError::Reset(msg.code)),
|
||||||
|
Message::SubscribeFin(msg) => self.recv_subscribe_error(msg.id, CacheError::Closed),
|
||||||
|
Message::SubscribeError(msg) => self.recv_subscribe_error(msg.id, CacheError::Reset(msg.code)),
|
||||||
Message::GoAway(_msg) => unimplemented!("GOAWAY"),
|
Message::GoAway(_msg) => unimplemented!("GOAWAY"),
|
||||||
_ => Err(SessionError::RoleViolation(msg.id())),
|
_ => Err(SessionError::RoleViolation(msg.id())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn recv_subscribe_reset(&mut self, msg: &message::SubscribeReset) -> Result<(), SessionError> {
|
fn recv_subscribe_error(&mut self, id: VarInt, err: CacheError) -> Result<(), SessionError> {
|
||||||
let err = CacheError::Reset(msg.code);
|
|
||||||
|
|
||||||
let mut subscribes = self.subscribes.lock().unwrap();
|
let mut subscribes = self.subscribes.lock().unwrap();
|
||||||
let subscribe = subscribes.remove(&msg.id).ok_or(CacheError::NotFound)?;
|
let subscribe = subscribes.remove(&id).ok_or(CacheError::NotFound)?;
|
||||||
subscribe.close(err)?;
|
subscribe.close(err)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -107,36 +108,82 @@ impl Subscriber {
|
||||||
|
|
||||||
async fn run_stream(self, mut stream: RecvStream) -> Result<(), SessionError> {
|
async fn run_stream(self, mut stream: RecvStream) -> Result<(), SessionError> {
|
||||||
// Decode the object on the data stream.
|
// Decode the object on the data stream.
|
||||||
let object = message::Object::decode(&mut stream)
|
let mut object = message::Object::decode(&mut stream)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| SessionError::Unknown(e.to_string()))?;
|
.map_err(|e| SessionError::Unknown(e.to_string()))?;
|
||||||
|
|
||||||
log::trace!("received object: {:?}", object);
|
log::trace!("received object: {:?}", object);
|
||||||
|
|
||||||
// A new scope is needed because the async compiler is dumb
|
// A new scope is needed because the async compiler is dumb
|
||||||
let mut publisher = {
|
let mut segment = {
|
||||||
let mut subscribes = self.subscribes.lock().unwrap();
|
let mut subscribes = self.subscribes.lock().unwrap();
|
||||||
let track = subscribes.get_mut(&object.track).ok_or(CacheError::NotFound)?;
|
let track = subscribes.get_mut(&object.track).ok_or(CacheError::NotFound)?;
|
||||||
|
|
||||||
track.create_segment(segment::Info {
|
track.create_segment(segment::Info {
|
||||||
sequence: object.sequence,
|
sequence: object.group,
|
||||||
priority: object.priority,
|
priority: object.priority,
|
||||||
expires: object.expires,
|
expires: object.expires,
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(data) = stream.read_chunk(usize::MAX, true).await? {
|
// Create the first fragment
|
||||||
// NOTE: This does not make a copy!
|
let mut fragment = segment.create_fragment(fragment::Info {
|
||||||
// Bytes are immutable and ref counted.
|
sequence: object.sequence,
|
||||||
publisher.write_chunk(data.bytes)?;
|
size: object.size,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut remain = object.size.map(usize::from);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(0) = remain {
|
||||||
|
// Decode the next object from the stream.
|
||||||
|
let next = match message::Object::decode(&mut stream).await {
|
||||||
|
Ok(next) => next,
|
||||||
|
|
||||||
|
// No more objects
|
||||||
|
Err(DecodeError::Final) => break,
|
||||||
|
|
||||||
|
// Unknown error
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: This is a custom restriction; not part of the moq-transport draft.
|
||||||
|
// We require every OBJECT to contain the same priority since prioritization is done per-stream.
|
||||||
|
// We also require every OBJECT to contain the same group so we know when the group ends, and can detect gaps.
|
||||||
|
if next.priority != object.priority && next.group != object.group {
|
||||||
|
return Err(SessionError::StreamMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new object.
|
||||||
|
fragment = segment.create_fragment(fragment::Info {
|
||||||
|
sequence: object.sequence,
|
||||||
|
size: object.size,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
object = next;
|
||||||
|
remain = object.size.map(usize::from);
|
||||||
|
}
|
||||||
|
|
||||||
|
match stream.read_chunk(remain.unwrap_or(usize::MAX), true).await? {
|
||||||
|
// Unbounded object has ended
|
||||||
|
None if remain.is_none() => break,
|
||||||
|
|
||||||
|
// Bounded object ended early, oops.
|
||||||
|
None => return Err(DecodeError::UnexpectedEnd.into()),
|
||||||
|
|
||||||
|
// NOTE: This does not make a copy!
|
||||||
|
// Bytes are immutable and ref counted.
|
||||||
|
Some(data) => fragment.write_chunk(data.bytes)?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_source(mut self) -> Result<(), SessionError> {
|
async fn run_source(mut self) -> Result<(), SessionError> {
|
||||||
// NOTE: This returns Closed when the source is closed.
|
loop {
|
||||||
while let Some(track) = self.source.next_track().await? {
|
// NOTE: This returns Closed when the source is closed.
|
||||||
|
let track = self.source.next_track().await?;
|
||||||
let name = track.name.clone();
|
let name = track.name.clone();
|
||||||
|
|
||||||
let id = VarInt::from_u32(self.next.fetch_add(1, atomic::Ordering::SeqCst));
|
let id = VarInt::from_u32(self.next.fetch_add(1, atomic::Ordering::SeqCst));
|
||||||
|
@ -146,11 +193,17 @@ impl Subscriber {
|
||||||
id,
|
id,
|
||||||
namespace: "".to_string(),
|
namespace: "".to_string(),
|
||||||
name,
|
name,
|
||||||
|
|
||||||
|
// TODO correctly support these
|
||||||
|
start_group: message::SubscribeLocation::Latest(VarInt::ZERO),
|
||||||
|
start_object: message::SubscribeLocation::Absolute(VarInt::ZERO),
|
||||||
|
end_group: message::SubscribeLocation::None,
|
||||||
|
end_object: message::SubscribeLocation::None,
|
||||||
|
|
||||||
|
params: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.control.send(msg).await?;
|
self.control.send(msg).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{Role, Versions};
|
use super::{Role, Versions};
|
||||||
use crate::{
|
use crate::{
|
||||||
coding::{DecodeError, EncodeError},
|
coding::{Decode, DecodeError, Encode, EncodeError, Params},
|
||||||
VarInt,
|
VarInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,29 +15,45 @@ pub struct Client {
|
||||||
pub versions: Versions,
|
pub versions: Versions,
|
||||||
|
|
||||||
/// Indicate if the client is a publisher, a subscriber, or both.
|
/// Indicate if the client is a publisher, a subscriber, or both.
|
||||||
// Proposal: moq-wg/moq-transport#151
|
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
|
|
||||||
|
/// Unknown parameters.
|
||||||
|
pub params: Params,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Decode a client setup message.
|
/// Decode a client setup message.
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let typ = VarInt::decode(r).await?;
|
let typ = VarInt::decode(r).await?;
|
||||||
if typ.into_inner() != 1 {
|
if typ.into_inner() != 0x40 {
|
||||||
return Err(DecodeError::InvalidType(typ));
|
return Err(DecodeError::InvalidMessage(typ));
|
||||||
}
|
}
|
||||||
|
|
||||||
let versions = Versions::decode(r).await?;
|
let versions = Versions::decode(r).await?;
|
||||||
let role = Role::decode(r).await?;
|
let mut params = Params::decode(r).await?;
|
||||||
|
|
||||||
Ok(Self { versions, role })
|
let role = params
|
||||||
|
.get::<Role>(VarInt::from_u32(0))
|
||||||
|
.await?
|
||||||
|
.ok_or(DecodeError::MissingParameter)?;
|
||||||
|
|
||||||
|
// Make sure the PATH parameter isn't used
|
||||||
|
// TODO: This assumes WebTransport support only
|
||||||
|
if params.has(VarInt::from_u32(1)) {
|
||||||
|
return Err(DecodeError::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { versions, role, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode a server setup message.
|
/// Encode a server setup message.
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
VarInt::from_u32(1).encode(w).await?;
|
VarInt::from_u32(0x40).encode(w).await?;
|
||||||
self.versions.encode(w).await?;
|
self.versions.encode(w).await?;
|
||||||
self.role.encode(w).await?;
|
|
||||||
|
let mut params = self.params.clone();
|
||||||
|
params.set(VarInt::from_u32(0), self.role).await?;
|
||||||
|
params.encode(w).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use crate::coding::{DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
/// Indicates the endpoint is a publisher, subscriber, or both.
|
/// Indicates the endpoint is a publisher, subscriber, or both.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -36,9 +36,9 @@ impl Role {
|
||||||
impl From<Role> for VarInt {
|
impl From<Role> for VarInt {
|
||||||
fn from(r: Role) -> Self {
|
fn from(r: Role) -> Self {
|
||||||
VarInt::from_u32(match r {
|
VarInt::from_u32(match r {
|
||||||
Role::Publisher => 0x0,
|
Role::Publisher => 0x1,
|
||||||
Role::Subscriber => 0x1,
|
Role::Subscriber => 0x2,
|
||||||
Role::Both => 0x2,
|
Role::Both => 0x3,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,23 +48,27 @@ impl TryFrom<VarInt> for Role {
|
||||||
|
|
||||||
fn try_from(v: VarInt) -> Result<Self, Self::Error> {
|
fn try_from(v: VarInt) -> Result<Self, Self::Error> {
|
||||||
match v.into_inner() {
|
match v.into_inner() {
|
||||||
0x0 => Ok(Self::Publisher),
|
0x1 => Ok(Self::Publisher),
|
||||||
0x1 => Ok(Self::Subscriber),
|
0x2 => Ok(Self::Subscriber),
|
||||||
0x2 => Ok(Self::Both),
|
0x3 => Ok(Self::Both),
|
||||||
_ => Err(DecodeError::InvalidType(v)),
|
_ => Err(DecodeError::InvalidRole(v)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Role {
|
#[async_trait::async_trait]
|
||||||
|
impl Decode for Role {
|
||||||
/// Decode the role.
|
/// Decode the role.
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let v = VarInt::decode(r).await?;
|
let v = VarInt::decode(r).await?;
|
||||||
v.try_into()
|
v.try_into()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Encode for Role {
|
||||||
/// Encode the role.
|
/// Encode the role.
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
VarInt::from(*self).encode(w).await
|
VarInt::from(*self).encode(w).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{Role, Version};
|
use super::{Role, Version};
|
||||||
use crate::{
|
use crate::{
|
||||||
coding::{DecodeError, EncodeError},
|
coding::{Decode, DecodeError, Encode, EncodeError, Params},
|
||||||
VarInt,
|
VarInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,27 +17,43 @@ pub struct Server {
|
||||||
/// Indicate if the server is a publisher, a subscriber, or both.
|
/// Indicate if the server is a publisher, a subscriber, or both.
|
||||||
// Proposal: moq-wg/moq-transport#151
|
// Proposal: moq-wg/moq-transport#151
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
|
|
||||||
|
/// Unknown parameters.
|
||||||
|
pub params: Params,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
/// Decode the server setup.
|
/// Decode the server setup.
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let typ = VarInt::decode(r).await?;
|
let typ = VarInt::decode(r).await?;
|
||||||
if typ.into_inner() != 2 {
|
if typ.into_inner() != 0x41 {
|
||||||
return Err(DecodeError::InvalidType(typ));
|
return Err(DecodeError::InvalidMessage(typ));
|
||||||
}
|
}
|
||||||
|
|
||||||
let version = Version::decode(r).await?;
|
let version = Version::decode(r).await?;
|
||||||
let role = Role::decode(r).await?;
|
let mut params = Params::decode(r).await?;
|
||||||
|
|
||||||
Ok(Self { version, role })
|
let role = params
|
||||||
|
.get::<Role>(VarInt::from_u32(0))
|
||||||
|
.await?
|
||||||
|
.ok_or(DecodeError::MissingParameter)?;
|
||||||
|
|
||||||
|
// Make sure the PATH parameter isn't used
|
||||||
|
if params.has(VarInt::from_u32(1)) {
|
||||||
|
return Err(DecodeError::InvalidParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { version, role, params })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the server setup.
|
/// Encode the server setup.
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
VarInt::from_u32(2).encode(w).await?;
|
VarInt::from_u32(0x41).encode(w).await?;
|
||||||
self.version.encode(w).await?;
|
self.version.encode(w).await?;
|
||||||
self.role.encode(w).await?;
|
|
||||||
|
let mut params = self.params.clone();
|
||||||
|
params.set(VarInt::from_u32(0), self.role).await?;
|
||||||
|
params.encode(w).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::coding::{DecodeError, EncodeError, VarInt};
|
use crate::coding::{Decode, DecodeError, Encode, EncodeError, VarInt};
|
||||||
|
|
||||||
use crate::coding::{AsyncRead, AsyncWrite};
|
use crate::coding::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
@ -56,6 +56,19 @@ impl Version {
|
||||||
/// # GROUP
|
/// # GROUP
|
||||||
/// - GROUP concept was removed, replaced with OBJECT as a QUIC stream.
|
/// - GROUP concept was removed, replaced with OBJECT as a QUIC stream.
|
||||||
pub const KIXEL_00: Version = Version(VarInt::from_u32(0xbad00));
|
pub const KIXEL_00: Version = Version(VarInt::from_u32(0xbad00));
|
||||||
|
|
||||||
|
/// Fork of draft-ietf-moq-transport-01.
|
||||||
|
///
|
||||||
|
/// Most of the KIXEL_00 changes made it into the draft, or were reverted.
|
||||||
|
/// Check out the referenced issue on: github.com/moq-wg/moq-transport
|
||||||
|
///
|
||||||
|
/// - SUBSCRIBE contains a separate track namespace and track name field (accidental revert). [#277](https://github.com/moq-wg/moq-transport/pull/277)
|
||||||
|
/// - SUBSCRIBE contains the `track_id` instead of SUBSCRIBE_OK. [#145](https://github.com/moq-wg/moq-transport/issues/145)
|
||||||
|
/// - SUBSCRIBE_* reference `track_id` the instead of the `track_full_name`. [#145](https://github.com/moq-wg/moq-transport/issues/145)
|
||||||
|
/// - OBJECT `priority` is still a VarInt, but the max value is a u32 (implementation reasons)
|
||||||
|
/// - OBJECT `expires` was added, a VarInt in seconds. [#249](https://github.com/moq-wg/moq-transport/issues/249)
|
||||||
|
/// - OBJECT messages within the same `group` MUST be on the same QUIC stream.
|
||||||
|
pub const KIXEL_01: Version = Version(VarInt::from_u32(0xbad01));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<VarInt> for Version {
|
impl From<VarInt> for Version {
|
||||||
|
@ -88,9 +101,10 @@ impl Version {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Versions(Vec<Version>);
|
pub struct Versions(Vec<Version>);
|
||||||
|
|
||||||
impl Versions {
|
#[async_trait::async_trait]
|
||||||
|
impl Decode for Versions {
|
||||||
/// Decode the version list.
|
/// Decode the version list.
|
||||||
pub async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
async fn decode<R: AsyncRead>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
let count = VarInt::decode(r).await?.into_inner();
|
let count = VarInt::decode(r).await?.into_inner();
|
||||||
let mut vs = Vec::new();
|
let mut vs = Vec::new();
|
||||||
|
|
||||||
|
@ -101,9 +115,12 @@ impl Versions {
|
||||||
|
|
||||||
Ok(Self(vs))
|
Ok(Self(vs))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Encode for Versions {
|
||||||
/// Encode the version list.
|
/// Encode the version list.
|
||||||
pub async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
async fn encode<W: AsyncWrite>(&self, w: &mut W) -> Result<(), EncodeError> {
|
||||||
let size: VarInt = self.0.len().try_into()?;
|
let size: VarInt = self.0.len().try_into()?;
|
||||||
size.encode(w).await?;
|
size.encode(w).await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue