use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use axum::extract::Request;
use axum::response::Response;
use http::header::RETRY_AFTER;
use http::HeaderValue;
use pin_project_lite::pin_project;
use tokio::sync::OwnedSemaphorePermit;
use tower::{Layer, Service};
use super::{reject, Class, Concurrency, Guards, Maintenance, Scope};
use crate::client_address::{ClientAddress, Resolution};
use crate::shield::matcher::Phase;
use crate::shield::{Decision, Report, Shield};
pub(crate) struct Gate {
require_client: Option<Arc<Scope>>,
maintenance: Option<(Arc<Maintenance>, Arc<Scope>)>,
concurrency: Option<(Arc<Concurrency>, Arc<Scope>)>,
shield: Option<(Arc<Shield>, Arc<Scope>)>,
}
impl Gate {
pub(super) fn layer(guards: &Guards, class: Class) -> Option<GateLayer> {
let covered = |scope: &Arc<Scope>| scope.covers(class);
let gate = Self {
require_client: guards.require_client.clone().filter(covered),
maintenance: guards
.maintenance
.clone()
.filter(|(_, scope)| covered(scope)),
concurrency: guards
.concurrency
.clone()
.filter(|(_, scope)| covered(scope)),
shield: guards
.shield
.clone()
.filter(|(shield, scope)| covered(scope) && shield.enforces(Phase::PreAuth)),
};
let empty = gate.require_client.is_none()
&& gate.maintenance.is_none()
&& gate.concurrency.is_none()
&& gate.shield.is_none();
(!empty).then(|| GateLayer(Arc::new(gate)))
}
fn admit(&self, request: &Request) -> Admission {
if let Some(scope) = &self.require_client {
if applies(scope, request) {
if let Some(refusal) = unresolved_client(request) {
return Admission::Refused(refusal);
}
}
}
if let Some((maintenance, scope)) = &self.maintenance {
if applies(scope, request) && !maintenance.exempts(request.uri().path()) {
let mut response = reject(tonic::Code::Unavailable, maintenance.message.clone());
response
.headers_mut()
.insert(RETRY_AFTER, HeaderValue::from_static("300"));
return Admission::Refused(response);
}
}
let mut admitted = Admitted::default();
if let Some((concurrency, scope)) = &self.concurrency {
if applies(scope, request) {
match concurrency.take() {
Some(slot) => admitted.slot = Some(slot),
None => return Admission::Refused(Concurrency::full()),
}
}
}
if let Some((shield, scope)) = &self.shield {
if applies(scope, request) {
match shield.decide(Phase::PreAuth, request) {
Decision::Pass => {}
Decision::Reject(response) => return Admission::Refused(response),
Decision::Report(report) => admitted.report = Some(report),
}
}
}
Admission::Admitted(admitted)
}
}
enum Admission {
Admitted(Admitted),
Refused(Response),
}
fn applies(scope: &Scope, request: &Request) -> bool {
!scope.narrows() || scope.matches(request)
}
fn unresolved_client(request: &Request) -> Option<Response> {
let resolution = request
.extensions()
.get::<ClientAddress>()
.map_or(Resolution::Unavailable, ClientAddress::resolution);
match resolution {
Resolution::Peer(_) | Resolution::Forwarded(_) => None,
Resolution::Invalid(invalid) => Some(reject(
tonic::Code::InvalidArgument,
format!("the client address a trusted proxy forwarded is {invalid}"),
)),
Resolution::Unavailable => Some(reject(
tonic::Code::Internal,
"no connection information to resolve the client address from",
)),
}
}
#[derive(Default)]
struct Admitted {
slot: Option<OwnedSemaphorePermit>,
report: Option<Report>,
}
impl Admitted {
fn settle(self, mut response: Response) -> Response {
if let Some(report) = &self.report {
report.apply(&mut response);
}
match self.slot {
Some(slot) => response.map(|body| crate::held::until_end(body, slot)),
None => response,
}
}
}
#[derive(Clone)]
pub(crate) struct GateLayer(Arc<Gate>);
impl<S> Layer<S> for GateLayer {
type Service = GateService<S>;
fn layer(&self, inner: S) -> GateService<S> {
GateService {
gate: self.0.clone(),
inner,
}
}
}
#[derive(Clone)]
pub(crate) struct GateService<S> {
gate: Arc<Gate>,
inner: S,
}
impl<S> Service<Request> for GateService<S>
where
S: Service<Request, Response = Response, Error = Infallible>,
{
type Response = Response;
type Error = Infallible;
type Future = GateFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Infallible>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
match self.gate.admit(&request) {
Admission::Admitted(admitted) => GateFuture::Admitted {
future: self.inner.call(request),
admitted: Some(admitted),
},
Admission::Refused(response) => GateFuture::Refused {
response: Some(response),
},
}
}
}
pin_project! {
#[project = GateProjection]
pub(crate) enum GateFuture<F> {
Admitted { #[pin] future: F, admitted: Option<Admitted> },
Refused { response: Option<Response> },
}
}
impl<F> Future for GateFuture<F>
where
F: Future<Output = Result<Response, Infallible>>,
{
type Output = Result<Response, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
GateProjection::Admitted { future, admitted } => {
let Ok(response) = std::task::ready!(future.poll(cx));
let admitted = admitted.take().expect("polled after completion");
Poll::Ready(Ok(admitted.settle(response)))
}
GateProjection::Refused { response } => {
Poll::Ready(Ok(response.take().expect("polled after completion")))
}
}
}
}