open Lwt.Infix

let mtu = 4000

let server_log = Logs.Src.create "test_deadlock_server" ~doc:"tcp deadlock tests: server"
module Server_log = (val Logs.src_log server_log : Logs.LOG)

let client_log = Logs.Src.create "test_deadlock_client" ~doc:"tcp deadlock tests: client"
module Client_log = (val Logs.src_log client_log : Logs.LOG)

module TCPIP =
struct
  module RANDOM = Mirage_crypto_rng

  module M =
  struct
    module B      = Basic_backend.Make
    module NETIF  = Vnetif.Make(B)
    module ETHIF  = Ethernet.Make(NETIF)
    module ARPV4  = Arp.Make(ETHIF)
    module IPV4   = Static_ipv4.Make(ETHIF)(ARPV4)
    module IPV6   = Ipv6.Make(NETIF)(ETHIF)
    module IP     = Tcpip_stack_direct.IPV4V6(IPV4)(IPV6)
    module ICMPV4 = Icmpv4.Make(IPV4)
    module UDP    = Udp.Make(IP)
    module TCP    = Tcp.Flow.Make(IP)
    module TCPIP  = Tcpip_stack_direct.MakeV4V6(NETIF)(ETHIF)(ARPV4)(IP)(ICMPV4)(UDP)(TCP)
  end
  open M

  type stack = TCPIP.t

  let server_ip = Ipaddr.V4.of_string_exn "192.168.10.10"
  let server_cidr = Ipaddr.V4.Prefix.make 24 server_ip
  let client_ip = Ipaddr.V4.of_string_exn "192.168.10.20"
  let client_cidr = Ipaddr.V4.Prefix.make 24 client_ip

  let make ~cidr ?gateway netif =
    ETHIF.connect netif >>= fun ethif ->
    ARPV4.connect ethif >>= fun arpv4 ->
    IPV4.connect ~cidr ?gateway ethif arpv4 >>= fun ipv4 ->
    IPV6.connect netif ethif >>= fun ipv6 ->
    IP.connect ~ipv4_only:false ~ipv6_only:false ipv4 ipv6 >>= fun ip ->
    ICMPV4.connect ipv4 >>= fun icmpv4 ->
    UDP.connect ip >>= fun udp ->
    TCP.connect ip >>= fun tcp ->
    TCPIP.connect netif ethif arpv4 ip icmpv4 udp tcp >>= fun tcpip ->
    Lwt.return tcpip

  include TCPIP

  let tcpip t = t

  let make role netif = match role with
    | `Server -> make ~cidr:server_cidr netif
    | `Client -> make ~cidr:client_cidr netif

  type conn = M.NETIF.t

  let get_stats _t =
    { Mirage_net.rx_pkts = 0l; rx_bytes = 0L;
      tx_pkts = 0l; tx_bytes = 0L;
    }

  let reset_stats _t = ()
end

let port = 10000

let test_digest netif1 netif2 =
  TCPIP.make `Client netif1 >>= fun client_stack ->
  TCPIP.make `Server netif2 >>= fun server_stack ->

  let send_data () =
    let data = Mirage_crypto_rng.generate 100_000_000 in
    let t0   = Unix.gettimeofday () in
    TCPIP.TCP.create_connection
      TCPIP.(tcp @@ tcpip server_stack) (Ipaddr.V4 TCPIP.client_ip, port) >>= function
    | Error _ -> failwith "could not establish tunneled connection"
    | Ok flow ->
      Server_log.debug (fun f -> f "established conn");
      let rec read_digest chunks =
        TCPIP.TCP.read flow >>= function
        | Error _ -> failwith "read error"
        | Ok (`Data data) -> read_digest (data :: chunks)
        | Ok `Eof ->
          Server_log.debug (fun f -> f "EOF");
          let dt = Unix.gettimeofday () -. t0 in
          Server_log.warn (fun f -> f "!!!!!!!!!! XXXX  needed %.2fs (%.1f MB/s)"
            dt (float (String.length data) /. dt /. 1024. ** 2.));
          Lwt.return_unit
      in
      Lwt.pick
        [ read_digest [];
          begin
            let rec send_data data =
              if Cstruct.length data < mtu then
                (TCPIP.TCP.write flow data >>= fun _ -> Lwt.return_unit)
              else
                let sub, data = Cstruct.split data mtu in
                Lwt.pick
                  [
                    (TCPIP.TCP.write flow sub >>= fun _ -> Lwt.return_unit);
                    (Lwt_unix.sleep 5. >>= fun () ->
                     Common.failf "=========== DEADLOCK!!! =============");
                  ]
                >>= fun () ->
                send_data data in
            send_data @@ Cstruct.of_string data >>= fun () ->
            Server_log.debug (fun f -> f "wrote data");
            TCPIP.TCP.close flow
          end
        ]
  in
  TCPIP.TCP.listen TCPIP.(tcp (tcpip client_stack)) ~port
    (fun flow ->
       Client_log.debug (fun f -> f "client got conn");
       let rec consume () =
         TCPIP.TCP.read flow >>= function
         | Error _ ->
           Client_log.debug (fun f -> f "XXXX client read error");
           TCPIP.TCP.close flow
         | Ok `Eof ->
           TCPIP.TCP.write flow @@ Cstruct.of_string "thanks for all the fish"
           >>= fun _ ->
           TCPIP.TCP.close flow
         | Ok (`Data _data) ->
           (if Random.float 1.0 < 0.01 then Lwt_unix.sleep 0.01
           else Lwt.return_unit) >>= fun () ->
           consume ()
       in
       consume ());
  Lwt.pick
    [
      send_data ();
      TCPIP.listen @@ TCPIP.tcpip server_stack;
      TCPIP.listen @@ TCPIP.tcpip client_stack;
    ]

let run_vnetif () =
  let backend = Basic_backend.Make.create
      ~use_async_readers:true ~yield:Lwt.pause () in
  TCPIP.M.NETIF.connect ~size_limit:mtu backend >>= fun c1 ->
  TCPIP.M.NETIF.connect ~size_limit:mtu backend >>= fun c2 ->
  test_digest c1 c2

let suite = [
  "test tcp deadlock with slow receiver", `Slow, run_vnetif
]
