diff --git a/core/Cargo.lock b/core/Cargo.lock index d9e378165..51a22fbe1 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -2262,7 +2262,7 @@ checksum = "6e39034cee21a2f5bbb66ba0e3689819c4bb5d00382a282006e802a7ffa6c41d" dependencies = [ "cfg-if", "libc", - "socket2", + "socket2 0.6.1", "windows-sys 0.60.2", ] @@ -2496,12 +2496,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "enum-as-inner" version = "0.6.1" @@ -3240,6 +3234,16 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashing-serializer" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c9b1a5e47c3bf40ae0f5705e84daa4cd6d8a74b2bdba43c06eb01dbc236f6e" +dependencies = [ + "digest 0.10.7", + "serde", +] + [[package]] name = "hashlink" version = "0.10.0" @@ -3300,25 +3304,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hickory-client" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c466cd63a4217d5b2b8e32f23f58312741ce96e3c84bf7438677d2baff0fc555" -dependencies = [ - "cfg-if", - "data-encoding", - "futures-channel", - "futures-util", - "hickory-proto", - "once_cell", - "radix_trie", - "rand 0.9.2", - "thiserror 2.0.17", - "tokio", - "tracing", -] - [[package]] name = "hickory-proto" version = "0.25.2" @@ -3345,6 +3330,28 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot 0.12.5", + "rand 0.9.2", + "resolv-conf", + "serde", + "smallvec", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "hickory-server" version = "0.25.2" @@ -3358,6 +3365,7 @@ dependencies = [ "enum-as-inner", "futures-util", "hickory-proto", + "hickory-resolver", "ipnet", "prefix-trie", "serde", @@ -3576,7 +3584,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.1", "system-configuration", "tokio", "tower-service", @@ -3919,6 +3927,18 @@ dependencies = [ "rustversion", ] +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -4297,7 +4317,7 @@ dependencies = [ "quoted_printable", "rustls 0.23.35", "rustls-platform-verifier", - "socket2", + "socket2 0.6.1", "tokio", "tokio-rustls 0.26.4", "url", @@ -4628,6 +4648,24 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moka" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot 0.12.5", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "uuid", +] + [[package]] name = "moxcms" version = "0.7.11" @@ -4671,15 +4709,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - [[package]] name = "nix" version = "0.23.2" @@ -5644,7 +5673,7 @@ dependencies = [ "shared_library", "shell-words", "winapi", - "winreg", + "winreg 0.10.1", ] [[package]] @@ -5965,7 +5994,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.35", - "socket2", + "socket2 0.6.1", "thiserror 2.0.17", "tokio", "tracing", @@ -6002,7 +6031,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2", + "socket2 0.6.1", "tracing", "windows-sys 0.60.2", ] @@ -6089,16 +6118,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rand" version = "0.7.3" @@ -6434,6 +6453,12 @@ dependencies = [ "url", ] +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + [[package]] name = "retry-error" version = "0.6.5" @@ -7380,6 +7405,16 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.1" @@ -7667,8 +7702,8 @@ dependencies = [ "form_urlencoded", "futures", "gpt", + "hashing-serializer", "hex", - "hickory-client", "hickory-server", "hmac 0.12.1", "http", @@ -7736,7 +7771,7 @@ dependencies = [ "sha-crypt", "sha2 0.10.9", "signal-hook", - "socket2", + "socket2 0.6.1", "socks5-impl", "sqlx", "sscanf", @@ -7979,6 +8014,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -8197,7 +8238,7 @@ dependencies = [ "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.1", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -8431,7 +8472,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "socket2", + "socket2 0.6.1", "sync_wrapper", "tokio", "tokio-stream", @@ -10229,6 +10270,12 @@ dependencies = [ "wasite", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -10743,6 +10790,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index e1689199c..335865c96 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -122,8 +122,7 @@ form_urlencoded = "1.2.1" futures = "0.3.28" gpt = "4.1.0" hex = "0.4.3" -hickory-client = "0.25.2" -hickory-server = "0.25.2" +hickory-server = { version = "0.25.2", features = ["resolver"] } hmac = "0.12.1" http = "1.0.0" http-body-util = "0.1" @@ -271,6 +270,7 @@ uuid = { version = "1.4.1", features = ["v4"] } visit-rs = "0.1.1" x25519-dalek = { version = "2.0.1", features = ["static_secrets"] } zbus = "5.1.1" +hashing-serializer = "0.1.1" [target.'cfg(target_os = "linux")'.dependencies] procfs = "0.18.0" diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index 7e0a09d1e..d2a3729d9 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -1,5 +1,5 @@ use std::borrow::Borrow; -use std::collections::{BTreeMap, VecDeque}; +use std::collections::BTreeMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::str::FromStr; use std::sync::{Arc, Weak}; @@ -7,27 +7,23 @@ use std::time::Duration; use clap::Parser; use color_eyre::eyre::eyre; -use futures::future::BoxFuture; -use futures::{FutureExt, StreamExt}; -use hickory_client::client::Client; -use hickory_client::proto::DnsHandle; -use hickory_client::proto::runtime::TokioRuntimeProvider; -use hickory_client::proto::tcp::TcpClientStream; -use hickory_client::proto::udp::UdpClientStream; -use hickory_client::proto::xfer::DnsRequestOptions; -use hickory_server::ServerFuture; -use hickory_server::authority::MessageResponseBuilder; +use futures::{FutureExt, StreamExt, TryStreamExt}; +use hickory_server::authority::{AuthorityObject, Catalog, MessageResponseBuilder}; use hickory_server::proto::op::{Header, ResponseCode}; -use hickory_server::proto::rr::{Name, Record, RecordType}; +use hickory_server::proto::rr::{LowerName, Name, Record, RecordType}; +use hickory_server::resolver::config::{ResolverConfig, ResolverOpts}; use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo}; +use hickory_server::store::forwarder::{ForwardAuthority, ForwardConfig}; +use hickory_server::{ServerFuture, resolver as hickory_resolver}; use imbl::OrdMap; use imbl_value::InternedString; -use patch_db::json_ptr::JsonPointer; +use itertools::Itertools; use rpc_toolkit::{ Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async, from_fn_blocking, }; use serde::{Deserialize, Serialize}; use tokio::net::{TcpListener, UdpSocket}; +use tokio::sync::RwLock; use tracing::instrument; use crate::context::{CliContext, RpcContext}; @@ -35,7 +31,6 @@ use crate::db::model::Database; use crate::db::model::public::NetworkInterfaceInfo; use crate::net::gateway::NetworkInterfaceWatcher; use crate::prelude::*; -use crate::util::actor::background::BackgroundJobQueue; use crate::util::future::NonDetachingJoinHandle; use crate::util::io::file_string_stream; use crate::util::serde::{HandlerExtSerde, display_serializable}; @@ -214,173 +209,6 @@ pub struct DnsController { dns_server: NonDetachingJoinHandle<()>, } -struct DnsClient { - client: Arc>>, - _thread: NonDetachingJoinHandle<()>, -} -impl DnsClient { - pub fn new(db: TypedPatchDb) -> Self { - let client = Arc::new(SyncRwLock::new(Vec::new())); - Self { - client: client.clone(), - _thread: tokio::spawn(async move { - let (bg, mut runner) = BackgroundJobQueue::new(); - runner - .run_while(async move { - let dhcp_ns_db = db.clone(); - bg.add_job(async move { - loop { - if let Err(e) = async { - let mut stream = - file_string_stream("/run/systemd/resolve/resolv.conf") - .filter_map(|a| futures::future::ready(a.transpose())) - .boxed(); - while let Some(conf) = stream.next().await { - let conf: String = conf?; - let mut nameservers = conf - .lines() - .map(|l| l.trim()) - .filter_map(|l| l.strip_prefix("nameserver ")) - .map(|n| { - n.parse::().or_else(|_| { - n.parse::().map(|a| (a, 53).into()) - }) - }) - .collect::, _>>()?; - if nameservers - .front() - .map_or(false, |addr| addr.ip().is_loopback()) - { - nameservers.pop_front(); - } - if nameservers.front().map_or(false, |addr| { - addr.ip() == IpAddr::from([1, 1, 1, 1]) - }) { - nameservers.pop_front(); - } - dhcp_ns_db - .mutate(|db| { - let dns = db - .as_public_mut() - .as_server_info_mut() - .as_network_mut() - .as_dns_mut(); - dns.as_dhcp_servers_mut().ser(&nameservers) - }) - .await - .result? - } - Ok::<_, Error>(()) - } - .await - { - tracing::error!("{e}"); - tracing::debug!("{e:?}"); - tokio::time::sleep(Duration::from_secs(1)).await; - } - } - }); - loop { - if let Err::<(), Error>(e) = async { - let mut dns_changed = db - .subscribe( - "/public/serverInfo/network/dns" - .parse::() - .with_kind(ErrorKind::Database)?, - ) - .await; - let mut prev_nameservers = VecDeque::new(); - let mut bg = BTreeMap::>::new(); - loop { - let dns = db - .peek() - .await - .into_public() - .into_server_info() - .into_network() - .into_dns(); - let nameservers = dns - .as_static_servers() - .transpose_ref() - .unwrap_or_else(|| dns.as_dhcp_servers()) - .de()?; - if nameservers != prev_nameservers { - let mut existing: BTreeMap<_, _> = - client.peek(|c| c.iter().cloned().collect()); - let mut new = Vec::with_capacity(nameservers.len()); - for addr in &nameservers { - if let Some(existing) = existing.remove(addr) { - new.push((*addr, existing)); - } else { - let client = if let Ok((client, bg_thread)) = - Client::connect( - UdpClientStream::builder( - *addr, - TokioRuntimeProvider::new(), - ) - .build(), - ) - .await - { - bg.insert(*addr, bg_thread.boxed()); - client - } else { - let (stream, sender) = TcpClientStream::new( - *addr, - None, - Some(Duration::from_secs(30)), - TokioRuntimeProvider::new(), - ); - let (client, bg_thread) = - Client::new(stream, sender, None) - .await - .with_kind(ErrorKind::Network)?; - bg.insert(*addr, bg_thread.fuse().boxed()); - client - }; - new.push((*addr, client)); - } - } - bg.retain(|n, _| nameservers.iter().any(|a| a == n)); - prev_nameservers = nameservers; - client.replace(new); - } - futures::future::select( - dns_changed.recv().boxed(), - futures::future::join( - futures::future::join_all(bg.values_mut()), - futures::future::pending::<()>(), - ), - ) - .await; - } - } - .await - { - tracing::error!("{e}"); - tracing::debug!("{e:?}"); - } - tokio::time::sleep(Duration::from_secs(1)).await; - } - }) - .await; - }) - .into(), - } - } - fn lookup( - &self, - query: hickory_client::proto::op::Query, - options: DnsRequestOptions, - ) -> Vec { - self.client.peek(|c| { - c.iter() - .map(|(_, c)| c.lookup(query.clone(), options.clone())) - .collect() - }) - } -} - lazy_static::lazy_static! { static ref LOCALHOST: Name = Name::from_ascii("localhost").unwrap(); static ref STARTOS: Name = Name::from_ascii("startos").unwrap(); @@ -388,11 +216,106 @@ lazy_static::lazy_static! { } struct Resolver { - client: DnsClient, - net_iface: Watch>, + catalog: Arc>, resolve: Arc>, + net_iface: Watch>, + _thread: NonDetachingJoinHandle<()>, } impl Resolver { + fn new( + db: TypedPatchDb, + net_iface: Watch>, + ) -> Self { + let catalog = Arc::new(RwLock::new(Catalog::new())); + Self { + catalog: catalog.clone(), + resolve: Arc::new(SyncRwLock::new(ResolveMap::default())), + net_iface, + _thread: tokio::spawn(async move { + let mut prev = crate::util::serde::hash_serializable::(&( + ResolverConfig::new(), + ResolverOpts::default(), + )) + .unwrap_or_default(); + loop { + if let Err(e) = async { + let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf") + .filter_map(|a| futures::future::ready(a.transpose())) + .boxed(); + while let Some(conf) = stream.try_next().await? { + let (config, mut opts) = + hickory_resolver::system_conf::parse_resolv_conf(conf) + .with_kind(ErrorKind::ParseSysInfo)?; + opts.timeout = Duration::from_secs(30); + let hash = crate::util::serde::hash_serializable::( + &(&config, &opts), + )?; + if hash != prev { + db.mutate(|db| { + db.as_public_mut() + .as_server_info_mut() + .as_network_mut() + .as_dns_mut() + .as_dhcp_servers_mut() + .ser( + &config + .name_servers() + .into_iter() + .map(|n| n.socket_addr) + .dedup() + .skip(2) + .collect(), + ) + }) + .await + .result?; + let auth: Vec> = vec![Arc::new( + ForwardAuthority::builder_tokio(ForwardConfig { + name_servers: from_value(Value::Array( + config + .name_servers() + .into_iter() + .skip(4) + .map(to_value) + .collect::>()?, + ))?, + options: Some(opts), + }) + .build() + .map_err(|e| Error::new(eyre!("{e}"), ErrorKind::Network))?, + )]; + { + let mut guard = tokio::time::timeout( + Duration::from_secs(10), + catalog.write(), + ) + .await + .map_err(|_| { + Error::new( + eyre!("timed out waiting to update dns catalog"), + ErrorKind::Timeout, + ) + })?; + guard.upsert(Name::root().into(), auth); + drop(guard); + } + } + prev = hash; + } + + Ok::<_, Error>(()) + } + .await + { + tracing::error!("{e}"); + tracing::debug!("{e:?}"); + tokio::time::sleep(Duration::from_secs(1)).await; + } + } + }) + .into(), + } + } fn resolve(&self, name: &Name, mut src: IpAddr) -> Option> { if name.zone_of(&*LOCALHOST) { return Some(vec![Ipv4Addr::LOCALHOST.into(), Ipv6Addr::LOCALHOST.into()]); @@ -495,6 +418,7 @@ impl RequestHandler for Resolver { ), ) .await + .map(Some) } RecordType::AAAA => { let mut header = Header::response_from_request(request.header()); @@ -521,6 +445,7 @@ impl RequestHandler for Resolver { ), ) .await + .map(Some) } _ => { let mut header = Header::response_from_request(request.header()); @@ -536,70 +461,27 @@ impl RequestHandler for Resolver { ), ) .await + .map(Some) } } } else { - let query = query.original().clone(); - let mut opt = DnsRequestOptions::default(); - opt.recursion_desired = request.recursion_desired(); - let mut streams = self.client.lookup(query, opt); - let mut err = None; - for stream in streams.iter_mut() { - match tokio::time::timeout(Duration::from_secs(5), stream.next()).await { - Ok(Some(Err(e))) => err = Some(e), - Ok(Some(Ok(msg))) => { - let mut header = msg.header().clone(); - header.set_id(request.id()); - header.set_checking_disabled(request.checking_disabled()); - header.set_recursion_available(true); - return response_handle - .send_response( - MessageResponseBuilder::from_message_request(&*request).build( - header, - msg.answers(), - msg.name_servers(), - &msg.soa().map(|s| s.to_owned().into_record_of_rdata()), - msg.additionals(), - ), - ) - .await; - } - _ => (), - } - } - if let Some(e) = err { - tracing::error!("{e}"); - tracing::debug!("{e:?}"); - } - let mut header = Header::response_from_request(request.header()); - header.set_recursion_available(true); - header.set_response_code(ResponseCode::ServFail); - response_handle - .send_response( - MessageResponseBuilder::from_message_request(&*request).build( - header, - [], - [], - [], - [], - ), - ) - .await + Ok(None) } } .await { - Ok(a) => a, + Ok(Some(a)) => return a, + Ok(None) => (), Err(e) => { - tracing::error!("{}", e); - tracing::debug!("{:?}", e); + tracing::error!("Error resolving internal DNS: {e}"); + tracing::debug!("{e:?}"); let mut header = Header::response_from_request(request.header()); header.set_recursion_available(true); header.set_response_code(ResponseCode::ServFail); - response_handle + return response_handle .send_response( MessageResponseBuilder::from_message_request(&*request).build( - header, + header.into(), [], [], [], @@ -607,9 +489,14 @@ impl RequestHandler for Resolver { ), ) .await - .unwrap_or(header.into()) + .unwrap_or_else(|_| header.into()); } } + self.catalog + .read() + .await + .handle_request(request, response_handle) + .await } } @@ -619,13 +506,9 @@ impl DnsController { db: TypedPatchDb, watcher: &NetworkInterfaceWatcher, ) -> Result { - let resolve = Arc::new(SyncRwLock::new(ResolveMap::default())); - - let mut server = ServerFuture::new(Resolver { - client: DnsClient::new(db), - net_iface: watcher.subscribe(), - resolve: resolve.clone(), - }); + let resolver = Resolver::new(db, watcher.subscribe()); + let resolve = Arc::downgrade(&resolver.resolve); + let mut server = ServerFuture::new(resolver); let dns_server = tokio::spawn( async move { @@ -653,7 +536,7 @@ impl DnsController { .into(); Ok(Self { - resolve: Arc::downgrade(&resolve), + resolve, dns_server, }) } diff --git a/core/startos/src/registry/package/add.rs b/core/startos/src/registry/package/add.rs index 421b5d73f..342929051 100644 --- a/core/startos/src/registry/package/add.rs +++ b/core/startos/src/registry/package/add.rs @@ -13,6 +13,7 @@ use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits}; +use crate::registry::asset::BufferedHttpSource; use crate::registry::context::RegistryContext; use crate::registry::package::index::PackageVersionInfo; use crate::s9pk::S9pk; @@ -131,18 +132,10 @@ pub async fn cli_add_package( sign_phase.complete(); verify_phase.start(); - let source = HttpSource::new(ctx.client.clone(), url.clone()).await?; - let len = source.size().await; + let source = BufferedHttpSource::new(ctx.client.clone(), url.clone(), verify_phase).await?; let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?; - if let Some(len) = len { - verify_phase.set_total(len); - } - verify_phase.set_units(Some(ProgressUnits::Bytes)); - let mut verify_writer = ProgressTrackerWriter::new(tokio::io::sink(), verify_phase); - src.serialize(&mut TrackingIO::new(0, &mut verify_writer), true) + src.serialize(&mut TrackingIO::new(0, &mut tokio::io::sink()), true) .await?; - let (_, mut verify_phase) = verify_writer.into_inner(); - verify_phase.complete(); index_phase.start(); ctx.call_remote::( diff --git a/core/startos/src/util/serde.rs b/core/startos/src/util/serde.rs index 286041822..3bbedb75b 100644 --- a/core/startos/src/util/serde.rs +++ b/core/startos/src/util/serde.rs @@ -7,6 +7,9 @@ use base64::Engine; use clap::builder::ValueParserFactory; use clap::{ArgMatches, CommandFactory, FromArgMatches}; use color_eyre::eyre::eyre; +use digest::Update; +use digest::generic_array::GenericArray; +use hashing_serializer::HashingSerializer; use imbl_value::imbl::OrdMap; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; @@ -16,6 +19,7 @@ use rpc_toolkit::{ use serde::de::DeserializeOwned; use serde::ser::{SerializeMap, SerializeSeq}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sha2::Digest; use ts_rs::TS; use super::IntoDoubleEndedIterator; @@ -1444,3 +1448,15 @@ pub fn is_partial_of(partial: &Value, full: &Value) -> bool { (_, _) => partial == full, } } + +pub fn hash_serializable( + value: &T, +) -> Result, Error> { + let mut digest = D::new(); + value + .serialize(HashingSerializer { + digest: &mut digest, + }) + .with_kind(ErrorKind::Serialization)?; + Ok(digest.finalize()) +}