use core::str::FromStr;
use minicbor::bytes::ByteArray;
use minicbor::{CborLen, Decode, Encode};
use ockam::Message;
use ockam_core::compat::rand;
use ockam_core::compat::rand::RngCore;
use ockam_core::compat::string::String;
use ockam_core::errcode::{Kind, Origin};
use ockam_core::{cbor_encode_preallocate, Result};
use ockam_core::{Decodable, Encodable, Encoded, Error};
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Formatter};
#[derive(Clone, Encode, Decode, CborLen, PartialEq, Eq, Copy, Message)]
#[rustfmt::skip]
#[cbor(map)]
pub struct OneTimeCode {
#[n(1)] code: ByteArray<32>,
}
impl Encodable for OneTimeCode {
fn encode(self) -> Result<Encoded> {
cbor_encode_preallocate(self)
}
}
impl Decodable for OneTimeCode {
fn decode(e: &[u8]) -> Result<Self> {
Ok(minicbor::decode(e)?)
}
}
impl Debug for OneTimeCode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("{ONE_TIME_CODE}")
}
}
impl OneTimeCode {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let mut code = [0; 32];
rand::thread_rng().fill_bytes(&mut code);
OneTimeCode::from(code)
}
pub fn code(&self) -> &[u8; 32] {
&self.code
}
}
impl From<[u8; 32]> for OneTimeCode {
fn from(code: [u8; 32]) -> Self {
OneTimeCode { code: code.into() }
}
}
impl From<&OneTimeCode> for String {
fn from(value: &OneTimeCode) -> Self {
hex::encode(value.code())
}
}
impl FromStr for OneTimeCode {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let bytes = hex::decode(s).map_err(|e| error(format!("{e}")))?;
let code: OneTimeCode = OneTimeCode::from(
<[u8; 32]>::try_from(bytes.as_slice()).map_err(|e| error(format!("{e}")))?,
);
Ok(code)
}
}
impl Serialize for OneTimeCode {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(String::from(self).as_str())
}
}
impl<'de> Deserialize<'de> for OneTimeCode {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
OneTimeCode::from_str(s.as_str()).map_err(serde::de::Error::custom)
}
}
fn error(message: String) -> Error {
Error::new(Origin::Identity, Kind::Invalid, message.as_str())
}
#[cfg(test)]
mod test {
use super::*;
use quickcheck::{Arbitrary, Gen};
use quickcheck_macros::quickcheck;
#[quickcheck]
fn test_from_to_string(one_time_code: OneTimeCode) -> bool {
OneTimeCode::from_str(&String::from(&one_time_code)).ok() == Some(one_time_code)
}
impl Arbitrary for OneTimeCode {
fn arbitrary(g: &mut Gen) -> Self {
OneTimeCode::from(Bytes32::arbitrary(g).bytes)
}
}
#[derive(Clone)]
struct Bytes32 {
bytes: [u8; 32],
}
impl Arbitrary for Bytes32 {
fn arbitrary(g: &mut Gen) -> Bytes32 {
let init: [u8; 32] = <[u8; 32]>::default();
Bytes32 {
bytes: init.map(|_| <u8>::arbitrary(g)),
}
}
fn shrink(&self) -> Box<dyn Iterator<Item = Bytes32>> {
Box::new(std::iter::empty())
}
}
}