format/specific: don't expose inner as pub

... users would be able to bypass the range checks and build a
defined Rust value which would be interpreted as `None` in C code.

Added format module examples for formatted values constructions.
This commit is contained in:
François Laignel 2022-10-10 13:31:24 +02:00
parent f6336b1be3
commit 08551bb1bc
9 changed files with 142 additions and 60 deletions

View file

@ -294,20 +294,19 @@ mod tests {
let mut buffer = gst::Buffer::with_size(1024).unwrap();
let start = gst::format::Default::ONE;
let stop = 2 * gst::format::Default::ONE;
{
let cmeta = AudioClippingMeta::add(
buffer.get_mut().unwrap(),
gst::format::Default(1),
gst::format::Default(2),
);
assert_eq!(cmeta.start().try_into(), Ok(Some(gst::format::Default(1))));
assert_eq!(cmeta.end().try_into(), Ok(Some(gst::format::Default(2))));
let cmeta = AudioClippingMeta::add(buffer.get_mut().unwrap(), start, stop);
assert_eq!(cmeta.start().try_into(), Ok(Some(start)));
assert_eq!(cmeta.end().try_into(), Ok(Some(stop)));
}
{
let cmeta = buffer.meta::<AudioClippingMeta>().unwrap();
assert_eq!(cmeta.start().try_into(), Ok(Some(gst::format::Default(1))));
assert_eq!(cmeta.end().try_into(), Ok(Some(gst::format::Default(2))));
assert_eq!(cmeta.start().try_into(), Ok(Some(start)));
assert_eq!(cmeta.end().try_into(), Ok(Some(stop)));
}
}

View file

