1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-26 00:51:00 +00:00

some improvements

This commit is contained in:
Luro02 2020-02-14 13:05:18 +01:00
parent 25f9691c75
commit 8cced1ac53
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
16 changed files with 331 additions and 152 deletions

View file

@ -35,6 +35,6 @@ script:
after_success: | after_success: |
# this does require a -Z flag for Doctests, which is unstable! # this does require a -Z flag for Doctests, which is unstable!
if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then
cargo tarpaulin --run-types Tests Doctests --out Xml cargo tarpaulin --ignore-panics --ignore-tests --run-types Tests Doctests --out Xml
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)
fi fi

View file

@ -1,7 +1,9 @@
use core::convert::TryFrom; use core::convert::TryFrom;
use core::fmt; use core::iter::FusedIterator;
use core::str::FromStr; use core::str::FromStr;
use derive_more::Display;
use crate::tags; use crate::tags;
use crate::Error; use crate::Error;
@ -33,16 +35,14 @@ impl<'a> Iterator for Lines<'a> {
} }
} }
impl<'a> FusedIterator for Lines<'a> {}
impl<'a> From<&'a str> for Lines<'a> { impl<'a> From<&'a str> for Lines<'a> {
fn from(buffer: &'a str) -> Self { fn from(buffer: &'a str) -> Self {
Self { Self {
lines: buffer.lines().filter_map(|line| { lines: buffer
if line.trim().is_empty() { .lines()
None .filter_map(|line| Some(line.trim()).filter(|v| !v.is_empty())),
} else {
Some(line.trim())
}
}),
} }
} }
} }
@ -55,7 +55,8 @@ pub(crate) enum Line<'a> {
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Display)]
#[display(fmt = "{}")]
pub(crate) enum Tag<'a> { pub(crate) enum Tag<'a> {
ExtXVersion(tags::ExtXVersion), ExtXVersion(tags::ExtXVersion),
ExtInf(tags::ExtInf), ExtInf(tags::ExtInf),
@ -80,34 +81,6 @@ pub(crate) enum Tag<'a> {
Unknown(&'a str), Unknown(&'a str),
} }
impl<'a> fmt::Display for Tag<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Self::ExtXVersion(value) => value.fmt(f),
Self::ExtInf(value) => value.fmt(f),
Self::ExtXByteRange(value) => value.fmt(f),
Self::ExtXDiscontinuity(value) => value.fmt(f),
Self::ExtXKey(value) => value.fmt(f),
Self::ExtXMap(value) => value.fmt(f),
Self::ExtXProgramDateTime(value) => value.fmt(f),
Self::ExtXDateRange(value) => value.fmt(f),
Self::ExtXTargetDuration(value) => value.fmt(f),
Self::ExtXMediaSequence(value) => value.fmt(f),
Self::ExtXDiscontinuitySequence(value) => value.fmt(f),
Self::ExtXEndList(value) => value.fmt(f),
Self::ExtXPlaylistType(value) => value.fmt(f),
Self::ExtXIFramesOnly(value) => value.fmt(f),
Self::ExtXMedia(value) => value.fmt(f),
Self::VariantStream(value) => value.fmt(f),
Self::ExtXSessionData(value) => value.fmt(f),
Self::ExtXSessionKey(value) => value.fmt(f),
Self::ExtXIndependentSegments(value) => value.fmt(f),
Self::ExtXStart(value) => value.fmt(f),
Self::Unknown(value) => value.fmt(f),
}
}
}
impl<'a> TryFrom<&'a str> for Tag<'a> { impl<'a> TryFrom<&'a str> for Tag<'a> {
type Error = Error; type Error = Error;

View file

@ -15,8 +15,9 @@ use crate::utils::tag;
use crate::{Error, RequiredVersion}; use crate::{Error, RequiredVersion};
/// The master playlist describes all of the available variants for your /// The master playlist describes all of the available variants for your
/// content. Each variant is a version of the stream at a particular bitrate /// content.
/// and is contained in a separate playlist. /// Each variant is a version of the stream at a particular bitrate and is
/// contained in a separate playlist.
#[derive(ShortHand, Debug, Clone, Builder, PartialEq)] #[derive(ShortHand, Debug, Clone, Builder, PartialEq)]
#[builder(build_fn(validate = "Self::validate"))] #[builder(build_fn(validate = "Self::validate"))]
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
@ -67,21 +68,24 @@ pub struct MasterPlaylist {
/// This tag is optional. /// This tag is optional.
#[builder(default)] #[builder(default)]
variants: Vec<VariantStream>, variants: Vec<VariantStream>,
/// The [`ExtXSessionData`] tags of the playlist. /// The [`ExtXSessionData`] tag allows arbitrary session data to be
/// carried in a [`MasterPlaylist`].
/// ///
/// # Note /// # Note
/// ///
/// This tag is optional. /// This tag is optional.
#[builder(default)] #[builder(default)]
session_data: Vec<ExtXSessionData>, session_data: Vec<ExtXSessionData>,
/// The [`ExtXSessionKey`] tags of the playlist. /// This is a list of [`ExtXSessionKey`]s, that allows the client to preload
/// these keys without having to read the [`MediaPlaylist`]s first.
/// ///
/// # Note /// # Note
/// ///
/// This tag is optional. /// This tag is optional.
#[builder(default)] #[builder(default)]
session_keys: Vec<ExtXSessionKey>, session_keys: Vec<ExtXSessionKey>,
/// A list of tags that are unknown. /// This is a list of all tags that could not be identified while parsing
/// the input.
/// ///
/// # Note /// # Note
/// ///
@ -140,7 +144,7 @@ impl MasterPlaylistBuilder {
} }
fn validate_stream_inf(&self, value: &[VariantStream]) -> crate::Result<()> { fn validate_stream_inf(&self, value: &[VariantStream]) -> crate::Result<()> {
let mut has_none_closed_captions = false; let mut closed_captions_none = false;
for t in value { for t in value {
if let VariantStream::ExtXStreamInf { if let VariantStream::ExtXStreamInf {
@ -170,31 +174,24 @@ impl MasterPlaylistBuilder {
if let Some(closed_captions) = &closed_captions { if let Some(closed_captions) = &closed_captions {
match &closed_captions { match &closed_captions {
ClosedCaptions::GroupId(group_id) => { ClosedCaptions::GroupId(group_id) => {
if closed_captions_none {
return Err(Error::custom(
"If one ClosedCaptions is None all have to be None!",
));
}
if !self.check_media_group(MediaType::ClosedCaptions, group_id) { if !self.check_media_group(MediaType::ClosedCaptions, group_id) {
return Err(Error::unmatched_group(group_id)); return Err(Error::unmatched_group(group_id));
} }
} }
ClosedCaptions::None => { _ => {
has_none_closed_captions = true; if !closed_captions_none {
closed_captions_none = true;
} }
} }
} }
} }
} }
if has_none_closed_captions
&& !value.iter().all(|t| {
if let VariantStream::ExtXStreamInf {
closed_captions, ..
} = &t
{
closed_captions == &Some(ClosedCaptions::None)
} else {
false
}
})
{
return Err(Error::invalid_input());
} }
Ok(()) Ok(())
@ -218,6 +215,8 @@ impl MasterPlaylistBuilder {
let mut set = HashSet::new(); let mut set = HashSet::new();
if let Some(value) = &self.session_data { if let Some(value) = &self.session_data {
set.reserve(value.len());
for t in value { for t in value {
if !set.insert((t.data_id(), t.language())) { if !set.insert((t.data_id(), t.language())) {
return Err(Error::custom(format!("Conflict: {}", t))); return Err(Error::custom(format!("Conflict: {}", t)));
@ -264,20 +263,20 @@ impl fmt::Display for MasterPlaylist {
writeln!(f, "{}", ExtXVersion::new(self.required_version()))?; writeln!(f, "{}", ExtXVersion::new(self.required_version()))?;
} }
for t in &self.media { for value in &self.media {
writeln!(f, "{}", t)?; writeln!(f, "{}", value)?;
} }
for t in &self.variants { for value in &self.variants {
writeln!(f, "{}", t)?; writeln!(f, "{}", value)?;
} }
for t in &self.session_data { for value in &self.session_data {
writeln!(f, "{}", t)?; writeln!(f, "{}", value)?;
} }
for t in &self.session_keys { for value in &self.session_keys {
writeln!(f, "{}", t)?; writeln!(f, "{}", value)?;
} }
if let Some(value) = &self.independent_segments { if let Some(value) = &self.independent_segments {
@ -288,8 +287,8 @@ impl fmt::Display for MasterPlaylist {
writeln!(f, "{}", value)?; writeln!(f, "{}", value)?;
} }
for t in &self.unknown_tags { for value in &self.unknown_tags {
writeln!(f, "{}", t)?; writeln!(f, "{}", value)?;
} }
Ok(()) Ok(())
@ -382,40 +381,194 @@ impl FromStr for MasterPlaylist {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::types::StreamData;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn test_parser() { fn test_parser() {
"#EXTM3U\n\ assert_eq!(
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\ concat!(
http://example.com/low/index.m3u8\n\ "#EXTM3U\n",
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\ "#EXT-X-STREAM-INF:",
http://example.com/lo_mid/index.m3u8\n\ "BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\ "http://example.com/low/index.m3u8\n",
http://example.com/hi_mid/index.m3u8\n\ "#EXT-X-STREAM-INF:",
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\ "BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
http://example.com/high/index.m3u8\n\ "http://example.com/lo_mid/index.m3u8\n",
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\ "#EXT-X-STREAM-INF:",
http://example.com/audio/index.m3u8\n" "BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/hi_mid/index.m3u8\n",
"#EXT-X-STREAM-INF:",
"BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n",
"http://example.com/high/index.m3u8\n",
"#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n",
"http://example.com/audio/index.m3u8\n"
)
.parse::<MasterPlaylist>() .parse::<MasterPlaylist>()
.unwrap(); .unwrap(),
MasterPlaylist::builder()
.variants(vec![
VariantStream::ExtXStreamInf {
uri: "http://example.com/low/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(150000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/lo_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(240000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/hi_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(440000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/high/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(640000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((640, 360))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/audio/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(64000)
.codecs("mp4a.40.5")
.build()
.unwrap()
},
])
.build()
.unwrap()
);
} }
#[test] #[test]
fn test_display() { fn test_display() {
let input = "#EXTM3U\n\ assert_eq!(
#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\ concat!(
http://example.com/low/index.m3u8\n\ "#EXTM3U\n",
#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\ "#EXT-X-STREAM-INF:",
http://example.com/lo_mid/index.m3u8\n\ "BANDWIDTH=150000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n\ "http://example.com/low/index.m3u8\n",
http://example.com/hi_mid/index.m3u8\n\ "#EXT-X-STREAM-INF:",
#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n\ "BANDWIDTH=240000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
http://example.com/high/index.m3u8\n\ "http://example.com/lo_mid/index.m3u8\n",
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n\ "#EXT-X-STREAM-INF:",
http://example.com/audio/index.m3u8\n"; "BANDWIDTH=440000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=416x234\n",
"http://example.com/hi_mid/index.m3u8\n",
let playlist = input.parse::<MasterPlaylist>().unwrap(); "#EXT-X-STREAM-INF:",
assert_eq!(playlist.to_string(), input); "BANDWIDTH=640000,CODECS=\"avc1.42e00a,mp4a.40.2\",RESOLUTION=640x360\n",
"http://example.com/high/index.m3u8\n",
"#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS=\"mp4a.40.5\"\n",
"http://example.com/audio/index.m3u8\n"
)
.to_string(),
MasterPlaylist::builder()
.variants(vec![
VariantStream::ExtXStreamInf {
uri: "http://example.com/low/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(150000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/lo_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(240000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/hi_mid/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(440000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((416, 234))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/high/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(640000)
.codecs("avc1.42e00a,mp4a.40.2")
.resolution((640, 360))
.build()
.unwrap()
},
VariantStream::ExtXStreamInf {
uri: "http://example.com/audio/index.m3u8".into(),
frame_rate: None,
audio: None,
subtitles: None,
closed_captions: None,
stream_data: StreamData::builder()
.bandwidth(64000)
.codecs("mp4a.40.5")
.build()
.unwrap()
},
])
.build()
.unwrap()
.to_string()
);
} }
} }

View file

@ -41,9 +41,7 @@ pub struct MediaSegment {
} }
impl MediaSegment { impl MediaSegment {
/// Returns a builder for a [`MasterPlaylist`]. /// Returns a builder for a [`MediaSegment`].
///
/// [`MasterPlaylist`]: crate::MasterPlaylist
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() } pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
} }
@ -131,12 +129,14 @@ mod tests {
.build() .build()
.unwrap() .unwrap()
.to_string(), .to_string(),
"#EXT-X-KEY:METHOD=NONE\n\ concat!(
#EXT-X-MAP:URI=\"https://www.example.com/\"\n\ "#EXT-X-KEY:METHOD=NONE\n",
#EXT-X-BYTERANGE:20@5\n\ "#EXT-X-MAP:URI=\"https://www.example.com/\"\n",
#EXT-X-DISCONTINUITY\n\ "#EXT-X-BYTERANGE:20@5\n",
#EXTINF:4,\n\ "#EXT-X-DISCONTINUITY\n",
http://www.uri.com/\n" "#EXTINF:4,\n",
"http://www.uri.com/\n"
)
.to_string() .to_string()
); );
} }

View file

@ -58,7 +58,7 @@ pub struct ExtXSessionData {
/// ///
/// This field is required. /// This field is required.
data: SessionData, data: SessionData,
/// The `language` attribute identifies the language of [`SessionData`]. /// The `language` attribute identifies the language of the [`SessionData`].
/// See [rfc5646](https://tools.ietf.org/html/rfc5646). /// See [rfc5646](https://tools.ietf.org/html/rfc5646).
#[builder(setter(into, strip_option), default)] #[builder(setter(into, strip_option), default)]
language: Option<String>, language: Option<String>,

View file

@ -11,9 +11,11 @@ use crate::RequiredVersion;
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE] /// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
/// ///
/// The [`ExtXDiscontinuitySequence`] tag allows synchronization between /// The [`ExtXDiscontinuitySequence`] tag allows synchronization between
/// different Renditions of the same Variant Stream or different Variant /// different renditions of the same [`VariantStream`] or different
/// Streams that have [`ExtXDiscontinuity`] tags in their [`Media Playlist`]s. /// [`VariantStream`]s that have [`ExtXDiscontinuity`] tags in their
/// [`MediaPlaylist`]s.
/// ///
/// [`VariantStream`]: crate::tags::VariantStream
/// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity /// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity
/// [`MediaPlaylist`]: crate::MediaPlaylist /// [`MediaPlaylist`]: crate::MediaPlaylist
/// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]: /// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]:

View file

@ -65,7 +65,10 @@ impl RequiredVersion for ExtXMediaSequence {
} }
impl fmt::Display for ExtXMediaSequence { impl fmt::Display for ExtXMediaSequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", Self::PREFIX, self.0) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
//
write!(f, "{}{}", Self::PREFIX, self.0)
}
} }
impl FromStr for ExtXMediaSequence { impl FromStr for ExtXMediaSequence {

View file

@ -1,11 +1,14 @@
//! The tags in this section can appear in either Master Playlists or //! The tags in this section can appear in either [`MasterPlaylist`]s or
//! Media Playlists. If one of these tags appears in a Master Playlist, //! [`MediaPlaylist`]s. If one of these tags appears in a [`MasterPlaylist`],
//! it should not appear in any Media Playlist referenced by that Master //! it should not appear in any [`MediaPlaylist`] referenced by that
//! Playlist. A tag that appears in both must have the same value; //! [`MasterPlaylist`]. A tag that appears in both must have the same value;
//! otherwise, clients should ignore the value in the Media Playlist(s). //! otherwise, clients should ignore the value in the [`MediaPlaylist`](s).
//! //!
//! These tags must not appear more than once in a Playlist. If a tag //! These tags must not appear more than once in a Playlist. If a tag
//! appears more than once, clients must fail to parse the Playlist. //! appears more than once, clients must fail to parse the Playlist.
//!
//! [`MediaPlaylist`]: crate::MediaPlaylist
//! [`MasterPlaylist`]: crate::MasterPlaylist
mod independent_segments; mod independent_segments;
mod start; mod start;

View file

@ -87,6 +87,7 @@ pub trait Encrypted {
if self.keys().is_empty() { if self.keys().is_empty() {
return false; return false;
} }
self.keys() self.keys()
.iter() .iter()
.any(|k| k.method() != EncryptionMethod::None) .any(|k| k.method() != EncryptionMethod::None)
@ -140,7 +141,7 @@ pub trait RequiredVersion {
impl<T: RequiredVersion> RequiredVersion for Vec<T> { impl<T: RequiredVersion> RequiredVersion for Vec<T> {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
self.iter() self.iter()
.map(|v| v.required_version()) .map(RequiredVersion::required_version)
.max() .max()
// return ProtocolVersion::V1, if the iterator is empty: // return ProtocolVersion::V1, if the iterator is empty:
.unwrap_or_default() .unwrap_or_default()
@ -150,8 +151,26 @@ impl<T: RequiredVersion> RequiredVersion for Vec<T> {
impl<T: RequiredVersion> RequiredVersion for Option<T> { impl<T: RequiredVersion> RequiredVersion for Option<T> {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
self.iter() self.iter()
.map(|v| v.required_version()) .map(RequiredVersion::required_version)
.max() .max()
.unwrap_or_default() .unwrap_or_default()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_required_version_trait() {
struct Example;
impl RequiredVersion for Example {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V3 }
}
assert_eq!(Example.required_version(), ProtocolVersion::V3);
assert_eq!(Example.introduced_version(), ProtocolVersion::V3);
}
}

View file

@ -86,6 +86,32 @@ mod tests {
use super::*; use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test]
fn test_parse_iv_from_str() {
assert_eq!(
parse_iv_from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(),
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF
]
);
assert_eq!(
parse_iv_from_str("0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").unwrap(),
[
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF
]
);
// missing `0x` at the start:
assert!(parse_iv_from_str("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").is_err());
// too small:
assert!(parse_iv_from_str("0xFF").is_err());
// too large:
assert!(parse_iv_from_str("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF").is_err());
}
#[test] #[test]
fn test_parse_yes_or_no() { fn test_parse_yes_or_no() {
assert!(parse_yes_or_no("YES").unwrap()); assert!(parse_yes_or_no("YES").unwrap());