Matrix protocol in OCaml, Eio specialised
1(** VoIP call signaling operations. *)
2
3(** Call state *)
4type call_state =
5 | Ringing
6 | Connected
7 | Ended
8
9(** Generate a new call ID. *)
10let generate_call_id () =
11 (* Use transaction ID generator for call IDs *)
12 Matrix_proto.Id.Transaction_id.(generate () |> to_string)
13
14(** Generate a new party ID for multi-party calls. *)
15let generate_party_id () =
16 Matrix_proto.Id.Transaction_id.(generate () |> to_string)
17
18(** Send a call invite.
19
20 @param room_id The room to send the invite in
21 @param call_id The call ID
22 @param offer The SDP offer
23 @param lifetime How long the invite is valid in milliseconds
24 @param version Call version (0 or 1)
25 @param party_id Party ID for version 1 calls
26 @param invitee Optional user to invite (for version 1 calls) *)
27let send_invite client ~room_id ~call_id ~offer ~lifetime
28 ?(version = 1) ?party_id ?invitee () =
29 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in
30 let txn_id = Matrix_proto.Id.Transaction_id.(generate () |> to_string) in
31 let path = Printf.sprintf "/rooms/%s/send/m.call.invite/%s"
32 (Uri.pct_encode room_id_str)
33 (Uri.pct_encode txn_id)
34 in
35 let content : Matrix_proto.Event.Call_invite_content.t = {
36 call_id;
37 party_id;
38 version;
39 lifetime;
40 offer;
41 invitee;
42 } in
43 match Client.encode_body Matrix_proto.Event.Call_invite_content.jsont content with
44 | Error e -> Error e
45 | Ok body ->
46 match Client.put client ~path ~body () with
47 | Error e -> Error e
48 | Ok resp_body ->
49 match Client.decode_response Messages.send_response_jsont resp_body with
50 | Error e -> Error e
51 | Ok resp -> Ok resp.event_id
52
53(** Send call candidates (ICE candidates).
54
55 @param room_id The room
56 @param call_id The call ID
57 @param candidates List of ICE candidates
58 @param version Call version
59 @param party_id Party ID for version 1 calls *)
60let send_candidates client ~room_id ~call_id ~candidates
61 ?(version = 1) ?party_id () =
62 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in
63 let txn_id = Matrix_proto.Id.Transaction_id.(generate () |> to_string) in
64 let path = Printf.sprintf "/rooms/%s/send/m.call.candidates/%s"
65 (Uri.pct_encode room_id_str)
66 (Uri.pct_encode txn_id)
67 in
68 let content : Matrix_proto.Event.Call_candidates_content.t = {
69 call_id;
70 party_id;
71 version;
72 candidates;
73 } in
74 match Client.encode_body Matrix_proto.Event.Call_candidates_content.jsont content with
75 | Error e -> Error e
76 | Ok body ->
77 match Client.put client ~path ~body () with
78 | Error e -> Error e
79 | Ok resp_body ->
80 match Client.decode_response Messages.send_response_jsont resp_body with
81 | Error e -> Error e
82 | Ok resp -> Ok resp.event_id
83
84(** Send a call answer.
85
86 @param room_id The room
87 @param call_id The call ID
88 @param answer The SDP answer
89 @param version Call version
90 @param party_id Party ID for version 1 calls *)
91let send_answer client ~room_id ~call_id ~answer
92 ?(version = 1) ?party_id () =
93 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in
94 let txn_id = Matrix_proto.Id.Transaction_id.(generate () |> to_string) in
95 let path = Printf.sprintf "/rooms/%s/send/m.call.answer/%s"
96 (Uri.pct_encode room_id_str)
97 (Uri.pct_encode txn_id)
98 in
99 let content : Matrix_proto.Event.Call_answer_content.t = {
100 call_id;
101 party_id;
102 version;
103 answer;
104 } in
105 match Client.encode_body Matrix_proto.Event.Call_answer_content.jsont content with
106 | Error e -> Error e
107 | Ok body ->
108 match Client.put client ~path ~body () with
109 | Error e -> Error e
110 | Ok resp_body ->
111 match Client.decode_response Messages.send_response_jsont resp_body with
112 | Error e -> Error e
113 | Ok resp -> Ok resp.event_id
114
115(** Hang up a call.
116
117 @param room_id The room
118 @param call_id The call ID
119 @param reason Optional hangup reason
120 @param version Call version
121 @param party_id Party ID for version 1 calls *)
122let send_hangup client ~room_id ~call_id ?reason
123 ?(version = 1) ?party_id () =
124 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in
125 let txn_id = Matrix_proto.Id.Transaction_id.(generate () |> to_string) in
126 let path = Printf.sprintf "/rooms/%s/send/m.call.hangup/%s"
127 (Uri.pct_encode room_id_str)
128 (Uri.pct_encode txn_id)
129 in
130 let content : Matrix_proto.Event.Call_hangup_content.t = {
131 call_id;
132 party_id;
133 version;
134 reason;
135 } in
136 match Client.encode_body Matrix_proto.Event.Call_hangup_content.jsont content with
137 | Error e -> Error e
138 | Ok body ->
139 match Client.put client ~path ~body () with
140 | Error e -> Error e
141 | Ok resp_body ->
142 match Client.decode_response Messages.send_response_jsont resp_body with
143 | Error e -> Error e
144 | Ok resp -> Ok resp.event_id
145
146(** Reject an incoming call (same as hangup but with different semantics). *)
147let reject_call client ~room_id ~call_id ?(version = 1) ?party_id () =
148 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in
149 let txn_id = Matrix_proto.Id.Transaction_id.(generate () |> to_string) in
150 let path = Printf.sprintf "/rooms/%s/send/m.call.reject/%s"
151 (Uri.pct_encode room_id_str)
152 (Uri.pct_encode txn_id)
153 in
154 (* m.call.reject has the same structure as m.call.hangup *)
155 let content : Matrix_proto.Event.Call_hangup_content.t = {
156 call_id;
157 party_id;
158 version;
159 reason = None;
160 } in
161 match Client.encode_body Matrix_proto.Event.Call_hangup_content.jsont content with
162 | Error e -> Error e
163 | Ok body ->
164 match Client.put client ~path ~body () with
165 | Error e -> Error e
166 | Ok resp_body ->
167 match Client.decode_response Messages.send_response_jsont resp_body with
168 | Error e -> Error e
169 | Ok resp -> Ok resp.event_id
170
171(** TURN server credentials *)
172type turn_server = {
173 username : string;
174 password : string;
175 uris : string list;
176 ttl : int;
177}
178
179let turn_server_jsont =
180 Jsont.Object.(
181 map (fun username password uris ttl ->
182 { username; password; uris; ttl })
183 |> mem "username" Jsont.string ~enc:(fun t -> t.username)
184 |> mem "password" Jsont.string ~enc:(fun t -> t.password)
185 |> mem "uris" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun t -> t.uris)
186 |> mem "ttl" Jsont.int ~enc:(fun t -> t.ttl)
187 |> finish)
188
189(** Get TURN server credentials from the homeserver. *)
190let get_turn_server client =
191 match Client.get client ~path:"/voip/turnServer" () with
192 | Error e -> Error e
193 | Ok body -> Client.decode_response turn_server_jsont body