diff options
Diffstat (limited to 'src/test_btfrv.cpp')
| -rw-r--r-- | src/test_btfrv.cpp | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/src/test_btfrv.cpp b/src/test_btfrv.cpp new file mode 100644 index 0000000..291e660 --- /dev/null +++ b/src/test_btfrv.cpp | |||
| @@ -0,0 +1,116 @@ | |||
| 1 | // Standalone loopback test of the rendezvous transport + echan on top. | ||
| 2 | // A relay, a service and a client all run in one process on 127.0.0.1 (a unit | ||
| 3 | // test of the protocol logic -- NOT a network/LAN test). Proves: service | ||
| 4 | // registers, client pairs by pubkey through the relay, and they exchange data | ||
| 5 | // end-to-end encrypted so the relay only ever forwards ciphertext. | ||
| 6 | // | ||
| 7 | // Build (UCRT64): | ||
| 8 | // g++ -std=gnu++14 test_btfrv.cpp btfrv.cpp btfchan.cpp -lsodium -lws2_32 -o test_btfrv.exe | ||
| 9 | |||
| 10 | #include "btfrv.h" | ||
| 11 | #include "btfchan.h" | ||
| 12 | #include <cstdio> | ||
| 13 | #include <cstring> | ||
| 14 | #include <string> | ||
| 15 | #include <vector> | ||
| 16 | #include <thread> | ||
| 17 | #include <chrono> | ||
| 18 | |||
| 19 | using namespace btf; | ||
| 20 | static int g_fail = 0; | ||
| 21 | #define CHECK(cond, name) do { \ | ||
| 22 | if (cond) { printf(" ok %s\n", name); } \ | ||
| 23 | else { printf(" FAIL %s\n", name); g_fail++; } } while(0) | ||
| 24 | |||
| 25 | static const unsigned short PORT = 18443; | ||
| 26 | |||
| 27 | // echan framing over a rendezvous socket | ||
| 28 | static bool WriteFrame(RvSocket s, const unsigned char shared[32], const std::string& pt) | ||
| 29 | { | ||
| 30 | std::vector<unsigned char> f = ChanEncryptFrame(shared, (const unsigned char*)pt.data(), pt.size()); | ||
| 31 | return RvWriteN(s, f.data(), (int)f.size()); | ||
| 32 | } | ||
| 33 | static bool ReadFrame(RvSocket s, const unsigned char shared[32], std::string& out) | ||
| 34 | { | ||
| 35 | unsigned char len[4]; | ||
| 36 | if (!RvReadN(s, len, 4)) return false; | ||
| 37 | size_t n = ((size_t)len[0]<<24)|((size_t)len[1]<<16)|((size_t)len[2]<<8)|len[3]; | ||
| 38 | if (n == 0 || n > (1u<<20)) return false; | ||
| 39 | std::vector<unsigned char> body(n); | ||
| 40 | if (!RvReadN(s, body.data(), (int)n)) return false; | ||
| 41 | std::vector<unsigned char> pt; | ||
| 42 | if (!ChanDecryptBody(shared, body.data(), (int)n, pt)) return false; | ||
| 43 | out.assign((char*)pt.data(), pt.size()); | ||
| 44 | return true; | ||
| 45 | } | ||
| 46 | |||
| 47 | static std::string g_svcGot; | ||
| 48 | static bool g_svcOk = false; | ||
| 49 | |||
| 50 | static void ServiceThread(std::vector<unsigned char> pairing_id, std::vector<unsigned char> svc_enc_sk) | ||
| 51 | { | ||
| 52 | RvSocket s = RvServiceRegister("127.0.0.1", PORT, pairing_id.data()); | ||
| 53 | if (s == RV_INVALID) return; | ||
| 54 | // read the client's ephemeral x25519 public key | ||
| 55 | unsigned char eph[32]; | ||
| 56 | if (!RvReadN(s, eph, 32)) { RvClose(s); return; } | ||
| 57 | unsigned char shared[32]; | ||
| 58 | if (!ChanEndpointHandshake(eph, svc_enc_sk.data(), shared)) { RvClose(s); return; } | ||
| 59 | std::string msg; | ||
| 60 | if (!ReadFrame(s, shared, msg)) { RvClose(s); return; } | ||
| 61 | g_svcGot = msg; | ||
| 62 | WriteFrame(s, shared, std::string("block accepted: ") + msg); | ||
| 63 | g_svcOk = true; | ||
| 64 | RvClose(s); | ||
| 65 | } | ||
| 66 | |||
| 67 | int main() | ||
| 68 | { | ||
| 69 | if (!ChanInit() || !RvInit()) { printf("init failed\n"); return 2; } | ||
| 70 | |||
| 71 | // service identity: a 32-byte .btf pairing id + an x25519 enc keypair | ||
| 72 | unsigned char svc_enc_pk[32], svc_enc_sk[32]; | ||
| 73 | ChanKeypair(svc_enc_pk, svc_enc_sk); | ||
| 74 | std::vector<unsigned char> pairing_id(32); | ||
| 75 | for (int i = 0; i < 32; i++) pairing_id[i] = (unsigned char)(i * 7 + 1); | ||
| 76 | |||
| 77 | printf("rendezvous_relay_pairs_and_forwards_encrypted\n"); | ||
| 78 | |||
| 79 | std::thread relay([]{ RvRelayRun(PORT); }); | ||
| 80 | std::this_thread::sleep_for(std::chrono::milliseconds(300)); // let it bind | ||
| 81 | |||
| 82 | std::thread svc(ServiceThread, pairing_id, std::vector<unsigned char>(svc_enc_sk, svc_enc_sk+32)); | ||
| 83 | std::this_thread::sleep_for(std::chrono::milliseconds(300)); // let service register | ||
| 84 | |||
| 85 | // client dials the pairing id through the relay | ||
| 86 | RvSocket c = RvClientConnect("127.0.0.1", PORT, pairing_id.data()); | ||
| 87 | CHECK(c != RV_INVALID, "client paired to service via relay"); | ||
| 88 | if (c != RV_INVALID) | ||
| 89 | { | ||
| 90 | unsigned char eph_pub[32], shared_client[32]; | ||
| 91 | CHECK(ChanClientHandshake(svc_enc_pk, eph_pub, shared_client), "client handshake"); | ||
| 92 | CHECK(RvWriteN(c, eph_pub, 32), "client sends ephemeral pubkey"); | ||
| 93 | CHECK(WriteFrame(c, shared_client, "hello one-cpu-one-vote"), "client sends encrypted frame"); | ||
| 94 | std::string reply; | ||
| 95 | CHECK(ReadFrame(c, shared_client, reply), "client reads encrypted reply"); | ||
| 96 | CHECK(reply == "block accepted: hello one-cpu-one-vote", "end-to-end message round-trips through relay"); | ||
| 97 | RvClose(c); | ||
| 98 | } | ||
| 99 | |||
| 100 | svc.join(); | ||
| 101 | CHECK(g_svcOk && g_svcGot == "hello one-cpu-one-vote", "service received the decrypted plaintext"); | ||
| 102 | |||
| 103 | printf("unknown_pubkey_is_rejected\n"); | ||
| 104 | { | ||
| 105 | unsigned char other[32]; | ||
| 106 | for (int i = 0; i < 32; i++) other[i] = 0xEE; | ||
| 107 | RvSocket bad = RvClientConnect("127.0.0.1", PORT, other); | ||
| 108 | CHECK(bad == RV_INVALID, "client for unregistered .btf is rejected"); | ||
| 109 | } | ||
| 110 | |||
| 111 | RvRelayStop(); | ||
| 112 | relay.join(); | ||
| 113 | |||
| 114 | printf("\n%s (%d failures)\n", g_fail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", g_fail); | ||
| 115 | return g_fail == 0 ? 0 : 1; | ||
| 116 | } | ||