use super::{
HEALTH_PATH, HealthWithLinks, METRICS_PATH, OperatorCredentialReissueOutcome,
OperatorCredentialReissueRefusal, OperatorCredentialReissueRequest, QUIESCE_PATH, READY_PATH,
RECLAIM_PATH, REISSUE_PATH, ReadyCondition, ReadyWithStream, ServedState, ServerError,
StatusCode, UNLOADABLE_PATH, empty_response, health_check, json_response, metrics_route,
parse_request_line, readiness_check, response,
};
pub(super) fn response_for_request(
request: &[u8],
served: &ServedState,
) -> Result<Vec<u8>, ServerError> {
if let Some(answer) = super::admin::route(request, served)? {
return Ok(answer);
}
let Ok(request) = std::str::from_utf8(request) else {
return Ok(empty_response(StatusCode::BadRequest));
};
let Some((method, path)) = parse_request_line(request) else {
return Ok(empty_response(StatusCode::BadRequest));
};
match (method, path) {
("GET", HEALTH_PATH) => {
let mut status = health_check();
let lys_root = served
.lys_root
.get()
.map(crate::auth_lys::root_presence_start::RootSlot::health);
status.ephemeral_sweep = served.ephemeral_sweep.get().copied().map(Into::into);
json_response(
StatusCode::Ok,
&HealthWithLinks {
started_at: served.started_at,
grant_stream: served.grant_stream.get().map(|state| state.health()),
lys_root: lys_root.map(|(answer, _)| answer),
lys_root_cause: lys_root.and_then(|(_, cause)| cause),
health: status,
links: served.link_bridges.snapshot(),
listen_addresses: served.listen_addresses.get().cloned().unwrap_or_default(),
},
)
}
("GET", READY_PATH) => {
let status = readiness_check(&served.readiness.snapshot());
let grant_stream = served.grant_stream.get().map(|state| state.health());
let stream_ready = grant_stream
.as_ref()
.is_none_or(crate::auth_membership::grant_stream::StreamHealth::is_ready);
let mut unmet_conditions: Vec<_> = status
.unmet_conditions
.into_iter()
.map(ReadyCondition::Existing)
.collect();
if !stream_ready {
unmet_conditions.push(ReadyCondition::Stream("grant_stream_ready"));
}
let ready = status.ready && stream_ready;
let status_code = if ready {
StatusCode::Ok
} else {
StatusCode::ServiceUnavailable
};
json_response(
status_code,
&ReadyWithStream {
ready,
unmet_conditions,
grant_stream,
},
)
}
("GET", METRICS_PATH) => Ok(response(
StatusCode::Ok,
Some(metrics_route::CONTENT_TYPE),
metrics_route::render_body().as_bytes(),
)),
("GET", UNLOADABLE_PATH) => json_response(StatusCode::Ok, &served.unloadable.status()),
("GET", RECLAIM_PATH) => served.reclaim.status().map_or_else(
|| Ok(empty_response(StatusCode::ServiceUnavailable)),
|status| json_response(StatusCode::Ok, &status),
),
("POST", QUIESCE_PATH) => served.reclaim.quiesce().map_or_else(
|| Ok(empty_response(StatusCode::ServiceUnavailable)),
|answer| json_response(StatusCode::Ok, &answer),
),
(
_,
HEALTH_PATH | READY_PATH | METRICS_PATH | UNLOADABLE_PATH | RECLAIM_PATH | QUIESCE_PATH,
) => Ok(empty_response(StatusCode::MethodNotAllowed)),
_ if path.split('?').next() == Some(REISSUE_PATH) => {
credential_reissue_response(method, path, served)
}
_ => Ok(empty_response(StatusCode::NotFound)),
}
}
pub(super) fn credential_reissue_response(
method: &str,
path: &str,
served: &ServedState,
) -> Result<Vec<u8>, ServerError> {
if method != "POST" {
return Ok(empty_response(StatusCode::MethodNotAllowed));
}
let query = path.split_once('?').map_or("", |(_, query)| query);
let Some(request) = parse_reissue_query(query) else {
return Ok(empty_response(StatusCode::BadRequest));
};
match served.reissue.reissue(request) {
Ok(None) => Ok(empty_response(StatusCode::ServiceUnavailable)),
Ok(Some(OperatorCredentialReissueOutcome::Issued(issued))) => {
json_response(StatusCode::Ok, &issued)
}
Ok(Some(OperatorCredentialReissueOutcome::Refused(refusal))) => {
json_response(reissue_refusal_status(&refusal), &refusal)
}
Err(error) => {
tracing::error!(%error, "operator credential re-issue could not be decided");
json_response(
StatusCode::ServiceUnavailable,
&serde_json::json!({ "error": error.message }),
)
}
}
}
pub(super) const fn reissue_refusal_status(
refusal: &OperatorCredentialReissueRefusal,
) -> StatusCode {
match refusal {
OperatorCredentialReissueRefusal::ConversationUnknown { .. }
| OperatorCredentialReissueRefusal::ParticipantUnknown { .. } => StatusCode::NotFound,
OperatorCredentialReissueRefusal::Retired { .. }
| OperatorCredentialReissueRefusal::LiveBinding { .. }
| OperatorCredentialReissueRefusal::DetachReplayOpen { .. }
| OperatorCredentialReissueRefusal::LiveReceipt { .. }
| OperatorCredentialReissueRefusal::GenerationMismatch { .. } => StatusCode::Conflict,
}
}
pub(super) fn parse_reissue_query(query: &str) -> Option<OperatorCredentialReissueRequest> {
let mut conversation_id = None;
let mut participant_id = None;
let mut expected_current_generation = None;
for pair in query.split('&') {
let (name, value) = pair.split_once('=')?;
let value = value.parse::<u64>().ok()?;
let slot = match name {
"conversation_id" => &mut conversation_id,
"participant_id" => &mut participant_id,
"expected_current_generation" => &mut expected_current_generation,
_ => return None,
};
if slot.replace(value).is_some() {
return None;
}
}
Some(OperatorCredentialReissueRequest {
conversation_id: conversation_id?,
participant_id: participant_id?,
expected_current_generation: expected_current_generation?,
})
}