WEB: websocket + websocket-lanes as Carrier

This commit is contained in:
Alexey
2026-08-26 09:16:06 +03:00
parent 8e577ec5ca
commit d2edd90479
50 changed files with 3642 additions and 350 deletions
+64 -21
View File
@@ -1,6 +1,6 @@
use std::io;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use crate::proxy::shared_state::ConntrackClosePolicy;
@@ -15,10 +15,15 @@ mod tests;
impl WebSession {
/// Starts one owned inner handshake and relay task for an admitted stream.
pub(super) fn spawn_stream(self: &Arc<Self>, stream_id: u32, peer_port: u16) {
pub(super) fn spawn_stream(
self: &Arc<Self>,
stream_id: u32,
peer_port: u16,
retain_reservation_on_reject: bool,
) -> bool {
let Some(manager) = self.manager.upgrade() else {
self.stream_finished(stream_id, peer_port);
return;
self.stream_rejected_before_spawn(stream_id, peer_port, retain_reservation_on_reject);
return false;
};
let generation = manager.active_generation();
if !*generation.admission_rx.borrow() {
@@ -27,8 +32,8 @@ impl WebSession {
Some(stream_id),
Some("admission_closed"),
);
self.stream_finished(stream_id, peer_port);
return;
self.stream_rejected_before_spawn(stream_id, peer_port, retain_reservation_on_reject);
return false;
}
let Ok(connection_permit) = generation.max_connections.clone().try_acquire_owned() else {
manager.record_stream_rejected();
@@ -37,21 +42,24 @@ impl WebSession {
Some(stream_id),
Some("connection_limit"),
);
self.stream_finished(stream_id, peer_port);
return;
self.stream_rejected_before_spawn(stream_id, peer_port, retain_reservation_on_reject);
return false;
};
let deps = generation.client_runtime_deps();
let replay_checker = Arc::clone(&generation.replay_checker);
let session = Arc::clone(self);
let cancel = self.cancel.clone();
let retain_rejected = Arc::new(AtomicBool::new(false));
self.tasks_live.fetch_add(1, Ordering::AcqRel);
let spawned = generation.spawn_session(async move {
let completion = StreamCompletion {
session: Arc::clone(&session),
stream_id,
peer_port,
retain_rejected: Arc::clone(&retain_rejected),
};
let future = async move {
let _connection_permit = connection_permit;
let _completion = StreamCompletion {
session: Arc::clone(&session),
stream_id,
peer_port,
};
let _completion = completion;
session.trace_lifecycle(
crate::web::trace::TraceLifecycleEvent::StreamAdmitted,
Some(stream_id),
@@ -69,16 +77,41 @@ impl WebSession {
peer_port,
) => {}
}
});
if !spawned {
};
if let Err(future) = generation.try_spawn_session(future) {
retain_rejected.store(retain_reservation_on_reject, Ordering::Release);
self.trace_lifecycle(
crate::web::trace::TraceLifecycleEvent::StreamRejected,
Some(stream_id),
Some("generation_closed"),
);
self.tasks_live.fetch_sub(1, Ordering::AcqRel);
drop(future);
return false;
}
true
}
fn stream_rejected_before_spawn(
&self,
stream_id: u32,
peer_port: u16,
retain_reservation: bool,
) {
if !retain_reservation {
self.stream_finished(stream_id, peer_port);
self.tasks_done.notify_waiters();
return;
}
let queued = {
let mut state = self.state.lock();
state.streams.remove(&stream_id).map(|stream| {
let (bytes, items) = inbound_queue_cost(&stream.inbound);
self.release_locked(&mut state, bytes, items, false);
self.remember_closed_locked(&mut state, stream_id);
self.queue_control_locked(&mut state, FrameType::Close, stream_id, &[])
})
};
if queued.is_some_and(|queued| !queued) {
self.close();
}
}
@@ -106,7 +139,7 @@ impl WebSession {
if !queued {
self.close();
}
if self.carrier() == crate::config::WebCarrier::Https {
if self.carrier().is_multiplexed() {
self.down_notify.notify_waiters();
}
}
@@ -117,6 +150,7 @@ struct StreamCompletion {
session: Arc<WebSession>,
stream_id: u32,
peer_port: u16,
retain_rejected: Arc<AtomicBool>,
}
impl Drop for StreamCompletion {
@@ -126,7 +160,12 @@ impl Drop for StreamCompletion {
Some(self.stream_id),
None,
);
self.session.stream_finished(self.stream_id, self.peer_port);
if self.retain_rejected.load(Ordering::Acquire) {
self.session
.stream_rejected_before_spawn(self.stream_id, self.peer_port, true);
} else {
self.session.stream_finished(self.stream_id, self.peer_port);
}
if self.session.tasks_live.fetch_sub(1, Ordering::AcqRel) == 1 {
self.session.tasks_done.notify_waiters();
}
@@ -263,6 +302,10 @@ async fn run_stream(
session.trace_lifecycle(
crate::web::trace::TraceLifecycleEvent::RelayEnded,
Some(stream_id),
Some(if relay_result.is_ok() { "completed" } else { "error" }),
Some(if relay_result.is_ok() {
"completed"
} else {
"error"
}),
);
}
+7 -9
View File
@@ -13,9 +13,7 @@ use crate::config::{
WebSecretMode,
};
use crate::crypto::{AesCtr, sha256};
use crate::maestro::generation::{
RuntimeGeneration, test_runtime_generation_with_admission,
};
use crate::maestro::generation::{RuntimeGeneration, test_runtime_generation_with_admission};
use crate::protocol::constants::{
DC_IDX_POS, HANDSHAKE_LEN, IV_LEN, PREKEY_LEN, PROTO_TAG_POS, ProtoTag, SKIP_LEN,
};
@@ -39,10 +37,11 @@ impl TestRuntime {
) -> Result<u64, ManagerError> {
let encoded = frame::encode(frame_type, stream_id, payload);
match self.session.carrier() {
WebCarrier::Https => self.session.process_up(sequence, &encoded),
WebCarrier::HttpsLanes => {
self.session
.process_up_lane(stream_id, sequence, &encoded)
WebCarrier::Https | WebCarrier::Websocket => {
self.session.process_up(sequence, &encoded)
}
WebCarrier::HttpsLanes | WebCarrier::WebsocketLanes => {
self.session.process_up_lane(stream_id, sequence, &encoded)
}
}
}
@@ -152,8 +151,7 @@ fn valid_plain_handshake() -> [u8; HANDSHAKE_LEN] {
let mut cipher = AesCtr::new(&dec_key, u128::from_be_bytes(dec_iv));
let keystream = cipher.encrypt(&[0u8; HANDSHAKE_LEN]);
let mut plaintext = [0u8; HANDSHAKE_LEN];
plaintext[PROTO_TAG_POS..PROTO_TAG_POS + 4]
.copy_from_slice(&ProtoTag::Intermediate.to_bytes());
plaintext[PROTO_TAG_POS..PROTO_TAG_POS + 4].copy_from_slice(&ProtoTag::Intermediate.to_bytes());
plaintext[DC_IDX_POS..DC_IDX_POS + 2].copy_from_slice(&2i16.to_le_bytes());
for index in PROTO_TAG_POS..HANDSHAKE_LEN {
handshake[index] = plaintext[index] ^ keystream[index];
+15 -8
View File
@@ -5,14 +5,13 @@ use bytes::{BufMut, Bytes, BytesMut};
use super::{
DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState, WebSession,
};
use crate::config::WebCarrier;
use crate::web::frame::{self, FrameType};
use crate::web::manager::ManagerError;
impl WebSession {
/// Polls pending downlink frames with cursor replay and newest-poll-wins semantics.
pub(crate) async fn poll_down(&self, cursor: u64) -> Result<PollResult, ManagerError> {
if self.carrier() != WebCarrier::Https {
if !self.carrier().is_multiplexed() {
return Err(ManagerError::Protocol);
}
let epoch = {
@@ -167,7 +166,13 @@ impl WebSession {
let Some(manager) = self.manager.upgrade() else {
return false;
};
if !manager.try_reserve_pending(bytes, items, control, class == PendingClass::Downlink) {
if !manager.try_reserve_pending(
self.profile_key,
bytes,
items,
control,
class == PendingClass::Downlink,
) {
return false;
}
state.pending_bytes += bytes;
@@ -194,7 +199,7 @@ impl WebSession {
state.pending_control_items = state.pending_control_items.saturating_sub(items);
}
if let Some(manager) = self.manager.upgrade() {
manager.release_pending(bytes, items, control);
manager.release_pending(self.profile_key, bytes, items, control);
}
}
@@ -208,7 +213,7 @@ impl WebSession {
if amount == 0 {
return true;
}
if self.carrier() == WebCarrier::HttpsLanes {
if self.carrier().uses_lanes() {
return self.queue_control_locked(
state,
FrameType::Window,
@@ -257,7 +262,7 @@ impl WebSession {
stream_id: u32,
payload: &[u8],
) -> bool {
if self.carrier() == WebCarrier::HttpsLanes {
if self.carrier().uses_lanes() {
return self.queue_frame_locked(state, FrameType::Data, stream_id, payload, false);
}
let can_coalesce = state.pending_frames.back().is_some_and(|last| {
@@ -290,7 +295,7 @@ impl WebSession {
payload: &[u8],
control: bool,
) -> bool {
if self.carrier() == WebCarrier::HttpsLanes {
if self.carrier().uses_lanes() {
return self.queue_lane_frame_locked(state, frame_type, stream_id, payload, control);
}
let cost = frame::HEADER_BYTES + payload.len() + QUEUE_ITEM_COST;
@@ -409,7 +414,9 @@ mod tests {
use std::net::SocketAddr;
use std::sync::Arc;
use crate::config::{WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
use crate::config::{
WebCarrier, WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
};
use crate::web::manager::WebProcessRuntime;
fn session() -> Arc<WebSession> {
+5 -4
View File
@@ -113,6 +113,7 @@ impl WebSession {
&mut state,
&frames,
&mut opened,
&mut None,
&mut unused_bytes,
&mut unused_items,
);
@@ -137,7 +138,7 @@ impl WebSession {
return result;
}
for (stream_id, peer_port) in opened {
self.spawn_stream(stream_id, peer_port);
self.spawn_stream(stream_id, peer_port, false);
}
if let Some(manager) = self.manager.upgrade() {
manager.record_up(body.len());
@@ -151,7 +152,7 @@ impl WebSession {
lane_id: u32,
cursor: u64,
) -> Result<PollResult, ManagerError> {
if self.carrier() != WebCarrier::HttpsLanes || lane_id > frame::MAX_STREAM_ID {
if !self.carrier().uses_lanes() || lane_id > frame::MAX_STREAM_ID {
return Err(ManagerError::Protocol);
}
let (epoch, notify) = {
@@ -404,7 +405,7 @@ impl WebSession {
pub(super) fn remember_closed_locked(&self, state: &mut SessionState, stream_id: u32) {
let evicted = remember_closed(state, stream_id, self.limits.max_tombstones_per_session);
if self.carrier() != WebCarrier::HttpsLanes {
if !self.carrier().uses_lanes() {
return;
}
if let Some(evicted) = evicted {
@@ -415,7 +416,7 @@ impl WebSession {
}
}
fn release_lane_locked(&self, state: &mut SessionState, lane_id: u32) {
pub(super) fn release_lane_locked(&self, state: &mut SessionState, lane_id: u32) {
let Some(mut lane) = state.carrier_lanes.remove(&lane_id) else {
return;
};
+29 -8
View File
@@ -11,7 +11,6 @@ use super::{
InboundChunk, PendingClass, QUEUE_ITEM_COST, SessionState, StreamState, WebSession,
inbound_queue_cost,
};
use crate::config::WebCarrier;
use crate::web::frame::{self, Frame, FrameType};
use crate::web::manager::{ManagerError, TokenHash};
@@ -22,7 +21,7 @@ impl WebSession {
sequence: u64,
body: &[u8],
) -> Result<u64, ManagerError> {
if self.carrier() != WebCarrier::Https {
if !self.carrier().is_multiplexed() {
return Err(ManagerError::Protocol);
}
if self
@@ -90,6 +89,7 @@ impl WebSession {
&mut state,
&frames,
&mut opened,
&mut None,
&mut unused_bytes,
&mut unused_items,
);
@@ -113,7 +113,7 @@ impl WebSession {
return result;
}
for (stream_id, peer_port) in opened {
self.spawn_stream(stream_id, peer_port);
self.spawn_stream(stream_id, peer_port, false);
}
if let Some(manager) = self.manager.upgrade() {
manager.record_up(body.len());
@@ -126,6 +126,7 @@ impl WebSession {
state: &mut SessionState,
frames: &[Frame<'_>],
opened: &mut Vec<(u32, u16)>,
reserved_open: &mut Option<(u32, u16)>,
unused_bytes: &mut usize,
unused_items: &mut usize,
) -> bool {
@@ -136,13 +137,31 @@ impl WebSession {
let was_closed = state.closed_streams.contains(&value.stream_id);
match value.frame_type {
FrameType::Open => {
let Some(peer_port) = self.reserve_stream_locked(state) else {
self.remember_closed_locked(state, value.stream_id);
if !self.queue_control_locked(state, FrameType::Close, value.stream_id, &[])
let peer_port = match reserved_open.take() {
Some((reserved_stream_id, peer_port))
if reserved_stream_id == value.stream_id =>
{
peer_port
}
Some(reserved) => {
*reserved_open = Some(reserved);
return false;
}
continue;
None => {
let Some(peer_port) = self.reserve_stream_locked(state) else {
self.remember_closed_locked(state, value.stream_id);
if !self.queue_control_locked(
state,
FrameType::Close,
value.stream_id,
&[],
) {
return false;
}
continue;
};
peer_port
}
};
state.streams.insert(
value.stream_id,
@@ -331,7 +350,9 @@ mod tests {
use super::*;
use std::net::SocketAddr;
use crate::config::{WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
use crate::config::{
WebCarrier, WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
};
use crate::web::manager::WebProcessRuntime;
fn session() -> Arc<WebSession> {
+250
View File
@@ -0,0 +1,250 @@
use std::sync::Arc;
use std::time::Instant;
use sha2::{Digest, Sha256};
use super::uplink::{inbound_reservation, validate_batch};
use super::{CarrierLane, PendingClass, WebSession, inbound_queue_cost};
use crate::config::WebCarrier;
use crate::web::frame;
use crate::web::manager::ManagerError;
/// Pre-OPEN stream quota and synthetic tuple ownership for one WebSocket lane.
pub(crate) struct WebSocketLaneReservation {
session: Arc<WebSession>,
lane_id: u32,
peer_port: u16,
transferred: bool,
}
impl WebSocketLaneReservation {
/// Returns the logical stream owned by this connection.
pub(crate) fn lane_id(&self) -> u32 {
self.lane_id
}
fn transfer_to_stream(&mut self) {
let removed = self
.session
.state
.lock()
.websocket_lane_reservations
.remove(&self.lane_id);
if removed == Some(self.peer_port) {
self.transferred = true;
}
}
}
impl Drop for WebSocketLaneReservation {
fn drop(&mut self) {
if !self.transferred {
self.session
.release_websocket_lane_reservation(self.lane_id, self.peer_port);
}
}
}
impl WebSession {
/// Acquires stream quota and tuple ownership before a lane returns HTTP 101.
pub(crate) fn reserve_websocket_lane(
self: &Arc<Self>,
lane_id: u32,
) -> Result<WebSocketLaneReservation, ManagerError> {
if self.carrier() != WebCarrier::WebsocketLanes
|| lane_id == 0
|| lane_id > frame::MAX_STREAM_ID
{
return Err(ManagerError::Protocol);
}
let mut state = self.state.lock();
if state.closed {
return Err(ManagerError::Closed);
}
if state.active_peer_ports.len() >= self.profile.max_streams_per_session
|| state.streams.contains_key(&lane_id)
|| state.closed_streams.contains(&lane_id)
|| state.websocket_lane_reservations.contains_key(&lane_id)
{
return Err(ManagerError::Limit);
}
let Some(manager) = self.manager.upgrade() else {
return Err(ManagerError::Closed);
};
let Some(peer_port) = manager.try_acquire_stream(
self.profile_key,
self.profile.max_streams,
self.client_ip,
self.profile.public_addr,
) else {
return Err(ManagerError::Limit);
};
if !state.active_peer_ports.insert(peer_port) {
manager.release_stream(
self.profile_key,
self.client_ip,
self.profile.public_addr,
peer_port,
);
return Err(ManagerError::Limit);
}
state.websocket_lane_reservations.insert(lane_id, peer_port);
state.carrier_lanes.insert(lane_id, CarrierLane::new());
Ok(WebSocketLaneReservation {
session: Arc::clone(self),
lane_id,
peer_port,
transferred: false,
})
}
/// Applies one ordered WebSocket lane message without closing sibling lanes.
pub(crate) fn process_websocket_lane(
self: &Arc<Self>,
reservation: &mut WebSocketLaneReservation,
sequence: u64,
body: &[u8],
) -> Result<(), ManagerError> {
if !Arc::ptr_eq(self, &reservation.session)
|| reservation.lane_id == 0
|| reservation.lane_id > frame::MAX_STREAM_ID
{
return Err(ManagerError::Protocol);
}
let lane_id = reservation.lane_id;
let frames = frame::parse_all(body, &self.limits).map_err(|_| ManagerError::Protocol)?;
if frames
.iter()
.copied()
.any(|value| value.stream_id != lane_id || frame::validate_client_shape(value).is_err())
{
return Err(ManagerError::Protocol);
}
let digest = Sha256::digest(body).into();
let mut opened = Vec::new();
let result = {
let mut state = self.state.lock();
if state.closed {
return Err(ManagerError::Closed);
}
if !reservation.transferred
&& state.websocket_lane_reservations.get(&lane_id) != Some(&reservation.peer_port)
{
return Err(ManagerError::Closed);
}
let Some(lane) = state.carrier_lanes.get_mut(&lane_id) else {
return Err(ManagerError::Closed);
};
if sequence == 0 || sequence != lane.last_up_sequence.saturating_add(1) {
return Err(ManagerError::Protocol);
}
if lane.up_active {
return Err(ManagerError::Concurrent);
}
lane.up_active = true;
if !validate_batch(&state, &frames) {
if let Some(lane) = state.carrier_lanes.get_mut(&lane_id) {
lane.up_active = false;
}
return Err(ManagerError::Protocol);
}
let (reserve_bytes, reserve_items) = inbound_reservation(&state, &frames);
if !self.reserve_locked(
&mut state,
reserve_bytes,
reserve_items,
PendingClass::Uplink,
) {
if let Some(lane) = state.carrier_lanes.get_mut(&lane_id) {
lane.up_active = false;
}
return Err(ManagerError::Backpressure);
}
let mut unused_bytes = reserve_bytes;
let mut unused_items = reserve_items;
let mut reserved_open =
(!reservation.transferred).then_some((lane_id, reservation.peer_port));
let applied = self.apply_batch_locked(
&mut state,
&frames,
&mut opened,
&mut reserved_open,
&mut unused_bytes,
&mut unused_items,
);
self.release_locked(&mut state, unused_bytes, unused_items, false);
if let Some(lane) = state.carrier_lanes.get_mut(&lane_id) {
lane.up_active = false;
if applied {
lane.last_up_sequence = sequence;
lane.last_up_digest = digest;
}
}
state.last_activity = Instant::now();
applied.then_some(()).ok_or(ManagerError::Protocol)
};
result?;
for (stream_id, peer_port) in opened {
if stream_id != lane_id || peer_port != reservation.peer_port {
return Err(ManagerError::Protocol);
}
if !self.spawn_stream(stream_id, peer_port, true) {
return Err(ManagerError::Limit);
}
reservation.transfer_to_stream();
}
if !reservation.transferred {
return Err(ManagerError::Protocol);
}
if let Some(manager) = self.manager.upgrade() {
manager.record_up(body.len());
}
Ok(())
}
/// Ends one failed or disconnected lane without closing its parent session.
pub(crate) fn close_websocket_lane(&self, lane_id: u32) {
let reserved = {
let mut state = self.state.lock();
let reserved = state.websocket_lane_reservations.remove(&lane_id);
if let Some(stream) = state.streams.remove(&lane_id) {
let (bytes, items) = inbound_queue_cost(&stream.inbound);
self.release_locked(&mut state, bytes, items, false);
if let Some(waker) = stream.read_waker {
waker.wake();
}
if let Some(waker) = stream.write_waker {
waker.wake();
}
}
self.remember_closed_locked(&mut state, lane_id);
self.release_lane_locked(&mut state, lane_id);
reserved
};
if let Some(peer_port) = reserved {
self.release_websocket_lane_reservation(lane_id, peer_port);
}
}
fn release_websocket_lane_reservation(&self, lane_id: u32, peer_port: u16) {
let removed = {
let mut state = self.state.lock();
if state.websocket_lane_reservations.get(&lane_id) == Some(&peer_port) {
state.websocket_lane_reservations.remove(&lane_id);
}
self.release_lane_locked(&mut state, lane_id);
state.active_peer_ports.remove(&peer_port)
};
if removed && let Some(manager) = self.manager.upgrade() {
manager.release_stream(
self.profile_key,
self.client_ip,
self.profile.public_addr,
peer_port,
);
}
}
}
#[cfg(test)]
mod tests;
+135
View File
@@ -0,0 +1,135 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use arc_swap::ArcSwap;
use tokio::sync::watch;
use super::*;
use crate::config::{ProxyConfig, WebRuntimeConfig, WebRuntimeProfile, WebSecretMode};
use crate::maestro::generation::{RuntimeGeneration, test_runtime_generation_with_admission};
use crate::web::frame::FrameType;
use crate::web::manager::WebProcessRuntime;
struct TestRuntime {
session: Arc<WebSession>,
manager: Arc<WebProcessRuntime>,
generation: Arc<RuntimeGeneration>,
}
impl TestRuntime {
async fn shutdown(self) {
self.session.close();
self.session.wait().await;
self.manager.shutdown().await;
self.generation.stop_sessions().await;
self.generation.stop_background_tasks().await;
}
}
fn runtime(admission: bool) -> TestRuntime {
let profile = Arc::new(WebRuntimeProfile {
host: "proxy.example.com".to_string(),
public_addr: "203.0.113.10:443".parse().unwrap(),
user: "default".to_string(),
secret_mode: WebSecretMode::Plain,
carrier: WebCarrier::WebsocketLanes,
capability: [7; 32],
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 2,
max_streams: 1,
max_streams_per_session: 1,
});
let mut config = ProxyConfig::default();
config.web.enabled = true;
config.web.carrier = WebCarrier::WebsocketLanes;
config.web.timeouts.shutdown_secs = 1;
config.web.runtime = Some(Arc::new(WebRuntimeConfig {
vhosts: BTreeMap::new(),
profiles: vec![Arc::clone(&profile)],
}));
config.rebuild_runtime_user_auth().unwrap();
let limits = config.web.limits.clone();
let timeouts = config.web.timeouts.clone();
let (_admission_tx, admission_rx) = watch::channel(admission);
let generation = test_runtime_generation_with_admission(1, config, admission_rx);
let manager = WebProcessRuntime::start(Arc::new(ArcSwap::from(Arc::clone(&generation))));
let session = WebSession::new(
Arc::downgrade(&manager),
[8; 32],
"192.0.2.10".parse().unwrap(),
1,
profile,
[7; 32],
limits,
timeouts,
);
TestRuntime {
session,
manager,
generation,
}
}
#[tokio::test]
async fn rejected_open_retains_stream_quota_until_lane_socket_teardown() {
let runtime = runtime(false);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
let open = frame::encode(FrameType::Open, 7, &[]);
assert_eq!(
runtime
.session
.process_websocket_lane(&mut reservation, 1, &open),
Err(ManagerError::Limit),
);
assert!(
runtime
.manager
.try_acquire_stream(
runtime.session.profile_key,
runtime.session.profile.max_streams,
runtime.session.client_ip,
runtime.session.profile.public_addr,
)
.is_none()
);
runtime.session.close_websocket_lane(7);
drop(reservation);
let peer_port = runtime
.manager
.try_acquire_stream(
runtime.session.profile_key,
runtime.session.profile.max_streams,
runtime.session.client_ip,
runtime.session.profile.public_addr,
)
.unwrap();
runtime.manager.release_stream(
runtime.session.profile_key,
runtime.session.client_ip,
runtime.session.profile.public_addr,
peer_port,
);
runtime.shutdown().await;
}
#[tokio::test]
async fn malformed_lane_message_does_not_close_sibling_session_state() {
let runtime = runtime(true);
let mut reservation = runtime.session.reserve_websocket_lane(7).unwrap();
let data = frame::encode(FrameType::Data, 7, &[1]);
assert_eq!(
runtime
.session
.process_websocket_lane(&mut reservation, 1, &data),
Err(ManagerError::Protocol),
);
assert!(!runtime.session.state.lock().closed);
runtime.session.close_websocket_lane(7);
drop(reservation);
assert!(runtime.session.reserve_websocket_lane(8).is_ok());
runtime.shutdown().await;
}