Skip to main content

roughenough_integration_test/
lib.rs

1#![forbid(unsafe_code)]
2
3mod load_gen;
4
5#[cfg(test)]
6mod integration_tests {
7    //! These tests verify that the server's request processing and response generation
8    //! paths are compatible with the client. They test the complete end-to-end flow
9    //! from request processing through response generation and client-side validation.
10    //!
11    //! These tests are not exhaustive and do not cover all possible edge cases.
12    //! They are intended to catch regressions and verify that the server's behavior
13    //! matches the client's expectations.
14
15    use std::net::SocketAddr;
16
17    use roughenough_client::validation::ResponseValidator;
18    use roughenough_protocol::cursor::ParseCursor;
19    use roughenough_protocol::request::Request;
20    use roughenough_protocol::response::Response;
21    use roughenough_protocol::tags::{Nonce, PublicKey};
22    use roughenough_protocol::wire::{FromWire, ToWire};
23    use roughenough_server::test_utils::TestContext;
24
25    /// Validates a response against its originating request using the client `ResponseValidator`
26    /// and `LongTermKey`'s public key.
27    fn validate_response(
28        request_bytes: &[u8],
29        response_bytes: &[u8],
30        pub_key: PublicKey,
31    ) -> Result<(), Box<dyn std::error::Error>> {
32        assert!(response_bytes.starts_with(b"ROUGHTIM"));
33        // Skip framing: 8-byte "ROUGHTIM" + 4-byte length
34        let mut buf = response_bytes[12..].to_vec();
35        let mut cursor = ParseCursor::new(&mut buf);
36        let response = Response::from_wire(&mut cursor)?;
37
38        let validator = ResponseValidator::new_with_key(pub_key);
39        validator.validate(request_bytes, &response)?;
40        Ok(())
41    }
42
43    /// Creates a test request with a Nonce based on a repeated single byte.
44    fn create_test_request(nonce_byte: u8) -> Request {
45        let nonce = Nonce::from([nonce_byte; 32]);
46        Request::new(&nonce)
47    }
48
49    /// Tests that a single request generates a valid response.
50    /// For single-element trees, the Merkle path is empty since there are no siblings, and the
51    /// client will verify that the request nonce hashes to the signed root.
52    #[test]
53    fn single_request_validation() {
54        let mut test_context = TestContext::new(64);
55        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
56
57        let request = create_test_request(42);
58        let request_bytes = request.as_bytes().unwrap();
59        test_context
60            .response_handler
61            .add_request(&request_bytes, request, addr);
62
63        let mut responses = Vec::new();
64        test_context
65            .response_handler
66            .process_responses(|addr, bytes| {
67                responses.push((addr, bytes.to_vec()));
68            });
69
70        assert_eq!(responses.len(), 1);
71        let (response_addr, response_bytes) = &responses[0];
72        assert_eq!(*response_addr, addr);
73
74        let pub_key = test_context.key_source.public_key();
75
76        validate_response(&request_bytes, response_bytes, pub_key).unwrap();
77    }
78
79    /// Stress tests the responder with a batch of 64 requests. This is an expected (but maximum)
80    /// batch size that the server supports.
81    #[test]
82    fn large_batch_validation() {
83        let num_requests = 64;
84
85        let mut test_context = TestContext::new(num_requests as u8);
86
87        let mut request_data = Vec::new();
88
89        for i in 0..num_requests {
90            let addr: SocketAddr = format!("127.0.0.1:{}", 8000 + i).parse().unwrap();
91            let request = create_test_request((i * 37) as u8);
92            let request_bytes = request.as_bytes().unwrap();
93
94            request_data.push((request_bytes.clone(), addr));
95            test_context
96                .response_handler
97                .add_request(&request_bytes, request, addr);
98        }
99
100        let mut responses = Vec::new();
101        test_context
102            .response_handler
103            .process_responses(|addr, bytes| {
104                responses.push((addr, bytes.to_vec()));
105            });
106
107        assert_eq!(responses.len(), num_requests);
108
109        for (idx, (response_addr, response_bytes)) in responses.iter().enumerate() {
110            let expected_addr = format!("127.0.0.1:{}", 8000 + idx)
111                .parse::<SocketAddr>()
112                .unwrap();
113            assert_eq!(*response_addr, expected_addr);
114
115            let (request_bytes, _) = &request_data[idx];
116            let public_key = test_context.key_source.public_key();
117
118            match validate_response(request_bytes, response_bytes, public_key) {
119                Ok(_) => {} // Success
120                Err(e) => {
121                    println!("Response {idx} validation failed: {e}");
122
123                    let mut buf = response_bytes[12..].to_vec();
124                    let mut cursor = ParseCursor::new(&mut buf);
125                    let response = Response::from_wire(&mut cursor).unwrap();
126
127                    println!("Debug info for failed validation:");
128                    println!("  Request bytes length: {}", request_bytes.len());
129                    println!("  Response index: {}", response.indx());
130                    println!("  Response path length: {}", response.path().as_ref().len());
131                    println!(
132                        "  Server root: {}",
133                        data_encoding::HEXLOWER.encode(response.srep().root().as_ref())
134                    );
135
136                    // Compare with what the client computes to identify mismatch source
137                    let tree = roughenough_merkle::MerkleTree::new();
138                    let computed_root = tree.root_from_paths(
139                        response.indx() as usize,
140                        request_bytes,
141                        response.path(),
142                    );
143                    println!(
144                        "  Client computed: {}",
145                        data_encoding::HEXLOWER.encode(&computed_root)
146                    );
147
148                    panic!("Validation failed for response {idx}: {e}");
149                }
150            }
151        }
152    }
153}