module Flow = struct let rewrap_st = function (`S _, st) -> `S st | (`C _, st) -> `C st let unwrap_st = function `S st -> st | `C st -> st let can_handle_appdata st = not (Tls.Engine.handshake_in_progress (unwrap_st st)) let send_application_data state data = match Tls.Engine.send_application_data (unwrap_st state) data with | None -> None | Some (st', cs) -> Some (rewrap_st (state, st'), cs) let handle_tls ~tag state msg = let (st, descr) = match state with | `S st -> (st, "server") | `C st -> (st, "client") in match msg with | None -> state, None, None | Some msg -> match Tls.Engine.handle_tls st msg with | Ok (_, Some `Eof, _, _) -> failwith "received eof" | Ok (st', _eof, `Response (Some ans), `Data appdata) -> (rewrap_st (state, st'), Some ans, appdata) | Ok (st', _eof, `Response None, `Data appdata) -> (rewrap_st (state, st'), None, appdata) | Error (a, _) -> failwith @@ Printf.sprintf "[%s] %s error: %s" tag descr (Tls.Engine.string_of_failure a) end let get_ok = function | Ok cfg -> cfg | Error `Msg msg -> invalid_arg msg let loop_chatter ~certificate ~loops ~size = Printf.eprintf "Looping %d times, %d bytes.\n%!" loops size; let message = Mirage_crypto_rng.generate size and server = Tls.(Engine.server (get_ok (Config.server ~certificates:(`Single certificate) ()))) and (client, init) = let authenticator ?ip:_ ~host:_ _ = Ok None in Tls.(Engine.client @@ get_ok (Config.client ~authenticator ())) in Testlib.time @@ fun () -> let rec handshake srv cli cli_msg = let tag = "handshake" in let (srv, ans, _) = Flow.handle_tls ~tag srv cli_msg in let (cli, ans, _) = Flow.handle_tls ~tag cli ans in if Flow.can_handle_appdata cli && Flow.can_handle_appdata srv then (srv, cli) else handshake srv cli ans and chat srv cli data = function | 0 -> data | n -> let tag = "chat" in let simplex sender recv data = match Flow.send_application_data sender [data] with | None -> failwith @@ "can't send" | Some (sender', msg) -> match Flow.handle_tls ~tag recv (Some msg) with | (recv', _, Some data') -> (sender', recv', data') | (_, _, None) -> failwith "expected data" in let (cli, srv, data1) = simplex cli srv data in let (srv, cli, data2) = simplex srv cli data1 in chat srv cli data2 (pred n) in let (srv, cli) = handshake (`S server) (`C client) (Some init) in let message' = chat srv cli message loops in if String.equal message message' then Ok () else Error "the message got corrupted :(" let string_of_file file = try let fh = open_in file in let content = really_input_string fh (in_channel_length fh) in close_in_noerr fh; content with _ -> invalid_arg "Error reading file" let load_priv () = let cert, key = if Sys.file_exists "./certificates/server.pem" then "./certificates/server.pem", "./certificates/server.key" else "server.pem", "server.key" in let cs1 = string_of_file cert and cs2 = string_of_file key in match X509.Certificate.decode_pem_multiple cs1, X509.Private_key.decode_pem cs2 with | Ok certs, Ok key -> certs, key | Error (`Msg m), _ -> failwith ("can't parse certificates " ^ m) | _, Error (`Msg m) -> failwith ("can't parse private key " ^ m) let jump () loops size = let certificate = load_priv () in loop_chatter ~certificate ~loops ~size let setup_log style_renderer level = Fmt_tty.setup_std_outputs ?style_renderer (); Logs.set_level level; Logs.set_reporter (Logs_fmt.reporter ~dst:Format.std_formatter ()) open Cmdliner let setup_log = Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) let loops = let doc = "Number of loops to take" in Arg.(value & opt int 10 & info ~docv:"LOOPS" ~doc ["loops"]) let size = let doc = "Bytes to exchange" in Arg.(value & opt int 1024 & info ~docv:"SIZE" ~doc ["size"]) let cmd = let term = Term.(const jump $ setup_log $ loops $ size) and info = Cmd.info "feedback" ~version:"2.0.3" in Cmd.v info term let () = exit (Cmd.eval_result cmd)