mte/unikernel/duniverse/ocaml-tls/async/io.ml
2025-11-11 02:07:51 +01:00

200 lines
5.6 KiB
OCaml

open! Core
open! Async
include Io_intf
module Tls_error = struct
module Alert = struct
type t = Tls.Packet.alert_type
let sexp_of_t a =
Sexplib.Sexp.Atom (Tls.Packet.alert_type_to_string a)
end
module Fail = struct
type t = Tls.Engine.failure
let sexp_of_t a =
Sexplib.Sexp.Atom (Tls.Engine.string_of_failure a)
end
type t =
| Tls_alert of Alert.t
(** [Tls_alert] exception received from the other endpoint *)
| Tls_failure of Fail.t
(** [Tls_failure] exception while processing incoming data *)
| Connection_closed
| Connection_not_ready
| Unexpected_eof
| Unable_to_renegotiate
| Unable_to_update_key
[@@deriving sexp_of]
end
module Make (Fd : Fd) : S with module Fd := Fd = struct
open Deferred.Or_error.Let_syntax
module State = struct
type t =
| Active of Tls.Engine.state
| Eof
| Error of Tls_error.t
end
type t =
{ fd : Fd.t
; mutable state : State.t
; mutable linger : string option
; recv_buf : bytes
}
let tls_error = Fn.compose Deferred.Or_error.error_s Tls_error.sexp_of_t
let rec read_react t =
let handle tls buf =
match Tls.Engine.handle_tls tls buf with
| Ok (state, eof, `Response resp, `Data data) ->
t.state
<- (match eof with
| None -> Active state
| Some `Eof -> Eof);
let%map () =
match resp with
| None -> return ()
| Some resp -> Fd.write_full t.fd resp
in
`Ok data
| Error (alert, `Response resp) ->
t.state <- Error (match alert with `Alert a -> Tls_alert a | f -> Tls_failure f);
let%bind () = Fd.write_full t.fd resp in
read_react t
in
match t.state with
| Error e -> tls_error e
| Eof -> return `Eof
| Active _ ->
let%bind n = Fd.read t.fd t.recv_buf in
(match t.state, n with
| Active _, `Eof ->
t.state <- Eof;
return `Eof
| Active tls, `Ok n -> handle tls (Stdlib.Bytes.sub_string t.recv_buf 0 n)
| Error e, _ -> tls_error e
| Eof, _ -> return `Eof)
;;
let rec read t buf =
let writeout res =
let rlen = String.length res in
let n = min (Bytes.length buf) rlen in
Stdlib.Bytes.blit_string res 0 buf 0 n;
t.linger <- (if n < rlen then Some (Stdlib.String.sub res n (rlen - n)) else None);
return n
in
match t.linger with
| Some res -> writeout res
| None ->
(match%bind read_react t with
| `Eof -> return 0
| `Ok None -> read t buf
| `Ok (Some res) -> writeout res)
;;
let writev t css =
match t.state with
| Error err -> tls_error err
| Eof -> tls_error Connection_closed
| Active tls ->
(match Tls.Engine.send_application_data tls css with
| Some (tls, tlsdata) ->
t.state <- Active tls;
Fd.write_full t.fd tlsdata
| None -> tls_error Connection_not_ready)
;;
(*
* XXX bad XXX
* This is a point that should particularly be protected from concurrent r/w.
* Doing this before a `t` is returned is safe; redoing it during rekeying is
* not, as the API client already sees the `t` and can mistakenly interleave
* writes while this is in progress.
* *)
let rec drain_handshake t =
let push_linger t mcs =
match mcs, t.linger with
| None, _ -> ()
| scs, None -> t.linger <- scs
| Some cs, Some l -> t.linger <- Some (l ^ cs)
in
match t.state with
| Active tls when not (Tls.Engine.handshake_in_progress tls) -> return t
| _ ->
(match%bind read_react t with
| `Eof -> tls_error Unexpected_eof
| `Ok cs ->
push_linger t cs;
drain_handshake t)
;;
let reneg ?authenticator ?acceptable_cas ?cert ?(drop = true) t =
match t.state with
| Error err -> tls_error err
| Eof -> tls_error Connection_closed
| Active tls ->
(match Tls.Engine.reneg ?authenticator ?acceptable_cas ?cert tls with
| None -> tls_error Unable_to_renegotiate
| Some (tls', buf) ->
if drop then t.linger <- None;
t.state <- Active tls';
let%bind () = Fd.write_full t.fd buf in
let%bind _ = drain_handshake t in
return ())
;;
let key_update ?request t =
match t.state with
| Error err -> tls_error err
| Eof -> tls_error Connection_closed
| Active tls ->
(match Tls.Engine.key_update ?request tls with
| Error _ -> tls_error Unable_to_update_key
| Ok (tls', buf) ->
t.state <- Active tls';
Fd.write_full t.fd buf)
;;
let close_tls t =
match t.state with
| Active tls ->
let _, buf = Tls.Engine.send_close_notify tls in
t.state <- Eof;
Fd.write_full t.fd buf
| _ -> return ()
;;
let server_of_fd config fd =
drain_handshake
{ state = Active (Tls.Engine.server config)
; fd
; linger = None
; recv_buf = Bytes.create 4096
}
;;
let client_of_fd config ?host fd =
let config' =
match host with
| None -> config
| Some host -> Tls.Config.peer config host
in
let t = { state = Eof; fd; linger = None; recv_buf = Bytes.create 4096 } in
let tls, init = Tls.Engine.client config' in
let t = { t with state = Active tls } in
let%bind () = Fd.write_full t.fd init in
drain_handshake t
;;
let epoch t =
match t.state with
| Active tls -> (match Tls.Engine.epoch tls with
| Ok _ as o -> o
| Error () -> Or_error.error_string "no TLS state available yet")
| Eof -> Or_error.error_string "TLS state is end of file"
| Error _ -> Or_error.error_string "TLS state is error"
;;
end