Skip to content

Commit

Permalink
steering manifest deserializing
Browse files Browse the repository at this point in the history
  • Loading branch information
VixieTSQ committed May 16, 2024
1 parent 2ee55d1 commit 10ed7c9
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 17 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ categories = ["parser-implementations"]
[dependencies]
serde = { version = "1.0.201", optional = true }
serde_json = { version = "1.0.117", optional = true }
thiserror = { version = "1.0.60", optional = true }

[features]
default = ["steering-manifest"]
steering-manifest = ["dep:serde", "dep:serde_json", "dep:thiserror"]
steering-manifest = ["dep:serde", "dep:serde_json"]
179 changes: 164 additions & 15 deletions src/steering_manifest.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! TODO: Document
//! A representation of a HLS steering manifest.
// Copyright 2024 Logan Wemyss
//
Expand All @@ -14,54 +14,203 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{collections::HashMap, io};
use std::collections::HashMap;
use std::collections::HashSet;
use std::io;

use serde::ser::SerializeStruct;
use serde::ser::Serializer;
use thiserror::Error;
use serde::Serialize;

/// TODO: Document
/// A steering manifest which identifies the available pathways
/// and their priority order.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SteeringManifest {
/// Specifies how many seconds the client must wait before
/// reloading the Steering Manifest.
ttl_seconds: u64,

/// Specifies the URI the client must use the
/// next time it obtains the Steering Manifest.
reload_uri: Option<String>,
pathway_priority: Vec<u64>,

/// A list of pathway IDs order to most preferred to least preferred.
pathway_priority: HashSet<String>,

/// A list of novel pathways made by cloning existing ones.
pathway_clones: Vec<PathwayClone>,
}

/// TODO: Document
/// A way to introduce novel Pathways by cloning existing ones.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathwayClone {
/// The ID of the base pathway, which this clone is based on.
base_id: String,

/// The ID of this new pathway.
id: String,

/// URI Replacement rules.
uri_replacement: UriReplacement,
}

/// TODO: Document
/// URI replacement rules.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UriReplacement {
/// If Some, replace the hostname of every rendition URI
/// in the new pathway.
host: Option<String>,

/// URI params to append to every rendition URI in the new
/// pathway.
query_parameters: Option<HashMap<String, String>>,

/// If the `stable_variant_id` of a `VariantStream` on the new
/// pathway appears in the map, set its URI to be the entry's value.
per_variant_uris: Option<HashMap<String, String>>,

/// Key value pairs. If the `stable_rendition_id` of a rendition referred to by a
/// `VariantStream` on the new pathway appears in the map, set
/// its URI to be the entry's value.
per_rendition_uris: Option<HashMap<String, String>>,
}

// TODO: Check invariants
impl SteeringManifest {
/// Serializes the manifest into it's json representation.
/// Guaranteed to write valid UTF-8 only.
///
/// # Errors
///
/// May return `Err` when encountering an io error on `output`.
pub fn serialize(&self, output: impl io::Write) -> Result<(), SerializeError> {
let mut serializer = serde_json::Serializer::new(output);
///
/// # Panics
///
/// A number of invariants mirroring the requirements of the HLS spec
/// must be upheld when making a call to this method, or else it may
/// panic. They are listed below.
///
/// * [`SteeringManifest::pathway_priority`] must be non-empty.
///
/// * [`SteeringManifest::pathway_priority`] must only contain items that contain
/// characters from the set [a..z], [A..Z], [0..9], '.', '-', and '_', aka
/// none of the items contain characters not contained in
/// [`SteeringManifest::PATHWAY_ID_ALLOWED_CHARACTERS`].
///
/// * [`UriReplacement::host`], if `Some`, must be non-empty.
///
/// * [`UriReplacement::query_parameters`] must not contain a key which is empty.
pub fn serialize(&self, output: impl io::Write) -> Result<(), serde_json::Error> {
serde_json::to_writer(output, self)
}

pub const PATHWAY_ID_ALLOWED_CHARACTERS: &'static str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_";
}

impl Serialize for SteeringManifest {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut len_of_fields = 3;
if self.reload_uri.is_some() {
len_of_fields += 1;
}
if !self.pathway_clones.is_empty() {
len_of_fields += 1;
}

let mut manifest = serializer.serialize_struct("SteeringManifest", len_of_fields)?;
manifest.serialize_field("VERSION", &1)?;
manifest.serialize_field("TTL", &self.ttl_seconds)?;
if let Some(reload_uri) = &self.reload_uri {
manifest.serialize_field("RELOAD-URI", reload_uri)?;
}

assert!(
!self.pathway_priority.is_empty(),
"Found an empty pathway priority list while serializing."
);
for id in &self.pathway_priority {
if !id
.chars()
.all(|x| Self::PATHWAY_ID_ALLOWED_CHARACTERS.contains(x))
{
panic!("Found a pathway ID that contains disallowed characters while serializing.")
}
}
manifest.serialize_field("PATHWAY-PRIORITY", &self.pathway_priority)?;

if self.pathway_clones.is_empty() {
return manifest.end();
}
manifest.serialize_field("PATHWAY-CLONES", &self.pathway_clones)?;

manifest.end()
}
}

serializer.serialize_u64(1)?;
impl Serialize for PathwayClone {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut clone = serializer.serialize_struct("PathwayClone", 3)?;

todo!()
clone.serialize_field("BASE-ID", &self.base_id)?;
clone.serialize_field("ID", &self.id)?;
clone.serialize_field("URI-REPLACEMENT", &self.uri_replacement)?;
clone.end()
}
}

#[derive(Debug, Error)]
pub enum SerializeError {
#[error(transparent)]
Json(#[from] serde_json::Error),
impl Serialize for UriReplacement {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut len_of_fields = 0;
if self.host.is_some() {
len_of_fields += 1;
}
if self.query_parameters.is_some() {
len_of_fields += 1;
}
if self.per_variant_uris.is_some() {
len_of_fields += 1;
}
if self.per_rendition_uris.is_some() {
len_of_fields += 1;
}
let mut replacement = serializer.serialize_struct("UriReplacement", len_of_fields)?;

if let Some(host) = &self.host {
assert!(
!host.is_empty(),
"Found an empty host string while serializing."
);

replacement.serialize_field("HOST", host)?;
}

if let Some(params) = &self.query_parameters {
assert!(
!params.contains_key(""),
"Found an empty query parameter key while serializing."
);

replacement.serialize_field("PARAMS", params)?;
}

if let Some(uris) = &self.per_variant_uris {
replacement.serialize_field("PER-VARIANT-URIS", uris)?;
}

if let Some(uris) = &self.per_rendition_uris {
replacement.serialize_field("PER-RENDITION-URIS", uris)?;
}

replacement.end()
}
}

0 comments on commit 10ed7c9

Please sign in to comment.