@ -14,7 +14,7 @@ use super::{
};
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub struct ClockTime(pub(crate) u64);
pub struct ClockTime(u64);
impl ClockTime {
#[doc(alias = "GST_SECOND")]

View file

@ -7,14 +7,20 @@ use crate::ClockTime;
impl Serialize for ClockTime {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
use std::ops::Deref;
self.deref().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ClockTime {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
skip_assert_initialized!();
u64::deserialize(deserializer).map(ClockTime::from_nseconds)
u64::deserialize(deserializer).and_then(|value| {
ClockTime::try_from(value).map_err(|_| {
use serde::de::{Error, Unexpected};
D::Error::invalid_value(Unexpected::Unsigned(value), &"valid `ClockTime`")
})
})
}
}

View file

@ -75,7 +75,7 @@ mod tests {
let res = serde_json::to_string(&value).unwrap();
assert_eq!("{\"Undefined\":42}".to_owned(), res);
let value = GenericFormattedValue::from(Default(42));
let value = GenericFormattedValue::from(42 * Default::ONE);
let res = ron::ser::to_string_pretty(&value, pretty_config.clone());
assert_eq!(Ok("Default(Some(42))".to_owned()), res);
let res = serde_json::to_string(&value).unwrap();
@ -87,7 +87,7 @@ mod tests {
let res = serde_json::to_string(&value).unwrap();
assert_eq!("{\"Default\":null}".to_owned(), res);
let value = GenericFormattedValue::from(Bytes(42));
let value = GenericFormattedValue::from(42 * Bytes::ONE);
let res = ron::ser::to_string_pretty(&value, pretty_config.clone());
assert_eq!(Ok("Bytes(Some(42))".to_owned()), res);
let res = serde_json::to_string(&value).unwrap();
@ -99,19 +99,21 @@ mod tests {
let res = serde_json::to_string(&value).unwrap();
assert_eq!("{\"Time\":42123456789}".to_owned(), res);
let value = GenericFormattedValue::from(Buffers(42));
let value = GenericFormattedValue::from(42 * Buffers::ONE);
let res = ron::ser::to_string_pretty(&value, pretty_config.clone());
assert_eq!(Ok("Buffers(Some(42))".to_owned()), res);
let res = serde_json::to_string(&value).unwrap();
assert_eq!("{\"Buffers\":42}".to_owned(), res);
let value = GenericFormattedValue::from(Percent::try_from(0.42).unwrap());
let percent = Percent::try_from(0.42).unwrap();
let value = GenericFormattedValue::from(percent);
let res = ron::ser::to_string_pretty(&value, pretty_config.clone());
assert_eq!(Ok("Percent(Some(4200))".to_owned()), res);
let res = serde_json::to_string(&value).unwrap();
assert_eq!("{\"Percent\":4200}".to_owned(), res);
let value = GenericFormattedValue::Other(Format::Percent, Other::try_from(42).ok());
let other = Other::try_from(42).ok();
let value = GenericFormattedValue::Other(Format::Percent, other);
let res = ron::ser::to_string_pretty(&value, pretty_config.clone());
assert_eq!(Ok("Other(Percent, Some(42))".to_owned()), res);
let res = serde_json::to_string(&value).unwrap();
@ -130,14 +132,14 @@ mod tests {
let value_ron = "Default(Some(42))";
let value_de: GenericFormattedValue = ron::de::from_str(value_ron).unwrap();
assert_eq!(value_de, GenericFormattedValue::from(Default(42)));
assert_eq!(value_de, GenericFormattedValue::from(42 * Default::ONE));
let value_json = "{\"Default\":42}";
let value_de: GenericFormattedValue = serde_json::from_str(value_json).unwrap();
assert_eq!(value_de, GenericFormattedValue::from(Default(42)));
assert_eq!(value_de, GenericFormattedValue::from(42 * Default::ONE));
let value_ron = "Other(Percent, Some(42))";
let gfv_value = GenericFormattedValue::Other(Format::Percent, Other::try_from(42).ok());
let gfv_value = GenericFormattedValue::Other(Format::Percent, Some(42 * Other::ONE));
let value_de: GenericFormattedValue = ron::de::from_str(value_ron).unwrap();
assert_eq!(value_de, gfv_value);
@ -159,16 +161,14 @@ mod tests {
}
);
test_roundrip!(GenericFormattedValue::Undefined(Undefined::from(42)));
test_roundrip!(GenericFormattedValue::from(Default(42)));
test_roundrip!(GenericFormattedValue::from(Bytes(42)));
test_roundrip!(GenericFormattedValue::Undefined(Undefined(42)));
test_roundrip!(GenericFormattedValue::from(42 * Default::ONE));
test_roundrip!(GenericFormattedValue::from(42 * Bytes::ONE));
test_roundrip!(GenericFormattedValue::from(ClockTime::from_nseconds(
42_123_456_789
)));
test_roundrip!(GenericFormattedValue::from(Buffers(42)));
test_roundrip!(GenericFormattedValue::from(
Percent::try_from(0.42).unwrap()
));
test_roundrip!(GenericFormattedValue::from(42 * Buffers::ONE));
test_roundrip!(GenericFormattedValue::from(42 * Percent::ONE));
let gfv_value = GenericFormattedValue::Other(Format::Percent, Other::try_from(42).ok());
test_roundrip!(gfv_value);
test_roundrip!(GenericFormattedValue::new(Format::__Unknown(7), 42));

View file

@ -229,9 +229,16 @@ macro_rules! impl_unsigned_int_into_signed(
macro_rules! impl_common_ops_for_newtype_uint(
($typ:ty, $inner:ty) => {
impl_common_ops_for_newtype_uint!($typ, $inner, one: 1);
};
($typ:ty, $inner:ty, one: $one:expr$(,)?) => {
impl $typ {
pub const ZERO: Self = Self(0);
pub const NONE: Option<Self> = None;
// rustdoc-stripper-ignore-next
/// The unitary value.
pub const ONE: Self = Self($one);
pub const MAX_SIGNED: crate::Signed::<$typ> = crate::Signed::Positive(Self::MAX);
pub const MIN_SIGNED: crate::Signed::<$typ> = crate::Signed::Negative(Self::MAX);
@ -1501,22 +1508,30 @@ macro_rules! impl_format_value_traits(
);
macro_rules! option_glib_newtype_from_to {
($typ_:ident, $none_value:expr) => {
($typ:ident, $none_value:expr) => {
#[doc(hidden)]
impl IntoGlib for $typ_ {
impl IntoGlib for $typ {
type GlibType = u64;
fn into_glib(self) -> u64 {
assert_ne!(
self.0, $none_value,
concat!(
"attempt to build a `None` glib variant",
"from a non-`Option` type ",
stringify!($typ),
),
);
self.0
}
}
#[doc(hidden)]
impl OptionIntoGlib for $typ_ {
impl OptionIntoGlib for $typ {
const GLIB_NONE: u64 = $none_value;
}
#[doc(hidden)]
impl TryFromGlib<u64> for $typ_ {
impl TryFromGlib<u64> for $typ {
type Error = GlibNoneError;
#[inline]
unsafe fn try_from_glib(val: u64) -> Result<Self, GlibNoneError> {
@ -1525,7 +1540,7 @@ macro_rules! option_glib_newtype_from_to {
return Err(GlibNoneError);
}
Ok($typ_(val))
Ok($typ(val))
}
}
};

View file

@ -17,7 +17,8 @@
//! Specific formatted values are also guaranteed to always represent a valid value.
//! For instance:
//!
//! - [`Percent`] only allows values in the range [0, 1_000_000].
//! - [`Percent`] only allows values in the integer range [0, 1_000_000] or
//! float range [0.0, 1.0].
//! - [`ClockTime`] can use all `u64` values except `u64::MAX` which is reserved by
//! the C constant `GST_CLOCK_TIME_NONE`.
//!
@ -60,6 +61,52 @@
//! assert_eq!(start.format(), gst::Format::Time);
//! ```
//!
//! ### Building a specific formatted value
//!
//! ```
//! # use gstreamer as gst;
//! use gst::format::{Buffers, Bytes, ClockTime, Default, Percent};
//!
//! // Specific formatted values implement the faillible `try_from` constructor:
//! let default = Default::try_from(42).unwrap();
//! assert_eq!(*default, 42);
//! assert_eq!(Default::try_from(42), Ok(default));
//! assert_eq!(Default::try_from(42).ok(), Some(default));
//!
//! // `ClockTime` provides specific constructors:
//! let time = ClockTime::from_nseconds(45_834_908_569_837);
//! let time = ClockTime::from_seconds(20);
//!
//! // This can be convenient:
//! assert_eq!(
//! 20 * ClockTime::MSECOND,
//! ClockTime::from_nseconds(20_000_000),
//! );
//! assert_eq!(
//! 40 * ClockTime::SECOND,
//! ClockTime::from_nseconds(40_000_000_000),
//! );
//!
//! // Specific formatted values provide the `ONE` value:
//! assert_eq!(*(128 * Buffers::ONE), 128);
//!
//! // `ZERO` and `NONE` can also come in handy sometimes:
//! assert_eq!(*Buffers::ZERO, 0);
//! assert!(ClockTime::NONE.is_none());
//!
//! // `Bytes` also comes with usual multipliers:
//! assert_eq!(*(512 * Bytes::K), 512 * 1024);
//! assert_eq!(*(8 * Bytes::M), 8 * 1024 * 1024);
//! assert_eq!(*(4 * Bytes::G), 4 * 1024 * 1024 * 1024);
//!
//! // `Percent` can be built from a float:
//! let a_quarter = Percent::try_from(0.25).unwrap();
//! // `Percent` has `SCALE` which represents 100%:
//! assert_eq!(Percent::SCALE / 4, a_quarter);
//! // ... and `ONE` which is 1%:
//! assert_eq!(25 * Percent::ONE, a_quarter);
//! ```
//!
//! ### Displaying a formatted value
//!
//! Formatted values implement the [`Display`] trait which allows getting
@ -334,7 +381,7 @@
//! // Signed formatted values implement the `MulDiv` trait:
//! # use gst::prelude::MulDiv;
//! # let rate = 48000u64;
//! let samples = gst::format::Default(1024).into_negative();
//! let samples = (1024 * gst::format::Default::ONE).into_negative();
//! let duration = samples
//! .mul_div_round(*gst::ClockTime::SECOND, rate)
//! .map(|signed_default| {
@ -383,7 +430,7 @@
//! # use gstreamer as gst;
//! # use gst::prelude::{Displayable, ElementExtManual};
//! # gst::init();
//! # let event = gst::event::SegmentDone::new(gst::format::Buffers(512));
//! # let event = gst::event::SegmentDone::new(512 * gst::format::Buffers::ONE);
//! if let gst::EventView::SegmentDone(seg_done_evt) = event.view() {
//! use gst::GenericFormattedValue::*;
//! match seg_done_evt.get() {
@ -592,11 +639,11 @@ mod tests {
fn incompatible() {
with_compatible_formats(
ClockTime::ZERO,
GenericFormattedValue::Buffers(Some(Buffers(42))),
GenericFormattedValue::Buffers(Some(42 * Buffers::ONE)),
)
.unwrap_err();
with_compatible_formats(
GenericFormattedValue::Buffers(Some(Buffers(42))),
GenericFormattedValue::Buffers(Some(42 * Buffers::ONE)),
ClockTime::NONE,
)
.unwrap_err();
@ -744,20 +791,18 @@ mod tests {
assert!(signed.positive().is_none());
assert_eq!(signed.signum(), -1);
let def = Default(1);
let signed = def.into_positive();
assert_eq!(signed, Signed::Positive(def));
let signed = Default::ONE.into_positive();
assert_eq!(signed, Signed::Positive(Default::ONE));
assert!(signed.is_positive());
assert_eq!(signed.positive(), Some(def));
assert_eq!(signed.positive(), Some(Default::ONE));
assert!(!signed.is_negative());
assert!(signed.negative().is_none());
assert_eq!(signed.signum(), 1);
let signed = def.into_negative();
assert_eq!(signed, Signed::Negative(def));
let signed = Default::ONE.into_negative();
assert_eq!(signed, Signed::Negative(Default::ONE));
assert!(signed.is_negative());
assert_eq!(signed.negative(), Some(def));
assert_eq!(signed.negative(), Some(Default::ONE));
assert!(!signed.is_positive());
assert!(signed.positive().is_none());
assert_eq!(signed.signum(), -1);
@ -855,14 +900,14 @@ mod tests {
#[test]
fn display_new_types() {
let bytes = Bytes(42);
let bytes = 42 * Bytes::ONE;
assert_eq!(&format!("{bytes}"), "42 bytes");
assert_eq!(&format!("{}", bytes.display()), "42 bytes");
assert_eq!(&format!("{}", Some(bytes).display()), "42 bytes");
assert_eq!(&format!("{}", Bytes::NONE.display()), "undef. bytes");
let gv_1 = GenericFormattedValue::Percent(Percent::try_from(0.42).ok());
let gv_1 = GenericFormattedValue::Percent(Some(42 * Percent::ONE));
assert_eq!(&format!("{gv_1}"), "42 %");
assert_eq!(
&format!("{}", GenericFormattedValue::Percent(None)),
@ -890,24 +935,25 @@ mod tests {
#[test]
fn display_signed() {
let p_bytes = Bytes(42).into_positive();
let bytes_42 = 42 * Bytes::ONE;
let p_bytes = bytes_42.into_positive();
assert_eq!(&format!("{p_bytes}"), "+42 bytes");
assert_eq!(&format!("{}", p_bytes.display()), "+42 bytes");
let some_p_bytes = Some(p_bytes);
assert_eq!(&format!("{}", some_p_bytes.display()), "+42 bytes");
let p_some_bytes = Signed::Positive(Some(Bytes(42)));
let p_some_bytes = Signed::Positive(Some(bytes_42));
assert_eq!(&format!("{}", p_some_bytes.display()), "+42 bytes");
let n_bytes = Bytes(42).into_negative();
let n_bytes = bytes_42.into_negative();
assert_eq!(&format!("{n_bytes}"), "-42 bytes");
assert_eq!(&format!("{}", n_bytes.display()), "-42 bytes");
let some_n_bytes = Some(n_bytes);
assert_eq!(&format!("{}", some_n_bytes.display()), "-42 bytes");
let n_some_bytes = Signed::Negative(Some(Bytes(42)));
let n_some_bytes = Signed::Negative(Some(bytes_42));
assert_eq!(&format!("{}", n_some_bytes.display()), "-42 bytes");
let p_none_bytes = Signed::Positive(Bytes::NONE);

View file

@ -21,7 +21,7 @@ pub trait SpecificFormattedValueFullRange: FormattedValueFullRange {}
pub trait SpecificFormattedValueIntrinsic: TryFromGlib<i64> + FormattedValueIntrinsic {}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default)]
pub struct Buffers(pub u64);
pub struct Buffers(u64);
impl Buffers {
#[doc(alias = "GST_BUFFER_OFFSET_NONE")]
pub const OFFSET_NONE: u64 = ffi::GST_BUFFER_OFFSET_NONE;
@ -35,8 +35,17 @@ option_glib_newtype_from_to!(Buffers, Buffers::OFFSET_NONE);
glib_newtype_display!(Buffers, DisplayableOptionBuffers, Format::Buffers);
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default)]
pub struct Bytes(pub u64);
pub struct Bytes(u64);
impl Bytes {
// rustdoc-stripper-ignore-next
/// 1K Bytes (1024).
pub const K: Self = Self(1024);
// rustdoc-stripper-ignore-next
/// 1M Bytes (1024 * 1024).
pub const M: Self = Self(1024 * 1024);
// rustdoc-stripper-ignore-next
/// 1G Bytes (1024 * 1024 * 1024).
pub const G: Self = Self(1024 * 1024 * 1024);
pub const MAX: Self = Self(u64::MAX - 1);
}
@ -47,7 +56,7 @@ option_glib_newtype_from_to!(Bytes, u64::MAX);
glib_newtype_display!(Bytes, DisplayableOptionBytes, Format::Bytes);
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default)]
pub struct Default(pub u64);
pub struct Default(u64);
impl Default {
pub const MAX: Self = Self(u64::MAX - 1);
}
@ -61,15 +70,19 @@ glib_newtype_display!(Default, DisplayableOptionDefault, Format::Default);
pub type Time = super::ClockTime;
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default)]
pub struct Percent(pub u32);
pub struct Percent(u32);
impl Percent {
#[doc(alias = "GST_FORMAT_PERCENT_MAX")]
pub const MAX: Self = Self(ffi::GST_FORMAT_PERCENT_MAX as u32);
#[doc(alias = "GST_FORMAT_PERCENT_SCALE")]
pub const SCALE: u32 = ffi::GST_FORMAT_PERCENT_SCALE as u32;
pub const SCALE: Self = Self(ffi::GST_FORMAT_PERCENT_SCALE as u32);
}
impl_common_ops_for_newtype_uint!(Percent, u32);
impl_common_ops_for_newtype_uint!(
Percent,
u32,
one: ffi::GST_FORMAT_PERCENT_SCALE as u32 / 100,
);
impl_signed_div_mul!(Percent, u32);
impl FormattedValue for Option<Percent> {
@ -227,8 +240,7 @@ impl TryFrom<f32> for Percent {
impl std::fmt::Display for Percent {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
const ONE_PERCENT: f32 = Percent::SCALE as f32 / 100.0;
std::fmt::Display::fmt(&(self.0 as f32 / ONE_PERCENT), f)?;
std::fmt::Display::fmt(&(self.0 as f32 / (*Percent::ONE) as f32), f)?;
f.write_str(" %")
}
}

View file

@ -10,6 +10,10 @@ use crate::Format;
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default)]
pub struct Undefined(pub i64);
impl Undefined {
pub const ONE: Undefined = Undefined(1);
}
impl FormattedValue for Undefined {
type FullRange = Undefined;

View file

@ -173,7 +173,7 @@ USAGE: Choose one of the following options, then press enter:
Command::NextFrame => {
if let Some(video_sink) = pipeline.property::<Option<Element>>("video-sink") {
// Send the event
let step = Step::new(gst::format::Buffers(1), rate.abs(), true, false);
let step = Step::new(gst::format::Buffers::ONE, rate.abs(), true, false);
video_sink.send_event(step);
println!("Stepping one frame\r");
}