diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index f70e5997..32b9bb99 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -40,14 +40,16 @@ jobs: - name: package-server run: |- - sudo apt-get update && sudo apt-get install make git gn ninja-build python3 python3-pip libgtk-3-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-x86-64-linux-gnu g++-x86-64-linux-gnu -y + sudo apt-get update && sudo apt-get install make git gn ninja-build python3 python3-pip libgtk-3-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-x86-64-linux-gnu g++-x86-64-linux-gnu rustc -y + rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu GOARCH=amd64 make release_server && mv release/linux-amd64-server release/linux-x86_64-server TARGET=aarch64-linux-gnu GOOS=linux GOARCH=arm64 make release_server && mv release/linux-arm64-server release/linux-aarch64-server ls -al release - name: package-client run: |- - sudo apt-get update && sudo apt-get install make git gn ninja-build python3 python3-pip libgtk-3-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-x86-64-linux-gnu g++-x86-64-linux-gnu -y + sudo apt-get update && sudo apt-get install make git gn ninja-build python3 python3-pip libgtk-3-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-x86-64-linux-gnu g++-x86-64-linux-gnu rustc -y + rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu GOARCH=amd64 make release_client && mv release/linux-amd64-client release/linux-x86_64-client TARGET=aarch64-linux-gnu GOOS=linux GOARCH=arm64 make release_client && mv release/linux-arm64-client release/linux-aarch64-client ls -al release diff --git a/.gitmodules b/.gitmodules index c8515e50..b2909677 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,6 +17,8 @@ url = https://git.mirror.iscas.ac.cn/ao-space/google-webrtc.git branch = main shallow = true + ignore = dirty [submodule "dep/_msquic"] path = dep/_msquic url = https://github.com/microsoft/msquic + ignore = dirty diff --git a/Dockerfile b/Dockerfile index 091e93ce..36a4668e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,20 +14,26 @@ FROM golang:1.20-bookworm +# 安装 gnu 编译链 +# 安装 msquic 依赖 +# 安装 nodejs 20 RUN apt update && \ - apt install xz-utils bzip2 sudo lsb-release ninja-build generate-ninja file patch -y + apt install -y xz-utils bzip2 sudo lsb-release ninja-build generate-ninja file patch \ + gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-x86-64-linux-gnu g++-x86-64-linux-gnu \ + cmake build-essential liblttng-ust-dev lttng-tools libssl-dev \ + ca-certificates curl gnupg -# 安装nodejs 20 -RUN apt-get install -y ca-certificates curl gnupg && \ - mkdir -p /etc/apt/keyrings && \ +RUN mkdir -p /etc/apt/keyrings && \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ NODE_MAJOR=20 && \ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && \ - apt-get install nodejs -y + apt update && \ + apt install nodejs -y -# 安装msquic依赖 -RUN apt-get install -y cmake build-essential liblttng-ust-dev lttng-tools libssl-dev +# 安装 rust +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \ + . "$HOME/.cargo/env" && \ + rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu # golang 切换国内源并且提前安装好依赖 ENV GO111MODULE=on @@ -37,9 +43,6 @@ RUN cd /go/src/github.com/isrc-cas/gt && \ go mod download && \ rm -rf /go/src/github.com/isrc-cas/gt -# 安装 gnu 编译链 -RUN apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu gcc-x86-64-linux-gnu g++-x86-64-linux-gnu - # 安装 google webrtc 依赖 ADD ./dep/_google-webrtc/src/build /root/build RUN sed -i 's/egrep -q "i686|x86_64"/true/g' /root/build/install-build-deps.sh && \ diff --git a/Makefile b/Makefile index 1d22f271..1ad33469 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,9 @@ TARGET_OS=$(shell echo $(TARGET) | awk -F '-' '{print $$2}') ifeq ($(TARGET_OS), native) TARGET_OS= endif +ifeq ($(TARGET_OS), linux) + RUST_TARGET?=$(shell echo $(TARGET) | awk -F '-' '{print $$1 "-unknown-" $$2 "-" $$3}') +endif TARGET_CPU=$(shell echo $(TARGET) | awk -F '-' '{print $$1}') ifeq ($(TARGET_CPU), native) TARGET_CPU= @@ -136,8 +139,12 @@ set_safe_directories: docker_create_image: update_submodule docker images | grep -cim1 -E "^gtbuild\s+?v1" || docker build -t gtbuild:v1 . -docker_build_linux_amd64: docker_build_linux_amd64_server docker_build_linux_amd64_client -docker_release_linux_amd64: docker_release_linux_amd64_server docker_release_linux_amd64_client +docker_build_linux_amd64: docker_create_image + $(eval MAKE_ENV=TARGET=x86_64-linux-gnu GOOS=linux GOARCH=amd64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) + docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make build' +docker_release_linux_amd64: docker_create_image + $(eval MAKE_ENV=TARGET=x86_64-linux-gnu GOOS=linux GOARCH=amd64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) + docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make release' docker_build_linux_amd64_client: docker_create_image $(eval MAKE_ENV=TARGET=x86_64-linux-gnu GOOS=linux GOARCH=amd64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make build_client' @@ -151,8 +158,12 @@ docker_release_linux_amd64_server: docker_create_image $(eval MAKE_ENV=TARGET=x86_64-linux-gnu GOOS=linux GOARCH=amd64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make release_server' -docker_build_linux_arm64: docker_build_linux_arm64_server docker_build_linux_arm64_client -docker_release_linux_arm64: docker_release_linux_arm64_server docker_release_linux_arm64_client +docker_build_linux_arm64: docker_create_image + $(eval MAKE_ENV=TARGET=aarch64-linux-gnu GOOS=linux GOARCH=arm64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) + docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make build' +docker_release_linux_arm64: docker_create_image + $(eval MAKE_ENV=TARGET=aarch64-linux-gnu GOOS=linux GOARCH=arm64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) + docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make release' docker_build_linux_arm64_client: docker_create_image $(eval MAKE_ENV=TARGET=aarch64-linux-gnu GOOS=linux GOARCH=arm64 STATIC_LINK=$(STATIC_LINK) RACE_CHECK=$(RACE_CHECK) WITH_OFFICIAL_WEBRTC=$(WITH_OFFICIAL_WEBRTC)) docker run --rm -v $(PWD):/go/src/github.com/isrc-cas/gt -w /go/src/github.com/isrc-cas/gt gtbuild:v1 sh -c '$(MAKE_ENV) make build_client' @@ -168,12 +179,14 @@ docker_release_linux_arm64_server: docker_create_image build: build_server build_client release: release_server release_client -build_client: $(SOURCES) Makefile compile_msquic compile_webrtc +build_client: $(SOURCES) Makefile compile_msquic compile_webrtc compile_p2p $(eval CGO_CXXFLAGS+=-O0 -g -ggdb) + $(eval CGO_LDFLAGS+=$(PWD)/dep/p2p/target/$(RUST_TARGET)/release/libp2p.a) $(eval NAME=$(GOOS)-$(GOARCH)-client) go build $(DEBUG_OPTIONS) -o build/$(NAME)$(EXE) ./cmd/client -release_client: $(SOURCES) Makefile compile_msquic compile_webrtc release_web_client +release_client: $(SOURCES) Makefile compile_msquic compile_webrtc compile_p2p release_web_client $(eval CGO_CXXFLAGS+=-O3) + $(eval CGO_LDFLAGS+=$(PWD)/dep/p2p/target/$(RUST_TARGET)/release/libp2p.a) $(eval NAME=$(GOOS)-$(GOARCH)-client) go build $(RELEASE_OPTIONS) -o release/$(NAME)$(EXE) ./cmd/client build_server: $(SOURCES) Makefile compile_msquic compile_webrtc @@ -203,7 +216,7 @@ duplicate_dist_server: duplicate_dist_client: cp -r $(FRONTEND_DIR)/dist $(CLIENT_FRONT_DIR)/dist -clean: clean_web +clean: clean_web clean_p2p rm -rf build/* release/* rm -rf dep/_google-webrtc/src/out/* @@ -277,4 +290,14 @@ compile_msquic: check_msquic_dependencies update_submodule cmake -B./dep/_msquic/$(TARGET) -S./dep/_msquic -DQUIC_BUILD_SHARED=OFF -DCMAKE_TARGET_ARCHITECTURE=$(TARGET_CPU) make -C./dep/_msquic/$(TARGET) -j$(shell nproc) @renameSymbols=$$(objdump -t ./dep/_msquic/$(TARGET)/bin/Release/libmsquic.a | awk -v RS= '/_YB80VJ/{next}1' | grep -E 'g +(F|O) ' | grep -Evi ' (ms){0,1}quic' | awk '{print " --redefine-sym " $$NF "=" $$NF "_YB80VJ"}') && \ - $(TARGET)-objcopy $$renameSymbols ./dep/_msquic/$(TARGET)/bin/Release/libmsquic.a \ No newline at end of file + $(TARGET)-objcopy $$renameSymbols ./dep/_msquic/$(TARGET)/bin/Release/libmsquic.a + +compile_p2p: + cd ./dep/p2p && \ + . "$$HOME/.cargo/env" && \ + cargo build -r --target $(RUST_TARGET) + +clean_p2p: + cd ./dep/p2p && \ + . "$$HOME/.cargo/env" && \ + cargo clean diff --git a/client/client.go b/client/client.go index f2d40408..70f5368e 100644 --- a/client/client.go +++ b/client/client.go @@ -89,7 +89,7 @@ func New(args []string, out io.Writer) (c *Client, err error) { c = &Client{ Logger: l, tunnels: make(map[*conn]struct{}), - peers: make(map[uint32]*peerTask), + peers: make(map[uint32]PeerTask), } c.config.Store(&conf) c.tunnelsCond = sync.NewCond(c.tunnelsRWMtx.RLocker()) diff --git a/client/config.go b/client/config.go index 610e8c10..a3af03dc 100644 --- a/client/config.go +++ b/client/config.go @@ -66,10 +66,11 @@ type Options struct { SentryDebug bool `yaml:"sentryDebug,omitempty" json:",omitempty" usage:"Sentry debug mode, the debug information is printed to help you understand what sentry is doing"` WebRTCConnectionIdleTimeout config.Duration `yaml:"webrtcConnectionIdleTimeout,omitempty" usage:"The timeout of WebRTC connection. Supports values like '30s', '5m'"` - WebRTCRemoteConnections uint `yaml:"webrtcConnections" usage:"The max number of webrtc connections. Valid value is 1 to 50"` + WebRTCRemoteConnections uint `yaml:"webrtcConnections" usage:"The max number of webrtc connections. Valid value is 1 to 50"` WebRTCLogLevel string `yaml:"webrtcLogLevel,omitempty" json:",omitempty" usage:"WebRTC log level: verbose, info, warning, error"` WebRTCMinPort uint16 `yaml:"webrtcMinPort,omitempty" json:",omitempty" usage:"The min port of WebRTC peer connection"` WebRTCMaxPort uint16 `yaml:"webrtcMaxPort,omitempty" json:",omitempty" usage:"The max port of WebRTC peer connection"` + WebRTCThread bool `yaml:"webrtcThreadMode,omitempty" json:",omitempty" usage:"Use thread mode of WebRTC peer connection"` TCPForwardAddr string `yaml:"tcpForwardAddr,omitempty" json:",omitempty" usage:"The address of TCP forward"` TCPForwardHostPrefix string `yaml:"tcpForwardHostPrefix,omitempty" json:",omitempty" usage:"The host prefix of TCP forward"` @@ -168,6 +169,8 @@ type service struct { LocalURL clientURL `yaml:"local,omitempty" json:",omitempty"` LocalTimeout config.Duration `yaml:"localTimeout,omitempty" json:",omitempty"` UseLocalAsHTTPHost bool `yaml:"useLocalAsHTTPHost,omitempty" json:",omitempty"` + + remoteTCPPort uint32 } func (s *service) String() string { diff --git a/client/conn.go b/client/conn.go index 3e12dd52..f42950e4 100644 --- a/client/conn.go +++ b/client/conn.go @@ -461,9 +461,10 @@ func (c *conn) processData(taskID uint32, r *bufio.LimitedReader) (readErr, writ }) return } - _, err := r.WriteTo(pt.apiConn.PipeWriter) + _, err := r.WriteTo(pt.APIWriter()) if err != nil { - pt.Logger.Error().Err(err).Msg("processP2P WriteTo failed") + c.Logger.Info(). + Uint32("peerTask", taskID).Msg("got closed because task with same id is received") } return } @@ -494,17 +495,65 @@ func (c *conn) processData(taskID uint32, r *bufio.LimitedReader) (readErr, writ } func (c *conn) processP2P(id uint32, r *bufio.LimitedReader) { - t, ok := c.newPeerTask(id) + var t PeerTask + var ok bool + if c.client.Config().WebRTCThread { + t, ok = c.newPeerTask(id) + } else { + t, ok = c.newPeerProcTask(id) + } if !ok { return } - c.client.apiServer.Listener.AcceptCh() <- t.apiConn - t.Logger.Info().Msg("peer task started") - _, err := r.WriteTo(t.apiConn.PipeWriter) + c.client.apiServer.Listener.AcceptCh() <- t.APIConn() + c.Logger.Info(). + Uint32("peerTask", id).Msg("peer task started") + _, err := r.WriteTo(t.APIWriter()) if err != nil { - t.Logger.Error().Err(err).Msg("processP2P WriteTo failed") + c.Logger.Error(). + Uint32("peerTask", id).Err(err).Msg("processP2P WriteTo failed") + } +} + +func (c *conn) newPeerProcTask(id uint32) (t *peerProcessTask, ok bool) { + c.client.peersRWMtx.Lock() + defer c.client.peersRWMtx.Unlock() + if !predef.Debug { + l := uint(len(c.client.peers)) + if l >= c.client.Config().WebRTCRemoteConnections { + respAndClose(id, c, [][]byte{ + []byte("HTTP/1.1 403 Forbidden\r\nConnection: Closed\r\n\r\n"), + }) + return + } + } + + t = &peerProcessTask{} + t.id = id + t.tunnel = c + t.apiConn = api.NewConn(id, "", c) + t.apiConn.ProcessOffer = t.processOffer + t.apiConn.GetOffer = t.getOffer + t.apiConn.ProcessAnswer = t.processAnswer + t.data = pool.BytesPool.Get().([]byte) + t.Logger = c.Logger.With(). + Uint32("peerTask", id). + Logger() + t.timer = time.AfterFunc(120*time.Second, func() { + t.Logger.Info().Msg("peer task timeout") + t.CloseWithLock() + }) + + ot, ok := c.client.peers[id] + if ok && ot != nil { + ot.CloseWithLock() + c.Logger.Info(). + Uint32("peerTask", id).Msg("got closed because task with same id is received") } + c.client.peers[id] = t + ok = true + return } func (c *conn) newPeerTask(id uint32) (t *peerTask, ok bool) { @@ -542,7 +591,8 @@ func (c *conn) newPeerTask(id uint32) (t *peerTask, ok bool) { ot, ok := c.client.peers[id] if ok && ot != nil { ot.CloseWithLock() - ot.Logger.Info().Msg("got closed because task with same id is received") + c.Logger.Info(). + Uint32("peerTask", id).Msg("got closed because task with same id is received") } c.client.peers[id] = t ok = true diff --git a/client/debug.go b/client/debug.go index 9b8ca9d9..73fe1c36 100644 --- a/client/debug.go +++ b/client/debug.go @@ -34,7 +34,7 @@ type Client struct { closing uint32 tunnels map[*conn]struct{} tunnelsRWMtx sync.RWMutex - peers map[uint32]*peerTask + peers map[uint32]PeerTask peersRWMtx sync.RWMutex tunnelsCond *sync.Cond idleManager *idleManager diff --git a/client/peer.go b/client/peer.go index e9b8f274..e0113be4 100644 --- a/client/peer.go +++ b/client/peer.go @@ -55,6 +55,14 @@ type peerTask struct { waitNegotiationNeeded chan struct{} } +func (pt *peerTask) APIConn() *api.Conn { + return pt.apiConn +} + +func (pt *peerTask) APIWriter() *std.PipeWriter { + return pt.apiConn.PipeWriter +} + func (pt *peerTask) OnSignalingChange(state webrtc.SignalingState) { pt.Logger.Info().Str("state", state.String()).Msg("signaling state changed") } @@ -492,7 +500,7 @@ func (pt *peerTask) processAnswer(r *http.Request, writer http.ResponseWriter) { err = errors.New("invalid task id") return } - task = pt + task = pt.(*peerTask) } task.process(r.Body, writer, func() (err error) { var answer webrtc.SessionDescription diff --git a/client/peerproc.go b/client/peerproc.go new file mode 100644 index 00000000..38784e60 --- /dev/null +++ b/client/peerproc.go @@ -0,0 +1,492 @@ +// Copyright (c) 2022 Institute of Software, Chinese Academy of Sciences (ISCAS) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "github.com/isrc-cas/gt/bufio" + "github.com/isrc-cas/gt/client/api" + "github.com/isrc-cas/gt/client/std" + "github.com/isrc-cas/gt/pool" + "github.com/isrc-cas/gt/predef" + "github.com/rs/zerolog" + "io" + "net/http" + "os" + "os/exec" + "strconv" + "strings" + "sync/atomic" + "time" +) + +type peerProcessTask struct { + Logger zerolog.Logger + id uint32 + closing uint32 + initDone uint32 + state uint8 + dataLen [2]byte + n int + data []byte + tunnel *conn + timer *time.Timer + apiConn *api.Conn + cmd *exec.Cmd + stdout io.ReadCloser + stdin io.WriteCloser + stderr io.ReadCloser +} + +type op struct { + Config *opConfig `json:"config,omitempty"` + OfferSDP string `json:"offerSDP,omitempty"` + AnswerSDP string `json:"answerSDP,omitempty"` + Candidate string `json:"candidate,omitempty"` + GetOfferSDP *opGetOfferSDP `json:"getOfferSDP,omitempty"` +} + +type opConfig struct { + Stuns []string `json:"stuns,omitempty"` + HTTPRoutes map[string]string `json:"httpRoutes,omitempty"` + TCPRoutes map[string]string `json:"tcpRoutes,omitempty"` + PortMin uint16 `json:"portMin,omitempty"` + PortMax uint16 `json:"portMax,omitempty"` + Timeout uint16 `json:"timeout,omitempty"` +} + +type opGetOfferSDP struct { + ChannelName string `json:"channelName"` +} + +func (pt *peerProcessTask) init() (err error) { + cmd := exec.Command(os.Args[0], "p2p") + pt.stdin, err = cmd.StdinPipe() + if err != nil { + return + } + pt.stdout, err = cmd.StdoutPipe() + if err != nil { + return + } + pt.stderr, err = cmd.StderrPipe() + if err != nil { + return + } + err = cmd.Start() + if err != nil { + return err + } + go func() { + var err error + defer func() { + pt.Logger.Info().Err(err).AnErr("wait", cmd.Wait()).Msg("peer process wait done") + pt.closeWithLock() + }() + reader := bufio.NewReader(pt.stderr) + for { + var line string + line, err = reader.ReadString('\n') + if err != nil { + return + } + pt.Logger.Info().Msg(line) + if strings.HasPrefix(line, "p2p done") { + break + } + } + }() + if pt.timer != nil { + pt.timer.Stop() + } + config := pt.tunnel.client.Config() + httpRouters := make(map[string]string) + tcpRouters := make(map[string]string) + for i, s := range config.Services { + if len(s.HostPrefix) > 0 { + if i == 0 { + httpRouters["@"] = s.LocalURL.String() + } + httpRouters[s.HostPrefix] = s.LocalURL.String() + } else { + tcpRouters[strconv.FormatUint(uint64(atomic.LoadUint32(&s.remoteTCPPort)), 10)] = s.LocalURL.String() + } + } + js, err := json.Marshal(&op{ + Config: &opConfig{ + Stuns: pt.tunnel.stuns, + HTTPRoutes: httpRouters, + TCPRoutes: tcpRouters, + PortMin: config.WebRTCMinPort, + PortMax: config.WebRTCMaxPort, + Timeout: uint16(config.WebRTCConnectionIdleTimeout.Duration.Seconds()), + }, + }) + if err != nil { + return + } + err = pt.writeJson(js) + pt.cmd = cmd + return +} + +func (pt *peerProcessTask) writeJson(json []byte) (err error) { + l := [4]byte{} + binary.BigEndian.PutUint32(l[:], uint32(len(json))) + _, err = pt.stdin.Write(l[:]) + if err != nil { + return + } + _, err = pt.stdin.Write(json) + return +} + +func (pt *peerProcessTask) readJson() (json []byte, err error) { + l := [4]byte{} + _, err = pt.stdout.Read(l[:]) + if err != nil { + return + } + jl := binary.BigEndian.Uint32(l[:]) + if jl > 8*1024 { + err = errors.New("json too large") + return + } + json = make([]byte, jl) + _, err = io.ReadFull(pt.stdout, json) + return +} + +func (pt *peerProcessTask) Close() { + if !atomic.CompareAndSwapUint32(&pt.closing, 0, 1) { + return + } + defer pool.BytesPool.Put(pt.data) + client := pt.tunnel.client + delete(client.peers, pt.id) + if pt.timer != nil { + pt.timer.Stop() + } + var cmdErr error + if pt.cmd != nil { + cmdErr = pt.cmd.Process.Kill() + } + pt.Logger.Info().AnErr("cmd", cmdErr).Msg("peer task closed") +} + +func (pt *peerProcessTask) CloseWithLock() { + if !atomic.CompareAndSwapUint32(&pt.closing, 0, 1) { + return + } + defer pool.BytesPool.Put(pt.data) + client := pt.tunnel.client + client.peersRWMtx.Lock() + delete(client.peers, pt.id) + client.peersRWMtx.Unlock() + if pt.timer != nil { + pt.timer.Stop() + } + var cmdErr error + if pt.cmd != nil { + cmdErr = pt.cmd.Process.Kill() + } + pt.Logger.Info().AnErr("cmd", cmdErr).Msg("peer task closed") +} + +func (pt *peerProcessTask) closeWithLock() { + if !atomic.CompareAndSwapUint32(&pt.closing, 0, 1) { + return + } + defer pool.BytesPool.Put(pt.data) + client := pt.tunnel.client + client.peersRWMtx.Lock() + delete(client.peers, pt.id) + client.peersRWMtx.Unlock() + if pt.timer != nil { + pt.timer.Stop() + } + pt.Logger.Info().Msg("peer task closed") +} + +func (pt *peerProcessTask) process(r io.Reader, writer http.ResponseWriter, initFn func() error) { + var err error + defer func() { + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + } + if !predef.Debug { + if e := recover(); e != nil { + pt.Logger.Info().Interface("panic", e).Msg("process panic") + } + } + pt.Logger.Info().Err(err).Msg("process done") + }() + reader := std.NewChunkedReader(r) + for { + switch pt.state { + case dataLength: + var n int + n, err = reader.Read(pt.dataLen[pt.n:2]) + if n > 0 { + pt.n += n + } + if pt.n != 2 { + if err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + err = nil + return + } + pt.Logger.Error().Err(err).Hex("data", pt.dataLen[:pt.n]).Int("read n", n).Msg("failed to read data") + return + } + continue + } + en := uint(pt.dataLen[0])<<8 | uint(pt.dataLen[1]) + if en > pool.MaxBufferSize { + err = errors.New("dataLen is too long") + return + } + pt.n = 0 + pt.state = dataBody + pt.Logger.Debug().Int("read n", n).Uint("len", en).Msg("read data length") + fallthrough + case dataBody: + var n int + en := uint16(pt.dataLen[0])<<8 | uint16(pt.dataLen[1]) + n, err = reader.Read(pt.data[pt.n:en]) + if n > 0 { + pt.n += n + } + if pt.n != int(en) { + if err != nil { + pt.Logger.Error().Err(err).Hex("data", pt.data[:pt.n]).Int("read n", n).Uint16("len", en).Msg("failed to read data") + return + } + continue + } + pt.state = processData + fallthrough + case processData: + pt.Logger.Debug().Str("data", string(pt.data[:pt.n])).Msg("read json") + if atomic.CompareAndSwapUint32(&pt.initDone, 0, 1) { + err = initFn() + if err != nil { + pt.Logger.Error().Err(err).Msg("initFn failed") + return + } + } else { + js, err := json.Marshal(&op{ + Candidate: string(pt.data[:pt.n]), + }) + if err != nil { + pt.Logger.Error().Err(err).Msg("failed to Marshal candidate") + return + } + err = pt.writeJson(js) + if err != nil { + pt.Logger.Error().Err(err).Msg("failed to add ice candidate") + return + } + pt.Logger.Debug().Msg("AddICECandidate") + } + + pt.n = 0 + pt.state = dataLength + if err != nil { + if err == io.EOF { + err = nil + return + } + pt.Logger.Error().Err(err).Hex("data", pt.data[:pt.n]).Msg("err after processData") + return + } + continue + } + } +} + +func (pt *peerProcessTask) response(writer http.ResponseWriter, sdp []byte) { + var err error + defer func() { + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + } + pt.Logger.Info().Err(err).Msg("response done") + }() + + pt.Logger.Debug().Bytes("sdp", sdp).Msg("local sdp") + writer.Header().Add("Transfer-Encoding", "chunked") + n := uint16(len(sdp)) + l := copy(pt.data, []byte{byte(n >> 8), byte(n)}) + l += copy(pt.data[l:], sdp) + _, err = writer.Write(pt.data[:l]) + if err != nil { + return + } + + for { + candidate, err := pt.readJson() + if err != nil { + return + } + pt.Logger.Debug().Str("candidate", string(candidate)).Msg("local candidate") + op := op{} + err = json.Unmarshal(candidate, &op) + if err != nil { + return + } + if len(op.Candidate) == 0 { + break + } + n := uint16(len(op.Candidate)) + l := copy(pt.data, []byte{byte(n >> 8), byte(n)}) + l += copy(pt.data[l:], op.Candidate) + _, err = writer.Write(pt.data[:l]) + if err != nil { + return + } + } +} + +func (pt *peerProcessTask) processOffer(r *http.Request, writer http.ResponseWriter) { + pt.process(r.Body, writer, func() (err error) { + err = pt.init() + if err != nil { + return + } + + js, err := json.Marshal(&op{ + OfferSDP: string(pt.data[:pt.n]), + }) + if err != nil { + return + } + err = pt.writeJson(js) + if err != nil { + return + } + + js, err = pt.readJson() + if err != nil { + return + } + op := op{} + err = json.Unmarshal(js, &op) + if err != nil { + return + } + pt.response(writer, []byte(op.AnswerSDP)) + return + }) +} + +func (pt *peerProcessTask) getOffer(_ *http.Request, writer http.ResponseWriter) { + var err error + defer func() { + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + } + pt.Logger.Info().Err(err).Msg("getOffer done") + }() + err = pt.init() + if err != nil { + return + } + + js, err := json.Marshal(&op{ + GetOfferSDP: &opGetOfferSDP{ChannelName: "default"}, + }) + if err != nil { + return + } + err = pt.writeJson(js) + if err != nil { + return + } + + js, err = pt.readJson() + if err != nil { + return + } + op := op{} + err = json.Unmarshal(js, &op) + if err != nil { + return + } + pt.response(writer, []byte(op.OfferSDP)) + + _, err = writer.Write([]byte(fmt.Sprintf(`{"id":%d}`, pt.id))) +} + +func (pt *peerProcessTask) processAnswer(r *http.Request, writer http.ResponseWriter) { + c := pt.tunnel + var err error + var shouldClose bool + defer func() { + if err != nil { + writer.WriteHeader(http.StatusBadRequest) + } + pt.Logger.Info().Err(err).Msg("processAnswer done") + if shouldClose { + pt.CloseWithLock() + } + }() + task := pt + idValue := r.Header.Get("WebRTC-OP-ID") + id, err := strconv.ParseUint(idValue, 10, 32) + if err != nil { + return + } + if uint32(id) != pt.id { + shouldClose = true + client := c.client + client.peersRWMtx.RLock() + pt, ok := client.peers[uint32(id)] + client.peersRWMtx.RUnlock() + if !ok { + err = errors.New("invalid task id") + return + } + task = pt.(*peerProcessTask) + } + task.process(r.Body, writer, func() (err error) { + js, err := json.Marshal(&op{ + AnswerSDP: string(task.data[:task.n]), + }) + if err != nil { + return + } + err = task.writeJson(js) + return + }) +} + +func (pt *peerProcessTask) APIWriter() *std.PipeWriter { + return pt.apiConn.PipeWriter +} + +func (pt *peerProcessTask) APIConn() *api.Conn { + return pt.apiConn +} + +type PeerTask interface { + Close() + CloseWithLock() + APIWriter() *std.PipeWriter + APIConn() *api.Conn +} diff --git a/client/release.go b/client/release.go index 62b55ea1..1b03b983 100644 --- a/client/release.go +++ b/client/release.go @@ -34,7 +34,7 @@ type Client struct { closing uint32 tunnels map[*conn]struct{} tunnelsRWMtx sync.RWMutex - peers map[uint32]*peerTask + peers map[uint32]PeerTask peersRWMtx sync.RWMutex tunnelsCond *sync.Cond idleManager *idleManager diff --git a/client/signal.go b/client/signal.go index f9f28f94..9679aa7e 100644 --- a/client/signal.go +++ b/client/signal.go @@ -2,6 +2,7 @@ package client import ( connection "github.com/isrc-cas/gt/conn" + "sync/atomic" ) func handleError(tunnel *conn) (err error) { @@ -91,6 +92,7 @@ func handleInfo(tunnel *conn) (err error) { var local string if s := tunnel.client.services.Load(); s != nil && serviceIndex < uint16(len(*s)) { local = (*s)[serviceIndex].LocalURL.String() + atomic.StoreUint32(&(*s)[serviceIndex].remoteTCPPort, uint32(tcpPort)) } tunnel.Logger.Info().Uint16("serviceIndex", serviceIndex). Str("local", local). diff --git a/cmd/client/main.go b/cmd/client/main.go index 14eb305f..b1d4b183 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -14,7 +14,26 @@ package main +/* +#include +#include + +extern void create_peer_connection(); + +__attribute__((constructor)) void init(int argc, char *argv[]) { + if (argc < 2) { + return; + } + if (strcmp(argv[1], "p2p") == 0) { + create_peer_connection(); + exit(0); + } +} + +*/ +import "C" import ( + "github.com/isrc-cas/gt/client" "github.com/isrc-cas/gt/client/web" "github.com/isrc-cas/gt/config" "github.com/isrc-cas/gt/predef" @@ -26,11 +45,16 @@ import ( "syscall" "time" - "github.com/isrc-cas/gt/client" "github.com/rs/zerolog/log" ) func main() { + if len(os.Args) >= 2 { + if "p2p" == os.Args[1] { + C.create_peer_connection() + return + } + } c, err := client.New(os.Args, nil) if err != nil { log.Fatal().Err(err).Msg("failed to create client") diff --git a/dep/p2p/Cargo.lock b/dep/p2p/Cargo.lock new file mode 100644 index 00000000..1b68a17b --- /dev/null +++ b/dep/p2p/Cargo.lock @@ -0,0 +1,2483 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f9214f3e703236b221f1a9cd88ec8b4adfa5296de01ab96216361f4692f56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "tokio", + "want", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "interceptor" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5927883184e6a819b22d5e4f5f7bc7ca134fde9b2026fbddd8d95249746ba21e" +dependencies = [ + "async-trait", + "bytes", + "log", + "rand", + "rtcp", + "rtp", + "thiserror", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.3", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p2p" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "cbindgen", + "env_logger", + "http-body-util", + "hyper", + "log", + "serde", + "serde_json", + "serde_repr", + "thiserror", + "tokio", + "url", + "webrtc", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pem" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "rtcp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3677908cadfbecb4cc1da9a56a32524fae4ebdfa7c2ea93886e1b1e846488cb9" +dependencies = [ + "bytes", + "thiserror", + "webrtc-util", +] + +[[package]] +name = "rtp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e60482acbe8afb31edf6b1413103b7bca7a65004c423b3c3993749a083994fbe" +dependencies = [ + "bytes", + "rand", + "serde", + "thiserror", + "webrtc-util", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "log", + "ring 0.17.6", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.6", + "untrusted 0.9.0", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.6", + "untrusted 0.9.0", +] + +[[package]] +name = "sdp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4653054c30ebce63658762eb0d64e27673868a95564474811ae6c220cf767640" +dependencies = [ + "rand", + "substring", + "thiserror", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "stun" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7beb1624a3ea34778d58d30e2b8606b4d29fe65e87c4d50b87ed30afd5c3830c" +dependencies = [ + "base64", + "crc", + "lazy_static", + "md-5", + "rand", + "ring 0.16.20", + "subtle", + "thiserror", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "turn" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f4fcb97da0426e8146fe0e9b78cc13120161087256198701d12d9df77f7701" +dependencies = [ + "async-trait", + "base64", + "futures", + "log", + "md-5", + "rand", + "ring 0.16.20", + "stun", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "web-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webrtc" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e7cf018f7185552bf6a5dd839f4ed9827aea33b746763c9a215f84a0d0b34" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "cfg-if", + "hex", + "interceptor", + "lazy_static", + "log", + "rand", + "rcgen", + "regex", + "ring 0.16.20", + "rtcp", + "rtp", + "rustls", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str", + "stun", + "thiserror", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45d2461d0e0bf93f181e30eb0b40df32b8bf3efb89c53cebb1990e603e2067d" +dependencies = [ + "bytes", + "log", + "thiserror", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b140b953f986e97828aa33ec6318186b05d862bee689efbc57af04a243e832" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "der-parser", + "hkdf", + "hmac", + "log", + "p256", + "p384", + "rand", + "rand_core", + "rcgen", + "ring 0.16.20", + "rustls", + "sec1", + "serde", + "sha1", + "sha2", + "subtle", + "thiserror", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser", +] + +[[package]] +name = "webrtc-ice" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66eb4b85646f1c52225779db3e1e7e873dede6db68cc9be080b648f1713083a3" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "rand", + "serde", + "serde_json", + "stun", + "thiserror", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bebbd40e7f8b630a0f1a74783dbfff1edfc0ccaae891c4689891156a8c4d8c" +dependencies = [ + "log", + "socket2", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfde3c7b9450b67d466bb2f02c6d9ff9514d33535eb9994942afd1f828839d1" +dependencies = [ + "byteorder", + "bytes", + "rand", + "rtp", + "thiserror", +] + +[[package]] +name = "webrtc-sctp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1af6116b7f9703560c3ad0b32f67220b171bb1b59633b03563db8404d0e482ea" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "rand", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db1f36c1c81e4b1e531c0b9678ba0c93809e196ce62122d87259bb71c03b9f" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adc96bee68417e1f4d19dd7698124a7f859db55ae2fd3eedbbb7e732f614735" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "cc", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "rand", + "thiserror", + "tokio", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "x25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring 0.16.20", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] diff --git a/dep/p2p/Cargo.toml b/dep/p2p/Cargo.toml new file mode 100644 index 00000000..75acd62d --- /dev/null +++ b/dep/p2p/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "p2p" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["lib", "staticlib"] + +[dependencies] +anyhow = "1.0.75" +bytes = "1.5.0" +env_logger = "0.10.1" +http-body-util = "0.1.0" +hyper = { version = "1.0.1", features = ["http1", "client"] } +log = { version = "0.4.20", features = [] } +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +serde_repr = "0.1.17" +thiserror = "1.0.51" +tokio = "1.34.0" +url = "2.5.0" +webrtc = "0.9.0" + +[build-dependencies] +cbindgen = "0.26.0" diff --git a/dep/p2p/src/conn.rs b/dep/p2p/src/conn.rs new file mode 100644 index 00000000..6febaadf --- /dev/null +++ b/dep/p2p/src/conn.rs @@ -0,0 +1,377 @@ +use std::collections::HashMap; +use std::future::Future; +use std::pin::Pin; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::{anyhow, bail, Context, Result}; +use log::*; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::sync::Mutex; +use tokio::time::timeout; +use tokio::{io, select, time}; +use url::Url; +use webrtc::api::interceptor_registry::register_default_interceptors; +use webrtc::api::media_engine::MediaEngine; +use webrtc::api::setting_engine::SettingEngine; +use webrtc::api::APIBuilder; +use webrtc::data::data_channel::PollDataChannel; +use webrtc::data_channel::RTCDataChannel; +use webrtc::ice::udp_network; +use webrtc::ice::udp_network::UDPNetwork; +use webrtc::ice_transport::ice_candidate::{RTCIceCandidate, RTCIceCandidateInit}; +use webrtc::ice_transport::ice_server::RTCIceServer; +use webrtc::interceptor::registry::Registry; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; +use webrtc::peer_connection::RTCPeerConnection; + +use crate::{read_json, write_json, LibError, OP}; + +pub(crate) struct PeerConnHandler { + http_routes: HashMap, + tcp_routes: HashMap, + reader: Arc>, + writer: Arc>, + channel_count: AtomicUsize, + no_channel_id: AtomicUsize, + peer_connection: Arc, + timeout: u16, +} + +impl PeerConnHandler +where + R: AsyncReadExt + Unpin + Send + 'static, + W: AsyncWriteExt + Unpin + Send + 'static, +{ + pub async fn new(reader: R, writer: W) -> Result> { + let reader = Arc::new(Mutex::new(reader)); + let writer = Arc::new(Mutex::new(writer)); + let json = timeout(Duration::from_secs(5), read_json(Arc::clone(&reader))) + .await + .context("read config json timeout")? + .context("read config json")?; + debug!("config json: {}", &json); + let op = serde_json::from_str::(&json) + .with_context(|| format!("deserialize config json failed: {}", json))?; + let config = match op { + OP::Config(config) => config, + _ => { + bail!("invalid config json {}", &json); + } + }; + + let rtc_config = RTCConfiguration { + ice_servers: vec![RTCIceServer { + urls: config.stuns, + ..Default::default() + }], + ..Default::default() + }; + + let mut m = MediaEngine::default(); + m.register_default_codecs() + .context("register default codecs")?; + + let mut registry = Registry::new(); + + registry = register_default_interceptors(registry, &mut m) + .context("register default interceptors")?; + + let mut s = SettingEngine::default(); + s.set_udp_network(UDPNetwork::Ephemeral( + udp_network::EphemeralUDP::new(config.port_min, config.port_max) + .context("create udp network")?, + )); + s.detach_data_channels(); + + let api = APIBuilder::new() + .with_media_engine(m) + .with_interceptor_registry(registry) + .with_setting_engine(s) + .build(); + + let peer_connection = Arc::new( + api.new_peer_connection(rtc_config) + .await + .context("new pc")?, + ); + + let timeout = config.timeout.max(5); + Ok(Arc::new(PeerConnHandler { + reader, + writer, + peer_connection, + timeout, + http_routes: config.http_routes, + tcp_routes: config.tcp_routes, + channel_count: Default::default(), + no_channel_id: Default::default(), + })) + } + + fn setup_data_channel(self: Arc, d: Arc) { + let dc = Arc::clone(&d); + d.on_open(Box::new(|| { + self.channel_count.fetch_add(1, Ordering::Acquire); + self.new_data_channel_process_handler(dc) + })); + } + + fn new_data_channel_process_handler( + self: Arc, + d: Arc, + ) -> Pin + Sized>> { + Box::pin(async move { + let label = d.label(); + info!("data channel '{}'-'{}' open.", label, d.id()); + let target = label.split_once('/').map_or_else( + || self.http_routes.get("@"), + |(t, _)| { + t.get(0..1).map_or_else( + || self.http_routes.get("@"), + |c| { + t.get(1..).map_or_else( + || self.http_routes.get(t), + |r| { + if c == "@" && !r.is_empty() { + self.http_routes.get(r) + } else if c == ":" && !r.is_empty() { + self.tcp_routes.get(r) + } else { + self.http_routes.get(t) + } + }, + ) + }, + ) + }, + ); + if let Some(target) = target { + info!("{} connect to {}", label, target); + let dc = Arc::clone(&d); + if let Err(err) = self.connect_target(target, dc).await { + info!("{} failed to connect to {}: {}", label, target, err); + } + } else { + error!("no routes for {}", label); + } + info!("data channel '{}'-'{}' done.", label, d.id()); + let _ = self + .channel_count + .fetch_update(Ordering::Release, Ordering::Relaxed, |v| { + if v == 1 { + self.no_channel_id.fetch_add(1, Ordering::Relaxed); + } + Some(v - 1) + }); + }) + } + + async fn connect_target(&self, target: &str, d: Arc) -> Result<()> { + let url = Url::parse(target).context("invalid url")?; + let addrs = url + .socket_addrs(|| match url.scheme() { + "http" | "ws" | "tcp" => Some(80), + "https" | "wss" | "tls" => Some(443), + _ => Some(80), + }) + .context("no address")?; + let raw = d.detach().await.context("detach data channel")?; + + let mut s = TcpStream::connect(&*addrs) + .await + .context("connect to service")?; + let result = io::copy_bidirectional(&mut PollDataChannel::new(raw), &mut s).await; + match result { + Ok((a, b)) => { + info!("{} copy done: {}, {}", d.label(), a, b); + } + Err(err) => { + error!("{} copy err: {}", d.label(), err); + bail!(err); + } + } + Ok(()) + } + + pub async fn handle(self: Arc) -> Result<()> { + let writer_on_ice_candidate = Arc::clone(&self.writer); + self.peer_connection + .on_ice_candidate(Box::new(move |c: Option| { + info!("on_ice_candidate {:?}", c); + let writer_on_ice_candidate = Arc::clone(&writer_on_ice_candidate); + Box::pin(async move { + if let Some(c) = c { + let json = match c.to_json() { + Err(e) => { + error!("failed to serialize ice candidate: {}", e); + return; + } + Ok(json) => json, + }; + let json = match serde_json::to_string(&json) { + Err(e) => { + error!("failed to serialize ice candidate init: {}", e); + return; + } + Ok(json) => json, + }; + let op = OP::Candidate(json); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } else { + let op = OP::Candidate("".to_owned()); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } + }) + })); + + let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::>(1); + + self.peer_connection + .on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| { + info!("peer Connection State has changed: {s}"); + match s { + RTCPeerConnectionState::Unspecified => {} + RTCPeerConnectionState::New => {} + RTCPeerConnectionState::Connecting => {} + RTCPeerConnectionState::Connected => {} + RTCPeerConnectionState::Disconnected => {} + RTCPeerConnectionState::Failed => { + let _ = done_tx.try_send(Err(anyhow!("peer connection state failed"))); + } + RTCPeerConnectionState::Closed => {} + } + + Box::pin(async {}) + })); + + let handler = Arc::clone(&self); + self.peer_connection + .on_data_channel(Box::new(move |d: Arc| { + info!("new dataChannel {} {}", d.label(), d.id()); + let handler = Arc::clone(&handler); + handler.setup_data_channel(d); + Box::pin(async {}) + })); + + let mut no_channel_id: usize = 0; + loop { + let sleep = time::sleep(Duration::from_secs(self.timeout as u64)); + tokio::pin!(sleep); + let json = select! { + result = read_json(Arc::clone(&self.reader)) => { + result? + }, + rx = done_rx.recv() => { + return match rx { + None => { + Ok(()) + } + Some(result) => { + result + } + } + } + _ = &mut sleep => { + if self.channel_count.load(Ordering::Acquire) == 0 { + let id = self.no_channel_id.load(Ordering::Relaxed); + if no_channel_id == id { + return Err(LibError::NoChannelInPeerConnectionTimeout.into()); + } else { + no_channel_id = id; + } + } + continue; + } + }; + debug!("op json: {}", &json); + let op = serde_json::from_str::(&json) + .with_context(|| format!("parse op json: {}", json))?; + + let pc = Arc::clone(&self.peer_connection); + match op { + OP::OfferSDP(sdp) => { + let sdp = serde_json::from_str::(&sdp) + .context("offer sdp from op")?; + pc.set_remote_description(sdp) + .await + .context("set remote description")?; + let answer = pc.create_answer(None).await.context("create answer")?; + let sdp = serde_json::to_string(&answer).context("serialize answer")?; + let op = OP::AnswerSDP(sdp); + write_json( + Arc::clone(&self.writer), + &serde_json::to_string(&op).context("encode op")?, + ) + .await + .context("write answer sdp to stdout")?; + pc.set_local_description(answer) + .await + .context("set local description")?; + } + OP::Candidate(candidate) => { + if candidate.is_empty() { + continue; + } + let candidate = serde_json::from_str::(&candidate) + .context("candidate from op")?; + pc.add_ice_candidate(candidate) + .await + .context("add candidate")?; + } + OP::GetOfferSDP { channel_name } => { + let data_channel = pc + .create_data_channel(&channel_name, None) + .await + .context("create data channel")?; + let handler = Arc::clone(&self); + handler.setup_data_channel(data_channel); + let offer = pc.create_offer(None).await.context("create offer")?; + let sdp = serde_json::to_string(&offer).context("serialize answer")?; + let op = OP::OfferSDP(sdp); + write_json( + Arc::clone(&self.writer), + &serde_json::to_string(&op).context("encode op")?, + ) + .await + .context("write answer sdp to stdout")?; + pc.set_local_description(offer) + .await + .context("set local description")?; + } + OP::AnswerSDP(sdp) => { + let sdp = serde_json::from_str::(&sdp) + .context("answer sdp from op")?; + pc.set_remote_description(sdp) + .await + .context("set remote description")?; + } + _ => { + bail!("invalid op {:?}", op) + } + }; + } + } +} diff --git a/dep/p2p/src/lib.rs b/dep/p2p/src/lib.rs new file mode 100644 index 00000000..6018f6c6 --- /dev/null +++ b/dep/p2p/src/lib.rs @@ -0,0 +1,141 @@ +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::{anyhow, Context, Result}; +use env_logger::Env; +use log::*; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tokio::io; +use tokio::io::{stdin, stdout}; +use tokio::sync::Mutex; + +mod conn; + +#[no_mangle] +pub extern "C" fn create_peer_connection() { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + rt.block_on(async { + match process(stdin(), stdout()).await { + Ok(_) => { + info!("create_peer_connection done"); + } + Err(e) => { + error!("create_peer_connection err: {:?}", e); + } + }; + }); + eprintln!("p2p done"); + rt.shutdown_timeout(Duration::from_millis(100)); +} + +pub async fn process(reader: R, writer: W) -> Result<()> +where + R: io::AsyncReadExt + Unpin + Send + 'static, + W: io::AsyncWriteExt + Unpin + Send + 'static, +{ + let handler = conn::PeerConnHandler::new(reader, writer).await?; + handler.handle().await +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(default, rename_all = "camelCase")] +pub struct Config { + pub stuns: Vec, + pub http_routes: HashMap, + pub tcp_routes: HashMap, + pub port_min: u16, + pub port_max: u16, + pub timeout: u16, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum OP { + Config(Config), + OfferSDP(String), + AnswerSDP(String), + Candidate(String), + GetOfferSDP { + #[serde(rename = "channelName")] + channel_name: String, + }, +} + +pub async fn read_json(reader: Arc>) -> Result +where + R: io::AsyncReadExt + Unpin, +{ + const MAX_JSON_LENGTH: u32 = 8 * 1024; + let mut buffer = [0; 4]; + let mut reader = reader.lock().await; + reader + .read_exact(&mut buffer) + .await + .with_context(|| "failed to receive header")?; + let length = u32::from_be_bytes(buffer); + if length > MAX_JSON_LENGTH { + return Err(anyhow!("json too large: {}", length)); + } + let mut buffer = vec![0; length as usize]; + reader + .read_exact(&mut buffer) + .await + .with_context(|| "failed to receive json")?; + let result = String::from_utf8(buffer).with_context(|| "not utf8 json")?; + Ok(result) +} + +pub async fn write_json(writer: Arc>, json: &str) -> Result<()> +where + W: io::AsyncWriteExt + Unpin, +{ + let mut writer = writer.lock().await; + let l = json.len() as u32; + writer + .write_all(&l.to_be_bytes()) + .await + .with_context(|| "write answer len")?; + writer + .write_all(json.as_bytes()) + .await + .with_context(|| "write answer")?; + writer.flush().await.with_context(|| "flush answer")?; + Ok(()) +} + +#[derive(Error, Debug)] +pub enum LibError { + #[error("no channel in peer connection timeout")] + NoChannelInPeerConnectionTimeout, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[ignore] + #[test] + fn test_op_json() { + let op = OP::Config(Config { + stuns: vec!["stun:stun.l.google.com:19302".to_owned()], + http_routes: HashMap::from([ + ("www".to_owned(), "http://www.baidu.com".to_owned()), + ("default".to_owned(), "http://www.baidu.com".to_owned()), + ]), + ..Default::default() + }); + println!("{}", serde_json::to_string(&op).unwrap()); + let op = OP::OfferSDP("abc".to_owned()); + println!("{}", serde_json::to_string(&op).unwrap()); + let op = OP::GetOfferSDP { + channel_name: "abc".to_owned(), + }; + println!("{}", serde_json::to_string(&op).unwrap()); + } +} diff --git a/dep/p2p/tests/answer.rs b/dep/p2p/tests/answer.rs new file mode 100644 index 00000000..676dbacb --- /dev/null +++ b/dep/p2p/tests/answer.rs @@ -0,0 +1,272 @@ +use std::collections::HashMap; +use std::process; +use std::sync::Arc; + +use anyhow::anyhow; +use bytes::Bytes; +use http_body_util::Empty; +use hyper::{Request, StatusCode}; +use log::error; +use tokio::io; +use tokio::sync::Mutex; +use webrtc::api::interceptor_registry::register_default_interceptors; +use webrtc::api::media_engine::MediaEngine; +use webrtc::api::setting_engine::SettingEngine; +use webrtc::api::APIBuilder; +use webrtc::data::data_channel::PollDataChannel; +use webrtc::data_channel::RTCDataChannel; +use webrtc::ice_transport::ice_candidate::{RTCIceCandidate, RTCIceCandidateInit}; +use webrtc::ice_transport::ice_server::RTCIceServer; +use webrtc::interceptor::registry::Registry; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; + +use common::compat::*; +use common::log::*; +use p2p::*; + +mod common; + +#[test] +fn answer_works() { + init(); + let (server_writer, client_reader) = io::duplex(8 * 1024); + let (server_reader, client_writer) = io::duplex(8 * 1024); + + let reader = Arc::new(Mutex::new(client_reader)); + let writer = Arc::new(Mutex::new(client_writer)); + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.spawn(async move { + if let Err(e) = process(server_reader, server_writer).await { + eprintln!("process: {}", e); + process::exit(1); + }; + }); + rt.block_on(async move { + let op = OP::Config(Config { + stuns: vec!["stun:stun.l.google.com:19302".to_owned()], + http_routes: HashMap::from([("www".to_owned(), "http://www.baidu.com".to_owned())]), + ..Default::default() + }); + write_json(Arc::clone(&writer), &serde_json::to_string(&op).unwrap()) + .await + .map_err(|e| println!("write json error: {:?}", e)) + .expect("write json"); + + let op = OP::GetOfferSDP { + channel_name: "@www/uuid".to_owned(), + }; + write_json( + Arc::clone(&writer), + &serde_json::to_string(&op).expect("encode op"), + ) + .await + .expect("write get offer to stdout"); + + // begin + let config = RTCConfiguration { + ice_servers: vec![RTCIceServer { + urls: vec!["stun:stun.l.google.com:19302".to_owned()], + ..Default::default() + }], + ..Default::default() + }; + + let mut m = MediaEngine::default(); + m.register_default_codecs() + .expect("register default codecs"); + + let mut registry = Registry::new(); + + registry = + register_default_interceptors(registry, &mut m).expect("register default interceptors"); + + let mut s = SettingEngine::default(); + s.detach_data_channels(); + + let api = APIBuilder::new() + .with_media_engine(m) + .with_interceptor_registry(registry) + .with_setting_engine(s) + .build(); + + let peer_connection = Arc::new(api.new_peer_connection(config).await.expect("new pc")); + + let writer_on_ice_candidate = Arc::clone(&writer); + peer_connection.on_ice_candidate(Box::new(move |c: Option| { + let writer_on_ice_candidate = Arc::clone(&writer_on_ice_candidate); + Box::pin(async move { + if let Some(c) = c { + let json = match c.to_json() { + Err(e) => { + error!("failed to serialize ice candidate: {}", e); + return; + } + Ok(json) => json, + }; + let json = match serde_json::to_string(&json) { + Err(e) => { + error!("failed to serialize ice candidate init: {}", e); + return; + } + Ok(json) => json, + }; + let op = OP::Candidate(json); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } else { + let op = OP::Candidate("".to_owned()); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } + }) + })); + + let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::>(1); + let done_tx_failed = done_tx.clone(); + peer_connection.on_peer_connection_state_change(Box::new( + move |s: RTCPeerConnectionState| { + println!("Peer Connection State has changed: {s}"); + match s { + RTCPeerConnectionState::Unspecified => {} + RTCPeerConnectionState::New => {} + RTCPeerConnectionState::Connecting => {} + RTCPeerConnectionState::Connected => {} + RTCPeerConnectionState::Disconnected => {} + RTCPeerConnectionState::Failed => { + let _ = done_tx_failed.try_send(Err(anyhow!("connection state failed"))); + } + RTCPeerConnectionState::Closed => {} + } + + Box::pin(async {}) + }, + )); + + peer_connection.on_data_channel(Box::new(move |dc: Arc| { + println!("New DataChannel {} {}", dc.label(), dc.id()); + let done_tx_on_open = done_tx.clone(); + let data_channel = Arc::clone(&dc); + data_channel.on_open(Box::new(|| { + Box::pin(async move { + println!( + "Data channel '{}'-'{}' open. Request is being sent", + dc.label(), + dc.id() + ); + let raw = dc.detach().await.expect("detach data channel"); + let stream = Compat::new(PollDataChannel::new(raw)); + let (mut sender, conn) = hyper::client::conn::http1::handshake(stream) + .await + .expect("handshake"); + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + let req = Request::builder() + .header(hyper::header::HOST, "www.baidu.com") + .method("GET") + .body(Empty::::new()) + .expect("build request"); + + let res = sender.send_request(req).await.expect("send request"); + // let mut res = sender.send_request(req).await.expect("send request"); + // println!("Response: {}", res.status()); + // while let Some(next) = res.frame().await { + // let frame = next.expect("read frame"); + // if let Some(chunk) = frame.data_ref() { + // io::stderr().write_all(chunk).await.expect("write chunk"); + // } + // } + // println!("\n"); + if res.status() == StatusCode::OK { + let _ = done_tx_on_open.try_send(Ok(())); + } + }) + })); + Box::pin(async {}) + })); + + loop { + let json = tokio::select! { + Ok(json) = read_json(Arc::clone(&reader)) => json.clone(), + result = done_rx.recv() => { + match result { + Some(r) => { + if let Err(err) = r { + println!("received pc failed signal: {}", err); + } else { + // received 200 http response + println!("received 200 success signal"); + return; + } + } + None => { + println!("received pc failed signal!"); + } + } + process::exit(1); + }, + else => { + process::exit(2); + } + }; + let op = serde_json::from_str::(&json).expect("parse op json"); + + let pc = Arc::clone(&peer_connection); + match op { + OP::OfferSDP(sdp) => { + let sdp = serde_json::from_str::(&sdp) + .expect("offer sdp from op"); + pc.set_remote_description(sdp) + .await + .expect("set remote description"); + let answer = pc.create_answer(None).await.expect("create answer"); + let sdp = serde_json::to_string(&answer).expect("serialize answer"); + let op = OP::AnswerSDP(sdp); + write_json( + Arc::clone(&writer), + &serde_json::to_string(&op).expect("encode op"), + ) + .await + .expect("write answer sdp to stdout"); + pc.set_local_description(answer) + .await + .expect("set local description"); + } + OP::Candidate(candidate) => { + if candidate.is_empty() { + continue; + } + let candidate = serde_json::from_str::(&candidate) + .expect("candidate from op"); + pc.add_ice_candidate(candidate) + .await + .expect("add candidate") + } + _ => { + panic!("invalid op {:?}", op) + } + }; + } + }); +} diff --git a/dep/p2p/tests/common/compat.rs b/dep/p2p/tests/common/compat.rs new file mode 100644 index 00000000..30361469 --- /dev/null +++ b/dep/p2p/tests/common/compat.rs @@ -0,0 +1,80 @@ +use std::pin::Pin; +use std::task::Poll; + +use hyper::rt; +use tokio::io; + +pub struct Compat(T); + +impl Compat { + pub fn new(io: T) -> Self { + Compat(io) + } + + fn p(self: Pin<&mut Self>) -> Pin<&mut T> { + unsafe { self.map_unchecked_mut(|me| &mut me.0) } + } +} + +impl rt::Read for Compat +where + T: io::AsyncRead, +{ + fn poll_read( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + mut buf: rt::ReadBufCursor<'_>, + ) -> Poll> { + let n = unsafe { + let mut tbuf = io::ReadBuf::uninit(buf.as_mut()); + match io::AsyncRead::poll_read(self.p(), cx, &mut tbuf) { + Poll::Ready(Ok(())) => tbuf.filled().len(), + other => return other, + } + }; + + unsafe { + buf.advance(n); + } + Poll::Ready(Ok(())) + } +} + +impl rt::Write for Compat +where + T: io::AsyncWrite, +{ + fn poll_write( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + io::AsyncWrite::poll_write(self.p(), cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + io::AsyncWrite::poll_flush(self.p(), cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + io::AsyncWrite::poll_shutdown(self.p(), cx) + } + + fn is_write_vectored(&self) -> bool { + io::AsyncWrite::is_write_vectored(&self.0) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + io::AsyncWrite::poll_write_vectored(self.p(), cx, bufs) + } +} diff --git a/dep/p2p/tests/common/log.rs b/dep/p2p/tests/common/log.rs new file mode 100644 index 00000000..f88ecf13 --- /dev/null +++ b/dep/p2p/tests/common/log.rs @@ -0,0 +1,13 @@ +use std::sync::Once; + +use env_logger::Env; + +static INIT: Once = Once::new(); + +pub fn init() { + INIT.call_once(|| { + let _ = env_logger::Builder::from_env(Env::default().default_filter_or("debug")) + .is_test(true) + .try_init(); + }); +} diff --git a/dep/p2p/tests/common/mod.rs b/dep/p2p/tests/common/mod.rs new file mode 100644 index 00000000..fd6cdfc3 --- /dev/null +++ b/dep/p2p/tests/common/mod.rs @@ -0,0 +1,2 @@ +pub mod compat; +pub mod log; diff --git a/dep/p2p/tests/offer.rs b/dep/p2p/tests/offer.rs new file mode 100644 index 00000000..74169b31 --- /dev/null +++ b/dep/p2p/tests/offer.rs @@ -0,0 +1,515 @@ +use std::collections::HashMap; +use std::process; +use std::sync::Arc; + +use anyhow::anyhow; +use bytes::Bytes; +use http_body_util::Empty; +use hyper::{Request, StatusCode}; +use log::{error, info}; +use tokio::io; +use tokio::sync::Mutex; +use webrtc::api::interceptor_registry::register_default_interceptors; +use webrtc::api::media_engine::MediaEngine; +use webrtc::api::setting_engine::SettingEngine; +use webrtc::api::APIBuilder; +use webrtc::data::data_channel::PollDataChannel; +use webrtc::ice_transport::ice_candidate::{RTCIceCandidate, RTCIceCandidateInit}; +use webrtc::ice_transport::ice_server::RTCIceServer; +use webrtc::interceptor::registry::Registry; +use webrtc::peer_connection::configuration::RTCConfiguration; +use webrtc::peer_connection::peer_connection_state::RTCPeerConnectionState; +use webrtc::peer_connection::sdp::session_description::RTCSessionDescription; + +use common::compat::*; +use common::log::*; +use p2p::*; + +mod common; + +#[test] +fn offer_works() { + init(); + let (server_writer, client_reader) = io::duplex(8 * 1024); + let (server_reader, client_writer) = io::duplex(8 * 1024); + + let reader = Arc::new(Mutex::new(client_reader)); + let writer = Arc::new(Mutex::new(client_writer)); + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.spawn(async move { + if let Err(e) = process(server_reader, server_writer).await { + eprintln!("process: {}", e); + process::exit(1); + }; + }); + rt.block_on(async move { + let op = OP::Config(Config { + stuns: vec!["stun:stun.l.google.com:19302".to_owned()], + http_routes: HashMap::from([("@".to_owned(), "http://www.baidu.com".to_owned())]), + ..Default::default() + }); + write_json(Arc::clone(&writer), &serde_json::to_string(&op).unwrap()) + .await + .map_err(|e| println!("write json error: {:?}", e)) + .expect("write json"); + + // begin + let config = RTCConfiguration { + ice_servers: vec![RTCIceServer { + urls: vec!["stun:stun.l.google.com:19302".to_owned()], + ..Default::default() + }], + ..Default::default() + }; + + let mut m = MediaEngine::default(); + m.register_default_codecs() + .expect("register default codecs"); + + let mut registry = Registry::new(); + + registry = + register_default_interceptors(registry, &mut m).expect("register default interceptors"); + + let mut s = SettingEngine::default(); + s.detach_data_channels(); + + let api = APIBuilder::new() + .with_media_engine(m) + .with_interceptor_registry(registry) + .with_setting_engine(s) + .build(); + + let peer_connection = Arc::new(api.new_peer_connection(config).await.expect("new pc")); + + let writer_on_ice_candidate = Arc::clone(&writer); + peer_connection.on_ice_candidate(Box::new(move |c: Option| { + let writer_on_ice_candidate = Arc::clone(&writer_on_ice_candidate); + Box::pin(async move { + if let Some(c) = c { + let json = match c.to_json() { + Err(e) => { + error!("failed to serialize ice candidate: {}", e); + return; + } + Ok(json) => json, + }; + let json = match serde_json::to_string(&json) { + Err(e) => { + error!("failed to serialize ice candidate init: {}", e); + return; + } + Ok(json) => json, + }; + let op = OP::Candidate(json); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } else { + let op = OP::Candidate("".to_owned()); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } + }) + })); + + let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::>(1); + let done_tx_failed = done_tx.clone(); + peer_connection.on_peer_connection_state_change(Box::new( + move |s: RTCPeerConnectionState| { + println!("Peer Connection State has changed: {s}"); + match s { + RTCPeerConnectionState::Unspecified => {} + RTCPeerConnectionState::New => {} + RTCPeerConnectionState::Connecting => {} + RTCPeerConnectionState::Connected => {} + RTCPeerConnectionState::Disconnected => {} + RTCPeerConnectionState::Failed => { + let _ = done_tx_failed.try_send(Err(anyhow!("connection state failed"))); + } + RTCPeerConnectionState::Closed => {} + } + + Box::pin(async {}) + }, + )); + + let data_channel = peer_connection + .create_data_channel("@/uuid", None) + .await + .expect("create data channel"); + let dc = Arc::clone(&data_channel); + data_channel.on_open(Box::new(|| { + Box::pin(async move { + println!( + "Data channel '{}'-'{}' open. Request is being sent", + dc.label(), + dc.id() + ); + let raw = dc.detach().await.expect("detach data channel"); + let stream = Compat::new(PollDataChannel::new(raw)); + let (mut sender, conn) = hyper::client::conn::http1::handshake(stream) + .await + .expect("handshake"); + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + let req = Request::builder() + .header(hyper::header::HOST, "www.baidu.com") + .method("GET") + .body(Empty::::new()) + .expect("build request"); + + let res = sender.send_request(req).await.expect("send request"); + // let mut res = sender.send_request(req).await.expect("send request"); + // println!("Response: {}", res.status()); + // while let Some(next) = res.frame().await { + // let frame = next.expect("read frame"); + // if let Some(chunk) = frame.data_ref() { + // io::stderr().write_all(chunk).await.expect("write chunk"); + // } + // } + // println!("\n"); + if res.status() == StatusCode::OK { + let _ = done_tx.try_send(Ok(())); + } + }) + })); + + let offer = peer_connection + .create_offer(None) + .await + .expect("create offer"); + + let sdp = serde_json::to_string(&offer).expect("encode offer"); + + peer_connection + .set_local_description(offer) + .await + .expect("set local description"); + + let op = OP::OfferSDP(sdp); + write_json( + Arc::clone(&writer), + &serde_json::to_string(&op).expect("encode op"), + ) + .await + .expect("write offer sdp to stdout"); + + loop { + let json = tokio::select! { + Ok(json) = read_json(Arc::clone(&reader)) => json.clone(), + result = done_rx.recv() => { + match result { + Some(r) => { + if let Err(err) = r { + println!("received pc failed signal: {}", err); + } else { + // received 200 http response + println!("received 200 success signal"); + return; + } + } + None => { + println!("received pc failed signal!"); + } + } + process::exit(1); + }, + else => { + process::exit(2); + } + }; + let op = serde_json::from_str::(&json).expect("parse op json"); + + let pc = Arc::clone(&peer_connection); + match op { + OP::Candidate(candidate) => { + if candidate.is_empty() { + continue; + } + let candidate = serde_json::from_str::(&candidate) + .expect("candidate from op"); + pc.add_ice_candidate(candidate) + .await + .expect("add candidate") + } + OP::AnswerSDP(sdp) => { + let sdp = serde_json::from_str::(&sdp) + .expect("answer sdp from op"); + pc.set_remote_description(sdp) + .await + .expect("set remote description"); + } + _ => { + panic!("invalid op {:?}", op) + } + }; + } + }); +} + +#[test] +fn offer_timeout_works() { + init(); + let (server_writer, client_reader) = io::duplex(8 * 1024); + let (server_reader, client_writer) = io::duplex(8 * 1024); + + let reader = Arc::new(Mutex::new(client_reader)); + let writer = Arc::new(Mutex::new(client_writer)); + let rt = tokio::runtime::Runtime::new().unwrap(); + let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::>(1); + let p2p_done = done_tx.clone(); + rt.spawn(async move { + let result = process(server_reader, server_writer).await; + match result { + Ok(()) => panic!("expect timeout"), + Err(e) => match e.downcast::() { + Ok(LibError::NoChannelInPeerConnectionTimeout) => { + info!("passed"); + p2p_done.send(Ok(())).await.expect("passed"); + } + _ => panic!("expect timeout"), + }, + } + }); + rt.block_on(async move { + let op = OP::Config(Config { + stuns: vec!["stun:stun.l.google.com:19302".to_owned()], + http_routes: HashMap::from([("@".to_owned(), "http://www.baidu.com".to_owned())]), + ..Default::default() + }); + write_json(Arc::clone(&writer), &serde_json::to_string(&op).unwrap()) + .await + .map_err(|e| println!("write json error: {:?}", e)) + .expect("write json"); + + // begin + let config = RTCConfiguration { + ice_servers: vec![RTCIceServer { + urls: vec!["stun:stun.l.google.com:19302".to_owned()], + ..Default::default() + }], + ..Default::default() + }; + + let mut m = MediaEngine::default(); + m.register_default_codecs() + .expect("register default codecs"); + + let mut registry = Registry::new(); + + registry = + register_default_interceptors(registry, &mut m).expect("register default interceptors"); + + let mut s = SettingEngine::default(); + s.detach_data_channels(); + + let api = APIBuilder::new() + .with_media_engine(m) + .with_interceptor_registry(registry) + .with_setting_engine(s) + .build(); + + let peer_connection = Arc::new(api.new_peer_connection(config).await.expect("new pc")); + + let writer_on_ice_candidate = Arc::clone(&writer); + peer_connection.on_ice_candidate(Box::new(move |c: Option| { + let writer_on_ice_candidate = Arc::clone(&writer_on_ice_candidate); + Box::pin(async move { + if let Some(c) = c { + let json = match c.to_json() { + Err(e) => { + error!("failed to serialize ice candidate: {}", e); + return; + } + Ok(json) => json, + }; + let json = match serde_json::to_string(&json) { + Err(e) => { + error!("failed to serialize ice candidate init: {}", e); + return; + } + Ok(json) => json, + }; + let op = OP::Candidate(json); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } else { + let op = OP::Candidate("".to_owned()); + let json = match serde_json::to_string(&op) { + Err(e) => { + error!("failed to serialize op: {}", e); + return; + } + Ok(json) => json, + }; + if let Err(e) = write_json(writer_on_ice_candidate, &json).await { + error!("failed to write ice candidate: {}", e); + } + } + }) + })); + + let done_tx_failed = done_tx.clone(); + peer_connection.on_peer_connection_state_change(Box::new( + move |s: RTCPeerConnectionState| { + println!("Peer Connection State has changed: {s}"); + match s { + RTCPeerConnectionState::Unspecified => {} + RTCPeerConnectionState::New => {} + RTCPeerConnectionState::Connecting => {} + RTCPeerConnectionState::Connected => {} + RTCPeerConnectionState::Disconnected => {} + RTCPeerConnectionState::Failed => { + let _ = done_tx_failed.try_send(Err(anyhow!("connection state failed"))); + } + RTCPeerConnectionState::Closed => {} + } + + Box::pin(async {}) + }, + )); + + let data_channel = peer_connection + .create_data_channel("uuid", None) + .await + .expect("create data channel"); + let dc = Arc::clone(&data_channel); + data_channel.on_open(Box::new(|| { + Box::pin(async move { + println!( + "Data channel '{}'-'{}' open. Request is being sent", + dc.label(), + dc.id() + ); + let raw = dc.detach().await.expect("detach data channel"); + let stream = Compat::new(PollDataChannel::new(raw)); + let (mut sender, conn) = hyper::client::conn::http1::handshake(stream) + .await + .expect("handshake"); + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + let req = Request::builder() + .header(hyper::header::HOST, "www.baidu.com") + .method("GET") + .body(Empty::::new()) + .expect("build request"); + + let res = sender.send_request(req).await.expect("send request"); + // let mut res = sender.send_request(req).await.expect("send request"); + // println!("Response: {}", res.status()); + // while let Some(next) = res.frame().await { + // let frame = next.expect("read frame"); + // if let Some(chunk) = frame.data_ref() { + // io::stderr().write_all(chunk).await.expect("write chunk"); + // } + // } + // println!("\n"); + if res.status() == StatusCode::OK { + println!("received 200 success signal"); + dc.close().await.expect("close channel"); + } + }) + })); + + let offer = peer_connection + .create_offer(None) + .await + .expect("create offer"); + + let sdp = serde_json::to_string(&offer).expect("encode offer"); + + peer_connection + .set_local_description(offer) + .await + .expect("set local description"); + + let op = OP::OfferSDP(sdp); + write_json( + Arc::clone(&writer), + &serde_json::to_string(&op).expect("encode op"), + ) + .await + .expect("write offer sdp to stdout"); + + loop { + let json = tokio::select! { + Ok(json) = read_json(Arc::clone(&reader)) => json.clone(), + result = done_rx.recv() => { + match result { + Some(r) => { + if let Err(err) = r { + println!("received pc failed signal: {}", err); + } else { + return; + } + } + None => { + println!("done_rx done"); + } + } + process::exit(1); + }, + else => { + process::exit(2); + } + }; + let op = serde_json::from_str::(&json).expect("parse op json"); + + let pc = Arc::clone(&peer_connection); + match op { + OP::Candidate(candidate) => { + if candidate.is_empty() { + continue; + } + let candidate = serde_json::from_str::(&candidate) + .expect("candidate from op"); + pc.add_ice_candidate(candidate) + .await + .expect("add candidate") + } + OP::AnswerSDP(sdp) => { + let sdp = serde_json::from_str::(&sdp) + .expect("answer sdp from op"); + pc.set_remote_description(sdp) + .await + .expect("set remote description"); + } + _ => { + panic!("invalid op {:?}", op) + } + }; + } + }); +}