bolt8

Encrypted and authenticated transport, per BOLT #8.
git clone git://git.ppad.tech/bolt8.git
Log | Files | Refs | README | LICENSE

commit 42afaddad44020e841f7ec0468b6626b8ee0d913
parent 2b87813654c338fef2cb90795ec24d031615df20
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 09:43:56 +0400

test: add partial framing tests

Test decrypt_frame_partial behavior:
- Short buffer (< 18 bytes) returns NeedMore
- Partial body (header complete, body incomplete) returns NeedMore
- Full frame returns FrameOk with plaintext and remainder

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
Mtest/Main.hs | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+), 0 deletions(-)

diff --git a/test/Main.hs b/test/Main.hs @@ -14,6 +14,7 @@ main = defaultMain $ testGroup "ppad-bolt8" [ handshake_tests , message_tests , framing_tests + , partial_framing_tests , negative_tests ] @@ -389,6 +390,109 @@ test_decrypt_frame_multi = do pt2 @?= "second" rest2 @?= BS.empty +-- partial framing tests ----------------------------------------------------- + +partial_framing_tests :: TestTree +partial_framing_tests = testGroup "Partial Framing" [ + testCase "short buffer returns NeedMore" test_partial_short_buffer + , testCase "partial body returns NeedMore" test_partial_body + , testCase "full frame returns FrameOk" test_partial_full_frame + ] + +test_partial_short_buffer :: Assertion +test_partial_short_buffer = do + let Just (i_s_sec, i_s_pub) = BOLT8.keypair initiator_s_priv + Just (r_s_sec, r_s_pub) = BOLT8.keypair responder_s_priv + Just rs = BOLT8.parse_pub responder_s_pub + case BOLT8.act1 i_s_sec i_s_pub rs initiator_e_priv of + Left err -> assertFailure $ "act1 failed: " ++ show err + Right (msg1, i_hs) -> + case BOLT8.act2 r_s_sec r_s_pub responder_e_priv msg1 of + Left err -> assertFailure $ "act2 failed: " ++ show err + Right (msg2, r_hs) -> + case BOLT8.act3 i_hs msg2 of + Left err -> assertFailure $ "act3 failed: " ++ show err + Right (msg3, _) -> + case BOLT8.finalize r_hs msg3 of + Left err -> assertFailure $ "finalize failed: " ++ show err + Right r_result -> do + let r_sess = BOLT8.session r_result + short_buf = BS.replicate 10 0x00 + case BOLT8.decrypt_frame_partial r_sess short_buf of + BOLT8.NeedMore n -> n @?= 8 + BOLT8.FrameOk {} -> + assertFailure "expected NeedMore, got FrameOk" + BOLT8.FrameError err -> + assertFailure $ "expected NeedMore, got: " ++ show err + +test_partial_body :: Assertion +test_partial_body = do + let Just (i_s_sec, i_s_pub) = BOLT8.keypair initiator_s_priv + Just (r_s_sec, r_s_pub) = BOLT8.keypair responder_s_priv + Just rs = BOLT8.parse_pub responder_s_pub + case BOLT8.act1 i_s_sec i_s_pub rs initiator_e_priv of + Left err -> assertFailure $ "act1 failed: " ++ show err + Right (msg1, i_hs) -> + case BOLT8.act2 r_s_sec r_s_pub responder_e_priv msg1 of + Left err -> assertFailure $ "act2 failed: " ++ show err + Right (msg2, r_hs) -> + case BOLT8.act3 i_hs msg2 of + Left err -> assertFailure $ "act3 failed: " ++ show err + Right (msg3, i_result) -> + case BOLT8.finalize r_hs msg3 of + Left err -> assertFailure $ "finalize failed: " ++ show err + Right r_result -> do + let i_sess = BOLT8.session i_result + r_sess = BOLT8.session r_result + case BOLT8.encrypt i_sess hello of + Left err -> assertFailure $ "encrypt failed: " ++ show err + Right (ct, _) -> do + -- take only length header (18 bytes) + 5 bytes of body + let partial = BS.take 23 ct + case BOLT8.decrypt_frame_partial r_sess partial of + BOLT8.NeedMore n -> do + -- "hello" = 5 bytes, so body = 5 + 16 = 21 + -- we have 5 bytes of body, need 16 more + n @?= 16 + BOLT8.FrameOk {} -> + assertFailure "expected NeedMore, got FrameOk" + BOLT8.FrameError err -> + assertFailure $ "expected NeedMore, got: " ++ show err + +test_partial_full_frame :: Assertion +test_partial_full_frame = do + let Just (i_s_sec, i_s_pub) = BOLT8.keypair initiator_s_priv + Just (r_s_sec, r_s_pub) = BOLT8.keypair responder_s_priv + Just rs = BOLT8.parse_pub responder_s_pub + case BOLT8.act1 i_s_sec i_s_pub rs initiator_e_priv of + Left err -> assertFailure $ "act1 failed: " ++ show err + Right (msg1, i_hs) -> + case BOLT8.act2 r_s_sec r_s_pub responder_e_priv msg1 of + Left err -> assertFailure $ "act2 failed: " ++ show err + Right (msg2, r_hs) -> + case BOLT8.act3 i_hs msg2 of + Left err -> assertFailure $ "act3 failed: " ++ show err + Right (msg3, i_result) -> + case BOLT8.finalize r_hs msg3 of + Left err -> assertFailure $ "finalize failed: " ++ show err + Right r_result -> do + let i_sess = BOLT8.session i_result + r_sess = BOLT8.session r_result + case BOLT8.encrypt i_sess hello of + Left err -> assertFailure $ "encrypt failed: " ++ show err + Right (ct, _) -> do + let trailing = "extra" + buf = ct <> trailing + case BOLT8.decrypt_frame_partial r_sess buf of + BOLT8.FrameOk pt remainder _ -> do + pt @?= hello + remainder @?= trailing + BOLT8.NeedMore n -> + assertFailure $ "expected FrameOk, got NeedMore " + ++ show n + BOLT8.FrameError err -> + assertFailure $ "expected FrameOk, got: " ++ show err + -- negative tests ------------------------------------------------------------ negative_tests :: TestTree