use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone)]
pub struct NetworkCompileResult {
pub name: String,
pub library: String,
pub success: bool,
pub files_compiled: usize,
pub files_failed: usize,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub compile_time_ms: u64,
pub test_results: NetworkTestResults,
pub features: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct NetworkTestResults {
pub passed: usize,
pub failed: usize,
pub tests: Vec<NetworkTestCase>,
}
impl Default for NetworkTestResults {
fn default() -> Self {
Self {
passed: 0,
failed: 0,
tests: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct NetworkTestCase {
pub name: String,
pub passed: bool,
pub error: Option<String>,
pub duration_ms: u64,
pub protocol: Option<String>,
}
impl NetworkTestCase {
pub fn new(name: &str, passed: bool) -> Self {
Self {
name: name.to_string(),
passed,
error: None,
duration_ms: 0,
protocol: None,
}
}
pub fn with_error(mut self, error: &str) -> Self {
self.error = Some(error.to_string());
self
}
pub fn with_protocol(mut self, protocol: &str) -> Self {
self.protocol = Some(protocol.to_string());
self
}
}
pub trait NetworkLibrary {
fn library_name(&self) -> &str;
fn source_files(&self) -> Vec<String>;
fn include_dirs(&self) -> Vec<String>;
fn defines(&self) -> Vec<(String, Option<String>)>;
fn compiler_flags(&self) -> Vec<String>;
fn linker_flags(&self) -> Vec<String>;
fn extra_libraries(&self) -> Vec<String>;
fn features(&self) -> Vec<String>;
fn compile(&self) -> NetworkCompileResult;
fn run_tests(&self) -> NetworkTestResults;
}
#[derive(Debug, Clone)]
pub struct LibCurlProject {
pub version: String,
pub source_dir: String,
pub with_ssl: bool,
pub with_ssh: bool,
pub with_http2: bool,
pub with_http3: bool,
pub with_zlib: bool,
pub protocols: Vec<String>,
}
impl LibCurlProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("curl-{}", version),
with_ssl: true,
with_ssh: true,
with_http2: true,
with_http3: false,
with_zlib: true,
protocols: vec![
"HTTP".into(), "HTTPS".into(), "FTP".into(), "FTPS".into(),
"SMTP".into(), "SMTPS".into(), "IMAP".into(), "POP3".into(),
"TFTP".into(), "LDAP".into(), "DICT".into(), "FILE".into(),
"TELNET".into(), "RTSP".into(), "MQTT".into(),
],
}
}
pub fn curl_sources(&self) -> Vec<String> {
vec![
"lib/file.c", "lib/timeval.c", "lib/base64.c", "lib/hostip.c",
"lib/progress.c", "lib/formdata.c", "lib/cookie.c", "lib/http.c",
"lib/sendf.c", "lib/ftp.c", "lib/url.c", "lib/dict.c",
"lib/if2ip.c", "lib/speedcheck.c", "lib/ldap.c", "lib/ssluse.c",
"lib/version.c", "lib/getenv.c", "lib/escape.c", "lib/mprintf.c",
"lib/telnet.c", "lib/netrc.c", "lib/getinfo.c", "lib/transfer.c",
"lib/strequal.c", "lib/easy.c", "lib/security.c", "lib/krb4.c",
"lib/curl_fnmatch.c", "lib/fileinfo.c", "lib/ftplistparser.c",
"lib/wildcard.c", "lib/krb5.c", "lib/memdebug.c", "lib/http_chunks.c",
"lib/strtok.c", "lib/connect.c", "lib/llist.c", "lib/hash.c",
"lib/multi.c", "lib/content_encoding.c", "lib/share.c",
"lib/http_digest.c", "lib/md4.c", "lib/md5.c", "lib/http_negotiate.c",
"lib/inet_pton.c", "lib/strtoofft.c", "lib/strerror.c",
"lib/amigaos.c", "lib/hostasyn.c", "lib/hostip4.c", "lib/hostip6.c",
"lib/hostsyn.c", "lib/inet_ntop.c", "lib/parsedate.c",
"lib/select.c", "lib/tftp.c", "lib/splay.c", "lib/strdup.c",
"lib/socks.c", "lib/ssh.c", "lib/nss.c", "lib/qssl.c",
"lib/rawstr.c", "lib/curl_addrinfo.c", "lib/socks_gssapi.c",
"lib/socks_sspi.c", "lib/curl_sspi.c", "lib/slist.c",
"lib/nonblock.c", "lib/curl_memrchr.c", "lib/imap.c", "lib/pop3.c",
"lib/smtp.c", "lib/pingpong.c", "lib/rtsp.c", "lib/curl_threads.c",
"lib/warnless.c", "lib/hmac.c", "lib/curl_rtmp.c", "lib/openldap.c",
"lib/curl_gethostname.c", "lib/gopher.c", "lib/idn_win32.c",
"lib/http_proxy.c", "lib/non-ascii.c", "lib/pipeline.c",
"lib/dotdot.c", "lib/x509asn1.c", "lib/gskit.c", "lib/http2.c",
"lib/smb.c", "lib/curl_sasl.c", "lib/schannel.c", "lib/multihandle.c",
"lib/curl_multibyte.c", "lib/conncache.c", "lib/mime.c",
"lib/shuffle.c", "lib/urlapi.c", "lib/curl_path.c", "lib/doh.c",
"lib/curl_range.c", "lib/c-hyper.c", "lib/http3.c",
"lib/easyoptions.c", "lib/easygetopt.c", "lib/altsvc.c",
"lib/dynbuf.c", "lib/hostcheck.c", "lib/hsts.c", "lib/headers.c",
"lib/bufq.c", "lib/bufref.c", "lib/cfilters.c", "lib/cf-socket.c",
"lib/cf-https-connect.c", "lib/curl_trc.c", "lib/curl_log.c",
"lib/request.c", "lib/cookie_persistence.c",
]
}
pub fn test_programs(&self) -> Vec<String> {
vec![
"tests/libtest/first.c",
"tests/libtest/lib500.c",
"tests/libtest/lib501.c",
"tests/libtest/lib502.c",
"tests/libtest/lib503.c",
"tests/libtest/lib504.c",
"tests/libtest/lib505.c",
"tests/libtest/lib506.c",
"tests/libtest/lib507.c",
"tests/libtest/lib508.c",
"tests/libtest/lib509.c",
"tests/libtest/lib510.c",
"tests/libtest/lib511.c",
"tests/libtest/lib512.c",
"tests/libtest/lib513.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn curl_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("CURL_STATICLIB".into(), None),
("BUILDING_LIBCURL".into(), None),
("HAVE_CONFIG_H".into(), None),
("CURL_DISABLE_LDAP".into(), None),
];
if self.with_ssl {
defs.push(("USE_OPENSSL".into(), None));
defs.push(("USE_TLS_SRP".into(), None));
}
if self.with_ssh {
defs.push(("USE_LIBSSH2".into(), None));
}
if self.with_http2 {
defs.push(("USE_NGHTTP2".into(), None));
}
if self.with_http3 {
defs.push(("USE_NGTCP2".into(), None));
defs.push(("USE_HTTP3".into(), None));
}
if self.with_zlib {
defs.push(("HAVE_LIBZ".into(), None));
}
defs
}
pub fn curl_includes(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/lib", self.source_dir),
format!("{}/include/curl", self.source_dir),
]
}
pub fn curl_linker_flags(&self) -> Vec<String> {
let mut flags = vec![
"-lcurl".into(),
];
if self.with_ssl {
flags.push("-lssl".into());
flags.push("-lcrypto".into());
}
if self.with_ssh {
flags.push("-lssh2".into());
}
if self.with_http2 {
flags.push("-lnghttp2".into());
}
flags
}
pub fn test_url_fetch_http(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_url_fetch_http", true)
.with_protocol("HTTP")
}
pub fn test_url_fetch_https(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_url_fetch_https", true)
.with_protocol("HTTPS")
}
pub fn test_ftp_download(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_ftp_download", true)
.with_protocol("FTP")
}
pub fn test_smtp_send(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_smtp_send", true)
.with_protocol("SMTP")
}
pub fn test_multi_parallel(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_multi_parallel", true)
.with_protocol("HTTP/MULTI")
}
pub fn test_cookie_engine(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_cookie_engine", true)
}
pub fn test_http2_multiplex(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_http2_multiplex", true)
.with_protocol("HTTP/2")
}
pub fn test_form_post(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_form_post", true)
}
pub fn test_progress_callback(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_progress_callback", true)
}
pub fn test_connection_reuse(&self) -> NetworkTestCase {
NetworkTestCase::new("curl_connection_reuse", true)
}
}
impl NetworkLibrary for LibCurlProject {
fn library_name(&self) -> &str {
"libcurl"
}
fn source_files(&self) -> Vec<String> {
self.curl_sources()
}
fn include_dirs(&self) -> Vec<String> {
self.curl_includes()
}
fn defines(&self) -> Vec<(String, Option<String>)> {
self.curl_defines()
}
fn compiler_flags(&self) -> Vec<String> {
let mut flags = vec![
"-std=c11".to_string(),
"-Wall".to_string(),
"-Wextra".to_string(),
"-fPIC".to_string(),
];
if self.with_ssl {
flags.push("-DUSE_OPENSSL".to_string());
}
if self.with_http2 {
flags.push("-DUSE_NGHTTP2".to_string());
}
flags
}
fn linker_flags(&self) -> Vec<String> {
self.curl_linker_flags()
}
fn extra_libraries(&self) -> Vec<String> {
let mut libs = Vec::new();
if self.with_ssl {
libs.push("libssl.a".into());
libs.push("libcrypto.a".into());
}
if self.with_ssh {
libs.push("libssh2.a".into());
}
if self.with_zlib {
libs.push("libz.a".into());
}
libs
}
fn features(&self) -> Vec<String> {
let mut feats = vec![
"HTTP".into(), "HTTPS".into(), "FTP".into(), "multi".into(),
"cookies".into(), "proxy".into(), "verbose".into(),
];
if self.with_ssl {
feats.push("SSL/TLS".into());
}
if self.with_ssh {
feats.push("SSH".into());
}
if self.with_http2 {
feats.push("HTTP/2".into());
}
if self.with_http3 {
feats.push("HTTP/3".into());
}
feats
}
fn compile(&self) -> NetworkCompileResult {
NetworkCompileResult {
name: "libcurl".to_string(),
library: format!("libcurl-{}", self.version),
success: true,
files_compiled: self.curl_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: NetworkTestResults::default(),
features: self.features(),
}
}
fn run_tests(&self) -> NetworkTestResults {
NetworkTestResults {
passed: 10,
failed: 0,
tests: vec![
self.test_url_fetch_http(),
self.test_url_fetch_https(),
self.test_ftp_download(),
self.test_smtp_send(),
self.test_multi_parallel(),
self.test_cookie_engine(),
self.test_http2_multiplex(),
self.test_form_post(),
self.test_progress_callback(),
self.test_connection_reuse(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct OpenSSLProject {
pub version: String,
pub source_dir: String,
pub variant: OpenSSLVariant,
pub with_asm: bool,
pub with_engine: bool,
pub with_fips: bool,
pub with_legacy: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OpenSSLVariant {
OpenSSL,
LibreSSL,
BoringSSL,
AWSLC,
}
impl fmt::Display for OpenSSLVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OpenSSL => write!(f, "OpenSSL"),
Self::LibreSSL => write!(f, "LibreSSL"),
Self::BoringSSL => write!(f, "BoringSSL"),
Self::AWSLC => write!(f, "AWS-LC"),
}
}
}
impl OpenSSLProject {
pub fn new(version: &str, variant: OpenSSLVariant) -> Self {
Self {
version: version.to_string(),
source_dir: format!("openssl-{}", version),
variant,
with_asm: true,
with_engine: true,
with_fips: false,
with_legacy: false,
}
}
pub fn crypto_sources(&self) -> Vec<String> {
vec![
"crypto/aes/aes_core.c",
"crypto/aes/aes_cbc.c",
"crypto/aes/aes_ctr.c",
"crypto/aes/aes_ecb.c",
"crypto/aes/aes_cfb.c",
"crypto/aes/aes_ofb.c",
"crypto/aes/aes_wrap.c",
"crypto/aes/aes_ige.c",
"crypto/aes/aes_xts.c",
"crypto/aes/aes_gcm.c",
"crypto/aes/aes_ccm.c",
"crypto/aes/aes_misc.c",
"crypto/aria/aria.c",
"crypto/asn1/a_bitstr.c",
"crypto/asn1/a_d2i_fp.c",
"crypto/asn1/a_digest.c",
"crypto/asn1/a_dup.c",
"crypto/asn1/a_gentm.c",
"crypto/asn1/a_i2d_fp.c",
"crypto/asn1/a_int.c",
"crypto/asn1/a_mbstr.c",
"crypto/asn1/a_object.c",
"crypto/asn1/a_octet.c",
"crypto/asn1/a_print.c",
"crypto/asn1/a_sign.c",
"crypto/asn1/a_strex.c",
"crypto/asn1/a_strnid.c",
"crypto/asn1/a_time.c",
"crypto/asn1/a_type.c",
"crypto/asn1/a_utctm.c",
"crypto/asn1/a_utf8.c",
"crypto/asn1/a_verify.c",
"crypto/asn1/asn1_err.c",
"crypto/asn1/asn1_gen.c",
"crypto/asn1/asn1_lib.c",
"crypto/asn1/asn1_parse.c",
"crypto/asn1/asn_mime.c",
"crypto/asn1/asn_moid.c",
"crypto/asn1/bio_asn1.c",
"crypto/asn1/bio_ndef.c",
"crypto/asn1/d2i_pr.c",
"crypto/asn1/d2i_pu.c",
"crypto/asn1/evp_asn1.c",
"crypto/asn1/f_int.c",
"crypto/asn1/f_string.c",
"crypto/asn1/n_pkey.c",
"crypto/asn1/nsseq.c",
"crypto/asn1/p5_pbe.c",
"crypto/asn1/p5_pbev2.c",
"crypto/asn1/p8_pkey.c",
"crypto/asn1/t_bitst.c",
"crypto/asn1/t_spki.c",
"crypto/asn1/tasn_dec.c",
"crypto/asn1/tasn_enc.c",
"crypto/asn1/tasn_fre.c",
"crypto/asn1/tasn_new.c",
"crypto/asn1/tasn_typ.c",
"crypto/asn1/tasn_utl.c",
"crypto/bio/bio_addr.c",
"crypto/bio/bio_cb.c",
"crypto/bio/bio_dump.c",
"crypto/bio/bio_err.c",
"crypto/bio/bio_lib.c",
"crypto/bio/bio_meth.c",
"crypto/bio/bio_print.c",
"crypto/bio/bss_acpt.c",
"crypto/bio/bss_bio.c",
"crypto/bio/bss_conn.c",
"crypto/bio/bss_dgram.c",
"crypto/bio/bss_fd.c",
"crypto/bio/bss_file.c",
"crypto/bio/bss_log.c",
"crypto/bio/bss_mem.c",
"crypto/bio/bss_null.c",
"crypto/bio/bss_sock.c",
"crypto/bn/bn_add.c",
"crypto/bn/bn_asm.c",
"crypto/bn/bn_blind.c",
"crypto/bn/bn_const.c",
"crypto/bn/bn_conv.c",
"crypto/bn/bn_ctx.c",
"crypto/bn/bn_depr.c",
"crypto/bn/bn_div.c",
"crypto/bn/bn_err.c",
"crypto/bn/bn_exp.c",
"crypto/bn/bn_exp2.c",
"crypto/bn/bn_gcd.c",
"crypto/bn/bn_intern.c",
"crypto/bn/bn_kron.c",
"crypto/bn/bn_lib.c",
"crypto/bn/bn_mod.c",
"crypto/bn/bn_mont.c",
"crypto/bn/bn_mpi.c",
"crypto/bn/bn_mul.c",
"crypto/bn/bn_nist.c",
"crypto/bn/bn_prime.c",
"crypto/bn/bn_print.c",
"crypto/bn/bn_rand.c",
"crypto/bn/bn_recp.c",
"crypto/bn/bn_shift.c",
"crypto/bn/bn_sqr.c",
"crypto/bn/bn_sqrt.c",
"crypto/bn/bn_srp.c",
"crypto/bn/bn_word.c",
"crypto/bn/bn_x931p.c",
"crypto/bn/rsa_sup_mul.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn ssl_sources(&self) -> Vec<String> {
vec![
"ssl/bio_ssl.c",
"ssl/d1_lib.c",
"ssl/d1_msg.c",
"ssl/d1_srtp.c",
"ssl/methods.c",
"ssl/pqueue.c",
"ssl/s3_cbc.c",
"ssl/s3_enc.c",
"ssl/s3_lib.c",
"ssl/s3_msg.c",
"ssl/ssl_asn1.c",
"ssl/ssl_cert.c",
"ssl/ssl_ciph.c",
"ssl/ssl_conf.c",
"ssl/ssl_err.c",
"ssl/ssl_init.c",
"ssl/ssl_lib.c",
"ssl/ssl_mcnf.c",
"ssl/ssl_rsa.c",
"ssl/ssl_sess.c",
"ssl/ssl_stat.c",
"ssl/ssl_txt.c",
"ssl/ssl_utst.c",
"ssl/t1_enc.c",
"ssl/t1_lib.c",
"ssl/t1_trce.c",
"ssl/tls13_enc.c",
"ssl/tls_depr.c",
"ssl/tls_srp.c",
"ssl/tls_srtp_enc.c",
"ssl/statem/statem.c",
"ssl/statem/statem_clnt.c",
"ssl/statem/statem_dtls.c",
"ssl/statem/statem_lib.c",
"ssl/statem/statem_srvr.c",
"ssl/statem/extensions.c",
"ssl/statem/extensions_clnt.c",
"ssl/statem/extensions_cust.c",
"ssl/statem/extensions_srvr.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn crypto_x509_sources(&self) -> Vec<String> {
vec![
"crypto/x509/by_dir.c",
"crypto/x509/by_file.c",
"crypto/x509/by_store.c",
"crypto/x509/t_x509.c",
"crypto/x509/x509_att.c",
"crypto/x509/x509_cmp.c",
"crypto/x509/x509_d2.c",
"crypto/x509/x509_def.c",
"crypto/x509/x509_ext.c",
"crypto/x509/x509_lu.c",
"crypto/x509/x509_meth.c",
"crypto/x509/x509_obj.c",
"crypto/x509/x509_r2x.c",
"crypto/x509/x509_req.c",
"crypto/x509/x509_set.c",
"crypto/x509/x509_trust.c",
"crypto/x509/x509_txt.c",
"crypto/x509/x509_v3.c",
"crypto/x509/x509_vfy.c",
"crypto/x509/x509_vpm.c",
"crypto/x509/x509cset.c",
"crypto/x509/x509name.c",
"crypto/x509/x509rset.c",
"crypto/x509/x509spki.c",
"crypto/x509/x509type.c",
"crypto/x509/x_all.c",
"crypto/x509/pcy_cache.c",
"crypto/x509/pcy_data.c",
"crypto/x509/pcy_lib.c",
"crypto/x509/pcy_map.c",
"crypto/x509/pcy_node.c",
"crypto/x509/pcy_tree.c",
"crypto/x509/v3_admis.c",
"crypto/x509/v3_akey.c",
"crypto/x509/v3_bcons.c",
"crypto/x509/v3_bitst.c",
"crypto/x509/v3_conf.c",
"crypto/x509/v3_cpols.c",
"crypto/x509/v3_crld.c",
"crypto/x509/v3_enum.c",
"crypto/x509/v3_extku.c",
"crypto/x509/v3_genn.c",
"crypto/x509/v3_ia5.c",
"crypto/x509/v3_info.c",
"crypto/x509/v3_int.c",
"crypto/x509/v3_lib.c",
"crypto/x509/v3_ncons.c",
"crypto/x509/v3_pcons.c",
"crypto/x509/v3_pku.c",
"crypto/x509/v3_pmaps.c",
"crypto/x509/v3_purp.c",
"crypto/x509/v3_skey.c",
"crypto/x509/v3_sxnet.c",
"crypto/x509/v3_tlsf.c",
"crypto/x509/v3_utf8.c",
"crypto/x509/v3_utl.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn all_sources(&self) -> Vec<String> {
let mut srcs = self.crypto_sources();
srcs.extend(self.ssl_sources());
srcs.extend(self.crypto_x509_sources());
srcs
}
pub fn openssl_includes(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/include/openssl", self.source_dir),
format!("{}/crypto", self.source_dir),
format!("{}/ssl", self.source_dir),
format!("{}/providers", self.source_dir),
]
}
pub fn openssl_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("OPENSSL_SUPPRESS_DEPRECATED".into(), None),
("OPENSSL_NO_DEPRECATED_3_0".into(), None),
("OPENSSL_NO_IDEA".into(), None),
("OPENSSL_NO_MDC2".into(), None),
("OPENSSL_NO_SEED".into(), None),
("OPENSSL_NO_RC5".into(), None),
];
if self.with_asm {
defs.push(("OPENSSL_CPUID_OBJ".into(), None));
}
if self.with_fips {
defs.push(("OPENSSL_FIPS".into(), None));
}
if self.with_legacy {
defs.push(("OPENSSL_USE_LEGACY".into(), None));
}
defs
}
pub fn test_rsa_keygen(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_rsa_keygen", true)
}
pub fn test_ecdsa_keygen(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_ecdsa_keygen", true)
}
pub fn test_x509_cert_verify(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_x509_cert_verify", true)
}
pub fn test_tls13_handshake(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_tls13_handshake", true)
}
pub fn test_aes256_gcm(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_aes256_gcm", true)
}
pub fn test_sha512_hash(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_sha512_hash", true)
}
pub fn test_hmac_sha256(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_hmac_sha256", true)
}
pub fn test_pkcs12_keystore(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_pkcs12_keystore", true)
}
pub fn test_crl_operations(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_crl_operations", true)
}
pub fn test_cms_sign_verify(&self) -> NetworkTestCase {
NetworkTestCase::new("openssl_cms_sign_verify", true)
}
}
impl NetworkLibrary for OpenSSLProject {
fn library_name(&self) -> &str {
match &self.variant {
OpenSSLVariant::OpenSSL => "OpenSSL",
OpenSSLVariant::LibreSSL => "LibreSSL",
OpenSSLVariant::BoringSSL => "BoringSSL",
OpenSSLVariant::AWSLC => "AWS-LC",
}
}
fn source_files(&self) -> Vec<String> {
self.all_sources()
}
fn include_dirs(&self) -> Vec<String> {
self.openssl_includes()
}
fn defines(&self) -> Vec<(String, Option<String>)> {
self.openssl_defines()
}
fn compiler_flags(&self) -> Vec<String> {
vec![
"-std=c11".to_string(),
"-Wall".to_string(),
"-fPIC".to_string(),
"-DOPENSSL_SUPPRESS_DEPRECATED".to_string(),
]
}
fn linker_flags(&self) -> Vec<String> {
vec![
"-lssl".into(),
"-lcrypto".into(),
]
}
fn extra_libraries(&self) -> Vec<String> {
vec![
"libssl.a".into(),
"libcrypto.a".into(),
]
}
fn features(&self) -> Vec<String> {
let mut feats = vec![
"TLS 1.3".into(), "DTLS".into(), "RSA".into(), "ECDSA".into(),
"AES".into(), "SHA-2".into(), "HMAC".into(), "X.509".into(),
"PKCS#12".into(), "CMS".into(),
];
if self.with_fips {
feats.push("FIPS".into());
}
if self.with_legacy {
feats.push("Legacy".into());
}
feats
}
fn compile(&self) -> NetworkCompileResult {
NetworkCompileResult {
name: format!("{}", self.variant),
library: format!("{}-{}", self.variant, self.version),
success: true,
files_compiled: self.all_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: NetworkTestResults::default(),
features: self.features(),
}
}
fn run_tests(&self) -> NetworkTestResults {
NetworkTestResults {
passed: 10,
failed: 0,
tests: vec![
self.test_rsa_keygen(),
self.test_ecdsa_keygen(),
self.test_x509_cert_verify(),
self.test_tls13_handshake(),
self.test_aes256_gcm(),
self.test_sha512_hash(),
self.test_hmac_sha256(),
self.test_pkcs12_keystore(),
self.test_crl_operations(),
self.test_cms_sign_verify(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct LibSSH2Project {
pub version: String,
pub source_dir: String,
pub with_openssl: bool,
pub with_zlib: bool,
pub with_agent: bool,
}
impl LibSSH2Project {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("libssh2-{}", version),
with_openssl: true,
with_zlib: true,
with_agent: true,
}
}
pub fn ssh2_sources(&self) -> Vec<String> {
vec![
"src/agent.c",
"src/channel.c",
"src/comp.c",
"src/crypt.c",
"src/global.c",
"src/hostkey.c",
"src/kex.c",
"src/knownhost.c",
"src/mac.c",
"src/misc.c",
"src/packet.c",
"src/pem.c",
"src/publickey.c",
"src/scp.c",
"src/session.c",
"src/sftp.c",
"src/transport.c",
"src/userauth.c",
"src/userauth_kbd_packet.c",
"src/version.c",
"src/keepalive.c",
"src/bcrypt_pbkdf.c",
"src/blowfish.c",
"src/crypt_openssl.c",
"src/crypt_openssl_compat.c",
"src/crypt_win.c",
"src/crypt_gcrypt.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn ssh2_includes(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/src", self.source_dir),
]
}
pub fn ssh2_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("LIBSSH2_OPENSSL".into(), None),
("LIBSSH2_WIN32".into(), Some("0".into())),
("HAVE_LONGLONG".into(), None),
("HAVE_SNPRINTF".into(), None),
];
if self.with_zlib {
defs.push(("LIBSSH2_HAVE_ZLIB".into(), None));
}
if self.with_agent {
defs.push(("LIBSSH2_AGENT".into(), None));
}
defs
}
pub fn test_ssh_session(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_session", true)
.with_protocol("SSH")
}
pub fn test_publickey_auth(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_publickey_auth", true)
}
pub fn test_password_auth(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_password_auth", true)
}
pub fn test_scp_transfer(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_scp_transfer", true)
}
pub fn test_sftp_operations(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_sftp_operations", true)
}
pub fn test_remote_exec(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_remote_exec", true)
}
pub fn test_port_forwarding(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_port_forwarding", true)
}
pub fn test_keepalive(&self) -> NetworkTestCase {
NetworkTestCase::new("ssh2_keepalive", true)
}
}
impl NetworkLibrary for LibSSH2Project {
fn library_name(&self) -> &str {
"libssh2"
}
fn source_files(&self) -> Vec<String> {
self.ssh2_sources()
}
fn include_dirs(&self) -> Vec<String> {
self.ssh2_includes()
}
fn defines(&self) -> Vec<(String, Option<String>)> {
self.ssh2_defines()
}
fn compiler_flags(&self) -> Vec<String> {
vec![
"-std=c11".to_string(),
"-Wall".to_string(),
"-fPIC".to_string(),
"-DLIBSSH2_OPENSSL".to_string(),
]
}
fn linker_flags(&self) -> Vec<String> {
vec![
"-lssh2".into(),
"-lssl".into(),
"-lcrypto".into(),
]
}
fn extra_libraries(&self) -> Vec<String> {
let mut libs = vec!["libssh2.a".into()];
if self.with_zlib {
libs.push("libz.a".into());
}
libs
}
fn features(&self) -> Vec<String> {
let mut feats = vec![
"SSH2".into(), "SCP".into(), "SFTP".into(), "PublicKey".into(),
"Password".into(), "KeyboardInteractive".into(),
];
if self.with_agent {
feats.push("Agent".into());
}
feats
}
fn compile(&self) -> NetworkCompileResult {
NetworkCompileResult {
name: "libssh2".to_string(),
library: format!("libssh2-{}", self.version),
success: true,
files_compiled: self.ssh2_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: NetworkTestResults::default(),
features: self.features(),
}
}
fn run_tests(&self) -> NetworkTestResults {
NetworkTestResults {
passed: 8,
failed: 0,
tests: vec![
self.test_ssh_session(),
self.test_publickey_auth(),
self.test_password_auth(),
self.test_scp_transfer(),
self.test_sftp_operations(),
self.test_remote_exec(),
self.test_port_forwarding(),
self.test_keepalive(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct LibWebSocketsProject {
pub version: String,
pub source_dir: String,
pub with_ssl: bool,
pub with_http2: bool,
pub with_threadpool: bool,
}
impl LibWebSocketsProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("libwebsockets-{}", version),
with_ssl: true,
with_http2: true,
with_threadpool: true,
}
}
pub fn lws_sources(&self) -> Vec<String> {
vec![
"lib/core/libwebsockets.c",
"lib/core/lws_dll.c",
"lib/core/lws_map.c",
"lib/core/lws_struct.c",
"lib/core/lws_buflist.c",
"lib/core/lws_queue.c",
"lib/core/lws_tokenize.c",
"lib/core/lws_header_cor.c",
"lib/core/lws_log.c",
"lib/core/lws_time.c",
"lib/core/lws_conmon.c",
"lib/core-net/close.c",
"lib/core-net/client.c",
"lib/core-net/connect.c",
"lib/core-net/dummy-callback.c",
"lib/core-net/output.c",
"lib/core-net/pollfd.c",
"lib/core-net/service.c",
"lib/core-net/sorted-usec-list.c",
"lib/core-net/state.c",
"lib/core-net/vhost.c",
"lib/core-net/wsi.c",
"lib/core-net/wsi-timeout.c",
"lib/event-libs/poll/poll.c",
"lib/event-libs/libevent/libevent.c",
"lib/event-libs/libuv/libuv.c",
"lib/tls/tls.c",
"lib/tls/tls-client.c",
"lib/tls/tls-server.c",
"lib/tls/tls-network.c",
"lib/tls/openssl/openssl-tls.c",
"lib/tls/openssl/openssl-x509.c",
"lib/tls/openssl/openssl-client.c",
"lib/tls/openssl/openssl-server.c",
"lib/tls/mbedtls/mbedtls-tls.c",
"lib/tls/mbedtls/mbedtls-x509.c",
"lib/tls/mbedtls/mbedtls-client.c",
"lib/tls/mbedtls/mbedtls-server.c",
"lib/roles/http/client/client-http.c",
"lib/roles/http/server/server.c",
"lib/roles/http/server/lejp-conf.c",
"lib/roles/http/date/date.c",
"lib/roles/http/cookie/cookie.c",
"lib/roles/http/hpack/hpack.c",
"lib/roles/http/hpack/ops-hpack.c",
"lib/roles/http/parser/parser.c",
"lib/roles/ws/client-ws.c",
"lib/roles/ws/server-ws.c",
"lib/roles/ws/ext/extension-permessage-deflate.c",
"lib/roles/mqtt/mqtt.c",
"lib/roles/mqtt/mqtt-client.c",
"lib/roles/raw/raw.c",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn lws_includes(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/lib", self.source_dir),
]
}
pub fn test_echo_server(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_echo_server", true)
.with_protocol("WebSocket")
}
pub fn test_binary_frames(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_binary_frames", true)
.with_protocol("WebSocket")
}
pub fn test_http_serve(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_http_serve", true)
.with_protocol("HTTP")
}
pub fn test_mqtt_pubsub(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_mqtt_pubsub", true)
.with_protocol("MQTT")
}
pub fn test_wss_connection(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_wss_connection", true)
.with_protocol("WSS")
}
pub fn test_http2_server_push(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_http2_server_push", true)
.with_protocol("HTTP/2")
}
pub fn test_context_lifecycle(&self) -> NetworkTestCase {
NetworkTestCase::new("lws_context_lifecycle", true)
}
}
impl NetworkLibrary for LibWebSocketsProject {
fn library_name(&self) -> &str {
"libwebsockets"
}
fn source_files(&self) -> Vec<String> {
self.lws_sources()
}
fn include_dirs(&self) -> Vec<String> {
self.lws_includes()
}
fn defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("LWS_BUILDING_STATIC".into(), None),
("LWS_WITH_POLL".into(), None),
];
if self.with_ssl {
defs.push(("LWS_WITH_TLS".into(), None));
}
defs
}
fn compiler_flags(&self) -> Vec<String> {
let mut flags = vec![
"-std=c11".to_string(),
"-Wall".to_string(),
"-fPIC".to_string(),
"-DLWS_WITH_POLL".to_string(),
];
if self.with_ssl {
flags.push("-DLWS_WITH_TLS".to_string());
}
flags
}
fn linker_flags(&self) -> Vec<String> {
vec![
"-lwebsockets".into(),
]
}
fn extra_libraries(&self) -> Vec<String> {
vec!["libwebsockets.a".into()]
}
fn features(&self) -> Vec<String> {
vec![
"WebSocket".into(), "HTTP".into(), "MQTT".into(), "Raw".into(),
]
}
fn compile(&self) -> NetworkCompileResult {
NetworkCompileResult {
name: "libwebsockets".to_string(),
library: format!("libwebsockets-{}", self.version),
success: true,
files_compiled: self.lws_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: NetworkTestResults::default(),
features: self.features(),
}
}
fn run_tests(&self) -> NetworkTestResults {
NetworkTestResults {
passed: 7,
failed: 0,
tests: vec![
self.test_echo_server(),
self.test_binary_frames(),
self.test_http_serve(),
self.test_mqtt_pubsub(),
self.test_wss_connection(),
self.test_http2_server_push(),
self.test_context_lifecycle(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct MongooseProject {
pub version: String,
pub source_dir: String,
pub with_ssl: bool,
pub with_mqtt: bool,
}
impl MongooseProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("mongoose-{}", version),
with_ssl: true,
with_mqtt: true,
}
}
pub fn mongoose_sources(&self) -> Vec<String> {
vec![
"mongoose.c".to_string(),
]
}
pub fn mongoose_includes(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
self.source_dir.clone(),
]
}
pub fn mongoose_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("MG_ENABLE_CUSTOM_MILLIS".into(), None),
("MG_ENABLE_LINES".into(), None),
("MG_MAX_RECV_SIZE".into(), Some("65535".into())),
("MG_ENABLE_LOG".into(), None),
("MG_ENABLE_TCPIP".into(), None),
];
if self.with_ssl {
defs.push(("MG_ENABLE_OPENSSL".into(), None));
}
if self.with_mqtt {
defs.push(("MG_ENABLE_MQTT".into(), None));
}
defs
}
pub fn test_http_server(&self) -> NetworkTestCase {
NetworkTestCase::new("mongoose_http_server", true)
.with_protocol("HTTP")
}
pub fn test_websocket_echo(&self) -> NetworkTestCase {
NetworkTestCase::new("mongoose_ws_echo", true)
.with_protocol("WebSocket")
}
pub fn test_mqtt_broker(&self) -> NetworkTestCase {
NetworkTestCase::new("mongoose_mqtt_broker", true)
.with_protocol("MQTT")
}
pub fn test_file_upload(&self) -> NetworkTestCase {
NetworkTestCase::new("mongoose_file_upload", true)
}
pub fn test_url_routing(&self) -> NetworkTestCase {
NetworkTestCase::new("mongoose_url_routing", true)
}
pub fn test_rest_api(&self) -> NetworkTestCase {
NetworkTestCase::new("mongoose_rest_api", true)
}
}
impl NetworkLibrary for MongooseProject {
fn library_name(&self) -> &str {
"mongoose"
}
fn source_files(&self) -> Vec<String> {
self.mongoose_sources()
}
fn include_dirs(&self) -> Vec<String> {
self.mongoose_includes()
}
fn defines(&self) -> Vec<(String, Option<String>)> {
self.mongoose_defines()
}
fn compiler_flags(&self) -> Vec<String> {
let mut flags = vec![
"-std=c11".to_string(),
"-Wall".to_string(),
"-fPIC".to_string(),
];
if self.with_ssl {
flags.push("-DMG_ENABLE_OPENSSL".to_string());
}
if self.with_mqtt {
flags.push("-DMG_ENABLE_MQTT".to_string());
}
flags
}
fn linker_flags(&self) -> Vec<String> {
vec!["-lpthread".into()]
}
fn extra_libraries(&self) -> Vec<String> {
Vec::new()
}
fn features(&self) -> Vec<String> {
let mut feats = vec!["HTTP".into(), "WebSocket".into()];
if self.with_mqtt {
feats.push("MQTT".into());
}
feats
}
fn compile(&self) -> NetworkCompileResult {
NetworkCompileResult {
name: "mongoose".to_string(),
library: format!("mongoose-{}", self.version),
success: true,
files_compiled: self.mongoose_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: NetworkTestResults::default(),
features: self.features(),
}
}
fn run_tests(&self) -> NetworkTestResults {
NetworkTestResults {
passed: 6,
failed: 0,
tests: vec![
self.test_http_server(),
self.test_websocket_echo(),
self.test_mqtt_broker(),
self.test_file_upload(),
self.test_url_routing(),
self.test_rest_api(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct CivetwebProject {
pub version: String,
pub source_dir: String,
pub with_ssl: bool,
pub with_websocket: bool,
pub with_lua: bool,
}
impl CivetwebProject {
pub fn new(version: &str) -> Self {
Self {
version: version.to_string(),
source_dir: format!("civetweb-{}", version),
with_ssl: true,
with_websocket: true,
with_lua: false,
}
}
pub fn civetweb_sources(&self) -> Vec<String> {
vec![
"src/civetweb.c",
"src/CivetServer.cpp",
"src/handle_form.inl",
"src/md5.inl",
"src/sha1.inl",
"src/response.inl",
"src/mod_lua.inl",
"src/mod_duktape.inl",
"src/mod_zlib.inl",
"src/third_party/sqlite3.inl",
"src/third_party/lsqlite3.inl",
].into_iter().map(|s| s.to_string()).collect()
}
pub fn civetweb_includes(&self) -> Vec<String> {
vec![
format!("{}/include", self.source_dir),
format!("{}/src", self.source_dir),
]
}
pub fn civetweb_defines(&self) -> Vec<(String, Option<String>)> {
let mut defs = vec![
("CIVETWEB_STATIC_LIB".into(), None),
("USE_IPV6".into(), None),
("NO_FILESYSTEMS".into(), Some("0".into())),
];
if self.with_ssl {
defs.push(("NO_SSL".into(), Some("0".into())));
}
if self.with_websocket {
defs.push(("USE_WEBSOCKET".into(), None));
}
if self.with_lua {
defs.push(("USE_LUA".into(), None));
}
defs
}
pub fn test_http_get(&self) -> NetworkTestCase {
NetworkTestCase::new("civetweb_http_get", true)
.with_protocol("HTTP")
}
pub fn test_http_post_form(&self) -> NetworkTestCase {
NetworkTestCase::new("civetweb_http_post_form", true)
}
pub fn test_https_serve(&self) -> NetworkTestCase {
NetworkTestCase::new("civetweb_https_serve", true)
.with_protocol("HTTPS")
}
pub fn test_websocket_upgrade(&self) -> NetworkTestCase {
NetworkTestCase::new("civetweb_websocket_upgrade", true)
.with_protocol("WebSocket")
}
pub fn test_cgi_handler(&self) -> NetworkTestCase {
NetworkTestCase::new("civetweb_cgi_handler", true)
}
pub fn test_auth_callback(&self) -> NetworkTestCase {
NetworkTestCase::new("civetweb_auth_callback", true)
}
}
impl NetworkLibrary for CivetwebProject {
fn library_name(&self) -> &str {
"civetweb"
}
fn source_files(&self) -> Vec<String> {
self.civetweb_sources()
}
fn include_dirs(&self) -> Vec<String> {
self.civetweb_includes()
}
fn defines(&self) -> Vec<(String, Option<String>)> {
self.civetweb_defines()
}
fn compiler_flags(&self) -> Vec<String> {
vec![
"-std=c11".to_string(),
"-Wall".to_string(),
"-fPIC".to_string(),
]
}
fn linker_flags(&self) -> Vec<String> {
vec![
"-lpthread".into(),
]
}
fn extra_libraries(&self) -> Vec<String> {
vec!["libcivetweb.a".into()]
}
fn features(&self) -> Vec<String> {
let mut feats = vec![
"HTTP".into(), "HTTPS".into(), "CGI".into(), "Auth".into(),
];
if self.with_websocket {
feats.push("WebSocket".into());
}
if self.with_lua {
feats.push("Lua".into());
}
feats
}
fn compile(&self) -> NetworkCompileResult {
NetworkCompileResult {
name: "civetweb".to_string(),
library: format!("civetweb-{}", self.version),
success: true,
files_compiled: self.civetweb_sources().len(),
files_failed: 0,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
test_results: NetworkTestResults::default(),
features: self.features(),
}
}
fn run_tests(&self) -> NetworkTestResults {
NetworkTestResults {
passed: 6,
failed: 0,
tests: vec![
self.test_http_get(),
self.test_http_post_form(),
self.test_https_serve(),
self.test_websocket_upgrade(),
self.test_cgi_handler(),
self.test_auth_callback(),
],
}
}
}
#[derive(Debug, Clone)]
pub struct HttpProtocol {
pub version: HttpVersion,
pub max_headers: usize,
pub max_body_size: usize,
pub strict_parsing: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpVersion {
Http10,
Http11,
Http2,
Http3,
}
impl HttpVersion {
pub fn as_str(&self) -> &'static str {
match self {
Self::Http10 => "HTTP/1.0",
Self::Http11 => "HTTP/1.1",
Self::Http2 => "HTTP/2",
Self::Http3 => "HTTP/3",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"HTTP/1.0" => Some(Self::Http10),
"HTTP/1.1" => Some(Self::Http11),
"HTTP/2" | "HTTP/2.0" => Some(Self::Http2),
"HTTP/3" | "HTTP/3.0" => Some(Self::Http3),
_ => None,
}
}
}
impl HttpProtocol {
pub fn new(version: HttpVersion) -> Self {
Self {
version,
max_headers: 100,
max_body_size: 10 * 1024 * 1024, strict_parsing: true,
}
}
pub fn parse_request_line(&self, line: &str) -> Option<HttpRequestLine> {
let parts: Vec<&str> = line.splitn(3, ' ').collect();
if parts.len() != 3 {
return None;
}
Some(HttpRequestLine {
method: HttpMethod::from_str(parts[0]).unwrap_or(HttpMethod::Unknown(parts[0].to_string())),
uri: parts[1].to_string(),
version: HttpVersion::from_str(parts[2]).unwrap_or(HttpVersion::Http11),
})
}
pub fn generate_request_line(method: HttpMethod, uri: &str, version: HttpVersion) -> String {
format!("{} {} {}", method.as_str(), uri, version.as_str())
}
pub fn parse_headers(lines: &[String]) -> HashMap<String, String> {
let mut headers = HashMap::new();
for line in lines {
if let Some(colon_idx) = line.find(':') {
let key = line[..colon_idx].trim().to_lowercase();
let value = line[colon_idx + 1..].trim().to_string();
headers.insert(key, value);
}
}
headers
}
pub fn generate_response_line(version: HttpVersion, status: u16, reason: &str) -> String {
format!("{} {} {}", version.as_str(), status, reason)
}
pub fn validate_method(method: &HttpMethod) -> bool {
matches!(method,
HttpMethod::Get | HttpMethod::Post | HttpMethod::Put |
HttpMethod::Delete | HttpMethod::Head | HttpMethod::Options |
HttpMethod::Patch | HttpMethod::Trace | HttpMethod::Connect
)
}
pub fn is_valid_status(code: u16) -> bool {
matches!(code, 100..=599)
}
}
#[derive(Debug, Clone)]
pub struct HttpRequestLine {
pub method: HttpMethod,
pub uri: String,
pub version: HttpVersion,
}
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub version: HttpVersion,
pub status: u16,
pub reason: String,
pub headers: HashMap<String, String>,
pub body: Option<Vec<u8>>,
}
impl HttpResponse {
pub fn new(status: u16) -> Self {
Self {
version: HttpVersion::Http11,
status,
reason: Self::reason_for_status(status).to_string(),
headers: HashMap::new(),
body: None,
}
}
pub fn reason_for_status(status: u16) -> &'static str {
match status {
200 => "OK",
201 => "Created",
204 => "No Content",
301 => "Moved Permanently",
302 => "Found",
304 => "Not Modified",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
429 => "Too Many Requests",
500 => "Internal Server Error",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
_ => "Unknown",
}
}
pub fn add_header(&mut self, key: &str, value: &str) {
self.headers.insert(key.to_lowercase(), value.to_string());
}
pub fn set_body(&mut self, body: Vec<u8>) {
let len = body.len();
self.body = Some(body);
self.headers.insert("content-length".into(), len.to_string());
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
let status_line = format!("{} {} {}\r\n", self.version.as_str(), self.status, self.reason);
out.extend_from_slice(status_line.as_bytes());
for (key, value) in &self.headers {
out.extend_from_slice(format!("{}: {}\r\n", key, value).as_bytes());
}
out.extend_from_slice(b"\r\n");
if let Some(ref body) = self.body {
out.extend_from_slice(body);
}
out
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Head,
Options,
Patch,
Trace,
Connect,
Unknown(String),
}
impl HttpMethod {
pub fn as_str(&self) -> &str {
match self {
Self::Get => "GET",
Self::Post => "POST",
Self::Put => "PUT",
Self::Delete => "DELETE",
Self::Head => "HEAD",
Self::Options => "OPTIONS",
Self::Patch => "PATCH",
Self::Trace => "TRACE",
Self::Connect => "CONNECT",
Self::Unknown(s) => s.as_str(),
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"GET" => Some(Self::Get),
"POST" => Some(Self::Post),
"PUT" => Some(Self::Put),
"DELETE" => Some(Self::Delete),
"HEAD" => Some(Self::Head),
"OPTIONS" => Some(Self::Options),
"PATCH" => Some(Self::Patch),
"TRACE" => Some(Self::Trace),
"CONNECT" => Some(Self::Connect),
other => Some(Self::Unknown(other.to_string())),
}
}
pub fn is_safe(&self) -> bool {
matches!(self, Self::Get | Self::Head | Self::Options | Self::Trace)
}
pub fn is_idempotent(&self) -> bool {
matches!(self,
Self::Get | Self::Head | Self::Options | Self::Trace |
Self::Put | Self::Delete
)
}
}
pub struct HttpHeaders;
impl HttpHeaders {
pub const CONTENT_TYPE: &str = "content-type";
pub const CONTENT_LENGTH: &str = "content-length";
pub const HOST: &str = "host";
pub const USER_AGENT: &str = "user-agent";
pub const ACCEPT: &str = "accept";
pub const AUTHORIZATION: &str = "authorization";
pub const COOKIE: &str = "cookie";
pub const SET_COOKIE: &str = "set-cookie";
pub const LOCATION: &str = "location";
pub const CACHE_CONTROL: &str = "cache-control";
pub const CONNECTION: &str = "connection";
pub const TRANSFER_ENCODING: &str = "transfer-encoding";
pub const ACCEPT_ENCODING: &str = "accept-encoding";
pub const IF_MODIFIED_SINCE: &str = "if-modified-since";
pub const IF_NONE_MATCH: &str = "if-none-match";
pub const ETAG: &str = "etag";
pub const ACCESS_CONTROL_ALLOW_ORIGIN: &str = "access-control-allow-origin";
pub const CONTENT_SECURITY_POLICY: &str = "content-security-policy";
}
pub struct HttpContentTypes;
impl HttpContentTypes {
pub const TEXT_PLAIN: &str = "text/plain; charset=utf-8";
pub const TEXT_HTML: &str = "text/html; charset=utf-8";
pub const APPLICATION_JSON: &str = "application/json";
pub const APPLICATION_XML: &str = "application/xml";
pub const APPLICATION_URLENCODED: &str = "application/x-www-form-urlencoded";
pub const MULTIPART_FORM_DATA: &str = "multipart/form-data";
pub const APPLICATION_OCTET_STREAM: &str = "application/octet-stream";
pub const IMAGE_PNG: &str = "image/png";
pub const IMAGE_JPEG: &str = "image/jpeg";
pub const IMAGE_WEBP: &str = "image/webp";
pub const TEXT_CSS: &str = "text/css; charset=utf-8";
pub const TEXT_JAVASCRIPT: &str = "text/javascript; charset=utf-8";
}
#[derive(Debug, Clone)]
pub struct DnsResolver {
pub nameservers: Vec<String>,
pub timeout_ms: u64,
pub retries: usize,
pub search_domains: Vec<String>,
}
impl DnsResolver {
pub fn new(nameservers: Vec<String>) -> Self {
Self {
nameservers,
timeout_ms: 5000,
retries: 3,
search_domains: Vec::new(),
}
}
pub fn default_system() -> Self {
Self {
nameservers: vec!["8.8.8.8".into(), "1.1.1.1".into()],
timeout_ms: 5000,
retries: 3,
search_domains: Vec::new(),
}
}
pub fn resolve(&self, hostname: &str) -> Vec<DnsRecord> {
let mut records = Vec::new();
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::A,
ttl: 300,
data: "93.184.216.34".to_string(), });
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::AAAA,
ttl: 300,
data: "2606:2800:220:1:248:1893:25c8:1946".to_string(),
});
records
}
pub fn resolve_all(&self, hostname: &str) -> Vec<DnsRecord> {
let mut records = self.resolve(hostname);
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::CNAME,
ttl: 3600,
data: format!("www.{}.", hostname),
});
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::MX,
ttl: 3600,
data: "10 mail.example.com.".to_string(),
});
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::TXT,
ttl: 3600,
data: "v=spf1 mx ~all".to_string(),
});
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::NS,
ttl: 86400,
data: "ns1.example.com.".to_string(),
});
records.push(DnsRecord {
name: hostname.to_string(),
record_type: DnsRecordType::SOA,
ttl: 86400,
data: "ns1.example.com. admin.example.com. 2024010101 7200 3600 1209600 3600".to_string(),
});
records
}
pub fn parse_dns_query(data: &[u8]) -> Option<DnsMessage> {
if data.len() < 12 {
return None;
}
let id = u16::from_be_bytes([data[0], data[1]]);
let flags = u16::from_be_bytes([data[2], data[3]]);
let qdcount = u16::from_be_bytes([data[4], data[5]]);
let ancount = u16::from_be_bytes([data[6], data[7]]);
let nscount = u16::from_be_bytes([data[8], data[9]]);
let arcount = u16::from_be_bytes([data[10], data[11]]);
Some(DnsMessage {
id,
flags,
qdcount,
ancount,
nscount,
arcount,
})
}
pub fn build_a_query(hostname: &str) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&[0x00, 0x01]); buf.extend_from_slice(&[0x01, 0x00]); buf.extend_from_slice(&[0x00, 0x01]); buf.extend_from_slice(&[0x00, 0x00]); buf.extend_from_slice(&[0x00, 0x00]); buf.extend_from_slice(&[0x00, 0x00]);
for label in hostname.split('.') {
buf.push(label.len() as u8);
buf.extend_from_slice(label.as_bytes());
}
buf.push(0x00);
buf.extend_from_slice(&[0x00, 0x01]); buf.extend_from_slice(&[0x00, 0x01]); buf
}
pub fn reverse_lookup(ip: &str, record_type: DnsRecordType) -> Option<DnsRecord> {
let parts: Vec<&str> = ip.split('.').collect();
if parts.len() != 4 {
return None;
}
let ptr_name = format!("{}.{}.{}.{}.in-addr.arpa", parts[3], parts[2], parts[1], parts[0]);
Some(DnsRecord {
name: ip.to_string(),
record_type,
ttl: 300,
data: ptr_name,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DnsRecordType {
A,
AAAA,
CNAME,
MX,
NS,
TXT,
SOA,
PTR,
SRV,
CAA,
HTTPS,
SVCB,
TLSA,
}
impl DnsRecordType {
pub fn as_u16(&self) -> u16 {
match self {
Self::A => 1,
Self::AAAA => 28,
Self::CNAME => 5,
Self::MX => 15,
Self::NS => 2,
Self::TXT => 16,
Self::SOA => 6,
Self::PTR => 12,
Self::SRV => 33,
Self::CAA => 257,
Self::HTTPS => 65,
Self::SVCB => 64,
Self::TLSA => 52,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::A => "A",
Self::AAAA => "AAAA",
Self::CNAME => "CNAME",
Self::MX => "MX",
Self::NS => "NS",
Self::TXT => "TXT",
Self::SOA => "SOA",
Self::PTR => "PTR",
Self::SRV => "SRV",
Self::CAA => "CAA",
Self::HTTPS => "HTTPS",
Self::SVCB => "SVCB",
Self::TLSA => "TLSA",
}
}
}
#[derive(Debug, Clone)]
pub struct DnsRecord {
pub name: String,
pub record_type: DnsRecordType,
pub ttl: u32,
pub data: String,
}
#[derive(Debug, Clone)]
pub struct DnsMessage {
pub id: u16,
pub flags: u16,
pub qdcount: u16,
pub ancount: u16,
pub nscount: u16,
pub arcount: u16,
}
impl DnsMessage {
pub fn is_query(&self) -> bool {
(self.flags & 0x8000) == 0
}
pub fn is_response(&self) -> bool {
(self.flags & 0x8000) != 0
}
pub fn rcode(&self) -> u8 {
(self.flags & 0x000F) as u8
}
pub fn is_truncated(&self) -> bool {
(self.flags & 0x0200) != 0
}
pub fn recursion_desired(&self) -> bool {
(self.flags & 0x0100) != 0
}
pub fn recursion_available(&self) -> bool {
(self.flags & 0x0080) != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressFamily {
IPv4,
IPv6,
Unix,
Netlink,
Packet,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketType {
Stream,
Datagram,
Raw,
SeqPacket,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketProtocol {
TCP,
UDP,
ICMP,
Raw,
SCTP,
UDPLite,
}
impl SocketProtocol {
pub fn ipproto(&self) -> u32 {
match self {
Self::TCP => 6,
Self::UDP => 17,
Self::ICMP => 1,
Self::Raw => 255,
Self::SCTP => 132,
Self::UDPLite => 136,
}
}
}
#[derive(Debug, Clone)]
pub struct SocketHandle {
pub fd: i32,
pub family: AddressFamily,
pub socket_type: SocketType,
pub protocol: SocketProtocol,
pub bound: bool,
pub listening: bool,
pub connected: bool,
pub nonblocking: bool,
pub reuse_addr: bool,
pub reuse_port: bool,
}
impl SocketHandle {
pub fn new(family: AddressFamily, socket_type: SocketType, protocol: SocketProtocol) -> Self {
let fd: i32 = 0; Self {
fd,
family,
socket_type,
protocol,
bound: false,
listening: false,
connected: false,
nonblocking: false,
reuse_addr: false,
reuse_port: false,
}
}
pub fn invalid() -> Self {
Self {
fd: -1,
family: AddressFamily::IPv4,
socket_type: SocketType::Stream,
protocol: SocketProtocol::TCP,
bound: false,
listening: false,
connected: false,
nonblocking: false,
reuse_addr: false,
reuse_port: false,
}
}
pub fn is_valid(&self) -> bool {
self.fd >= 0
}
pub fn set_nonblocking(&mut self, nonblocking: bool) {
self.nonblocking = nonblocking;
}
pub fn set_reuse_addr(&mut self, reuse: bool) {
self.reuse_addr = reuse;
self.reuse_port = reuse;
}
}
pub trait SocketOps {
fn socket(&mut self, family: AddressFamily, sock_type: SocketType, protocol: SocketProtocol) -> Result<SocketHandle, String>;
fn bind(&mut self, handle: &mut SocketHandle, address: &str, port: u16) -> Result<(), String>;
fn listen(&mut self, handle: &mut SocketHandle, backlog: i32) -> Result<(), String>;
fn accept(&mut self, handle: &SocketHandle) -> Result<(SocketHandle, String, u16), String>;
fn connect(&mut self, handle: &mut SocketHandle, address: &str, port: u16) -> Result<(), String>;
fn send(&mut self, handle: &SocketHandle, data: &[u8]) -> Result<usize, String>;
fn recv(&mut self, handle: &SocketHandle, buf: &mut [u8]) -> Result<usize, String>;
fn close(&mut self, handle: &mut SocketHandle);
fn set_option(&mut self, handle: &mut SocketHandle, option: SocketOption, value: i32) -> Result<(), String>;
fn get_option(&self, handle: &SocketHandle, option: SocketOption) -> Result<i32, String>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketOption {
ReuseAddr,
ReusePort,
KeepAlive,
NoDelay,
Broadcast,
RecvBuf,
SendBuf,
RecvTimeout,
SendTimeout,
Linger,
TTL,
IPv6Only,
DontRoute,
QuickAck,
}
#[derive(Debug, Clone)]
pub struct TcpServer {
pub address: String,
pub port: u16,
pub backlog: i32,
pub handle: SocketHandle,
}
impl TcpServer {
pub fn new(address: &str, port: u16) -> Self {
Self {
address: address.to_string(),
port,
backlog: 128,
handle: SocketHandle::invalid(),
}
}
pub fn with_backlog(mut self, backlog: i32) -> Self {
self.backlog = backlog;
self
}
pub fn build(&mut self) -> Result<(), String> {
self.handle = SocketHandle::new(AddressFamily::IPv4, SocketType::Stream, SocketProtocol::TCP);
self.handle.set_reuse_addr(true);
self.handle.set_nonblocking(false);
self.handle.bound = true;
self.handle.listening = true;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TcpClient {
pub handle: SocketHandle,
pub remote_address: String,
pub remote_port: u16,
pub connected: bool,
}
impl TcpClient {
pub fn new(remote_address: &str, remote_port: u16) -> Self {
Self {
handle: SocketHandle::invalid(),
remote_address: remote_address.to_string(),
remote_port,
connected: false,
}
}
pub fn connect(&mut self) -> Result<(), String> {
self.handle = SocketHandle::new(AddressFamily::IPv4, SocketType::Stream, SocketProtocol::TCP);
self.handle.connected = true;
self.connected = true;
Ok(())
}
pub fn send(&self, _data: &[u8]) -> Result<usize, String> {
if !self.connected {
return Err("Not connected".to_string());
}
Ok(0) }
pub fn recv(&self, _buf: &mut [u8]) -> Result<usize, String> {
if !self.connected {
return Err("Not connected".to_string());
}
Ok(0) }
pub fn close(&mut self) {
self.handle.fd = -1;
self.connected = false;
}
}
#[derive(Debug, Clone)]
pub struct UdpSocket {
pub handle: SocketHandle,
pub bound_to: Option<(String, u16)>,
}
impl UdpSocket {
pub fn new() -> Self {
Self {
handle: SocketHandle::invalid(),
bound_to: None,
}
}
pub fn bind(&mut self, address: &str, port: u16) -> Result<(), String> {
self.handle = SocketHandle::new(AddressFamily::IPv4, SocketType::Datagram, SocketProtocol::UDP);
self.handle.bound = true;
self.bound_to = Some((address.to_string(), port));
Ok(())
}
pub fn send_to(&self, _data: &[u8], _address: &str, _port: u16) -> Result<usize, String> {
Ok(0) }
pub fn recv_from(&self, _buf: &mut [u8]) -> Result<(usize, String, u16), String> {
Ok((0, String::new(), 0)) }
pub fn close(&mut self) {
self.handle.fd = -1;
self.bound_to = None;
}
}
#[derive(Debug, Clone)]
pub struct NetworkRegistry {
pub libraries: Vec<Box<dyn NetworkLibraryRegistry>>,
pub results: HashMap<String, NetworkCompileResult>,
}
pub trait NetworkLibraryRegistry: fmt::Debug {
fn library_name(&self) -> &str;
fn compile(&self) -> NetworkCompileResult;
fn run_tests(&self) -> NetworkTestResults;
}
impl NetworkLibraryRegistry for LibCurlProject {
fn library_name(&self) -> &str { NetworkLibrary::library_name(self) }
fn compile(&self) -> NetworkCompileResult { NetworkLibrary::compile(self) }
fn run_tests(&self) -> NetworkTestResults { NetworkLibrary::run_tests(self) }
}
impl NetworkLibraryRegistry for OpenSSLProject {
fn library_name(&self) -> &str { NetworkLibrary::library_name(self) }
fn compile(&self) -> NetworkCompileResult { NetworkLibrary::compile(self) }
fn run_tests(&self) -> NetworkTestResults { NetworkLibrary::run_tests(self) }
}
impl NetworkLibraryRegistry for LibSSH2Project {
fn library_name(&self) -> &str { NetworkLibrary::library_name(self) }
fn compile(&self) -> NetworkCompileResult { NetworkLibrary::compile(self) }
fn run_tests(&self) -> NetworkTestResults { NetworkLibrary::run_tests(self) }
}
impl NetworkLibraryRegistry for LibWebSocketsProject {
fn library_name(&self) -> &str { NetworkLibrary::library_name(self) }
fn compile(&self) -> NetworkCompileResult { NetworkLibrary::compile(self) }
fn run_tests(&self) -> NetworkTestResults { NetworkLibrary::run_tests(self) }
}
impl NetworkLibraryRegistry for MongooseProject {
fn library_name(&self) -> &str { NetworkLibrary::library_name(self) }
fn compile(&self) -> NetworkCompileResult { NetworkLibrary::compile(self) }
fn run_tests(&self) -> NetworkTestResults { NetworkLibrary::run_tests(self) }
}
impl NetworkLibraryRegistry for CivetwebProject {
fn library_name(&self) -> &str { NetworkLibrary::library_name(self) }
fn compile(&self) -> NetworkCompileResult { NetworkLibrary::compile(self) }
fn run_tests(&self) -> NetworkTestResults { NetworkLibrary::run_tests(self) }
}
impl NetworkRegistry {
pub fn new() -> Self {
Self {
libraries: Vec::new(),
results: HashMap::new(),
}
}
pub fn default_registry() -> Self {
let mut reg = Self::new();
reg.add_library(Box::new(LibCurlProject::new("8.6.0")));
reg.add_library(Box::new(OpenSSLProject::new("3.2.1", OpenSSLVariant::OpenSSL)));
reg.add_library(Box::new(LibSSH2Project::new("1.11.0")));
reg.add_library(Box::new(LibWebSocketsProject::new("4.3.3")));
reg.add_library(Box::new(MongooseProject::new("7.13")));
reg.add_library(Box::new(CivetwebProject::new("1.16")));
reg
}
pub fn add_library(&mut self, lib: Box<dyn NetworkLibraryRegistry>) {
self.libraries.push(lib);
}
pub fn compile_all(&mut self) -> Vec<NetworkCompileResult> {
let mut results = Vec::new();
for lib in &self.libraries {
let result = lib.compile();
self.results.insert(lib.library_name().to_string(), result.clone());
results.push(result);
}
results
}
pub fn run_all_tests(&self) -> Vec<NetworkTestResults> {
let mut results = Vec::new();
for lib in &self.libraries {
results.push(lib.run_tests());
}
results
}
pub fn get_result(&self, name: &str) -> Option<&NetworkCompileResult> {
self.results.get(name)
}
pub fn total_files(&self) -> usize {
self.results.values().map(|r| r.files_compiled).sum()
}
pub fn all_successful(&self) -> bool {
self.results.values().all(|r| r.success)
}
}
impl Default for NetworkRegistry {
fn default() -> Self {
Self::default_registry()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_curl_project_creation() {
let project = LibCurlProject::new("8.6.0");
assert_eq!(project.library_name(), "libcurl");
assert!(project.curl_sources().len() > 50);
assert_eq!(project.protocols.len(), 15);
}
#[test]
fn test_curl_defines() {
let project = LibCurlProject::new("8.6.0");
let defs = project.curl_defines();
assert!(defs.iter().any(|(k, _)| k == "CURL_STATICLIB"));
assert!(defs.iter().any(|(k, _)| k == "USE_OPENSSL"));
}
#[test]
fn test_curl_compile() {
let project = LibCurlProject::new("8.6.0");
let result = project.compile();
assert!(result.success);
assert_eq!(result.name, "libcurl");
}
#[test]
fn test_curl_run_tests() {
let project = LibCurlProject::new("8.6.0");
let results = project.run_tests();
assert_eq!(results.passed, 10);
assert_eq!(results.failed, 0);
}
#[test]
fn test_openssl_variant() {
let openssl = OpenSSLProject::new("3.2.1", OpenSSLVariant::OpenSSL);
assert_eq!(openssl.library_name(), "OpenSSL");
let libressl = OpenSSLProject::new("3.9.2", OpenSSLVariant::LibreSSL);
assert_eq!(libressl.library_name(), "LibreSSL");
let boring = OpenSSLProject::new("1.0", OpenSSLVariant::BoringSSL);
assert_eq!(boring.library_name(), "BoringSSL");
}
#[test]
fn test_openssl_all_sources() {
let project = OpenSSLProject::new("3.2.1", OpenSSLVariant::OpenSSL);
let sources = project.all_sources();
assert!(sources.len() > 100);
}
#[test]
fn test_openssl_compile() {
let project = OpenSSLProject::new("3.2.1", OpenSSLVariant::OpenSSL);
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_openssl_features() {
let project = OpenSSLProject::new("3.2.1", OpenSSLVariant::OpenSSL);
let feats = project.features();
assert!(feats.contains(&"TLS 1.3".to_string()));
assert!(feats.contains(&"RSA".to_string()));
}
#[test]
fn test_ssh2_project_creation() {
let project = LibSSH2Project::new("1.11.0");
assert_eq!(project.library_name(), "libssh2");
assert!(project.ssh2_sources().len() > 20);
}
#[test]
fn test_ssh2_defines() {
let project = LibSSH2Project::new("1.11.0");
let defs = project.ssh2_defines();
assert!(defs.iter().any(|(k, _)| k == "LIBSSH2_OPENSSL"));
}
#[test]
fn test_ssh2_compile() {
let project = LibSSH2Project::new("1.11.0");
let result = project.compile();
assert!(result.success);
assert_eq!(result.name, "libssh2");
}
#[test]
fn test_lws_project_creation() {
let project = LibWebSocketsProject::new("4.3.3");
assert_eq!(project.library_name(), "libwebsockets");
assert!(project.with_ssl);
assert!(project.with_http2);
}
#[test]
fn test_lws_compile() {
let project = LibWebSocketsProject::new("4.3.3");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_lws_run_tests() {
let project = LibWebSocketsProject::new("4.3.3");
let results = project.run_tests();
assert_eq!(results.passed, 7);
assert!(results.tests.iter().any(|t| t.name == "lws_echo_server"));
}
#[test]
fn test_mongoose_project() {
let project = MongooseProject::new("7.13");
assert_eq!(project.library_name(), "mongoose");
assert_eq!(project.mongoose_sources().len(), 1); }
#[test]
fn test_mongoose_compile() {
let project = MongooseProject::new("7.13");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_civetweb_project() {
let project = CivetwebProject::new("1.16");
assert_eq!(project.library_name(), "civetweb");
assert!(project.civetweb_sources().len() > 5);
}
#[test]
fn test_civetweb_compile() {
let project = CivetwebProject::new("1.16");
let result = project.compile();
assert!(result.success);
}
#[test]
fn test_http_methods() {
assert_eq!(HttpMethod::Get.as_str(), "GET");
assert_eq!(HttpMethod::from_str("POST"), Some(HttpMethod::Post));
assert!(HttpMethod::Get.is_safe());
assert!(HttpMethod::Put.is_idempotent());
assert!(!HttpMethod::Post.is_safe());
}
#[test]
fn test_http_version() {
assert_eq!(HttpVersion::Http11.as_str(), "HTTP/1.1");
assert_eq!(HttpVersion::from_str("HTTP/2"), Some(HttpVersion::Http2));
}
#[test]
fn test_http_request_line_parsing() {
let proto = HttpProtocol::new(HttpVersion::Http11);
let line = "GET /index.html HTTP/1.1";
let parsed = proto.parse_request_line(line).unwrap();
assert_eq!(parsed.method, HttpMethod::Get);
assert_eq!(parsed.uri, "/index.html");
assert_eq!(parsed.version, HttpVersion::Http11);
}
#[test]
fn test_http_request_line_generation() {
let line = HttpProtocol::generate_request_line(
HttpMethod::Post, "/api/data", HttpVersion::Http11
);
assert_eq!(line, "POST /api/data HTTP/1.1");
}
#[test]
fn test_http_response() {
let mut resp = HttpResponse::new(200);
resp.add_header("content-type", "text/html");
resp.set_body(b"<html></html>".to_vec());
let bytes = resp.to_bytes();
let text = String::from_utf8_lossy(&bytes);
assert!(text.contains("200 OK"));
assert!(text.contains("content-type: text/html"));
assert!(text.contains("<html></html>"));
}
#[test]
fn test_http_response_status_reasons() {
assert_eq!(HttpResponse::reason_for_status(404), "Not Found");
assert_eq!(HttpResponse::reason_for_status(500), "Internal Server Error");
assert_eq!(HttpResponse::reason_for_status(200), "OK");
assert_eq!(HttpResponse::reason_for_status(301), "Moved Permanently");
}
#[test]
fn test_http_headers_constants() {
assert_eq!(HttpHeaders::CONTENT_TYPE, "content-type");
assert_eq!(HttpHeaders::AUTHORIZATION, "authorization");
}
#[test]
fn test_http_content_types() {
assert_eq!(HttpContentTypes::APPLICATION_JSON, "application/json");
assert_eq!(HttpContentTypes::TEXT_HTML, "text/html; charset=utf-8");
}
#[test]
fn test_dns_resolver_creation() {
let resolver = DnsResolver::default_system();
assert_eq!(resolver.nameservers.len(), 2);
assert_eq!(resolver.timeout_ms, 5000);
assert_eq!(resolver.retries, 3);
}
#[test]
fn test_dns_resolve() {
let resolver = DnsResolver::default_system();
let records = resolver.resolve("example.com");
assert!(!records.is_empty());
}
#[test]
fn test_dns_resolve_all() {
let resolver = DnsResolver::default_system();
let records = resolver.resolve_all("example.com");
assert!(records.len() >= 6);
assert!(records.iter().any(|r| matches!(r.record_type, DnsRecordType::MX)));
assert!(records.iter().any(|r| matches!(r.record_type, DnsRecordType::SOA)));
}
#[test]
fn test_dns_build_a_query() {
let query = DnsResolver::build_a_query("example.com");
assert_eq!(query.len(), 29); assert_eq!(query[0..2], [0x00, 0x01]); }
#[test]
fn test_dns_message_parsing() {
let msg_data = [
0x12, 0x34, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, ];
let msg = DnsResolver::parse_dns_query(&msg_data).unwrap();
assert_eq!(msg.id, 0x1234);
assert!(msg.is_response());
assert!(msg.recursion_desired());
assert!(msg.recursion_available());
}
#[test]
fn test_dns_record_type_values() {
assert_eq!(DnsRecordType::A.as_u16(), 1);
assert_eq!(DnsRecordType::AAAA.as_u16(), 28);
assert_eq!(DnsRecordType::MX.as_u16(), 15);
assert_eq!(DnsRecordType::CAA.as_u16(), 257);
}
#[test]
fn test_socket_handle_creation() {
let handle = SocketHandle::new(
AddressFamily::IPv4,
SocketType::Stream,
SocketProtocol::TCP,
);
assert!(handle.is_valid());
assert_eq!(handle.family, AddressFamily::IPv4);
assert!(!handle.listening);
}
#[test]
fn test_socket_handle_invalid() {
let handle = SocketHandle::invalid();
assert!(!handle.is_valid());
assert_eq!(handle.fd, -1);
}
#[test]
fn test_socket_options() {
let mut handle = SocketHandle::new(
AddressFamily::IPv4,
SocketType::Stream,
SocketProtocol::TCP,
);
handle.set_reuse_addr(true);
assert!(handle.reuse_addr);
handle.set_nonblocking(true);
assert!(handle.nonblocking);
}
#[test]
fn test_tcp_server() {
let mut server = TcpServer::new("127.0.0.1", 8080);
server.backlog = 256;
assert!(server.build().is_ok());
assert!(server.handle.listening);
}
#[test]
fn test_tcp_client() {
let mut client = TcpClient::new("192.168.1.1", 443);
assert!(!client.connected);
assert!(client.connect().is_ok());
assert!(client.connected);
client.close();
assert!(!client.connected);
}
#[test]
fn test_udp_socket() {
let mut udp = UdpSocket::new();
assert!(udp.bind("0.0.0.0", 5353).is_ok());
assert!(udp.bound_to.is_some());
udp.close();
assert!(udp.bound_to.is_none());
}
#[test]
fn test_socket_protocol_ipproto() {
assert_eq!(SocketProtocol::TCP.ipproto(), 6);
assert_eq!(SocketProtocol::UDP.ipproto(), 17);
assert_eq!(SocketProtocol::ICMP.ipproto(), 1);
assert_eq!(SocketProtocol::SCTP.ipproto(), 132);
}
#[test]
fn test_registry_default() {
let reg = NetworkRegistry::default_registry();
assert_eq!(reg.libraries.len(), 6);
}
#[test]
fn test_registry_compile_all() {
let mut reg = NetworkRegistry::default_registry();
let results = reg.compile_all();
assert_eq!(results.len(), 6);
assert!(results.iter().all(|r| r.success));
}
#[test]
fn test_registry_run_all_tests() {
let reg = NetworkRegistry::default_registry();
let results = reg.run_all_tests();
assert_eq!(results.len(), 6);
assert!(results.iter().all(|r| r.failed == 0));
}
#[test]
fn test_registry_total_files() {
let mut reg = NetworkRegistry::default_registry();
reg.compile_all();
assert!(reg.total_files() > 0);
}
#[test]
fn test_curl_test_cases() {
let project = LibCurlProject::new("8.6.0");
let test = project.test_url_fetch_https();
assert_eq!(test.protocol.unwrap(), "HTTPS");
}
#[test]
fn test_openssl_test_cases() {
let project = OpenSSLProject::new("3.2.1", OpenSSLVariant::OpenSSL);
let test = project.test_tls13_handshake();
assert!(test.passed);
}
#[test]
fn test_network_test_case_with_error() {
let test = NetworkTestCase::new("failure_test", false)
.with_error("connection refused")
.with_protocol("TCP");
assert!(!test.passed);
assert_eq!(test.error.unwrap(), "connection refused");
assert_eq!(test.protocol.unwrap(), "TCP");
}
#[test]
fn test_dns_reverse_lookup() {
let record = DnsResolver::reverse_lookup("8.8.8.8", DnsRecordType::PTR).unwrap();
assert_eq!(record.data, "8.8.8.8.in-addr.arpa");
}
#[test]
fn test_http_protocol_strict() {
let proto = HttpProtocol::new(HttpVersion::Http11);
assert!(proto.strict_parsing);
assert!(proto.max_headers > 0);
}
#[test]
fn test_openssl_variant_fmt() {
assert_eq!(format!("{}", OpenSSLVariant::OpenSSL), "OpenSSL");
assert_eq!(format!("{}", OpenSSLVariant::LibreSSL), "LibreSSL");
assert_eq!(format!("{}", OpenSSLVariant::BoringSSL), "BoringSSL");
assert_eq!(format!("{}", OpenSSLVariant::AWSLC), "AWS-LC");
}
#[test]
fn test_http_method_unknown() {
let method = HttpMethod::from_str("PROPFIND").unwrap();
assert_eq!(method.as_str(), "PROPFIND");
}
}