2222//!
2323//! // The same value, as a binding would hand it over.
2424//! let from_host = ValueFrame::from_bytes(frame.as_protected_ref())?;
25- //! assert_eq!(from_host.risky_ref() , frame.risky_ref() );
25+ //! assert_eq!(from_host, frame);
2626//! # Ok(())
2727//! # }
2828//! ```
@@ -39,7 +39,8 @@ use vitaminc_aead::plaintext::{
3939} ;
4040use vitaminc_aead:: { DecodeError , Decrypt , Encrypt } ;
4141use vitaminc_protected:: {
42- AsProtectedRef , Buffer , Controlled , OpaqueDebug , Protected , ProtectedRef ,
42+ AsProtectedRef , Buffer , Controlled , Equatable , OpaqueDebug , Protected , ProtectedRef ,
43+ TimingSafeEq ,
4344} ;
4445
4546use super :: ValueKind ;
@@ -61,6 +62,9 @@ use crate::{Label, LabelError};
6162/// `Debug` ([`OpaqueDebug`]) names the type and masks the frame, which is
6263/// plaintext.
6364///
65+ /// Two frames compare with `==` in constant time. See the [`PartialEq`
66+ /// impl](#impl-PartialEq-for-ValueFrame) for what equal means.
67+ ///
6468/// # The value model
6569///
6670/// A frame can carry any of vitaminc's scalars. The engine admits a subset,
@@ -75,7 +79,7 @@ use crate::{Label, LabelError};
7579/// sink adapter that holds these rules, so a frame from a host and a frame
7680/// opened from a ciphertext are read the same way.
7781#[ derive( OpaqueDebug ) ]
78- pub struct ValueFrame ( Protected < Vec < u8 > > ) ;
82+ pub struct ValueFrame ( Equatable < Protected < Vec < u8 > > > ) ;
7983
8084/// Why [`ValueFrame::from_bytes`] or [`ValueFrame::check`] refused its bytes.
8185#[ derive( Debug , thiserror:: Error ) ]
@@ -170,12 +174,12 @@ impl ValueFrame {
170174 /// move into the frame's [`Protected`] without being copied. While the
171175 /// writer wrote, the `Buffer` wiped each allocation it outgrew.
172176 fn from_buffer ( buffer : Buffer ) -> Self {
173- Self ( buffer . into ( ) )
177+ Self ( Equatable :: from ( Protected :: from ( buffer ) ) )
174178 }
175179
176180 /// The null, as a frame: what an absent value opens to.
177181 pub ( super ) fn null ( ) -> Self {
178- Self ( Protected :: new ( vec ! [ vitaminc_aead:: tags:: NULL ] ) )
182+ Self ( Equatable :: new ( vec ! [ vitaminc_aead:: tags:: NULL ] ) )
179183 }
180184
181185 /// A typed null of `ty`, as a frame: the leaf alone. Walked into the
@@ -184,7 +188,7 @@ impl ValueFrame {
184188 /// typed null of its own types only ([`model_null`]), so `ty` is one of
185189 /// those a [`ValueKind`] names.
186190 pub ( super ) fn typed_null ( ty : FixedType ) -> Self {
187- let mut leaf = Protected :: new ( Vec :: new ( ) ) ;
191+ let mut leaf = Equatable :: new ( Vec :: new ( ) ) ;
188192 vitaminc_aead:: leaf:: write_typed_null ( ty, leaf. risky_inner_mut ( ) ) ;
189193 Self ( leaf)
190194 }
@@ -209,7 +213,7 @@ impl ValueFrame {
209213
210214 /// The frame bytes, still protected.
211215 pub fn into_inner ( self ) -> Protected < Vec < u8 > > {
212- self . 0
216+ Protected :: new ( self . 0 . risky_unwrap ( ) )
213217 }
214218
215219 /// The frame, for walking.
@@ -362,7 +366,35 @@ impl Decrypt for ValueFrame {
362366/// original does.
363367impl Clone for ValueFrame {
364368 fn clone ( & self ) -> Self {
365- Self ( Protected :: new ( self . 0 . risky_ref ( ) . to_vec ( ) ) )
369+ Self ( Equatable :: new ( self . 0 . risky_ref ( ) . to_vec ( ) ) )
370+ }
371+ }
372+
373+ /// Two frames are equal when their bytes are. The bytes are compared in
374+ /// constant time, so the comparison does not reveal where two frames first
375+ /// differ. Frames of different lengths are unequal at once, which reveals
376+ /// only their lengths. A ciphertext's length already shows those.
377+ ///
378+ /// This compares frames, not values. A frame records each scalar's width and
379+ /// type, so `34u32` and `34u64` are not equal, and neither are `0.0` and
380+ /// `-0.0`, or a typed null and a plain one. A map is equal to another only
381+ /// when its keys are written in the same order.
382+ ///
383+ /// ```
384+ /// use stack_encrypt::dynamic::ValueFrame;
385+ ///
386+ /// assert_eq!(ValueFrame::of(34u64)?, ValueFrame::of(34u64)?);
387+ /// assert_ne!(ValueFrame::of(34u64)?, ValueFrame::of(34u32)?);
388+ /// # Ok::<(), Box<dyn std::error::Error>>(())
389+ /// ```
390+ impl PartialEq for ValueFrame {
391+ fn eq ( & self , other : & Self ) -> bool {
392+ // The frame is held in an `Equatable`, which would compare it itself,
393+ // but vitaminc-protected 0.9.1 has no `ConstantTimeEq` for `Vec<u8>`.
394+ // Until it does, compare the bytes through `TimingSafeEq`, which uses
395+ // `subtle`, and derive `PartialEq` once the impl is released.
396+ let ( ours, theirs) = ( self . 0 . risky_ref ( ) , other. 0 . risky_ref ( ) ) ;
397+ TimingSafeEq :: ts_eq ( ours. as_slice ( ) , theirs. as_slice ( ) ) . into ( )
366398 }
367399}
368400
@@ -1295,6 +1327,46 @@ mod tests {
12951327 use super :: * ;
12961328 use crate :: dynamic:: test_frame:: Tree ;
12971329
1330+ mod equality {
1331+ use super :: * ;
1332+
1333+ #[ test]
1334+ fn frames_with_the_same_bytes_are_equal ( ) {
1335+ let frame = ValueFrame :: of ( "alice" ) . unwrap ( ) ;
1336+ assert_eq ! ( frame, frame. clone( ) ) ;
1337+ assert_eq ! (
1338+ frame,
1339+ ValueFrame :: from_bytes( frame. as_protected_ref( ) ) . unwrap( )
1340+ ) ;
1341+ }
1342+
1343+ #[ test]
1344+ fn frames_with_different_bytes_of_the_same_length_are_not_equal ( ) {
1345+ let ( a, b) = (
1346+ ValueFrame :: of ( "alice" ) . unwrap ( ) ,
1347+ ValueFrame :: of ( "alicf" ) . unwrap ( ) ,
1348+ ) ;
1349+ assert_eq ! ( a. risky_ref( ) . len( ) , b. risky_ref( ) . len( ) ) ;
1350+ assert_ne ! ( a, b) ;
1351+ }
1352+
1353+ #[ test]
1354+ fn frames_of_different_lengths_are_not_equal ( ) {
1355+ assert_ne ! (
1356+ ValueFrame :: of( "alice" ) . unwrap( ) ,
1357+ ValueFrame :: of( "alice!" ) . unwrap( )
1358+ ) ;
1359+ }
1360+
1361+ #[ test]
1362+ fn the_same_number_at_two_widths_is_not_equal ( ) {
1363+ assert_ne ! (
1364+ ValueFrame :: of( 34u32 ) . unwrap( ) ,
1365+ ValueFrame :: of( 34u64 ) . unwrap( )
1366+ ) ;
1367+ }
1368+ }
1369+
12981370 fn s ( value : & str ) -> Tree {
12991371 Tree :: String ( value. into ( ) )
13001372 }
0 commit comments