1use std::sync::atomic::{AtomicU64, Ordering};
4
5#[derive(Clone, Debug, Eq, PartialEq)]
7#[cfg_attr(feature = "native-bindings", derive(uniffi::Record))]
8pub struct BuildFingerprint {
9 pub version: String,
11 pub revision: String,
13 pub protocol_profile: u32,
15 pub target_arch: String,
17 pub target_os: String,
19}
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23#[cfg_attr(feature = "native-bindings", derive(uniffi::Record))]
24pub struct DiagnosticsSnapshot {
25 pub nip44_encryptions: u64,
27 pub nip44_decryptions: u64,
29 pub verified_events: u64,
31 pub signed_events: u64,
33 pub wrapped_envelopes: u64,
35 pub unwrapped_envelopes: u64,
37 pub rejected_operations: u64,
39 pub input_bytes: u64,
41 pub output_bytes: u64,
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub(crate) enum Operation {
47 Nip44Encrypt,
48 Nip44Decrypt,
49 VerifyEvent,
50 SignEvent,
51 WrapEnvelope,
52 UnwrapEnvelope,
53}
54
55static NIP44_ENCRYPTIONS: AtomicU64 = AtomicU64::new(0);
56static NIP44_DECRYPTIONS: AtomicU64 = AtomicU64::new(0);
57static VERIFIED_EVENTS: AtomicU64 = AtomicU64::new(0);
58static SIGNED_EVENTS: AtomicU64 = AtomicU64::new(0);
59static WRAPPED_ENVELOPES: AtomicU64 = AtomicU64::new(0);
60static UNWRAPPED_ENVELOPES: AtomicU64 = AtomicU64::new(0);
61static REJECTED_OPERATIONS: AtomicU64 = AtomicU64::new(0);
62static INPUT_BYTES: AtomicU64 = AtomicU64::new(0);
63static OUTPUT_BYTES: AtomicU64 = AtomicU64::new(0);
64
65#[cfg_attr(feature = "native-bindings", uniffi::export)]
67#[must_use]
68pub fn build_fingerprint() -> BuildFingerprint {
69 BuildFingerprint {
70 version: env!("CARGO_PKG_VERSION").to_owned(),
71 revision: option_env!("SOFTCHAT_BUILD_REVISION")
72 .unwrap_or("unversioned")
73 .to_owned(),
74 protocol_profile: 1,
75 target_arch: std::env::consts::ARCH.to_owned(),
76 target_os: std::env::consts::OS.to_owned(),
77 }
78}
79
80#[cfg_attr(feature = "native-bindings", uniffi::export)]
82#[must_use]
83pub fn diagnostics_snapshot() -> DiagnosticsSnapshot {
84 DiagnosticsSnapshot {
85 nip44_encryptions: NIP44_ENCRYPTIONS.load(Ordering::Relaxed),
86 nip44_decryptions: NIP44_DECRYPTIONS.load(Ordering::Relaxed),
87 verified_events: VERIFIED_EVENTS.load(Ordering::Relaxed),
88 signed_events: SIGNED_EVENTS.load(Ordering::Relaxed),
89 wrapped_envelopes: WRAPPED_ENVELOPES.load(Ordering::Relaxed),
90 unwrapped_envelopes: UNWRAPPED_ENVELOPES.load(Ordering::Relaxed),
91 rejected_operations: REJECTED_OPERATIONS.load(Ordering::Relaxed),
92 input_bytes: INPUT_BYTES.load(Ordering::Relaxed),
93 output_bytes: OUTPUT_BYTES.load(Ordering::Relaxed),
94 }
95}
96
97pub(crate) fn record_success(operation: Operation, input_bytes: usize, output_bytes: usize) {
98 counter(operation).fetch_add(1, Ordering::Relaxed);
99 INPUT_BYTES.fetch_add(saturating_u64(input_bytes), Ordering::Relaxed);
100 OUTPUT_BYTES.fetch_add(saturating_u64(output_bytes), Ordering::Relaxed);
101}
102
103pub(crate) fn record_rejection(input_bytes: usize) {
104 REJECTED_OPERATIONS.fetch_add(1, Ordering::Relaxed);
105 INPUT_BYTES.fetch_add(saturating_u64(input_bytes), Ordering::Relaxed);
106}
107
108fn counter(operation: Operation) -> &'static AtomicU64 {
109 match operation {
110 Operation::Nip44Encrypt => &NIP44_ENCRYPTIONS,
111 Operation::Nip44Decrypt => &NIP44_DECRYPTIONS,
112 Operation::VerifyEvent => &VERIFIED_EVENTS,
113 Operation::SignEvent => &SIGNED_EVENTS,
114 Operation::WrapEnvelope => &WRAPPED_ENVELOPES,
115 Operation::UnwrapEnvelope => &UNWRAPPED_ENVELOPES,
116 }
117}
118
119fn saturating_u64(value: usize) -> u64 {
120 u64::try_from(value).unwrap_or(u64::MAX)
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn fingerprint_and_snapshots_are_redacted() {
129 let fingerprint = build_fingerprint();
130 assert_eq!(fingerprint.protocol_profile, 1);
131 assert!(!fingerprint.version.is_empty());
132
133 record_success(Operation::Nip44Encrypt, 12, 34);
134 record_rejection(56);
135 let snapshot = diagnostics_snapshot();
136 assert!(snapshot.nip44_encryptions >= 1);
137 assert!(snapshot.rejected_operations >= 1);
138 let debug = format!("{snapshot:?}");
139 assert!(!debug.contains("plaintext"));
140 assert!(!debug.contains("ciphertext"));
141 }
142}