package tiny_libs

  1. Overview
  2. Docs
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_libs.networking_unix/Relay_client.ml.html

Source file Relay_client.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Relay_client.mli *)

let connect (caps : < Cap.network ; .. >) ~(host : string) ~(port : int) : Transport.t =
  let fd = Tcp.connect caps ~host ~port () in
  Unix.set_nonblock fd;
  (* claude: as in Server's accept_all: a tick's packet leaves now *)
  Unix.setsockopt fd Unix.TCP_NODELAY true;
  let seed = ref (Lehmer.scramble (int_of_float (Unix.gettimeofday () *. 1000.))) in
  let bytes n =
    String.init n (fun _ ->
        seed := Lehmer.next !seed;
        Char.chr (int_of_float (256. *. Lehmer.to_unit !seed)))
  in
  let key = Base64.encode (bytes 16) in
  let inbox = ref "" and outbox = ref (Websocket.request ~host:(Printf.sprintf "%s:%d" host port) ~path:"/" ~key) in
  let upgraded = ref false and player = ref None and closed = ref false in
  let flush () =
    if !outbox <> "" && not !closed then
      match Unix.write_substring fd !outbox 0 (String.length !outbox) with
      | n -> outbox := String.sub !outbox n (String.length !outbox - n)
      | exception Unix.Unix_error ((Unix.EAGAIN | Unix.EWOULDBLOCK), _, _) -> ()
      | exception Unix.Unix_error _ -> closed := true
  in
  let read () =
    let buf = Bytes.create 65536 in
    let rec go () =
      match Unix.read fd buf 0 (Bytes.length buf) with
      | 0 -> closed := true
      | n ->
          inbox := !inbox ^ Bytes.sub_string buf 0 n;
          go ()
      | exception Unix.Unix_error ((Unix.EAGAIN | Unix.EWOULDBLOCK), _, _) -> ()
      | exception Unix.Unix_error _ -> closed := true
    in
    if not !closed then go ()
  in
  (* the answer to the handshake: 101, and the accept of our key *)
  let upgrade () =
    match Websocket.handshake !inbox with
    | None -> ()
    | Some (headers, stop) ->
        inbox := String.sub !inbox stop (String.length !inbox - stop);
        if List.assoc_opt "sec-websocket-accept" headers = Some (Websocket.accept key) then upgraded := true
        else closed := true
  in
  let rec frames acc =
    match Websocket.decode !inbox with
    | Incomplete -> List.rev acc
    | Bad _ ->
        closed := true;
        List.rev acc
    | Frame (f, n) -> (
        inbox := String.sub !inbox n (String.length !inbox - n);
        match f.opcode with
        (* the relay's welcome: which player we are *)
        | Binary when !player = None && String.length f.payload = 2 && f.payload.[0] = '\002' ->
            player := Some (Char.code f.payload.[1]);
            frames acc
        | Binary -> frames (f.payload :: acc)
        | Close ->
            closed := true;
            List.rev acc
        | _ -> frames acc)
  in
  {
    send =
      (fun packet ->
        (* before the handshake's answer too: the frame waits in the
         * outbox, behind the request, and the server reads them in
         * order (IRC's NICK and USER are sent at once) *)
        outbox := !outbox ^ Websocket.encode ~mask:(bytes 4) { fin = true; opcode = Binary; payload = packet };
        flush ());
    receive =
      (fun () ->
        flush ();
        read ();
        if not !upgraded then upgrade ();
        if !upgraded then frames [] else []);
    status =
      (fun () ->
        match (!closed, !player) with
        | true, _ -> Printf.sprintf "%s:%d closed the connection (full?)" host port
        | false, None -> Printf.sprintf "connecting to %s:%d" host port
        | false, Some _ -> Printf.sprintf "through %s:%d" host port);
    player = (fun () -> !player);
  }