open Test_common
open H2
open H2__

module Server_connection_tests = struct
  open Server_connection

  module Read_operation = struct
    type t =
      [ `Read
      | `Close
      | `Error of Error.t
      ]

    let pp_hum fmt t =
      let str =
        match t with
        | `Read -> "Read"
        | `Error (Error.ConnectionError (e, msg)) ->
          Format.sprintf "ConnectionError: %ld %S" (Error_code.serialize e) msg
        | `Error (Error.StreamError (stream_id, e)) ->
          Format.sprintf
            "StreamError on %ld: %ld"
            stream_id
            (Error_code.serialize e)
        | `Close -> "Close"
      in
      Format.pp_print_string fmt str
  end

  module Write_operation = struct
    type t =
      [ `Write of Bigstringaf.t IOVec.t list
      | `Yield
      | `Close of int
      ]

    let iovecs_to_string iovecs =
      let len = IOVec.lengthv iovecs in
      let bytes = Bytes.create len in
      let dst_off = ref 0 in
      List.iter
        (fun { IOVec.buffer; off = src_off; len } ->
           Bigstringaf.unsafe_blit_to_bytes
             buffer
             ~src_off
             bytes
             ~dst_off:!dst_off
             ~len;
           dst_off := !dst_off + len)
        iovecs;
      Bytes.unsafe_to_string bytes

    let pp_hum fmt t =
      match t with
      | `Write iovecs ->
        Format.fprintf fmt "Write %S" (iovecs_to_string iovecs |> hex_of_string)
      | `Yield -> Format.pp_print_string fmt "Yield"
      | `Close len -> Format.fprintf fmt "Close %i" len

    let to_write_as_string t =
      match t with
      | `Write iovecs -> Some (iovecs_to_string iovecs)
      | `Close _ | `Yield -> None
  end

  let read_operation = Alcotest.of_pp Read_operation.pp_hum
  let write_operation = Alcotest.of_pp Write_operation.pp_hum

  let default_request_handler reqd =
    Reqd.respond_with_string reqd (Response.create `OK) ""

  let test_initial_reader_state () =
    let t = create default_request_handler in
    Alcotest.(check read_operation)
      "A new reader wants input"
      `Read
      (next_read_operation t)

  let test_reader_is_closed_after_eof () =
    let t = create default_request_handler in
    let c = read_eof t Bigstringaf.empty ~off:0 ~len:0 in
    Alcotest.(check int) "read_eof with no input returns 0" 0 c;
    Alcotest.(check read_operation)
      "Shutting down a reader closes it"
      `Close
      (next_read_operation t);
    let t = create default_request_handler in
    let c = read t Bigstringaf.empty ~off:0 ~len:0 in
    Alcotest.(check int) "read with no input returns 0" 0 c;
    let c = read_eof t Bigstringaf.empty ~off:0 ~len:0 in
    Alcotest.(check int) "read_eof with no input returns 0" 0 c;
    Alcotest.(check read_operation)
      "Shutting down a reader closes it"
      `Close
      (next_read_operation t)

  let preface ?(settings = []) () =
    let writer = Serialize.Writer.create 0x400 in
    Serialize.Writer.write_connection_preface writer settings;
    Faraday.serialize_to_string (Serialize.Writer.faraday writer)

  let empty_preface = preface ()

  let handle_preface ?settings t =
    let preface = preface ?settings () in
    let preface_len = String.length preface in
    let preface = read t (bs_of_string preface) ~off:0 ~len:preface_len in
    Alcotest.(check int)
      "read preface returns preface length"
      preface_len
      preface;
    match next_write_operation t with
    | `Write iovecs ->
      let iovec_len = IOVec.lengthv iovecs in
      Alcotest.(check bool "Write more than 0" true (iovec_len > 0));
      report_write_result t (`Ok iovec_len)
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing the \
         connection preface."

  let create_and_handle_preface ?settings ?error_handler ?config request_handler
    =
    let t = create ?config ?error_handler request_handler in
    handle_preface ?settings t;
    t

  let read_string t str =
    let len = String.length str in
    let input = Bigstringaf.of_string str ~off:0 ~len in
    let c = read t input ~off:0 ~len in
    Alcotest.(check int) "read consumes all input" len c

  let request_to_string ?body request =
    let has_body = match body with None -> false | Some _ -> true in
    let hpack_encoder = Hpack.Encoder.create 4096 in
    let writer = Serialize.Writer.create 0x400 in
    let frame_info =
      Writer.make_frame_info
        ~flags:
          (if has_body
           then Flags.default_flags
           else Flags.(default_flags |> set_end_stream))
        1l
    in
    Serialize.Writer.write_request_headers
      writer
      hpack_encoder
      ~priority:Priority.default_priority
      frame_info
      request;
    Faraday.serialize_to_string (Serialize.Writer.faraday writer)

  let read_request ?body t request =
    let request_string = request_to_string ?body request in
    read_string t request_string

  let response_to_string t ?body response =
    let has_body = match body with None -> false | Some _ -> true in
    let writer = Serialize.Writer.create 0x400 in
    let frame_info =
      Writer.make_frame_info
        ~flags:
          (if has_body
           then Flags.default_flags
           else Flags.(default_flags |> set_end_stream))
        1l
    in
    Serialize.Writer.write_response_headers
      writer
      t.hpack_encoder
      frame_info
      response;
    (match body with
    | None -> ()
    | Some body ->
      Serialize.Writer.write_data
        writer
        { frame_info with flags = Flags.(default_flags |> set_end_stream) }
        body);
    Faraday.serialize_to_string (Serialize.Writer.faraday writer)

  let write_string t ?(msg = "frames written") str =
    let len = String.length str in
    Alcotest.(check (option string))
      msg
      (Some (str |> hex_of_string))
      (next_write_operation t
      |> Write_operation.to_write_as_string
      |> Option.map hex_of_string);
    report_write_result t (`Ok len)

  let write_response t ?body response =
    let response_string = response_to_string t ?body response in
    write_string t ~msg:"Response written" response_string

  let write_eof t = report_write_result t `Closed

  let ready_to_read t =
    Alcotest.check
      read_operation
      "Reader wants to read"
      `Read
      (next_read_operation t)

  let writer_yields t =
    Alcotest.check
      write_operation
      "Writer yields"
      `Yield
      (next_write_operation t)

  let writer_closed ?(unread = 0) t =
    Alcotest.(check write_operation)
      "Next operation should be `Close"
      (`Close unread)
      (next_write_operation t)

  let error_handler ?request:_ error handle =
    let message =
      match error with
      | `Exn exn -> Printexc.to_string exn
      | (#Status.client_error | #Status.server_error) as error ->
        Status.to_string error
    in
    let body = handle Headers.empty in
    Body.Writer.write_string body message;
    Body.Writer.close body

  let test_reading_malformed_frame ?(is_failure = false) wire () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let len = String.length wire in
    let bs = Bigstringaf.of_string ~off:0 ~len wire in
    let c = read_eof t bs ~off:0 ~len in
    if is_failure
    then (
      Alcotest.(check int)
        "read_eof with bad input (triggers a parse error) reads the header"
        10
        c;
      match Reader.next t.reader with
      | `Error _ ->
        Alcotest.(check pass)
          "bad input triggers an `Error` in the parser state"
          ()
          ()
      | _ -> Alcotest.fail "expected parser to be in an error state")
    else (
      Alcotest.(check int) "read_eof with invalid reads the whole frame" len c;
      Alcotest.check
        read_operation
        "Shutting down a reader closes it"
        `Close
        (Reader.next t.reader))

  (* Well-formed HEADERS + CONTINUATION frames. *)
  let header_and_continuation_frames =
    let hpack_encoder = Hpack.Encoder.create 4096 in
    let headers =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.(set_end_stream default_flags)
          ; frame_type = Headers
          }
      ; frame_payload =
          Frame.Headers
            ( Priority.default_priority
            , encode_headers
                hpack_encoder
                Headers.(
                  of_list [ ":method", "GET"; ":scheme", "https"; ":path", "/" ])
            )
      }
    in
    let continuation =
      { Frame.frame_header =
          { headers.frame_header with
            flags = Flags.(default_flags |> set_end_header)
          ; frame_type = Continuation
          }
      ; frame_payload =
          Frame.Continuation
            (encode_headers hpack_encoder Headers.(of_list [ "baz", "qux" ]))
      }
    in
    headers, continuation

  let test_send_frame_after_padded_frame () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let headers, continuation = header_and_continuation_frames in
    let padding = Bigstringaf.of_string ~off:0 ~len:10 "0123456789" in
    let headers_wire = Test_common.serialize_frame ~padding headers in
    let headers_length = Bigstringaf.length headers_wire in
    let continuation_wire = Test_common.serialize_frame continuation in
    let continuation_length = Bigstringaf.length continuation_wire in
    let read_headers = read t ~off:0 ~len:headers_length headers_wire in
    Alcotest.(check int)
      "Read the entire first frame"
      headers_length
      read_headers;
    let read_continuation =
      read t ~off:0 ~len:continuation_length continuation_wire
    in
    Alcotest.(check int)
      "Read the entire second frame"
      continuation_length
      read_continuation

  let read_frames conn frames =
    List.iter
      (fun frame ->
         let frame_wire = Test_common.serialize_frame frame in
         let frame_length = Bigstringaf.length frame_wire in
         let read_frame = read conn ~off:0 ~len:frame_length frame_wire in
         Alcotest.(check int) "Read the entire frame" frame_length read_frame)
      frames

  let check_response conn =
    match next_write_operation conn with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      let frame = List.hd frames in
      Alcotest.(check int)
        "Next write operation is a HEADERS frame"
        (Frame.FrameType.serialize Headers)
        Frame.(frame.frame_header.frame_type |> FrameType.serialize);
      let iovec_len = IOVec.lengthv iovecs in
      report_write_result conn (`Ok iovec_len);
      writer_yields conn;
      ready_to_read conn
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing \
         headers."

  let test_continuation_frame () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let headers, continuation = header_and_continuation_frames in
    read_frames t [ headers; continuation ];
    write_response t (Response.create `OK);
    let new_headers =
      { headers with
        frame_header =
          { headers.frame_header with
            stream_id = 3l
          ; flags = Flags.(set_end_header default_flags)
          }
      }
    in
    read_frames t [ new_headers ];
    check_response t

  let test_continuation_frame_another_stream () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let headers, continuation = header_and_continuation_frames in
    let continuation =
      { continuation (* continuation header on a different stream *) with
        frame_header = { continuation.frame_header with stream_id = 3l }
      }
    in
    read_frames t [ headers; continuation ];
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      let frame = List.hd frames in
      Alcotest.(check int)
        "Next write operation is a GOAWAY frame"
        (Frame.FrameType.serialize GoAway)
        Frame.(frame.frame_header.frame_type |> FrameType.serialize);
      let iovec_len = IOVec.lengthv iovecs in
      report_write_result t (`Ok iovec_len);
      writer_closed t;
      Alcotest.(check bool) "Connection is shutdown" true (is_closed t)
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing a \
         malformed CONTINUATION frame."

  let test_read_frame_size_error () =
    let max_length = String.length (preface ()) in
    let config = { Config.default with read_buffer_size = max_length } in
    let hpack_encoder = Hpack.Encoder.create 4096 in
    let headers =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.default_flags
          ; frame_type = Headers
          }
      ; frame_payload =
          Frame.Headers
            ( Priority.default_priority
            , encode_headers
                hpack_encoder
                Headers.(
                  of_list
                    [ ":method", "GET"
                    ; ":scheme", "https"
                    ; ":path", "/"
                    ; ( "some_really_long_header_name"
                      , "some_really_long_header_value" )
                    ]) )
      }
    in
    let frame_wire = Test_common.serialize_frame headers in
    let frame_length = Bigstringaf.length frame_wire in
    Alcotest.(check bool)
      "Frame payload is surely over the max length"
      true
      (frame_length > max_length);
    let t =
      create_and_handle_preface ~config ~error_handler default_request_handler
    in
    let read1 = read t ~off:0 ~len:max_length frame_wire in
    Alcotest.(check int) "only read the frame header" 9 read1;
    let read2 = read t ~off:9 ~len:(max_length - 9) frame_wire in
    Alcotest.(check int) "advances over fed input" (max_length - 9) read2;
    let read3 =
      read t ~off:(read1 + read2) ~len:(frame_length - max_length) frame_wire
    in
    Alcotest.(check int)
      "advances over more input"
      (frame_length - max_length)
      read3;
    Alcotest.check
      read_operation
      "Reader wants to read"
      `Read
      (Reader.next t.reader);
    (* #2 *)
    let t =
      create_and_handle_preface ~config ~error_handler default_request_handler
    in
    let read1 = read t ~off:0 ~len:max_length frame_wire in
    Alcotest.(check int) "only read the frame header" 9 read1;
    let read2 = read t ~off:9 ~len:(max_length - 9) frame_wire in
    Alcotest.(check int) "advances over fed input" (max_length - 9) read2;
    (* Read buffer advanced, contents are not the same anymore. *)
    let read3 =
      read_eof
        t
        ~off:(read1 + read2)
        ~len:
          (frame_length
          - max_length
          - (* random *)
          5)
        frame_wire
    in
    Alcotest.(check int)
      "advances over more input"
      (frame_length
      - max_length
      - (* random *)
      5)
      read3;
    Alcotest.check
      read_operation
      "There was a connection error of type FRAME_SIZE_ERROR"
      (`Error Error.(ConnectionError (FrameSizeError, "")))
      (Reader.next t.reader)

  let test_read_frame_size_error_priority_frame () =
    let max_length = String.length empty_preface in
    let config = { Config.default with read_buffer_size = max_length } in
    let t =
      create_and_handle_preface ~config ~error_handler default_request_handler
    in
    let frame_header_wire =
      "000025020000000001" |> string_of_hex |> bs_of_string
    in
    let frame_payload_wire =
      "6365727461696E6C7900000000000000000000000000000000000000000000000000000000"
      |> string_of_hex
      |> bs_of_string
    in
    let frame_payload_length = Bigstringaf.length frame_payload_wire in
    Alcotest.(check bool)
      "Frame payload is surely over the max length"
      true
      (frame_payload_length > max_length);
    let read1 = read t ~off:0 ~len:9 frame_header_wire in
    Alcotest.(check int) "read only the frame header" 9 read1;
    Alcotest.check
      read_operation
      "There was a stream error of type FRAME_SIZE_ERROR"
      (`Error Error.(StreamError (1l, FrameSizeError)))
      (Reader.next t.reader);
    (* payload length declared in the frame header *)
    let bytes_to_skip = ref 0x25 in
    let read2 = read t ~off:0 ~len:max_length frame_payload_wire in
    Alcotest.(check int)
      "keeps advancing over bad input (payload length)"
      max_length
      read2;
    bytes_to_skip := !bytes_to_skip - read2;
    let read3 = read t ~off:read2 ~len:!bytes_to_skip frame_payload_wire in
    Alcotest.(check int) "Read last bytes of payload" !bytes_to_skip read3;
    bytes_to_skip := !bytes_to_skip - read3;
    Alcotest.(check int) "No more to read" 0 !bytes_to_skip;
    Alcotest.check
      read_operation
      "Stream Error is not reported again, reader wants to read now"
      `Read
      (Reader.next t.reader)

  let test_preface_read_with_more_frames () =
    let t = create ~error_handler default_request_handler in
    let headers, _ = header_and_continuation_frames in
    let frame_wire = Test_common.serialize_frame headers in
    let frame_length = Bigstringaf.length frame_wire in
    let preface_length = String.length empty_preface in
    let preface_and_headers =
      Bigstringaf.create (frame_length + preface_length)
    in
    let preface_headers_length = Bigstringaf.length preface_and_headers in
    Bigstringaf.blit_from_string
      empty_preface
      ~src_off:0
      preface_and_headers
      ~dst_off:0
      ~len:preface_length;
    Bigstringaf.blit
      frame_wire
      ~src_off:0
      preface_and_headers
      ~dst_off:preface_length
      ~len:frame_length;
    let read_preface =
      read t preface_and_headers ~off:0 ~len:preface_headers_length
    in
    Alcotest.(check int)
      "read preface returns preface length"
      (preface_length + frame_length)
      read_preface;
    Alcotest.check
      read_operation
      "Reader wants to read"
      `Read
      (next_read_operation t)

  let test_settings_frame_unsigned () =
    let settings =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 0l
          ; flags = Flags.default_flags
          ; frame_type = Settings
          }
      ; frame_payload =
          Frame.Settings Settings.[ InitialWindowSize (Int32.shift_left 1l 31) ]
      }
    in
    let frame_wire = Test_common.serialize_frame settings in
    match parse_frames_bigstring frame_wire with
    | [ { Frame.frame_payload = Settings [ Settings.InitialWindowSize v ]; _ } ]
      ->
      (* The protocol says it should read a uint32 here, but because the
       * largest value it accepts is 2^31 - 1 we work around that by checking
       * for negative numbers (that have overflown). We avoid adding a new
       * dependency this way, but if we ever want to support it at least we
       * have this test. *)
      Alcotest.(check int32)
        "Window Size value roundtrips in a signed fashion"
        (Int32.shift_left (-1l) 31)
        v
    | _ -> Alcotest.fail "Expected frame to parse successfully."

  let test_open_existing_stream () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let priority =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.default_flags
          ; frame_type = Priority
          }
      ; frame_payload = Frame.Priority Priority.default_priority
      }
    in
    read_frames t [ priority ];
    let open Scheduler in
    let (Connection root) = t.streams in
    let root_children = root.children |> PriorityQueue.to_list in
    Alcotest.(check (list int32))
      "Stream has been added to the priority tree"
      [ 1l ]
      (List.map fst root_children);
    let (Stream { descriptor = old_reqd; _ }) =
      root_children |> List.hd |> snd
    in
    let headers, _ = header_and_continuation_frames in
    let headers =
      { headers with
        Frame.frame_header =
          { headers.frame_header with
            flags = Flags.(default_flags |> set_end_header |> set_end_stream)
          }
      }
    in
    read_frames t [ headers ];
    let open Scheduler in
    let new_root_children = root.children |> PriorityQueue.to_list in
    let (Stream { descriptor; _ }) = new_root_children |> List.hd |> snd in
    Alcotest.(check (list int32))
      "Priority tree still only contains one stream"
      [ 1l ]
      (new_root_children |> List.map fst);
    Alcotest.(check bool) "Reqd is the same" true (old_reqd == descriptor)

  let data_request_handler reqd =
    Reqd.respond_with_string reqd (Response.create `OK) "Some data"

  let test_dependent_stream () =
    let t = create_and_handle_preface ~error_handler data_request_handler in
    let hpack_encoder = Hpack.Encoder.create 4096 in
    let headers =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.(default_flags |> set_end_header |> set_end_stream)
          ; frame_type = Headers
          }
      ; frame_payload =
          Frame.Headers
            ( Priority.default_priority
            , encode_headers
                hpack_encoder
                Headers.(
                  of_list [ ":method", "GET"; ":scheme", "https"; ":path", "/" ])
            )
      }
    in
    let second_headers =
      { Frame.frame_header = { headers.frame_header with stream_id = 3l }
      ; frame_payload =
          Frame.Headers
            ( { Priority.default_priority with stream_dependency = 1l }
            , encode_headers
                hpack_encoder
                Headers.(
                  of_list [ ":method", "GET"; ":scheme", "https"; ":path", "/" ])
            )
      }
    in
    read_frames t [ headers; second_headers ];
    let open Scheduler in
    let (Stream first_stream) = Scheduler.get_node t.streams 1l |> opt_exn in
    let first_stream_children =
      first_stream.children |> PriorityQueue.to_list
    in
    Alcotest.(check (list int32))
      "Stream 3 has been added to the priority tree"
      [ 3l ]
      (List.map fst first_stream_children);
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      Alcotest.(check (list int))
        "HEADERS frames are flushed immediately, along with DATA for stream 1"
        Frame.FrameType.(List.map serialize [ Headers; Headers; Data ])
        (frames
        |> List.map (fun { Frame.frame_header; _ } ->
          Frame.(frame_header.frame_type |> FrameType.serialize)));
      let data_frame = List.nth frames 2 in
      Alcotest.(check int32)
        "The emitted DATA frame belongs to stream 1"
        1l
        data_frame.frame_header.stream_id;
      Server_connection.report_write_result t (`Ok (IOVec.lengthv iovecs));
      (match next_write_operation t with
      | `Write iovecs ->
        let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
        Alcotest.(check (list int))
          "DATA for stream 3 is flushed next"
          Frame.FrameType.[ serialize Data ]
          (frames
          |> List.map (fun { Frame.frame_header; _ } ->
            Frame.(frame_header.frame_type |> FrameType.serialize)));
        let data_frame = List.hd frames in
        Alcotest.(check int32)
          "The emitted DATA frame belongs to stream 3"
          3l
          data_frame.frame_header.stream_id
      | _ ->
        Alcotest.fail
          "Expected state machine to issue a write operation after seeing \
           headers.")
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing \
         headers."

  let server_push_request_handler reqd =
    let request = Request.create `GET ~scheme:"http" "/main.css" in
    let pushed_reqd =
      match Reqd.push reqd request with
      | Ok reqd -> reqd
      | Error _ -> Alcotest.fail "Expected `push` to succeed"
    in
    let response = Response.create `OK in
    (* Send the response for / *)
    Reqd.respond_with_string reqd response "Hello";
    (* Send the response for /main.css *)
    Reqd.respond_with_string pushed_reqd response "Hello"

  let test_server_push () =
    let t =
      create_and_handle_preface ~error_handler server_push_request_handler
    in
    let request = Request.create ~scheme:"https" `GET "/" in
    (* This calls the request handler. We then expect to receive at least 3
     * frames: 1 HEADERS frame for the response, a PUSH_PROMISE frame for the
     * pushed request and its respective HEADERS frame. *)
    read_request t request;
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      Alcotest.(check (list int))
        "Next write operation surfaces writes for the PUSH_PROMISE frame and \
         HEADERS / DATA"
        List.(
          map
            (fun { Frame.frame_header; _ } ->
               Frame.FrameType.serialize frame_header.frame_type)
            frames)
        List.(
          map
            Frame.FrameType.serialize
            Frame.FrameType.[ PushPromise; Headers; Headers; Data ]);
      let (Stream pushed_stream) = opt_exn (Scheduler.get_node t.streams 2l) in
      Alcotest.(check int32)
        "Pushed stream has a stream dependency on the parent stream"
        1l
        pushed_stream.priority.stream_dependency;
      let iovec_len = IOVec.lengthv iovecs in
      report_write_result t (`Ok iovec_len);
      (match next_write_operation t with
      | `Write iovecs ->
        let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
        let frame = List.hd frames in
        Alcotest.(check int)
          "Next write operation surfaces the last DATA frame"
          Frame.FrameType.(serialize frame.frame_header.frame_type)
          Frame.FrameType.(serialize Data);
        Alcotest.(check int32)
          "The last DATA frame is for the server-pushed stream"
          2l
          frame.frame_header.stream_id;
        let iovec_len = IOVec.lengthv iovecs in
        report_write_result t (`Ok iovec_len);
        writer_yields t;
        ready_to_read t
      | _ ->
        Alcotest.fail
          "Expected state machine to issue a write operation after seeing \
           headers.")
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing \
         headers."

  (* data frame with invalid amount of padding *)
  let w = string_of_hex "00000400080000000104AAAAAA"

  (* data frame with frame size error *)
  let w2 =
    string_of_hex "0080000008000000020648656C6C6F2C20776F726C6421686F77647921"

  (* priority frame with (stream error of) frame size error - header declares 4
   * bytes of payload, not yet totally available on the buffer. *)
  let w3 = string_of_hex "0000040200000000018000"

  (* Testing for https://github.com/inhabitedtype/angstrom/pull/166 *)
  let test_reading_just_header wire () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let wire = bs_of_string wire in
    let wire_length = Bigstringaf.length wire in
    let c = read t wire ~off:0 ~len:wire_length in
    Alcotest.(check int) "read with invalid payload reads only the header" 9 c;
    Alcotest.check
      read_operation
      "Reader wants more input to advance and report the stream error"
      `Read
      (Reader.next t.reader);
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let c = read_eof t wire ~off:0 ~len:wire_length in
    (* Difference between this test and the one above is `read_eof` vs `read` *)
    Alcotest.(check int)
      "read_eof with invalid payload reads only the header"
      9
      c;
    Alcotest.check
      read_operation
      "Shutting down a reader closes it"
      (`Error Error.(StreamError (1l, FrameSizeError)))
      (Reader.next t.reader)

  let test_connect () =
    let error_handler_called = ref false in
    let error_handler ?request:_ error handle =
      error_handler_called := true;
      Alcotest.(check bool) "request was malformed" true (error = `Bad_request);
      let body = handle Headers.empty in
      Body.Writer.write_string body "";
      Body.Writer.close body
    in
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let request =
      Request.create
        ~headers:Headers.(of_list [ ":authority", "foo.com:8080" ])
        ~scheme:"https"
        `CONNECT
        "/"
    in
    read_request t request;
    write_response t (Response.create `OK);
    writer_yields t;
    Alcotest.(check bool)
      "error handler was not called"
      false
      !error_handler_called

  let test_connect_malformed () =
    let error_handler_called = ref false in
    let error_handler ?request:_ error handle =
      error_handler_called := true;
      Alcotest.(check bool) "request was malformed" true (error = `Bad_request);
      let body = handle Headers.empty in
      Body.Writer.write_string body "";
      Body.Writer.close body
    in
    let t = create_and_handle_preface ~error_handler default_request_handler in
    (* CONNECT is malformed if it doesn't include the `:authority`
     * pseudo-header. Additionally, the `:scheme` and `:path` pseudo-headers
     * must be omitted, but we take care of that when serializing. See
     * RFC7540§8.3. *)
    let request = Request.create ~scheme:"https" `CONNECT "/" in
    read_request t request;
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      let frame = List.hd frames in
      Alcotest.(check int)
        "Next write operation is a HEADERS frame"
        (Frame.FrameType.serialize Headers)
        Frame.(frame.frame_header.frame_type |> FrameType.serialize);
      let iovec_len = IOVec.lengthv iovecs in
      report_write_result t (`Ok iovec_len);
      Alcotest.(check bool)
        "error handler was called"
        true
        !error_handler_called
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing \
         headers."

  let test_client_max_concurrent_streams () =
    (* From RFC7540§5.1.2: [...] clients specify the maximum number of
     * concurrent streams the server can initiate, and servers specify the
     * maximum number of concurrent streams the client can initiate.
     *
     * Note: in this test, the client is saying the server is not allowed to
     * initiate streams. The client, however, is. *)
    let t =
      create_and_handle_preface
        ~settings:[ MaxConcurrentStreams 0l ]
        ~error_handler
        default_request_handler
    in
    let request = Request.create ~scheme:"https" `GET "/" in
    read_request t request;
    write_response t (Response.create `OK)

  let streaming_handler ?(flush = false) response writes reqd =
    let request_body = Reqd.request_body reqd in
    Body.Reader.close request_body;
    let body =
      Reqd.respond_with_streaming ~flush_headers_immediately:flush reqd response
    in
    let rec write writes =
      match writes with
      | [] -> Body.Writer.close body
      | w :: ws ->
        Body.Writer.write_string body w;
        Body.Writer.flush body (function
          | `Closed -> assert false
          | `Written -> write ws)
    in
    write writes

  let test_empty_fixed_streaming_response () =
    let request = Request.create ~scheme:"http" `GET "/" in
    let response =
      Response.create `OK ~headers:(Headers.of_list [ "content-length", "0" ])
    in
    let t =
      create_and_handle_preface ~error_handler (streaming_handler response [])
    in
    read_request t request;
    write_response t ~body:"" response;
    writer_yields t

  let test_h2c () =
    let settings_payload = Settings.[ EnablePush 0; MaxConcurrentStreams 2l ] in
    let f = Faraday.create 100 in
    Settings.write_settings_payload f settings_payload;
    let serialized_settings = Faraday.serialize_to_string f in
    let request_handler_called = ref false in
    match
      create_h2c
        ~meth:`GET
        ~target:"/"
        ~headers:
          (Httpun_types.Headers.of_list
             [ "Connection", "Upgrade, HTTP2-Settings"
             ; "Upgrade", "h2c"
             ; ( "HTTP2-Settings"
               , Base64.(
                   encode_string ~alphabet:uri_safe_alphabet serialized_settings)
               )
             ; "Host", "localhost"
             ])
        (fun _ -> request_handler_called := true)
    with
    | Ok t ->
      Alcotest.(check bool)
        "Request handler called"
        true
        !request_handler_called;
      Alcotest.(check bool)
        "Connection settings were set as per the incoming settings"
        true
        (t.settings
        = { Settings.default with
            enable_push = false
          ; max_concurrent_streams = 2l
          });
      (match next_write_operation t with
      | `Write iovecs ->
        let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
        let frame = List.hd frames in
        Alcotest.(check int)
          "Next write operation is a SETTINGS frame (server connection preface)"
          (Frame.FrameType.serialize Settings)
          Frame.(frame.frame_header.frame_type |> FrameType.serialize)
      | _ ->
        Alcotest.fail
          "Expected state machine to issue a write operation after seeing \
           headers.")
    | Error msg -> Alcotest.fail msg

  let test_nonzero_content_length_no_data_frames () =
    let request =
      Request.create
        ~headers:(Headers.of_list [ "content-length", "1234" ])
        ~scheme:"http"
        `GET
        "/"
    in
    let t = create_and_handle_preface ~error_handler default_request_handler in
    read_request t request;
    write_response t ?body:None (Response.create `OK);
    writer_yields t

  let test_unexpected_eof () =
    let t = create_and_handle_preface ~error_handler default_request_handler in
    let request = Request.create ~scheme:"http" `GET "/" in
    read_request t request;
    write_eof t;
    writer_closed t ~unread:10

  let test_read_frame_size_error_unknown_frame () =
    (* Enough for a frame header *)
    let max_length = String.length (preface ()) in
    let config = { Config.default with read_buffer_size = max_length } in
    let t =
      create_and_handle_preface ~config ~error_handler default_request_handler
    in
    let frame =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.default_flags
          ; frame_type = Unknown 80
          }
      ; frame_payload =
          Frame.Unknown
            (80, Bigstringaf.of_string ~off:0 ~len:40 (String.make 40 'a'))
      }
    in
    let frame_wire = Test_common.serialize_frame frame in
    let frame_length = Bigstringaf.length frame_wire in
    Alcotest.(check bool)
      "Frame payload is surely over the max length"
      true
      (frame_length > max_length);
    let read1 = read t ~off:0 ~len:max_length frame_wire in
    Alcotest.(check int) "only read the frame header" 9 read1;
    let read2 = read t ~off:9 ~len:(max_length - 9) frame_wire in
    Alcotest.(check int) "advances over fed input" (max_length - 9) read2;
    let read3 =
      read t ~off:(read1 + read2) ~len:(frame_length - max_length) frame_wire
    in
    Alcotest.(check int)
      "advances over more input"
      (frame_length - max_length)
      read3;
    Alcotest.check
      read_operation
      "Reader wants to read, unknown frame type is ignored"
      `Read
      (Reader.next t.reader)

  let test_reading_request_body () =
    let body_read_called = ref false in
    let body_eof_called = ref false in
    let request = Request.create ~scheme:"http" `GET "/" in
    let response =
      Response.create `OK ~headers:(Headers.of_list [ "content-length", "0" ])
    in
    let request_handler reqd =
      let request_body = Reqd.request_body reqd in
      Body.Reader.schedule_read
        request_body
        ~on_eof:ignore
        ~on_read:(fun _bs ~off:_ ~len:_ ->
          body_read_called := true;
          Alcotest.(check bool)
            "Response body isn't closed (yet) when reading"
            false
            (Body.Reader.is_closed request_body);
          Body.Reader.schedule_read
            ~on_read:(fun _ ~off:_ ~len:_ ->
              Body.Reader.schedule_read
                ~on_read:(fun _ ~off:_ ~len:_ -> ())
                ~on_eof:(fun () ->
                  body_eof_called := true;
                  Reqd.respond_with_string reqd response "")
                request_body)
            ~on_eof:ignore
            request_body)
    in
    let t = create_and_handle_preface ~error_handler request_handler in
    read_request ~body:"request body" t request;
    let data_frame =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.default_flags
          ; frame_type = Data
          }
      ; frame_payload = Frame.Data (Bigstringaf.of_string ~off:0 ~len:3 "foo")
      }
    in
    read_frames t [ data_frame ];
    read_frames
      t
      [ { data_frame with
          frame_header =
            { data_frame.frame_header with
              flags = Flags.(default_flags |> set_end_stream)
            }
        }
      ];
    let window_update_and_response_frames =
      next_write_operation t |> Write_operation.to_write_as_string |> Option.get
    in
    report_write_result
      t
      (`Ok (String.length window_update_and_response_frames));
    writer_yields t;
    Alcotest.(check bool)
      "Response body read handler called"
      true
      !body_read_called;
    Alcotest.(check bool)
      "Response body EOF handler called"
      true
      !body_eof_called

  let test_rst_stream_frames () =
    let t = create_and_handle_preface default_request_handler in
    let request = Request.create ~scheme:"http" `GET "/" in
    read_request ~body:"request body" t request;
    let data_frame =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.default_flags
          ; frame_type = Data
          }
      ; frame_payload = Frame.Data (Bigstringaf.of_string ~off:0 ~len:3 "foo")
      }
    in
    let rst_stream =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.default_flags
          ; frame_type = RSTStream
          }
      ; frame_payload = Frame.RSTStream Error_code.ProtocolError
      }
    in
    read_frames t [ data_frame; rst_stream; rst_stream ];
    let window_update_and_response_frames =
      next_write_operation t |> Write_operation.to_write_as_string |> Option.get
    in
    report_write_result
      t
      (`Ok (String.length window_update_and_response_frames));
    writer_yields t

  let test_flow_control () =
    let body_read_called = ref false in
    let request = Request.create ~scheme:"http" `GET "/" in
    let request_handler reqd =
      let request_body = Reqd.request_body reqd in
      Body.Reader.schedule_read
        request_body
        ~on_eof:ignore
        ~on_read:(fun _bs ~off:_ ~len:_ -> body_read_called := true)
    in
    let t = create_and_handle_preface ~error_handler request_handler in
    read_request ~body:"request body" t request;
    let data_frame =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.(default_flags |> set_end_stream)
          ; frame_type = Data
          }
      ; frame_payload = Frame.Data (Bigstringaf.of_string ~off:0 ~len:3 "foo")
      }
    in
    read_frames t [ data_frame ];
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      Alcotest.(check (list int))
        "Only writes are WINDOW_UPDATE frames"
        (List.map
           Frame.FrameType.serialize
           Frame.FrameType.[ WindowUpdate; WindowUpdate ])
        (List.map
           (fun Frame.{ frame_header = { frame_type; _ }; _ } ->
              Frame.FrameType.serialize frame_type)
           frames);
      report_write_result t (`Ok (IOVec.lengthv iovecs));
      Alcotest.(check bool) "Response handler called" true !body_read_called;
      writer_yields t
    | _ -> assert false

  let test_flow_control_can_send_empty_data_frame () =
    let request = Request.create ~scheme:"http" `GET "/" in
    let request_handler reqd =
      let response = Response.create `OK in
      let response_body = Reqd.respond_with_streaming reqd response in
      Body.Writer.write_string response_body "hello";
      Body.Writer.flush response_body (function
        | `Closed -> assert false
        | `Written -> Body.Writer.close response_body)
    in
    let t =
      create_and_handle_preface
        ~settings:Settings.[ InitialWindowSize 5l ]
        ~error_handler
        request_handler
    in
    read_request ~body:"request body" t request;
    let data_frame =
      { Frame.frame_header =
          { payload_length = 0
          ; stream_id = 1l
          ; flags = Flags.(default_flags |> set_end_stream)
          ; frame_type = Data
          }
      ; frame_payload = Frame.Data (Bigstringaf.of_string ~off:0 ~len:3 "foo")
      }
    in
    read_frames t [ data_frame ];
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      Alcotest.(check (list int))
        "Response written"
        (List.map Frame.FrameType.serialize Frame.FrameType.[ Headers; Data ])
        (List.map
           (fun Frame.{ frame_header = { frame_type; _ }; _ } ->
              Frame.FrameType.serialize frame_type)
           frames);
      report_write_result t (`Ok (IOVec.lengthv iovecs));
      (match next_write_operation t with
      | `Write iovecs ->
        let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
        Alcotest.(check (list int))
          "Final 0-length DATA frame is not subject to flow control"
          (List.map Frame.FrameType.serialize Frame.FrameType.[ Data ])
          (List.map
             (fun Frame.{ frame_header = { frame_type; _ }; _ } ->
                Frame.FrameType.serialize frame_type)
             frames);
        report_write_result t (`Ok (IOVec.lengthv iovecs));
        writer_yields t
      | _ -> assert false)
    | _ -> assert false

  let trailers_request_handler reqd =
    let response = Response.create `OK in
    (* Send the response for / *)
    let response_body = Reqd.respond_with_streaming reqd response in
    Body.Writer.write_string response_body "somedata";
    Body.Writer.flush response_body (function
      | `Closed -> assert false
      | `Written ->
        Reqd.schedule_trailers reqd Headers.(add empty "foo" "bar");
        Body.Writer.close response_body)

  let test_trailers () =
    let t = create ~error_handler trailers_request_handler in
    handle_preface t;
    let headers, _ = header_and_continuation_frames in
    let headers =
      { headers with
        Frame.frame_header =
          { headers.frame_header with
            flags = Flags.(default_flags |> set_end_header |> set_end_stream)
          }
      }
    in
    read_frames t [ headers ];
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      List.iter2
        (fun { Frame.frame_header; _ } (label, frame_type, flags) ->
           Alcotest.(check int)
             ("Next write operation surfaces writes for " ^ label)
             (Frame.FrameType.serialize frame_header.frame_type)
             (Frame.FrameType.serialize frame_type);
           Alcotest.(check int)
             "Correct flags are used"
             frame_header.flags
             flags)
        frames
        Frame.FrameType.
          [ ("HEADERS", Headers, Flags.(set_end_header default_flags))
          ; "DATA", Data, Flags.default_flags
          ];
      let iovec_len = IOVec.lengthv iovecs in
      report_write_result t (`Ok iovec_len);
      (match next_write_operation t with
      | `Write iovecs ->
        let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
        let frame = List.hd frames in
        Alcotest.(check int)
          "Next write operation surfaces the trailers HEADERS frame"
          Frame.FrameType.(serialize Headers)
          Frame.FrameType.(serialize frame.frame_header.frame_type);
        Alcotest.(check int)
          "Last HEADERS frame has END_STREAM and END_HEADERS flag"
          frame.frame_header.flags
          Flags.(set_end_stream (set_end_header default_flags));
        let iovec_len = IOVec.lengthv iovecs in
        report_write_result t (`Ok iovec_len);
        Alcotest.check
          write_operation
          "Writer yields"
          `Yield
          (next_write_operation t);
        Alcotest.check
          read_operation
          "Reader wants to read"
          `Read
          (next_read_operation t)
      | _ ->
        Alcotest.fail
          "Expected state machine to issue a write operation after seeing \
           headers.")
    | _ ->
      Alcotest.fail
        "Expected state machine to issue a write operation after seeing \
         headers."

  let test_reset_stream () =
    let request = Request.create ~scheme:"http" `GET "/" in
    let request_handler reqd =
      Reqd.respond_with_string
        reqd
        (Response.create `Internal_server_error)
        "An error occurred";
      Reqd.report_exn reqd Not_found
    in
    let t = create_and_handle_preface ~error_handler request_handler in
    read_request ~body:"request body" t request;
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      Alcotest.(check (list int))
        "Doesn't send DATA frames after RST_STREAM frames"
        (List.map
           Frame.FrameType.serialize
           Frame.FrameType.[ Headers; RSTStream ])
        (List.map
           (fun Frame.{ frame_header = { frame_type; _ }; _ } ->
              Frame.FrameType.serialize frame_type)
           frames);
      report_write_result t (`Ok (IOVec.lengthv iovecs));
      writer_yields t
    | _ -> assert false

  let test_reset_stream_streaming_response () =
    (* CONNECT is malformed if it doesn't include the `:authority`
     * pseudo-header. Additionally, the `:scheme` and `:path` pseudo-headers
     * must be omitted, but we take care of that when serializing. See
     * RFC7540§8.3. *)
    let request = Request.create ~scheme:"https" `CONNECT "/" in
    let error_handler ?request:_ error handle =
      Alcotest.(check bool) "request was malformed" true (error = `Bad_request);
      let body = handle Headers.empty in
      Body.Writer.write_string body "hi";
      Body.Writer.close body
    in

    let t = create_and_handle_preface ~error_handler default_request_handler in
    read_request ~body:"request body" t request;
    match next_write_operation t with
    | `Write iovecs ->
      let frames = parse_frames (Write_operation.iovecs_to_string iovecs) in
      Alcotest.(check (list int))
        "Doesn't send RST_STREAM frames if the error is `Bad_request"
        (List.map Frame.FrameType.serialize Frame.FrameType.[ Headers; Data ])
        (List.map
           (fun Frame.{ frame_header = { frame_type; _ }; _ } ->
              Frame.FrameType.serialize frame_type)
           frames);
      report_write_result t (`Ok (IOVec.lengthv iovecs))
    | _ -> assert false

  (* TODO: test graceful shutdown, allowing lower numbered streams to
     complete. *)
  let suite =
    [ "initial reader state", `Quick, test_initial_reader_state
    ; "shutdown reader closed", `Quick, test_reader_is_closed_after_eof
    ; "malformed frame", `Quick, test_reading_malformed_frame w
    ; ( "malformed frame"
      , `Quick
      , test_reading_malformed_frame ~is_failure:true w2 )
    ; "malformed frame", `Quick, test_reading_just_header w3
    ; ( "send frames after a padded frame"
      , `Quick
      , test_send_frame_after_padded_frame )
    ; ( "continuation frame on the same stream (correct)"
      , `Quick
      , test_continuation_frame )
    ; ( "continuation frame on another stream"
      , `Quick
      , test_continuation_frame_another_stream )
    ; ( "frame size error (frame size exceeds `Config.read_buffer_size`)"
      , `Quick
      , test_read_frame_size_error )
    ; ( "frame size error on a priority frame"
      , `Quick
      , test_read_frame_size_error_priority_frame )
    ; ( "connection preface read with more frames"
      , `Quick
      , test_preface_read_with_more_frames )
    ; ( "settings that exceeds the maximum allowed"
      , `Quick
      , test_settings_frame_unsigned )
    ; "open existing stream", `Quick, test_open_existing_stream
    ; "dependent stream", `Quick, test_dependent_stream
    ; "server push", `Quick, test_server_push
    ; "CONNECT method", `Quick, test_connect
    ; "CONNECT method (malformed)", `Quick, test_connect_malformed
    ; ( "Client sends 0 max concurrent streams"
      , `Quick
      , test_client_max_concurrent_streams )
    ; ( "empty fixed streaming response"
      , `Quick
      , test_empty_fixed_streaming_response )
    ; "starting an h2c connection", `Quick, test_h2c
    ; ( "non-zero `content-length` and no DATA frames"
      , `Quick
      , test_nonzero_content_length_no_data_frames )
    ; "premature remote close with pending bytes", `Quick, test_unexpected_eof
    ; ( "frame size error unknown frame"
      , `Quick
      , test_read_frame_size_error_unknown_frame )
    ; ( "reading the request body as it arrives"
      , `Quick
      , test_reading_request_body )
    ; "accepting multiple RST_STREAM frames", `Quick, test_rst_stream_frames
    ; "flow control", `Quick, test_flow_control
    ; ( "flow control -- can send empty data frame"
      , `Quick
      , test_flow_control_can_send_empty_data_frame )
    ; "trailers", `Quick, test_trailers
    ; "reset stream before all DATA frames were sent", `Quick, test_reset_stream
    ; ( "reset stream before all DATA frames were sent (streaming response)"
      , `Quick
      , test_reset_stream_streaming_response )
    ]
end

let () =
  Alcotest.run
    "ocaml-h2 unit tests"
    [ "server_connection", Server_connection_tests.suite ]
