mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 08:31:00 +00:00
improve DecryptionKey
This commit is contained in:
parent
7c26d2f7f1
commit
fc1136265c
5 changed files with 366 additions and 80 deletions
|
@ -14,14 +14,14 @@ use crate::{Error, RequiredVersion};
|
|||
/// preload these keys without having to read the [`MediaPlaylist`]s
|
||||
/// first.
|
||||
///
|
||||
/// If an [`ExtXSessionKey`] is used, the values of [`ExtXKey::method`],
|
||||
/// [`ExtXKey::key_format`] and [`ExtXKey::key_format_versions`] must match any
|
||||
/// If an [`ExtXSessionKey`] is used, the values of [`DecryptionKey::method`],
|
||||
/// [`DecryptionKey::format`] and [`DecryptionKey::versions`] must match any
|
||||
/// [`ExtXKey`] with the same uri field.
|
||||
///
|
||||
/// [`MediaPlaylist`]: crate::MediaPlaylist
|
||||
/// [`MasterPlaylist`]: crate::MasterPlaylist
|
||||
/// [`ExtXKey`]: crate::tags::ExtXKey
|
||||
#[derive(AsRef, AsMut, From, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[from(forward)]
|
||||
pub struct ExtXSessionKey(pub DecryptionKey);
|
||||
|
||||
impl ExtXSessionKey {
|
||||
|
|
|
@ -5,53 +5,73 @@ use derive_builder::Builder;
|
|||
use shorthand::ShortHand;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{EncryptionMethod, KeyFormat, KeyFormatVersions, ProtocolVersion};
|
||||
use crate::utils::{parse_iv_from_str, quote, unquote};
|
||||
use crate::types::{
|
||||
EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion,
|
||||
};
|
||||
use crate::utils::{quote, unquote};
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// Specifies how to decrypt encrypted data from the server.
|
||||
#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[builder(setter(into), build_fn(validate = "Self::validate"))]
|
||||
#[shorthand(enable(must_use, into))]
|
||||
#[shorthand(enable(skip, must_use, into))]
|
||||
#[non_exhaustive]
|
||||
pub struct DecryptionKey {
|
||||
/// HLS supports multiple [`EncryptionMethod`]s for a [`MediaSegment`].
|
||||
/// The encryption method, which has been used to encrypt the data.
|
||||
///
|
||||
/// For example `AES-128`.
|
||||
/// An [`EncryptionMethod::Aes128`] signals that the data is encrypted using
|
||||
/// the Advanced Encryption Standard (AES) with a 128-bit key, Cipher Block
|
||||
/// Chaining (CBC), and Public-Key Cryptography Standards #7 (PKCS7)
|
||||
/// padding. CBC is restarted on each segment boundary, using either the
|
||||
/// [`DecryptionKey::iv`] field or the [`MediaSegment::number`] as the IV.
|
||||
///
|
||||
/// An [`EncryptionMethod::SampleAes`] means that the [`MediaSegment`]s
|
||||
/// contain media samples, such as audio or video, that are encrypted using
|
||||
/// the Advanced Encryption Standard (Aes128). How these media streams are
|
||||
/// encrypted and encapsulated in a segment depends on the media encoding
|
||||
/// and the media format of the segment.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This field is required.
|
||||
///
|
||||
/// [`MediaSegment::number`]: crate::MediaSegment::number
|
||||
/// [`MediaSegment`]: crate::MediaSegment
|
||||
//#[shorthand(enable(skip))]
|
||||
#[shorthand(enable(copy))]
|
||||
pub method: EncryptionMethod,
|
||||
/// An `URI` that specifies how to obtain the key.
|
||||
/// This uri points to a key file, which contains the cipher key.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This attribute is required, if the [`EncryptionMethod`] is not `None`.
|
||||
/// This field is required.
|
||||
#[builder(setter(into, strip_option), default)]
|
||||
#[shorthand(disable(skip))]
|
||||
pub(crate) uri: String,
|
||||
/// An IV (initialization vector) is used to prevent repetitions between
|
||||
/// segments of encrypted data.
|
||||
/// An initialization vector (IV) is a fixed size input that can be used
|
||||
/// along with a secret key for data encryption.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This field is optional and an absent value indicates that
|
||||
/// [`MediaSegment::number`] should be used instead.
|
||||
///
|
||||
/// [`MediaSegment::number`]: crate::MediaSegment::number
|
||||
#[builder(setter(into, strip_option), default)]
|
||||
pub iv: InitializationVector,
|
||||
/// A server may offer multiple ways to retrieve a key by providing multiple
|
||||
/// [`DecryptionKey`]s with different [`KeyFormat`] values.
|
||||
///
|
||||
/// An [`EncryptionMethod::Aes128`] uses 16-octet (16 byte/128 bit) keys. If
|
||||
/// the format is [`KeyFormat::Identity`], the key file is a single packed
|
||||
/// array of 16 octets (16 byte/128 bit) in binary format.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(setter(into, strip_option), default)]
|
||||
// TODO: workaround for https://github.com/Luro02/shorthand/issues/20
|
||||
#[shorthand(enable(copy), disable(option_as_ref))]
|
||||
pub(crate) iv: Option<[u8; 0x10]>,
|
||||
/// The [`KeyFormat`] specifies how the key is
|
||||
/// represented in the resource identified by the `URI`.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(setter(into, strip_option), default)]
|
||||
#[shorthand(enable(copy))]
|
||||
pub format: Option<KeyFormat>,
|
||||
/// The [`KeyFormatVersions`] attribute.
|
||||
/// A list of numbers that can be used to indicate which version(s)
|
||||
/// this instance complies with, if more than one version of a particular
|
||||
/// [`KeyFormat`] is defined.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
|
@ -67,7 +87,7 @@ impl DecryptionKey {
|
|||
Self {
|
||||
method,
|
||||
uri: uri.into(),
|
||||
iv: None,
|
||||
iv: InitializationVector::default(),
|
||||
format: None,
|
||||
versions: None,
|
||||
}
|
||||
|
@ -115,7 +135,7 @@ impl FromStr for DecryptionKey {
|
|||
uri = Some(unquoted_uri);
|
||||
}
|
||||
}
|
||||
"IV" => iv = Some(parse_iv_from_str(value)?),
|
||||
"IV" => iv = Some(value.parse()?),
|
||||
"KEYFORMAT" => format = Some(value.parse()?),
|
||||
"KEYFORMATVERSIONS" => versions = Some(value.parse()?),
|
||||
_ => {
|
||||
|
@ -128,6 +148,7 @@ impl FromStr for DecryptionKey {
|
|||
|
||||
let method = method.ok_or_else(|| Error::missing_value("METHOD"))?;
|
||||
let uri = uri.ok_or_else(|| Error::missing_value("URI"))?;
|
||||
let iv = iv.unwrap_or_default();
|
||||
|
||||
Ok(Self {
|
||||
method,
|
||||
|
@ -143,11 +164,8 @@ impl fmt::Display for DecryptionKey {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "METHOD={},URI={}", self.method, quote(&self.uri))?;
|
||||
|
||||
if let Some(value) = &self.iv {
|
||||
let mut result = [0; 0x10 * 2];
|
||||
::hex::encode_to_slice(value, &mut result).unwrap();
|
||||
|
||||
write!(f, ",IV=0x{}", ::core::str::from_utf8(&result).unwrap())?;
|
||||
if let InitializationVector::Aes128(_) = &self.iv {
|
||||
write!(f, ",IV={}", &self.iv)?;
|
||||
}
|
||||
|
||||
if let Some(value) = &self.format {
|
||||
|
@ -215,9 +233,10 @@ mod test {
|
|||
#[test]
|
||||
fn test_builder() {
|
||||
let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/");
|
||||
key.set_iv(Some([
|
||||
key.iv = [
|
||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82,
|
||||
]));
|
||||
]
|
||||
.into();
|
||||
key.format = Some(KeyFormat::Identity);
|
||||
key.versions = Some(vec![1, 2, 3, 4, 5].into());
|
||||
|
||||
|
|
305
src/types/initialization_vector.rs
Normal file
305
src/types/initialization_vector.rs
Normal file
|
@ -0,0 +1,305 @@
|
|||
use core::fmt;
|
||||
use core::str::FromStr;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
/// An initialization vector (IV) is a fixed size input that can be used along
|
||||
/// with a secret key for data encryption.
|
||||
///
|
||||
/// The use of an IV prevents repetition in encrypted data, making it more
|
||||
/// difficult for a hacker using a dictionary attack to find patterns and break
|
||||
/// a cipher. For example, a sequence might appear twice or more within the body
|
||||
/// of a message. If there are repeated sequences in encrypted data, an attacker
|
||||
/// could assume that the corresponding sequences in the message were also
|
||||
/// identical. The IV prevents the appearance of corresponding duplicate
|
||||
/// character sequences in the ciphertext.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum InitializationVector {
|
||||
/// An IV for use with Aes128.
|
||||
Aes128([u8; 0x10]),
|
||||
/// An [`ExtXKey`] tag with [`KeyFormat::Identity`] that does not have an IV
|
||||
/// field indicates that the [`MediaSegment::number`] is to be used as the
|
||||
/// IV when decrypting a `MediaSegment`.
|
||||
///
|
||||
/// [`ExtXKey`]: crate::tags::ExtXKey
|
||||
/// [`KeyFormat::Identity`]: crate::types::KeyFormat::Identity
|
||||
/// [`MediaSegment::number`]: crate::MediaSegment::number
|
||||
Number(u128),
|
||||
/// Signals that an IV is missing.
|
||||
Missing,
|
||||
}
|
||||
|
||||
impl InitializationVector {
|
||||
/// Returns the IV as an [`u128`]. `None` is returned for
|
||||
/// [`InitializationVector::Missing`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::InitializationVector;
|
||||
/// assert_eq!(
|
||||
/// InitializationVector::Aes128([
|
||||
/// 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78,
|
||||
/// 0x90, 0x12
|
||||
/// ])
|
||||
/// .to_u128(),
|
||||
/// Some(0x12345678901234567890123456789012)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Number(0x10).to_u128(), Some(0x10));
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Missing.to_u128(), None);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn to_u128(&self) -> Option<u128> {
|
||||
match *self {
|
||||
Self::Aes128(v) => Some(u128::from_be_bytes(v)),
|
||||
Self::Number(n) => Some(n),
|
||||
Self::Missing => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the IV as a slice, which can be used to for example decrypt
|
||||
/// a [`MediaSegment`]. `None` is returned for
|
||||
/// [`InitializationVector::Missing`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::InitializationVector;
|
||||
/// assert_eq!(
|
||||
/// InitializationVector::Aes128([
|
||||
/// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
/// 0x0F, 0x10,
|
||||
/// ])
|
||||
/// .to_slice(),
|
||||
/// Some([
|
||||
/// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
/// 0x0F, 0x10,
|
||||
/// ])
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// InitializationVector::Number(0x12345678901234567890123456789012).to_slice(),
|
||||
/// Some([
|
||||
/// 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78,
|
||||
/// 0x90, 0x12
|
||||
/// ])
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Missing.to_slice(), None);
|
||||
/// ```
|
||||
///
|
||||
/// [`MediaSegment`]: crate::MediaSegment
|
||||
#[must_use]
|
||||
pub fn to_slice(&self) -> Option<[u8; 0x10]> {
|
||||
match &self {
|
||||
Self::Aes128(v) => Some(*v),
|
||||
Self::Number(v) => Some(v.to_be_bytes()),
|
||||
Self::Missing => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the initialization vector is not missing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::InitializationVector;
|
||||
/// assert_eq!(
|
||||
/// InitializationVector::Aes128([
|
||||
/// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
/// 0x0F, 0x10,
|
||||
/// ])
|
||||
/// .is_some(),
|
||||
/// true
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Number(4).is_some(), true);
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Missing.is_some(), false);
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn is_some(&self) -> bool { *self != Self::Missing }
|
||||
|
||||
/// Returns `true` if the initialization vector is missing.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hls_m3u8::types::InitializationVector;
|
||||
/// assert_eq!(
|
||||
/// InitializationVector::Aes128([
|
||||
/// 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
/// 0x0F, 0x10,
|
||||
/// ])
|
||||
/// .is_none(),
|
||||
/// false
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Number(4).is_none(), false);
|
||||
///
|
||||
/// assert_eq!(InitializationVector::Missing.is_none(), true);
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool { *self == Self::Missing }
|
||||
}
|
||||
|
||||
impl Default for InitializationVector {
|
||||
fn default() -> Self { Self::Missing }
|
||||
}
|
||||
|
||||
impl From<[u8; 0x10]> for InitializationVector {
|
||||
fn from(value: [u8; 0x10]) -> Self { Self::Aes128(value) }
|
||||
}
|
||||
|
||||
impl From<Option<[u8; 0x10]>> for InitializationVector {
|
||||
fn from(value: Option<[u8; 0x10]>) -> Self {
|
||||
match value {
|
||||
Some(v) => Self::Aes128(v),
|
||||
None => Self::Missing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InitializationVector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self {
|
||||
Self::Aes128(buffer) => {
|
||||
let mut result = [0; 0x10 * 2];
|
||||
::hex::encode_to_slice(buffer, &mut result).unwrap();
|
||||
|
||||
write!(f, "0x{}", ::core::str::from_utf8(&result).unwrap())?;
|
||||
}
|
||||
Self::Number(num) => {
|
||||
write!(f, "InitializationVector::Number({})", num)?;
|
||||
}
|
||||
Self::Missing => {
|
||||
write!(f, "InitializationVector::Missing")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for InitializationVector {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
if !(input.starts_with("0x") || input.starts_with("0X")) {
|
||||
return Err(Error::custom("An IV should either start with `0x` or `0X`"));
|
||||
}
|
||||
|
||||
if input.len() - 2 != 32 {
|
||||
return Err(Error::custom(
|
||||
"An IV must be 32 bytes long + 2 bytes for 0x/0X",
|
||||
));
|
||||
}
|
||||
|
||||
let mut result = [0; 16];
|
||||
|
||||
::hex::decode_to_slice(&input.as_bytes()[2..], &mut result).map_err(Error::hex)?;
|
||||
|
||||
Ok(Self::Aes128(result))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
assert_eq!(
|
||||
InitializationVector::default(),
|
||||
InitializationVector::Missing
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from() {
|
||||
assert_eq!(
|
||||
InitializationVector::from([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
]),
|
||||
InitializationVector::Aes128([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InitializationVector::from(None),
|
||||
InitializationVector::Missing
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InitializationVector::from(Some([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
])),
|
||||
InitializationVector::Aes128([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
InitializationVector::Aes128([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
])
|
||||
.to_string(),
|
||||
"0xffffffffffffffffffffffffffffffff".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InitializationVector::Number(5).to_string(),
|
||||
"InitializationVector::Number(5)".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InitializationVector::Missing.to_string(),
|
||||
"InitializationVector::Missing".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
assert_eq!(
|
||||
InitializationVector::Aes128([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
]),
|
||||
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".parse().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
InitializationVector::Aes128([
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF
|
||||
]),
|
||||
"0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".parse().unwrap()
|
||||
);
|
||||
|
||||
// missing `0x` at the start:
|
||||
assert!("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
.parse::<InitializationVector>()
|
||||
.is_err());
|
||||
// too small:
|
||||
assert!("0xFF".parse::<InitializationVector>().is_err());
|
||||
// too large:
|
||||
assert!("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
.parse::<InitializationVector>()
|
||||
.is_err());
|
||||
}
|
||||
}
|
|
@ -5,12 +5,16 @@ use crate::types::ProtocolVersion;
|
|||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::{Error, RequiredVersion};
|
||||
|
||||
/// [`KeyFormat`] specifies, how the key is represented in the
|
||||
/// resource identified by the `URI`.
|
||||
/// Specifies how the key is represented in the resource identified by the
|
||||
/// `URI`.
|
||||
#[non_exhaustive]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
pub enum KeyFormat {
|
||||
/// The key is a single packed array of 16 octets in binary format.
|
||||
/// An [`EncryptionMethod::Aes128`] uses 16-octet (16 byte/128 bit) keys. If
|
||||
/// the format is [`KeyFormat::Identity`], the key file is a single packed
|
||||
/// array of 16 octets (16 byte/128 bit) in binary format.
|
||||
///
|
||||
/// [`EncryptionMethod::Aes128`]: crate::types::EncryptionMethod::Aes128
|
||||
Identity,
|
||||
}
|
||||
|
||||
|
|
42
src/utils.rs
42
src/utils.rs
|
@ -12,22 +12,6 @@ macro_rules! required_version {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_iv_from_str(input: &str) -> crate::Result<[u8; 16]> {
|
||||
if !(input.starts_with("0x") || input.starts_with("0X")) {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
|
||||
if input.len() - 2 != 32 {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
|
||||
let mut result = [0; 16];
|
||||
|
||||
hex::decode_to_slice(&input.as_bytes()[2..], &mut result).map_err(Error::hex)?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> crate::Result<bool> {
|
||||
match s.as_ref() {
|
||||
"YES" => Ok(true),
|
||||
|
@ -86,32 +70,6 @@ mod tests {
|
|||
use super::*;
|
||||
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]
|
||||
fn test_parse_yes_or_no() {
|
||||
assert!(parse_yes_or_no("YES").unwrap());
|
||||
|
|
Loading…
Reference in a new issue