···11+ISC License
22+33+Copyright (c) 2025 Thomas Gazagnaire
44+55+Permission to use, copy, modify, and/or distribute this software for any
66+purpose with or without fee is hereby granted, provided that the above
77+copyright notice and this permission notice appear in all copies.
88+99+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1010+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1111+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1212+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1313+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1414+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1515+PERFORMANCE OF THIS SOFTWARE.
+26
dune-project
···11+(lang dune 3.0)
22+33+(name ltp)
44+55+(generate_opam_files true)
66+77+(license ISC)
88+(authors "Thomas Gazagnaire <thomas@gazagnaire.org>")
99+(maintainers "Thomas Gazagnaire <thomas@gazagnaire.org>")
1010+1111+(source
1212+ (uri https://tangled.org/gazagnaire.org/ocaml-ltp))
1313+1414+(package
1515+ (name ltp)
1616+ (synopsis "Licklider Transmission Protocol (RFC 5326)")
1717+ (description
1818+ "LTP is a reliable data delivery protocol designed for high-delay and
1919+ disruption-prone communication links, such as deep-space communications.
2020+ It provides selective acknowledgment and retransmission with support for
2121+ both reliable (red) and unreliable (green) data segments.")
2222+ (depends
2323+ (ocaml (>= 4.14))
2424+ (fmt (>= 0.9))
2525+ (alcotest :with-test)
2626+ (crowbar :with-test)))
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+open Crowbar
77+88+let truncate s = if String.length s > 1024 then String.sub s 0 1024 else s
99+1010+(** SDNV decode - must not crash. *)
1111+let test_sdnv_decode buf =
1212+ let buf = truncate buf in
1313+ let _ = Ltp.decode_sdnv buf 0 in
1414+ ()
1515+1616+(** SDNV roundtrip. *)
1717+let test_sdnv_roundtrip n =
1818+ if n < 0L then ()
1919+ else
2020+ let encoded = Ltp.encode_sdnv n in
2121+ match Ltp.decode_sdnv encoded 0 with
2222+ | Ok (decoded, _) -> if decoded <> n then fail "SDNV roundtrip mismatch"
2323+ | Error _ -> fail "SDNV decode failed after encode"
2424+2525+(** Segment decode - must not crash. *)
2626+let test_segment_decode buf =
2727+ let buf = truncate buf in
2828+ let _ = Ltp.decode_segment buf in
2929+ ()
3030+3131+(** Green data segment roundtrip. *)
3232+let test_green_roundtrip orig_n sess_n svc_id offset data =
3333+ let data = truncate data in
3434+ if String.length data = 0 then ()
3535+ else
3636+ let session_id = Ltp.{ originator = Int64.of_int orig_n; number = Int64.of_int sess_n } in
3737+ let seg =
3838+ Ltp.make_data_segment ~session_id
3939+ ~client_service_id:(Int64.of_int svc_id)
4040+ ~block_offset:(Int64.of_int offset) data
4141+ in
4242+ let encoded = Ltp.encode_segment seg in
4343+ match Ltp.decode_segment encoded with
4444+ | Ok decoded -> (
4545+ match decoded.content with
4646+ | Ltp.Data ds -> if ds.data <> data then fail "green data mismatch"
4747+ | _ -> fail "wrong content type")
4848+ | Error _ -> fail "green segment decode failed"
4949+5050+(** Red checkpoint roundtrip. *)
5151+let test_red_roundtrip orig_n sess_n cp_serial data =
5252+ let data = truncate data in
5353+ if String.length data = 0 then ()
5454+ else
5555+ let session_id = Ltp.{ originator = Int64.of_int orig_n; number = Int64.of_int sess_n } in
5656+ let seg =
5757+ Ltp.make_data_segment ~session_id ~client_service_id:1L ~block_offset:0L
5858+ ~checkpoint_serial:(Int64.of_int cp_serial) ~report_serial:0L data
5959+ in
6060+ let encoded = Ltp.encode_segment seg in
6161+ match Ltp.decode_segment encoded with
6262+ | Ok decoded -> (
6363+ match decoded.content with
6464+ | Ltp.Data ds ->
6565+ if ds.data <> data then fail "red data mismatch";
6666+ if ds.checkpoint_serial <> Some (Int64.of_int cp_serial) then
6767+ fail "checkpoint serial mismatch"
6868+ | _ -> fail "wrong content type")
6969+ | Error _ -> fail "red segment decode failed"
7070+7171+(** Report segment roundtrip. *)
7272+let test_report_roundtrip orig_n sess_n rpt_serial cp_serial upper =
7373+ let session_id = Ltp.{ originator = Int64.of_int orig_n; number = Int64.of_int sess_n } in
7474+ let claims = [ Ltp.{ offset = 0L; length = Int64.of_int upper } ] in
7575+ let seg =
7676+ Ltp.make_report_segment ~session_id
7777+ ~report_serial:(Int64.of_int rpt_serial)
7878+ ~checkpoint_serial:(Int64.of_int cp_serial)
7979+ ~upper_bound:(Int64.of_int upper) claims
8080+ in
8181+ let encoded = Ltp.encode_segment seg in
8282+ match Ltp.decode_segment encoded with
8383+ | Ok decoded -> (
8484+ match decoded.content with
8585+ | Ltp.Report rs ->
8686+ if rs.report_serial <> Int64.of_int rpt_serial then
8787+ fail "report serial mismatch"
8888+ | _ -> fail "wrong content type")
8989+ | Error _ -> fail "report segment decode failed"
9090+9191+(** Cancel segment roundtrip. *)
9292+let test_cancel_roundtrip orig_n sess_n reason_code from_sender =
9393+ let session_id = Ltp.{ originator = Int64.of_int orig_n; number = Int64.of_int sess_n } in
9494+ let reason = Ltp.cancel_reason_of_int (reason_code mod 6) in
9595+ let seg = Ltp.make_cancel ~session_id ~from_sender reason in
9696+ let encoded = Ltp.encode_segment seg in
9797+ match Ltp.decode_segment encoded with
9898+ | Ok decoded -> (
9999+ match decoded.content with
100100+ | Ltp.Cancel cs ->
101101+ if Ltp.cancel_reason_to_int cs.reason <> reason_code mod 6 then
102102+ fail "cancel reason mismatch"
103103+ | _ -> fail "wrong content type")
104104+ | Error _ -> fail "cancel segment decode failed"
105105+106106+let () =
107107+ add_test ~name:"ltp: SDNV decode no crash" [ bytes ] test_sdnv_decode;
108108+ add_test ~name:"ltp: SDNV roundtrip" [ int64 ] test_sdnv_roundtrip;
109109+ add_test ~name:"ltp: segment decode no crash" [ bytes ] test_segment_decode;
110110+ add_test ~name:"ltp: green segment roundtrip"
111111+ [ range 1000; range 1000; range 100; range 1000; bytes ]
112112+ test_green_roundtrip;
113113+ add_test ~name:"ltp: red segment roundtrip"
114114+ [ range 1000; range 1000; range 1000; bytes ]
115115+ test_red_roundtrip;
116116+ add_test ~name:"ltp: report segment roundtrip"
117117+ [ range 1000; range 1000; range 1000; range 1000; range 10000 ]
118118+ test_report_roundtrip;
119119+ add_test ~name:"ltp: cancel segment roundtrip"
120120+ [ range 1000; range 1000; range 256; bool ]
121121+ test_cancel_roundtrip