From 6ac7eac3012c51c11dd7f001ccccb3c328e7bf4f Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Wed, 25 Sep 2024 20:49:40 -0700 Subject: [PATCH 01/18] WiP --- go.mod | 39 +++++++-------- go.sum | 94 ++++++++++++++++-------------------- pkg/siptest/client.go | 78 +++++++++++++++++++++++------- test/integration/sip_test.go | 37 +++++++++++--- 4 files changed, 151 insertions(+), 97 deletions(-) diff --git a/go.mod b/go.mod index dca4461..a4d9136 100644 --- a/go.mod +++ b/go.mod @@ -12,23 +12,23 @@ require ( github.com/icholy/digest v0.1.23 github.com/jfreymuth/oggvorbis v1.0.5 github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 - github.com/livekit/mediatransportutil v0.0.0-20240625074155-301bb4a816b7 + github.com/livekit/mediatransportutil v0.0.0-20240730083616-559fa5ece598 github.com/livekit/protocol v1.22.1-0.20240920184753-71b9c184e5c8 github.com/livekit/psrpc v0.5.3-0.20240616012458-ac39c8549a0a - github.com/livekit/server-sdk-go/v2 v2.2.1-0.20240726160203-3f7f396734c3 + github.com/livekit/server-sdk-go/v2 v2.2.2-0.20240920185319-a83c50186010 github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 github.com/ory/dockertest/v3 v3.10.0 - github.com/pion/interceptor v0.1.29 - github.com/pion/rtp v1.8.6 + github.com/pion/interceptor v0.1.30 + github.com/pion/rtp v1.8.9 github.com/pion/sdp/v3 v3.0.9 - github.com/pion/webrtc/v3 v3.2.47 + github.com/pion/webrtc/v3 v3.3.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.1 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.2 github.com/zaf/resample v1.5.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 google.golang.org/protobuf v1.34.2 gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 gopkg.in/yaml.v3 v3.0.1 @@ -86,17 +86,17 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.12 // indirect - github.com/pion/datachannel v1.5.6 // indirect - github.com/pion/dtls/v2 v2.2.11 // indirect - github.com/pion/ice/v2 v2.3.29 // indirect + github.com/pion/datachannel v1.5.8 // indirect + github.com/pion/dtls/v2 v2.2.12 // indirect + github.com/pion/ice/v2 v2.3.34 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/mdns v0.0.12 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.14 // indirect - github.com/pion/sctp v1.8.16 // indirect - github.com/pion/srtp/v2 v2.0.18 // indirect + github.com/pion/sctp v1.8.19 // indirect + github.com/pion/srtp/v2 v2.0.20 // indirect github.com/pion/stun v0.6.1 // indirect - github.com/pion/transport/v2 v2.2.5 // indirect + github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/turn/v2 v2.1.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect @@ -108,6 +108,7 @@ require ( github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/twitchtv/twirp v8.1.3+incompatible // indirect + github.com/wlynxg/anet v0.0.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -117,13 +118,13 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f // indirect google.golang.org/grpc v1.65.0 // indirect diff --git a/go.sum b/go.sum index 9789ad6..1dfe504 100644 --- a/go.sum +++ b/go.sum @@ -122,14 +122,14 @@ github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58= github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ= -github.com/livekit/mediatransportutil v0.0.0-20240625074155-301bb4a816b7 h1:F1L8inJoynwIAYpZENNYS+1xHJMF5RFRorsnAlcxfSY= -github.com/livekit/mediatransportutil v0.0.0-20240625074155-301bb4a816b7/go.mod h1:jwKUCmObuiEDH0iiuJHaGMXwRs3RjrB4G6qqgkr/5oE= +github.com/livekit/mediatransportutil v0.0.0-20240730083616-559fa5ece598 h1:yLlkHk2feSLHstD9n4VKg7YEBR4rLODTI4WE8gNBEnQ= +github.com/livekit/mediatransportutil v0.0.0-20240730083616-559fa5ece598/go.mod h1:jwKUCmObuiEDH0iiuJHaGMXwRs3RjrB4G6qqgkr/5oE= github.com/livekit/protocol v1.22.1-0.20240920184753-71b9c184e5c8 h1:Tt/INVn5HOgyy/OknLzEK46sWDKRnQ+NvsjFkMZDbWc= github.com/livekit/protocol v1.22.1-0.20240920184753-71b9c184e5c8/go.mod h1:AFuwk3+uIWFeO5ohKjx5w606Djl940+wktaZ441VoCI= github.com/livekit/psrpc v0.5.3-0.20240616012458-ac39c8549a0a h1:EQAHmcYEGlc6V517cQ3Iy0+jHgP6+tM/B4l2vGuLpQo= github.com/livekit/psrpc v0.5.3-0.20240616012458-ac39c8549a0a/go.mod h1:CQUBSPfYYAaevg1TNCc6/aYsa8DJH4jSRFdCeSZk5u0= -github.com/livekit/server-sdk-go/v2 v2.2.1-0.20240726160203-3f7f396734c3 h1:I/XGsUSnB4ajiWNokzpuRLl8VirnaYyLPwKdjHxOHiE= -github.com/livekit/server-sdk-go/v2 v2.2.1-0.20240726160203-3f7f396734c3/go.mod h1:slEHd/HaPGeHdDVj2O8uZVk/NcAj8bSCdMT8dRwntmk= +github.com/livekit/server-sdk-go/v2 v2.2.2-0.20240920185319-a83c50186010 h1:skq+yZbTLm2yUIJ2eEyzQR+LYZEOIl9uFNAPJMQXaVs= +github.com/livekit/server-sdk-go/v2 v2.2.2-0.20240920185319-a83c50186010/go.mod h1:LjZPbNnUGUIUeAqjQb2CozRK3yOXc07pLsLyK1VqdoU= github.com/livekit/sipgo v0.13.2-0.20240820220653-befde351a575 h1:9On8gLpup6Hs9f679MO2jNtLZzpB901vSRKXzVmRP4w= github.com/livekit/sipgo v0.13.2-0.20240820220653-befde351a575/go.mod h1:BY01/cjS7NQGCKAvgD+4FtIJ/8hPXwcDKnSan5uvklc= github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= @@ -156,15 +156,15 @@ github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg= -github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4= +github.com/pion/datachannel v1.5.8 h1:ph1P1NsGkazkjrvyMfhRBUAWMxugJjq2HfQifaOoSNo= +github.com/pion/datachannel v1.5.8/go.mod h1:PgmdpoaNBLX9HNzNClmdki4DYW5JtI7Yibu8QzbL3tI= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= -github.com/pion/dtls/v2 v2.2.11 h1:9U/dpCYl1ySttROPWJgqWKEylUdT0fXp/xst6JwY5Ks= -github.com/pion/dtls/v2 v2.2.11/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/ice/v2 v2.3.29 h1:nKSU0Kb7F0Idfaz15EwGB1GbOxBlONXnWma5p1lOFcE= -github.com/pion/ice/v2 v2.3.29/go.mod h1:KXJJcZK7E8WzrBEYnV4UtqEZsGeWfHxsNqhVcVvgjxw= -github.com/pion/interceptor v0.1.29 h1:39fsnlP1U8gw2JzOFWdfCU82vHvhW9o0rZnZF56wF+M= -github.com/pion/interceptor v0.1.29/go.mod h1:ri+LGNjRUc5xUNtDEPzfdkmSqISixVTBF/z/Zms/6T4= +github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= +github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= +github.com/pion/ice/v2 v2.3.34 h1:Ic1ppYCj4tUOcPAp76U6F3fVrlSw8A9JtRXLqw6BbUM= +github.com/pion/ice/v2 v2.3.34/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= +github.com/pion/interceptor v0.1.30 h1:au5rlVHsgmxNi+v/mjOPazbW1SHzfx7/hYOEYQnUcxA= +github.com/pion/interceptor v0.1.30/go.mod h1:RQuKT5HTdkP2Fi0cuOS5G5WNymTjzXaGF75J4k7z2nc= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= @@ -175,31 +175,29 @@ github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9 github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE= github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/rtp v1.8.6 h1:MTmn/b0aWWsAzux2AmP8WGllusBVw4NPYPVFFd7jUPw= -github.com/pion/rtp v1.8.6/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/sctp v1.8.13/go.mod h1:YKSgO/bO/6aOMP9LCie1DuD7m+GamiK2yIiPM6vH+GA= -github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY= -github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE= +github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk= +github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/sctp v1.8.19 h1:2CYuw+SQ5vkQ9t0HdOPccsCz1GQMDuVy5PglLgKVBW8= +github.com/pion/sctp v1.8.19/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE= github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= -github.com/pion/srtp/v2 v2.0.18 h1:vKpAXfawO9RtTRKZJbG4y0v1b11NZxQnxRl85kGuUlo= -github.com/pion/srtp/v2 v2.0.18/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= +github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk= +github.com/pion/srtp/v2 v2.0.20/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= -github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= -github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc= -github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= +github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= -github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= -github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= +github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= +github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= -github.com/pion/webrtc/v3 v3.2.47 h1:2DrJ7YnxiZVcmVA+HRyyACCSYvVW8E1YpOvF/EXeRYI= -github.com/pion/webrtc/v3 v3.2.47/go.mod h1:g7pwdiN9Gj2zZZlSTW5XC7OzrgHS9QzRM0y+O2jtjVg= +github.com/pion/webrtc/v3 v3.3.1 h1:VAJr70z+YQ5sNwMhYA7HgRFfu9qlHWKRtNPTU7EY71s= +github.com/pion/webrtc/v3 v3.3.1/go.mod h1:hVmrDJvwhEertRWObeb1xzulzHGeVUoPlWvxdGzcfU0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -243,6 +241,8 @@ github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJX github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/wlynxg/anet v0.0.3 h1:PvR53psxFXstc12jelG6f1Lv4MWqE0tI76/hHGjh9rg= +github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -276,21 +276,19 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -300,20 +298,17 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -328,41 +323,36 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index 296d24e..9a0ab24 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -34,6 +34,7 @@ import ( "github.com/at-wat/ebml-go/webm" "github.com/emiago/sipgo" "github.com/emiago/sipgo/sip" + "github.com/frostbyte73/core" "github.com/icholy/digest" "github.com/pion/sdp/v3" @@ -56,6 +57,8 @@ type ClientConfig struct { OnBye func() OnMediaTimeout func() OnDTMF func(ev dtmf.Event) + OnRefer func(to string) + NotifyRequests chan<- *sip.Request Codec string } @@ -146,6 +149,17 @@ func NewClient(id string, conf ClientConfig) (*Client, error) { default: } }) + cli.sipServer.OnRefer(func(req *sip.Request, tx sip.ServerTransaction) { + if c.conf.OnRefer != nil { + h := req.GetHeader("TransferTo") + if h != nil { + c.conf.OnRefer(h.Value) + } + } + + _ = tx.Respond(sip.NewResponseFromRequest(req, 202, "ACCEPTED", nil)) + tx.Terminate() + }) return cli, nil } @@ -168,7 +182,7 @@ type Client struct { inviteReq *sip.Request inviteResp *sip.Response recordHandler atomic.Pointer[rtp.Handler] - closed atomic.Bool + closed core.Fuse } func (c *Client) LocalIP() string { @@ -183,23 +197,22 @@ func (c *Client) RemoteHeaders() []sip.Header { } func (c *Client) Close() { - if !c.closed.CompareAndSwap(false, true) { - return - } - if c.mediaConn != nil { - c.mediaConn.Close() - } - if c.inviteResp != nil { - c.sendBye() - c.inviteReq = nil - c.inviteResp = nil - } - if c.sipClient != nil { - c.sipClient.Close() - } - if c.sipServer != nil { - c.sipServer.Close() - } + c.closed.Once(func() { + if c.mediaConn != nil { + c.mediaConn.Close() + } + if c.inviteResp != nil { + c.sendBye() + c.inviteReq = nil + c.inviteResp = nil + } + if c.sipClient != nil { + c.sipClient.Close() + } + if c.sipServer != nil { + c.sipServer.Close() + } + }) } func (c *Client) setupRTPReceiver() { @@ -297,6 +310,8 @@ func (c *Client) Dial(ip string, uri string, number string, headers map[string]s break } + go c.notifyLoop() + if contactHeader, ok := resp.Contact(); ok { req.Recipient = &contactHeader.Address if req.Recipient.Port == 0 { @@ -356,6 +371,33 @@ func (c *Client) attemptInvite(ip, uri, number string, offer []byte, authHeader return req, resp, err } +func (c *Client) notifyLoop() { + for { + select { + case req, ok := <-c.conf.NotifyRequests: + if !ok { + return + } + tx, err := c.sipClient.TransactionRequest(req) + if err != nil { + panic(err) + } + defer tx.Terminate() + + resp, err := getResponse(tx) + if err != nil { + panic(err) + } + if resp.StatusCode != sip.StatusOK { + panic("NOTIFY response was not 200/OK") + } + + case <-c.closed.Watch(): + return + } + } +} + func (c *Client) sendBye() { c.log.Debug("sending bye") req := sip.NewByeRequest(c.inviteReq, c.inviteResp, nil) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index fea1e72..df2f1b4 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -225,11 +225,11 @@ func (s *SIPServer) DeleteDispatch(t testing.TB, id string) { } } -func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event)) *siptest.Client { - return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF) +func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(to string)) *siptest.Client { + return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF, onRefer, notifyRequests) } -func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event)) *siptest.Client { +func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(to string)) *siptest.Client { cconf := siptest.ClientConfig{ // IP: dockerBridgeIP, Number: number, @@ -240,7 +240,9 @@ func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number stri OnMediaTimeout: func() { t.Fatal("media timeout from server to test client") }, - OnDTMF: onDTMF, + OnDTMF: onDTMF, + OnRefer: onRefer, + NotifyRequests: notifyRequests, } cli, err := siptest.NewClient(id, cconf) @@ -265,18 +267,21 @@ func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number stri const ( serverNumber = "+000000000" clientNumber = "+111111111" + transferNumber = "+222222222" participantsJoinTimeout = 5 * time.Second participantsJoinWithPinTimeout = participantsJoinTimeout + 5*time.Second participantsLeaveTimeout = 3 * time.Second webrtcSetupDelay = 5 * time.Second + notifyIntervalDelay = 100 * time.Millisecond ) func TestSIPJoinOpenRoom(t *testing.T) { lk := runLiveKit(t) var ( - dmu sync.Mutex - dtmfOut string - dtmfIn string + dmu sync.Mutex + dtmfOut string + dtmfIn string + transferTo string ) const ( clientID = "test-cli" @@ -311,12 +316,16 @@ func TestSIPJoinOpenRoom(t *testing.T) { customAttr: customVal, }) + notifyRequests := make(chan *sip.Request) + cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ "X-LK-Inbound": "1", }, func(ev dtmf.Event) { dmu.Lock() defer dmu.Unlock() dtmfIn += string(ev.Digit) + }, func(to string) { + transferTo = to }) h := sip.Headers(cli.RemoteHeaders()).GetHeader("X-LK-Accepted") @@ -362,7 +371,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { require.NoError(t, err) require.Eventually(t, func() bool { - dmu.Lock() + dmu.Lock(22222222) defer dmu.Unlock() return dtmfOut == dtmfDigits }, 5*time.Second, time.Second/2) @@ -376,6 +385,18 @@ func TestSIPJoinOpenRoom(t *testing.T) { return dtmfIn == "4567" }, 5*time.Second, time.Second/2) + _.err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ + RoomName: "test-open", + ParticipantIdentity: "sip_" + clientNumber, + TransferTo: "tel:" + transferNumber, + }) + + time.Sleep(notifyIntervalDelay) + notifyRequests <- newNotifyRequest(100, "Trying") + + time.Sleep(notifyIntervalDelay) + notifyRequests <- newNotifyRequest(200, "OK") + cli.Close() r.Disconnect() From 456eb06bc915dbe1aa1fb3eb9ef94487d653c038 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 14:13:46 -0700 Subject: [PATCH 02/18] Test --- pkg/siptest/client.go | 133 +++++++++++++++++++++++++---------- test/integration/sip_test.go | 55 +++++++++------ 2 files changed, 131 insertions(+), 57 deletions(-) diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index 9a0ab24..2b419d9 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -57,8 +57,7 @@ type ClientConfig struct { OnBye func() OnMediaTimeout func() OnDTMF func(ev dtmf.Event) - OnRefer func(to string) - NotifyRequests chan<- *sip.Request + OnRefer func(req *sip.Request) Codec string } @@ -150,11 +149,8 @@ func NewClient(id string, conf ClientConfig) (*Client, error) { } }) cli.sipServer.OnRefer(func(req *sip.Request, tx sip.ServerTransaction) { - if c.conf.OnRefer != nil { - h := req.GetHeader("TransferTo") - if h != nil { - c.conf.OnRefer(h.Value) - } + if conf.OnRefer != nil { + conf.OnRefer(req) } _ = tx.Respond(sip.NewResponseFromRequest(req, 202, "ACCEPTED", nil)) @@ -182,6 +178,7 @@ type Client struct { inviteReq *sip.Request inviteResp *sip.Response recordHandler atomic.Pointer[rtp.Handler] + lastCSeq atomic.Uint32 closed core.Fuse } @@ -310,8 +307,6 @@ func (c *Client) Dial(ip string, uri string, number string, headers map[string]s break } - go c.notifyLoop() - if contactHeader, ok := resp.Contact(); ok { req.Recipient = &contactHeader.Address if req.Recipient.Port == 0 { @@ -337,6 +332,11 @@ func (c *Client) Dial(ip string, uri string, number string, headers map[string]s } c.inviteReq = req c.inviteResp = resp + + if h, ok := req.CSeq(); ok { + c.lastCSeq.Store(h.SeqNo) + } + c.mediaConn.SetDestAddr(dstAddr) c.log.Debug("client connected", "media-dst", dstAddr) return nil @@ -371,38 +371,15 @@ func (c *Client) attemptInvite(ip, uri, number string, offer []byte, authHeader return req, resp, err } -func (c *Client) notifyLoop() { - for { - select { - case req, ok := <-c.conf.NotifyRequests: - if !ok { - return - } - tx, err := c.sipClient.TransactionRequest(req) - if err != nil { - panic(err) - } - defer tx.Terminate() - - resp, err := getResponse(tx) - if err != nil { - panic(err) - } - if resp.StatusCode != sip.StatusOK { - panic("NOTIFY response was not 200/OK") - } - - case <-c.closed.Watch(): - return - } - } -} - func (c *Client) sendBye() { c.log.Debug("sending bye") req := sip.NewByeRequest(c.inviteReq, c.inviteResp, nil) req.AppendHeader(sip.NewHeader("User-Agent", "LiveKit")) + cseq := c.lastCSeq.Add(1) + cseqH, _ := req.CSeq() + cseqH.SeqNo = cseq + tx, err := c.sipClient.TransactionRequest(req) if err != nil { return @@ -423,6 +400,90 @@ func (c *Client) SendDTMF(digits string) error { return dtmf.Write(context.Background(), c.audioOut, c.mediaDTMF, c.mediaAudio.GetCurrentTimestamp(), digits) } +func (c *Client) SendNotify(eventReq *sip.Request, notifyStatus string) error { + var recipient *sip.Uri + + if contact, ok := eventReq.Contact(); ok { + recipient = &contact.Address + } else if from, ok := eventReq.From(); ok { + recipient = &from.Address + } else { + errors.New("missing destination address") + } + + req := sip.NewRequest(sip.NOTIFY, recipient) + + req.SipVersion = eventReq.SipVersion + sip.CopyHeaders("Via", eventReq, req) + + if len(eventReq.GetHeaders("Route")) > 0 { + sip.CopyHeaders("Route", eventReq, req) + } else { + hdrs := c.inviteResp.GetHeaders("Record-Route") + for i := len(hdrs) - 1; i >= 0; i-- { + rrh, ok := hdrs[i].(*sip.RecordRouteHeader) + if !ok { + continue + } + + h := rrh.Clone() + req.AppendHeader(h) + } + } + + maxForwardsHeader := sip.MaxForwardsHeader(70) + req.AppendHeader(&maxForwardsHeader) + + if to, ok := eventReq.To(); ok { + req.AppendHeader((*sip.FromHeader)(to)) + } else { + return errors.New("missing To header in NOTIFY request") + } + + if from, ok := eventReq.From(); ok { + req.AppendHeader((*sip.ToHeader)(from)) + } else { + return errors.New("missing From header in NOTIFY request") + } + + if callId, ok := eventReq.CallID(); ok { + req.AppendHeader(callId) + } + + ct := sip.ContentTypeHeader("message/sipfrag") + req.AppendHeader(&ct) + + cseq := c.lastCSeq.Add(1) + cseqH := &sip.CSeqHeader{ + SeqNo: cseq, + MethodName: sip.NOTIFY, + } + req.AppendHeader(cseqH) + + req.SetTransport(eventReq.Transport()) + req.SetSource(eventReq.Destination()) + req.SetDestination(eventReq.Source()) + + req.SetBody([]byte(notifyStatus)) + + tx, err := c.sipClient.TransactionRequest(req) + if err != nil { + return err + } + defer tx.Terminate() + + resp, err := getResponse(tx) + if err != nil { + return err + } + + if resp.StatusCode != sip.StatusOK { + return fmt.Errorf("NOTIFY failed with status %d", resp.StatusCode) + } + + return nil +} + func (c *Client) createOffer() ([]byte, error) { sessionId := rand.Uint64() diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index df2f1b4..dda3690 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -225,11 +225,11 @@ func (s *SIPServer) DeleteDispatch(t testing.TB, id string) { } } -func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(to string)) *siptest.Client { - return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF, onRefer, notifyRequests) +func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sip.Request)) *siptest.Client { + return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF, onRefer) } -func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(to string)) *siptest.Client { +func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sip.Request)) *siptest.Client { cconf := siptest.ClientConfig{ // IP: dockerBridgeIP, Number: number, @@ -240,9 +240,8 @@ func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number stri OnMediaTimeout: func() { t.Fatal("media timeout from server to test client") }, - OnDTMF: onDTMF, - OnRefer: onRefer, - NotifyRequests: notifyRequests, + OnDTMF: onDTMF, + OnRefer: onRefer, } cli, err := siptest.NewClient(id, cconf) @@ -278,10 +277,10 @@ const ( func TestSIPJoinOpenRoom(t *testing.T) { lk := runLiveKit(t) var ( - dmu sync.Mutex - dtmfOut string - dtmfIn string - transferTo string + dmu sync.Mutex + dtmfOut string + dtmfIn string + referRequest *sip.Request ) const ( clientID = "test-cli" @@ -324,8 +323,10 @@ func TestSIPJoinOpenRoom(t *testing.T) { dmu.Lock() defer dmu.Unlock() dtmfIn += string(ev.Digit) - }, func(to string) { - transferTo = to + }, func(req *sip.Request) { + dmu.Lock() + defer dmu.Unlock() + referRequest = req }) h := sip.Headers(cli.RemoteHeaders()).GetHeader("X-LK-Accepted") @@ -391,19 +392,31 @@ func TestSIPJoinOpenRoom(t *testing.T) { TransferTo: "tel:" + transferNumber, }) - time.Sleep(notifyIntervalDelay) - notifyRequests <- newNotifyRequest(100, "Trying") + require.Eventually(t, func() bool { + dmu.Lock() + defer dmu.Unlock() + + return referRequest != nil + + }, 5*time.Second, time.Second/2) + + require.Equal(t, referRequest.cancel) time.Sleep(notifyIntervalDelay) - notifyRequests <- newNotifyRequest(200, "OK") + err = cli.SendNotify("100 Trying") + require.NoError(err) - cli.Close() - r.Disconnect() + time.Sleep(notifyIntervalDelay) + err = cli.SendNotify("200 OK") - // SIP participant must disconnect from LK room on hangup. + // SIP participant should have left ctx, cancel = context.WithTimeout(context.Background(), participantsLeaveTimeout) defer cancel() lk.ExpectRoomWithParticipants(t, ctx, roomName, nil) + + cli.Close() + r.Disconnect() + } func TestSIPJoinPinRoom(t *testing.T) { @@ -447,7 +460,7 @@ func TestSIPJoinPinRoom(t *testing.T) { cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ "X-LK-Inbound": "1", - }, nil) + }, nil, nil) // Even though we set this header in the dispatch rule, PIN forces us to send response earlier. // Because of this, we can no longer attach attributes from a selected dispatch rule later. @@ -530,7 +543,7 @@ func TestSIPJoinOpenRoomWithPin(t *testing.T) { }) srv.CreateDirectDispatch(t, "test-priv", "1234", "", nil) - cli := runClient(t, nc, clientID, clientNumber, true, nil, nil) + cli := runClient(t, nc, clientID, clientNumber, true, nil, nil, nil) // Send audio, so that we don't trigger media timeout. mctx, mcancel := context.WithCancel(context.Background()) @@ -592,7 +605,7 @@ func TestSIPJoinRoomIndividual(t *testing.T) { rch <- room }() - cli := runClient(t, nc, clientID, clientNumber, false, nil, nil) + cli := runClient(t, nc, clientID, clientNumber, false, nil, nil, nil) // Send audio, so that we don't trigger media timeout. mctx, mcancel := context.WithCancel(context.Background()) From 7ae71ae6e6a18a5098f8e674cb7e7b4e880ad533 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 14:43:38 -0700 Subject: [PATCH 03/18] WiP --- pkg/siptest/client.go | 6 ++++++ test/integration/sip_test.go | 34 +++++++++++++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index 2b419d9..e9f0427 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -464,6 +464,12 @@ func (c *Client) SendNotify(eventReq *sip.Request, notifyStatus string) error { req.SetSource(eventReq.Destination()) req.SetDestination(eventReq.Source()) + if eventCSeq, ok := eventReq.CSeq(); ok { + req.AppendHeader(sip.NewHeader("Event", fmt.Sprintf("refer;id=%d", eventCSeq.SeqNo))) + } else { + return errors.New("missing CSeq header in REFER request") + } + req.SetBody([]byte(notifyStatus)) tx, err := c.sipClient.TransactionRequest(req) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index dda3690..95051f4 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -11,6 +11,9 @@ import ( "testing" "time" + sipgo "github.com/emiago/sipgo/sip" + "github.com/stretchr/testify/require" + "github.com/livekit/mediatransportutil/pkg/rtcconfig" "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/logger" @@ -20,10 +23,6 @@ import ( "github.com/livekit/psrpc" lksdk "github.com/livekit/server-sdk-go/v2" - "github.com/livekit/sip/pkg/stats" - - "github.com/stretchr/testify/require" - "github.com/livekit/sip/pkg/config" "github.com/livekit/sip/pkg/media/dtmf" "github.com/livekit/sip/pkg/media/g711" @@ -31,6 +30,7 @@ import ( "github.com/livekit/sip/pkg/service" "github.com/livekit/sip/pkg/sip" "github.com/livekit/sip/pkg/siptest" + "github.com/livekit/sip/pkg/stats" "github.com/livekit/sip/test/lktest" ) @@ -225,11 +225,11 @@ func (s *SIPServer) DeleteDispatch(t testing.TB, id string) { } } -func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sip.Request)) *siptest.Client { +func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sipgo.Request)) *siptest.Client { return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF, onRefer) } -func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sip.Request)) *siptest.Client { +func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sipgo.Request)) *siptest.Client { cconf := siptest.ClientConfig{ // IP: dockerBridgeIP, Number: number, @@ -280,7 +280,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { dmu sync.Mutex dtmfOut string dtmfIn string - referRequest *sip.Request + referRequest *sipgo.Request ) const ( clientID = "test-cli" @@ -315,15 +315,13 @@ func TestSIPJoinOpenRoom(t *testing.T) { customAttr: customVal, }) - notifyRequests := make(chan *sip.Request) - cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ "X-LK-Inbound": "1", }, func(ev dtmf.Event) { dmu.Lock() defer dmu.Unlock() dtmfIn += string(ev.Digit) - }, func(req *sip.Request) { + }, func(req *sipgo.Request) { dmu.Lock() defer dmu.Unlock() referRequest = req @@ -372,7 +370,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { require.NoError(t, err) require.Eventually(t, func() bool { - dmu.Lock(22222222) + dmu.Lock() defer dmu.Unlock() return dtmfOut == dtmfDigits }, 5*time.Second, time.Second/2) @@ -386,7 +384,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { return dtmfIn == "4567" }, 5*time.Second, time.Second/2) - _.err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ + _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ RoomName: "test-open", ParticipantIdentity: "sip_" + clientNumber, TransferTo: "tel:" + transferNumber, @@ -400,14 +398,16 @@ func TestSIPJoinOpenRoom(t *testing.T) { }, 5*time.Second, time.Second/2) - require.Equal(t, referRequest.cancel) + require.Equal(t, sipgo.REFER, referRequest.Method) + transferTo := referRequest.GetHeader("Refer-To") + require.Equal(t, "tel:"+transferNumber, transferTo) time.Sleep(notifyIntervalDelay) - err = cli.SendNotify("100 Trying") - require.NoError(err) + err = cli.SendNotify(referRequest, "100 Trying") + require.NoError(t, err) time.Sleep(notifyIntervalDelay) - err = cli.SendNotify("200 OK") + err = cli.SendNotify(referRequest, "200 OK") // SIP participant should have left ctx, cancel = context.WithTimeout(context.Background(), participantsLeaveTimeout) @@ -685,7 +685,7 @@ func TestSIPAudio(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - cli := runClientWithCodec(t, nc, strconv.Itoa(i+1), fmt.Sprintf("+%d", 111111111*(i+1)), codec, false, nil, nil) + cli := runClientWithCodec(t, nc, strconv.Itoa(i+1), fmt.Sprintf("+%d", 111111111*(i+1)), codec, false, nil, nil, nil) mu.Lock() clients[i] = cli audios[i] = cli From 09b5a09a806e536182f65e78cd8080127de050a7 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 14:59:10 -0700 Subject: [PATCH 04/18] WiP --- test/integration/sip_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index 95051f4..cfa7b5a 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -400,7 +400,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { require.Equal(t, sipgo.REFER, referRequest.Method) transferTo := referRequest.GetHeader("Refer-To") - require.Equal(t, "tel:"+transferNumber, transferTo) + require.Equal(t, "tel:"+transferNumber, transferTo.Value()) time.Sleep(notifyIntervalDelay) err = cli.SendNotify(referRequest, "100 Trying") From 572497abc1a6f6e1ecb60bb13ba8c77ba64b3e91 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 15:15:14 -0700 Subject: [PATCH 05/18] WiP --- test/integration/sip_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index cfa7b5a..91bc807 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -403,11 +403,11 @@ func TestSIPJoinOpenRoom(t *testing.T) { require.Equal(t, "tel:"+transferNumber, transferTo.Value()) time.Sleep(notifyIntervalDelay) - err = cli.SendNotify(referRequest, "100 Trying") + err = cli.SendNotify(referRequest, "SIP/2.0 100 Trying") require.NoError(t, err) time.Sleep(notifyIntervalDelay) - err = cli.SendNotify(referRequest, "200 OK") + err = cli.SendNotify(referRequest, "SIP/2.0 200 OK") // SIP participant should have left ctx, cancel = context.WithTimeout(context.Background(), participantsLeaveTimeout) From 24e4797933e57a40f62e3e826303c33e812126d7 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 15:43:37 -0700 Subject: [PATCH 06/18] WiP --- test/integration/sip_test.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index 91bc807..a53fe00 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -225,11 +225,11 @@ func (s *SIPServer) DeleteDispatch(t testing.TB, id string) { } } -func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sipgo.Request)) *siptest.Client { - return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF, onRefer) +func runClient(t testing.TB, conf *NumberConfig, id string, number string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onBye func(), onRefer func(req *sipgo.Request)) *siptest.Client { + return runClientWithCodec(t, conf, id, number, "", forcePin, headers, onDTMF, onBye, onRefer) } -func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onRefer func(req *sipgo.Request)) *siptest.Client { +func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number string, codec string, forcePin bool, headers map[string]string, onDTMF func(ev dtmf.Event), onBye func(), onRefer func(req *sipgo.Request)) *siptest.Client { cconf := siptest.ClientConfig{ // IP: dockerBridgeIP, Number: number, @@ -241,6 +241,7 @@ func runClientWithCodec(t testing.TB, conf *NumberConfig, id string, number stri t.Fatal("media timeout from server to test client") }, OnDTMF: onDTMF, + OnBye: onBye, OnRefer: onRefer, } @@ -315,12 +316,16 @@ func TestSIPJoinOpenRoom(t *testing.T) { customAttr: customVal, }) + byeReceived := make(chan struct{}) + cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ "X-LK-Inbound": "1", }, func(ev dtmf.Event) { dmu.Lock() defer dmu.Unlock() dtmfIn += string(ev.Digit) + }, func() { + close(byeReceived) }, func(req *sipgo.Request) { dmu.Lock() defer dmu.Unlock() @@ -409,14 +414,19 @@ func TestSIPJoinOpenRoom(t *testing.T) { time.Sleep(notifyIntervalDelay) err = cli.SendNotify(referRequest, "SIP/2.0 200 OK") - // SIP participant should have left - ctx, cancel = context.WithTimeout(context.Background(), participantsLeaveTimeout) - defer cancel() - lk.ExpectRoomWithParticipants(t, ctx, roomName, nil) + select { + case <-byeReceived: + case <-time.After(participantsLeaveTimeout): + t.Fatal("did not receive bye after notify") + } cli.Close() r.Disconnect() + // SIP participant should have left + ctx, cancel = context.WithTimeout(context.Background(), participantsLeaveTimeout) + defer cancel() + lk.ExpectRoomWithParticipants(t, ctx, roomName, nil) } func TestSIPJoinPinRoom(t *testing.T) { @@ -460,7 +470,7 @@ func TestSIPJoinPinRoom(t *testing.T) { cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ "X-LK-Inbound": "1", - }, nil, nil) + }, nil, nil, nil) // Even though we set this header in the dispatch rule, PIN forces us to send response earlier. // Because of this, we can no longer attach attributes from a selected dispatch rule later. @@ -543,7 +553,7 @@ func TestSIPJoinOpenRoomWithPin(t *testing.T) { }) srv.CreateDirectDispatch(t, "test-priv", "1234", "", nil) - cli := runClient(t, nc, clientID, clientNumber, true, nil, nil, nil) + cli := runClient(t, nc, clientID, clientNumber, true, nil, nil, nil, nil) // Send audio, so that we don't trigger media timeout. mctx, mcancel := context.WithCancel(context.Background()) @@ -605,7 +615,7 @@ func TestSIPJoinRoomIndividual(t *testing.T) { rch <- room }() - cli := runClient(t, nc, clientID, clientNumber, false, nil, nil, nil) + cli := runClient(t, nc, clientID, clientNumber, false, nil, nil, nil, nil) // Send audio, so that we don't trigger media timeout. mctx, mcancel := context.WithCancel(context.Background()) @@ -685,7 +695,7 @@ func TestSIPAudio(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - cli := runClientWithCodec(t, nc, strconv.Itoa(i+1), fmt.Sprintf("+%d", 111111111*(i+1)), codec, false, nil, nil, nil) + cli := runClientWithCodec(t, nc, strconv.Itoa(i+1), fmt.Sprintf("+%d", 111111111*(i+1)), codec, false, nil, nil, nil, nil) mu.Lock() clients[i] = cli audios[i] = cli From 4a6319654c9e65522d8fceb97892a6f1993f97ee Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 16:25:00 -0700 Subject: [PATCH 07/18] more logging --- pkg/sip/inbound.go | 2 ++ pkg/sip/outbound.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 01f4fbe..c8ba2da 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -744,6 +744,8 @@ func (c *inboundCall) transferCall(ctx context.Context, transferTo string) error return err } + c.log.Infow("inbound call tranferred", "transferTo", transferTo) + // This is needed to actually terminate the session before a media timeout c.Close() diff --git a/pkg/sip/outbound.go b/pkg/sip/outbound.go index 7eb206e..7380647 100644 --- a/pkg/sip/outbound.go +++ b/pkg/sip/outbound.go @@ -391,6 +391,8 @@ func (c *outboundCall) transferCall(ctx context.Context, transferTo string) erro return err } + c.log.Infow("outbound l tranferred", "transferTo", transferTo) + // This is needed to actually terminate the session before a media timeout c.CloseWithReason(CallHangup, "call transferred") From b7e3222509e1a2ad3ae130a82d4fe5a84bf8df01 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 21:34:34 -0700 Subject: [PATCH 08/18] debug --- pkg/sip/inbound.go | 2 ++ pkg/sip/protocol.go | 4 ++++ pkg/siptest/client.go | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index c8ba2da..e5be3c4 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -1084,6 +1084,8 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er c.mu.RLock() defer c.mu.RUnlock() + logger.Errorw("HANDLE NOTIFY OBJ", nil, "cseq", cseq, "referCseq", c.referCseq) + if cseq != 0 && cseq != uint32(c.referCseq) { // NOTIFY for a different REFER, skip return nil diff --git a/pkg/sip/protocol.go b/pkg/sip/protocol.go index 83d503c..f3efe8e 100644 --- a/pkg/sip/protocol.go +++ b/pkg/sip/protocol.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/emiago/sipgo/sip" + "github.com/livekit/protocol/logger" "github.com/livekit/psrpc" "github.com/pkg/errors" ) @@ -173,6 +174,8 @@ func handleNotify(req *sip.Request) (method sip.RequestMethod, cseq uint32, stat event := req.GetHeader("Event") var cseq64 uint64 + logger.Errorw("HANDLE NOTIFY Header", nil, "Event", event) + if m := referIdRegexp.FindStringSubmatch(strings.ToLower(event.Value())); len(m) > 0 { // REFER Notify method = sip.REFER @@ -182,6 +185,7 @@ func handleNotify(req *sip.Request) (method sip.RequestMethod, cseq uint32, stat } status, err = parseNotifyBody(string(req.Body())) + logger.Errorw("HANDLE NOTIFY parse", err, "status", status, "cseq", cseq64) if err != nil { return "", 0, 0, err } diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index e9f0427..155a342 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -437,13 +437,13 @@ func (c *Client) SendNotify(eventReq *sip.Request, notifyStatus string) error { if to, ok := eventReq.To(); ok { req.AppendHeader((*sip.FromHeader)(to)) } else { - return errors.New("missing To header in NOTIFY request") + return errors.New("missing To header in REFER request") } if from, ok := eventReq.From(); ok { req.AppendHeader((*sip.ToHeader)(from)) } else { - return errors.New("missing From header in NOTIFY request") + return errors.New("missing From header in REFER request") } if callId, ok := eventReq.CallID(); ok { From ae1c3d43f879d24f526267ec77699a8cb689a911 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 21:47:07 -0700 Subject: [PATCH 09/18] Debug --- pkg/sip/inbound.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index e5be3c4..96823a9 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -1065,6 +1065,7 @@ func (c *sipInbound) transferCall(ctx context.Context, transferTo string) error case <-ctx.Done(): return psrpc.NewErrorf(psrpc.Canceled, "refer canceled") case err := <-c.referDone: + logger.Errorw("TRANSFER DONE", err) if err != nil { return err } @@ -1084,7 +1085,7 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er c.mu.RLock() defer c.mu.RUnlock() - logger.Errorw("HANDLE NOTIFY OBJ", nil, "cseq", cseq, "referCseq", c.referCseq) + logger.Errorw("HANDLE NOTIFY OBJ", nil, "cseq", cseq, "referCseq", c.referCseq, "status", status) if cseq != 0 && cseq != uint32(c.referCseq) { // NOTIFY for a different REFER, skip @@ -1093,12 +1094,15 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er switch { case status >= 100 && status < 200: + logger.Errorw("HANDLE NOTIFY TRYING", nil) // still trying case status == 200: // Success select { case c.referDone <- nil: + logger.Errorw("HANDLE NOTIFY NIL RES", nil) default: + logger.Errorw("HANDLE NOTIFY DEFAULT", nil, "referDone", c.referDone) } default: // Failure From 23e8cd157dd32343ac99f8774cc1a40c0034b84a Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 22:00:40 -0700 Subject: [PATCH 10/18] Debug --- pkg/sip/inbound.go | 2 ++ pkg/siptest/client.go | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 96823a9..58c6819 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -1056,7 +1056,9 @@ func (c *sipInbound) transferCall(ctx context.Context, transferTo string) error c.referCseq = cseq.SeqNo c.mu.Unlock() + logger.Errorw("SENDING REFER", nil) _, err := sendRefer(c, req, c.s.closing.Watch()) + logger.Errorw("SENT REFER", err) if err != nil { return err } diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index 155a342..7225008 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -38,6 +38,7 @@ import ( "github.com/icholy/digest" "github.com/pion/sdp/v3" + "github.com/livekit/protocol/logger" "github.com/livekit/sip/pkg/audiotest" "github.com/livekit/sip/pkg/config" "github.com/livekit/sip/pkg/media" @@ -153,7 +154,8 @@ func NewClient(id string, conf ClientConfig) (*Client, error) { conf.OnRefer(req) } - _ = tx.Respond(sip.NewResponseFromRequest(req, 202, "ACCEPTED", nil)) + err = tx.Respond(sip.NewResponseFromRequest(req, 202, "Accepted", nil)) + logger.Errorw("REFER Respond", err) tx.Terminate() }) From c148dfeea206354938fa3c3126d79bff5e03a0fe Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Thu, 26 Sep 2024 22:10:17 -0700 Subject: [PATCH 11/18] Debug --- pkg/sip/inbound.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 58c6819..97b5b2e 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -1058,13 +1058,14 @@ func (c *sipInbound) transferCall(ctx context.Context, transferTo string) error logger.Errorw("SENDING REFER", nil) _, err := sendRefer(c, req, c.s.closing.Watch()) - logger.Errorw("SENT REFER", err) + logger.Errorw("SENT REFER", err, "referDone", c.referDone) if err != nil { return err } select { case <-ctx.Done(): + logger.Errorw("TRANSFER TIMEOUT", nil) return psrpc.NewErrorf(psrpc.Canceled, "refer canceled") case err := <-c.referDone: logger.Errorw("TRANSFER DONE", err) @@ -1102,7 +1103,7 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er // Success select { case c.referDone <- nil: - logger.Errorw("HANDLE NOTIFY NIL RES", nil) + logger.Errorw("HANDLE NOTIFY NIL RES", nil, "referDone", c.referDone) default: logger.Errorw("HANDLE NOTIFY DEFAULT", nil, "referDone", c.referDone) } From 3e293ea4d46a3961c26cf9052d1a1585078ecc61 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Sep 2024 09:03:22 -0700 Subject: [PATCH 12/18] WiP --- pkg/sip/inbound.go | 8 +++----- pkg/sip/outbound.go | 7 ++++--- pkg/sip/service.go | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 97b5b2e..9c7537c 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -760,7 +760,7 @@ func (s *Server) newInbound(id LocalTag, invite *sip.Request, inviteTx sip.Serve invite: invite, inviteTx: inviteTx, cancelled: make(chan struct{}), - referDone: make(chan error, 1), + referDone: make(chan error), // Do not buffer the channel to avoid reading a result for an old request } c.from, _ = invite.From() if c.from != nil { @@ -1103,16 +1103,14 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er // Success select { case c.referDone <- nil: - logger.Errorw("HANDLE NOTIFY NIL RES", nil, "referDone", c.referDone) - default: - logger.Errorw("HANDLE NOTIFY DEFAULT", nil, "referDone", c.referDone) + case <-time.After(5 * time.Second): } default: // Failure select { // TODO be more specific in the reported error case c.referDone <- psrpc.NewErrorf(psrpc.Canceled, "call transfer failed"): - default: + case <-time.After(5 * time.Second): } } } diff --git a/pkg/sip/outbound.go b/pkg/sip/outbound.go index 7380647..855b023 100644 --- a/pkg/sip/outbound.go +++ b/pkg/sip/outbound.go @@ -22,6 +22,7 @@ import ( "net/netip" "sort" "sync" + "time" "github.com/emiago/sipgo/sip" "github.com/frostbyte73/core" @@ -411,7 +412,7 @@ func (c *Client) newOutbound(id LocalTag, from URI) *sipOutbound { c: c, id: id, from: fromHeader, - referDone: make(chan error, 1), + referDone: make(chan error), // Do not buffer the channel to avoid reading a result for an old request } } @@ -727,14 +728,14 @@ func (c *sipOutbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) e // Success select { case c.referDone <- nil: - default: + case <-time.After(5 * time.Second): } default: // Failure select { // TODO be more specific in the reported error case c.referDone <- psrpc.NewErrorf(psrpc.Canceled, "call transfer failed"): - default: + case <-time.After(5 * time.Second): } } } diff --git a/pkg/sip/service.go b/pkg/sip/service.go index 4126f1e..239ccb6 100644 --- a/pkg/sip/service.go +++ b/pkg/sip/service.go @@ -124,7 +124,7 @@ func (s *Service) CreateSIPParticipantAffinity(ctx context.Context, req *rpc.Int func (s *Service) TransferSIPParticipant(ctx context.Context, req *rpc.InternalTransferSIPParticipantRequest) (*emptypb.Empty, error) { s.log.Infow("transfering SIP call", "callID", req.SipCallId, "transferTo", req.TransferTo) - ctx, done := context.WithTimeout(ctx, 30*time.Second) + ctx, done := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) defer done() // Look for call both in client (outbound) and server (inbound) From c720689b094c2279b430a9fcb2a4a5df19c012e7 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Sep 2024 09:50:07 -0700 Subject: [PATCH 13/18] WiP --- test/integration/sip_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index a53fe00..d5cc06e 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -389,11 +389,15 @@ func TestSIPJoinOpenRoom(t *testing.T) { return dtmfIn == "4567" }, 5*time.Second, time.Second/2) - _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ - RoomName: "test-open", - ParticipantIdentity: "sip_" + clientNumber, - TransferTo: "tel:" + transferNumber, - }) + go func() { + // TransferSIPParticipant is synchronous + _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ + RoomName: "test-open", + ParticipantIdentity: "sip_" + clientNumber, + TransferTo: "tel:" + transferNumber, + }) + require.NoError(t, err) + }() require.Eventually(t, func() bool { dmu.Lock() From 790babc8179cdd6902731a0e695a916a140fab76 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Sep 2024 09:56:39 -0700 Subject: [PATCH 14/18] cleanup --- pkg/sip/inbound.go | 7 ------- pkg/sip/protocol.go | 4 ---- pkg/siptest/client.go | 2 -- 3 files changed, 13 deletions(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 9c7537c..66bb7e1 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -1056,19 +1056,15 @@ func (c *sipInbound) transferCall(ctx context.Context, transferTo string) error c.referCseq = cseq.SeqNo c.mu.Unlock() - logger.Errorw("SENDING REFER", nil) _, err := sendRefer(c, req, c.s.closing.Watch()) - logger.Errorw("SENT REFER", err, "referDone", c.referDone) if err != nil { return err } select { case <-ctx.Done(): - logger.Errorw("TRANSFER TIMEOUT", nil) return psrpc.NewErrorf(psrpc.Canceled, "refer canceled") case err := <-c.referDone: - logger.Errorw("TRANSFER DONE", err) if err != nil { return err } @@ -1088,8 +1084,6 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er c.mu.RLock() defer c.mu.RUnlock() - logger.Errorw("HANDLE NOTIFY OBJ", nil, "cseq", cseq, "referCseq", c.referCseq, "status", status) - if cseq != 0 && cseq != uint32(c.referCseq) { // NOTIFY for a different REFER, skip return nil @@ -1097,7 +1091,6 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er switch { case status >= 100 && status < 200: - logger.Errorw("HANDLE NOTIFY TRYING", nil) // still trying case status == 200: // Success diff --git a/pkg/sip/protocol.go b/pkg/sip/protocol.go index f3efe8e..83d503c 100644 --- a/pkg/sip/protocol.go +++ b/pkg/sip/protocol.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/emiago/sipgo/sip" - "github.com/livekit/protocol/logger" "github.com/livekit/psrpc" "github.com/pkg/errors" ) @@ -174,8 +173,6 @@ func handleNotify(req *sip.Request) (method sip.RequestMethod, cseq uint32, stat event := req.GetHeader("Event") var cseq64 uint64 - logger.Errorw("HANDLE NOTIFY Header", nil, "Event", event) - if m := referIdRegexp.FindStringSubmatch(strings.ToLower(event.Value())); len(m) > 0 { // REFER Notify method = sip.REFER @@ -185,7 +182,6 @@ func handleNotify(req *sip.Request) (method sip.RequestMethod, cseq uint32, stat } status, err = parseNotifyBody(string(req.Body())) - logger.Errorw("HANDLE NOTIFY parse", err, "status", status, "cseq", cseq64) if err != nil { return "", 0, 0, err } diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index 7225008..b46d48e 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -38,7 +38,6 @@ import ( "github.com/icholy/digest" "github.com/pion/sdp/v3" - "github.com/livekit/protocol/logger" "github.com/livekit/sip/pkg/audiotest" "github.com/livekit/sip/pkg/config" "github.com/livekit/sip/pkg/media" @@ -155,7 +154,6 @@ func NewClient(id string, conf ClientConfig) (*Client, error) { } err = tx.Respond(sip.NewResponseFromRequest(req, 202, "Accepted", nil)) - logger.Errorw("REFER Respond", err) tx.Terminate() }) From d0f2de3675562d255081c2d723d63b1574b468a7 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Sep 2024 10:41:08 -0700 Subject: [PATCH 15/18] WiP --- test/integration/sip_test.go | 78 ++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index d5cc06e..c31aae9 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -316,6 +316,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { customAttr: customVal, }) + transferDone := make(chan struct{}) byeReceived := make(chan struct{}) cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ @@ -397,6 +398,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { TransferTo: "tel:" + transferNumber, }) require.NoError(t, err) + close(transferDone) }() require.Eventually(t, func() bool { @@ -417,6 +419,13 @@ func TestSIPJoinOpenRoom(t *testing.T) { time.Sleep(notifyIntervalDelay) err = cli.SendNotify(referRequest, "SIP/2.0 200 OK") + require.NoError(t, err) + + select { + case <-transferDone: + case <-time.After(participantsLeaveTimeout): + t.Fatal("participant transfer call never completed") + } select { case <-byeReceived: @@ -436,8 +445,9 @@ func TestSIPJoinOpenRoom(t *testing.T) { func TestSIPJoinPinRoom(t *testing.T) { lk := runLiveKit(t) var ( - dmu sync.Mutex - dtmf string + dmu sync.Mutex + dtmf string + referRequest *sipgo.Request ) const ( clientID = "test-cli" @@ -472,9 +482,15 @@ func TestSIPJoinPinRoom(t *testing.T) { customAttr: customVal, }) + transferDone := make(chan struct{}) + cli := runClient(t, nc, clientID, clientNumber, false, map[string]string{ "X-LK-Inbound": "1", - }, nil, nil, nil) + }, nil, nil, func(req *sipgo.Request) { + dmu.Lock() + defer dmu.Unlock() + referRequest = req + }) // Even though we set this header in the dispatch rule, PIN forces us to send response earlier. // Because of this, we can no longer attach attributes from a selected dispatch rule later. @@ -530,6 +546,62 @@ func TestSIPJoinPinRoom(t *testing.T) { return dtmf == dtmfDigits }, 5*time.Second, time.Second/2) + go func() { + // TransferSIPParticipant is synchronous + _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ + RoomName: "test-open", + ParticipantIdentity: "sip_" + clientNumber, + TransferTo: "tel:" + transferNumber, + }) + require.Error(t, err) + close(transferDone) + }() + + require.Eventually(t, func() bool { + dmu.Lock() + defer dmu.Unlock() + + return referRequest != nil + + }, 5*time.Second, time.Second/2) + + require.Equal(t, sipgo.REFER, referRequest.Method) + transferTo := referRequest.GetHeader("Refer-To") + require.Equal(t, "tel:"+transferNumber, transferTo.Value()) + + time.Sleep(notifyIntervalDelay) + err = cli.SendNotify(referRequest, "SIP/2.0 403 Fobidden") + require.NoError(t, err) + + select { + case <-transferDone: + case <-time.After(participantsLeaveTimeout): + t.Fatal("participant transfer call never completed") + } + + // Participants should all still be there + time.Sleep(time.Second) + lk.ExpectRoomWithParticipants(t, ctx, roomName, []lktest.ParticipantInfo{ + {Identity: "test"}, + { + Identity: "sip_" + clientNumber, + Name: "Phone " + clientNumber, + Kind: livekit.ParticipantInfo_SIP, + Metadata: meta, + Attributes: map[string]string{ + "sip.callID": "", // special case + "sip.callStatus": "active", + "sip.trunkPhoneNumber": serverNumber, + "sip.phoneNumber": clientNumber, + "sip.ruleID": nc.RuleID, + "sip.trunkID": nc.TrunkID, + "lktest.id": clientID, + "test.lk.inbound": "1", // from SIP headers + customAttr: customVal, + }, + }, + }) + cli.Close() r.Disconnect() From 55d272da09ebd61b99e16acff0afa96f5b402cbc Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Sep 2024 10:48:10 -0700 Subject: [PATCH 16/18] Fix room name --- test/integration/sip_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index c31aae9..7806627 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -549,7 +549,7 @@ func TestSIPJoinPinRoom(t *testing.T) { go func() { // TransferSIPParticipant is synchronous _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ - RoomName: "test-open", + RoomName: "test-priv", ParticipantIdentity: "sip_" + clientNumber, TransferTo: "tel:" + transferNumber, }) From f451e6972dff7f7c888b29bbaa70ac7d3c6c3bdd Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Sep 2024 18:04:20 -0700 Subject: [PATCH 17/18] Support Event header shortcut --- pkg/sip/protocol.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/sip/protocol.go b/pkg/sip/protocol.go index 83d503c..9b04239 100644 --- a/pkg/sip/protocol.go +++ b/pkg/sip/protocol.go @@ -171,6 +171,13 @@ func parseNotifyBody(body string) (int, error) { func handleNotify(req *sip.Request) (method sip.RequestMethod, cseq uint32, status int, err error) { event := req.GetHeader("Event") + if event == nil { + event = req.GetHeader("o") + } + if event == nil { + return "", 0, 0, psrpc.NewErrorf(psrpc.MalformedRequest, "no event in NOTIFY request") + } + var cseq64 uint64 if m := referIdRegexp.FindStringSubmatch(strings.ToLower(event.Value())); len(m) > 0 { From 0423a1ffa03d83a04146c3e7989bdea2ea4d6fb4 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Wed, 2 Oct 2024 18:59:29 -0700 Subject: [PATCH 18/18] cleanup --- pkg/sip/inbound.go | 4 ++-- pkg/sip/outbound.go | 4 ++-- pkg/sip/protocol.go | 9 ++++++++- pkg/siptest/client.go | 2 +- test/integration/sip_test.go | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pkg/sip/inbound.go b/pkg/sip/inbound.go index 66bb7e1..db8e9bc 100644 --- a/pkg/sip/inbound.go +++ b/pkg/sip/inbound.go @@ -1096,14 +1096,14 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er // Success select { case c.referDone <- nil: - case <-time.After(5 * time.Second): + case <-time.After(notifyAckTimeout): } default: // Failure select { // TODO be more specific in the reported error case c.referDone <- psrpc.NewErrorf(psrpc.Canceled, "call transfer failed"): - case <-time.After(5 * time.Second): + case <-time.After(notifyAckTimeout): } } } diff --git a/pkg/sip/outbound.go b/pkg/sip/outbound.go index 855b023..41d57f3 100644 --- a/pkg/sip/outbound.go +++ b/pkg/sip/outbound.go @@ -728,14 +728,14 @@ func (c *sipOutbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) e // Success select { case c.referDone <- nil: - case <-time.After(5 * time.Second): + case <-time.After(notifyAckTimeout): } default: // Failure select { // TODO be more specific in the reported error case c.referDone <- psrpc.NewErrorf(psrpc.Canceled, "call transfer failed"): - case <-time.After(5 * time.Second): + case <-time.After(notifyAckTimeout): } } } diff --git a/pkg/sip/protocol.go b/pkg/sip/protocol.go index 9b04239..b7d9b01 100644 --- a/pkg/sip/protocol.go +++ b/pkg/sip/protocol.go @@ -19,13 +19,20 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/emiago/sipgo/sip" "github.com/livekit/psrpc" "github.com/pkg/errors" ) -var referIdRegexp = regexp.MustCompile(`^refer(;id=(\d+))?$`) +const ( + notifyAckTimeout = 5 * time.Second +) + +var ( + referIdRegexp = regexp.MustCompile(`^refer(;id=(\d+))?$`) +) type ErrorStatus struct { StatusCode int diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index b46d48e..a0b0a2c 100644 --- a/pkg/siptest/client.go +++ b/pkg/siptest/client.go @@ -408,7 +408,7 @@ func (c *Client) SendNotify(eventReq *sip.Request, notifyStatus string) error { } else if from, ok := eventReq.From(); ok { recipient = &from.Address } else { - errors.New("missing destination address") + return errors.New("missing destination address") } req := sip.NewRequest(sip.NOTIFY, recipient) diff --git a/test/integration/sip_test.go b/test/integration/sip_test.go index 7806627..6e194e6 100644 --- a/test/integration/sip_test.go +++ b/test/integration/sip_test.go @@ -393,7 +393,7 @@ func TestSIPJoinOpenRoom(t *testing.T) { go func() { // TransferSIPParticipant is synchronous _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ - RoomName: "test-open", + RoomName: roomName, ParticipantIdentity: "sip_" + clientNumber, TransferTo: "tel:" + transferNumber, })