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/sip/inbound.go b/pkg/sip/inbound.go index 01f4fbe..db8e9bc 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() @@ -758,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 { @@ -1094,14 +1096,14 @@ func (c *sipInbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) er // Success select { case c.referDone <- nil: - default: + 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"): - default: + case <-time.After(notifyAckTimeout): } } } diff --git a/pkg/sip/outbound.go b/pkg/sip/outbound.go index 7eb206e..41d57f3 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" @@ -391,6 +392,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") @@ -409,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 } } @@ -725,14 +728,14 @@ func (c *sipOutbound) handleNotify(req *sip.Request, tx sip.ServerTransaction) e // Success select { case c.referDone <- nil: - default: + 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"): - default: + case <-time.After(notifyAckTimeout): } } } diff --git a/pkg/sip/protocol.go b/pkg/sip/protocol.go index 83d503c..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 @@ -171,6 +178,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 { 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) diff --git a/pkg/siptest/client.go b/pkg/siptest/client.go index 296d24e..a0b0a2c 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,7 @@ type ClientConfig struct { OnBye func() OnMediaTimeout func() OnDTMF func(ev dtmf.Event) + OnRefer func(req *sip.Request) Codec string } @@ -146,6 +148,14 @@ func NewClient(id string, conf ClientConfig) (*Client, error) { default: } }) + cli.sipServer.OnRefer(func(req *sip.Request, tx sip.ServerTransaction) { + if conf.OnRefer != nil { + conf.OnRefer(req) + } + + err = tx.Respond(sip.NewResponseFromRequest(req, 202, "Accepted", nil)) + tx.Terminate() + }) return cli, nil } @@ -168,7 +178,8 @@ type Client struct { inviteReq *sip.Request inviteResp *sip.Response recordHandler atomic.Pointer[rtp.Handler] - closed atomic.Bool + lastCSeq atomic.Uint32 + closed core.Fuse } func (c *Client) LocalIP() string { @@ -183,23 +194,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() { @@ -322,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 @@ -361,6 +376,10 @@ func (c *Client) sendBye() { 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 @@ -381,6 +400,96 @@ 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 { + return 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 REFER request") + } + + if from, ok := eventReq.From(); ok { + req.AppendHeader((*sip.ToHeader)(from)) + } else { + return errors.New("missing From header in REFER 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()) + + 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) + 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 fea1e72..6e194e6 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)) *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), 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)) *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, @@ -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, + OnBye: onBye, + OnRefer: onRefer, } 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 + referRequest *sipgo.Request ) const ( clientID = "test-cli" @@ -311,12 +316,21 @@ 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{ "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() + referRequest = req }) h := sip.Headers(cli.RemoteHeaders()).GetHeader("X-LK-Accepted") @@ -376,10 +390,53 @@ func TestSIPJoinOpenRoom(t *testing.T) { return dtmfIn == "4567" }, 5*time.Second, time.Second/2) + go func() { + // TransferSIPParticipant is synchronous + _, err = lk.SIP.TransferSIPParticipant(context.Background(), &livekit.TransferSIPParticipantRequest{ + RoomName: roomName, + ParticipantIdentity: "sip_" + clientNumber, + TransferTo: "tel:" + transferNumber, + }) + require.NoError(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 100 Trying") + require.NoError(t, err) + + 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: + case <-time.After(participantsLeaveTimeout): + t.Fatal("did not receive bye after notify") + } + cli.Close() r.Disconnect() - // 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) @@ -388,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" @@ -424,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, 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. @@ -482,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-priv", + 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() @@ -509,7 +629,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, nil) // Send audio, so that we don't trigger media timeout. mctx, mcancel := context.WithCancel(context.Background()) @@ -571,7 +691,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, nil) // Send audio, so that we don't trigger media timeout. mctx, mcancel := context.WithCancel(context.Background()) @@ -651,7 +771,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, nil) mu.Lock() clients[i] = cli audios[i] = cli