From 58bc94bc7497f20b93ec302fc8f4cfd99ee8b135 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:22:10 +0400 Subject: [PATCH] core: carry ICE restart session in call extra info (#7718) --- docs/protocol/simplex-chat.md | 4 +++- docs/protocol/simplex-chat.schema.json | 3 +++ src/Simplex/Chat/Call.hs | 3 ++- tests/ChatTests/Direct.hs | 7 +++++++ tests/ProtocolTests.hs | 6 ++++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/protocol/simplex-chat.md b/docs/protocol/simplex-chat.md index 8cd655d0c0..a95e7ee75f 100644 --- a/docs/protocol/simplex-chat.md +++ b/docs/protocol/simplex-chat.md @@ -286,7 +286,9 @@ These message are used for WebRTC calls: 3. `x.call.answer`: to continue with call connection the initiating clients must reply with `x.call.answer` message. This message contains WebRTC answer and collected ICE candidates. Additional ICE candidates can be sent in `x.call.extra` message. -4. `x.call.end` message is sent to notify the other party that the call is terminated. +4. `x.call.extra` with `rtcSession` property: when an established connection is lost, the client that sent `x.call.offer` restarts ICE and sends the new offer in `rtcSession` of `x.call.extra`. The other client replies with the answer in `rtcSession` of `x.call.extra`. Clients that do not support it ignore this property, and the call ends when reconnection times out. + +5. `x.call.end` message is sent to notify the other party that the call is terminated. Call messages have their own version, independent of the chat protocol version. The initiating client sends the range of call versions it supports in `callVRange` property of `x.call.inv`. The receiving client chooses the highest version supported by both clients and sends it in `callVersion` property of `x.call.offer`. The initiating client rejects the offer if this version is not in the range it sent. A missing range or version means version 1. diff --git a/docs/protocol/simplex-chat.schema.json b/docs/protocol/simplex-chat.schema.json index 5fb4794094..a9bb2d8735 100644 --- a/docs/protocol/simplex-chat.schema.json +++ b/docs/protocol/simplex-chat.schema.json @@ -364,6 +364,9 @@ "rtcExtraInfo": { "properties": { "rtcIceCandidates": {"type": "string"} + }, + "optionalProperties": { + "rtcSession": {"type": "string"} } } } diff --git a/src/Simplex/Chat/Call.hs b/src/Simplex/Chat/Call.hs index f64cd8e736..fec5473996 100644 --- a/src/Simplex/Chat/Call.hs +++ b/src/Simplex/Chat/Call.hs @@ -230,7 +230,8 @@ data WebRTCSession = WebRTCSession deriving (Eq, Show) data WebRTCExtraInfo = WebRTCExtraInfo - { rtcIceCandidates :: Text -- LZW compressed JSON encoding of array of ICE candidates + { rtcSession :: Maybe Text, -- LZW compressed JSON encoding of ICE restart offer or answer + rtcIceCandidates :: Text -- LZW compressed JSON encoding of array of ICE candidates } deriving (Eq, Show) diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 7300fcff22..3ce8f97130 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1362,6 +1362,13 @@ testNegotiateCall = bob <## "ok" threadDelay 100000 bob #$> ("/_get chat @2 count=100", callChat, chatFeatures <> [(0, "incoming call: in progress")]) + -- either party can restart ICE of a connected call + bob ##> "/_call extra @2 {\"rtcSession\":\"{}\",\"rtcIceCandidates\":\"[]\"}" + bob <## "ok" + alice <## "call extra info from bob" + alice ##> "/_call extra @2 {\"rtcSession\":\"{}\",\"rtcIceCandidates\":\"[]\"}" + alice <## "ok" + bob <## "call extra info from alice" -- either party can end the call bob ##> "/_call end @2" bob <## "ok" diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index fb9e8317c3..79415071f7 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -433,5 +433,11 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do it "x.call.offer with call version" $ "{\"v\":\"9\",\"event\":\"x.call.offer\",\"params\":{\"callId\":\"AQIDBA==\",\"offer\":{\"callType\":{\"media\":\"video\",\"capabilities\":{\"encryption\":true}},\"rtcSession\":{\"rtcSession\":\"{}\",\"rtcIceCandidates\":\"[]\"},\"callVersion\":2}}}" #==# XCallOffer (CallId "\1\2\3\4") CallOffer {callType = defaultCallType, rtcSession = testRTCSession, callDhPubKey = Nothing, callVersion = Just $ VersionCall 2} + it "x.call.extra without session" $ + "{\"v\":\"9\",\"event\":\"x.call.extra\",\"params\":{\"callId\":\"AQIDBA==\",\"extra\":{\"rtcExtraInfo\":{\"rtcIceCandidates\":\"[]\"}}}}" + #==# XCallExtra (CallId "\1\2\3\4") CallExtraInfo {rtcExtraInfo = WebRTCExtraInfo {rtcSession = Nothing, rtcIceCandidates = "[]"}} + it "x.call.extra with ICE restart session" $ + "{\"v\":\"9\",\"event\":\"x.call.extra\",\"params\":{\"callId\":\"AQIDBA==\",\"extra\":{\"rtcExtraInfo\":{\"rtcSession\":\"{}\",\"rtcIceCandidates\":\"[]\"}}}}" + #==# XCallExtra (CallId "\1\2\3\4") CallExtraInfo {rtcExtraInfo = WebRTCExtraInfo {rtcSession = Just "{}", rtcIceCandidates = "[]"}} where testRTCSession = WebRTCSession {rtcSession = "{}", rtcIceCandidates = "[]"}