use core::fmt;
use super::constants::{
is_tls_grease_u16, tls_protocol_version_label, tls_protocol_version_name,
tls_protocol_version_status, TlsCodepointStatus, TLS_CURRENT_VERSION, TLS_LEGACY_VERSION,
TLS_VERSION_1_0, TLS_VERSION_1_1, TLS_VERSION_1_2, TLS_VERSION_1_3, TLS_VERSION_SSL_3_0,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TlsVersion {
raw: u16,
}
impl TlsVersion {
pub const SSL_3_0: Self = Self::new(TLS_VERSION_SSL_3_0);
pub const TLS_1_0: Self = Self::new(TLS_VERSION_1_0);
pub const TLS_1_1: Self = Self::new(TLS_VERSION_1_1);
pub const TLS_1_2: Self = Self::new(TLS_VERSION_1_2);
pub const TLS_1_3: Self = Self::new(TLS_VERSION_1_3);
pub const LEGACY_RECORD: Self = Self::new(TLS_LEGACY_VERSION);
pub const NEGOTIATED_TLS_1_3: Self = Self::new(TLS_CURRENT_VERSION);
pub const fn new(raw: u16) -> Self {
Self { raw }
}
pub const fn from_u16(raw: u16) -> Self {
Self::new(raw)
}
pub const fn from_be_bytes(bytes: [u8; 2]) -> Self {
Self::new(u16::from_be_bytes(bytes))
}
pub const fn ssl_3_0() -> Self {
Self::SSL_3_0
}
pub const fn tls_1_0() -> Self {
Self::TLS_1_0
}
pub const fn tls_1_1() -> Self {
Self::TLS_1_1
}
pub const fn tls_1_2() -> Self {
Self::TLS_1_2
}
pub const fn tls_1_3() -> Self {
Self::TLS_1_3
}
pub const fn legacy_record() -> Self {
Self::LEGACY_RECORD
}
pub const fn legacy_hello() -> Self {
Self::LEGACY_RECORD
}
pub const fn supported_versions_tls_1_3() -> Self {
Self::NEGOTIATED_TLS_1_3
}
pub const fn raw(self) -> u16 {
self.raw
}
pub const fn as_u16(self) -> u16 {
self.raw
}
pub const fn to_be_bytes(self) -> [u8; 2] {
self.raw.to_be_bytes()
}
pub const fn name(self) -> Option<&'static str> {
tls_protocol_version_name(self.raw)
}
pub const fn status(self) -> TlsCodepointStatus {
tls_protocol_version_status(self.raw)
}
pub const fn is_grease(self) -> bool {
is_tls_grease_u16(self.raw)
}
pub const fn is_legacy_compatibility_value(self) -> bool {
self.raw == TLS_LEGACY_VERSION
}
pub const fn is_supported_versions_tls_1_3(self) -> bool {
self.raw == TLS_VERSION_1_3
}
pub fn label(self) -> String {
tls_protocol_version_label(self.raw)
}
pub fn inspection_label(self) -> String {
format!(
"{} raw=0x{:04x} status={}",
self.label(),
self.raw,
self.status().label()
)
}
pub fn inspection_fields(self) -> Vec<(&'static str, String)> {
self.inspection_fields_for(TlsVersionField::ProtocolVersion)
}
pub fn inspection_fields_for(self, field: TlsVersionField) -> Vec<(&'static str, String)> {
vec![
("field", field.label().to_string()),
("role", field.role().to_string()),
("version", self.label()),
("raw", format!("0x{:04x}", self.raw)),
("status", self.status().label().to_string()),
]
}
}
impl From<u16> for TlsVersion {
fn from(value: u16) -> Self {
Self::new(value)
}
}
impl From<TlsVersion> for u16 {
fn from(value: TlsVersion) -> Self {
value.raw()
}
}
impl fmt::Display for TlsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.label())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TlsVersionField {
ProtocolVersion,
LegacyRecordVersion,
LegacyHelloVersion,
SupportedVersions,
}
impl TlsVersionField {
pub const fn label(self) -> &'static str {
match self {
Self::ProtocolVersion => "protocol_version",
Self::LegacyRecordVersion => "legacy_record_version",
Self::LegacyHelloVersion => "legacy_version",
Self::SupportedVersions => "supported_versions",
}
}
pub const fn role(self) -> &'static str {
match self {
Self::ProtocolVersion => "protocol version",
Self::LegacyRecordVersion => "record compatibility field",
Self::LegacyHelloVersion => "hello compatibility field",
Self::SupportedVersions => "negotiated version extension value",
}
}
}
impl fmt::Display for TlsVersionField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.label())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tls_version_known_constructors_expose_raw_values() {
assert_eq!(TlsVersion::ssl_3_0().raw(), TLS_VERSION_SSL_3_0);
assert_eq!(TlsVersion::tls_1_0().raw(), TLS_VERSION_1_0);
assert_eq!(TlsVersion::tls_1_1().raw(), TLS_VERSION_1_1);
assert_eq!(TlsVersion::tls_1_2().raw(), TLS_VERSION_1_2);
assert_eq!(TlsVersion::tls_1_3().raw(), TLS_VERSION_1_3);
assert_eq!(TlsVersion::from_be_bytes([0x03, 0x04]), TlsVersion::TLS_1_3);
assert_eq!(TlsVersion::TLS_1_3.to_be_bytes(), [0x03, 0x04]);
}
#[test]
fn tls_version_distinguishes_legacy_record_from_supported_versions() {
let record_legacy = TlsVersion::legacy_record();
let hello_legacy = TlsVersion::legacy_hello();
let negotiated = TlsVersion::supported_versions_tls_1_3();
assert_eq!(record_legacy.raw(), TLS_LEGACY_VERSION);
assert_eq!(hello_legacy.raw(), TLS_VERSION_1_2);
assert_eq!(negotiated.raw(), TLS_CURRENT_VERSION);
assert_ne!(record_legacy, negotiated);
assert!(record_legacy.is_legacy_compatibility_value());
assert!(!record_legacy.is_supported_versions_tls_1_3());
assert!(negotiated.is_supported_versions_tls_1_3());
}
#[test]
fn tls_version_labels_and_statuses_reuse_constants() {
let tls12 = TlsVersion::TLS_1_2;
let grease = TlsVersion::from_u16(0x7a7a);
let unknown = TlsVersion::from_u16(0x4242);
assert_eq!(tls12.name(), Some("TLS 1.2"));
assert_eq!(tls12.status(), TlsCodepointStatus::DefaultEligible);
assert_eq!(tls12.label(), "TLS 1.2");
assert_eq!(tls12.to_string(), "TLS 1.2");
assert!(grease.is_grease());
assert_eq!(grease.status(), TlsCodepointStatus::ReservedGrease);
assert_eq!(grease.label(), "reserved grease protocol version 0x7a7a");
assert_eq!(unknown.status(), TlsCodepointStatus::Unknown);
assert_eq!(unknown.label(), "unknown protocol version 0x4242");
}
#[test]
fn tls_version_inspection_output_includes_context_and_raw_value() {
let legacy_fields =
TlsVersion::legacy_record().inspection_fields_for(TlsVersionField::LegacyRecordVersion);
assert!(legacy_fields.contains(&("field", "legacy_record_version".to_string())));
assert!(legacy_fields.contains(&("role", "record compatibility field".to_string())));
assert!(legacy_fields.contains(&("version", "TLS 1.2".to_string())));
assert!(legacy_fields.contains(&("raw", "0x0303".to_string())));
assert!(legacy_fields.contains(&("status", "default-eligible".to_string())));
let supported_fields = TlsVersion::supported_versions_tls_1_3()
.inspection_fields_for(TlsVersionField::SupportedVersions);
assert!(supported_fields.contains(&("field", "supported_versions".to_string())));
assert!(
supported_fields.contains(&("role", "negotiated version extension value".to_string()))
);
assert!(supported_fields.contains(&("version", "TLS 1.3".to_string())));
assert!(supported_fields.contains(&("raw", "0x0304".to_string())));
assert_eq!(
TlsVersion::from_u16(0x4242).inspection_label(),
"unknown protocol version 0x4242 raw=0x4242 status=unknown"
);
}
}