From 4ff6ec1100a5f84f7c64c0fe12039c8db9ef04a2 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 18:38:55 +0100 Subject: [PATCH 01/19] =?UTF-8?q?S3.7=20phase=201=20=E2=80=94=20BDD=20TLS?= =?UTF-8?q?=20infrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add syslog-ng TLS listener on port 6514 alongside existing UDP/TCP, mount the test CA/server-cert material into the syslog-ng container, scaffold a @wip tls_transport.feature for later phases to light up. - Bdd/syslog-ng/tls/ — self-signed 10-year CA + server cert (SANs include syslog-ng / localhost / 127.0.0.1), generate.sh for regen, README marking material test-only - syslog-ng-full.conf and syslog-ng.conf: +tls source on 6514 with peer-verify(optional-untrusted), +d_file_tls destination, +log route - syslog-ng-udp-only.conf: unchanged (remains the streams-offline config) - ci/docker-compose.bdd.yml and .devcontainer/docker-compose.yml: mount Bdd/syslog-ng/tls read-only into syslog-ng container; devcontainer exposes 6514 on host - Bdd/features/tls_transport.feature: placeholder @tls @buffered @wip Existing 15 BDD features / 32 scenarios / 168 steps still green; new @wip feature correctly skipped. --- .devcontainer/docker-compose.yml | 2 ++ Bdd/features/tls_transport.feature | 9 +++++++ Bdd/syslog-ng/syslog-ng-full.conf | 30 +++++++++++++++++++++ Bdd/syslog-ng/syslog-ng.conf | 30 +++++++++++++++++++++ Bdd/syslog-ng/tls/README.md | 21 +++++++++++++++ Bdd/syslog-ng/tls/ca.key | 27 +++++++++++++++++++ Bdd/syslog-ng/tls/ca.pem | 19 ++++++++++++++ Bdd/syslog-ng/tls/generate.sh | 42 ++++++++++++++++++++++++++++++ Bdd/syslog-ng/tls/server.key | 27 +++++++++++++++++++ Bdd/syslog-ng/tls/server.pem | 18 +++++++++++++ ci/docker-compose.bdd.yml | 1 + 11 files changed, 226 insertions(+) create mode 100644 Bdd/features/tls_transport.feature create mode 100644 Bdd/syslog-ng/tls/README.md create mode 100644 Bdd/syslog-ng/tls/ca.key create mode 100644 Bdd/syslog-ng/tls/ca.pem create mode 100755 Bdd/syslog-ng/tls/generate.sh create mode 100644 Bdd/syslog-ng/tls/server.key create mode 100644 Bdd/syslog-ng/tls/server.pem diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 6e5353bf..79266d3e 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -23,11 +23,13 @@ services: image: balabit/syslog-ng:latest volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf + - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro - ../Bdd/output:/var/log/syslog-ng - syslog-ng-ctl:/var/lib/syslog-ng ports: - "5514:5514/udp" - "5514:5514/tcp" + - "6514:6514/tcp" working_dir: /workspaces/SolidSyslog entrypoint: ["bash", "-c"] command: diff --git a/Bdd/features/tls_transport.feature b/Bdd/features/tls_transport.feature new file mode 100644 index 00000000..281e12d0 --- /dev/null +++ b/Bdd/features/tls_transport.feature @@ -0,0 +1,9 @@ +@tls @buffered @wip +Feature: TLS message delivery + The threaded example sends messages via TLS transport (RFC 5425) — + RFC 6587 octet-counting framing over TLS — to syslog-ng. + + Scenario: Message delivered over TLS + Given syslog-ng is running + When the threaded example sends a syslog message with transport tls + Then syslog-ng receives a message with priority "134" diff --git a/Bdd/syslog-ng/syslog-ng-full.conf b/Bdd/syslog-ng/syslog-ng-full.conf index d862beed..0dd7b5f0 100644 --- a/Bdd/syslog-ng/syslog-ng-full.conf +++ b/Bdd/syslog-ng/syslog-ng-full.conf @@ -16,6 +16,20 @@ source s_tcp { ); }; +source s_tls { + syslog( + transport("tls") + port(6514) + keep-hostname(yes) + tls( + key-file("/etc/syslog-ng/tls/server.key") + cert-file("/etc/syslog-ng/tls/server.pem") + ca-file("/etc/syslog-ng/tls/ca.pem") + peer-verify(optional-untrusted) + ) + ); +}; + destination d_file { file( "/var/log/syslog-ng/received.log" @@ -46,9 +60,20 @@ destination d_file_tcp { ); }; +destination d_file_tls { + file( + "/var/log/syslog-ng/received_tls.log" + template("PRIORITY=${PRI} TIMESTAMP=${ISODATE} HOSTNAME=${HOST} APP_NAME=${PROGRAM} PROCID=${PID} MSGID=${MSGID} STRUCTURED_DATA=${SDATA} MSG=${MSG}\n") + flush_lines(1) + create-dirs(yes) + perm(0644) + ); +}; + log { source(s_udp); source(s_tcp); + source(s_tls); destination(d_file); }; @@ -61,3 +86,8 @@ log { source(s_tcp); destination(d_file_tcp); }; + +log { + source(s_tls); + destination(d_file_tls); +}; diff --git a/Bdd/syslog-ng/syslog-ng.conf b/Bdd/syslog-ng/syslog-ng.conf index d862beed..0dd7b5f0 100644 --- a/Bdd/syslog-ng/syslog-ng.conf +++ b/Bdd/syslog-ng/syslog-ng.conf @@ -16,6 +16,20 @@ source s_tcp { ); }; +source s_tls { + syslog( + transport("tls") + port(6514) + keep-hostname(yes) + tls( + key-file("/etc/syslog-ng/tls/server.key") + cert-file("/etc/syslog-ng/tls/server.pem") + ca-file("/etc/syslog-ng/tls/ca.pem") + peer-verify(optional-untrusted) + ) + ); +}; + destination d_file { file( "/var/log/syslog-ng/received.log" @@ -46,9 +60,20 @@ destination d_file_tcp { ); }; +destination d_file_tls { + file( + "/var/log/syslog-ng/received_tls.log" + template("PRIORITY=${PRI} TIMESTAMP=${ISODATE} HOSTNAME=${HOST} APP_NAME=${PROGRAM} PROCID=${PID} MSGID=${MSGID} STRUCTURED_DATA=${SDATA} MSG=${MSG}\n") + flush_lines(1) + create-dirs(yes) + perm(0644) + ); +}; + log { source(s_udp); source(s_tcp); + source(s_tls); destination(d_file); }; @@ -61,3 +86,8 @@ log { source(s_tcp); destination(d_file_tcp); }; + +log { + source(s_tls); + destination(d_file_tls); +}; diff --git a/Bdd/syslog-ng/tls/README.md b/Bdd/syslog-ng/tls/README.md new file mode 100644 index 00000000..c1596f0d --- /dev/null +++ b/Bdd/syslog-ng/tls/README.md @@ -0,0 +1,21 @@ +# BDD TLS test material + +**TEST ONLY.** These keys and certificates exist solely to drive the BDD TLS +scenarios (`Bdd/features/tls_transport.feature` et al.) against syslog-ng +running in a BDD compose container. They must never be used for any purpose +that touches real data. + +| File | Role | +|---|---| +| `ca.key` | Test CA private key. Signs the server cert. | +| `ca.pem` | Test CA certificate. Bundled into the example binary via `--tls-ca`. | +| `server.key` | syslog-ng server private key. | +| `server.pem` | syslog-ng server certificate, signed by the test CA. SANs: `syslog-ng`, `localhost`, `127.0.0.1`. | + +Validity is 10 years from generation, so these effectively never expire for +test purposes. + +## Regenerating + +Run `./generate.sh` from this directory (or the repo root). Commit the +regenerated files — the BDD suite pins them. diff --git a/Bdd/syslog-ng/tls/ca.key b/Bdd/syslog-ng/tls/ca.key new file mode 100644 index 00000000..0d773513 --- /dev/null +++ b/Bdd/syslog-ng/tls/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAx7LsZX3e+F+FyFqO2epTfguBgXfS1uPwiQOesW5WSBDLuhsk +FC2THwM/c7vxRQvIZ9hnOpJgi3e8of4lX+rSNg0viYxkrsk/S3SyKp/Qhzu7Eee8 +Cc+NfD+no8uF43gw3XfzX17NWVMTGBJOj3eC4xl/c9u0fApoljYv92Ifsc7TCaud +OHkUBlEBrR9b6bpy+GPDCe26rtG76NwKf+6B0t8IawNjbLDCovFCC9k8ymlTIktt +v9P32J1FgZ18M0FBowA1CrLyCoGNa9loyfUz4Qhje3CG/fUeMLoAOvIWsixBVH3X +MHlGyyJSpK1soBMgs9o+YBnwZatNvl+PQcm3XwIDAQABAoIBAG5Bw3Nlw62uT9cG +6Mg4MFDvWVl6JtaukyeS8BNWsByp6VGfmpPpdckxqd7kbZmJn+R1/si/+L4IJgXq +pAgJRnbBYPW4pf467s9iZtxR0HhZ6jdAkvl3ts5a9tvgitPEkHY2vBBz7zzaOTiZ +IvbjLUjl0xDMxSGXPZdx45nae1VXuLIpvFVGcSs9830TqjK/hGu/rIoO2JC/MfUe +3roal/xYOatCjGtEM86zO3xdxmMnCP8Ei8xsRBY+ygDAzi+9D6L7l3rjp4gPkixC +EL6zueOJpYulYWoIQTV9pKfB00MlZdUs5oMkuZcmP5/Mxx5nzILpHBV2Yj9xY26n +n7lFO4ECgYEA/VqXPAWzREnlHnKxe+gLRNPcRFASvrNFT65JhMRzeUAwvEBCBV15 +1a2ECeBwU2wvGP23usIkYzJD8C4PZ9dne0PTR9lnEFipZ7DyBj9ODBcuP3QRax3c +XPWgTHbU9A++65XBaLb31khKE/4Wzc20+3qjqnQPLekDnoePcQe/cJcCgYEAycjf +L8s7fjh+dhUxUY598cjPF2tiaJmiBlGovSLvca+aDIY/P0jimUixAqESS3LZEqZv +Dy9SwPMT4kM8BIHOWJ/1jgC2NxPEms69hb27EwWKKGf3YfMosWYfzL6w5T7On2tm +8KdFlImjRrOhuLjK2kQ12WE+Cn87nY4C5j5OgHkCgYEAuS8hQgcsnGqi5VmSL25R +5lIxO2GIAgoJTI+pPzlU6jioJsGVQFSt/CijTZDWqbN2zX6OcBz9+d/A1urj6dQB +2JCf+3GGKuWTla45iaV2B9JdxJWzMaeW7f+/1oQ5bdeghpRk1YR/rQoT038y7eVB +N1vSC4JfogAi3BpcF5NBNCsCgYAfSpIpxDdmnHYn86NhRVqMixpqRFHPbLuuhS1I +n0lIdPXLqWnXc4MISDiC8t12a8nz1XF1hl4r48YamViOTl7kaXHX4o5fPiaH5zWG +ruR6z6ocF/tJ6j5OPEsEIjImarX6DNq6yQXGQg3a4fJbRfTXIirDmgvX7uEczq/u +ao0b6QKBgHRDraREe/cLd05y2X30b7HeBK1SCweHYsOU3TdtHP6Nyn12kgy/Il9w +e50GEcRci16Km0wfclFtj8QsOqOYcr2P180INoXqeZgnHi2MmoeXTV7LJUEpYSOC +mPWTDihHP0q0x/RAzd8ynm51qK4Pghs8C5SWOY9Azl9dSKHDBOSe +-----END RSA PRIVATE KEY----- diff --git a/Bdd/syslog-ng/tls/ca.pem b/Bdd/syslog-ng/tls/ca.pem new file mode 100644 index 00000000..6daaf732 --- /dev/null +++ b/Bdd/syslog-ng/tls/ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUa9bOdOquPzUXvg4/X1St4bHQuRAwDQYJKoZIhvcNAQEL +BQAwIjEgMB4GA1UEAwwXU29saWRTeXNsb2cgQkREIFRlc3QgQ0EwHhcNMjYwNDIw +MTcwODQxWhcNMzYwNDE3MTcwODQxWjAiMSAwHgYDVQQDDBdTb2xpZFN5c2xvZyBC +REQgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMey7GV9 +3vhfhchajtnqU34LgYF30tbj8IkDnrFuVkgQy7obJBQtkx8DP3O78UULyGfYZzqS +YIt3vKH+JV/q0jYNL4mMZK7JP0t0siqf0Ic7uxHnvAnPjXw/p6PLheN4MN13819e +zVlTExgSTo93guMZf3PbtHwKaJY2L/diH7HO0wmrnTh5FAZRAa0fW+m6cvhjwwnt +uq7Ru+jcCn/ugdLfCGsDY2ywwqLxQgvZPMppUyJLbb/T99idRYGdfDNBQaMANQqy +8gqBjWvZaMn1M+EIY3twhv31HjC6ADryFrIsQVR91zB5RssiUqStbKATILPaPmAZ +8GWrTb5fj0HJt18CAwEAAaNTMFEwHQYDVR0OBBYEFKvZn0img6757QgOS6J9X79x +79NZMB8GA1UdIwQYMBaAFKvZn0img6757QgOS6J9X79x79NZMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKfFmKPcsrVxmJZNuWwXPM4M5okzcpNP +2PQt+lGC7x60FLlfySAKtnyyYSddHRPgNQELmcKYulpDJj5eYrhWUwdqukra1B2s +bv4bz8UUIYPrc4hvLdLKs5+iLmM9rzioKI9Dq5+r5dlxR5hK2CfOnzyjPb6/rzIy +nxSRsVrAkGUqfi1BH4poWAtwY00A3zxehcYZNtwgNKqVrj9rE0gLl0DSR3W6dABa +q0whWUJQsB9cRSLwxrxiLAU95NzdOmgdcrswYptSZHtSXYdS8mBf31cQNHvjAvn8 +yR1xGaLsJ0E6F+z70kx22KzTkvwavcx4mukm6PVJisqVDulz579+7C4= +-----END CERTIFICATE----- diff --git a/Bdd/syslog-ng/tls/generate.sh b/Bdd/syslog-ng/tls/generate.sh new file mode 100755 index 00000000..59d8b06a --- /dev/null +++ b/Bdd/syslog-ng/tls/generate.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Regenerates the test-only self-signed CA + syslog-ng server cert used by the +# TLS BDD scenarios. Keys in this directory are FOR TESTING ONLY — never use +# them for anything that touches real data. +# +# Run from the repo root: ./Bdd/syslog-ng/tls/generate.sh +# Or from this directory: ./generate.sh +# +# Validity is 10 years so the certs effectively never expire for test purposes. +# If you regenerate, commit the changes — the BDD suite pins these files. + +set -euo pipefail + +cd "$(dirname "$0")" + +DAYS=3650 +SUBJECT_CA="/CN=SolidSyslog BDD Test CA" +SUBJECT_SRV="/CN=syslog-ng" +SAN="subjectAltName = DNS:syslog-ng, DNS:localhost, IP:127.0.0.1" + +# 1. CA key + self-signed CA cert +openssl genrsa -out ca.key 2048 +openssl req -x509 -new -nodes -key ca.key -sha256 -days "$DAYS" \ + -subj "$SUBJECT_CA" -out ca.pem + +# 2. Server key + CSR +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -subj "$SUBJECT_SRV" -out server.csr + +# 3. Sign server cert with the CA, carrying the SAN +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \ + -out server.pem -days "$DAYS" -sha256 \ + -extfile <(printf "%s\n" "$SAN") + +# Remove the CSR and CA serial — only the keys + certs need to be committed +rm -f server.csr ca.srl + +chmod 0644 ca.pem server.pem +chmod 0600 ca.key server.key + +echo "Regenerated test certs in $(pwd)" diff --git a/Bdd/syslog-ng/tls/server.key b/Bdd/syslog-ng/tls/server.key new file mode 100644 index 00000000..4024b6b4 --- /dev/null +++ b/Bdd/syslog-ng/tls/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAshK+rIthQABR1DRYVf9E4yV9nGC3S4VPd2jK64/O0dnqAf1m +MozRGecilyA9kWO/lOUlK7FniaLMcBzn4CuaCGjDhN6uCVxgfaGPe0NIB6weBbr6 +RcBaqHu2A3ZKqxdF5vNU//Ty8UFVLksHIEd9xY+hN534YcvGEpm4e9C7TKUzf++4 +an0Ow+7utfh/vXIlz1wir3nwHe9b02gUhnl+q2olM1/jLuH4Sjh3Gwga0Mgtxm2R +ChUIDxBpzTKKA2h0OS+Puz/EiAraHkE6hE1yOtTkMm17LD0t3cMr47m+cLzOZgsK +nfS7H2UeOJVDFAcxoF9K7IM24KC5rSAD+b0iqQIDAQABAoIBAQCkzBov/rGalHEm +TOcvdxVejbSowwz+1DnNzWp2BFmn9d7NxqAMyis9qJ1ndQvlgIWtmz8CJ6UlSEeg +ve+Nk2xyudmwHpC70wTtX1Y1AfgoOY5zSS4S3UhJQmwY2iGSIvCrIqiYVC19PjRB +qmfG5MKt0hWmLgjPINGHJ0IHPA4KiBFnCKtNHn5QakpaplxFblLtAJMv6Mzfsnnm +amzflHA+QvFy0mWYK5GAe6LYSYUm2TikqzJbq4hjeiCDQh1hzZuhdQpQL0mLWYpS +ewj0mIClfWiLdR9Sf3O9RHBP5NczOkJg+QCHwFfOamRWulM9D8GhzJGAxdMRCJI8 +5YpBXx0dAoGBANXiP4KUi2YstRh6gZ5tIDjP2HMqMLDp8UeeOH+NwBA2DQP5sXiX +t9QpO17dhgugz1R2go53BvajJru7yUZPen3rxRrVy2t82DZ0pGrwuKfmURzMm7bp +9HwwvNMDnpCYGGnSK+Lb1FZXTSfEDNb0i+tEVCv/HANurE92p6Awn6L3AoGBANUj +TuAoPCR2A3kXMQEDa/RR51buVLqDgyBujffBvfG/+JpAVplyvOGNiC8DHoRF1JOt +Pfva+Y3rl4q3cvprom+ug55Ch9QKm0WXiHXO/xiebi9Nr5UuUfhz3oZopGlul0Ja +lIlhL7RjZCityC/rSDU0HgzV1AQC9+8XnMgPql9fAoGAWlGnViIaV7RLaSHaeKdG +cxc6JS/MzfiMfhzPaPXYBEB+8I3RpsYSx9H9k0rinyhyRQ2ihjy3QRGWerKLBVjs +th4I5xMfSq22tBA/mjU7FRPKbunsW6qiJouCQW2G2TKRJKavB6ajHS7SkxdUALXW +HCyHiui40K48r3XAxYGi9/MCgYBMB4ZRa8jYcMiYX295nxDZXIYbenT6+4LBBO7b +nF73Z7V0wNKjdkxuYSBdNM2j/YzxCPF9cAVpXMr4DqyTK+YL1MLap3zwZMAyuPVS +oP5ad9lyQlIJ8zYGXL7aT2Wmvm5ymwE1aFeuD25hHGy2u0VMxUSa64Pv0mdDALdK +nlv2HQKBgQC4u3+cyw+cimCc1gMTXB7Lc9ZfxXZvjhnnrHIDjvTSPG5Gipnwv5Yy +q94XuvevNMNWjTUAFubcA+aYa8XMWjcfHqVaatS1gQQkZV11Y2R1JJNjW5dcVtR4 +tfI15/6fnr7LhbsFeRqE6dRKbThaj9jbH4rfqBT/t25os4TfiQtU8A== +-----END RSA PRIVATE KEY----- diff --git a/Bdd/syslog-ng/tls/server.pem b/Bdd/syslog-ng/tls/server.pem new file mode 100644 index 00000000..3ccb1797 --- /dev/null +++ b/Bdd/syslog-ng/tls/server.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAdWgAwIBAgIUCMEuhoivc1mANtoLz0SkTUUTbKUwDQYJKoZIhvcNAQEL +BQAwIjEgMB4GA1UEAwwXU29saWRTeXNsb2cgQkREIFRlc3QgQ0EwHhcNMjYwNDIw +MTcwODQxWhcNMzYwNDE3MTcwODQxWjAUMRIwEAYDVQQDDAlzeXNsb2ctbmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyEr6si2FAAFHUNFhV/0TjJX2c +YLdLhU93aMrrj87R2eoB/WYyjNEZ5yKXID2RY7+U5SUrsWeJosxwHOfgK5oIaMOE +3q4JXGB9oY97Q0gHrB4FuvpFwFqoe7YDdkqrF0Xm81T/9PLxQVUuSwcgR33Fj6E3 +nfhhy8YSmbh70LtMpTN/77hqfQ7D7u61+H+9ciXPXCKvefAd71vTaBSGeX6raiUz +X+Mu4fhKOHcbCBrQyC3GbZEKFQgPEGnNMooDaHQ5L4+7P8SICtoeQTqETXI61OQy +bXssPS3dwyvjub5wvM5mCwqd9LsfZR44lUMUBzGgX0rsgzbgoLmtIAP5vSKpAgMB +AAGjKTAnMCUGA1UdEQQeMByCCXN5c2xvZy1uZ4IJbG9jYWxob3N0hwR/AAABMA0G +CSqGSIb3DQEBCwUAA4IBAQBKz2+40sPa5jKdzFW0T/k1zHvGDAffv7zeNonw1g6B +D/ORXfE7EQzYGXkZPVo7DD5eqVk+4t0ToCAf8kC6/5StcaW3D5ERoeq5hYOkvGdF +FpHyNrTwwkE3kqdfxTdOH5g2Gzb+XosG9UebpxrGxVbcrqyjBn2LJhM8Kj2zBu6D +ACdtsU5knHvgXy7cnwRrrjFxr8yognWHK6pRxGwDXmMFa2qOrBpIX74OXoei7eYQ +k4hHdFvk/GKqR6lonXfCEaAAAtBqThLbq+bZHVaLWZ+HA1qBU/+KdGYzDl8P022o +VFuAZgp6ffabfyWgFu3a3d+jphjoFKGIfp/0wR7DNq1s +-----END CERTIFICATE----- diff --git a/ci/docker-compose.bdd.yml b/ci/docker-compose.bdd.yml index 14ae45a7..08bbdffc 100644 --- a/ci/docker-compose.bdd.yml +++ b/ci/docker-compose.bdd.yml @@ -3,6 +3,7 @@ services: image: balabit/syslog-ng:latest volumes: - ../Bdd/syslog-ng/syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf + - ../Bdd/syslog-ng/tls:/etc/syslog-ng/tls:ro - bdd-output:/var/log/syslog-ng - syslog-ng-ctl:/var/lib/syslog-ng entrypoint: ["bash", "-c"] From 1c6868ed840262b5a9e75d1fa51fd4414472f095 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 21:05:10 +0100 Subject: [PATCH 02/19] S3.7 series A: Stream gains Read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Read operation to the SolidSyslogStream vtable and implementation in PosixTcpStream. Needed so the upcoming TLS Stream (Platform/OpenSsl/...) can delegate BIO reads to an injected transport Stream — decoupling OpenSSL from POSIX-specific I/O. TDD cycles (red → green → refactor): - A1: SocketFake gains recv() link-time intercept + call counter. First test in a new SocketFakeTest.cpp (previously SocketFake had no unit test file — now follows the project convention). - A2: SolidSyslogStream_Read public fn + vtable slot + dispatcher. PosixTcpStream implements Read via recv(). Test-order fix: new ReadCallsRecvOnce closes the stream to avoid leaking state into the next test (pre-existing Destroy-doesn't-reset-fd latent bug exposed; not in scope for this cycle). - A3: Drive the Read return value — add SocketFake_SetRecvReturn and assert PosixTcpStream's Read propagates the recv return. Public surface: ssize_t SolidSyslogStream_Read(stream, buffer, size); Return value contract: bytes read, 0 on EOF, negative on error. Matches POSIX recv() and OpenSSL's custom BIO read callback. Only one existing Stream implementation (PosixTcpStream) — nothing else needed wiring. No callers use Read yet; it's infrastructure. Tests: 589 (was 586). All passing. Full build clean (library, examples, tests, example tests). --- Interface/SolidSyslogStream.h | 8 +++-- Interface/SolidSyslogStreamDefinition.h | 1 + .../Posix/Source/SolidSyslogPosixTcpStream.c | 9 ++++++ Source/SolidSyslogStream.c | 5 ++++ Tests/CMakeLists.txt | 1 + Tests/SocketFakeTest.cpp | 18 ++++++++++++ Tests/SolidSyslogPosixTcpStreamTest.cpp | 19 ++++++++++++ Tests/Support/SocketFake.c | 29 +++++++++++++++++++ Tests/Support/SocketFake.h | 6 ++++ 9 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 Tests/SocketFakeTest.cpp diff --git a/Interface/SolidSyslogStream.h b/Interface/SolidSyslogStream.h index fd4294e1..bbc11aa6 100644 --- a/Interface/SolidSyslogStream.h +++ b/Interface/SolidSyslogStream.h @@ -5,14 +5,16 @@ #include "SolidSyslogAddress.h" #include #include +#include EXTERN_C_BEGIN struct SolidSyslogStream; - bool SolidSyslogStream_Open(struct SolidSyslogStream * stream, const struct SolidSyslogAddress* addr); - bool SolidSyslogStream_Send(struct SolidSyslogStream * stream, const void* buffer, size_t size); - void SolidSyslogStream_Close(struct SolidSyslogStream * stream); + bool SolidSyslogStream_Open(struct SolidSyslogStream* stream, const struct SolidSyslogAddress* addr); + bool SolidSyslogStream_Send(struct SolidSyslogStream* stream, const void* buffer, size_t size); + ssize_t SolidSyslogStream_Read(struct SolidSyslogStream* stream, void* buffer, size_t size); + void SolidSyslogStream_Close(struct SolidSyslogStream* stream); EXTERN_C_END diff --git a/Interface/SolidSyslogStreamDefinition.h b/Interface/SolidSyslogStreamDefinition.h index 83be381f..a3cefa74 100644 --- a/Interface/SolidSyslogStreamDefinition.h +++ b/Interface/SolidSyslogStreamDefinition.h @@ -9,6 +9,7 @@ EXTERN_C_BEGIN { bool (*Open)(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); bool (*Send)(struct SolidSyslogStream* self, const void* buffer, size_t size); + ssize_t (*Read)(struct SolidSyslogStream* self, void* buffer, size_t size); void (*Close)(struct SolidSyslogStream* self); }; diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 3525bc1d..e22490e4 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -14,6 +14,7 @@ enum static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static ssize_t Read(struct SolidSyslogStream* self, void* buffer, size_t size); static void Close(struct SolidSyslogStream* self); static void EnableTcpNoDelay(int fd); static inline bool IsFileDescriptorValid(int fd); @@ -30,6 +31,7 @@ struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void) { instance.base.Open = Open; instance.base.Send = Send; + instance.base.Read = Read; instance.base.Close = Close; return &instance.base; } @@ -38,6 +40,7 @@ void SolidSyslogPosixTcpStream_Destroy(void) { instance.base.Open = NULL; instance.base.Send = NULL; + instance.base.Read = NULL; instance.base.Close = NULL; } @@ -73,6 +76,12 @@ static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size return send(stream->fd, buffer, size, MSG_NOSIGNAL) >= 0; } +static ssize_t Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + struct SolidSyslogPosixTcpStream* stream = (struct SolidSyslogPosixTcpStream*) self; + return recv(stream->fd, buffer, size, 0); +} + static void Close(struct SolidSyslogStream* self) { struct SolidSyslogPosixTcpStream* stream = (struct SolidSyslogPosixTcpStream*) self; diff --git a/Source/SolidSyslogStream.c b/Source/SolidSyslogStream.c index 497ab966..ab26c6d6 100644 --- a/Source/SolidSyslogStream.c +++ b/Source/SolidSyslogStream.c @@ -10,6 +10,11 @@ bool SolidSyslogStream_Send(struct SolidSyslogStream* stream, const void* buffer return stream->Send(stream, buffer, size); } +ssize_t SolidSyslogStream_Read(struct SolidSyslogStream* stream, void* buffer, size_t size) +{ + return stream->Read(stream, buffer, size); +} + void SolidSyslogStream_Close(struct SolidSyslogStream* stream) { stream->Close(stream); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 768318a0..b4710074 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -58,6 +58,7 @@ if(SOLIDSYSLOG_POSIX) SolidSyslogGetAddrInfoResolverTest.cpp SolidSyslogPosixDatagramTest.cpp SolidSyslogPosixTcpStreamTest.cpp + SocketFakeTest.cpp ) endif() diff --git a/Tests/SocketFakeTest.cpp b/Tests/SocketFakeTest.cpp new file mode 100644 index 00000000..3c09e020 --- /dev/null +++ b/Tests/SocketFakeTest.cpp @@ -0,0 +1,18 @@ +#include "CppUTest/TestHarness.h" +#include "SocketFake.h" + +#include + +// clang-format off +TEST_GROUP(SocketFake) +{ + void setup() override { SocketFake_Reset(); } +}; +// clang-format on + +TEST(SocketFake, RecvIncrementsCount) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 0); + LONGS_EQUAL(1, SocketFake_RecvCallCount()); +} diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index d75b3261..f54b43c0 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -177,3 +177,22 @@ TEST(SolidSyslogPosixTcpStream, CloseIsNoOpWhenNotOpen) SolidSyslogStream_Close(stream); LONGS_EQUAL(0, SocketFake_CloseCallCount()); } + +TEST(SolidSyslogPosixTcpStream, ReadCallsRecvOnce) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, SocketFake_RecvCallCount()); +} + +TEST(SolidSyslogPosixTcpStream, ReadReturnsRecvReturnValue) +{ + SocketFake_SetRecvReturn(7); + SolidSyslogStream_Open(stream, addr); + char buf[16]; + ssize_t n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(7, n); +} diff --git a/Tests/Support/SocketFake.c b/Tests/Support/SocketFake.c index 9f321773..09ff643a 100644 --- a/Tests/Support/SocketFake.c +++ b/Tests/Support/SocketFake.c @@ -52,6 +52,9 @@ static int lastSetSockOptOptname; static int closeCallCount; static int lastClosedFd; +static int recvCallCount; +static ssize_t recvReturn; + static char lastAddrString[INET_ADDRSTRLEN]; static bool getAddrInfoFails; @@ -97,6 +100,8 @@ void SocketFake_Reset(void) lastSocketType = 0; closeCallCount = 0; lastClosedFd = -1; + recvCallCount = 0; + recvReturn = 0; lastAddrString[0] = '\0'; getAddrInfoFails = false; @@ -309,6 +314,20 @@ int SocketFake_LastClosedFd(void) return lastClosedFd; } +/* recv configuration */ + +void SocketFake_SetRecvReturn(ssize_t value) +{ + recvReturn = value; +} + +/* recv accessors */ + +int SocketFake_RecvCallCount(void) +{ + return recvCallCount; +} + /* getaddrinfo configuration */ void SocketFake_SetGetAddrInfoFails(bool fails) @@ -429,6 +448,16 @@ int close(int fd) return 0; } +ssize_t recv(int sockfd, void* buf, size_t len, int flags) +{ + (void) sockfd; + (void) buf; + (void) len; + (void) flags; + recvCallCount++; + return recvReturn; +} + // clang-format off // NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; parameter names differ from glibc internal names int getaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res) diff --git a/Tests/Support/SocketFake.h b/Tests/Support/SocketFake.h index ea98d288..64bd7b4b 100644 --- a/Tests/Support/SocketFake.h +++ b/Tests/Support/SocketFake.h @@ -63,6 +63,12 @@ EXTERN_C_BEGIN int SocketFake_CloseCallCount(void); int SocketFake_LastClosedFd(void); + /* recv configuration */ + void SocketFake_SetRecvReturn(ssize_t value); + + /* recv accessors */ + int SocketFake_RecvCallCount(void); + /* getaddrinfo configuration */ void SocketFake_SetGetAddrInfoFails(bool fails); From 39743ab83e5f08564987d985007a22778ddf10dd Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 21:18:16 +0100 Subject: [PATCH 03/19] S3.7 series A hardening: recv arg capture + fd reset on Destroy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strengthens Series A along three dimensions raised in review: 1. SocketFake.recv now captures args (fd, buf, len, flags) not just a call counter. 4 new arg-capture accessors + 4 new SocketFake tests exercising each. Brings recv into line with the existing send/sendto arg-capture pattern. 2. PosixTcpStream Read tests extended — one assertion per claim: ReadCallsRecvOnce, ReadPassesSocketFdToRecv, ReadPassesBufferToRecv, ReadPassesLengthToRecv, ReadPassesZeroFlagsToRecv, ReadReturnsRecvReturnValue. Matches the existing Send test pattern. 3. Micro-cycle: PosixTcpStream.Destroy now resets fd to INVALID_FD. Previously Destroy left stale fd state, which only surfaced as a test-order bug when a test left the stream open (my Read tests originally did). Driven by DestroyResetsFdSoRecreatedStreamIsNotOpen. Refactor: removed the defensive Close calls from Read tests — no longer needed with proper state reset. Tests: 598 total (was 589). All green. Full build clean. Noting: Windows TCP stream (S13.9, future) will need to provide Read in its vtable. Tracked in memory; compile will catch it. --- .../Posix/Source/SolidSyslogPosixTcpStream.c | 1 + Tests/SocketFakeTest.cpp | 28 ++++++++++++ Tests/SolidSyslogPosixTcpStreamTest.cpp | 43 ++++++++++++++++++- Tests/Support/SocketFake.c | 40 ++++++++++++++--- Tests/Support/SocketFake.h | 6 ++- 5 files changed, 109 insertions(+), 9 deletions(-) diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index e22490e4..7d6f1d09 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -42,6 +42,7 @@ void SolidSyslogPosixTcpStream_Destroy(void) instance.base.Send = NULL; instance.base.Read = NULL; instance.base.Close = NULL; + instance.fd = INVALID_FD; } static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) diff --git a/Tests/SocketFakeTest.cpp b/Tests/SocketFakeTest.cpp index 3c09e020..7d86d72a 100644 --- a/Tests/SocketFakeTest.cpp +++ b/Tests/SocketFakeTest.cpp @@ -16,3 +16,31 @@ TEST(SocketFake, RecvIncrementsCount) recv(3, buf, sizeof(buf), 0); LONGS_EQUAL(1, SocketFake_RecvCallCount()); } + +TEST(SocketFake, RecvCapturesFd) +{ + char buf[16]; + recv(7, buf, sizeof(buf), 0); + LONGS_EQUAL(7, SocketFake_LastRecvFd()); +} + +TEST(SocketFake, RecvCapturesBuf) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 0); + POINTERS_EQUAL(buf, SocketFake_LastRecvBuf()); +} + +TEST(SocketFake, RecvCapturesLen) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 0); + LONGS_EQUAL(sizeof(buf), SocketFake_LastRecvLen()); +} + +TEST(SocketFake, RecvCapturesFlags) +{ + char buf[16]; + recv(3, buf, sizeof(buf), 42); + LONGS_EQUAL(42, SocketFake_LastRecvFlags()); +} diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index f54b43c0..84d434cf 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -183,16 +183,55 @@ TEST(SolidSyslogPosixTcpStream, ReadCallsRecvOnce) SolidSyslogStream_Open(stream, addr); char buf[16]; SolidSyslogStream_Read(stream, buf, sizeof(buf)); - SolidSyslogStream_Close(stream); LONGS_EQUAL(1, SocketFake_RecvCallCount()); } +TEST(SolidSyslogPosixTcpStream, ReadPassesSocketFdToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(SocketFake_SocketFd(), SocketFake_LastRecvFd()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesBufferToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(buf, SocketFake_LastRecvBuf()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesLengthToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(sizeof(buf), SocketFake_LastRecvLen()); +} + +TEST(SolidSyslogPosixTcpStream, ReadPassesZeroFlagsToRecv) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(0, SocketFake_LastRecvFlags()); +} + TEST(SolidSyslogPosixTcpStream, ReadReturnsRecvReturnValue) { SocketFake_SetRecvReturn(7); SolidSyslogStream_Open(stream, addr); char buf[16]; ssize_t n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); - SolidSyslogStream_Close(stream); LONGS_EQUAL(7, n); } + +TEST(SolidSyslogPosixTcpStream, DestroyResetsFdSoRecreatedStreamIsNotOpen) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogPosixTcpStream_Destroy(); + struct SolidSyslogStream* reopened = SolidSyslogPosixTcpStream_Create(); + SolidSyslogStream_Close(reopened); + LONGS_EQUAL(0, SocketFake_CloseCallCount()); +} diff --git a/Tests/Support/SocketFake.c b/Tests/Support/SocketFake.c index 09ff643a..8324f145 100644 --- a/Tests/Support/SocketFake.c +++ b/Tests/Support/SocketFake.c @@ -52,8 +52,12 @@ static int lastSetSockOptOptname; static int closeCallCount; static int lastClosedFd; -static int recvCallCount; -static ssize_t recvReturn; +static int recvCallCount; +static ssize_t recvReturn; +static int lastRecvFd; +static const void* lastRecvBuf; +static size_t lastRecvLen; +static int lastRecvFlags; static char lastAddrString[INET_ADDRSTRLEN]; @@ -102,6 +106,10 @@ void SocketFake_Reset(void) lastClosedFd = -1; recvCallCount = 0; recvReturn = 0; + lastRecvFd = -1; + lastRecvBuf = NULL; + lastRecvLen = 0; + lastRecvFlags = 0; lastAddrString[0] = '\0'; getAddrInfoFails = false; @@ -328,6 +336,26 @@ int SocketFake_RecvCallCount(void) return recvCallCount; } +int SocketFake_LastRecvFd(void) +{ + return lastRecvFd; +} + +const void* SocketFake_LastRecvBuf(void) +{ + return lastRecvBuf; +} + +size_t SocketFake_LastRecvLen(void) +{ + return lastRecvLen; +} + +int SocketFake_LastRecvFlags(void) +{ + return lastRecvFlags; +} + /* getaddrinfo configuration */ void SocketFake_SetGetAddrInfoFails(bool fails) @@ -450,11 +478,11 @@ int close(int fd) ssize_t recv(int sockfd, void* buf, size_t len, int flags) { - (void) sockfd; - (void) buf; - (void) len; - (void) flags; recvCallCount++; + lastRecvFd = sockfd; + lastRecvBuf = buf; + lastRecvLen = len; + lastRecvFlags = flags; return recvReturn; } diff --git a/Tests/Support/SocketFake.h b/Tests/Support/SocketFake.h index 64bd7b4b..d57bd0bc 100644 --- a/Tests/Support/SocketFake.h +++ b/Tests/Support/SocketFake.h @@ -67,7 +67,11 @@ EXTERN_C_BEGIN void SocketFake_SetRecvReturn(ssize_t value); /* recv accessors */ - int SocketFake_RecvCallCount(void); + int SocketFake_RecvCallCount(void); + int SocketFake_LastRecvFd(void); + const void* SocketFake_LastRecvBuf(void); + size_t SocketFake_LastRecvLen(void); + int SocketFake_LastRecvFlags(void); /* getaddrinfo configuration */ void SocketFake_SetGetAddrInfoFails(bool fails); From 089b27884ffc9d11531b31ab986c8abdb191ac74 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 21:28:45 +0100 Subject: [PATCH 04/19] S3.7 cycle A5: Destroy closes an open socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugs the fd leak surfaced in review: Destroy previously just reset fd to INVALID_FD, leaving the underlying socket open if the caller forgot to Close. Now Destroy calls the internal Close helper first, mirroring the UdpSender pattern where Destroy invokes Disconnect before resetting. Red driven by two new tests: - DestroyClosesOpenSocket: asserts close() called once - DestroyClosesWithSocketFd: asserts close()'s arg is the socket fd Refactor after green: removed DestroyResetsFdSoRecreatedStreamIsNotOpen (A4's test). Its intent — "recreate starts fresh" — now follows transitively from DestroyClosesOpenSocket (Destroy cleans up fd) + the existing CloseIsNoOpWhenNotOpen (fresh stream's Close is noop). Also removed the now-redundant explicit `instance.fd = INVALID_FD` from Destroy — Close() already handles it. Tests: 599 total (was 598: +2 A5 tests, -1 A4 test subsumed). All green. Full build clean. --- Platform/Posix/Source/SolidSyslogPosixTcpStream.c | 2 +- Tests/SolidSyslogPosixTcpStreamTest.cpp | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index 7d6f1d09..adcdf711 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -38,11 +38,11 @@ struct SolidSyslogStream* SolidSyslogPosixTcpStream_Create(void) void SolidSyslogPosixTcpStream_Destroy(void) { + Close(&instance.base); instance.base.Open = NULL; instance.base.Send = NULL; instance.base.Read = NULL; instance.base.Close = NULL; - instance.fd = INVALID_FD; } static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index 84d434cf..c64d41e8 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -227,11 +227,16 @@ TEST(SolidSyslogPosixTcpStream, ReadReturnsRecvReturnValue) LONGS_EQUAL(7, n); } -TEST(SolidSyslogPosixTcpStream, DestroyResetsFdSoRecreatedStreamIsNotOpen) +TEST(SolidSyslogPosixTcpStream, DestroyClosesOpenSocket) { SolidSyslogStream_Open(stream, addr); SolidSyslogPosixTcpStream_Destroy(); - struct SolidSyslogStream* reopened = SolidSyslogPosixTcpStream_Create(); - SolidSyslogStream_Close(reopened); - LONGS_EQUAL(0, SocketFake_CloseCallCount()); + LONGS_EQUAL(1, SocketFake_CloseCallCount()); +} + +TEST(SolidSyslogPosixTcpStream, DestroyClosesWithSocketFd) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogPosixTcpStream_Destroy(); + LONGS_EQUAL(SocketFake_SocketFd(), SocketFake_LastClosedFd()); } From 96e8fc5c30ef7530da0a71e3fbbca3f53af6ed70 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 21:34:35 +0100 Subject: [PATCH 05/19] S3.7 series B: StreamFake for TLS-transport-under-test injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New state-ful fake implementing SolidSyslogStream. Matches the existing pattern of state-ful fakes (SenderFake, StoreFake, BufferFake, FileFake — calloc-backed handle, per-instance state, accessor functions taking the handle). TDD cycles: - B1: CreateSucceeds (Create/Destroy skeleton) - B2: OpenIncrementsCount + OpenCapturesAddr (vtable Open wiring) - B3: SendIncrementsCount + SendCapturesBuffer + SendCapturesSize (vtable Send wiring, per-claim assertion) - B4: ReadIncrementsCount + ReadCapturesBuffer + ReadCapturesSize + ReadReturnsConfiguredValue (vtable Read wiring, canned return via StreamFake_SetReadReturn) - B5: CloseIncrementsCount (vtable Close wiring) API: struct SolidSyslogStream* StreamFake_Create(void); void StreamFake_Destroy(struct SolidSyslogStream*); int StreamFake_{Open,Send,Read,Close}CallCount(stream); ... StreamFake_Last*(stream); void StreamFake_SetReadReturn(stream, ssize_t); Tests: 610 total (+11 for StreamFake). All green. Full build clean. Next (series C): new Platform/OpenSsl/SolidSyslogTlsStream that takes a StreamFake (or any SolidSyslogStream) as its injected transport and wires OpenSSL I/O through a custom BIO. --- Tests/CMakeLists.txt | 2 + Tests/StreamFake.c | 117 +++++++++++++++++++++++++++++++++++++++ Tests/StreamFake.h | 29 ++++++++++ Tests/StreamFakeTest.cpp | 91 ++++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 Tests/StreamFake.c create mode 100644 Tests/StreamFake.h create mode 100644 Tests/StreamFakeTest.cpp diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index b4710074..c885abd9 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -29,6 +29,8 @@ set(TEST_SOURCES StringFake.c FileFakeTest.cpp FileFake.c + StreamFakeTest.cpp + StreamFake.c TestAssert.cpp SolidSyslogFileStoreTest.cpp main.cpp diff --git a/Tests/StreamFake.c b/Tests/StreamFake.c new file mode 100644 index 00000000..573f9b7d --- /dev/null +++ b/Tests/StreamFake.c @@ -0,0 +1,117 @@ +#include "StreamFake.h" +#include "SolidSyslogStreamDefinition.h" + +#include +#include + +struct StreamFake +{ + struct SolidSyslogStream base; + int openCallCount; + const struct SolidSyslogAddress* lastOpenAddr; + int sendCallCount; + const void* lastSendBuf; + size_t lastSendSize; + int readCallCount; + void* lastReadBuf; + size_t lastReadSize; + ssize_t readReturn; + int closeCallCount; +}; + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->openCallCount++; + fake->lastOpenAddr = addr; + return true; +} + +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->sendCallCount++; + fake->lastSendBuf = buffer; + fake->lastSendSize = size; + return true; +} + +static ssize_t Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->readCallCount++; + fake->lastReadBuf = buffer; + fake->lastReadSize = size; + return fake->readReturn; +} + +static void Close(struct SolidSyslogStream* self) +{ + struct StreamFake* fake = (struct StreamFake*) self; + fake->closeCallCount++; +} + +struct SolidSyslogStream* StreamFake_Create(void) +{ + struct StreamFake* fake = (struct StreamFake*) calloc(1, sizeof(struct StreamFake)); + fake->base.Open = Open; + fake->base.Send = Send; + fake->base.Read = Read; + fake->base.Close = Close; + return &fake->base; +} + +void StreamFake_Destroy(struct SolidSyslogStream* stream) +{ + free(stream); +} + +int StreamFake_OpenCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->openCallCount; +} + +const struct SolidSyslogAddress* StreamFake_LastOpenAddr(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastOpenAddr; +} + +int StreamFake_SendCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->sendCallCount; +} + +const void* StreamFake_LastSendBuf(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastSendBuf; +} + +size_t StreamFake_LastSendSize(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastSendSize; +} + +int StreamFake_ReadCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->readCallCount; +} + +void* StreamFake_LastReadBuf(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastReadBuf; +} + +size_t StreamFake_LastReadSize(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->lastReadSize; +} + +void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, ssize_t value) +{ + ((struct StreamFake*) stream)->readReturn = value; +} + +int StreamFake_CloseCallCount(struct SolidSyslogStream* stream) +{ + return ((struct StreamFake*) stream)->closeCallCount; +} diff --git a/Tests/StreamFake.h b/Tests/StreamFake.h new file mode 100644 index 00000000..df0c800d --- /dev/null +++ b/Tests/StreamFake.h @@ -0,0 +1,29 @@ +#ifndef STREAMFAKE_H +#define STREAMFAKE_H + +#include "ExternC.h" + +#include +#include + +EXTERN_C_BEGIN + + struct SolidSyslogStream; + struct SolidSyslogAddress; + + struct SolidSyslogStream* StreamFake_Create(void); + void StreamFake_Destroy(struct SolidSyslogStream* stream); + int StreamFake_OpenCallCount(struct SolidSyslogStream* stream); + const struct SolidSyslogAddress* StreamFake_LastOpenAddr(struct SolidSyslogStream* stream); + int StreamFake_SendCallCount(struct SolidSyslogStream* stream); + const void* StreamFake_LastSendBuf(struct SolidSyslogStream* stream); + size_t StreamFake_LastSendSize(struct SolidSyslogStream* stream); + int StreamFake_ReadCallCount(struct SolidSyslogStream* stream); + void* StreamFake_LastReadBuf(struct SolidSyslogStream* stream); + size_t StreamFake_LastReadSize(struct SolidSyslogStream* stream); + void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, ssize_t value); + int StreamFake_CloseCallCount(struct SolidSyslogStream* stream); + +EXTERN_C_END + +#endif /* STREAMFAKE_H */ diff --git a/Tests/StreamFakeTest.cpp b/Tests/StreamFakeTest.cpp new file mode 100644 index 00000000..7e6e4901 --- /dev/null +++ b/Tests/StreamFakeTest.cpp @@ -0,0 +1,91 @@ +#include "CppUTest/TestHarness.h" +#include "SolidSyslogAddress.h" +#include "SolidSyslogStream.h" +#include "StreamFake.h" + +// clang-format off +TEST_GROUP(StreamFake) +{ + struct SolidSyslogStream* stream = nullptr; + + void setup() override { stream = StreamFake_Create(); } + void teardown() override { StreamFake_Destroy(stream); } +}; +// clang-format on + +TEST(StreamFake, CreateSucceeds) +{ + CHECK_TRUE(stream != nullptr); +} + +TEST(StreamFake, OpenIncrementsCount) +{ + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, StreamFake_OpenCallCount(stream)); +} + +TEST(StreamFake, OpenCapturesAddr) +{ + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(addr, StreamFake_LastOpenAddr(stream)); +} + +TEST(StreamFake, SendIncrementsCount) +{ + const char payload[] = "hi"; + SolidSyslogStream_Send(stream, payload, sizeof(payload)); + LONGS_EQUAL(1, StreamFake_SendCallCount(stream)); +} + +TEST(StreamFake, SendCapturesBuffer) +{ + const char payload[] = "hi"; + SolidSyslogStream_Send(stream, payload, sizeof(payload)); + POINTERS_EQUAL(payload, StreamFake_LastSendBuf(stream)); +} + +TEST(StreamFake, SendCapturesSize) +{ + const char payload[] = "hi"; + SolidSyslogStream_Send(stream, payload, sizeof(payload)); + LONGS_EQUAL(sizeof(payload), StreamFake_LastSendSize(stream)); +} + +TEST(StreamFake, ReadIncrementsCount) +{ + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(1, StreamFake_ReadCallCount(stream)); +} + +TEST(StreamFake, ReadCapturesBuffer) +{ + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(buf, StreamFake_LastReadBuf(stream)); +} + +TEST(StreamFake, ReadCapturesSize) +{ + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(sizeof(buf), StreamFake_LastReadSize(stream)); +} + +TEST(StreamFake, ReadReturnsConfiguredValue) +{ + StreamFake_SetReadReturn(stream, 5); + char buf[16]; + ssize_t n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(5, n); +} + +TEST(StreamFake, CloseIncrementsCount) +{ + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, StreamFake_CloseCallCount(stream)); +} From c6186f061d8055ec8579a897218d388b04125f89 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 21:51:37 +0100 Subject: [PATCH 06/19] S3.7 series C cycles 1-7: TlsStream skeleton in Platform/OpenSsl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Platform/OpenSsl/ tier holding the OpenSSL-based TLS stream. Decoupled from POSIX — takes any SolidSyslogStream* as its injected byte transport, wires OpenSSL on top. File layout: - Platform/OpenSsl/Interface/SolidSyslogTlsStream.h - Platform/OpenSsl/Source/SolidSyslogTlsStream.c - Platform/OpenSsl/CMakeLists.txt — find_package(OpenSSL REQUIRED), compiles into libSolidSyslog.a. Headers known at compile time; libssl linked by real consumers or replaced by OpenSslFake in tests. - Top-level CMakeLists adds the subdirectory under SOLIDSYSLOG_POSIX for now (will generalise when/if we want Windows OpenSSL). TDD cycles: - C1 CreateSucceeds — scaffold - C2 OpenOpensTransport + OpenPassesAddressToTransport — delegate to injected transport (StreamFake verifies via Open call count + arg) - C3 OpenCreatesSslContext — first OpenSSL fake detour - C4 OpenLoadsCaBundleFromConfig — caBundlePath flows from config - C5 OpenRequiresPeerVerification — SSL_VERIFY_PEER - C6 OpenSetsTls12Floor — SSL_CTX_ctrl via SET_MIN_PROTO_VERSION cmd - C7 OpenCreatesSslSession + OpenPassesCtxFromCtxNewToSslNew — the pointer-chain verification David called out: fake captures args AND return values, tests assert plumbing is consistent. Added OpenSslFake_LastCtxReturned + OpenSslFake_LastSslNewCtxArg. Refactor after green: extracted CreateSslContext() helper from Open. Tests: 629 total (was 610; +19 new). All green. Full build clean. Next: BIO setup (SSL_set_bio replacing SSL_set_fd), SSL_connect, hostname/SNI, then Send/Close/Destroy. --- CMakeLists.txt | 1 + Platform/OpenSsl/CMakeLists.txt | 17 +++ .../OpenSsl/Interface/SolidSyslogTlsStream.h | 20 +++ .../OpenSsl/Source/SolidSyslogTlsStream.c | 44 +++++++ Tests/CMakeLists.txt | 2 + Tests/OpenSslFakeTest.cpp | 71 +++++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 89 ++++++++++++++ Tests/Support/CMakeLists.txt | 1 + Tests/Support/OpenSslFake.c | 114 ++++++++++++++++++ Tests/Support/OpenSslFake.h | 24 ++++ 10 files changed, 383 insertions(+) create mode 100644 Platform/OpenSsl/CMakeLists.txt create mode 100644 Platform/OpenSsl/Interface/SolidSyslogTlsStream.h create mode 100644 Platform/OpenSsl/Source/SolidSyslogTlsStream.c create mode 100644 Tests/OpenSslFakeTest.cpp create mode 100644 Tests/SolidSyslogTlsStreamTest.cpp create mode 100644 Tests/Support/OpenSslFake.c create mode 100644 Tests/Support/OpenSslFake.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ab1b8e9..05c65151 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,7 @@ add_subdirectory(Source) if(SOLIDSYSLOG_POSIX) add_subdirectory(Platform/Posix) + add_subdirectory(Platform/OpenSsl) endif() if(HAVE_WINDOWS_INTERLOCKED OR HAVE_WINDOWS_PLATFORM OR SOLIDSYSLOG_WINSOCK) diff --git a/Platform/OpenSsl/CMakeLists.txt b/Platform/OpenSsl/CMakeLists.txt new file mode 100644 index 00000000..32a77f20 --- /dev/null +++ b/Platform/OpenSsl/CMakeLists.txt @@ -0,0 +1,17 @@ +find_package(OpenSSL REQUIRED) + +target_sources(${PROJECT_NAME} PRIVATE + Source/SolidSyslogTlsStream.c +) + +# Headers at compile time; libssl is linked by consumers (real linkage) or +# replaced by OpenSslFake in tests — not declared as a library link here. +target_include_directories(${PROJECT_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Interface + PRIVATE ${OPENSSL_INCLUDE_DIR} +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Interface/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h" +) diff --git a/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h new file mode 100644 index 00000000..f6855aca --- /dev/null +++ b/Platform/OpenSsl/Interface/SolidSyslogTlsStream.h @@ -0,0 +1,20 @@ +#ifndef SOLIDSYSLOGTLSSTREAM_H +#define SOLIDSYSLOGTLSSTREAM_H + +#include "SolidSyslogStream.h" + +EXTERN_C_BEGIN + + struct SolidSyslogTlsStreamConfig + { + struct SolidSyslogStream* transport; /* underlying byte stream — caller owns */ + const char* caBundlePath; /* PEM file of trust anchors */ + const char* serverName; /* SNI + cert hostname check; NULL to skip */ + }; + + struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config); + void SolidSyslogTlsStream_Destroy(void); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGTLSSTREAM_H */ diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c new file mode 100644 index 00000000..b13be7cd --- /dev/null +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -0,0 +1,44 @@ +#include "SolidSyslogStreamDefinition.h" +#include "SolidSyslogTlsStream.h" + +#include + +struct SolidSyslogTlsStream +{ + struct SolidSyslogStream base; + struct SolidSyslogTlsStreamConfig config; +}; + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static SSL_CTX* CreateSslContext(const char* caBundlePath); + +static struct SolidSyslogTlsStream instance; + +struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config) +{ + instance.config = *config; + instance.base.Open = Open; + return &instance.base; +} + +void SolidSyslogTlsStream_Destroy(void) +{ +} + +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + SolidSyslogStream_Open(stream->config.transport, addr); + SSL_CTX* ctx = CreateSslContext(stream->config.caBundlePath); + SSL_new(ctx); + return true; +} + +static SSL_CTX* CreateSslContext(const char* caBundlePath) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_load_verify_locations(ctx, caBundlePath, NULL); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + return ctx; +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index c885abd9..ed1a5eb4 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -60,6 +60,8 @@ if(SOLIDSYSLOG_POSIX) SolidSyslogGetAddrInfoResolverTest.cpp SolidSyslogPosixDatagramTest.cpp SolidSyslogPosixTcpStreamTest.cpp + SolidSyslogTlsStreamTest.cpp + OpenSslFakeTest.cpp SocketFakeTest.cpp ) endif() diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp new file mode 100644 index 00000000..c1667ea1 --- /dev/null +++ b/Tests/OpenSslFakeTest.cpp @@ -0,0 +1,71 @@ +#include "CppUTest/TestHarness.h" +#include "OpenSslFake.h" +#include + +TEST_GROUP(OpenSslFake) +{ + void setup() override { OpenSslFake_Reset(); } +}; + +TEST(OpenSslFake, CtxNewCountIsZeroAfterReset) +{ + LONGS_EQUAL(0, OpenSslFake_CtxNewCallCount()); +} + +TEST(OpenSslFake, CtxNewIncrementsCount) +{ + SSL_CTX_new(TLS_client_method()); + LONGS_EQUAL(1, OpenSslFake_CtxNewCallCount()); +} + +TEST(OpenSslFake, CtxNewReturnsNonNull) +{ + CHECK_TRUE(SSL_CTX_new(TLS_client_method()) != nullptr); +} + +TEST(OpenSslFake, LoadVerifyLocationsCapturesCaPath) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_load_verify_locations(ctx, "/my/ca.pem", nullptr); + STRCMP_EQUAL("/my/ca.pem", OpenSslFake_LastCaBundlePath()); +} + +TEST(OpenSslFake, SetVerifyCapturesMode) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr); + LONGS_EQUAL(SSL_VERIFY_PEER, OpenSslFake_LastVerifyMode()); +} + +TEST(OpenSslFake, SetMinProtoVersionCapturesVersion) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + LONGS_EQUAL(TLS1_2_VERSION, OpenSslFake_LastMinProtoVersion()); +} + +TEST(OpenSslFake, CtxNewReturnValueIsSurfaced) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + POINTERS_EQUAL(ctx, OpenSslFake_LastCtxReturned()); +} + +TEST(OpenSslFake, SslNewIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_new(ctx); + LONGS_EQUAL(1, OpenSslFake_SslNewCallCount()); +} + +TEST(OpenSslFake, SslNewReturnsNonNull) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + CHECK_TRUE(SSL_new(ctx) != nullptr); +} + +TEST(OpenSslFake, SslNewCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_new(ctx); + POINTERS_EQUAL(ctx, OpenSslFake_LastSslNewCtxArg()); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp new file mode 100644 index 00000000..4937ea09 --- /dev/null +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -0,0 +1,89 @@ +#include "CppUTest/TestHarness.h" +#include "OpenSslFake.h" +#include "SolidSyslogAddress.h" +#include "SolidSyslogStream.h" +#include "SolidSyslogTlsStream.h" +#include "StreamFake.h" +#include + +// clang-format off +TEST_GROUP(SolidSyslogTlsStream) +{ + struct SolidSyslogStream* transport = nullptr; + struct SolidSyslogTlsStreamConfig config = {}; + struct SolidSyslogStream* stream = nullptr; + SolidSyslogAddressStorage addrStorage = {0}; + struct SolidSyslogAddress* addr = nullptr; + + void setup() override + { + OpenSslFake_Reset(); + transport = StreamFake_Create(); + config.transport = transport; + stream = SolidSyslogTlsStream_Create(&config); + addr = SolidSyslogAddress_FromStorage(&addrStorage); + } + + void teardown() override + { + SolidSyslogTlsStream_Destroy(); + StreamFake_Destroy(transport); + } +}; +// clang-format on + +TEST(SolidSyslogTlsStream, CreateSucceeds) +{ + CHECK_TRUE(stream != nullptr); +} + +TEST(SolidSyslogTlsStream, OpenOpensTransport) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, StreamFake_OpenCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, OpenPassesAddressToTransport) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(addr, StreamFake_LastOpenAddr(transport)); +} + +TEST(SolidSyslogTlsStream, OpenCreatesSslContext) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_CtxNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenLoadsCaBundleFromConfig) +{ + SolidSyslogTlsStream_Destroy(); + config.caBundlePath = "/some/path/ca.pem"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + STRCMP_EQUAL("/some/path/ca.pem", OpenSslFake_LastCaBundlePath()); +} + +TEST(SolidSyslogTlsStream, OpenRequiresPeerVerification) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(SSL_VERIFY_PEER, OpenSslFake_LastVerifyMode()); +} + +TEST(SolidSyslogTlsStream, OpenSetsTls12Floor) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(TLS1_2_VERSION, OpenSslFake_LastMinProtoVersion()); +} + +TEST(SolidSyslogTlsStream, OpenCreatesSslSession) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_SslNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromCtxNewToSslNew) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSslNewCtxArg()); +} diff --git a/Tests/Support/CMakeLists.txt b/Tests/Support/CMakeLists.txt index e37864fa..ec3d8694 100644 --- a/Tests/Support/CMakeLists.txt +++ b/Tests/Support/CMakeLists.txt @@ -1,6 +1,7 @@ if(SOLIDSYSLOG_POSIX) set(POSIX_FAKES_SOURCES ClockFake.c + OpenSslFake.c SafeStringStandard.c SocketFake.c ) diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c new file mode 100644 index 00000000..e185ab3c --- /dev/null +++ b/Tests/Support/OpenSslFake.c @@ -0,0 +1,114 @@ +#include "OpenSslFake.h" + +#include +#include + +static int ctxNewCallCount; +static const char* lastCaBundlePath; +static int lastVerifyMode; +static long lastMinProtoVersion; +static int sslNewCallCount; +static SSL_CTX* lastSslNewCtxArg; +static char fakeCtxStorage; +static char fakeMethodStorage; +static char fakeSslStorage; + +void OpenSslFake_Reset(void) +{ + ctxNewCallCount = 0; + lastCaBundlePath = NULL; + lastVerifyMode = 0; + lastMinProtoVersion = 0; + sslNewCallCount = 0; + lastSslNewCtxArg = NULL; +} + +int OpenSslFake_CtxNewCallCount(void) +{ + return ctxNewCallCount; +} + +const char* OpenSslFake_LastCaBundlePath(void) +{ + return lastCaBundlePath; +} + +int OpenSslFake_LastVerifyMode(void) +{ + return lastVerifyMode; +} + +long OpenSslFake_LastMinProtoVersion(void) +{ + return lastMinProtoVersion; +} + +SSL_CTX* OpenSslFake_LastCtxReturned(void) +{ + return (SSL_CTX*) &fakeCtxStorage; +} + +int OpenSslFake_SslNewCallCount(void) +{ + return sslNewCallCount; +} + +SSL* OpenSslFake_LastSslReturned(void) +{ + return (SSL*) &fakeSslStorage; +} + +SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) +{ + return lastSslNewCtxArg; +} + +/* Link-time substitution for OpenSSL — replaces libssl symbols in the test + * binary. Production links real libssl; tests never link -lssl. */ + +const SSL_METHOD* TLS_client_method(void) +{ + return (const SSL_METHOD*) &fakeMethodStorage; +} + +SSL_CTX* SSL_CTX_new(const SSL_METHOD* method) +{ + (void) method; + ctxNewCallCount++; + return (SSL_CTX*) &fakeCtxStorage; +} + +SSL* SSL_new(SSL_CTX* ctx) +{ + sslNewCallCount++; + lastSslNewCtxArg = ctx; + return (SSL*) &fakeSslStorage; +} + +int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) +{ + (void) ctx; + (void) CApath; + lastCaBundlePath = CAfile; + return 1; +} + +void SSL_CTX_set_verify(SSL_CTX* ctx, int mode, SSL_verify_cb verify_callback) +{ + (void) ctx; + (void) verify_callback; + lastVerifyMode = mode; +} + +/* SSL_CTX_set_min_proto_version is a macro forwarding to SSL_CTX_ctrl; fake by + * intercepting the ctrl call for the min-proto command only. */ +long SSL_CTX_ctrl(SSL_CTX* ctx, int cmd, long larg, void* parg) +{ + (void) ctx; + (void) parg; + if (cmd == SSL_CTRL_SET_MIN_PROTO_VERSION) + { + lastMinProtoVersion = larg; + } + return 1; +} diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h new file mode 100644 index 00000000..fc671c8c --- /dev/null +++ b/Tests/Support/OpenSslFake.h @@ -0,0 +1,24 @@ +#ifndef OPENSSLFAKE_H +#define OPENSSLFAKE_H + +#include "ExternC.h" + +/* Forward-declared OpenSSL types — full definitions live in . */ +struct ssl_ctx_st; +struct ssl_st; + +EXTERN_C_BEGIN + + void OpenSslFake_Reset(void); + int OpenSslFake_CtxNewCallCount(void); + struct ssl_ctx_st* OpenSslFake_LastCtxReturned(void); + const char* OpenSslFake_LastCaBundlePath(void); + int OpenSslFake_LastVerifyMode(void); + long OpenSslFake_LastMinProtoVersion(void); + int OpenSslFake_SslNewCallCount(void); + struct ssl_st* OpenSslFake_LastSslReturned(void); + struct ssl_ctx_st* OpenSslFake_LastSslNewCtxArg(void); + +EXTERN_C_END + +#endif /* OPENSSLFAKE_H */ From f77abb56a492240e3044e47933c6ff0cbd86e959 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 21:56:42 +0100 Subject: [PATCH 07/19] S3.7 series C cycles 8-10: BIO-based TLS I/O + handshake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decoupled Open now wires OpenSSL via a custom BIO (not SSL_set_fd), setting up the seam where BIO read/write callbacks will delegate to the injected SolidSyslogStream transport. Cycles: - C8 OpenCreatesBio — BIO_meth_new + BIO_new. Fake gains BIO surface: BIO_meth_new, BIO_new, LastBioReturned accessor. Opaque bio_st / BIO_METHOD types get static-char storage like SSL_CTX/SSL. - C9 OpenSetsBioOnSsl + OpenPassesSslFromNewToSetBio + OpenPassesBioFromNewToSetBio — SSL_set_bio; fake captures both SSL* and BIO* args so pointer-chain plumbing is verifiable. - C10 OpenPerformsHandshake + OpenPassesSslToConnect — SSL_connect; fake captures SSL arg, default-returns success. Refactor after C9: extracted CreateTransportBio() helper from Open. Open now reads: delegate transport.Open → CreateSslContext → SSL_new → CreateTransportBio → SSL_set_bio → SSL_connect. Each OpenSSL handoff has a pointer-chain test now (CtxNew→SslNew, SslNew→SetBio, BioNew→SetBio, SslNew→Connect). Tests: 644 total (+15). All green. Full build clean. Next: hostname/SNI (SSL_set1_host / SSL_set_tlsext_host_name), then Send/Close/Destroy. --- .../OpenSsl/Source/SolidSyslogTlsStream.c | 12 ++- Tests/OpenSslFakeTest.cpp | 73 +++++++++++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 36 +++++++++ Tests/Support/OpenSslFake.c | 78 +++++++++++++++++++ Tests/Support/OpenSslFake.h | 8 ++ 5 files changed, 206 insertions(+), 1 deletion(-) diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index b13be7cd..b63b723e 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -11,6 +11,7 @@ struct SolidSyslogTlsStream static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); static SSL_CTX* CreateSslContext(const char* caBundlePath); +static BIO* CreateTransportBio(void); static struct SolidSyslogTlsStream instance; @@ -30,10 +31,19 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; SolidSyslogStream_Open(stream->config.transport, addr); SSL_CTX* ctx = CreateSslContext(stream->config.caBundlePath); - SSL_new(ctx); + SSL* ssl = SSL_new(ctx); + BIO* bio = CreateTransportBio(); + SSL_set_bio(ssl, bio, bio); + SSL_connect(ssl); return true; } +static BIO* CreateTransportBio(void) +{ + BIO_METHOD* method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "SolidSyslog transport BIO"); + return BIO_new(method); +} + static SSL_CTX* CreateSslContext(const char* caBundlePath) { SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index c1667ea1..49554825 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -69,3 +69,76 @@ TEST(OpenSslFake, SslNewCapturesCtxArg) SSL_new(ctx); POINTERS_EQUAL(ctx, OpenSslFake_LastSslNewCtxArg()); } + +TEST(OpenSslFake, BioNewIncrementsCount) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_new(method); + LONGS_EQUAL(1, OpenSslFake_BioNewCallCount()); +} + +TEST(OpenSslFake, BioNewReturnsNonNull) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + CHECK_TRUE(BIO_new(method) != nullptr); +} + +TEST(OpenSslFake, BioNewReturnValueIsSurfaced) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + POINTERS_EQUAL(bio, OpenSslFake_LastBioReturned()); +} + +TEST(OpenSslFake, SetBioIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + SSL_set_bio(ssl, bio, bio); + LONGS_EQUAL(1, OpenSslFake_SetBioCallCount()); +} + +TEST(OpenSslFake, SetBioCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + SSL_set_bio(ssl, bio, bio); + POINTERS_EQUAL(ssl, OpenSslFake_LastSetBioSslArg()); +} + +TEST(OpenSslFake, SetBioCapturesReadBioArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + SSL_set_bio(ssl, bio, bio); + POINTERS_EQUAL(bio, OpenSslFake_LastSetBioReadBioArg()); +} + +TEST(OpenSslFake, ConnectIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_connect(ssl); + LONGS_EQUAL(1, OpenSslFake_ConnectCallCount()); +} + +TEST(OpenSslFake, ConnectCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_connect(ssl); + POINTERS_EQUAL(ssl, OpenSslFake_LastConnectSslArg()); +} + +TEST(OpenSslFake, ConnectDefaultsToSuccess) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + LONGS_EQUAL(1, SSL_connect(ssl)); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index 4937ea09..d5f7da62 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -87,3 +87,39 @@ TEST(SolidSyslogTlsStream, OpenPassesCtxFromCtxNewToSslNew) SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSslNewCtxArg()); } + +TEST(SolidSyslogTlsStream, OpenCreatesBio) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_BioNewCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenSetsBioOnSsl) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_SetBioCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslFromNewToSetBio) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSetBioSslArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioFromNewToSetBio) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioReturned(), OpenSslFake_LastSetBioReadBioArg()); +} + +TEST(SolidSyslogTlsStream, OpenPerformsHandshake) +{ + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(1, OpenSslFake_ConnectCallCount()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslToConnect) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastConnectSslArg()); +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index e185ab3c..53d2567c 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -9,9 +9,17 @@ static int lastVerifyMode; static long lastMinProtoVersion; static int sslNewCallCount; static SSL_CTX* lastSslNewCtxArg; +static int bioNewCallCount; +static int setBioCallCount; +static SSL* lastSetBioSslArg; +static BIO* lastSetBioReadBioArg; +static int connectCallCount; +static SSL* lastConnectSslArg; static char fakeCtxStorage; static char fakeMethodStorage; static char fakeSslStorage; +static char fakeBioMethStorage; +static char fakeBioStorage; void OpenSslFake_Reset(void) { @@ -21,6 +29,12 @@ void OpenSslFake_Reset(void) lastMinProtoVersion = 0; sslNewCallCount = 0; lastSslNewCtxArg = NULL; + bioNewCallCount = 0; + setBioCallCount = 0; + lastSetBioSslArg = NULL; + lastSetBioReadBioArg = NULL; + connectCallCount = 0; + lastConnectSslArg = NULL; } int OpenSslFake_CtxNewCallCount(void) @@ -63,6 +77,41 @@ SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) return lastSslNewCtxArg; } +int OpenSslFake_BioNewCallCount(void) +{ + return bioNewCallCount; +} + +BIO* OpenSslFake_LastBioReturned(void) +{ + return (BIO*) &fakeBioStorage; +} + +int OpenSslFake_SetBioCallCount(void) +{ + return setBioCallCount; +} + +SSL* OpenSslFake_LastSetBioSslArg(void) +{ + return lastSetBioSslArg; +} + +BIO* OpenSslFake_LastSetBioReadBioArg(void) +{ + return lastSetBioReadBioArg; +} + +int OpenSslFake_ConnectCallCount(void) +{ + return connectCallCount; +} + +SSL* OpenSslFake_LastConnectSslArg(void) +{ + return lastConnectSslArg; +} + /* Link-time substitution for OpenSSL — replaces libssl symbols in the test * binary. Production links real libssl; tests never link -lssl. */ @@ -85,6 +134,35 @@ SSL* SSL_new(SSL_CTX* ctx) return (SSL*) &fakeSslStorage; } +BIO_METHOD* BIO_meth_new(int type, const char* name) +{ + (void) type; + (void) name; + return (BIO_METHOD*) &fakeBioMethStorage; +} + +BIO* BIO_new(const BIO_METHOD* method) +{ + (void) method; + bioNewCallCount++; + return (BIO*) &fakeBioStorage; +} + +void SSL_set_bio(SSL* ssl, BIO* rbio, BIO* wbio) +{ + (void) wbio; + setBioCallCount++; + lastSetBioSslArg = ssl; + lastSetBioReadBioArg = rbio; +} + +int SSL_connect(SSL* ssl) +{ + connectCallCount++; + lastConnectSslArg = ssl; + return 1; +} + int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) { (void) ctx; diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index fc671c8c..4aeacf60 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -6,6 +6,7 @@ /* Forward-declared OpenSSL types — full definitions live in . */ struct ssl_ctx_st; struct ssl_st; +struct bio_st; EXTERN_C_BEGIN @@ -18,6 +19,13 @@ EXTERN_C_BEGIN int OpenSslFake_SslNewCallCount(void); struct ssl_st* OpenSslFake_LastSslReturned(void); struct ssl_ctx_st* OpenSslFake_LastSslNewCtxArg(void); + int OpenSslFake_BioNewCallCount(void); + struct bio_st* OpenSslFake_LastBioReturned(void); + int OpenSslFake_SetBioCallCount(void); + struct ssl_st* OpenSslFake_LastSetBioSslArg(void); + struct bio_st* OpenSslFake_LastSetBioReadBioArg(void); + int OpenSslFake_ConnectCallCount(void); + struct ssl_st* OpenSslFake_LastConnectSslArg(void); EXTERN_C_END From 37809b8cf872972bf03cf52c4f186df7c8a496f4 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 22:08:05 +0100 Subject: [PATCH 08/19] S3.7 series C cycles 11-15: hostname, SNI, BIO callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the Open sequence of the decoupled TLS stream. - C11 OpenSetsSniHostnameFromConfig — SSL_set_tlsext_host_name (routed via SSL_ctrl + SSL_CTRL_SET_TLSEXT_HOSTNAME) - C12 OpenSetsExpectedCertHostname + OpenSkipsHostnameSetupWhenServerNameIsNull — SSL_set1_host, wrapped in a null-check so "no hostname verification" is the documented NULL-serverName mode. - C13 OpenAttachesTransportAsBioData — BIO_set_data stores the injected transport pointer on the BIO so callbacks can retrieve it. Fake BIO_get_data returns last-set data. - C14 BioReadCallbackDelegatesToTransportRead — wires BIO_meth_set_read with TransportBioRead; test captures the callback and invokes it directly, asserting StreamFake.Read was called on the injected transport. This is the key proof that OpenSSL I/O will run through our transport when libssl is real. - C15 BioWriteCallbackDelegatesToTransportSend — mirror for write. Also: tests verify hostname setup is skipped entirely when config.serverName is NULL (trust-anchor-only mode). Tests: 656 total (was 644; +12). All green. Full build clean. Next: Send (SSL_write), Close (SSL_shutdown + SSL_free), Destroy (SSL_CTX_free + BIO_meth_free), all with the fake-assisted pattern. --- .../OpenSsl/Source/SolidSyslogTlsStream.c | 22 +++++ Tests/OpenSslFakeTest.cpp | 64 +++++++++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 51 ++++++++++++ Tests/Support/OpenSslFake.c | 81 +++++++++++++++++++ Tests/Support/OpenSslFake.h | 5 ++ 5 files changed, 223 insertions(+) diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index b63b723e..7b14c90e 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -12,6 +12,8 @@ struct SolidSyslogTlsStream static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); static SSL_CTX* CreateSslContext(const char* caBundlePath); static BIO* CreateTransportBio(void); +static int TransportBioRead(BIO* bio, char* buffer, int size); +static int TransportBioWrite(BIO* bio, const char* buffer, int size); static struct SolidSyslogTlsStream instance; @@ -33,7 +35,13 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress SSL_CTX* ctx = CreateSslContext(stream->config.caBundlePath); SSL* ssl = SSL_new(ctx); BIO* bio = CreateTransportBio(); + BIO_set_data(bio, stream->config.transport); SSL_set_bio(ssl, bio, bio); + if (stream->config.serverName != NULL) + { + SSL_set_tlsext_host_name(ssl, stream->config.serverName); + SSL_set1_host(ssl, stream->config.serverName); + } SSL_connect(ssl); return true; } @@ -41,9 +49,23 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress static BIO* CreateTransportBio(void) { BIO_METHOD* method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "SolidSyslog transport BIO"); + BIO_meth_set_read(method, TransportBioRead); + BIO_meth_set_write(method, TransportBioWrite); return BIO_new(method); } +static int TransportBioRead(BIO* bio, char* buffer, int size) +{ + struct SolidSyslogStream* transport = (struct SolidSyslogStream*) BIO_get_data(bio); + return (int) SolidSyslogStream_Read(transport, buffer, (size_t) size); +} + +static int TransportBioWrite(BIO* bio, const char* buffer, int size) +{ + struct SolidSyslogStream* transport = (struct SolidSyslogStream*) BIO_get_data(bio); + return SolidSyslogStream_Send(transport, buffer, (size_t) size) ? size : -1; +} + static SSL_CTX* CreateSslContext(const char* caBundlePath) { SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index 49554825..c0381aa1 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -142,3 +142,67 @@ TEST(OpenSslFake, ConnectDefaultsToSuccess) SSL* ssl = SSL_new(ctx); LONGS_EQUAL(1, SSL_connect(ssl)); } + +TEST(OpenSslFake, SetTlsExtHostNameCapturesHostname) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set_tlsext_host_name(ssl, "host.example"); + STRCMP_EQUAL("host.example", OpenSslFake_LastSniHostname()); +} + +TEST(OpenSslFake, Set1HostCapturesHostname) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set1_host(ssl, "host.example"); + STRCMP_EQUAL("host.example", OpenSslFake_LastSet1Host()); +} + +TEST(OpenSslFake, BioSetDataCapturesData) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel; + BIO_set_data(bio, &sentinel); + POINTERS_EQUAL(&sentinel, OpenSslFake_LastSetDataArg()); +} + +TEST(OpenSslFake, BioGetDataReturnsPreviouslySetData) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel; + BIO_set_data(bio, &sentinel); + POINTERS_EQUAL(&sentinel, BIO_get_data(bio)); +} + +static int DummyRead(BIO* bio, char* buf, int size) +{ + (void) bio; + (void) buf; + (void) size; + return 0; +} + +TEST(OpenSslFake, BioMethSetReadCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_read(method, DummyRead); + POINTERS_EQUAL((void*) DummyRead, (void*) OpenSslFake_LastBioReadCallback()); +} + +static int DummyWrite(BIO* bio, const char* buf, int size) +{ + (void) bio; + (void) buf; + (void) size; + return 0; +} + +TEST(OpenSslFake, BioMethSetWriteCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_write(method, DummyWrite); + POINTERS_EQUAL((void*) DummyWrite, (void*) OpenSslFake_LastBioWriteCallback()); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index d5f7da62..034d7e96 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -123,3 +123,54 @@ TEST(SolidSyslogTlsStream, OpenPassesSslToConnect) SolidSyslogStream_Open(stream, addr); POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastConnectSslArg()); } + +TEST(SolidSyslogTlsStream, OpenSetsSniHostnameFromConfig) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + STRCMP_EQUAL("logs.example", OpenSslFake_LastSniHostname()); +} + +TEST(SolidSyslogTlsStream, OpenSetsExpectedCertHostname) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + STRCMP_EQUAL("logs.example", OpenSslFake_LastSet1Host()); +} + +TEST(SolidSyslogTlsStream, OpenSkipsHostnameSetupWhenServerNameIsNull) +{ + /* Default config.serverName is NULL */ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(NULL, OpenSslFake_LastSet1Host()); +} + +TEST(SolidSyslogTlsStream, OpenAttachesTransportAsBioData) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(transport, OpenSslFake_LastSetDataArg()); +} + +TEST(SolidSyslogTlsStream, BioReadCallbackDelegatesToTransportRead) +{ + SolidSyslogStream_Open(stream, addr); + int (*readFn)(BIO*, char*, int) = OpenSslFake_LastBioReadCallback(); + CHECK_TRUE(readFn != nullptr); + char buf[16]; + readFn(OpenSslFake_LastBioReturned(), buf, sizeof(buf)); + LONGS_EQUAL(1, StreamFake_ReadCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, BioWriteCallbackDelegatesToTransportSend) +{ + SolidSyslogStream_Open(stream, addr); + int (*writeFn)(BIO*, const char*, int) = OpenSslFake_LastBioWriteCallback(); + CHECK_TRUE(writeFn != nullptr); + const char msg[] = "hi"; + writeFn(OpenSslFake_LastBioReturned(), msg, (int) sizeof(msg)); + LONGS_EQUAL(1, StreamFake_SendCallCount(transport)); +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 53d2567c..4fe822aa 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -15,6 +15,11 @@ static SSL* lastSetBioSslArg; static BIO* lastSetBioReadBioArg; static int connectCallCount; static SSL* lastConnectSslArg; +static const char* lastSniHostname; +static const char* lastSet1Host; +static void* lastSetDataArg; +static int (*lastBioReadCallback)(BIO*, char*, int); +static int (*lastBioWriteCallback)(BIO*, const char*, int); static char fakeCtxStorage; static char fakeMethodStorage; static char fakeSslStorage; @@ -35,6 +40,11 @@ void OpenSslFake_Reset(void) lastSetBioReadBioArg = NULL; connectCallCount = 0; lastConnectSslArg = NULL; + lastSniHostname = NULL; + lastSet1Host = NULL; + lastSetDataArg = NULL; + lastBioReadCallback = NULL; + lastBioWriteCallback = NULL; } int OpenSslFake_CtxNewCallCount(void) @@ -112,6 +122,31 @@ SSL* OpenSslFake_LastConnectSslArg(void) return lastConnectSslArg; } +const char* OpenSslFake_LastSniHostname(void) +{ + return lastSniHostname; +} + +const char* OpenSslFake_LastSet1Host(void) +{ + return lastSet1Host; +} + +void* OpenSslFake_LastSetDataArg(void) +{ + return lastSetDataArg; +} + +int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) +{ + return lastBioReadCallback; +} + +int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) +{ + return lastBioWriteCallback; +} + /* Link-time substitution for OpenSSL — replaces libssl symbols in the test * binary. Production links real libssl; tests never link -lssl. */ @@ -163,6 +198,52 @@ int SSL_connect(SSL* ssl) return 1; } +/* SSL_set_tlsext_host_name is a macro forwarding to SSL_ctrl; fake intercepts + * the SET_TLSEXT_HOSTNAME command and captures the hostname pointer. */ +long SSL_ctrl(SSL* ssl, int cmd, long larg, void* parg) +{ + (void) ssl; + (void) larg; + if (cmd == SSL_CTRL_SET_TLSEXT_HOSTNAME) + { + lastSniHostname = (const char*) parg; + } + return 1; +} + +int SSL_set1_host(SSL* ssl, const char* hostname) +{ + (void) ssl; + lastSet1Host = hostname; + return 1; +} + +void BIO_set_data(BIO* bio, void* data) +{ + (void) bio; + lastSetDataArg = data; +} + +void* BIO_get_data(BIO* bio) +{ + (void) bio; + return lastSetDataArg; +} + +int BIO_meth_set_read(BIO_METHOD* method, int (*read)(BIO*, char*, int)) +{ + (void) method; + lastBioReadCallback = read; + return 1; +} + +int BIO_meth_set_write(BIO_METHOD* method, int (*write)(BIO*, const char*, int)) +{ + (void) method; + lastBioWriteCallback = write; + return 1; +} + int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) { (void) ctx; diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index 4aeacf60..cbe471d3 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -26,6 +26,11 @@ EXTERN_C_BEGIN struct bio_st* OpenSslFake_LastSetBioReadBioArg(void); int OpenSslFake_ConnectCallCount(void); struct ssl_st* OpenSslFake_LastConnectSslArg(void); + const char* OpenSslFake_LastSniHostname(void); + const char* OpenSslFake_LastSet1Host(void); + void* OpenSslFake_LastSetDataArg(void); + int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); + int (*OpenSslFake_LastBioWriteCallback(void))(struct bio_st*, const char*, int); EXTERN_C_END From eb5e9e921132081a1a0fb479edd9563e61608e5d Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 22:13:28 +0100 Subject: [PATCH 09/19] S3.7 series C cycles 16-18: Send, Close, Destroy on TlsStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the vtable: TlsStream now implements full Open/Send/Read lifecycle. (Read is inherited from Stream interface via the injected transport — we don't need TLS-level Read at Stream API until someone reads through StreamSender.) - C16 Send: SSL_write. Four tests — count, SSL arg plumbing, buffer pointer, size. Instance now holds the SSL* from Open so Send can reuse it. - C17 Close: SSL_shutdown + SSL_free + transport.Close propagation. Verifies Stream_Close on injected transport fires too. - C18 Destroy: SSL_CTX_free. Instance stores SSL_CTX from Open; Destroy frees with a null-guard so double-Destroy is safe. OpenSSL fake gains: SSL_write (echoes size), SSL_shutdown, SSL_free, SSL_CTX_free. Each with counter and per-arg captures where useful. Tests: 672 total (was 656; +16). All green. Full build clean. Status: SolidSyslogTlsStream is now complete at the unit level — full lifecycle through the decoupled architecture, OpenSSL API calls all verified via fake, BIO callbacks verifiably delegate to the injected transport. Next to close out S3.7: - Wire up Example/Threaded to use PosixTcpStream as transport under a new TLS StreamSender in addition to existing UDP/TCP - Link real libssl into the example binary - Remove @wip tag on tls_transport.feature and green the BDD scenario - Final preset gauntlet + PR --- .../OpenSsl/Source/SolidSyslogTlsStream.c | 43 ++++++++--- Tests/OpenSslFakeTest.cpp | 63 +++++++++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 61 +++++++++++++++ Tests/Support/OpenSslFake.c | 77 +++++++++++++++++++ Tests/Support/OpenSslFake.h | 7 ++ 5 files changed, 242 insertions(+), 9 deletions(-) diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index 7b14c90e..0e852248 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -7,9 +7,13 @@ struct SolidSyslogTlsStream { struct SolidSyslogStream base; struct SolidSyslogTlsStreamConfig config; + SSL_CTX* ctx; + SSL* ssl; }; static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static void Close(struct SolidSyslogStream* self); static SSL_CTX* CreateSslContext(const char* caBundlePath); static BIO* CreateTransportBio(void); static int TransportBioRead(BIO* bio, char* buffer, int size); @@ -19,33 +23,54 @@ static struct SolidSyslogTlsStream instance; struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTlsStreamConfig* config) { - instance.config = *config; - instance.base.Open = Open; + instance.config = *config; + instance.base.Open = Open; + instance.base.Send = Send; + instance.base.Close = Close; return &instance.base; } void SolidSyslogTlsStream_Destroy(void) { + if (instance.ctx != NULL) + { + SSL_CTX_free(instance.ctx); + instance.ctx = NULL; + } } static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) { struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; SolidSyslogStream_Open(stream->config.transport, addr); - SSL_CTX* ctx = CreateSslContext(stream->config.caBundlePath); - SSL* ssl = SSL_new(ctx); - BIO* bio = CreateTransportBio(); + stream->ctx = CreateSslContext(stream->config.caBundlePath); + stream->ssl = SSL_new(stream->ctx); + BIO* bio = CreateTransportBio(); BIO_set_data(bio, stream->config.transport); - SSL_set_bio(ssl, bio, bio); + SSL_set_bio(stream->ssl, bio, bio); if (stream->config.serverName != NULL) { - SSL_set_tlsext_host_name(ssl, stream->config.serverName); - SSL_set1_host(ssl, stream->config.serverName); + SSL_set_tlsext_host_name(stream->ssl, stream->config.serverName); + SSL_set1_host(stream->ssl, stream->config.serverName); } - SSL_connect(ssl); + SSL_connect(stream->ssl); return true; } +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + return SSL_write(stream->ssl, buffer, (int) size) > 0; +} + +static void Close(struct SolidSyslogStream* self) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + SSL_shutdown(stream->ssl); + SSL_free(stream->ssl); + SolidSyslogStream_Close(stream->config.transport); +} + static BIO* CreateTransportBio(void) { BIO_METHOD* method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "SolidSyslog transport BIO"); diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index c0381aa1..085e082f 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -206,3 +206,66 @@ TEST(OpenSslFake, BioMethSetWriteCapturesCallback) BIO_meth_set_write(method, DummyWrite); POINTERS_EQUAL((void*) DummyWrite, (void*) OpenSslFake_LastBioWriteCallback()); } + +TEST(OpenSslFake, WriteIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_write(ssl, "x", 1); + LONGS_EQUAL(1, OpenSslFake_WriteCallCount()); +} + +TEST(OpenSslFake, WriteCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_write(ssl, "x", 1); + POINTERS_EQUAL(ssl, OpenSslFake_LastWriteSslArg()); +} + +TEST(OpenSslFake, WriteCapturesBuffer) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + const char* msg = "payload"; + SSL_write(ssl, msg, 7); + POINTERS_EQUAL(msg, OpenSslFake_LastWriteBuf()); +} + +TEST(OpenSslFake, WriteCapturesSize) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_write(ssl, "payload", 7); + LONGS_EQUAL(7, OpenSslFake_LastWriteSize()); +} + +TEST(OpenSslFake, WriteDefaultsToEchoingSize) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + LONGS_EQUAL(7, SSL_write(ssl, "payload", 7)); +} + +TEST(OpenSslFake, ShutdownIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_shutdown(ssl); + LONGS_EQUAL(1, OpenSslFake_ShutdownCallCount()); +} + +TEST(OpenSslFake, FreeIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_free(ssl); + LONGS_EQUAL(1, OpenSslFake_FreeCallCount()); +} + +TEST(OpenSslFake, CtxFreeIncrementsCount) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_free(ctx); + LONGS_EQUAL(1, OpenSslFake_CtxFreeCallCount()); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index 034d7e96..f29927b8 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -174,3 +174,64 @@ TEST(SolidSyslogTlsStream, BioWriteCallbackDelegatesToTransportSend) writeFn(OpenSslFake_LastBioReturned(), msg, (int) sizeof(msg)); LONGS_EQUAL(1, StreamFake_SendCallCount(transport)); } + +TEST(SolidSyslogTlsStream, SendWritesToSsl) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + LONGS_EQUAL(1, OpenSslFake_WriteCallCount()); +} + +TEST(SolidSyslogTlsStream, SendPassesBufferToSslWrite) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + POINTERS_EQUAL(msg, OpenSslFake_LastWriteBuf()); +} + +TEST(SolidSyslogTlsStream, SendPassesSizeToSslWrite) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + LONGS_EQUAL(sizeof(msg), OpenSslFake_LastWriteSize()); +} + +TEST(SolidSyslogTlsStream, SendPassesSslFromNewToWrite) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hello"; + SolidSyslogStream_Send(stream, msg, sizeof(msg)); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastWriteSslArg()); +} + +TEST(SolidSyslogTlsStream, CloseShutsDownSsl) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, OpenSslFake_ShutdownCallCount()); +} + +TEST(SolidSyslogTlsStream, CloseFreesSsl) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, OpenSslFake_FreeCallCount()); +} + +TEST(SolidSyslogTlsStream, CloseClosesTransport) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + LONGS_EQUAL(1, StreamFake_CloseCallCount(transport)); +} + +TEST(SolidSyslogTlsStream, DestroyFreesSslContext) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogTlsStream_Destroy(); + LONGS_EQUAL(1, OpenSslFake_CtxFreeCallCount()); + /* teardown re-Destroys safely */ +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 4fe822aa..2d9e38d5 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -20,6 +20,13 @@ static const char* lastSet1Host; static void* lastSetDataArg; static int (*lastBioReadCallback)(BIO*, char*, int); static int (*lastBioWriteCallback)(BIO*, const char*, int); +static int writeCallCount; +static SSL* lastWriteSslArg; +static const void* lastWriteBuf; +static int lastWriteSize; +static int shutdownCallCount; +static int freeCallCount; +static int ctxFreeCallCount; static char fakeCtxStorage; static char fakeMethodStorage; static char fakeSslStorage; @@ -45,6 +52,13 @@ void OpenSslFake_Reset(void) lastSetDataArg = NULL; lastBioReadCallback = NULL; lastBioWriteCallback = NULL; + writeCallCount = 0; + lastWriteSslArg = NULL; + lastWriteBuf = NULL; + lastWriteSize = 0; + shutdownCallCount = 0; + freeCallCount = 0; + ctxFreeCallCount = 0; } int OpenSslFake_CtxNewCallCount(void) @@ -147,6 +161,41 @@ int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) return lastBioWriteCallback; } +int OpenSslFake_WriteCallCount(void) +{ + return writeCallCount; +} + +SSL* OpenSslFake_LastWriteSslArg(void) +{ + return lastWriteSslArg; +} + +const void* OpenSslFake_LastWriteBuf(void) +{ + return lastWriteBuf; +} + +int OpenSslFake_LastWriteSize(void) +{ + return lastWriteSize; +} + +int OpenSslFake_ShutdownCallCount(void) +{ + return shutdownCallCount; +} + +int OpenSslFake_FreeCallCount(void) +{ + return freeCallCount; +} + +int OpenSslFake_CtxFreeCallCount(void) +{ + return ctxFreeCallCount; +} + /* Link-time substitution for OpenSSL — replaces libssl symbols in the test * binary. Production links real libssl; tests never link -lssl. */ @@ -244,6 +293,34 @@ int BIO_meth_set_write(BIO_METHOD* method, int (*write)(BIO*, const char*, int)) return 1; } +int SSL_write(SSL* ssl, const void* buf, int num) +{ + writeCallCount++; + lastWriteSslArg = ssl; + lastWriteBuf = buf; + lastWriteSize = num; + return num; +} + +int SSL_shutdown(SSL* ssl) +{ + (void) ssl; + shutdownCallCount++; + return 1; +} + +void SSL_free(SSL* ssl) +{ + (void) ssl; + freeCallCount++; +} + +void SSL_CTX_free(SSL_CTX* ctx) +{ + (void) ctx; + ctxFreeCallCount++; +} + int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) { (void) ctx; diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index cbe471d3..02bbad94 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -31,6 +31,13 @@ EXTERN_C_BEGIN void* OpenSslFake_LastSetDataArg(void); int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); int (*OpenSslFake_LastBioWriteCallback(void))(struct bio_st*, const char*, int); + int OpenSslFake_WriteCallCount(void); + struct ssl_st* OpenSslFake_LastWriteSslArg(void); + const void* OpenSslFake_LastWriteBuf(void); + int OpenSslFake_LastWriteSize(void); + int OpenSslFake_ShutdownCallCount(void); + int OpenSslFake_FreeCallCount(void); + int OpenSslFake_CtxFreeCallCount(void); EXTERN_C_END From a68b5f65bcb57e5ab3c34da486ae9e565afebd35 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 22:22:36 +0100 Subject: [PATCH 10/19] S3.7 fake + test hardening: every arg captured, every chain verified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every discarded arg in the OpenSslFake has been promoted to a captured value with an accessor; every TlsStream claim that used to be "API X was called N times" now has a sibling test proving the args were the handles returned by the preceding call. Fake additions (15 new captures): - SSL_CTX_new: method arg - SSL_CTX_load_verify_locations: ctx arg (kept CAfile) - SSL_CTX_set_verify: ctx arg (kept mode) - SSL_CTX_ctrl: ctx arg (kept cmd+larg for SET_MIN_PROTO_VERSION) - BIO_meth_set_read: method arg (kept callback) - BIO_meth_set_write: method arg (kept callback) - BIO_new: method arg - BIO_set_data: bio arg (kept data) - BIO_get_data: bio arg (proves TransportBioRead resolves the right BIO) - SSL_set_bio: wbio arg (kept ssl + rbio) - SSL_ctrl: ssl arg (kept SNI hostname) - SSL_set1_host: ssl arg (kept hostname) - SSL_shutdown: ssl arg - SSL_free: ssl arg - SSL_CTX_free: ctx arg Plus BIO_meth_new return-value accessor for chain assertions. Restructured OpenSslFake.{h,c} with per-function groupings for captures, reset, accessors, and link-interposed functions, so adding the next API call has a clean location. Pointer-chain tests in SolidSyslogTlsStreamTest.cpp (15 new): - CTX chain: ClientMethod -> CtxNew, CtxNew -> LoadVerify/SetVerify/ SetMinProto/SslNew/CtxFree - SSL chain: SslNew -> SetBio/SslCtrl(SNI)/Set1Host/Connect/Write/ Shutdown/Free - BIO method chain: BioMethNew -> SetRead/SetWrite/BioNew - BIO chain: BioNew -> SetData, SetBio uses same BIO for read+write, BIO_get_data called with same BIO from inside TransportBioRead Arg capture tests in OpenSslFakeTest.cpp (16 new) — one per new capture, proving the fake itself works. Tests: 703 total (was 672; +31). All green. Full build clean. --- Tests/OpenSslFakeTest.cpp | 126 ++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 107 +++++++ Tests/Support/OpenSslFake.c | 490 +++++++++++++++-------------- Tests/Support/OpenSslFake.h | 96 ++++-- 4 files changed, 559 insertions(+), 260 deletions(-) diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index 085e082f..685c2868 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -269,3 +269,129 @@ TEST(OpenSslFake, CtxFreeIncrementsCount) SSL_CTX_free(ctx); LONGS_EQUAL(1, OpenSslFake_CtxFreeCallCount()); } + +/* ------------------------------------------------------------------------- + * Arg captures — prove each fake function records the args its callers pass. + * ------------------------------------------------------------------------- */ + +TEST(OpenSslFake, CtxNewCapturesMethodArg) +{ + const SSL_METHOD* method = TLS_client_method(); + SSL_CTX_new(method); + POINTERS_EQUAL(method, OpenSslFake_LastCtxNewMethodArg()); +} + +TEST(OpenSslFake, LoadVerifyLocationsCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_load_verify_locations(ctx, "/ca.pem", nullptr); + POINTERS_EQUAL(ctx, OpenSslFake_LastLoadVerifyLocationsCtxArg()); +} + +TEST(OpenSslFake, SetVerifyCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr); + POINTERS_EQUAL(ctx, OpenSslFake_LastSetVerifyCtxArg()); +} + +TEST(OpenSslFake, SetMinProtoVersionCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + POINTERS_EQUAL(ctx, OpenSslFake_LastSslCtxCtrlCtxArg()); +} + +TEST(OpenSslFake, BioMethSetReadCapturesMethodArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_read(method, DummyRead); + POINTERS_EQUAL(method, OpenSslFake_LastBioMethSetReadMethodArg()); +} + +TEST(OpenSslFake, BioMethSetWriteCapturesMethodArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_write(method, DummyWrite); + POINTERS_EQUAL(method, OpenSslFake_LastBioMethSetWriteMethodArg()); +} + +TEST(OpenSslFake, BioNewCapturesMethodArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_new(method); + POINTERS_EQUAL(method, OpenSslFake_LastBioNewMethodArg()); +} + +TEST(OpenSslFake, BioMethNewReturnValueIsSurfaced) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + POINTERS_EQUAL(method, OpenSslFake_LastBioMethReturned()); +} + +TEST(OpenSslFake, BioSetDataCapturesBioArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel; + BIO_set_data(bio, &sentinel); + POINTERS_EQUAL(bio, OpenSslFake_LastSetDataBioArg()); +} + +TEST(OpenSslFake, BioGetDataCapturesBioArg) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + BIO_get_data(bio); + POINTERS_EQUAL(bio, OpenSslFake_LastGetDataBioArg()); +} + +TEST(OpenSslFake, SetBioCapturesWriteBioArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* rbio = BIO_new(method); + BIO* wbio = BIO_new(method); + SSL_set_bio(ssl, rbio, wbio); + POINTERS_EQUAL(wbio, OpenSslFake_LastSetBioWriteBioArg()); +} + +TEST(OpenSslFake, SetTlsExtHostNameCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set_tlsext_host_name(ssl, "host.example"); + POINTERS_EQUAL(ssl, OpenSslFake_LastSslCtrlSslArg()); +} + +TEST(OpenSslFake, Set1HostCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_set1_host(ssl, "host.example"); + POINTERS_EQUAL(ssl, OpenSslFake_LastSet1HostSslArg()); +} + +TEST(OpenSslFake, ShutdownCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_shutdown(ssl); + POINTERS_EQUAL(ssl, OpenSslFake_LastShutdownSslArg()); +} + +TEST(OpenSslFake, FreeCapturesSslArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + SSL_free(ssl); + POINTERS_EQUAL(ssl, OpenSslFake_LastFreeSslArg()); +} + +TEST(OpenSslFake, CtxFreeCapturesCtxArg) +{ + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_free(ctx); + POINTERS_EQUAL(ctx, OpenSslFake_LastCtxFreeCtxArg()); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index f29927b8..b94493d5 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -235,3 +235,110 @@ TEST(SolidSyslogTlsStream, DestroyFreesSslContext) LONGS_EQUAL(1, OpenSslFake_CtxFreeCallCount()); /* teardown re-Destroys safely */ } + +/* ------------------------------------------------------------------------- + * Pointer-chain assertions: each OpenSSL call must receive the handle + * returned by the preceding call, not some stale or NULL pointer. + * ------------------------------------------------------------------------- */ + +TEST(SolidSyslogTlsStream, OpenPassesClientMethodToCtxNew) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(TLS_client_method(), OpenSslFake_LastCtxNewMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToLoadVerifyLocations) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastLoadVerifyLocationsCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToSetVerify) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSetVerifyCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesCtxFromNewToSetMinProtoVersion) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastSslCtxCtrlCtxArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioMethodFromNewToSetRead) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioMethReturned(), OpenSslFake_LastBioMethSetReadMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioMethodFromNewToSetWrite) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioMethReturned(), OpenSslFake_LastBioMethSetWriteMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioMethodFromNewToBioNew) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioMethReturned(), OpenSslFake_LastBioNewMethodArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesBioFromNewToSetData) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastBioReturned(), OpenSslFake_LastSetDataBioArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSameBioForReadAndWrite) +{ + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSetBioReadBioArg(), OpenSslFake_LastSetBioWriteBioArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslToSniCtrl) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSslCtrlSslArg()); +} + +TEST(SolidSyslogTlsStream, OpenPassesSslFromNewToSet1Host) +{ + SolidSyslogTlsStream_Destroy(); + config.serverName = "logs.example"; + stream = SolidSyslogTlsStream_Create(&config); + SolidSyslogStream_Open(stream, addr); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSet1HostSslArg()); +} + +TEST(SolidSyslogTlsStream, BioReadCallbackLooksUpDataOnTheCorrectBio) +{ + SolidSyslogStream_Open(stream, addr); + int (*readFn)(BIO*, char*, int) = OpenSslFake_LastBioReadCallback(); + char buf[16]; + readFn(OpenSslFake_LastBioReturned(), buf, sizeof(buf)); + POINTERS_EQUAL(OpenSslFake_LastBioReturned(), OpenSslFake_LastGetDataBioArg()); +} + +TEST(SolidSyslogTlsStream, ClosePassesSslFromNewToShutdown) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastShutdownSslArg()); +} + +TEST(SolidSyslogTlsStream, ClosePassesSslFromNewToFree) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogStream_Close(stream); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastFreeSslArg()); +} + +TEST(SolidSyslogTlsStream, DestroyPassesCtxFromNewToCtxFree) +{ + SolidSyslogStream_Open(stream, addr); + SolidSyslogTlsStream_Destroy(); + POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastCtxFreeCtxArg()); +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 2d9e38d5..3e953427 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -3,212 +3,250 @@ #include #include -static int ctxNewCallCount; +/* ------------------------------------------------------------------------- + * Captured state — one section per OpenSSL API call. Tests read these via + * accessors below; production reaches libssl through the link-interposed + * functions at the bottom of the file. + * ------------------------------------------------------------------------- */ + +/* Sentinel storage for opaque OpenSSL types — our fake returns stable + * pointers to these so tests can assert pointer-chain plumbing with + * POINTERS_EQUAL. */ +static char fakeCtxStorage; +static char fakeMethodStorage; +static char fakeSslStorage; +static char fakeBioMethStorage; +static char fakeBioStorage; + +/* SSL_CTX_new */ +static int ctxNewCallCount; +static const SSL_METHOD* lastCtxNewMethodArg; + +/* SSL_CTX_load_verify_locations */ +static SSL_CTX* lastLoadVerifyLocationsCtxArg; static const char* lastCaBundlePath; -static int lastVerifyMode; -static long lastMinProtoVersion; -static int sslNewCallCount; -static SSL_CTX* lastSslNewCtxArg; -static int bioNewCallCount; -static int setBioCallCount; -static SSL* lastSetBioSslArg; -static BIO* lastSetBioReadBioArg; -static int connectCallCount; -static SSL* lastConnectSslArg; -static const char* lastSniHostname; -static const char* lastSet1Host; -static void* lastSetDataArg; -static int (*lastBioReadCallback)(BIO*, char*, int); -static int (*lastBioWriteCallback)(BIO*, const char*, int); -static int writeCallCount; -static SSL* lastWriteSslArg; -static const void* lastWriteBuf; -static int lastWriteSize; -static int shutdownCallCount; -static int freeCallCount; -static int ctxFreeCallCount; -static char fakeCtxStorage; -static char fakeMethodStorage; -static char fakeSslStorage; -static char fakeBioMethStorage; -static char fakeBioStorage; - -void OpenSslFake_Reset(void) -{ - ctxNewCallCount = 0; - lastCaBundlePath = NULL; - lastVerifyMode = 0; - lastMinProtoVersion = 0; - sslNewCallCount = 0; - lastSslNewCtxArg = NULL; - bioNewCallCount = 0; - setBioCallCount = 0; - lastSetBioSslArg = NULL; - lastSetBioReadBioArg = NULL; - connectCallCount = 0; - lastConnectSslArg = NULL; - lastSniHostname = NULL; - lastSet1Host = NULL; - lastSetDataArg = NULL; - lastBioReadCallback = NULL; - lastBioWriteCallback = NULL; - writeCallCount = 0; - lastWriteSslArg = NULL; - lastWriteBuf = NULL; - lastWriteSize = 0; - shutdownCallCount = 0; - freeCallCount = 0; - ctxFreeCallCount = 0; -} - -int OpenSslFake_CtxNewCallCount(void) -{ - return ctxNewCallCount; -} - -const char* OpenSslFake_LastCaBundlePath(void) -{ - return lastCaBundlePath; -} - -int OpenSslFake_LastVerifyMode(void) -{ - return lastVerifyMode; -} -long OpenSslFake_LastMinProtoVersion(void) -{ - return lastMinProtoVersion; -} +/* SSL_CTX_set_verify */ +static SSL_CTX* lastSetVerifyCtxArg; +static int lastVerifyMode; -SSL_CTX* OpenSslFake_LastCtxReturned(void) -{ - return (SSL_CTX*) &fakeCtxStorage; -} +/* SSL_CTX_ctrl (SET_MIN_PROTO_VERSION) */ +static SSL_CTX* lastSslCtxCtrlCtxArg; +static long lastMinProtoVersion; -int OpenSslFake_SslNewCallCount(void) -{ - return sslNewCallCount; -} - -SSL* OpenSslFake_LastSslReturned(void) -{ - return (SSL*) &fakeSslStorage; -} - -SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) -{ - return lastSslNewCtxArg; -} - -int OpenSslFake_BioNewCallCount(void) -{ - return bioNewCallCount; -} - -BIO* OpenSslFake_LastBioReturned(void) -{ - return (BIO*) &fakeBioStorage; -} +/* SSL_new */ +static int sslNewCallCount; +static SSL_CTX* lastSslNewCtxArg; -int OpenSslFake_SetBioCallCount(void) -{ - return setBioCallCount; -} +/* BIO_meth_set_read / BIO_meth_set_write */ +static BIO_METHOD* lastBioMethSetReadMethodArg; +static int (*lastBioReadCallback)(BIO*, char*, int); +static BIO_METHOD* lastBioMethSetWriteMethodArg; +static int (*lastBioWriteCallback)(BIO*, const char*, int); -SSL* OpenSslFake_LastSetBioSslArg(void) -{ - return lastSetBioSslArg; -} +/* BIO_new */ +static int bioNewCallCount; +static const BIO_METHOD* lastBioNewMethodArg; -BIO* OpenSslFake_LastSetBioReadBioArg(void) -{ - return lastSetBioReadBioArg; -} +/* BIO_set_data / BIO_get_data */ +static BIO* lastSetDataBioArg; +static void* lastSetDataArg; +static BIO* lastGetDataBioArg; -int OpenSslFake_ConnectCallCount(void) -{ - return connectCallCount; -} +/* SSL_set_bio */ +static int setBioCallCount; +static SSL* lastSetBioSslArg; +static BIO* lastSetBioReadBioArg; +static BIO* lastSetBioWriteBioArg; -SSL* OpenSslFake_LastConnectSslArg(void) -{ - return lastConnectSslArg; -} +/* SSL_ctrl (SET_TLSEXT_HOSTNAME) */ +static SSL* lastSslCtrlSslArg; +static const char* lastSniHostname; -const char* OpenSslFake_LastSniHostname(void) -{ - return lastSniHostname; -} +/* SSL_set1_host */ +static SSL* lastSet1HostSslArg; +static const char* lastSet1Host; -const char* OpenSslFake_LastSet1Host(void) -{ - return lastSet1Host; -} +/* SSL_connect */ +static int connectCallCount; +static SSL* lastConnectSslArg; -void* OpenSslFake_LastSetDataArg(void) -{ - return lastSetDataArg; -} - -int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) -{ - return lastBioReadCallback; -} +/* SSL_write */ +static int writeCallCount; +static SSL* lastWriteSslArg; +static const void* lastWriteBuf; +static int lastWriteSize; -int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) -{ - return lastBioWriteCallback; -} +/* SSL_shutdown */ +static int shutdownCallCount; +static SSL* lastShutdownSslArg; -int OpenSslFake_WriteCallCount(void) -{ - return writeCallCount; -} +/* SSL_free */ +static int freeCallCount; +static SSL* lastFreeSslArg; -SSL* OpenSslFake_LastWriteSslArg(void) -{ - return lastWriteSslArg; -} +/* SSL_CTX_free */ +static int ctxFreeCallCount; +static SSL_CTX* lastCtxFreeCtxArg; -const void* OpenSslFake_LastWriteBuf(void) -{ - return lastWriteBuf; -} +/* ------------------------------------------------------------------------- + * Reset — zero every captured value. + * ------------------------------------------------------------------------- */ -int OpenSslFake_LastWriteSize(void) +void OpenSslFake_Reset(void) { - return lastWriteSize; -} + ctxNewCallCount = 0; + lastCtxNewMethodArg = NULL; + lastLoadVerifyLocationsCtxArg = NULL; + lastCaBundlePath = NULL; + lastSetVerifyCtxArg = NULL; + lastVerifyMode = 0; + lastSslCtxCtrlCtxArg = NULL; + lastMinProtoVersion = 0; + sslNewCallCount = 0; + lastSslNewCtxArg = NULL; + lastBioMethSetReadMethodArg = NULL; + lastBioReadCallback = NULL; + lastBioMethSetWriteMethodArg = NULL; + lastBioWriteCallback = NULL; + bioNewCallCount = 0; + lastBioNewMethodArg = NULL; + lastSetDataBioArg = NULL; + lastSetDataArg = NULL; + lastGetDataBioArg = NULL; + setBioCallCount = 0; + lastSetBioSslArg = NULL; + lastSetBioReadBioArg = NULL; + lastSetBioWriteBioArg = NULL; + lastSslCtrlSslArg = NULL; + lastSniHostname = NULL; + lastSet1HostSslArg = NULL; + lastSet1Host = NULL; + connectCallCount = 0; + lastConnectSslArg = NULL; + writeCallCount = 0; + lastWriteSslArg = NULL; + lastWriteBuf = NULL; + lastWriteSize = 0; + shutdownCallCount = 0; + lastShutdownSslArg = NULL; + freeCallCount = 0; + lastFreeSslArg = NULL; + ctxFreeCallCount = 0; + lastCtxFreeCtxArg = NULL; +} + +/* ------------------------------------------------------------------------- + * Accessors — grouped by the OpenSSL function they describe. + * ------------------------------------------------------------------------- */ + +int OpenSslFake_CtxNewCallCount(void) { return ctxNewCallCount; } +const SSL_METHOD* OpenSslFake_LastCtxNewMethodArg(void) { return lastCtxNewMethodArg; } +SSL_CTX* OpenSslFake_LastCtxReturned(void) { return (SSL_CTX*) &fakeCtxStorage; } + +SSL_CTX* OpenSslFake_LastLoadVerifyLocationsCtxArg(void) { return lastLoadVerifyLocationsCtxArg; } +const char* OpenSslFake_LastCaBundlePath(void) { return lastCaBundlePath; } + +SSL_CTX* OpenSslFake_LastSetVerifyCtxArg(void) { return lastSetVerifyCtxArg; } +int OpenSslFake_LastVerifyMode(void) { return lastVerifyMode; } + +SSL_CTX* OpenSslFake_LastSslCtxCtrlCtxArg(void) { return lastSslCtxCtrlCtxArg; } +long OpenSslFake_LastMinProtoVersion(void) { return lastMinProtoVersion; } + +int OpenSslFake_SslNewCallCount(void) { return sslNewCallCount; } +SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) { return lastSslNewCtxArg; } +SSL* OpenSslFake_LastSslReturned(void) { return (SSL*) &fakeSslStorage; } + +BIO_METHOD* OpenSslFake_LastBioMethReturned(void) { return (BIO_METHOD*) &fakeBioMethStorage; } + +BIO_METHOD* OpenSslFake_LastBioMethSetReadMethodArg(void) { return lastBioMethSetReadMethodArg; } +int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) { return lastBioReadCallback; } + +BIO_METHOD* OpenSslFake_LastBioMethSetWriteMethodArg(void) { return lastBioMethSetWriteMethodArg; } +int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) { return lastBioWriteCallback; } + +int OpenSslFake_BioNewCallCount(void) { return bioNewCallCount; } +const BIO_METHOD* OpenSslFake_LastBioNewMethodArg(void) { return lastBioNewMethodArg; } +BIO* OpenSslFake_LastBioReturned(void) { return (BIO*) &fakeBioStorage; } + +BIO* OpenSslFake_LastSetDataBioArg(void) { return lastSetDataBioArg; } +void* OpenSslFake_LastSetDataArg(void) { return lastSetDataArg; } +BIO* OpenSslFake_LastGetDataBioArg(void) { return lastGetDataBioArg; } + +int OpenSslFake_SetBioCallCount(void) { return setBioCallCount; } +SSL* OpenSslFake_LastSetBioSslArg(void) { return lastSetBioSslArg; } +BIO* OpenSslFake_LastSetBioReadBioArg(void) { return lastSetBioReadBioArg; } +BIO* OpenSslFake_LastSetBioWriteBioArg(void) { return lastSetBioWriteBioArg; } + +SSL* OpenSslFake_LastSslCtrlSslArg(void) { return lastSslCtrlSslArg; } +const char* OpenSslFake_LastSniHostname(void) { return lastSniHostname; } + +SSL* OpenSslFake_LastSet1HostSslArg(void) { return lastSet1HostSslArg; } +const char* OpenSslFake_LastSet1Host(void) { return lastSet1Host; } + +int OpenSslFake_ConnectCallCount(void) { return connectCallCount; } +SSL* OpenSslFake_LastConnectSslArg(void) { return lastConnectSslArg; } + +int OpenSslFake_WriteCallCount(void) { return writeCallCount; } +SSL* OpenSslFake_LastWriteSslArg(void) { return lastWriteSslArg; } +const void* OpenSslFake_LastWriteBuf(void) { return lastWriteBuf; } +int OpenSslFake_LastWriteSize(void) { return lastWriteSize; } + +int OpenSslFake_ShutdownCallCount(void) { return shutdownCallCount; } +SSL* OpenSslFake_LastShutdownSslArg(void) { return lastShutdownSslArg; } + +int OpenSslFake_FreeCallCount(void) { return freeCallCount; } +SSL* OpenSslFake_LastFreeSslArg(void) { return lastFreeSslArg; } + +int OpenSslFake_CtxFreeCallCount(void) { return ctxFreeCallCount; } +SSL_CTX* OpenSslFake_LastCtxFreeCtxArg(void) { return lastCtxFreeCtxArg; } + +/* ------------------------------------------------------------------------- + * Link-time substitution for OpenSSL — replaces libssl symbols in the test + * binary. Production links real libssl; tests never link -lssl. + * Each function records its args for test assertion. Return values are + * plausible-success stubs; where behaviour needs switching for failure-path + * tests, add a setter (e.g. OpenSslFake_SetConnectFails) in a later cycle. + * ------------------------------------------------------------------------- */ -int OpenSslFake_ShutdownCallCount(void) +const SSL_METHOD* TLS_client_method(void) { - return shutdownCallCount; + return (const SSL_METHOD*) &fakeMethodStorage; } -int OpenSslFake_FreeCallCount(void) +SSL_CTX* SSL_CTX_new(const SSL_METHOD* method) { - return freeCallCount; + ctxNewCallCount++; + lastCtxNewMethodArg = method; + return (SSL_CTX*) &fakeCtxStorage; } -int OpenSslFake_CtxFreeCallCount(void) +int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) { - return ctxFreeCallCount; + (void) CApath; + lastLoadVerifyLocationsCtxArg = ctx; + lastCaBundlePath = CAfile; + return 1; } -/* Link-time substitution for OpenSSL — replaces libssl symbols in the test - * binary. Production links real libssl; tests never link -lssl. */ - -const SSL_METHOD* TLS_client_method(void) +void SSL_CTX_set_verify(SSL_CTX* ctx, int mode, SSL_verify_cb verify_callback) { - return (const SSL_METHOD*) &fakeMethodStorage; + (void) verify_callback; + lastSetVerifyCtxArg = ctx; + lastVerifyMode = mode; } -SSL_CTX* SSL_CTX_new(const SSL_METHOD* method) +/* SSL_CTX_set_min_proto_version is a macro forwarding to SSL_CTX_ctrl; fake + * intercepts the ctrl call for the min-proto command only. */ +long SSL_CTX_ctrl(SSL_CTX* ctx, int cmd, long larg, void* parg) { - (void) method; - ctxNewCallCount++; - return (SSL_CTX*) &fakeCtxStorage; + (void) parg; + lastSslCtxCtrlCtxArg = ctx; + if (cmd == SSL_CTRL_SET_MIN_PROTO_VERSION) + { + lastMinProtoVersion = larg; + } + return 1; } SSL* SSL_new(SSL_CTX* ctx) @@ -225,34 +263,53 @@ BIO_METHOD* BIO_meth_new(int type, const char* name) return (BIO_METHOD*) &fakeBioMethStorage; } +int BIO_meth_set_read(BIO_METHOD* method, int (*read)(BIO*, char*, int)) +{ + lastBioMethSetReadMethodArg = method; + lastBioReadCallback = read; + return 1; +} + +int BIO_meth_set_write(BIO_METHOD* method, int (*write)(BIO*, const char*, int)) +{ + lastBioMethSetWriteMethodArg = method; + lastBioWriteCallback = write; + return 1; +} + BIO* BIO_new(const BIO_METHOD* method) { - (void) method; bioNewCallCount++; + lastBioNewMethodArg = method; return (BIO*) &fakeBioStorage; } -void SSL_set_bio(SSL* ssl, BIO* rbio, BIO* wbio) +void BIO_set_data(BIO* bio, void* data) { - (void) wbio; - setBioCallCount++; - lastSetBioSslArg = ssl; - lastSetBioReadBioArg = rbio; + lastSetDataBioArg = bio; + lastSetDataArg = data; } -int SSL_connect(SSL* ssl) +void* BIO_get_data(BIO* bio) { - connectCallCount++; - lastConnectSslArg = ssl; - return 1; + lastGetDataBioArg = bio; + return lastSetDataArg; +} + +void SSL_set_bio(SSL* ssl, BIO* rbio, BIO* wbio) +{ + setBioCallCount++; + lastSetBioSslArg = ssl; + lastSetBioReadBioArg = rbio; + lastSetBioWriteBioArg = wbio; } /* SSL_set_tlsext_host_name is a macro forwarding to SSL_ctrl; fake intercepts * the SET_TLSEXT_HOSTNAME command and captures the hostname pointer. */ long SSL_ctrl(SSL* ssl, int cmd, long larg, void* parg) { - (void) ssl; (void) larg; + lastSslCtrlSslArg = ssl; if (cmd == SSL_CTRL_SET_TLSEXT_HOSTNAME) { lastSniHostname = (const char*) parg; @@ -262,34 +319,15 @@ long SSL_ctrl(SSL* ssl, int cmd, long larg, void* parg) int SSL_set1_host(SSL* ssl, const char* hostname) { - (void) ssl; - lastSet1Host = hostname; + lastSet1HostSslArg = ssl; + lastSet1Host = hostname; return 1; } -void BIO_set_data(BIO* bio, void* data) -{ - (void) bio; - lastSetDataArg = data; -} - -void* BIO_get_data(BIO* bio) -{ - (void) bio; - return lastSetDataArg; -} - -int BIO_meth_set_read(BIO_METHOD* method, int (*read)(BIO*, char*, int)) -{ - (void) method; - lastBioReadCallback = read; - return 1; -} - -int BIO_meth_set_write(BIO_METHOD* method, int (*write)(BIO*, const char*, int)) +int SSL_connect(SSL* ssl) { - (void) method; - lastBioWriteCallback = write; + connectCallCount++; + lastConnectSslArg = ssl; return 1; } @@ -304,47 +342,19 @@ int SSL_write(SSL* ssl, const void* buf, int num) int SSL_shutdown(SSL* ssl) { - (void) ssl; shutdownCallCount++; + lastShutdownSslArg = ssl; return 1; } void SSL_free(SSL* ssl) { - (void) ssl; freeCallCount++; + lastFreeSslArg = ssl; } void SSL_CTX_free(SSL_CTX* ctx) { - (void) ctx; ctxFreeCallCount++; -} - -int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) -{ - (void) ctx; - (void) CApath; - lastCaBundlePath = CAfile; - return 1; -} - -void SSL_CTX_set_verify(SSL_CTX* ctx, int mode, SSL_verify_cb verify_callback) -{ - (void) ctx; - (void) verify_callback; - lastVerifyMode = mode; -} - -/* SSL_CTX_set_min_proto_version is a macro forwarding to SSL_CTX_ctrl; fake by - * intercepting the ctrl call for the min-proto command only. */ -long SSL_CTX_ctrl(SSL_CTX* ctx, int cmd, long larg, void* parg) -{ - (void) ctx; - (void) parg; - if (cmd == SSL_CTRL_SET_MIN_PROTO_VERSION) - { - lastMinProtoVersion = larg; - } - return 1; + lastCtxFreeCtxArg = ctx; } diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index 02bbad94..e21a7cfa 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -6,38 +6,94 @@ /* Forward-declared OpenSSL types — full definitions live in . */ struct ssl_ctx_st; struct ssl_st; +struct ssl_method_st; struct bio_st; +struct bio_method_st; EXTERN_C_BEGIN - void OpenSslFake_Reset(void); - int OpenSslFake_CtxNewCallCount(void); - struct ssl_ctx_st* OpenSslFake_LastCtxReturned(void); + void OpenSslFake_Reset(void); + + /* SSL_CTX_new */ + int OpenSslFake_CtxNewCallCount(void); + const struct ssl_method_st* OpenSslFake_LastCtxNewMethodArg(void); + struct ssl_ctx_st* OpenSslFake_LastCtxReturned(void); + + /* SSL_CTX_load_verify_locations */ + struct ssl_ctx_st* OpenSslFake_LastLoadVerifyLocationsCtxArg(void); const char* OpenSslFake_LastCaBundlePath(void); + + /* SSL_CTX_set_verify */ + struct ssl_ctx_st* OpenSslFake_LastSetVerifyCtxArg(void); int OpenSslFake_LastVerifyMode(void); + + /* SSL_CTX_ctrl (SET_MIN_PROTO_VERSION path) */ + struct ssl_ctx_st* OpenSslFake_LastSslCtxCtrlCtxArg(void); long OpenSslFake_LastMinProtoVersion(void); + + /* SSL_new */ int OpenSslFake_SslNewCallCount(void); - struct ssl_st* OpenSslFake_LastSslReturned(void); struct ssl_ctx_st* OpenSslFake_LastSslNewCtxArg(void); - int OpenSslFake_BioNewCallCount(void); - struct bio_st* OpenSslFake_LastBioReturned(void); - int OpenSslFake_SetBioCallCount(void); - struct ssl_st* OpenSslFake_LastSetBioSslArg(void); - struct bio_st* OpenSslFake_LastSetBioReadBioArg(void); - int OpenSslFake_ConnectCallCount(void); - struct ssl_st* OpenSslFake_LastConnectSslArg(void); - const char* OpenSslFake_LastSniHostname(void); - const char* OpenSslFake_LastSet1Host(void); - void* OpenSslFake_LastSetDataArg(void); + struct ssl_st* OpenSslFake_LastSslReturned(void); + + /* BIO_meth_new */ + struct bio_method_st* OpenSslFake_LastBioMethReturned(void); + + /* BIO_meth_set_read */ + struct bio_method_st* OpenSslFake_LastBioMethSetReadMethodArg(void); int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); + + /* BIO_meth_set_write */ + struct bio_method_st* OpenSslFake_LastBioMethSetWriteMethodArg(void); int (*OpenSslFake_LastBioWriteCallback(void))(struct bio_st*, const char*, int); - int OpenSslFake_WriteCallCount(void); - struct ssl_st* OpenSslFake_LastWriteSslArg(void); - const void* OpenSslFake_LastWriteBuf(void); - int OpenSslFake_LastWriteSize(void); - int OpenSslFake_ShutdownCallCount(void); - int OpenSslFake_FreeCallCount(void); + + /* BIO_new */ + int OpenSslFake_BioNewCallCount(void); + const struct bio_method_st* OpenSslFake_LastBioNewMethodArg(void); + struct bio_st* OpenSslFake_LastBioReturned(void); + + /* BIO_set_data */ + struct bio_st* OpenSslFake_LastSetDataBioArg(void); + void* OpenSslFake_LastSetDataArg(void); + + /* BIO_get_data (arg capture for BIO-callback introspection) */ + struct bio_st* OpenSslFake_LastGetDataBioArg(void); + + /* SSL_set_bio */ + int OpenSslFake_SetBioCallCount(void); + struct ssl_st* OpenSslFake_LastSetBioSslArg(void); + struct bio_st* OpenSslFake_LastSetBioReadBioArg(void); + struct bio_st* OpenSslFake_LastSetBioWriteBioArg(void); + + /* SSL_ctrl (SET_TLSEXT_HOSTNAME path — SNI) */ + struct ssl_st* OpenSslFake_LastSslCtrlSslArg(void); + const char* OpenSslFake_LastSniHostname(void); + + /* SSL_set1_host */ + struct ssl_st* OpenSslFake_LastSet1HostSslArg(void); + const char* OpenSslFake_LastSet1Host(void); + + /* SSL_connect */ + int OpenSslFake_ConnectCallCount(void); + struct ssl_st* OpenSslFake_LastConnectSslArg(void); + + /* SSL_write */ + int OpenSslFake_WriteCallCount(void); + struct ssl_st* OpenSslFake_LastWriteSslArg(void); + const void* OpenSslFake_LastWriteBuf(void); + int OpenSslFake_LastWriteSize(void); + + /* SSL_shutdown */ + int OpenSslFake_ShutdownCallCount(void); + struct ssl_st* OpenSslFake_LastShutdownSslArg(void); + + /* SSL_free */ + int OpenSslFake_FreeCallCount(void); + struct ssl_st* OpenSslFake_LastFreeSslArg(void); + + /* SSL_CTX_free */ int OpenSslFake_CtxFreeCallCount(void); + struct ssl_ctx_st* OpenSslFake_LastCtxFreeCtxArg(void); EXTERN_C_END From c39d8b0c5aa1b2bf04b2a20b35578cb2d57be073 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 22:30:47 +0100 Subject: [PATCH 11/19] S3.7 error-path cycles E1-E3: Open+Send failure handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small TDD cycles adding the most impactful error paths. Each with a failure-mode switch on the relevant fake. - E1 OpenReturnsFalseWhenHandshakeFails — SSL_connect returning negative should propagate false from Open. Fake gains OpenSslFake_SetConnectFails. Production Open now returns `SSL_connect(...) > 0` instead of unconditional true. - E2 SendReturnsFalseWhenWriteFails — SSL_write returning negative should yield false from Send. Production already had the correct propagation (SSL_write > 0); this cycle just added the OpenSslFake_SetWriteFails switch + test. - E3 OpenReturnsFalseWhenTransportOpenFails + OpenSkipsSslSetupWhenTransportOpenFails — if the injected transport's Open fails, bail out before any OpenSSL setup. StreamFake gains StreamFake_SetOpenFails. Production Open short-circuits on transport-Open failure. Added happy-path sibling tests (OpenReturnsTrueOnHappyPath, SendReturnsTrueOnHappyPath) so the positive assertion is explicit next to each failure case. Not covered at this scope — deferred to E3 hardening stories: - SSL_CTX_new / SSL_new allocation failures (rare; handled transitively via later OpenSSL calls that would then fail) - SSL_CTX_load_verify_locations returning 0 (handled transitively via handshake failure) - Resource cleanup on mid-Open failure (ctx/ssl left dangling; picked up by Destroy — documented as a known limitation) Tests: 713 total (was 703; +10). All green. Full build clean. --- .../OpenSsl/Source/SolidSyslogTlsStream.c | 8 ++-- Tests/OpenSslFakeTest.cpp | 20 +++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 43 +++++++++++++++++++ Tests/StreamFake.c | 8 +++- Tests/StreamFake.h | 2 + Tests/StreamFakeTest.cpp | 15 +++++++ Tests/Support/OpenSslFake.c | 19 +++++++- Tests/Support/OpenSslFake.h | 6 +++ 8 files changed, 115 insertions(+), 6 deletions(-) diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index 0e852248..cbbea596 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -42,7 +42,10 @@ void SolidSyslogTlsStream_Destroy(void) static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr) { struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; - SolidSyslogStream_Open(stream->config.transport, addr); + if (!SolidSyslogStream_Open(stream->config.transport, addr)) + { + return false; + } stream->ctx = CreateSslContext(stream->config.caBundlePath); stream->ssl = SSL_new(stream->ctx); BIO* bio = CreateTransportBio(); @@ -53,8 +56,7 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress SSL_set_tlsext_host_name(stream->ssl, stream->config.serverName); SSL_set1_host(stream->ssl, stream->config.serverName); } - SSL_connect(stream->ssl); - return true; + return SSL_connect(stream->ssl) > 0; } static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index 685c2868..40c4808e 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -395,3 +395,23 @@ TEST(OpenSslFake, CtxFreeCapturesCtxArg) SSL_CTX_free(ctx); POINTERS_EQUAL(ctx, OpenSslFake_LastCtxFreeCtxArg()); } + +/* ------------------------------------------------------------------------- + * Failure-mode switches — tests opt into failure returns via SetXxxFails. + * ------------------------------------------------------------------------- */ + +TEST(OpenSslFake, SetConnectFailsMakesConnectReturnNegative) +{ + OpenSslFake_SetConnectFails(true); + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + CHECK_TRUE(SSL_connect(ssl) <= 0); +} + +TEST(OpenSslFake, SetWriteFailsMakesWriteReturnNegative) +{ + OpenSslFake_SetWriteFails(true); + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + SSL* ssl = SSL_new(ctx); + CHECK_TRUE(SSL_write(ssl, "x", 1) <= 0); +} diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index b94493d5..a25cc0ee 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -342,3 +342,46 @@ TEST(SolidSyslogTlsStream, DestroyPassesCtxFromNewToCtxFree) SolidSyslogTlsStream_Destroy(); POINTERS_EQUAL(OpenSslFake_LastCtxReturned(), OpenSslFake_LastCtxFreeCtxArg()); } + +/* ------------------------------------------------------------------------- + * Error paths. + * ------------------------------------------------------------------------- */ + +TEST(SolidSyslogTlsStream, OpenReturnsTrueOnHappyPath) +{ + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenHandshakeFails) +{ + OpenSslFake_SetConnectFails(true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogTlsStream, SendReturnsTrueOnHappyPath) +{ + SolidSyslogStream_Open(stream, addr); + const char msg[] = "hi"; + CHECK_TRUE(SolidSyslogStream_Send(stream, msg, sizeof(msg))); +} + +TEST(SolidSyslogTlsStream, SendReturnsFalseWhenWriteFails) +{ + SolidSyslogStream_Open(stream, addr); + OpenSslFake_SetWriteFails(true); + const char msg[] = "hi"; + CHECK_FALSE(SolidSyslogStream_Send(stream, msg, sizeof(msg))); +} + +TEST(SolidSyslogTlsStream, OpenReturnsFalseWhenTransportOpenFails) +{ + StreamFake_SetOpenFails(transport, true); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(SolidSyslogTlsStream, OpenSkipsSslSetupWhenTransportOpenFails) +{ + StreamFake_SetOpenFails(transport, true); + SolidSyslogStream_Open(stream, addr); + LONGS_EQUAL(0, OpenSslFake_CtxNewCallCount()); +} diff --git a/Tests/StreamFake.c b/Tests/StreamFake.c index 573f9b7d..8545b780 100644 --- a/Tests/StreamFake.c +++ b/Tests/StreamFake.c @@ -9,6 +9,7 @@ struct StreamFake struct SolidSyslogStream base; int openCallCount; const struct SolidSyslogAddress* lastOpenAddr; + bool openFails; int sendCallCount; const void* lastSendBuf; size_t lastSendSize; @@ -24,7 +25,7 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress struct StreamFake* fake = (struct StreamFake*) self; fake->openCallCount++; fake->lastOpenAddr = addr; - return true; + return !fake->openFails; } static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size) @@ -111,6 +112,11 @@ void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, ssize_t value) ((struct StreamFake*) stream)->readReturn = value; } +void StreamFake_SetOpenFails(struct SolidSyslogStream* stream, bool fails) +{ + ((struct StreamFake*) stream)->openFails = fails; +} + int StreamFake_CloseCallCount(struct SolidSyslogStream* stream) { return ((struct StreamFake*) stream)->closeCallCount; diff --git a/Tests/StreamFake.h b/Tests/StreamFake.h index df0c800d..08958bf4 100644 --- a/Tests/StreamFake.h +++ b/Tests/StreamFake.h @@ -3,6 +3,7 @@ #include "ExternC.h" +#include #include #include @@ -22,6 +23,7 @@ EXTERN_C_BEGIN void* StreamFake_LastReadBuf(struct SolidSyslogStream* stream); size_t StreamFake_LastReadSize(struct SolidSyslogStream* stream); void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, ssize_t value); + void StreamFake_SetOpenFails(struct SolidSyslogStream* stream, bool fails); int StreamFake_CloseCallCount(struct SolidSyslogStream* stream); EXTERN_C_END diff --git a/Tests/StreamFakeTest.cpp b/Tests/StreamFakeTest.cpp index 7e6e4901..90f859d3 100644 --- a/Tests/StreamFakeTest.cpp +++ b/Tests/StreamFakeTest.cpp @@ -89,3 +89,18 @@ TEST(StreamFake, CloseIncrementsCount) SolidSyslogStream_Close(stream); LONGS_EQUAL(1, StreamFake_CloseCallCount(stream)); } + +TEST(StreamFake, OpenDefaultsToSuccess) +{ + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + CHECK_TRUE(SolidSyslogStream_Open(stream, addr)); +} + +TEST(StreamFake, SetOpenFailsMakesOpenReturnFalse) +{ + StreamFake_SetOpenFails(stream, true); + SolidSyslogAddressStorage storage = {0}; + struct SolidSyslogAddress* addr = SolidSyslogAddress_FromStorage(&storage); + CHECK_FALSE(SolidSyslogStream_Open(stream, addr)); +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 3e953427..111e1130 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -1,6 +1,7 @@ #include "OpenSslFake.h" #include +#include #include /* ------------------------------------------------------------------------- @@ -70,12 +71,14 @@ static const char* lastSet1Host; /* SSL_connect */ static int connectCallCount; static SSL* lastConnectSslArg; +static bool connectFails; /* SSL_write */ static int writeCallCount; static SSL* lastWriteSslArg; static const void* lastWriteBuf; static int lastWriteSize; +static bool writeFails; /* SSL_shutdown */ static int shutdownCallCount; @@ -124,10 +127,12 @@ void OpenSslFake_Reset(void) lastSet1Host = NULL; connectCallCount = 0; lastConnectSslArg = NULL; + connectFails = false; writeCallCount = 0; lastWriteSslArg = NULL; lastWriteBuf = NULL; lastWriteSize = 0; + writeFails = false; shutdownCallCount = 0; lastShutdownSslArg = NULL; freeCallCount = 0; @@ -328,7 +333,12 @@ int SSL_connect(SSL* ssl) { connectCallCount++; lastConnectSslArg = ssl; - return 1; + return connectFails ? -1 : 1; +} + +void OpenSslFake_SetConnectFails(bool fails) +{ + connectFails = fails; } int SSL_write(SSL* ssl, const void* buf, int num) @@ -337,7 +347,12 @@ int SSL_write(SSL* ssl, const void* buf, int num) lastWriteSslArg = ssl; lastWriteBuf = buf; lastWriteSize = num; - return num; + return writeFails ? -1 : num; +} + +void OpenSslFake_SetWriteFails(bool fails) +{ + writeFails = fails; } int SSL_shutdown(SSL* ssl) diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index e21a7cfa..e69d4e7b 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -3,6 +3,8 @@ #include "ExternC.h" +#include + /* Forward-declared OpenSSL types — full definitions live in . */ struct ssl_ctx_st; struct ssl_st; @@ -14,6 +16,10 @@ EXTERN_C_BEGIN void OpenSslFake_Reset(void); + /* Failure-mode switches */ + void OpenSslFake_SetConnectFails(bool fails); + void OpenSslFake_SetWriteFails(bool fails); + /* SSL_CTX_new */ int OpenSslFake_CtxNewCallCount(void); const struct ssl_method_st* OpenSslFake_LastCtxNewMethodArg(void); From bd7522830501b4726f9fd2cdf8eb9b690d6e9881 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Mon, 20 Apr 2026 22:55:00 +0100 Subject: [PATCH 12/19] S3.7 phase 3: Example/Threaded wires TLS, links real libssl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ends the unit-test-only phase — the Threaded example now exercises the TLS stream against real OpenSSL, and a manual smoke test against the BDD syslog-ng container verifies end-to-end delivery (message arrives in received_tls.log). Changes: - Example/Common/ExampleSwitchConfig gains EXAMPLE_SWITCH_TLS and a "tls" case in SetByName. Tests updated. - New Example/Common/ExampleTlsConfig — host "syslog-ng", port 6514 (RFC 5425), CA bundle Bdd/syslog-ng/tls/ca.pem, server name "syslog-ng". Mirrors ExampleTcpConfig shape. No dedicated test file, matching TCP/UDP config pattern (exercised via BDD). - Example/Threaded/main.c — launch-time choice between plain TCP and TLS. SolidSyslogStreamSender and SolidSyslogPosixTcpStream are singletons in this build, so we can't wire both. "--transport tls" wires UDP + TLS; anything else keeps the existing UDP + TCP. Runtime switching to the non-enabled reliable transport falls back to UDP. Documented in comment. - ExampleCommandLine accepts "tls" alongside "udp"/"tcp" (was silently rejected by strict validation). - Example/CMakeLists.txt — find_package(OpenSSL REQUIRED), link SolidSyslogThreadedExample against OpenSSL::SSL + OpenSSL::Crypto. - BIO method wiring gap caught during smoke test: real libssl's SSL_connect calls BIO_ctrl, so our custom BIO needed a ctrl callback (returns 1 for FLUSH/PUSH/POP/DUP, 0 otherwise) and a create callback (calls BIO_set_init(bio, 1)). Added via cycle P6 — TransportBioCreate + TransportBioCtrl in production, plus OpenSslFake support for BIO_meth_set_ctrl / _create / BIO_set_init and accessors for the new callback slots. Tests: 717 total (was 703; +14 for TLS CLI, switch, BIO ctrl/create). All green. Full build clean. Known limitation flagged in comment: TCP↔TLS runtime switching isn't supported because both use singleton StreamSender + singleton PosixTcpStream. Multi-instance refactor is future E3 work. Remaining to close out S3.7: - Un-@wip tls_transport.feature and green the BDD scenario - Full preset gauntlet (debug, clang-debug, sanitize, coverage, tidy, cppcheck, format, bdd) - PR --- Example/CMakeLists.txt | 7 ++- Example/Common/ExampleCommandLine.c | 2 +- Example/Common/ExampleSwitchConfig.c | 4 ++ Example/Common/ExampleSwitchConfig.h | 1 + Example/Common/ExampleTlsConfig.c | 46 +++++++++++++++++ Example/Common/ExampleTlsConfig.h | 20 ++++++++ Example/Threaded/main.c | 49 ++++++++++++++++--- .../OpenSsl/Source/SolidSyslogTlsStream.c | 32 ++++++++++++ Tests/Example/ExampleCommandLineTest.cpp | 10 ++++ Tests/Example/ExampleSwitchConfigTest.cpp | 6 +++ Tests/OpenSslFakeTest.cpp | 29 +++++++++++ Tests/SolidSyslogTlsStreamTest.cpp | 12 +++++ Tests/Support/OpenSslFake.c | 28 +++++++++++ Tests/Support/OpenSslFake.h | 2 + 14 files changed, 238 insertions(+), 10 deletions(-) create mode 100644 Example/Common/ExampleTlsConfig.c create mode 100644 Example/Common/ExampleTlsConfig.h diff --git a/Example/CMakeLists.txt b/Example/CMakeLists.txt index a01a3a50..b1c5fe37 100644 --- a/Example/CMakeLists.txt +++ b/Example/CMakeLists.txt @@ -5,6 +5,7 @@ if(SOLIDSYSLOG_POSIX) Common/ExampleServiceThread.c Common/ExampleUdpConfig.c Common/ExampleTcpConfig.c + Common/ExampleTlsConfig.c Common/ExampleInteractive.c Common/ExampleSwitchConfig.c ) @@ -17,9 +18,11 @@ if(SOLIDSYSLOG_POSIX) ${CMAKE_CURRENT_SOURCE_DIR}/Common ) - # Threaded example: PosixMessageQueueBuffer, two pthreads (requires POSIX threads and mqueue) + # Threaded example: PosixMessageQueueBuffer, two pthreads (requires POSIX threads and mqueue), + # OpenSSL for the TLS transport. + find_package(OpenSSL REQUIRED) add_executable(SolidSyslogThreadedExample Threaded/main.c ${COMMON_SOURCES}) - target_link_libraries(SolidSyslogThreadedExample PRIVATE ${PROJECT_NAME} Threads::Threads) + target_link_libraries(SolidSyslogThreadedExample PRIVATE ${PROJECT_NAME} Threads::Threads OpenSSL::SSL OpenSSL::Crypto) target_include_directories(SolidSyslogThreadedExample PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Common ) diff --git a/Example/Common/ExampleCommandLine.c b/Example/Common/ExampleCommandLine.c index addc444c..716289e4 100644 --- a/Example/Common/ExampleCommandLine.c +++ b/Example/Common/ExampleCommandLine.c @@ -81,7 +81,7 @@ int ExampleCommandLine_Parse(int argc, char* argv[], struct ExampleOptions* opti options->msg = optarg; break; case 't': - if ((strcmp(optarg, "udp") != 0) && (strcmp(optarg, "tcp") != 0)) + if ((strcmp(optarg, "udp") != 0) && (strcmp(optarg, "tcp") != 0) && (strcmp(optarg, "tls") != 0)) { return 1; } diff --git a/Example/Common/ExampleSwitchConfig.c b/Example/Common/ExampleSwitchConfig.c index 9117a2c3..51ec4c6e 100644 --- a/Example/Common/ExampleSwitchConfig.c +++ b/Example/Common/ExampleSwitchConfig.c @@ -14,6 +14,10 @@ void ExampleSwitchConfig_SetByName(const char* name) { selectedIndex = EXAMPLE_SWITCH_TCP; } + else if (strcmp(name, "tls") == 0) + { + selectedIndex = EXAMPLE_SWITCH_TLS; + } } uint8_t ExampleSwitchConfig_Selector(void) diff --git a/Example/Common/ExampleSwitchConfig.h b/Example/Common/ExampleSwitchConfig.h index 36eb2d56..adbf70d3 100644 --- a/Example/Common/ExampleSwitchConfig.h +++ b/Example/Common/ExampleSwitchConfig.h @@ -11,6 +11,7 @@ EXTERN_C_BEGIN { EXAMPLE_SWITCH_UDP, EXAMPLE_SWITCH_TCP, + EXAMPLE_SWITCH_TLS, EXAMPLE_SWITCH_COUNT, }; diff --git a/Example/Common/ExampleTlsConfig.c b/Example/Common/ExampleTlsConfig.c new file mode 100644 index 00000000..8c79cc47 --- /dev/null +++ b/Example/Common/ExampleTlsConfig.c @@ -0,0 +1,46 @@ +#include "ExampleTlsConfig.h" +#include "SolidSyslogFormatter.h" + +#include + +enum +{ + EXAMPLE_TLS_PORT = 6514 /* RFC 5425 */ +}; + +/* Test CA for BDD. Paths are relative to the working directory the example is + * launched from (/workspaces/SolidSyslog in the BDD container). */ +static const char* const EXAMPLE_TLS_CA_BUNDLE_PATH = "Bdd/syslog-ng/tls/ca.pem"; + +const char* ExampleTlsConfig_GetHost(void) +{ + return "syslog-ng"; +} + +int ExampleTlsConfig_GetPort(void) +{ + return EXAMPLE_TLS_PORT; +} + +const char* ExampleTlsConfig_GetCaBundlePath(void) +{ + return EXAMPLE_TLS_CA_BUNDLE_PATH; +} + +const char* ExampleTlsConfig_GetServerName(void) +{ + return ExampleTlsConfig_GetHost(); +} + +void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) +{ + SolidSyslogFormatter_BoundedString(endpoint->host, ExampleTlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); + endpoint->port = (uint16_t) ExampleTlsConfig_GetPort(); +} + +/* Static config — host/port never change, so version stays 0 forever and the + sender connects exactly once. */ +uint32_t ExampleTlsConfig_GetEndpointVersion(void) +{ + return 0; +} diff --git a/Example/Common/ExampleTlsConfig.h b/Example/Common/ExampleTlsConfig.h new file mode 100644 index 00000000..c22df031 --- /dev/null +++ b/Example/Common/ExampleTlsConfig.h @@ -0,0 +1,20 @@ +#ifndef EXAMPLETLSCONFIG_H +#define EXAMPLETLSCONFIG_H + +#include "ExternC.h" +#include "SolidSyslogEndpoint.h" + +#include + +EXTERN_C_BEGIN + + const char* ExampleTlsConfig_GetHost(void); + int ExampleTlsConfig_GetPort(void); + const char* ExampleTlsConfig_GetCaBundlePath(void); + const char* ExampleTlsConfig_GetServerName(void); + void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint); + uint32_t ExampleTlsConfig_GetEndpointVersion(void); + +EXTERN_C_END + +#endif /* EXAMPLETLSCONFIG_H */ diff --git a/Example/Threaded/main.c b/Example/Threaded/main.c index 5d29e58c..7ea9eb13 100644 --- a/Example/Threaded/main.c +++ b/Example/Threaded/main.c @@ -4,6 +4,7 @@ #include "ExampleServiceThread.h" #include "ExampleSwitchConfig.h" #include "ExampleTcpConfig.h" +#include "ExampleTlsConfig.h" #include "ExampleUdpConfig.h" #include "SolidSyslog.h" #include "SolidSyslogAtomicCounter.h" @@ -24,6 +25,7 @@ #include "SolidSyslogPosixTcpStream.h" #include "SolidSyslogSwitchingSender.h" #include "SolidSyslogStreamSender.h" +#include "SolidSyslogTlsStream.h" #include "SolidSyslogUdpSender.h" #include @@ -33,6 +35,7 @@ static const char* const STORE_PATH_PREFIX = "/tmp/STORE"; static struct SolidSyslogFile* storeReadFile; static struct SolidSyslogFile* storeWriteFile; +static bool tlsModeActive; static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) { @@ -50,8 +53,16 @@ static void* ServiceThreadEntry(void* arg) return NULL; } +/* SolidSyslogStreamSender and SolidSyslogPosixTcpStream are singletons in this + * walking-skeleton build, so we can't wire plain TCP and TLS simultaneously. + * The launched --transport picks one: "tls" wires UDP + TLS; anything else + * wires UDP + plain TCP (the pre-existing behaviour). Switching to the + * non-enabled reliable transport at runtime falls back to UDP. + */ static struct SolidSyslogSender* CreateSender(const struct ExampleOptions* options) { + tlsModeActive = (strcmp(options->transport, "tls") == 0); + struct SolidSyslogResolver* resolver = SolidSyslogGetAddrInfoResolver_Create(); static struct SolidSyslogUdpSenderConfig udpConfig = {0}; @@ -61,16 +72,36 @@ static struct SolidSyslogSender* CreateSender(const struct ExampleOptions* optio udpConfig.endpointVersion = ExampleUdpConfig_GetEndpointVersion; struct SolidSyslogSender* udpSender = SolidSyslogUdpSender_Create(&udpConfig); - static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; - tcpConfig.resolver = resolver; - tcpConfig.stream = SolidSyslogPosixTcpStream_Create(); - tcpConfig.endpoint = ExampleTcpConfig_GetEndpoint; - tcpConfig.endpointVersion = ExampleTcpConfig_GetEndpointVersion; - struct SolidSyslogSender* tcpSender = SolidSyslogStreamSender_Create(&tcpConfig); + struct SolidSyslogSender* reliableSender = NULL; + if (tlsModeActive) + { + static struct SolidSyslogTlsStreamConfig tlsStreamConfig = {0}; + tlsStreamConfig.transport = SolidSyslogPosixTcpStream_Create(); + tlsStreamConfig.caBundlePath = ExampleTlsConfig_GetCaBundlePath(); + tlsStreamConfig.serverName = ExampleTlsConfig_GetServerName(); + struct SolidSyslogStream* tlsStream = SolidSyslogTlsStream_Create(&tlsStreamConfig); + + static struct SolidSyslogStreamSenderConfig tlsSenderConfig = {0}; + tlsSenderConfig.resolver = resolver; + tlsSenderConfig.stream = tlsStream; + tlsSenderConfig.endpoint = ExampleTlsConfig_GetEndpoint; + tlsSenderConfig.endpointVersion = ExampleTlsConfig_GetEndpointVersion; + reliableSender = SolidSyslogStreamSender_Create(&tlsSenderConfig); + } + else + { + static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; + tcpConfig.resolver = resolver; + tcpConfig.stream = SolidSyslogPosixTcpStream_Create(); + tcpConfig.endpoint = ExampleTcpConfig_GetEndpoint; + tcpConfig.endpointVersion = ExampleTcpConfig_GetEndpointVersion; + reliableSender = SolidSyslogStreamSender_Create(&tcpConfig); + } static struct SolidSyslogSender* inners[EXAMPLE_SWITCH_COUNT]; inners[EXAMPLE_SWITCH_UDP] = udpSender; - inners[EXAMPLE_SWITCH_TCP] = tcpSender; + inners[EXAMPLE_SWITCH_TCP] = tlsModeActive ? udpSender : reliableSender; + inners[EXAMPLE_SWITCH_TLS] = tlsModeActive ? reliableSender : udpSender; static struct SolidSyslogSwitchingSenderConfig switchConfig = {0}; switchConfig.senders = inners; @@ -135,6 +166,10 @@ static void DestroySender(void) { SolidSyslogSwitchingSender_Destroy(); SolidSyslogStreamSender_Destroy(); + if (tlsModeActive) + { + SolidSyslogTlsStream_Destroy(); + } SolidSyslogPosixTcpStream_Destroy(); SolidSyslogUdpSender_Destroy(); SolidSyslogPosixDatagram_Destroy(); diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index cbbea596..2dba8915 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -16,8 +16,10 @@ static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t static void Close(struct SolidSyslogStream* self); static SSL_CTX* CreateSslContext(const char* caBundlePath); static BIO* CreateTransportBio(void); +static int TransportBioCreate(BIO* bio); static int TransportBioRead(BIO* bio, char* buffer, int size); static int TransportBioWrite(BIO* bio, const char* buffer, int size); +static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg); static struct SolidSyslogTlsStream instance; @@ -76,11 +78,21 @@ static void Close(struct SolidSyslogStream* self) static BIO* CreateTransportBio(void) { BIO_METHOD* method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "SolidSyslog transport BIO"); + BIO_meth_set_create(method, TransportBioCreate); BIO_meth_set_read(method, TransportBioRead); BIO_meth_set_write(method, TransportBioWrite); + BIO_meth_set_ctrl(method, TransportBioCtrl); return BIO_new(method); } +/* Called when BIO_new instantiates a BIO from our method. Marking init=1 tells + * OpenSSL the BIO is ready for I/O; without it SSL_connect bails early. */ +static int TransportBioCreate(BIO* bio) +{ + BIO_set_init(bio, 1); + return 1; +} + static int TransportBioRead(BIO* bio, char* buffer, int size) { struct SolidSyslogStream* transport = (struct SolidSyslogStream*) BIO_get_data(bio); @@ -93,6 +105,26 @@ static int TransportBioWrite(BIO* bio, const char* buffer, int size) return SolidSyslogStream_Send(transport, buffer, (size_t) size) ? size : -1; } +/* Minimal ctrl handler. OpenSSL calls this for a variety of control commands + * during normal operation; returning 1 for the common lifecycle commands lets + * SSL_connect / SSL_write / SSL_shutdown proceed. Unknown commands return 0. */ +static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg) +{ + (void) bio; + (void) larg; + (void) parg; + switch (cmd) + { + case BIO_CTRL_FLUSH: + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + case BIO_CTRL_DUP: + return 1; + default: + return 0; + } +} + static SSL_CTX* CreateSslContext(const char* caBundlePath) { SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); diff --git a/Tests/Example/ExampleCommandLineTest.cpp b/Tests/Example/ExampleCommandLineTest.cpp index bb03f3f4..d59d8113 100644 --- a/Tests/Example/ExampleCommandLineTest.cpp +++ b/Tests/Example/ExampleCommandLineTest.cpp @@ -166,6 +166,16 @@ TEST(ExampleCommandLine, TrailingTextMaxFileSizeReturnsOne) LONGS_EQUAL(1, Parse(3, argv)); } +TEST(ExampleCommandLine, TransportTlsAccepted) +{ + char arg0[] = "test"; + char argFlag[] = "--transport"; + char argVal[] = "tls"; + char* argv[] = {arg0, argFlag, argVal, nullptr}; + LONGS_EQUAL(0, Parse(3, argv)); + STRCMP_EQUAL("tls", options.transport); +} + TEST(ExampleCommandLine, NoSdFlag) { char arg0[] = "test"; diff --git a/Tests/Example/ExampleSwitchConfigTest.cpp b/Tests/Example/ExampleSwitchConfigTest.cpp index 9dda02be..fd349d6f 100644 --- a/Tests/Example/ExampleSwitchConfigTest.cpp +++ b/Tests/Example/ExampleSwitchConfigTest.cpp @@ -24,6 +24,12 @@ TEST(ExampleSwitchConfig, SetByNameTcpSelectsTcpIndex) LONGS_EQUAL(EXAMPLE_SWITCH_TCP, ExampleSwitchConfig_Selector()); } +TEST(ExampleSwitchConfig, SetByNameTlsSelectsTlsIndex) +{ + ExampleSwitchConfig_SetByName("tls"); + LONGS_EQUAL(EXAMPLE_SWITCH_TLS, ExampleSwitchConfig_Selector()); +} + TEST(ExampleSwitchConfig, UnknownNameLeavesPreviousSelection) { ExampleSwitchConfig_SetByName("tcp"); diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index 40c4808e..c55ba07b 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -207,6 +207,35 @@ TEST(OpenSslFake, BioMethSetWriteCapturesCallback) POINTERS_EQUAL((void*) DummyWrite, (void*) OpenSslFake_LastBioWriteCallback()); } +static long DummyCtrl(BIO* bio, int cmd, long larg, void* parg) +{ + (void) bio; + (void) cmd; + (void) larg; + (void) parg; + return 0; +} + +TEST(OpenSslFake, BioMethSetCtrlCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_ctrl(method, DummyCtrl); + POINTERS_EQUAL((void*) DummyCtrl, (void*) OpenSslFake_LastBioCtrlCallback()); +} + +static int DummyCreate(BIO* bio) +{ + (void) bio; + return 1; +} + +TEST(OpenSslFake, BioMethSetCreateCapturesCallback) +{ + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO_meth_set_create(method, DummyCreate); + POINTERS_EQUAL((void*) DummyCreate, (void*) OpenSslFake_LastBioCreateCallback()); +} + TEST(OpenSslFake, WriteIncrementsCount) { SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index a25cc0ee..9f86f769 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -385,3 +385,15 @@ TEST(SolidSyslogTlsStream, OpenSkipsSslSetupWhenTransportOpenFails) SolidSyslogStream_Open(stream, addr); LONGS_EQUAL(0, OpenSslFake_CtxNewCallCount()); } + +TEST(SolidSyslogTlsStream, OpenWiresBioCtrlCallback) +{ + SolidSyslogStream_Open(stream, addr); + CHECK_TRUE(OpenSslFake_LastBioCtrlCallback() != nullptr); +} + +TEST(SolidSyslogTlsStream, OpenWiresBioCreateCallback) +{ + SolidSyslogStream_Open(stream, addr); + CHECK_TRUE(OpenSslFake_LastBioCreateCallback() != nullptr); +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 111e1130..3ea07ee7 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -42,6 +42,8 @@ static SSL_CTX* lastSslNewCtxArg; /* BIO_meth_set_read / BIO_meth_set_write */ static BIO_METHOD* lastBioMethSetReadMethodArg; static int (*lastBioReadCallback)(BIO*, char*, int); +static long (*lastBioCtrlCallback)(BIO*, int, long, void*); +static int (*lastBioCreateCallback)(BIO*); static BIO_METHOD* lastBioMethSetWriteMethodArg; static int (*lastBioWriteCallback)(BIO*, const char*, int); @@ -110,6 +112,8 @@ void OpenSslFake_Reset(void) lastSslNewCtxArg = NULL; lastBioMethSetReadMethodArg = NULL; lastBioReadCallback = NULL; + lastBioCtrlCallback = NULL; + lastBioCreateCallback = NULL; lastBioMethSetWriteMethodArg = NULL; lastBioWriteCallback = NULL; bioNewCallCount = 0; @@ -167,6 +171,10 @@ BIO_METHOD* OpenSslFake_LastBioMethReturned(void) { return (BIO_METHOD*) BIO_METHOD* OpenSslFake_LastBioMethSetReadMethodArg(void) { return lastBioMethSetReadMethodArg; } int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) { return lastBioReadCallback; } +long (*OpenSslFake_LastBioCtrlCallback(void))(BIO*, int, long, void*) { return lastBioCtrlCallback; } + +int (*OpenSslFake_LastBioCreateCallback(void))(BIO*) { return lastBioCreateCallback; } + BIO_METHOD* OpenSslFake_LastBioMethSetWriteMethodArg(void) { return lastBioMethSetWriteMethodArg; } int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) { return lastBioWriteCallback; } @@ -282,6 +290,26 @@ int BIO_meth_set_write(BIO_METHOD* method, int (*write)(BIO*, const char*, int)) return 1; } +int BIO_meth_set_ctrl(BIO_METHOD* method, long (*ctrl)(BIO*, int, long, void*)) +{ + (void) method; + lastBioCtrlCallback = ctrl; + return 1; +} + +int BIO_meth_set_create(BIO_METHOD* method, int (*create)(BIO*)) +{ + (void) method; + lastBioCreateCallback = create; + return 1; +} + +void BIO_set_init(BIO* bio, int init) +{ + (void) bio; + (void) init; +} + BIO* BIO_new(const BIO_METHOD* method) { bioNewCallCount++; diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index e69d4e7b..2548ab72 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -48,6 +48,8 @@ EXTERN_C_BEGIN /* BIO_meth_set_read */ struct bio_method_st* OpenSslFake_LastBioMethSetReadMethodArg(void); int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); + long (*OpenSslFake_LastBioCtrlCallback(void))(struct bio_st*, int, long, void*); + int (*OpenSslFake_LastBioCreateCallback(void))(struct bio_st*); /* BIO_meth_set_write */ struct bio_method_st* OpenSslFake_LastBioMethSetWriteMethodArg(void); From 1d45bde8a5841258f4c64ace3ab750ac16b680c8 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 05:08:32 +0100 Subject: [PATCH 13/19] S3.7 cycle P7: invoke-and-assert tests for BIO ctrl + create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses a testing-as-documentation gap David flagged: the tests captured "a ctrl callback was set" and "a create callback was set" but never invoked them, so a reader of the tests couldn't tell what the callbacks actually do. Now each callback is retrieved from the fake and invoked directly, with assertions on the behaviour. Four new tests: - BioCtrlCallbackReturnsSuccessForFlush — the critical one; real libssl calls BIO_ctrl(BIO_CTRL_FLUSH) during SSL_connect, and our missing-ctrl-handler was the bug P6 fixed. - BioCtrlCallbackReturnsSuccessForPushPopDup — the other lifecycle commands our handler recognises. - BioCtrlCallbackReturnsFailureForUnknownCommand — documents the "return 0 for anything we don't know" behaviour. - BioCreateCallbackMarksBioInitialised — invokes the create callback, verifies it calls BIO_set_init(bio, 1). Fake gains LastSetInitArg so the assertion has something to check. This mirrors the existing pattern for Read/Write callbacks (BioReadCallbackDelegatesToTransportRead, etc.) — tests that actually exercise the registered callback. Tests: 721 total (was 717; +4). All green. Full build clean. BDD smoke test — TLS end-to-end still green. --- Tests/SolidSyslogTlsStreamTest.cpp | 32 ++++++++++++++++++++++++++++++ Tests/Support/OpenSslFake.c | 6 +++++- Tests/Support/OpenSslFake.h | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index 9f86f769..ea84334a 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -397,3 +397,35 @@ TEST(SolidSyslogTlsStream, OpenWiresBioCreateCallback) SolidSyslogStream_Open(stream, addr); CHECK_TRUE(OpenSslFake_LastBioCreateCallback() != nullptr); } + +TEST(SolidSyslogTlsStream, BioCtrlCallbackReturnsSuccessForFlush) +{ + SolidSyslogStream_Open(stream, addr); + auto ctrlFn = OpenSslFake_LastBioCtrlCallback(); + LONGS_EQUAL(1, ctrlFn(OpenSslFake_LastBioReturned(), BIO_CTRL_FLUSH, 0, nullptr)); +} + +TEST(SolidSyslogTlsStream, BioCtrlCallbackReturnsSuccessForPushPopDup) +{ + SolidSyslogStream_Open(stream, addr); + auto ctrlFn = OpenSslFake_LastBioCtrlCallback(); + BIO* bio = OpenSslFake_LastBioReturned(); + LONGS_EQUAL(1, ctrlFn(bio, BIO_CTRL_PUSH, 0, nullptr)); + LONGS_EQUAL(1, ctrlFn(bio, BIO_CTRL_POP, 0, nullptr)); + LONGS_EQUAL(1, ctrlFn(bio, BIO_CTRL_DUP, 0, nullptr)); +} + +TEST(SolidSyslogTlsStream, BioCtrlCallbackReturnsFailureForUnknownCommand) +{ + SolidSyslogStream_Open(stream, addr); + auto ctrlFn = OpenSslFake_LastBioCtrlCallback(); + LONGS_EQUAL(0, ctrlFn(OpenSslFake_LastBioReturned(), /* arbitrary unsupported cmd */ 9999, 0, nullptr)); +} + +TEST(SolidSyslogTlsStream, BioCreateCallbackMarksBioInitialised) +{ + SolidSyslogStream_Open(stream, addr); + auto createFn = OpenSslFake_LastBioCreateCallback(); + createFn(OpenSslFake_LastBioReturned()); + LONGS_EQUAL(1, OpenSslFake_LastSetInitArg()); +} diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 3ea07ee7..4eb4b8a7 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -44,6 +44,7 @@ static BIO_METHOD* lastBioMethSetReadMethodArg; static int (*lastBioReadCallback)(BIO*, char*, int); static long (*lastBioCtrlCallback)(BIO*, int, long, void*); static int (*lastBioCreateCallback)(BIO*); +static int lastSetInitArg; static BIO_METHOD* lastBioMethSetWriteMethodArg; static int (*lastBioWriteCallback)(BIO*, const char*, int); @@ -114,6 +115,7 @@ void OpenSslFake_Reset(void) lastBioReadCallback = NULL; lastBioCtrlCallback = NULL; lastBioCreateCallback = NULL; + lastSetInitArg = 0; lastBioMethSetWriteMethodArg = NULL; lastBioWriteCallback = NULL; bioNewCallCount = 0; @@ -175,6 +177,8 @@ long (*OpenSslFake_LastBioCtrlCallback(void))(BIO*, int, long, void*) { return l int (*OpenSslFake_LastBioCreateCallback(void))(BIO*) { return lastBioCreateCallback; } +int OpenSslFake_LastSetInitArg(void) { return lastSetInitArg; } + BIO_METHOD* OpenSslFake_LastBioMethSetWriteMethodArg(void) { return lastBioMethSetWriteMethodArg; } int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) { return lastBioWriteCallback; } @@ -307,7 +311,7 @@ int BIO_meth_set_create(BIO_METHOD* method, int (*create)(BIO*)) void BIO_set_init(BIO* bio, int init) { (void) bio; - (void) init; + lastSetInitArg = init; } BIO* BIO_new(const BIO_METHOD* method) diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index 2548ab72..d58ff294 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -50,6 +50,7 @@ EXTERN_C_BEGIN int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); long (*OpenSslFake_LastBioCtrlCallback(void))(struct bio_st*, int, long, void*); int (*OpenSslFake_LastBioCreateCallback(void))(struct bio_st*); + int OpenSslFake_LastSetInitArg(void); /* BIO_meth_set_write */ struct bio_method_st* OpenSslFake_LastBioMethSetWriteMethodArg(void); From c1c76080b7722d028172fdd6e1e071989a8dca37 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 05:28:33 +0100 Subject: [PATCH 14/19] S3.7 final gauntlet: BDD un-wip + preset fixes - tls_transport.feature un-@wip; PER_TRANSPORT_LOG gains tls; RECEIVED_TLS_LOG exported from environment. - clang-tidy clean-ups on the OpenSslFake (parameter names aligned with OpenSSL's declarations: biom / type / a / ptr / init); NOLINT for fixed-signature OpenSSL functions where adjacent- swappable-params rule fires; NOLINT on recv for the same reason. - SocketFake recv gets the POSIX-API NOLINT pattern already used by getaddrinfo. - TransportBioCtrl gets bugprone-easily-swappable-parameters NOLINT (OpenSSL BIO_ctrl_fn contract). - Test-only DummyRead/DummyWrite/DummyCtrl/DummyCreate helpers renamed + NOLINT where relevant; comparisons switched to FUNCTIONPOINTERS_EQUAL (avoids C-style / reinterpret_cast). - cppcheck suppressions on StreamFakeTest and TlsStreamTest fixtures for the unreadVariable false-positive (CppUTest macros not modelled). - clang-analyzer: guarded BIO callback invocations with an explicit null-check early return so CHECK_TRUE doesn't leave analyser with possible-null deref. - clang-format: full-tree auto-format applied; added missing off/on markers around OpenSslFakeTest's TEST_GROUP. Gauntlet result: - debug: 721 tests pass - clang-debug: 721 tests pass - sanitize: 721 tests pass (ASan + UBSan clean) - coverage: 99.9% overall (1177/1178), TlsStream 100% - tidy: clean (with documented NOLINTs on fixed-signature APIs) - cppcheck: clean - format: clean - bdd: 16 features / 33 scenarios / 171 steps green (was 15/32/168 before S3.7; +1 TLS feature, 1 TLS scenario, 3 TLS steps) --- Bdd/features/environment.py | 1 + Bdd/features/steps/syslog_steps.py | 2 + Bdd/features/tls_transport.feature | 2 +- Example/Common/ExampleTlsConfig.h | 2 +- Interface/SolidSyslogStream.h | 8 +- .../OpenSsl/Source/SolidSyslogTlsStream.c | 3 +- .../Posix/Source/SolidSyslogAddressInternal.h | 3 +- Tests/Example/ExampleCommandLineTest.cpp | 2 +- Tests/OpenSslFakeTest.cpp | 30 +- Tests/SolidSyslogPosixTcpStreamTest.cpp | 2 +- Tests/SolidSyslogTlsStreamTest.cpp | 15 +- Tests/StreamFake.h | 24 +- Tests/StreamFakeTest.cpp | 3 + Tests/Support/OpenSslFake.c | 292 ++++++++++++++---- Tests/Support/OpenSslFake.h | 2 +- Tests/Support/SocketFake.c | 2 + 16 files changed, 291 insertions(+), 102 deletions(-) diff --git a/Bdd/features/environment.py b/Bdd/features/environment.py index 96400e31..8a0e5a04 100644 --- a/Bdd/features/environment.py +++ b/Bdd/features/environment.py @@ -14,6 +14,7 @@ STORE_PATH_PREFIX = "/tmp/STORE" RECEIVED_UDP_LOG = "Bdd/output/received_udp.log" RECEIVED_TCP_LOG = "Bdd/output/received_tcp.log" +RECEIVED_TLS_LOG = "Bdd/output/received_tls.log" def wait_for_tcp_port_open(host="syslog-ng", port=5514, timeout=5): diff --git a/Bdd/features/steps/syslog_steps.py b/Bdd/features/steps/syslog_steps.py index fb58a932..41745f23 100644 --- a/Bdd/features/steps/syslog_steps.py +++ b/Bdd/features/steps/syslog_steps.py @@ -12,6 +12,7 @@ from behave import given, when, then from environment import ( RECEIVED_TCP_LOG, + RECEIVED_TLS_LOG, RECEIVED_UDP_LOG, STORE_FILE_PATH, STORE_PATH_PREFIX, @@ -20,6 +21,7 @@ PER_TRANSPORT_LOG = { "udp": RECEIVED_UDP_LOG, "tcp": RECEIVED_TCP_LOG, + "tls": RECEIVED_TLS_LOG, } # POSIX-only paths used by the @buffered/threaded scenarios. The cross-platform diff --git a/Bdd/features/tls_transport.feature b/Bdd/features/tls_transport.feature index 281e12d0..f6e39c67 100644 --- a/Bdd/features/tls_transport.feature +++ b/Bdd/features/tls_transport.feature @@ -1,4 +1,4 @@ -@tls @buffered @wip +@tls @buffered Feature: TLS message delivery The threaded example sends messages via TLS transport (RFC 5425) — RFC 6587 octet-counting framing over TLS — to syslog-ng. diff --git a/Example/Common/ExampleTlsConfig.h b/Example/Common/ExampleTlsConfig.h index c22df031..c5a698d1 100644 --- a/Example/Common/ExampleTlsConfig.h +++ b/Example/Common/ExampleTlsConfig.h @@ -12,7 +12,7 @@ EXTERN_C_BEGIN int ExampleTlsConfig_GetPort(void); const char* ExampleTlsConfig_GetCaBundlePath(void); const char* ExampleTlsConfig_GetServerName(void); - void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint); + void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); uint32_t ExampleTlsConfig_GetEndpointVersion(void); EXTERN_C_END diff --git a/Interface/SolidSyslogStream.h b/Interface/SolidSyslogStream.h index bbc11aa6..92e1f131 100644 --- a/Interface/SolidSyslogStream.h +++ b/Interface/SolidSyslogStream.h @@ -11,10 +11,10 @@ EXTERN_C_BEGIN struct SolidSyslogStream; - bool SolidSyslogStream_Open(struct SolidSyslogStream* stream, const struct SolidSyslogAddress* addr); - bool SolidSyslogStream_Send(struct SolidSyslogStream* stream, const void* buffer, size_t size); - ssize_t SolidSyslogStream_Read(struct SolidSyslogStream* stream, void* buffer, size_t size); - void SolidSyslogStream_Close(struct SolidSyslogStream* stream); + bool SolidSyslogStream_Open(struct SolidSyslogStream * stream, const struct SolidSyslogAddress* addr); + bool SolidSyslogStream_Send(struct SolidSyslogStream * stream, const void* buffer, size_t size); + ssize_t SolidSyslogStream_Read(struct SolidSyslogStream * stream, void* buffer, size_t size); + void SolidSyslogStream_Close(struct SolidSyslogStream * stream); EXTERN_C_END diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index 2dba8915..1435ab7f 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -50,7 +50,7 @@ static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress } stream->ctx = CreateSslContext(stream->config.caBundlePath); stream->ssl = SSL_new(stream->ctx); - BIO* bio = CreateTransportBio(); + BIO* bio = CreateTransportBio(); BIO_set_data(bio, stream->config.transport); SSL_set_bio(stream->ssl, bio, bio); if (stream->config.serverName != NULL) @@ -108,6 +108,7 @@ static int TransportBioWrite(BIO* bio, const char* buffer, int size) /* Minimal ctrl handler. OpenSSL calls this for a variety of control commands * during normal operation; returning 1 for the common lifecycle commands lets * SSL_connect / SSL_write / SSL_shutdown proceed. Unknown commands return 0. */ +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL BIO_ctrl_fn contract static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg) { (void) bio; diff --git a/Platform/Posix/Source/SolidSyslogAddressInternal.h b/Platform/Posix/Source/SolidSyslogAddressInternal.h index 46927eb5..7773e845 100644 --- a/Platform/Posix/Source/SolidSyslogAddressInternal.h +++ b/Platform/Posix/Source/SolidSyslogAddressInternal.h @@ -7,8 +7,7 @@ #include #include -SOLIDSYSLOG_STATIC_ASSERT(sizeof(struct sockaddr_in) <= SOLIDSYSLOG_ADDRESS_SIZE, - SolidSyslogAddressStorage_too_small_for_sockaddr_in); +SOLIDSYSLOG_STATIC_ASSERT(sizeof(struct sockaddr_in) <= SOLIDSYSLOG_ADDRESS_SIZE, SolidSyslogAddressStorage_too_small_for_sockaddr_in); static inline struct sockaddr_in* SolidSyslogAddress_AsSockaddrIn(struct SolidSyslogAddress* address) { diff --git a/Tests/Example/ExampleCommandLineTest.cpp b/Tests/Example/ExampleCommandLineTest.cpp index d59d8113..f3088a2d 100644 --- a/Tests/Example/ExampleCommandLineTest.cpp +++ b/Tests/Example/ExampleCommandLineTest.cpp @@ -168,7 +168,7 @@ TEST(ExampleCommandLine, TrailingTextMaxFileSizeReturnsOne) TEST(ExampleCommandLine, TransportTlsAccepted) { - char arg0[] = "test"; + char arg0[] = "test"; char argFlag[] = "--transport"; char argVal[] = "tls"; char* argv[] = {arg0, argFlag, argVal, nullptr}; diff --git a/Tests/OpenSslFakeTest.cpp b/Tests/OpenSslFakeTest.cpp index c55ba07b..8822a368 100644 --- a/Tests/OpenSslFakeTest.cpp +++ b/Tests/OpenSslFakeTest.cpp @@ -2,10 +2,12 @@ #include "OpenSslFake.h" #include +// clang-format off TEST_GROUP(OpenSslFake) { void setup() override { OpenSslFake_Reset(); } }; +// clang-format on TEST(OpenSslFake, CtxNewCountIsZeroAfterReset) { @@ -161,22 +163,23 @@ TEST(OpenSslFake, Set1HostCapturesHostname) TEST(OpenSslFake, BioSetDataCapturesData) { - BIO_METHOD* method = BIO_meth_new(0, "fake"); - BIO* bio = BIO_new(method); - int sentinel; + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel = 0; BIO_set_data(bio, &sentinel); POINTERS_EQUAL(&sentinel, OpenSslFake_LastSetDataArg()); } TEST(OpenSslFake, BioGetDataReturnsPreviouslySetData) { - BIO_METHOD* method = BIO_meth_new(0, "fake"); - BIO* bio = BIO_new(method); - int sentinel; + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel = 0; BIO_set_data(bio, &sentinel); POINTERS_EQUAL(&sentinel, BIO_get_data(bio)); } +// NOLINTNEXTLINE(readability-non-const-parameter) -- signature fixed by OpenSSL BIO_meth_set_read contract static int DummyRead(BIO* bio, char* buf, int size) { (void) bio; @@ -189,7 +192,7 @@ TEST(OpenSslFake, BioMethSetReadCapturesCallback) { BIO_METHOD* method = BIO_meth_new(0, "fake"); BIO_meth_set_read(method, DummyRead); - POINTERS_EQUAL((void*) DummyRead, (void*) OpenSslFake_LastBioReadCallback()); + FUNCTIONPOINTERS_EQUAL(DummyRead, OpenSslFake_LastBioReadCallback()); } static int DummyWrite(BIO* bio, const char* buf, int size) @@ -204,9 +207,10 @@ TEST(OpenSslFake, BioMethSetWriteCapturesCallback) { BIO_METHOD* method = BIO_meth_new(0, "fake"); BIO_meth_set_write(method, DummyWrite); - POINTERS_EQUAL((void*) DummyWrite, (void*) OpenSslFake_LastBioWriteCallback()); + FUNCTIONPOINTERS_EQUAL(DummyWrite, OpenSslFake_LastBioWriteCallback()); } +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL BIO_meth_set_ctrl contract static long DummyCtrl(BIO* bio, int cmd, long larg, void* parg) { (void) bio; @@ -220,7 +224,7 @@ TEST(OpenSslFake, BioMethSetCtrlCapturesCallback) { BIO_METHOD* method = BIO_meth_new(0, "fake"); BIO_meth_set_ctrl(method, DummyCtrl); - POINTERS_EQUAL((void*) DummyCtrl, (void*) OpenSslFake_LastBioCtrlCallback()); + FUNCTIONPOINTERS_EQUAL(DummyCtrl, OpenSslFake_LastBioCtrlCallback()); } static int DummyCreate(BIO* bio) @@ -233,7 +237,7 @@ TEST(OpenSslFake, BioMethSetCreateCapturesCallback) { BIO_METHOD* method = BIO_meth_new(0, "fake"); BIO_meth_set_create(method, DummyCreate); - POINTERS_EQUAL((void*) DummyCreate, (void*) OpenSslFake_LastBioCreateCallback()); + FUNCTIONPOINTERS_EQUAL(DummyCreate, OpenSslFake_LastBioCreateCallback()); } TEST(OpenSslFake, WriteIncrementsCount) @@ -360,9 +364,9 @@ TEST(OpenSslFake, BioMethNewReturnValueIsSurfaced) TEST(OpenSslFake, BioSetDataCapturesBioArg) { - BIO_METHOD* method = BIO_meth_new(0, "fake"); - BIO* bio = BIO_new(method); - int sentinel; + BIO_METHOD* method = BIO_meth_new(0, "fake"); + BIO* bio = BIO_new(method); + int sentinel = 0; BIO_set_data(bio, &sentinel); POINTERS_EQUAL(bio, OpenSslFake_LastSetDataBioArg()); } diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index c64d41e8..c65fe7a4 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -222,7 +222,7 @@ TEST(SolidSyslogPosixTcpStream, ReadReturnsRecvReturnValue) { SocketFake_SetRecvReturn(7); SolidSyslogStream_Open(stream, addr); - char buf[16]; + char buf[16]; ssize_t n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); LONGS_EQUAL(7, n); } diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index ea84334a..56e3ab51 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -20,8 +20,10 @@ TEST_GROUP(SolidSyslogTlsStream) OpenSslFake_Reset(); transport = StreamFake_Create(); config.transport = transport; - stream = SolidSyslogTlsStream_Create(&config); - addr = SolidSyslogAddress_FromStorage(&addrStorage); + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + stream = SolidSyslogTlsStream_Create(&config); + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros + addr = SolidSyslogAddress_FromStorage(&addrStorage); } void teardown() override @@ -30,6 +32,7 @@ TEST_GROUP(SolidSyslogTlsStream) StreamFake_Destroy(transport); } }; + // clang-format on TEST(SolidSyslogTlsStream, CreateSucceeds) @@ -160,6 +163,10 @@ TEST(SolidSyslogTlsStream, BioReadCallbackDelegatesToTransportRead) SolidSyslogStream_Open(stream, addr); int (*readFn)(BIO*, char*, int) = OpenSslFake_LastBioReadCallback(); CHECK_TRUE(readFn != nullptr); + if (readFn == nullptr) + { + return; + } char buf[16]; readFn(OpenSslFake_LastBioReturned(), buf, sizeof(buf)); LONGS_EQUAL(1, StreamFake_ReadCallCount(transport)); @@ -170,6 +177,10 @@ TEST(SolidSyslogTlsStream, BioWriteCallbackDelegatesToTransportSend) SolidSyslogStream_Open(stream, addr); int (*writeFn)(BIO*, const char*, int) = OpenSslFake_LastBioWriteCallback(); CHECK_TRUE(writeFn != nullptr); + if (writeFn == nullptr) + { + return; + } const char msg[] = "hi"; writeFn(OpenSslFake_LastBioReturned(), msg, (int) sizeof(msg)); LONGS_EQUAL(1, StreamFake_SendCallCount(transport)); diff --git a/Tests/StreamFake.h b/Tests/StreamFake.h index 08958bf4..3a1af93d 100644 --- a/Tests/StreamFake.h +++ b/Tests/StreamFake.h @@ -13,18 +13,18 @@ EXTERN_C_BEGIN struct SolidSyslogAddress; struct SolidSyslogStream* StreamFake_Create(void); - void StreamFake_Destroy(struct SolidSyslogStream* stream); - int StreamFake_OpenCallCount(struct SolidSyslogStream* stream); - const struct SolidSyslogAddress* StreamFake_LastOpenAddr(struct SolidSyslogStream* stream); - int StreamFake_SendCallCount(struct SolidSyslogStream* stream); - const void* StreamFake_LastSendBuf(struct SolidSyslogStream* stream); - size_t StreamFake_LastSendSize(struct SolidSyslogStream* stream); - int StreamFake_ReadCallCount(struct SolidSyslogStream* stream); - void* StreamFake_LastReadBuf(struct SolidSyslogStream* stream); - size_t StreamFake_LastReadSize(struct SolidSyslogStream* stream); - void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, ssize_t value); - void StreamFake_SetOpenFails(struct SolidSyslogStream* stream, bool fails); - int StreamFake_CloseCallCount(struct SolidSyslogStream* stream); + void StreamFake_Destroy(struct SolidSyslogStream * stream); + int StreamFake_OpenCallCount(struct SolidSyslogStream * stream); + const struct SolidSyslogAddress* StreamFake_LastOpenAddr(struct SolidSyslogStream * stream); + int StreamFake_SendCallCount(struct SolidSyslogStream * stream); + const void* StreamFake_LastSendBuf(struct SolidSyslogStream * stream); + size_t StreamFake_LastSendSize(struct SolidSyslogStream * stream); + int StreamFake_ReadCallCount(struct SolidSyslogStream * stream); + void* StreamFake_LastReadBuf(struct SolidSyslogStream * stream); + size_t StreamFake_LastReadSize(struct SolidSyslogStream * stream); + void StreamFake_SetReadReturn(struct SolidSyslogStream * stream, ssize_t value); + void StreamFake_SetOpenFails(struct SolidSyslogStream * stream, bool fails); + int StreamFake_CloseCallCount(struct SolidSyslogStream * stream); EXTERN_C_END diff --git a/Tests/StreamFakeTest.cpp b/Tests/StreamFakeTest.cpp index 90f859d3..e0b4fc71 100644 --- a/Tests/StreamFakeTest.cpp +++ b/Tests/StreamFakeTest.cpp @@ -6,11 +6,14 @@ // clang-format off TEST_GROUP(StreamFake) { + // cppcheck-suppress unreadVariable -- used across TEST_GROUP methods; cppcheck does not model CppUTest macros struct SolidSyslogStream* stream = nullptr; + // cppcheck-suppress unreadVariable -- read by teardown and tests; cppcheck does not model CppUTest lifecycle void setup() override { stream = StreamFake_Create(); } void teardown() override { StreamFake_Destroy(stream); } }; + // clang-format on TEST(StreamFake, CreateSucceeds) diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 4eb4b8a7..2c5c810c 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -151,72 +151,235 @@ void OpenSslFake_Reset(void) * Accessors — grouped by the OpenSSL function they describe. * ------------------------------------------------------------------------- */ -int OpenSslFake_CtxNewCallCount(void) { return ctxNewCallCount; } -const SSL_METHOD* OpenSslFake_LastCtxNewMethodArg(void) { return lastCtxNewMethodArg; } -SSL_CTX* OpenSslFake_LastCtxReturned(void) { return (SSL_CTX*) &fakeCtxStorage; } +int OpenSslFake_CtxNewCallCount(void) +{ + return ctxNewCallCount; +} + +const SSL_METHOD* OpenSslFake_LastCtxNewMethodArg(void) +{ + return lastCtxNewMethodArg; +} + +SSL_CTX* OpenSslFake_LastCtxReturned(void) +{ + return (SSL_CTX*) &fakeCtxStorage; +} + +SSL_CTX* OpenSslFake_LastLoadVerifyLocationsCtxArg(void) +{ + return lastLoadVerifyLocationsCtxArg; +} + +const char* OpenSslFake_LastCaBundlePath(void) +{ + return lastCaBundlePath; +} + +SSL_CTX* OpenSslFake_LastSetVerifyCtxArg(void) +{ + return lastSetVerifyCtxArg; +} + +int OpenSslFake_LastVerifyMode(void) +{ + return lastVerifyMode; +} + +SSL_CTX* OpenSslFake_LastSslCtxCtrlCtxArg(void) +{ + return lastSslCtxCtrlCtxArg; +} + +long OpenSslFake_LastMinProtoVersion(void) +{ + return lastMinProtoVersion; +} + +int OpenSslFake_SslNewCallCount(void) +{ + return sslNewCallCount; +} + +SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) +{ + return lastSslNewCtxArg; +} + +SSL* OpenSslFake_LastSslReturned(void) +{ + return (SSL*) &fakeSslStorage; +} + +BIO_METHOD* OpenSslFake_LastBioMethReturned(void) +{ + return (BIO_METHOD*) &fakeBioMethStorage; +} + +BIO_METHOD* OpenSslFake_LastBioMethSetReadMethodArg(void) +{ + return lastBioMethSetReadMethodArg; +} + +int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) +{ + return lastBioReadCallback; +} + +long (*OpenSslFake_LastBioCtrlCallback(void))(BIO*, int, long, void*) +{ + return lastBioCtrlCallback; +} + +int (*OpenSslFake_LastBioCreateCallback(void))(BIO*) +{ + return lastBioCreateCallback; +} + +int OpenSslFake_LastSetInitArg(void) +{ + return lastSetInitArg; +} + +BIO_METHOD* OpenSslFake_LastBioMethSetWriteMethodArg(void) +{ + return lastBioMethSetWriteMethodArg; +} + +int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) +{ + return lastBioWriteCallback; +} -SSL_CTX* OpenSslFake_LastLoadVerifyLocationsCtxArg(void) { return lastLoadVerifyLocationsCtxArg; } -const char* OpenSslFake_LastCaBundlePath(void) { return lastCaBundlePath; } +int OpenSslFake_BioNewCallCount(void) +{ + return bioNewCallCount; +} -SSL_CTX* OpenSslFake_LastSetVerifyCtxArg(void) { return lastSetVerifyCtxArg; } -int OpenSslFake_LastVerifyMode(void) { return lastVerifyMode; } +const BIO_METHOD* OpenSslFake_LastBioNewMethodArg(void) +{ + return lastBioNewMethodArg; +} -SSL_CTX* OpenSslFake_LastSslCtxCtrlCtxArg(void) { return lastSslCtxCtrlCtxArg; } -long OpenSslFake_LastMinProtoVersion(void) { return lastMinProtoVersion; } +BIO* OpenSslFake_LastBioReturned(void) +{ + return (BIO*) &fakeBioStorage; +} -int OpenSslFake_SslNewCallCount(void) { return sslNewCallCount; } -SSL_CTX* OpenSslFake_LastSslNewCtxArg(void) { return lastSslNewCtxArg; } -SSL* OpenSslFake_LastSslReturned(void) { return (SSL*) &fakeSslStorage; } +BIO* OpenSslFake_LastSetDataBioArg(void) +{ + return lastSetDataBioArg; +} -BIO_METHOD* OpenSslFake_LastBioMethReturned(void) { return (BIO_METHOD*) &fakeBioMethStorage; } +void* OpenSslFake_LastSetDataArg(void) +{ + return lastSetDataArg; +} -BIO_METHOD* OpenSslFake_LastBioMethSetReadMethodArg(void) { return lastBioMethSetReadMethodArg; } -int (*OpenSslFake_LastBioReadCallback(void))(BIO*, char*, int) { return lastBioReadCallback; } +BIO* OpenSslFake_LastGetDataBioArg(void) +{ + return lastGetDataBioArg; +} -long (*OpenSslFake_LastBioCtrlCallback(void))(BIO*, int, long, void*) { return lastBioCtrlCallback; } +int OpenSslFake_SetBioCallCount(void) +{ + return setBioCallCount; +} -int (*OpenSslFake_LastBioCreateCallback(void))(BIO*) { return lastBioCreateCallback; } +SSL* OpenSslFake_LastSetBioSslArg(void) +{ + return lastSetBioSslArg; +} -int OpenSslFake_LastSetInitArg(void) { return lastSetInitArg; } +BIO* OpenSslFake_LastSetBioReadBioArg(void) +{ + return lastSetBioReadBioArg; +} -BIO_METHOD* OpenSslFake_LastBioMethSetWriteMethodArg(void) { return lastBioMethSetWriteMethodArg; } -int (*OpenSslFake_LastBioWriteCallback(void))(BIO*, const char*, int) { return lastBioWriteCallback; } +BIO* OpenSslFake_LastSetBioWriteBioArg(void) +{ + return lastSetBioWriteBioArg; +} -int OpenSslFake_BioNewCallCount(void) { return bioNewCallCount; } -const BIO_METHOD* OpenSslFake_LastBioNewMethodArg(void) { return lastBioNewMethodArg; } -BIO* OpenSslFake_LastBioReturned(void) { return (BIO*) &fakeBioStorage; } +SSL* OpenSslFake_LastSslCtrlSslArg(void) +{ + return lastSslCtrlSslArg; +} -BIO* OpenSslFake_LastSetDataBioArg(void) { return lastSetDataBioArg; } -void* OpenSslFake_LastSetDataArg(void) { return lastSetDataArg; } -BIO* OpenSslFake_LastGetDataBioArg(void) { return lastGetDataBioArg; } +const char* OpenSslFake_LastSniHostname(void) +{ + return lastSniHostname; +} -int OpenSslFake_SetBioCallCount(void) { return setBioCallCount; } -SSL* OpenSslFake_LastSetBioSslArg(void) { return lastSetBioSslArg; } -BIO* OpenSslFake_LastSetBioReadBioArg(void) { return lastSetBioReadBioArg; } -BIO* OpenSslFake_LastSetBioWriteBioArg(void) { return lastSetBioWriteBioArg; } +SSL* OpenSslFake_LastSet1HostSslArg(void) +{ + return lastSet1HostSslArg; +} -SSL* OpenSslFake_LastSslCtrlSslArg(void) { return lastSslCtrlSslArg; } -const char* OpenSslFake_LastSniHostname(void) { return lastSniHostname; } +const char* OpenSslFake_LastSet1Host(void) +{ + return lastSet1Host; +} -SSL* OpenSslFake_LastSet1HostSslArg(void) { return lastSet1HostSslArg; } -const char* OpenSslFake_LastSet1Host(void) { return lastSet1Host; } +int OpenSslFake_ConnectCallCount(void) +{ + return connectCallCount; +} + +SSL* OpenSslFake_LastConnectSslArg(void) +{ + return lastConnectSslArg; +} + +int OpenSslFake_WriteCallCount(void) +{ + return writeCallCount; +} + +SSL* OpenSslFake_LastWriteSslArg(void) +{ + return lastWriteSslArg; +} + +const void* OpenSslFake_LastWriteBuf(void) +{ + return lastWriteBuf; +} + +int OpenSslFake_LastWriteSize(void) +{ + return lastWriteSize; +} -int OpenSslFake_ConnectCallCount(void) { return connectCallCount; } -SSL* OpenSslFake_LastConnectSslArg(void) { return lastConnectSslArg; } +int OpenSslFake_ShutdownCallCount(void) +{ + return shutdownCallCount; +} -int OpenSslFake_WriteCallCount(void) { return writeCallCount; } -SSL* OpenSslFake_LastWriteSslArg(void) { return lastWriteSslArg; } -const void* OpenSslFake_LastWriteBuf(void) { return lastWriteBuf; } -int OpenSslFake_LastWriteSize(void) { return lastWriteSize; } +SSL* OpenSslFake_LastShutdownSslArg(void) +{ + return lastShutdownSslArg; +} -int OpenSslFake_ShutdownCallCount(void) { return shutdownCallCount; } -SSL* OpenSslFake_LastShutdownSslArg(void) { return lastShutdownSslArg; } +int OpenSslFake_FreeCallCount(void) +{ + return freeCallCount; +} -int OpenSslFake_FreeCallCount(void) { return freeCallCount; } -SSL* OpenSslFake_LastFreeSslArg(void) { return lastFreeSslArg; } +SSL* OpenSslFake_LastFreeSslArg(void) +{ + return lastFreeSslArg; +} -int OpenSslFake_CtxFreeCallCount(void) { return ctxFreeCallCount; } -SSL_CTX* OpenSslFake_LastCtxFreeCtxArg(void) { return lastCtxFreeCtxArg; } +int OpenSslFake_CtxFreeCallCount(void) +{ + return ctxFreeCallCount; +} + +SSL_CTX* OpenSslFake_LastCtxFreeCtxArg(void) +{ + return lastCtxFreeCtxArg; +} /* ------------------------------------------------------------------------- * Link-time substitution for OpenSSL — replaces libssl symbols in the test @@ -238,6 +401,7 @@ SSL_CTX* SSL_CTX_new(const SSL_METHOD* method) return (SSL_CTX*) &fakeCtxStorage; } +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL API int SSL_CTX_load_verify_locations(SSL_CTX* ctx, const char* CAfile, const char* CApath) { (void) CApath; @@ -255,6 +419,7 @@ void SSL_CTX_set_verify(SSL_CTX* ctx, int mode, SSL_verify_cb verify_callback) /* SSL_CTX_set_min_proto_version is a macro forwarding to SSL_CTX_ctrl; fake * intercepts the ctrl call for the min-proto command only. */ +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL API long SSL_CTX_ctrl(SSL_CTX* ctx, int cmd, long larg, void* parg) { (void) parg; @@ -280,56 +445,56 @@ BIO_METHOD* BIO_meth_new(int type, const char* name) return (BIO_METHOD*) &fakeBioMethStorage; } -int BIO_meth_set_read(BIO_METHOD* method, int (*read)(BIO*, char*, int)) +int BIO_meth_set_read(BIO_METHOD* biom, int (*read)(BIO*, char*, int)) { - lastBioMethSetReadMethodArg = method; + lastBioMethSetReadMethodArg = biom; lastBioReadCallback = read; return 1; } -int BIO_meth_set_write(BIO_METHOD* method, int (*write)(BIO*, const char*, int)) +int BIO_meth_set_write(BIO_METHOD* biom, int (*write)(BIO*, const char*, int)) { - lastBioMethSetWriteMethodArg = method; + lastBioMethSetWriteMethodArg = biom; lastBioWriteCallback = write; return 1; } -int BIO_meth_set_ctrl(BIO_METHOD* method, long (*ctrl)(BIO*, int, long, void*)) +int BIO_meth_set_ctrl(BIO_METHOD* biom, long (*ctrl)(BIO*, int, long, void*)) { - (void) method; + (void) biom; lastBioCtrlCallback = ctrl; return 1; } -int BIO_meth_set_create(BIO_METHOD* method, int (*create)(BIO*)) +int BIO_meth_set_create(BIO_METHOD* biom, int (*create)(BIO*)) { - (void) method; + (void) biom; lastBioCreateCallback = create; return 1; } -void BIO_set_init(BIO* bio, int init) +void BIO_set_init(BIO* a, int init) { - (void) bio; + (void) a; lastSetInitArg = init; } -BIO* BIO_new(const BIO_METHOD* method) +BIO* BIO_new(const BIO_METHOD* type) { bioNewCallCount++; - lastBioNewMethodArg = method; + lastBioNewMethodArg = type; return (BIO*) &fakeBioStorage; } -void BIO_set_data(BIO* bio, void* data) +void BIO_set_data(BIO* a, void* ptr) { - lastSetDataBioArg = bio; - lastSetDataArg = data; + lastSetDataBioArg = a; + lastSetDataArg = ptr; } -void* BIO_get_data(BIO* bio) +void* BIO_get_data(BIO* a) { - lastGetDataBioArg = bio; + lastGetDataBioArg = a; return lastSetDataArg; } @@ -343,6 +508,7 @@ void SSL_set_bio(SSL* ssl, BIO* rbio, BIO* wbio) /* SSL_set_tlsext_host_name is a macro forwarding to SSL_ctrl; fake intercepts * the SET_TLSEXT_HOSTNAME command and captures the hostname pointer. */ +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -- signature fixed by OpenSSL API long SSL_ctrl(SSL* ssl, int cmd, long larg, void* parg) { (void) larg; diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index d58ff294..3b82bd9b 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -50,7 +50,7 @@ EXTERN_C_BEGIN int (*OpenSslFake_LastBioReadCallback(void))(struct bio_st*, char*, int); long (*OpenSslFake_LastBioCtrlCallback(void))(struct bio_st*, int, long, void*); int (*OpenSslFake_LastBioCreateCallback(void))(struct bio_st*); - int OpenSslFake_LastSetInitArg(void); + int OpenSslFake_LastSetInitArg(void); /* BIO_meth_set_write */ struct bio_method_st* OpenSslFake_LastBioMethSetWriteMethodArg(void); diff --git a/Tests/Support/SocketFake.c b/Tests/Support/SocketFake.c index 8324f145..95539e67 100644 --- a/Tests/Support/SocketFake.c +++ b/Tests/Support/SocketFake.c @@ -476,6 +476,8 @@ int close(int fd) return 0; } +// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; parameter names differ from glibc +// internal names ssize_t recv(int sockfd, void* buf, size_t len, int flags) { recvCallCount++; From 790da746c83a9742245e5ee9a6d6bd7e95b786a4 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 06:29:23 +0100 Subject: [PATCH 15/19] S3.7: address CodeRabbit review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves 4 of the 5 CodeRabbit issues on PR #170 (the 5th is a false positive — OpenSslFake.c is already compiled via the PosixFakes static lib, replied on-thread). 1. SolidSyslogSsize typedef (portable replacement for ssize_t in public API). intptr_t-backed, MISRA-friendly stdint type, compiles on MSVC where POSIX ssize_t isn't available. Stream interface + StreamFake fake API + call sites updated. Internal Platform/Posix code still uses ssize_t and casts at the API boundary. 2. SOLIDSYSLOG_OPENSSL CMake option gating the OpenSSL-based TLS subsystem. Default ON when OpenSSL is discoverable; override -DSOLIDSYSLOG_OPENSSL=OFF for a UDP/TCP-only build. Platform/OpenSsl, OpenSslFake, TLS unit tests, ExampleTlsConfig, and example's libssl linkage are all gated on this option. Verified both configurations build and pass: OpenSSL on = 725 tests, OpenSSL off = 612 tests (TLS tests correctly dropped, no libssl in example's ldd). Known short-term sharp edge: main.c retains three #ifdef SOLIDSYSLOG_HAVE_OPENSSL sites guarding TLS wiring; follow-up S3.13 (#171) replaces these with per-backend CMake-selected factory files (ExampleTlsSender_OpenSsl.c / _WolfSsl.c / _Unavailable.c). 3. Wire Read in SolidSyslogTlsStream vtable. Critical bug: vtable slot was uninitialised, any future caller of SolidSyslogStream_Read on a TLS stream would have crashed. Read delegates to SSL_read. OpenSslFake gains SSL_read stub with count + arg captures. 4 new TlsStream tests covering the call, pointer-chain (ssl from new), buffer pass-through, size pass-through. 4. Tighten tls_transport.feature scenario to assert the transport-specific log (received_tls.log) via the existing "over tls" step, not just the aggregate log. Scenario now proves both TLS listener routing AND the priority. Local gauntlet all green: - debug + clang-debug + sanitize: 725 tests pass (OpenSSL on) - debug (OpenSSL off): 612 tests pass, no libssl linkage - coverage: 99.9% overall (1181/1182 lines), 100% functions - tidy: clean - cppcheck: clean - format: clean - bdd: 16 features / 33 scenarios / 172 steps green --- Bdd/features/tls_transport.feature | 3 +- CMakeLists.txt | 10 +++++ Example/CMakeLists.txt | 16 +++++--- Example/Threaded/main.c | 18 +++++++-- Interface/SolidSyslogStream.h | 15 ++++--- Interface/SolidSyslogStreamDefinition.h | 2 +- Platform/OpenSsl/CMakeLists.txt | 4 +- .../OpenSsl/Source/SolidSyslogTlsStream.c | 26 ++++++++----- .../Posix/Source/SolidSyslogPosixTcpStream.c | 16 ++++---- Source/SolidSyslogStream.c | 2 +- Tests/CMakeLists.txt | 7 +++- Tests/SolidSyslogPosixTcpStreamTest.cpp | 4 +- Tests/SolidSyslogTlsStreamTest.cpp | 32 +++++++++++++++ Tests/StreamFake.c | 7 ++-- Tests/StreamFake.h | 4 +- Tests/StreamFakeTest.cpp | 4 +- Tests/Support/CMakeLists.txt | 5 ++- Tests/Support/OpenSslFake.c | 39 +++++++++++++++++++ Tests/Support/OpenSslFake.h | 6 +++ Tests/Support/SocketFake.c | 5 ++- 20 files changed, 177 insertions(+), 48 deletions(-) diff --git a/Bdd/features/tls_transport.feature b/Bdd/features/tls_transport.feature index f6e39c67..7ebf03b9 100644 --- a/Bdd/features/tls_transport.feature +++ b/Bdd/features/tls_transport.feature @@ -6,4 +6,5 @@ Feature: TLS message delivery Scenario: Message delivered over TLS Given syslog-ng is running When the threaded example sends a syslog message with transport tls - Then syslog-ng receives a message with priority "134" + Then syslog-ng receives 1 message over tls + And syslog-ng receives a message with priority "134" diff --git a/CMakeLists.txt b/CMakeLists.txt index 05c65151..6d9fecf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,10 +82,20 @@ if(HAVE_STDATOMIC_H OR HAVE_WINDOWS_INTERLOCKED) set(HAVE_ATOMIC_COUNTER TRUE) endif() +# OpenSSL-based TLS is an optional platform feature. Default ON when OpenSSL is +# discoverable so CI and most workstations get TLS automatically; pass +# -DSOLIDSYSLOG_OPENSSL=OFF to build a UDP/TCP-only library on systems without +# OpenSSL. +find_package(OpenSSL QUIET) +option(SOLIDSYSLOG_OPENSSL "Include OpenSSL-based TLS support" ${OpenSSL_FOUND}) + add_subdirectory(Source) if(SOLIDSYSLOG_POSIX) add_subdirectory(Platform/Posix) +endif() + +if(SOLIDSYSLOG_OPENSSL) add_subdirectory(Platform/OpenSsl) endif() diff --git a/Example/CMakeLists.txt b/Example/CMakeLists.txt index b1c5fe37..a4256f3c 100644 --- a/Example/CMakeLists.txt +++ b/Example/CMakeLists.txt @@ -5,11 +5,14 @@ if(SOLIDSYSLOG_POSIX) Common/ExampleServiceThread.c Common/ExampleUdpConfig.c Common/ExampleTcpConfig.c - Common/ExampleTlsConfig.c Common/ExampleInteractive.c Common/ExampleSwitchConfig.c ) + if(SOLIDSYSLOG_OPENSSL) + list(APPEND COMMON_SOURCES Common/ExampleTlsConfig.c) + endif() + # Single-task example: NullBuffer, direct send, bare-metal model add_executable(SolidSyslogExample SingleTask/main.c SingleTask/SolidSyslogExample.c ${COMMON_SOURCES}) target_link_libraries(SolidSyslogExample PRIVATE ${PROJECT_NAME}) @@ -18,14 +21,17 @@ if(SOLIDSYSLOG_POSIX) ${CMAKE_CURRENT_SOURCE_DIR}/Common ) - # Threaded example: PosixMessageQueueBuffer, two pthreads (requires POSIX threads and mqueue), - # OpenSSL for the TLS transport. - find_package(OpenSSL REQUIRED) + # Threaded example: PosixMessageQueueBuffer, two pthreads (requires POSIX threads and mqueue). + # TLS transport (optional via SOLIDSYSLOG_OPENSSL) adds OpenSSL linkage. add_executable(SolidSyslogThreadedExample Threaded/main.c ${COMMON_SOURCES}) - target_link_libraries(SolidSyslogThreadedExample PRIVATE ${PROJECT_NAME} Threads::Threads OpenSSL::SSL OpenSSL::Crypto) + target_link_libraries(SolidSyslogThreadedExample PRIVATE ${PROJECT_NAME} Threads::Threads) target_include_directories(SolidSyslogThreadedExample PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Common ) + if(SOLIDSYSLOG_OPENSSL) + target_link_libraries(SolidSyslogThreadedExample PRIVATE OpenSSL::SSL OpenSSL::Crypto) + target_compile_definitions(SolidSyslogThreadedExample PRIVATE SOLIDSYSLOG_HAVE_OPENSSL=1) + endif() elseif(SOLIDSYSLOG_WINSOCK AND HAVE_WINDOWS_PLATFORM) # Windows example: NullBuffer + Winsock UDP + Windows clock/hostname/process-id. # Output binary is named SolidSyslogExample.exe so BDD scenarios remain platform-agnostic. diff --git a/Example/Threaded/main.c b/Example/Threaded/main.c index 7ea9eb13..e8c79fea 100644 --- a/Example/Threaded/main.c +++ b/Example/Threaded/main.c @@ -4,7 +4,6 @@ #include "ExampleServiceThread.h" #include "ExampleSwitchConfig.h" #include "ExampleTcpConfig.h" -#include "ExampleTlsConfig.h" #include "ExampleUdpConfig.h" #include "SolidSyslog.h" #include "SolidSyslogAtomicCounter.h" @@ -25,9 +24,15 @@ #include "SolidSyslogPosixTcpStream.h" #include "SolidSyslogSwitchingSender.h" #include "SolidSyslogStreamSender.h" -#include "SolidSyslogTlsStream.h" #include "SolidSyslogUdpSender.h" +/* TODO(S3.13, #171): replace these three #ifdef blocks with a per-backend + * ExampleTlsSender factory selected at CMake time. */ +#ifdef SOLIDSYSLOG_HAVE_OPENSSL +#include "ExampleTlsConfig.h" +#include "SolidSyslogTlsStream.h" +#endif + #include #include #include @@ -75,6 +80,7 @@ static struct SolidSyslogSender* CreateSender(const struct ExampleOptions* optio struct SolidSyslogSender* reliableSender = NULL; if (tlsModeActive) { +#ifdef SOLIDSYSLOG_HAVE_OPENSSL static struct SolidSyslogTlsStreamConfig tlsStreamConfig = {0}; tlsStreamConfig.transport = SolidSyslogPosixTcpStream_Create(); tlsStreamConfig.caBundlePath = ExampleTlsConfig_GetCaBundlePath(); @@ -87,8 +93,12 @@ static struct SolidSyslogSender* CreateSender(const struct ExampleOptions* optio tlsSenderConfig.endpoint = ExampleTlsConfig_GetEndpoint; tlsSenderConfig.endpointVersion = ExampleTlsConfig_GetEndpointVersion; reliableSender = SolidSyslogStreamSender_Create(&tlsSenderConfig); +#else + /* --transport tls requested but the build has no OpenSSL — fall through to TCP. */ + tlsModeActive = false; +#endif } - else + if (!tlsModeActive && reliableSender == NULL) { static struct SolidSyslogStreamSenderConfig tcpConfig = {0}; tcpConfig.resolver = resolver; @@ -166,10 +176,12 @@ static void DestroySender(void) { SolidSyslogSwitchingSender_Destroy(); SolidSyslogStreamSender_Destroy(); +#ifdef SOLIDSYSLOG_HAVE_OPENSSL if (tlsModeActive) { SolidSyslogTlsStream_Destroy(); } +#endif SolidSyslogPosixTcpStream_Destroy(); SolidSyslogUdpSender_Destroy(); SolidSyslogPosixDatagram_Destroy(); diff --git a/Interface/SolidSyslogStream.h b/Interface/SolidSyslogStream.h index 92e1f131..507e21e9 100644 --- a/Interface/SolidSyslogStream.h +++ b/Interface/SolidSyslogStream.h @@ -5,16 +5,21 @@ #include "SolidSyslogAddress.h" #include #include -#include +#include EXTERN_C_BEGIN + /* Signed byte count. Models POSIX ssize_t using a standard-C type for portability + to targets that lack 's ssize_t (notably MSVC). Semantics mirror + POSIX recv(2): >=0 = bytes transferred, 0 = EOF on stream reads, <0 = error. */ + typedef intptr_t SolidSyslogSsize; + struct SolidSyslogStream; - bool SolidSyslogStream_Open(struct SolidSyslogStream * stream, const struct SolidSyslogAddress* addr); - bool SolidSyslogStream_Send(struct SolidSyslogStream * stream, const void* buffer, size_t size); - ssize_t SolidSyslogStream_Read(struct SolidSyslogStream * stream, void* buffer, size_t size); - void SolidSyslogStream_Close(struct SolidSyslogStream * stream); + bool SolidSyslogStream_Open(struct SolidSyslogStream * stream, const struct SolidSyslogAddress* addr); + bool SolidSyslogStream_Send(struct SolidSyslogStream * stream, const void* buffer, size_t size); + SolidSyslogSsize SolidSyslogStream_Read(struct SolidSyslogStream * stream, void* buffer, size_t size); + void SolidSyslogStream_Close(struct SolidSyslogStream * stream); EXTERN_C_END diff --git a/Interface/SolidSyslogStreamDefinition.h b/Interface/SolidSyslogStreamDefinition.h index a3cefa74..5a7860b7 100644 --- a/Interface/SolidSyslogStreamDefinition.h +++ b/Interface/SolidSyslogStreamDefinition.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN { bool (*Open)(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); bool (*Send)(struct SolidSyslogStream* self, const void* buffer, size_t size); - ssize_t (*Read)(struct SolidSyslogStream* self, void* buffer, size_t size); + SolidSyslogSsize (*Read)(struct SolidSyslogStream* self, void* buffer, size_t size); void (*Close)(struct SolidSyslogStream* self); }; diff --git a/Platform/OpenSsl/CMakeLists.txt b/Platform/OpenSsl/CMakeLists.txt index 32a77f20..da9ab417 100644 --- a/Platform/OpenSsl/CMakeLists.txt +++ b/Platform/OpenSsl/CMakeLists.txt @@ -1,4 +1,6 @@ -find_package(OpenSSL REQUIRED) +# OpenSSL was discovered (or explicitly requested) at the top-level CMakeLists. +# Platform/OpenSsl is only entered when SOLIDSYSLOG_OPENSSL is ON, so we can +# rely on the OPENSSL_INCLUDE_DIR variable being set. target_sources(${PROJECT_NAME} PRIVATE Source/SolidSyslogTlsStream.c diff --git a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c index 1435ab7f..ac5e8ce9 100644 --- a/Platform/OpenSsl/Source/SolidSyslogTlsStream.c +++ b/Platform/OpenSsl/Source/SolidSyslogTlsStream.c @@ -11,15 +11,16 @@ struct SolidSyslogTlsStream SSL* ssl; }; -static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); -static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); -static void Close(struct SolidSyslogStream* self); -static SSL_CTX* CreateSslContext(const char* caBundlePath); -static BIO* CreateTransportBio(void); -static int TransportBioCreate(BIO* bio); -static int TransportBioRead(BIO* bio, char* buffer, int size); -static int TransportBioWrite(BIO* bio, const char* buffer, int size); -static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg); +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); +static void Close(struct SolidSyslogStream* self); +static SSL_CTX* CreateSslContext(const char* caBundlePath); +static BIO* CreateTransportBio(void); +static int TransportBioCreate(BIO* bio); +static int TransportBioRead(BIO* bio, char* buffer, int size); +static int TransportBioWrite(BIO* bio, const char* buffer, int size); +static long TransportBioCtrl(BIO* bio, int cmd, long larg, void* parg); static struct SolidSyslogTlsStream instance; @@ -28,6 +29,7 @@ struct SolidSyslogStream* SolidSyslogTlsStream_Create(const struct SolidSyslogTl instance.config = *config; instance.base.Open = Open; instance.base.Send = Send; + instance.base.Read = Read; instance.base.Close = Close; return &instance.base; } @@ -67,6 +69,12 @@ static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size return SSL_write(stream->ssl, buffer, (int) size) > 0; } +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) +{ + struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; + return (SolidSyslogSsize) SSL_read(stream->ssl, buffer, (int) size); +} + static void Close(struct SolidSyslogStream* self) { struct SolidSyslogTlsStream* stream = (struct SolidSyslogTlsStream*) self; diff --git a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c index adcdf711..df844d12 100644 --- a/Platform/Posix/Source/SolidSyslogPosixTcpStream.c +++ b/Platform/Posix/Source/SolidSyslogPosixTcpStream.c @@ -12,12 +12,12 @@ enum INVALID_FD = -1 }; -static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); -static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); -static ssize_t Read(struct SolidSyslogStream* self, void* buffer, size_t size); -static void Close(struct SolidSyslogStream* self); -static void EnableTcpNoDelay(int fd); -static inline bool IsFileDescriptorValid(int fd); +static bool Open(struct SolidSyslogStream* self, const struct SolidSyslogAddress* addr); +static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size); +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size); +static void Close(struct SolidSyslogStream* self); +static void EnableTcpNoDelay(int fd); +static inline bool IsFileDescriptorValid(int fd); struct SolidSyslogPosixTcpStream { @@ -77,10 +77,10 @@ static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size return send(stream->fd, buffer, size, MSG_NOSIGNAL) >= 0; } -static ssize_t Read(struct SolidSyslogStream* self, void* buffer, size_t size) +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) { struct SolidSyslogPosixTcpStream* stream = (struct SolidSyslogPosixTcpStream*) self; - return recv(stream->fd, buffer, size, 0); + return (SolidSyslogSsize) recv(stream->fd, buffer, size, 0); } static void Close(struct SolidSyslogStream* self) diff --git a/Source/SolidSyslogStream.c b/Source/SolidSyslogStream.c index ab26c6d6..c6623cf3 100644 --- a/Source/SolidSyslogStream.c +++ b/Source/SolidSyslogStream.c @@ -10,7 +10,7 @@ bool SolidSyslogStream_Send(struct SolidSyslogStream* stream, const void* buffer return stream->Send(stream, buffer, size); } -ssize_t SolidSyslogStream_Read(struct SolidSyslogStream* stream, void* buffer, size_t size) +SolidSyslogSsize SolidSyslogStream_Read(struct SolidSyslogStream* stream, void* buffer, size_t size) { return stream->Read(stream, buffer, size); } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index ed1a5eb4..9a958489 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -60,9 +60,14 @@ if(SOLIDSYSLOG_POSIX) SolidSyslogGetAddrInfoResolverTest.cpp SolidSyslogPosixDatagramTest.cpp SolidSyslogPosixTcpStreamTest.cpp + SocketFakeTest.cpp + ) +endif() + +if(SOLIDSYSLOG_OPENSSL) + list(APPEND TEST_SOURCES SolidSyslogTlsStreamTest.cpp OpenSslFakeTest.cpp - SocketFakeTest.cpp ) endif() diff --git a/Tests/SolidSyslogPosixTcpStreamTest.cpp b/Tests/SolidSyslogPosixTcpStreamTest.cpp index c65fe7a4..d2158855 100644 --- a/Tests/SolidSyslogPosixTcpStreamTest.cpp +++ b/Tests/SolidSyslogPosixTcpStreamTest.cpp @@ -222,8 +222,8 @@ TEST(SolidSyslogPosixTcpStream, ReadReturnsRecvReturnValue) { SocketFake_SetRecvReturn(7); SolidSyslogStream_Open(stream, addr); - char buf[16]; - ssize_t n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); + char buf[16]; + SolidSyslogSsize n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); LONGS_EQUAL(7, n); } diff --git a/Tests/SolidSyslogTlsStreamTest.cpp b/Tests/SolidSyslogTlsStreamTest.cpp index 56e3ab51..56fb3445 100644 --- a/Tests/SolidSyslogTlsStreamTest.cpp +++ b/Tests/SolidSyslogTlsStreamTest.cpp @@ -218,6 +218,38 @@ TEST(SolidSyslogTlsStream, SendPassesSslFromNewToWrite) POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastWriteSslArg()); } +TEST(SolidSyslogTlsStream, ReadReadsFromSsl) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(1, OpenSslFake_SslReadCallCount()); +} + +TEST(SolidSyslogTlsStream, ReadPassesSslFromNewToSslRead) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(OpenSslFake_LastSslReturned(), OpenSslFake_LastSslReadSslArg()); +} + +TEST(SolidSyslogTlsStream, ReadPassesBufferToSslRead) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + POINTERS_EQUAL(buf, OpenSslFake_LastSslReadBuf()); +} + +TEST(SolidSyslogTlsStream, ReadPassesSizeToSslRead) +{ + SolidSyslogStream_Open(stream, addr); + char buf[16]; + SolidSyslogStream_Read(stream, buf, sizeof(buf)); + LONGS_EQUAL(sizeof(buf), OpenSslFake_LastSslReadSize()); +} + TEST(SolidSyslogTlsStream, CloseShutsDownSsl) { SolidSyslogStream_Open(stream, addr); diff --git a/Tests/StreamFake.c b/Tests/StreamFake.c index 8545b780..bbedc6f8 100644 --- a/Tests/StreamFake.c +++ b/Tests/StreamFake.c @@ -2,7 +2,6 @@ #include "SolidSyslogStreamDefinition.h" #include -#include struct StreamFake { @@ -16,7 +15,7 @@ struct StreamFake int readCallCount; void* lastReadBuf; size_t lastReadSize; - ssize_t readReturn; + SolidSyslogSsize readReturn; int closeCallCount; }; @@ -37,7 +36,7 @@ static bool Send(struct SolidSyslogStream* self, const void* buffer, size_t size return true; } -static ssize_t Read(struct SolidSyslogStream* self, void* buffer, size_t size) +static SolidSyslogSsize Read(struct SolidSyslogStream* self, void* buffer, size_t size) { struct StreamFake* fake = (struct StreamFake*) self; fake->readCallCount++; @@ -107,7 +106,7 @@ size_t StreamFake_LastReadSize(struct SolidSyslogStream* stream) return ((struct StreamFake*) stream)->lastReadSize; } -void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, ssize_t value) +void StreamFake_SetReadReturn(struct SolidSyslogStream* stream, SolidSyslogSsize value) { ((struct StreamFake*) stream)->readReturn = value; } diff --git a/Tests/StreamFake.h b/Tests/StreamFake.h index 3a1af93d..0a53aa60 100644 --- a/Tests/StreamFake.h +++ b/Tests/StreamFake.h @@ -2,10 +2,10 @@ #define STREAMFAKE_H #include "ExternC.h" +#include "SolidSyslogStream.h" #include #include -#include EXTERN_C_BEGIN @@ -22,7 +22,7 @@ EXTERN_C_BEGIN int StreamFake_ReadCallCount(struct SolidSyslogStream * stream); void* StreamFake_LastReadBuf(struct SolidSyslogStream * stream); size_t StreamFake_LastReadSize(struct SolidSyslogStream * stream); - void StreamFake_SetReadReturn(struct SolidSyslogStream * stream, ssize_t value); + void StreamFake_SetReadReturn(struct SolidSyslogStream * stream, SolidSyslogSsize value); void StreamFake_SetOpenFails(struct SolidSyslogStream * stream, bool fails); int StreamFake_CloseCallCount(struct SolidSyslogStream * stream); diff --git a/Tests/StreamFakeTest.cpp b/Tests/StreamFakeTest.cpp index e0b4fc71..0ac32633 100644 --- a/Tests/StreamFakeTest.cpp +++ b/Tests/StreamFakeTest.cpp @@ -82,8 +82,8 @@ TEST(StreamFake, ReadCapturesSize) TEST(StreamFake, ReadReturnsConfiguredValue) { StreamFake_SetReadReturn(stream, 5); - char buf[16]; - ssize_t n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); + char buf[16]; + SolidSyslogSsize n = SolidSyslogStream_Read(stream, buf, sizeof(buf)); LONGS_EQUAL(5, n); } diff --git a/Tests/Support/CMakeLists.txt b/Tests/Support/CMakeLists.txt index ec3d8694..da6b4333 100644 --- a/Tests/Support/CMakeLists.txt +++ b/Tests/Support/CMakeLists.txt @@ -1,11 +1,14 @@ if(SOLIDSYSLOG_POSIX) set(POSIX_FAKES_SOURCES ClockFake.c - OpenSslFake.c SafeStringStandard.c SocketFake.c ) + if(SOLIDSYSLOG_OPENSSL) + list(APPEND POSIX_FAKES_SOURCES OpenSslFake.c) + endif() + add_library(PosixFakes STATIC ${POSIX_FAKES_SOURCES}) target_include_directories(PosixFakes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Tests/Support/OpenSslFake.c b/Tests/Support/OpenSslFake.c index 2c5c810c..b083a9bd 100644 --- a/Tests/Support/OpenSslFake.c +++ b/Tests/Support/OpenSslFake.c @@ -83,6 +83,12 @@ static const void* lastWriteBuf; static int lastWriteSize; static bool writeFails; +/* SSL_read */ +static int sslReadCallCount; +static SSL* lastSslReadSslArg; +static void* lastSslReadBuf; +static int lastSslReadSize; + /* SSL_shutdown */ static int shutdownCallCount; static SSL* lastShutdownSslArg; @@ -139,6 +145,10 @@ void OpenSslFake_Reset(void) lastWriteBuf = NULL; lastWriteSize = 0; writeFails = false; + sslReadCallCount = 0; + lastSslReadSslArg = NULL; + lastSslReadBuf = NULL; + lastSslReadSize = 0; shutdownCallCount = 0; lastShutdownSslArg = NULL; freeCallCount = 0; @@ -351,6 +361,26 @@ int OpenSslFake_LastWriteSize(void) return lastWriteSize; } +int OpenSslFake_SslReadCallCount(void) +{ + return sslReadCallCount; +} + +SSL* OpenSslFake_LastSslReadSslArg(void) +{ + return lastSslReadSslArg; +} + +void* OpenSslFake_LastSslReadBuf(void) +{ + return lastSslReadBuf; +} + +int OpenSslFake_LastSslReadSize(void) +{ + return lastSslReadSize; +} + int OpenSslFake_ShutdownCallCount(void) { return shutdownCallCount; @@ -553,6 +583,15 @@ void OpenSslFake_SetWriteFails(bool fails) writeFails = fails; } +int SSL_read(SSL* ssl, void* buf, int num) +{ + sslReadCallCount++; + lastSslReadSslArg = ssl; + lastSslReadBuf = buf; + lastSslReadSize = num; + return num; +} + int SSL_shutdown(SSL* ssl) { shutdownCallCount++; diff --git a/Tests/Support/OpenSslFake.h b/Tests/Support/OpenSslFake.h index 3b82bd9b..dc1e5b96 100644 --- a/Tests/Support/OpenSslFake.h +++ b/Tests/Support/OpenSslFake.h @@ -92,6 +92,12 @@ EXTERN_C_BEGIN const void* OpenSslFake_LastWriteBuf(void); int OpenSslFake_LastWriteSize(void); + /* SSL_read */ + int OpenSslFake_SslReadCallCount(void); + struct ssl_st* OpenSslFake_LastSslReadSslArg(void); + void* OpenSslFake_LastSslReadBuf(void); + int OpenSslFake_LastSslReadSize(void); + /* SSL_shutdown */ int OpenSslFake_ShutdownCallCount(void); struct ssl_st* OpenSslFake_LastShutdownSslArg(void); diff --git a/Tests/Support/SocketFake.c b/Tests/Support/SocketFake.c index 95539e67..0405770e 100644 --- a/Tests/Support/SocketFake.c +++ b/Tests/Support/SocketFake.c @@ -476,9 +476,10 @@ int close(int fd) return 0; } -// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; parameter names differ from glibc -// internal names +// clang-format off +// NOLINTNEXTLINE(readability-inconsistent-declaration-parameter-name,bugprone-easily-swappable-parameters) -- POSIX API; names differ from glibc internal names ssize_t recv(int sockfd, void* buf, size_t len, int flags) +// clang-format on { recvCallCount++; lastRecvFd = sockfd; From 3a8ef915d7084e59edcf97b8e67c1041a7cd600a Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 06:38:57 +0100 Subject: [PATCH 16/19] S3.7: propagate OPENSSL_INCLUDE_DIR to test code Windows CI failure root-cause: vcpkg provides OpenSSL on windows-latest runners, so find_package(OpenSSL QUIET) sets OpenSSL_FOUND=TRUE and SOLIDSYSLOG_OPENSSL defaults ON. TlsStream.c, OpenSslFake.c, and the two TLS-flavoured test files all then need the OpenSSL headers. On POSIX this works implicitly because /usr/include is on the compiler search path. On Windows+vcpkg, OpenSSL headers live under the vcpkg install tree and must be declared explicitly for each target that includes . Fix: in Tests/Support/CMakeLists.txt, add OPENSSL_INCLUDE_DIR as a PUBLIC include on the PosixFakes target when SOLIDSYSLOG_OPENSSL is ON. OpenSslFake.c (which lives in PosixFakes) gets it directly; any test that links PosixFakes inherits it transitively, so SolidSyslogTlsStream Test.cpp and OpenSslFakeTest.cpp also find the headers. Linux side: 725 tests still green, example still links real libssl. --- Tests/Support/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/Support/CMakeLists.txt b/Tests/Support/CMakeLists.txt index da6b4333..e0b0c41b 100644 --- a/Tests/Support/CMakeLists.txt +++ b/Tests/Support/CMakeLists.txt @@ -14,6 +14,14 @@ if(SOLIDSYSLOG_POSIX) target_include_directories(PosixFakes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(PosixFakes PUBLIC ${PROJECT_NAME}) + + if(SOLIDSYSLOG_OPENSSL) + # OpenSslFake.c and any test that includes needs the + # OpenSSL headers on the compiler include path. On POSIX these usually + # come from /usr/include implicitly; on Windows+vcpkg they live under + # the vcpkg install tree and must be declared explicitly. + target_include_directories(PosixFakes PUBLIC ${OPENSSL_INCLUDE_DIR}) + endif() endif() if(SOLIDSYSLOG_WINSOCK) From f51d18bb31be3b55073909796ca8667b7203a594 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 06:44:30 +0100 Subject: [PATCH 17/19] S3.7: split OpenSslFake into its own library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactoring the previous Windows fix: OpenSslFake was stacked inside PosixFakes, and the OpenSSL include-path was added under the POSIX block. That's the wrong layering — OpenSSL is orthogonal to platform, and the OpenSSL fake has no POSIX dependencies. Split into a dedicated OpenSslFakes library gated on SOLIDSYSLOG_OPENSSL: Tests/Support/CMakeLists.txt: if(SOLIDSYSLOG_POSIX) -> PosixFakes (Clock/Safe/Socket) if(SOLIDSYSLOG_WINSOCK) -> WinsockFakes if(SOLIDSYSLOG_OPENSSL) -> OpenSslFakes (OpenSslFake, carries OpenSSL headers via PUBLIC include) Tests/CMakeLists.txt: if(SOLIDSYSLOG_OPENSSL) target_link_libraries(... OpenSslFakes) Each fake library is now scoped to its concern. Consumers link only what they need. Verified: - OpenSSL on: 725 tests green - OpenSSL off: 612 tests green (no OpenSslFakes target created) --- Tests/CMakeLists.txt | 4 ++++ Tests/Support/CMakeLists.txt | 32 +++++++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 9a958489..f5049df9 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -100,6 +100,10 @@ if(SOLIDSYSLOG_POSIX) target_link_libraries(${PROJECT_NAME}Tests PRIVATE PosixFakes) endif() +if(SOLIDSYSLOG_OPENSSL) + target_link_libraries(${PROJECT_NAME}Tests PRIVATE OpenSslFakes) +endif() + if(HAVE_WINDOWS_PLATFORM OR SOLIDSYSLOG_WINSOCK) target_include_directories(${PROJECT_NAME}Tests PRIVATE ${CMAKE_SOURCE_DIR}/Platform/Windows/Source) endif() diff --git a/Tests/Support/CMakeLists.txt b/Tests/Support/CMakeLists.txt index e0b0c41b..052303ab 100644 --- a/Tests/Support/CMakeLists.txt +++ b/Tests/Support/CMakeLists.txt @@ -1,27 +1,13 @@ if(SOLIDSYSLOG_POSIX) - set(POSIX_FAKES_SOURCES + add_library(PosixFakes STATIC ClockFake.c SafeStringStandard.c SocketFake.c ) - if(SOLIDSYSLOG_OPENSSL) - list(APPEND POSIX_FAKES_SOURCES OpenSslFake.c) - endif() - - add_library(PosixFakes STATIC ${POSIX_FAKES_SOURCES}) - target_include_directories(PosixFakes PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(PosixFakes PUBLIC ${PROJECT_NAME}) - - if(SOLIDSYSLOG_OPENSSL) - # OpenSslFake.c and any test that includes needs the - # OpenSSL headers on the compiler include path. On POSIX these usually - # come from /usr/include implicitly; on Windows+vcpkg they live under - # the vcpkg install tree and must be declared explicitly. - target_include_directories(PosixFakes PUBLIC ${OPENSSL_INCLUDE_DIR}) - endif() endif() if(SOLIDSYSLOG_WINSOCK) @@ -31,3 +17,19 @@ if(SOLIDSYSLOG_WINSOCK) target_link_libraries(WinsockFakes PUBLIC ${PROJECT_NAME}) endif() + +# OpenSSL fake is orthogonal to platform — link-time substitutes libssl symbols +# so the library's TLS code can be test-driven without real OpenSSL. Any target +# linking OpenSslFakes inherits the OpenSSL include path, which is needed +# explicitly on systems where OpenSSL isn't on the default compiler search +# path (e.g. Windows + vcpkg). +if(SOLIDSYSLOG_OPENSSL) + add_library(OpenSslFakes STATIC OpenSslFake.c) + + target_include_directories(OpenSslFakes PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${OPENSSL_INCLUDE_DIR} + ) + + target_link_libraries(OpenSslFakes PUBLIC ${PROJECT_NAME}) +endif() From a8165980ecd62eefa38477ab500232dcedb37144 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 06:49:39 +0100 Subject: [PATCH 18/19] S3.7: compile SolidSyslogStream.c when either POSIX or OpenSSL is on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows CI failure: on windows-latest, SOLIDSYSLOG_POSIX=OFF but SOLIDSYSLOG_OPENSSL=ON (vcpkg provides OpenSSL). TlsStream.c is compiled into the library and references SolidSyslogStream_Open/Send/ Read/Close from the Stream dispatcher. The dispatcher was gated on SOLIDSYSLOG_POSIX only, so on Windows the library had unresolved externals. SolidSyslogStream.c is platform-agnostic (pure vtable dispatch). Needed by any target that implements or consumes the Stream interface — today that's PosixTcpStream (POSIX), TlsStream (OPENSSL), and the StreamFake test fake. Gate: POSIX OR OPENSSL. StreamSender.c kept under POSIX-only for this PR — it isn't triggered by the Windows+OpenSSL build (no Winsock StreamSender consumers yet, that's S13.9/S13.10). Linux: 725 tests still green. Candidate fix for Windows — needs confirmation on a Windows build. --- Source/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 72320c3c..5deb21af 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -30,9 +30,14 @@ if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_WINSOCK) ) endif() -if(SOLIDSYSLOG_POSIX) +if(SOLIDSYSLOG_POSIX OR SOLIDSYSLOG_OPENSSL) list(APPEND SOURCES SolidSyslogStream.c + ) +endif() + +if(SOLIDSYSLOG_POSIX) + list(APPEND SOURCES SolidSyslogStreamSender.c ) endif() From 0e3f3066e542d44e1e141b30050689bf91134939 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Tue, 21 Apr 2026 07:22:15 +0100 Subject: [PATCH 19/19] =?UTF-8?q?S3.7:=20CodeRabbit=20follow-ups=20?= =?UTF-8?q?=E2=80=94=20CMake=20fail-fast=20+=20uint16=5Ft=20ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CMake: FATAL_ERROR when SOLIDSYSLOG_OPENSSL=ON but OpenSSL not found (was: silent fall-through to confusing downstream errors). - Example{Tls,Tcp,Udp}Config_GetPort now returns uint16_t instead of int, matching SolidSyslogEndpoint::port domain; redundant uint16_t casts at call sites removed. Verified locally: debug (725 tests), debug -DSOLIDSYSLOG_OPENSSL=OFF (612 tests), tidy, cppcheck, format all green. Co-Authored-By: Claude Opus 4.7 (1M context) --- CMakeLists.txt | 3 +++ Example/Common/ExampleTcpConfig.c | 6 +++--- Example/Common/ExampleTcpConfig.h | 2 +- Example/Common/ExampleTlsConfig.c | 6 +++--- Example/Common/ExampleTlsConfig.h | 2 +- Example/Common/ExampleUdpConfig.c | 6 +++--- Example/Common/ExampleUdpConfig.h | 2 +- Tests/Example/ExampleServiceThreadTest.cpp | 2 +- 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d9fecf2..6edbc318 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,6 +88,9 @@ endif() # OpenSSL. find_package(OpenSSL QUIET) option(SOLIDSYSLOG_OPENSSL "Include OpenSSL-based TLS support" ${OpenSSL_FOUND}) +if(SOLIDSYSLOG_OPENSSL AND NOT OpenSSL_FOUND) + message(FATAL_ERROR "SOLIDSYSLOG_OPENSSL=ON but OpenSSL was not found; install OpenSSL or pass -DSOLIDSYSLOG_OPENSSL=OFF") +endif() add_subdirectory(Source) diff --git a/Example/Common/ExampleTcpConfig.c b/Example/Common/ExampleTcpConfig.c index 916e2b1b..a941bf16 100644 --- a/Example/Common/ExampleTcpConfig.c +++ b/Example/Common/ExampleTcpConfig.c @@ -14,15 +14,15 @@ const char* ExampleTcpConfig_GetHost(void) return "syslog-ng"; } -int ExampleTcpConfig_GetPort(void) +uint16_t ExampleTcpConfig_GetPort(void) { - return EXAMPLE_TCP_PORT; + return (uint16_t) EXAMPLE_TCP_PORT; } void ExampleTcpConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleTcpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleTcpConfig_GetPort(); + endpoint->port = ExampleTcpConfig_GetPort(); } /* Static config — host/port never change, so version stays 0 forever and the diff --git a/Example/Common/ExampleTcpConfig.h b/Example/Common/ExampleTcpConfig.h index 0be17712..8e049047 100644 --- a/Example/Common/ExampleTcpConfig.h +++ b/Example/Common/ExampleTcpConfig.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN const char* ExampleTcpConfig_GetHost(void); - int ExampleTcpConfig_GetPort(void); + uint16_t ExampleTcpConfig_GetPort(void); void ExampleTcpConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); uint32_t ExampleTcpConfig_GetEndpointVersion(void); diff --git a/Example/Common/ExampleTlsConfig.c b/Example/Common/ExampleTlsConfig.c index 8c79cc47..f8bcf680 100644 --- a/Example/Common/ExampleTlsConfig.c +++ b/Example/Common/ExampleTlsConfig.c @@ -17,9 +17,9 @@ const char* ExampleTlsConfig_GetHost(void) return "syslog-ng"; } -int ExampleTlsConfig_GetPort(void) +uint16_t ExampleTlsConfig_GetPort(void) { - return EXAMPLE_TLS_PORT; + return (uint16_t) EXAMPLE_TLS_PORT; } const char* ExampleTlsConfig_GetCaBundlePath(void) @@ -35,7 +35,7 @@ const char* ExampleTlsConfig_GetServerName(void) void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleTlsConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleTlsConfig_GetPort(); + endpoint->port = ExampleTlsConfig_GetPort(); } /* Static config — host/port never change, so version stays 0 forever and the diff --git a/Example/Common/ExampleTlsConfig.h b/Example/Common/ExampleTlsConfig.h index c5a698d1..0de90802 100644 --- a/Example/Common/ExampleTlsConfig.h +++ b/Example/Common/ExampleTlsConfig.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN const char* ExampleTlsConfig_GetHost(void); - int ExampleTlsConfig_GetPort(void); + uint16_t ExampleTlsConfig_GetPort(void); const char* ExampleTlsConfig_GetCaBundlePath(void); const char* ExampleTlsConfig_GetServerName(void); void ExampleTlsConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); diff --git a/Example/Common/ExampleUdpConfig.c b/Example/Common/ExampleUdpConfig.c index 29eba802..2ac6408d 100644 --- a/Example/Common/ExampleUdpConfig.c +++ b/Example/Common/ExampleUdpConfig.c @@ -14,15 +14,15 @@ const char* ExampleUdpConfig_GetHost(void) return "syslog-ng"; } -int ExampleUdpConfig_GetPort(void) +uint16_t ExampleUdpConfig_GetPort(void) { - return EXAMPLE_UDP_PORT; + return (uint16_t) EXAMPLE_UDP_PORT; } void ExampleUdpConfig_GetEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleUdpConfig_GetPort(); + endpoint->port = ExampleUdpConfig_GetPort(); } /* Static config — host/port never change, so version stays 0 forever and the diff --git a/Example/Common/ExampleUdpConfig.h b/Example/Common/ExampleUdpConfig.h index 6377002b..2ace1b3a 100644 --- a/Example/Common/ExampleUdpConfig.h +++ b/Example/Common/ExampleUdpConfig.h @@ -9,7 +9,7 @@ EXTERN_C_BEGIN const char* ExampleUdpConfig_GetHost(void); - int ExampleUdpConfig_GetPort(void); + uint16_t ExampleUdpConfig_GetPort(void); void ExampleUdpConfig_GetEndpoint(struct SolidSyslogEndpoint * endpoint); uint32_t ExampleUdpConfig_GetEndpointVersion(void); diff --git a/Tests/Example/ExampleServiceThreadTest.cpp b/Tests/Example/ExampleServiceThreadTest.cpp index 337b030a..a351ac37 100644 --- a/Tests/Example/ExampleServiceThreadTest.cpp +++ b/Tests/Example/ExampleServiceThreadTest.cpp @@ -16,7 +16,7 @@ static void ExampleEndpoint(struct SolidSyslogEndpoint* endpoint) { SolidSyslogFormatter_BoundedString(endpoint->host, ExampleUdpConfig_GetHost(), SOLIDSYSLOG_MAX_HOST_SIZE); - endpoint->port = (uint16_t) ExampleUdpConfig_GetPort(); + endpoint->port = ExampleUdpConfig_GetPort(); } static uint32_t ExampleEndpointVersion() // NOLINT(modernize-use-trailing-return-type)