use core::num::NonZeroU32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Parity {
Odd,
Even,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StreamIdError {
Reserved,
ParityMismatch {
expected: Parity,
got: u32,
},
OutOfRange {
value: u32,
},
}
impl std::fmt::Display for StreamIdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Reserved => write!(f, "stream ID 0 is reserved for connection control"),
Self::ParityMismatch { expected, got } => {
let expected_str = match expected {
Parity::Odd => "odd",
Parity::Even => "even",
};
write!(
f,
"stream ID {got} parity mismatch: expected {expected_str}"
)
}
Self::OutOfRange { value } => write!(f, "stream ID {value} exceeds 31-bit range"),
}
}
}
impl std::error::Error for StreamIdError {}
pub(crate) const STREAM_ID_MAX: u32 = (1u32 << 31) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ClientStreamId(NonZeroU32);
impl ClientStreamId {
pub fn new(id: u32) -> Result<Self, StreamIdError> {
if id == 0 {
return Err(StreamIdError::Reserved);
}
if id > STREAM_ID_MAX {
return Err(StreamIdError::OutOfRange { value: id });
}
if id.is_multiple_of(2) {
return Err(StreamIdError::ParityMismatch {
expected: Parity::Odd,
got: id,
});
}
Ok(Self(NonZeroU32::new(id).expect("non-zero checked above")))
}
pub const fn from_static(id: u32) -> Self {
assert!(
id <= STREAM_ID_MAX,
"ClientStreamId::from_static: id must be <= 2^31-1"
);
assert!(
!id.is_multiple_of(2),
"ClientStreamId::from_static: id must be odd (RFC 9113 §5.1.1)"
);
match NonZeroU32::new(id) {
Some(v) => Self(v),
None => panic!("ClientStreamId::from_static: id must not be 0 (RFC 9113 §5.1.1)"),
}
}
pub(crate) fn from_validated_parts(id: NonZeroU32) -> Self {
debug_assert!(
id.get() <= STREAM_ID_MAX,
"ClientStreamId::from_validated_parts: id must be <= 2^31-1"
);
debug_assert!(
!id.get().is_multiple_of(2),
"ClientStreamId::from_validated_parts: id must be odd"
);
Self(id)
}
pub const fn get(self) -> NonZeroU32 {
self.0
}
pub const fn as_u32(self) -> u32 {
self.0.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ServerStreamId(NonZeroU32);
impl ServerStreamId {
pub fn new(id: u32) -> Result<Self, StreamIdError> {
if id == 0 {
return Err(StreamIdError::Reserved);
}
if id > STREAM_ID_MAX {
return Err(StreamIdError::OutOfRange { value: id });
}
if !id.is_multiple_of(2) {
return Err(StreamIdError::ParityMismatch {
expected: Parity::Even,
got: id,
});
}
Ok(Self(NonZeroU32::new(id).expect("non-zero checked above")))
}
pub const fn from_static(id: u32) -> Self {
assert!(
id <= STREAM_ID_MAX,
"ServerStreamId::from_static: id must be <= 2^31-1"
);
assert!(
id.is_multiple_of(2),
"ServerStreamId::from_static: id must be even (RFC 9113 §5.1.1)"
);
match NonZeroU32::new(id) {
Some(v) => Self(v),
None => panic!("ServerStreamId::from_static: id must not be 0 (RFC 9113 §5.1.1)"),
}
}
pub(crate) fn from_validated_parts(id: NonZeroU32) -> Self {
debug_assert!(
id.get() <= STREAM_ID_MAX,
"ServerStreamId::from_validated_parts: id must be <= 2^31-1"
);
debug_assert!(
id.get().is_multiple_of(2),
"ServerStreamId::from_validated_parts: id must be even"
);
Self(id)
}
pub const fn get(self) -> NonZeroU32 {
self.0
}
pub const fn as_u32(self) -> u32 {
self.0.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NonZeroStreamId {
Client(ClientStreamId),
Server(ServerStreamId),
}
impl NonZeroStreamId {
pub fn new(id: u32) -> Result<Self, StreamIdError> {
if id == 0 {
return Err(StreamIdError::Reserved);
}
if id > STREAM_ID_MAX {
return Err(StreamIdError::OutOfRange { value: id });
}
if id.is_multiple_of(2) {
Ok(Self::Server(ServerStreamId::from_validated_parts(
NonZeroU32::new(id).expect("non-zero checked above"),
)))
} else {
Ok(Self::Client(ClientStreamId::from_validated_parts(
NonZeroU32::new(id).expect("non-zero checked above"),
)))
}
}
pub const fn from_static(id: u32) -> Self {
assert!(id != 0, "NonZeroStreamId::from_static: id must not be 0");
assert!(
id <= STREAM_ID_MAX,
"NonZeroStreamId::from_static: id must be <= 2^31-1"
);
if id.is_multiple_of(2) {
Self::Server(ServerStreamId::from_static(id))
} else {
Self::Client(ClientStreamId::from_static(id))
}
}
pub(crate) fn from_validated_parts(id: NonZeroU32) -> Self {
debug_assert!(
id.get() <= STREAM_ID_MAX,
"NonZeroStreamId::from_validated_parts: id must be <= 2^31-1"
);
if id.get().is_multiple_of(2) {
Self::Server(ServerStreamId::from_validated_parts(id))
} else {
Self::Client(ClientStreamId::from_validated_parts(id))
}
}
pub const fn as_u32(self) -> u32 {
match self {
Self::Client(id) => id.as_u32(),
Self::Server(id) => id.as_u32(),
}
}
pub const fn client(self) -> Option<ClientStreamId> {
match self {
Self::Client(id) => Some(id),
Self::Server(_) => None,
}
}
pub const fn server(self) -> Option<ServerStreamId> {
match self {
Self::Client(_) => None,
Self::Server(id) => Some(id),
}
}
}
impl From<ClientStreamId> for NonZeroStreamId {
fn from(id: ClientStreamId) -> Self {
Self::Client(id)
}
}
impl From<ServerStreamId> for NonZeroStreamId {
fn from(id: ServerStreamId) -> Self {
Self::Server(id)
}
}
impl std::fmt::Display for NonZeroStreamId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_u32())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StreamId {
Connection,
Client(ClientStreamId),
Server(ServerStreamId),
}
impl StreamId {
pub fn from_wire(id: u32) -> Self {
debug_assert!(
id <= STREAM_ID_MAX,
"StreamId::from_wire: id must be <= 2^31-1, got {id}"
);
if id == 0 {
Self::Connection
} else if id.is_multiple_of(2) {
Self::Server(ServerStreamId::from_validated_parts(
NonZeroU32::new(id).expect("non-zero checked above"),
))
} else {
Self::Client(ClientStreamId::from_validated_parts(
NonZeroU32::new(id).expect("non-zero checked above"),
))
}
}
pub const fn as_u32(self) -> u32 {
match self {
Self::Connection => 0,
Self::Client(id) => id.as_u32(),
Self::Server(id) => id.as_u32(),
}
}
pub const fn non_zero(self) -> Option<NonZeroStreamId> {
match self {
Self::Connection => None,
Self::Client(id) => Some(NonZeroStreamId::Client(id)),
Self::Server(id) => Some(NonZeroStreamId::Server(id)),
}
}
}
impl std::fmt::Display for StreamId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_u32())
}
}
impl From<ClientStreamId> for StreamId {
fn from(id: ClientStreamId) -> Self {
Self::Client(id)
}
}
impl From<ServerStreamId> for StreamId {
fn from(id: ServerStreamId) -> Self {
Self::Server(id)
}
}
impl From<NonZeroStreamId> for StreamId {
fn from(id: NonZeroStreamId) -> Self {
match id {
NonZeroStreamId::Client(c) => Self::Client(c),
NonZeroStreamId::Server(s) => Self::Server(s),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_stream_id_new_out_of_range() {
assert_eq!(
ClientStreamId::new(STREAM_ID_MAX + 1),
Err(StreamIdError::OutOfRange {
value: STREAM_ID_MAX + 1,
})
);
}
#[test]
fn non_zero_stream_id_new_out_of_range() {
assert_eq!(
NonZeroStreamId::new(STREAM_ID_MAX + 1),
Err(StreamIdError::OutOfRange {
value: STREAM_ID_MAX + 1,
})
);
}
#[test]
fn client_stream_id_from_validated_parts() {
let raw = NonZeroU32::new(5).unwrap();
let id = ClientStreamId::from_validated_parts(raw);
assert_eq!(id.as_u32(), 5);
}
#[test]
fn server_stream_id_from_validated_parts() {
let raw = NonZeroU32::new(6).unwrap();
let id = ServerStreamId::from_validated_parts(raw);
assert_eq!(id.as_u32(), 6);
}
mod validated_parts {
use proptest::prelude::*;
use super::{ClientStreamId, NonZeroStreamId, NonZeroU32, STREAM_ID_MAX, ServerStreamId};
proptest! {
#[test]
fn client_stream_id_validated_matches_new(
id in (1u32..=STREAM_ID_MAX).prop_filter(
"奇数のみ",
|id| id % 2 == 1,
),
) {
let via_new = ClientStreamId::new(id).unwrap();
let nz = NonZeroU32::new(id).unwrap();
let via_validated = ClientStreamId::from_validated_parts(nz);
prop_assert_eq!(via_new, via_validated);
}
#[test]
fn server_stream_id_validated_matches_new(
id in (2u32..=STREAM_ID_MAX).prop_filter(
"偶数のみ",
|id| id % 2 == 0,
),
) {
let via_new = ServerStreamId::new(id).unwrap();
let nz = NonZeroU32::new(id).unwrap();
let via_validated = ServerStreamId::from_validated_parts(nz);
prop_assert_eq!(via_new, via_validated);
}
#[test]
fn non_zero_stream_id_validated_matches_new(
id in 1u32..=STREAM_ID_MAX,
) {
let via_new = NonZeroStreamId::new(id).unwrap();
let nz = NonZeroU32::new(id).unwrap();
let via_validated = NonZeroStreamId::from_validated_parts(nz);
prop_assert_eq!(via_new, via_validated);
}
}
}
}