use std::future::Future;
use std::sync::Arc;
use std::time::Instant;
use rocketmq_error::AUTH_PERMISSION_DENIED;
use rocketmq_protocol::code::response_code::ResponseCode;
use rocketmq_protocol::protocol::remoting_command::RemotingCommand;
use rocketmq_runtime::OperationContext;
use rocketmq_runtime::ResourceBudget;
use rocketmq_runtime::RuntimeError;
use rocketmq_runtime::ShutdownDeadline;
use rocketmq_runtime::ShutdownReport;
use rocketmq_runtime::TaskGroup;
use rocketmq_runtime::TaskId;
use rocketmq_security_api::Action;
use rocketmq_security_api::AuthorizationDecision;
use rocketmq_security_api::Resource;
use rocketmq_security_api::ResourceKind;
use rocketmq_security_api::SecurityBootstrapProfile;
use super::OriginalRequestIdentity;
use super::RequestContext;
use crate::admission::AdmissionClass;
use crate::admission::AdmissionController;
use crate::admission::AdmissionRejection;
use crate::admission::AdmissionScopeHandle;
use crate::admission::FullPolicy;
use crate::admission::PartialFramePermit;
use crate::base::pending_request_table::PendingRequestLimits;
use crate::base::pending_request_table::PendingRequestTable;
use crate::error::TransportError;
use crate::request_ordering::RequestOrdering;
use crate::runtime::processor::RequestProcessor;
use crate::runtime::RPCHook;
use crate::security::TransportSecurity;
use crate::session_executor::SessionDispatchAttempt;
use crate::session_executor::SessionExecutor;
use crate::telemetry::TransportTelemetry;
#[path = "authorized_dispatcher/core.rs"]
mod core;
#[path = "authorized_dispatcher/embedded.rs"]
mod embedded;
fn authorization_denied_response() -> RemotingCommand {
RemotingCommand::create_response_command_with_code_remark(
ResponseCode::NoPermission,
AUTH_PERMISSION_DENIED.public_message(),
)
}
pub(crate) use core::AuthorizedDispatchError;
pub(crate) use core::AuthorizedDispatcherCore;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DispatchOutcome {
Accepted(TaskId),
Rejected,
CloseSession,
SessionClosed,
}
impl DispatchOutcome {
pub(crate) const fn keeps_session_open(self) -> bool {
matches!(self, Self::Accepted(_) | Self::Rejected)
}
}
pub(crate) struct AuthorizedDispatchBoundary {
security: Arc<TransportSecurity>,
admission: Arc<AdmissionController>,
}
impl AuthorizedDispatchBoundary {
#[must_use]
pub(crate) fn new(security: Arc<TransportSecurity>, admission: Arc<AdmissionController>) -> Self {
Self { security, admission }
}
#[must_use]
pub(crate) fn admission_controller(&self) -> Arc<AdmissionController> {
Arc::clone(&self.admission)
}
pub(crate) fn deferred_admission(&self) -> Option<crate::dispatch::DeferredAdmission> {
self.admission.deferred_admission()
}
#[must_use]
pub(crate) fn security_profile(&self) -> SecurityBootstrapProfile {
self.security.profile()
}
pub(crate) fn has_security_owner(&self, security: &Arc<TransportSecurity>) -> bool {
Arc::ptr_eq(&self.security, security)
}
pub(crate) fn security_owner(&self) -> Arc<TransportSecurity> {
Arc::clone(&self.security)
}
pub(crate) fn has_admission_owner(&self, admission: &Arc<AdmissionController>) -> bool {
Arc::ptr_eq(&self.admission, admission)
}
pub(crate) fn session(
self: &Arc<Self>,
task_group: &TaskGroup,
scope: AdmissionScopeHandle,
) -> Result<AuthorizedDispatchSession, RuntimeError> {
Ok(AuthorizedDispatchSession {
boundary: Arc::clone(self),
session_id: scope.session_id(),
executor: SessionExecutor::try_new(task_group, scope)?,
})
}
}
pub struct AuthorizedCommandDispatcher<P> {
boundary: Arc<AuthorizedDispatchBoundary>,
core: Arc<AuthorizedDispatcherCore<crate::dispatch::ExplicitProcessor<P>>>,
telemetry: TransportTelemetry,
}
impl<P> AuthorizedCommandDispatcher<P>
where
P: RequestProcessor + Clone + Sync + 'static,
{
#[must_use]
pub fn new(
request_processor: P,
rpc_hooks: Vec<Arc<dyn RPCHook>>,
security: Arc<TransportSecurity>,
admission: Arc<AdmissionController>,
) -> Self {
Self::new_with_telemetry(
request_processor,
rpc_hooks,
security,
admission,
TransportTelemetry::noop(),
)
}
#[must_use]
pub fn new_with_telemetry(
request_processor: P,
rpc_hooks: Vec<Arc<dyn RPCHook>>,
security: Arc<TransportSecurity>,
admission: Arc<AdmissionController>,
telemetry: TransportTelemetry,
) -> Self {
Self {
boundary: Arc::new(AuthorizedDispatchBoundary::new(security, admission)),
core: Arc::new(AuthorizedDispatcherCore::new_with_telemetry(
request_processor,
rpc_hooks,
telemetry.clone(),
)),
telemetry,
}
}
pub fn try_new_with_telemetry_and_budget(
request_processor: P,
rpc_hooks: Vec<Arc<dyn RPCHook>>,
security: Arc<TransportSecurity>,
admission: Arc<AdmissionController>,
telemetry: TransportTelemetry,
process_budget: &ResourceBudget,
) -> Result<Self, rocketmq_error::SharedError> {
let response_table = PendingRequestTable::try_with_limits_and_budget(
PendingRequestLimits {
max_count: 512,
..PendingRequestLimits::default()
},
process_budget,
)?;
Ok(Self {
boundary: Arc::new(AuthorizedDispatchBoundary::new(security, admission)),
core: Arc::new(AuthorizedDispatcherCore::new_with_pending_requests_and_telemetry(
request_processor,
rpc_hooks,
response_table,
telemetry.clone(),
)),
telemetry,
})
}
#[must_use]
pub(crate) fn boundary(&self) -> Arc<AuthorizedDispatchBoundary> {
Arc::clone(&self.boundary)
}
#[must_use]
pub fn deferred_admission(&self) -> Option<crate::dispatch::DeferredAdmission> {
self.boundary.deferred_admission()
}
pub(crate) fn register_rpc_hook(&self, hook: Arc<dyn RPCHook>) {
self.core.register_rpc_hook(hook);
}
pub(crate) fn clear_rpc_hook(&self) {
self.core.clear_rpc_hook();
}
pub(crate) fn hook_snapshot(&self) -> Option<Arc<crate::hook_registry::HookSnapshot>> {
self.core.hook_snapshot()
}
pub(crate) async fn dispatch_network(
&self,
authorized_session: &AuthorizedDispatchSession,
network_session: crate::dispatch::NetworkSession,
session: crate::server::SessionHandle,
context: RequestContext,
command: RemotingCommand,
received_at: Instant,
retained_bytes: usize,
partial_frame_permit: Option<PartialFramePermit>,
session_cleanup: crate::dispatch::DeferredSessionCleanupRegistration,
) -> Result<DispatchOutcome, TransportError> {
self.core
.dispatch_network(
authorized_session,
network_session,
session,
context,
command,
received_at,
retained_bytes,
partial_frame_permit,
Some(session_cleanup),
)
.await
.map_err(|source| TransportError::dispatch(source))
}
pub(crate) fn open_network_session(&self) -> crate::dispatch::NetworkSession {
self.core.open_network_session()
}
pub(crate) fn complete_network_response(
&self,
session: &crate::dispatch::NetworkSession,
response: RemotingCommand,
) {
self.core.complete_network_response(session, response);
}
pub(crate) fn close_network_session(&self, session: &crate::dispatch::NetworkSession) {
self.core.close_network_session(session);
}
}
pub(crate) struct AuthorizedDispatchSession {
boundary: Arc<AuthorizedDispatchBoundary>,
session_id: Option<u64>,
executor: SessionExecutor,
}
impl AuthorizedDispatchSession {
pub(crate) fn begin_close(&self) {
self.executor.begin_close();
}
pub(crate) fn operation_context(&self) -> &OperationContext {
self.executor.operation_context()
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn dispatch_handler<F, Fut>(
&self,
context: RequestContext,
original_request_identity: OriginalRequestIdentity,
command: RemotingCommand,
retained_bytes: usize,
partial_frame_permit: Option<PartialFramePermit>,
ordering: RequestOrdering,
response_session: crate::server::SessionHandle,
execute: F,
) -> Result<DispatchOutcome, TransportError>
where
F: FnOnce(OperationContext, RemotingCommand) -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
if context.transport() != super::RequestTransport::Network
|| self.session_id != Some(response_session.session_id())
|| original_request_identity.request_id().owner_id() != response_session.session_id()
|| !original_request_identity.matches_command(&command)
{
return Err(TransportError::dispatch(AuthorizedDispatchError::SessionMismatch));
}
let opaque = original_request_identity.original_opaque();
let request_code = original_request_identity.original_code();
let is_one_way = original_request_identity.is_one_way();
if context.deadline().is_some_and(|deadline| deadline.is_expired()) {
send_handler_boundary_response(&response_session, is_one_way, deadline_response(opaque))
.await
.map_err(|source| TransportError::dispatch(source))?;
return Ok(DispatchOutcome::Rejected);
}
if !matches!(
self.boundary.security.authorize_for_dispatch(
&command,
context.peer(),
context.principal(),
Resource::new(ResourceKind::Other, request_code.to_string()),
Action::Manage,
),
Ok(AuthorizationDecision::Allow)
) {
send_handler_boundary_response(
&response_session,
is_one_way,
authorization_denied_response().set_opaque(opaque),
)
.await
.map_err(|source| TransportError::dispatch(source))?;
return Ok(DispatchOutcome::Rejected);
}
let deadline = context.deadline();
let timeout_session = response_session.clone();
let rejection_session = response_session.clone();
match self.executor.try_execute(
retained_bytes,
AdmissionClass::for_request_code(request_code),
partial_frame_permit,
ordering,
move |operation| async move {
let execution = execute(operation, command);
if let Some(deadline) = deadline {
if deadline.timeout(execution).await.is_err() {
let _ = send_handler_boundary_response(&timeout_session, is_one_way, deadline_response(opaque))
.await;
}
} else {
execution.await;
}
},
move |_operation, error| async move {
let _ =
send_handler_boundary_response(&rejection_session, is_one_way, admission_response(opaque, &error))
.await;
},
) {
Ok(SessionDispatchAttempt::Accepted(task_id)) => Ok(DispatchOutcome::Accepted(task_id)),
Ok(SessionDispatchAttempt::AdmissionRejected {
rejection,
retained_partial,
}) if rejection.policy() == FullPolicy::Reject => {
drop(retained_partial);
send_handler_boundary_response(&response_session, is_one_way, admission_response(opaque, &rejection))
.await
.map_err(|source| TransportError::dispatch(source))?;
Ok(DispatchOutcome::Rejected)
}
Ok(SessionDispatchAttempt::AdmissionRejected {
rejection: _,
retained_partial,
}) => {
drop(retained_partial);
Ok(DispatchOutcome::CloseSession)
}
Ok(SessionDispatchAttempt::SessionClosed { retained_partial }) => {
drop(retained_partial);
Ok(DispatchOutcome::SessionClosed)
}
Err(error) => Err(TransportError::dispatch(AuthorizedDispatchError::Closing(error))),
}
}
pub(crate) async fn drain_until(&self, deadline: ShutdownDeadline) -> ShutdownReport {
self.executor.drain_until(deadline).await
}
pub(crate) async fn drain_report_until(
&self,
deadline: ShutdownDeadline,
) -> crate::session_executor::SessionExecutorDrainReport {
self.executor.drain_report_until(deadline).await
}
}
async fn send_handler_response(
session: &crate::server::SessionHandle,
command: RemotingCommand,
) -> Result<(), AuthorizedDispatchError> {
session
.connection()
.send_command(command)
.await
.map_err(AuthorizedDispatchError::BoundaryResponse)
}
async fn send_handler_boundary_response(
session: &crate::server::SessionHandle,
is_one_way: bool,
command: RemotingCommand,
) -> Result<(), AuthorizedDispatchError> {
if is_one_way {
return Ok(());
}
send_handler_response(session, command).await
}
pub(super) fn admission_response(opaque: i32, error: &AdmissionRejection) -> RemotingCommand {
RemotingCommand::create_response_command_with_code_remark(ResponseCode::SystemBusy, error.to_string())
.set_opaque(opaque)
}
pub(super) fn deadline_response(opaque: i32) -> RemotingCommand {
RemotingCommand::create_response_command_with_code_remark(
ResponseCode::SystemError,
"request deadline exceeded".to_owned(),
)
.set_opaque(opaque)
}