Files
ppc_amnet/amnet-server/src/webserver.rs
T

100 lines
3.3 KiB
Rust

use std::time::{Duration, Instant};
use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use axum::routing::{get, post};
use axum::{Json, Router};
use tower::ServiceBuilder;
use tower_http::{cors::{Any, CorsLayer}, trace::TraceLayer};
use crate::card::{CardPresentResult, CardPresenter};
use crate::config::Config;
use crate::metrics::SystemMetrics;
use crate::utils;
use crate::webapi::API_VERSION;
pub fn build_server() -> Router {
let services = ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::new().allow_origin(Any).allow_methods(Any));
Router::<()>::new()
.route("/amnet/info", get(server_state_handler))
.route("/amnet/signin", post(card_submission_handler))
.layer(services)
}
fn build_error_response(status: StatusCode, message: &str) -> Response {
let error = crate::webapi::ServerError {
error: message.to_string(),
};
(status, Json(error)).into_response()
}
async fn server_state_handler() -> Json<crate::webapi::ServerState> {
let config = Config::instance();
let metrics = SystemMetrics::instance();
let session_uptime = metrics.get_server_uptime().map(|d| d.as_secs()).unwrap_or(0);
let time_since_last_poll = metrics.get_last_poll().map(|d| d.as_secs());
let state = crate::webapi::ServerState {
api_version: API_VERSION,
game_id: config.game_id.clone(),
server_name: config.server_name.clone(),
session_uptime,
time_since_last_poll,
};
Json(state)
}
async fn card_submission_handler(
Json(payload): Json<crate::webapi::CardSubmissionRequest>,
) -> impl IntoResponse {
if payload.access_code.len() > 20 || payload.access_code.len() == 0 {
return build_error_response(StatusCode::BAD_REQUEST, "Access code is required");
}
let Some(access_code) = utils::parse_access_code(&payload.access_code) else {
return build_error_response(StatusCode::UNPROCESSABLE_ENTITY, "Invalid access code provided");
};
let card_idm_bytes = if let Some(idm_hex) = payload.card_idm_hex.as_ref() {
if idm_hex.len() != 16 {
return build_error_response(StatusCode::BAD_REQUEST, "IDm must be a 16-char hex string");
}
let Some(idm_bytes) = utils::parse_idm_hex(idm_hex) else {
return build_error_response(StatusCode::UNPROCESSABLE_ENTITY, "Invalid IDm provided");
};
Some(idm_bytes)
} else {
None
};
match CardPresenter::instance().present_card(access_code, card_idm_bytes, Duration::from_secs(5)) {
CardPresentResult::Accepted => StatusCode::ACCEPTED.into_response(),
CardPresentResult::SlotUnavailable(release_time) => {
let now = Instant::now();
let wait_secs = if release_time > now {
(release_time - now).as_secs()
} else {
0
};
let mut response = build_error_response(
StatusCode::TOO_MANY_REQUESTS,
"A card has already been submitted. Please wait before submitting another.",
);
let headers = response.headers_mut();
headers.insert(header::RETRY_AFTER, wait_secs.to_string().parse().unwrap());
response
}
}
}