use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::time::Duration;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use super::common::{PcStatus, guard, out_write, slice};
use crate::ec::{BoxedEcdsaPrivateKey, Ed25519PrivateKey};
use crate::quic::{QuicConfig, QuicConnection, Role as QuicRole, StreamId, TransportParameters};
use crate::rsa::BoxedRsaPrivateKey;
use crate::tls::{Config, ConfigBuilder, ProtocolVersion, RootCertStore, SigningKey};
#[allow(dead_code)]
pub const PC_QUIC_V1: i32 = 0x0000_0001;
fn role_from_i32(v: i32) -> Option<QuicRole> {
Some(match v {
0 => QuicRole::Client,
1 => QuicRole::Server,
_ => return None,
})
}
pub struct PcQuicCfg {
role: QuicRole,
roots_pem: Vec<String>,
server_name: Option<String>,
cert: Option<CertAndKey>,
alpn: Vec<Vec<u8>>,
verify_certs: bool,
tp: TransportParameters,
require_retry: bool,
}
struct CertAndKey {
chain_der: Vec<Vec<u8>>,
key: PcKey,
}
#[allow(clippy::large_enum_variant)]
enum PcKey {
Rsa(BoxedRsaPrivateKey),
Ecdsa(BoxedEcdsaPrivateKey),
Ed25519(Ed25519PrivateKey),
}
impl PcKey {
fn to_signing_key(&self) -> SigningKey {
match self {
PcKey::Rsa(k) => SigningKey::Rsa(k.clone()),
PcKey::Ecdsa(k) => SigningKey::Ecdsa(k.clone()),
PcKey::Ed25519(k) => SigningKey::Ed25519(k.clone()),
}
}
}
impl PcQuicCfg {
fn new(role: QuicRole) -> Self {
let tp = TransportParameters {
max_idle_timeout_ms: Some(60_000),
initial_max_data: Some(1 << 20),
initial_max_stream_data_bidi_local: Some(256 * 1024),
initial_max_stream_data_bidi_remote: Some(256 * 1024),
initial_max_stream_data_uni: Some(256 * 1024),
initial_max_streams_bidi: Some(16),
initial_max_streams_uni: Some(16),
ack_delay_exponent: Some(3),
max_ack_delay_ms: Some(25),
active_connection_id_limit: Some(2),
max_datagram_frame_size: Some(1200),
..TransportParameters::default()
};
PcQuicCfg {
role,
roots_pem: Vec::new(),
server_name: None,
cert: None,
alpn: Vec::new(),
verify_certs: true,
tp,
require_retry: false,
}
}
fn build_roots(&self) -> RootCertStore {
let mut store = RootCertStore::new();
for pem in &self.roots_pem {
store
.add_pem(pem)
.expect("build_roots: pre-validated PEM failed to re-parse");
}
store
}
fn build_tls_config(&self) -> Option<Config> {
let mut b: ConfigBuilder = Config::builder()
.versions(ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_3)
.verify_certificates(self.verify_certs)
.roots(self.build_roots());
if !self.alpn.is_empty() {
b = b.alpn(self.alpn.clone());
}
if let Some(sni) = &self.server_name {
b = b.server_name(sni.clone());
}
if let Some(ck) = &self.cert {
b = b.identity(ck.chain_der.clone(), ck.key.to_signing_key());
}
Some(b.build())
}
}
#[unsafe(no_mangle)]
pub extern "C" fn pc_quic_cfg_new(role: i32) -> *mut PcQuicCfg {
crate::ffi::common::guard_ptr(|| {
let Some(r) = role_from_i32(role) else {
return core::ptr::null_mut();
};
Box::into_raw(Box::new(PcQuicCfg::new(r)))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_free(cfg: *mut PcQuicCfg) {
if !cfg.is_null() {
drop(unsafe { Box::from_raw(cfg) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_add_root_pem(
cfg: *mut PcQuicCfg,
pem: *const u8,
len: usize,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
let Some(b) = (unsafe { slice(pem, len) }) else {
return PcStatus::NullPointer;
};
let Ok(s) = core::str::from_utf8(b) else {
return PcStatus::BadEncoding;
};
let mut tmp = RootCertStore::new();
if tmp.add_pem(s).is_err() {
return PcStatus::BadEncoding;
}
unsafe { &mut *cfg }.roots_pem.push(s.to_string());
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_server_name(
cfg: *mut PcQuicCfg,
sni: *const core::ffi::c_char,
) -> PcStatus {
guard(|| {
if cfg.is_null() || sni.is_null() {
return PcStatus::NullPointer;
}
let cs = unsafe { core::ffi::CStr::from_ptr(sni) };
let Ok(s) = cs.to_str() else {
return PcStatus::BadEncoding;
};
unsafe { &mut *cfg }.server_name = Some(s.to_string());
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_certificate(
cfg: *mut PcQuicCfg,
chain_pem: *const u8,
chain_len: usize,
key_pem: *const u8,
key_pem_len: usize,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
let (Some(chain), Some(kp)) = (unsafe { slice(chain_pem, chain_len) }, unsafe {
slice(key_pem, key_pem_len)
}) else {
return PcStatus::NullPointer;
};
let chain_str = match core::str::from_utf8(chain) {
Ok(s) => s,
Err(_) => return PcStatus::BadEncoding,
};
let chain_der = pem_split_cert_chain(chain_str);
if chain_der.is_empty() {
return PcStatus::BadEncoding;
}
let key_str = match core::str::from_utf8(kp) {
Ok(s) => s,
Err(_) => return PcStatus::BadEncoding,
};
let key = if let Ok(k) = BoxedRsaPrivateKey::from_pkcs1_pem(key_str) {
PcKey::Rsa(k)
} else if let Ok(k) = BoxedEcdsaPrivateKey::from_sec1_pem(key_str) {
PcKey::Ecdsa(k)
} else if let Ok(k) = Ed25519PrivateKey::from_pkcs8_pem(key_str) {
PcKey::Ed25519(k)
} else {
return PcStatus::BadEncoding;
};
unsafe { &mut *cfg }.cert = Some(CertAndKey { chain_der, key });
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_alpn(
cfg: *mut PcQuicCfg,
protocols: *const *const core::ffi::c_char,
n: usize,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
let mut out: Vec<Vec<u8>> = Vec::with_capacity(n);
if n > 0 {
if protocols.is_null() {
return PcStatus::NullPointer;
}
for i in 0..n {
let p = unsafe { *protocols.add(i) };
if p.is_null() {
return PcStatus::NullPointer;
}
let cs = unsafe { core::ffi::CStr::from_ptr(p) };
out.push(cs.to_bytes().to_vec());
}
}
unsafe { &mut *cfg }.alpn = out;
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_verify_certificates(
cfg: *mut PcQuicCfg,
verify: i32,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
unsafe { &mut *cfg }.verify_certs = verify != 0;
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_max_idle_timeout_ms(
cfg: *mut PcQuicCfg,
ms: u64,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
unsafe { &mut *cfg }.tp.max_idle_timeout_ms = Some(ms);
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_initial_max_data(
cfg: *mut PcQuicCfg,
bytes: u64,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
unsafe { &mut *cfg }.tp.initial_max_data = Some(bytes);
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_initial_max_streams_bidi(
cfg: *mut PcQuicCfg,
streams: u64,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
unsafe { &mut *cfg }.tp.initial_max_streams_bidi = Some(streams);
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_max_datagram_frame_size(
cfg: *mut PcQuicCfg,
bytes: u64,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
unsafe { &mut *cfg }.tp.max_datagram_frame_size = Some(bytes);
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_cfg_set_require_retry(
cfg: *mut PcQuicCfg,
require: i32,
) -> PcStatus {
guard(|| {
if cfg.is_null() {
return PcStatus::NullPointer;
}
unsafe { &mut *cfg }.require_retry = require != 0;
PcStatus::Ok
})
}
pub struct PcQuic {
inner: QuicConnection,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_new(cfg: *const PcQuicCfg) -> *mut PcQuic {
crate::ffi::common::guard_ptr(|| {
if cfg.is_null() {
return core::ptr::null_mut();
}
let c = unsafe { &*cfg };
let tls_cfg = match c.build_tls_config() {
Some(t) => t,
None => return core::ptr::null_mut(),
};
let mut qcfg = QuicConfig {
tls: tls_cfg,
transport_params: c.tp.clone(),
..QuicConfig::default()
};
if c.role == QuicRole::Server && c.require_retry {
let mut secret = [0u8; 32];
crate::rng::RngCore::fill_bytes(&mut crate::rng::OsRng, &mut secret);
qcfg.require_retry = true;
qcfg.retry_secret = Some(secret);
}
let conn = match c.role {
QuicRole::Client => {
let sni = match c.server_name.as_deref() {
Some(s) => s,
None => return core::ptr::null_mut(),
};
QuicConnection::client(qcfg, sni)
}
QuicRole::Server => QuicConnection::server(qcfg),
};
let Ok(inner) = conn else {
return core::ptr::null_mut();
};
Box::into_raw(Box::new(PcQuic { inner }))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_free(q: *mut PcQuic) {
if !q.is_null() {
drop(unsafe { Box::from_raw(q) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_feed_datagram(
q: *mut PcQuic,
dg: *const u8,
len: usize,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let Some(b) = (unsafe { slice(dg, len) }) else {
return PcStatus::NullPointer;
};
let conn = &mut unsafe { &mut *q }.inner;
match conn.feed_datagram(b) {
Ok(()) => PcStatus::Ok,
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_pop_datagram(
q: *mut PcQuic,
out: *mut u8,
out_len: *mut usize,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
let dg = conn.pop_datagram();
unsafe { out_write(&dg, out, out_len) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_handshake(q: *mut PcQuic) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
if conn.is_handshake_complete() {
PcStatus::Ok
} else {
PcStatus::WantRead
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_is_handshake_complete(
q: *const PcQuic,
out: *mut i32,
) -> PcStatus {
guard(|| {
if q.is_null() || out.is_null() {
return PcStatus::NullPointer;
}
unsafe {
*out = if (*q).inner.is_handshake_complete() {
1
} else {
0
}
};
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_next_timeout(
q: *const PcQuic,
seconds_out: *mut u64,
nanos_out: *mut u32,
has_timeout: *mut i32,
) -> PcStatus {
guard(|| {
if q.is_null() || seconds_out.is_null() || nanos_out.is_null() || has_timeout.is_null() {
return PcStatus::NullPointer;
}
let conn = unsafe { &*q };
match conn.inner.next_timeout() {
Some(d) => unsafe {
*seconds_out = d.as_secs();
*nanos_out = d.subsec_nanos();
*has_timeout = 1;
},
None => unsafe {
*seconds_out = 0;
*nanos_out = 0;
*has_timeout = 0;
},
}
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_on_timeout(
q: *mut PcQuic,
since_start_secs: u64,
since_start_nanos: u32,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
conn.on_timeout(Duration::new(since_start_secs, since_start_nanos));
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_open_bidi(q: *mut PcQuic, id_out: *mut u64) -> PcStatus {
guard(|| {
if q.is_null() || id_out.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
match conn.open_bidi() {
Ok(id) => {
unsafe { *id_out = id.value() };
PcStatus::Ok
}
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_open_uni(q: *mut PcQuic, id_out: *mut u64) -> PcStatus {
guard(|| {
if q.is_null() || id_out.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
match conn.open_uni() {
Ok(id) => {
unsafe { *id_out = id.value() };
PcStatus::Ok
}
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_stream_write(
q: *mut PcQuic,
id: u64,
data: *const u8,
len: usize,
written_out: *mut usize,
) -> PcStatus {
guard(|| {
if q.is_null() || written_out.is_null() {
return PcStatus::NullPointer;
}
let Some(b) = (unsafe { slice(data, len) }) else {
return PcStatus::NullPointer;
};
let conn = &mut unsafe { &mut *q }.inner;
match conn.write(StreamId(id), b) {
Ok(n) => {
unsafe { *written_out = n };
PcStatus::Ok
}
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_stream_finish(q: *mut PcQuic, id: u64) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
match conn.finish(StreamId(id)) {
Ok(()) => PcStatus::Ok,
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_stream_read(
q: *mut PcQuic,
id: u64,
out: *mut u8,
out_len: *mut usize,
fin_seen: *mut i32,
) -> PcStatus {
guard(|| {
if q.is_null() || out_len.is_null() || fin_seen.is_null() {
return PcStatus::NullPointer;
}
let cap = unsafe { *out_len };
const PC_QUIC_STREAM_READ_MAX: usize = 1 << 20; if cap > PC_QUIC_STREAM_READ_MAX {
unsafe { *out_len = PC_QUIC_STREAM_READ_MAX };
return PcStatus::BufferTooSmall;
}
if cap > 0 && out.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
let mut tmp: Vec<u8> = alloc::vec![0u8; cap];
let (n, fin) = match conn.read(StreamId(id), &mut tmp) {
Ok(p) => p,
Err(_) => return PcStatus::Internal,
};
unsafe { *out_len = n };
unsafe { *fin_seen = if fin { 1 } else { 0 } };
if n > 0 {
unsafe { core::ptr::copy_nonoverlapping(tmp.as_ptr(), out, n) };
}
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_stream_reset(q: *mut PcQuic, id: u64, app_error: u64) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
match conn.reset(StreamId(id), app_error) {
Ok(()) => PcStatus::Ok,
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_stream_stop_sending(
q: *mut PcQuic,
id: u64,
app_error: u64,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
match conn.stop_sending(StreamId(id), app_error) {
Ok(()) => PcStatus::Ok,
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_send_datagram(
q: *mut PcQuic,
data: *const u8,
len: usize,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let Some(b) = (unsafe { slice(data, len) }) else {
return PcStatus::NullPointer;
};
let conn = &mut unsafe { &mut *q }.inner;
if !conn.is_handshake_complete() {
return PcStatus::WantHandshake;
}
match conn.send_datagram(b) {
Ok(()) => PcStatus::Ok,
Err(_) => PcStatus::BadEncoding,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_recv_datagram(
q: *mut PcQuic,
out: *mut u8,
out_len: *mut usize,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
let payload = conn.recv_datagram().unwrap_or_default();
unsafe { out_write(&payload, out, out_len) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_initiate_key_update(q: *mut PcQuic) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let conn = &mut unsafe { &mut *q }.inner;
match conn.initiate_key_update() {
Ok(()) => PcStatus::Ok,
Err(_) => PcStatus::Internal,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_set_peer_addr(
q: *mut PcQuic,
ipv6_bytes_16: *const u8,
port: u16,
) -> PcStatus {
guard(|| {
if q.is_null() || ipv6_bytes_16.is_null() {
return PcStatus::NullPointer;
}
let mut octets = [0u8; 16];
unsafe { core::ptr::copy_nonoverlapping(ipv6_bytes_16, octets.as_mut_ptr(), 16) };
let addr =
if octets[..10].iter().all(|b| *b == 0) && octets[10] == 0xff && octets[11] == 0xff {
SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(
octets[12], octets[13], octets[14], octets[15],
)),
port,
)
} else {
SocketAddr::new(IpAddr::V6(Ipv6Addr::from(octets)), port)
};
let conn = &mut unsafe { &mut *q }.inner;
conn.set_peer_addr(addr);
PcStatus::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_negotiated_alpn(
q: *const PcQuic,
out: *mut u8,
out_len: *mut usize,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
let alpn: Vec<u8> = Vec::new();
unsafe { out_write(&alpn, out, out_len) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pc_quic_peer_certificate(
q: *const PcQuic,
_out: *mut u8,
_out_len: *mut usize,
) -> PcStatus {
guard(|| {
if q.is_null() {
return PcStatus::NullPointer;
}
PcStatus::BadEncoding
})
}
fn pem_split(pem: &str, label: &str) -> Vec<String> {
let begin = alloc::format!("-----BEGIN {label}-----");
let end = alloc::format!("-----END {label}-----");
let mut out = Vec::new();
let mut rest = pem;
while let Some(b_off) = rest.find(&begin) {
let after_b = b_off;
if let Some(e_off) = rest[after_b..].find(&end) {
let abs_end = after_b + e_off + end.len();
out.push(rest[after_b..abs_end].to_string());
rest = &rest[abs_end..];
} else {
break;
}
}
out
}
fn pem_split_cert_chain(pem: &str) -> Vec<Vec<u8>> {
let mut chain = Vec::new();
for block in pem_split(pem, "CERTIFICATE") {
if let Ok(der) = crate::der::pem_decode(&block, "CERTIFICATE") {
chain.push(der);
}
}
chain
}