#![warn(missing_docs)]
#![deny(rustdoc::missing_crate_level_docs)]
#![doc(test(attr(deny(warnings))))]
#![deny(clippy::all)]
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::*;
fn field_names(data: Data) -> Vec<String> {
match data {
Data::Struct(DataStruct { fields, .. }) => match fields {
Fields::Named(FieldsNamed { named, .. }) => named
.iter()
.map(|Field { ident, .. }| {
ident
.as_ref()
.expect("named fields have idents")
.to_string()
})
.collect(),
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => unnamed
.iter()
.enumerate()
.map(|(i, _)| i.to_string())
.collect(),
Fields::Unit => Vec::new(),
},
_ => panic!("this macro does not support enums or unions for constant-time operations"),
}
}
#[proc_macro_derive(ConstantTimeEq)]
pub fn derive_eq(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
let eq_block = {
let field_names = field_names(data);
let mut eq_stmts: Vec<Stmt> = vec![parse_str("let mut ret: u8 = 1;").unwrap()];
eq_stmts.extend(field_names.into_iter().map(|name| {
parse_str(&format!(
"ret &= self.{}.ct_eq(&other.{}).unwrap_u8();",
name, name
))
.unwrap()
}));
eq_stmts.push(parse_str("return ret.into();").unwrap());
Block {
brace_token: token::Brace {
span: Span::mixed_site(),
},
stmts: eq_stmts,
}
};
let output = if cfg!(feature = "with-ng") {
quote! {
impl ::subtle_ng::ConstantTimeEq for #ident {
#[inline]
fn ct_eq(&self, other: &Self) -> ::subtle_ng::Choice {
#eq_block
}
}
}
} else {
quote! {
impl ::subtle::ConstantTimeEq for #ident {
#[inline]
fn ct_eq(&self, other: &Self) -> ::subtle_ng::Choice {
#eq_block
}
}
}
};
output.into()
}
#[proc_macro_derive(ConstantTimeGreater)]
pub fn derive_gt(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
let gt_block = {
let field_names = field_names(data);
let mut gt_stmts: Vec<Stmt> = vec![
parse_str("let mut still_at_least_eq: u8 = 1;").unwrap(),
parse_str("let mut was_gt: u8 = 0;").unwrap(),
];
for name in field_names.into_iter() {
gt_stmts.push(
parse_str(&format!(
"was_gt |= still_at_least_eq & self.{}.ct_gt(&other.{}).unwrap_u8();",
name, name,
))
.unwrap(),
);
gt_stmts.push(
parse_str(&format!(
"still_at_least_eq &= self.{}.ct_eq(&other.{}).unwrap_u8();",
name, name,
))
.unwrap(),
);
}
gt_stmts.push(parse_str("return was_gt.into();").unwrap());
Block {
brace_token: token::Brace {
span: Span::mixed_site(),
},
stmts: gt_stmts,
}
};
let output = if cfg!(feature = "with-ng") {
quote! {
impl ::subtle_ng::ConstantTimeGreater for #ident {
#[inline]
fn ct_gt(&self, other: &Self) -> ::subtle_ng::Choice {
use ::subtle_ng::{ConstantTimeEq, ConstantTimeGreater};
#gt_block
}
}
}
} else {
quote! {
impl ::subtle::ConstantTimeGreater for #ident {
#[inline]
fn ct_gt(&self, other: &Self) -> ::subtle_ng::Choice {
use ::subtle::{ConstantTimeEq, ConstantTimeGreater};
#gt_block
}
}
}
};
output.into()
}