1use std::sync::Arc;
7
8use axum::{
9 Router,
10 extract::{Query, State},
11 response::Json,
12 routing::get,
13};
14use serde::{Deserialize, Serialize};
15use tokio::sync::Mutex;
16
17use openentropy_core::conditioning::ConditioningMode;
18use openentropy_core::pool::EntropyPool;
19
20struct AppState {
22 pool: Mutex<EntropyPool>,
23 allow_raw: bool,
24}
25
26#[derive(Deserialize)]
27struct RandomParams {
28 length: Option<usize>,
29 #[serde(rename = "type")]
30 data_type: Option<String>,
31 raw: Option<bool>,
33 conditioning: Option<String>,
35}
36
37#[derive(Serialize)]
38struct RandomResponse {
39 #[serde(rename = "type")]
40 data_type: String,
41 length: usize,
42 data: serde_json::Value,
43 success: bool,
44 conditioned: bool,
46}
47
48#[derive(Serialize)]
49struct HealthResponse {
50 status: String,
51 sources_healthy: usize,
52 sources_total: usize,
53 raw_bytes: u64,
54 output_bytes: u64,
55}
56
57#[derive(Serialize)]
58struct SourcesResponse {
59 sources: Vec<SourceEntry>,
60 total: usize,
61}
62
63#[derive(Serialize)]
64struct SourceEntry {
65 name: String,
66 healthy: bool,
67 bytes: u64,
68 entropy: f64,
69 time: f64,
70 failures: u64,
71}
72
73async fn handle_random(
74 State(state): State<Arc<AppState>>,
75 Query(params): Query<RandomParams>,
76) -> Json<RandomResponse> {
77 let length = params.length.unwrap_or(1024).clamp(1, 65536);
78 let data_type = params.data_type.unwrap_or_else(|| "hex16".to_string());
79
80 let mode = if let Some(ref c) = params.conditioning {
82 match c.as_str() {
83 "raw" if state.allow_raw => ConditioningMode::Raw,
84 "vonneumann" | "von_neumann" | "vn" => ConditioningMode::VonNeumann,
85 "raw" => ConditioningMode::Sha256, _ => ConditioningMode::Sha256,
87 }
88 } else if params.raw.unwrap_or(false) && state.allow_raw {
89 ConditioningMode::Raw
90 } else {
91 ConditioningMode::Sha256
92 };
93
94 let pool = state.pool.lock().await;
95 let raw = pool.get_bytes(length, mode);
96 let use_raw = mode == ConditioningMode::Raw;
97
98 let data = match data_type.as_str() {
99 "hex16" => {
100 let hex_pairs: Vec<String> = raw
101 .chunks(2)
102 .filter(|c| c.len() == 2)
103 .map(|c| format!("{:02x}{:02x}", c[0], c[1]))
104 .collect();
105 serde_json::Value::Array(
106 hex_pairs
107 .into_iter()
108 .map(serde_json::Value::String)
109 .collect(),
110 )
111 }
112 "uint8" => {
113 serde_json::Value::Array(raw.iter().map(|&b| serde_json::Value::from(b)).collect())
114 }
115 "uint16" => {
116 let vals: Vec<u16> = raw
117 .chunks(2)
118 .filter(|c| c.len() == 2)
119 .map(|c| u16::from_le_bytes([c[0], c[1]]))
120 .collect();
121 serde_json::Value::Array(vals.into_iter().map(serde_json::Value::from).collect())
122 }
123 _ => serde_json::Value::String(hex::encode(&raw)),
124 };
125
126 let len = match &data {
127 serde_json::Value::Array(a) => a.len(),
128 _ => length,
129 };
130
131 Json(RandomResponse {
132 data_type,
133 length: len,
134 data,
135 success: true,
136 conditioned: !use_raw,
137 })
138}
139
140async fn handle_health(State(state): State<Arc<AppState>>) -> Json<HealthResponse> {
141 let pool = state.pool.lock().await;
142 let report = pool.health_report();
143 Json(HealthResponse {
144 status: if report.healthy > 0 {
145 "healthy".to_string()
146 } else {
147 "degraded".to_string()
148 },
149 sources_healthy: report.healthy,
150 sources_total: report.total,
151 raw_bytes: report.raw_bytes,
152 output_bytes: report.output_bytes,
153 })
154}
155
156async fn handle_sources(State(state): State<Arc<AppState>>) -> Json<SourcesResponse> {
157 let pool = state.pool.lock().await;
158 let report = pool.health_report();
159 let sources: Vec<SourceEntry> = report
160 .sources
161 .iter()
162 .map(|s| SourceEntry {
163 name: s.name.clone(),
164 healthy: s.healthy,
165 bytes: s.bytes,
166 entropy: s.entropy,
167 time: s.time,
168 failures: s.failures,
169 })
170 .collect();
171 let total = sources.len();
172 Json(SourcesResponse { sources, total })
173}
174
175async fn handle_pool_status(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
176 let pool = state.pool.lock().await;
177 let report = pool.health_report();
178 Json(serde_json::json!({
179 "healthy": report.healthy,
180 "total": report.total,
181 "raw_bytes": report.raw_bytes,
182 "output_bytes": report.output_bytes,
183 "buffer_size": report.buffer_size,
184 "sources": report.sources.iter().map(|s| serde_json::json!({
185 "name": s.name,
186 "healthy": s.healthy,
187 "bytes": s.bytes,
188 "entropy": s.entropy,
189 "time": s.time,
190 "failures": s.failures,
191 })).collect::<Vec<_>>(),
192 }))
193}
194
195fn build_router(pool: EntropyPool, allow_raw: bool) -> Router {
197 let state = Arc::new(AppState {
198 pool: Mutex::new(pool),
199 allow_raw,
200 });
201
202 Router::new()
203 .route("/api/v1/random", get(handle_random))
204 .route("/health", get(handle_health))
205 .route("/sources", get(handle_sources))
206 .route("/pool/status", get(handle_pool_status))
207 .with_state(state)
208}
209
210pub async fn run_server(pool: EntropyPool, host: &str, port: u16, allow_raw: bool) {
212 let app = build_router(pool, allow_raw);
213 let addr = format!("{host}:{port}");
214 let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
215 axum::serve(listener, app).await.unwrap();
216}
217
218mod hex {
220 pub fn encode(data: &[u8]) -> String {
221 data.iter().map(|b| format!("{b:02x}")).collect()
222 }
223}