1use crate::app::AppState;
7use anyhow::Result;
8use axum::{
9 Router,
10 routing::{get, put},
11};
12use axum_server::tls_rustls::RustlsConfig;
13use bytes::Bytes;
14use std::{net::SocketAddr, path::Path};
15use tokio::sync::broadcast;
16use tower_http::{
17 services::{ServeDir, ServeFile},
18 trace::TraceLayer,
19};
20
21mod ad9361;
22mod api;
23mod ddc;
24mod geolocation;
25mod iqengine;
26mod recording;
27mod spectrometer;
28mod time;
29mod version;
30mod websocket;
31mod zeros;
32
33pub use recording::{RecorderFinishWaiter, RecorderState};
34
35#[derive(Debug)]
41pub struct Server {
42 http_server: axum_server::Server,
43 https_server: Option<axum_server::Server<axum_server::tls_rustls::RustlsAcceptor>>,
44 app: Router,
45}
46
47impl Server {
48 pub async fn new(
63 http_address: SocketAddr,
64 https_address: SocketAddr,
65 ssl_cert: Option<impl AsRef<Path>>,
66 ssl_key: Option<impl AsRef<Path>>,
67 ca_cert: Option<impl AsRef<Path>>,
68 state: AppState,
69 waterfall_sender: broadcast::Sender<Bytes>,
70 ) -> Result<Server> {
71 let mut app = Router::new()
72 .route("/api", get(api::get_api))
74 .route(
75 "/api/ad9361",
76 get(ad9361::get_ad9361)
77 .put(ad9361::put_ad9361)
78 .patch(ad9361::patch_ad9361),
79 )
80 .route(
81 "/api/spectrometer",
82 get(spectrometer::get_spectrometer).patch(spectrometer::patch_spectrometer),
83 )
84 .route(
85 "/api/ddc/config",
86 get(ddc::get_ddc_config)
87 .put(ddc::put_ddc_config)
88 .patch(ddc::patch_ddc_config),
89 )
90 .route("/api/ddc/design", put(ddc::put_ddc_design))
91 .route(
92 "/api/geolocation",
93 get(geolocation::get_geolocation).put(geolocation::put_geolocation),
94 )
95 .route(
96 "/api/recorder",
97 get(recording::get_recorder).patch(recording::patch_recorder),
98 )
99 .route(
100 "/api/recording/metadata",
101 get(recording::get_recording_metadata)
102 .put(recording::put_recording_metadata)
103 .patch(recording::patch_recording_metadata),
104 )
105 .route("/api/versions", get(version::get_versions))
106 .route("/recording", get(recording::get_recording))
107 .route("/version", get(version::get_version))
108 .route(
110 "/api/datasources/maiasdr/maiasdr/recording/meta",
111 get(recording::iqengine::meta),
112 )
113 .route(
114 "/api/datasources/maiasdr/maiasdr/recording/iq-data",
115 get(recording::iqengine::iq_data),
116 )
117 .route(
118 "/api/datasources/maiasdr/maiasdr/recording/minimap-data",
119 get(recording::iqengine::minimap_data),
120 )
121 .with_state(state)
122 .route(
124 "/api/time",
125 get(time::get_time)
126 .put(time::put_time)
127 .patch(time::patch_time),
128 )
129 .route(
130 "/waterfall",
131 get(websocket::handler).with_state(waterfall_sender),
132 )
133 .route("/zeros", get(zeros::get_zeros)); if let Some(ca_cert) = &ca_cert {
135 app = app.route_service("/ca.crt", ServeFile::new(ca_cert));
137 }
138 let app = app
139 .route_service(
141 "/view/api/maiasdr/maiasdr/recording",
142 ServeFile::new("iqengine/index.html"),
143 )
144 .route("/assets/{filename}", get(iqengine::serve_assets))
145 .fallback_service(ServeDir::new("."))
146 .layer(TraceLayer::new_for_http());
147 tracing::info!(%http_address, "starting HTTP server");
148 let http_server = axum_server::bind(http_address);
149 tracing::info!(%https_address, "starting HTTPS server");
150 let https_server = match (&ssl_cert, &ssl_key) {
151 (Some(ssl_cert), Some(ssl_key)) => Some(axum_server::bind_rustls(
152 https_address,
153 RustlsConfig::from_pem_file(ssl_cert, ssl_key).await?,
154 )),
155 _ => None,
156 };
157 Ok(Server {
158 http_server,
159 https_server,
160 app,
161 })
162 }
163
164 pub async fn run(self) -> Result<()> {
168 let http_server = self.http_server.serve(self.app.clone().into_make_service());
169 if let Some(https_server) = self.https_server {
170 let https_server = https_server.serve(self.app.into_make_service());
171 Ok(tokio::select! {
172 ret = http_server => ret,
173 ret = https_server => ret,
174 }?)
175 } else {
176 Ok(http_server.await?)
177 }
178 }
179}
180
181mod json_error {
182 use anyhow::Error;
183 use axum::{
184 http::StatusCode,
185 response::{IntoResponse, Response},
186 };
187 use serde::Serialize;
188
189 #[derive(Serialize, Debug, Clone, Eq, PartialEq)]
190 pub struct JsonError(maia_json::Error);
191
192 impl JsonError {
193 pub fn from_error<E: Into<Error>>(
194 error: E,
195 status_code: StatusCode,
196 suggested_action: maia_json::ErrorAction,
197 ) -> JsonError {
198 let error: Error = error.into();
199 JsonError(maia_json::Error {
200 http_status_code: status_code.as_u16(),
201 error_description: format!("{error:#}"),
202 suggested_action,
203 })
204 }
205
206 pub fn client_error_alert<E: Into<Error>>(error: E) -> JsonError {
207 JsonError::from_error(
208 error,
209 StatusCode::BAD_REQUEST,
210 maia_json::ErrorAction::Alert,
211 )
212 }
213
214 pub fn client_error<E: Into<Error>>(error: E) -> JsonError {
215 JsonError::from_error(error, StatusCode::BAD_REQUEST, maia_json::ErrorAction::Log)
216 }
217
218 pub fn server_error<E: Into<Error>>(error: E) -> JsonError {
219 JsonError::from_error(
220 error,
221 StatusCode::INTERNAL_SERVER_ERROR,
222 maia_json::ErrorAction::Log,
223 )
224 }
225 }
226
227 impl IntoResponse for JsonError {
228 fn into_response(self) -> Response {
229 let status_code = StatusCode::from_u16(self.0.http_status_code).unwrap();
230 let json = serde_json::to_string(&self.0).unwrap();
231 (status_code, json).into_response()
232 }
233 }
234}