use std::sync::{Mutex, PoisonError};
use rusty_esp_core::hal::{Kv, Rng};
use rusty_esp_mid_core::did::{Did, MAX_DID_LEN};
use rusty_esp_mid_core::key::DeviceKey;
use crate::board;
use crate::error::{Error, Result, record};
pub const DEVICE_ID: &str = "janus";
pub const PAGE_TOKEN_KEY: &str = "page.token";
const PAGE_TOKEN_BYTES: usize = 16;
fn load_or_mint_token(kv: &mut DynKv, rng: &mut DynRng) -> Result<String> {
let io = |e: rusty_esp_core::Error| Error::Io(format!("page token: {e:?}"));
let mut raw = [0u8; PAGE_TOKEN_BYTES];
let have = match kv.get(PAGE_TOKEN_KEY, &mut raw) {
Ok(n) => n,
Err(rusty_esp_core::Error::BufferTooSmall { .. }) => None,
Err(e) => return Err(io(e)),
};
if have != Some(PAGE_TOKEN_BYTES) {
rng.fill(&mut raw).map_err(io)?;
kv.put(PAGE_TOKEN_KEY, &raw).map_err(io)?;
}
let mut out = [0u8; 2 * PAGE_TOKEN_BYTES];
let n = bs58::encode(&raw)
.onto(&mut out[..])
.map_err(|_| Error::Io("page token: base58".into()))?;
String::from_utf8(out[..n].to_vec()).map_err(|_| Error::Io("page token: base58".into()))
}
pub(crate) struct DynKv(pub(crate) Box<dyn Kv + Send>);
impl Kv for DynKv {
fn get(&self, key: &str, out: &mut [u8]) -> rusty_esp_core::error::Result<Option<usize>> {
self.0.get(key, out)
}
fn put(&mut self, key: &str, value: &[u8]) -> rusty_esp_core::error::Result<()> {
self.0.put(key, value)
}
fn remove(&mut self, key: &str) -> rusty_esp_core::error::Result<bool> {
self.0.remove(key)
}
}
pub(crate) struct DynRng(pub(crate) Box<dyn Rng + Send>);
impl Rng for DynRng {
fn fill(&mut self, buf: &mut [u8]) -> rusty_esp_core::error::Result<()> {
self.0.fill(buf)
}
}
struct State {
did: String,
maker: Option<String>,
page_token: String,
#[cfg_attr(not(feature = "mesh"), allow(dead_code))]
store: Option<(Box<dyn Kv + Send>, Box<dyn Rng + Send>)>,
}
static IDENTITY: Mutex<Option<State>> = Mutex::new(None);
fn try_begin(maker: Option<&str>) -> Result<String> {
if let Some(m) = maker {
Did::parse(m).map_err(|_| Error::Io(format!("maker {m:?} is not a did:mata")))?;
}
let (kv, rng) = board::with(|b| {
let kv = b
.take_kv()
.ok_or(Error::Missing("store for the device key"))?;
let rng = b.take_rng().ok_or(Error::Missing("entropy source"))?;
Ok((kv, rng))
})?;
let mut kv = DynKv(kv);
let mut rng = DynRng(rng);
let key = DeviceKey::load_or_generate(&mut kv, &mut rng, DEVICE_ID)
.map_err(|e| Error::Io(format!("device key: {e:?}")))?;
let mut buf = [0u8; MAX_DID_LEN];
let did = key
.did()
.write(&mut buf)
.map_err(|e| Error::Io(format!("did: {e:?}")))?
.to_owned();
let page_token = load_or_mint_token(&mut kv, &mut rng)?;
*IDENTITY.lock().unwrap_or_else(PoisonError::into_inner) = Some(State {
did: did.clone(),
maker: maker.map(str::to_owned),
page_token,
store: Some((kv.0, rng.0)),
});
Ok(did)
}
pub fn begin(maker: Option<&str>) -> bool {
match try_begin(maker) {
Ok(_) => true,
Err(e) => {
record(e);
false
}
}
}
#[must_use]
pub fn did() -> Option<String> {
IDENTITY
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.map(|s| s.did.clone())
}
#[must_use]
pub fn page_token() -> Option<String> {
IDENTITY
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.map(|s| s.page_token.clone())
}
#[must_use]
pub fn maker() -> Option<String> {
IDENTITY
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.and_then(|s| s.maker.clone())
}
#[must_use]
pub fn begun() -> bool {
did().is_some()
}
#[cfg_attr(not(feature = "mesh"), allow(dead_code))]
pub(crate) fn take_store() -> Option<(Box<dyn Kv + Send>, Box<dyn Rng + Send>)> {
IDENTITY
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_mut()
.and_then(|s| s.store.take())
}
pub fn end() {
*IDENTITY.lock().unwrap_or_else(PoisonError::into_inner) = None;
}