Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements

This commit is contained in:
Shtorm
2026-08-06 14:19:34 +03:00
parent 051e928b01
commit d6b9f693c4
89 changed files with 15671 additions and 132 deletions

View File

@@ -0,0 +1,472 @@
package livekit
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v4"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/transport/call/common"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
const (
ProtocolVersion = "15"
SDKName = "js"
SDKVersion = "2.7.0"
PingPeriod = 5 * time.Second
TargetPublisher = signalTargetPublisher
TargetSubscriber = signalTargetSubscriber
TrackTypeAudio = trackTypeAudio
TrackTypeVideo = trackTypeVideo
TrackTypeData = trackTypeData
TrackSourceCamera = trackSourceCamera
TrackSourceScreenShare = trackSourceScreenShare
)
type ICEServer = iceServer
type JoinResponse = joinResponse
type Config struct {
ServerURL string
Token string
Origin string
UserAgent string
Logger logger.ContextLogger
SettingEngine *webrtc.SettingEngine
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
DNSRouter adapter.DNSRouter
Dialer N.Dialer
}
type Client struct {
logger logger.ContextLogger
wsURL string
token string
origin string
ua string
settingEngine *webrtc.SettingEngine
netDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
dnsRouter adapter.DNSRouter
dialer N.Dialer
ws *websocket.Conn
wsMu sync.Mutex
join JoinResponse
pubPC *webrtc.PeerConnection
subPC *webrtc.PeerConnection
pubMu sync.Mutex
subMu sync.Mutex
pubRemoteSet bool
subRemoteSet bool
closed atomic.Bool
OnReady func()
OnTrack func(*webrtc.TrackRemote, *webrtc.RTPReceiver)
OnDataChannel func(*webrtc.DataChannel)
OnPubConnected func()
OnParticipantUpdate func([]ParticipantInfo)
OnRemoteCandidate func(target int, candidate webrtc.ICECandidateInit)
OnRemoteSDP func(target int, sdpType, sdp string)
}
func NewClient(cfg Config) *Client {
return &Client{
logger: cfg.Logger,
wsURL: cfg.ServerURL,
token: cfg.Token,
origin: cfg.Origin,
ua: cfg.UserAgent,
settingEngine: cfg.SettingEngine,
netDialContext: cfg.NetDialContext,
dnsRouter: cfg.DNSRouter,
dialer: cfg.Dialer,
}
}
func (c *Client) Join() JoinResponse { return c.join }
func (c *Client) PubPC() *webrtc.PeerConnection { return c.pubPC }
func (c *Client) SubPC() *webrtc.PeerConnection { return c.subPC }
func (c *Client) Connect() error {
u, err := url.Parse(c.wsURL)
if err != nil {
return fmt.Errorf("parse url: %w", err)
}
u.Path = "/rtc"
q := u.Query()
q.Set("access_token", c.token)
q.Set("protocol", ProtocolVersion)
q.Set("sdk", SDKName)
q.Set("version", SDKVersion)
q.Set("auto_subscribe", "1")
q.Set("adaptive_stream", "true")
u.RawQuery = q.Encode()
headers := http.Header{}
if c.ua != "" {
headers.Set("User-Agent", c.ua)
}
if c.origin != "" {
headers.Set("Origin", c.origin)
}
dialer := *websocket.DefaultDialer
if c.netDialContext != nil {
dialer.NetDialContext = c.netDialContext
}
conn, resp, err := dialer.Dial(u.String(), headers)
if err != nil {
if resp != nil {
return fmt.Errorf("ws dial: %w (status %d)", err, resp.StatusCode)
}
return fmt.Errorf("ws dial: %w", err)
}
c.ws = conn
c.logger.Info("[lk] signaling connected")
return nil
}
func (c *Client) SendOffer(sdp string) error {
return c.sendSignal(encSignalRequestOffer(sessionDescription{Type: "offer", SDP: sdp}))
}
func (c *Client) SendAnswer(sdp string) error {
return c.sendSignal(encSignalRequestAnswer(sessionDescription{Type: "answer", SDP: sdp}))
}
func (c *Client) SendTrickle(candidate webrtc.ICECandidateInit, target int) error {
js, _ := json.Marshal(candidate)
return c.sendSignal(encSignalRequestTrickle(trickleMsg{
CandidateInit: string(js),
Target: target,
}))
}
func (c *Client) SendAddTrack(cid, name string, trackType, source int, width, height uint32) error {
return c.sendSignal(encSignalRequestAddTrack(cid, name, trackType, source, width, height))
}
func (c *Client) SendLeave() error { return c.sendSignal(encSignalRequestLeave()) }
func (c *Client) SendPing() error {
return c.sendSignal(encSignalRequestPing(time.Now().UnixMilli()))
}
func (c *Client) Close() {
if !c.closed.CompareAndSwap(false, true) {
return
}
c.wsMu.Lock()
ws := c.ws
c.wsMu.Unlock()
common.CloseWS(ws)
if c.pubPC != nil {
_ = c.pubPC.Close()
}
if c.subPC != nil {
_ = c.subPC.Close()
}
}
func (c *Client) ReadLoop() error {
defer c.Close()
for {
mt, data, err := c.ws.ReadMessage()
if err != nil {
return err
}
if mt != websocket.BinaryMessage {
continue
}
c.handleSignal(data)
}
}
func (c *Client) PingLoop() {
period := PingPeriod
if c.join.PingIntervalSec > 0 {
period = time.Duration(c.join.PingIntervalSec) * time.Second
}
t := time.NewTicker(period)
defer t.Stop()
var sentN int
for range t.C {
if c.closed.Load() {
return
}
if err := c.SendPing(); err != nil {
c.logger.Warn(fmt.Sprintf("[lk] ping send failed: %v", err))
return
}
sentN++
if sentN <= 3 || sentN%12 == 0 {
c.logger.Debug(fmt.Sprintf("[lk] ping #%d sent", sentN))
}
}
}
func (c *Client) sendSignal(payload []byte) error {
c.wsMu.Lock()
defer c.wsMu.Unlock()
if c.ws == nil {
return fmt.Errorf("ws not connected")
}
return c.ws.WriteMessage(websocket.BinaryMessage, payload)
}
func (c *Client) iceServersAsWebRTC() []webrtc.ICEServer {
out := make([]webrtc.ICEServer, 0, len(c.join.ICEServers))
resolved := make(map[string]string)
for _, s := range c.join.ICEServers {
urls := make([]string, len(s.URLs))
copy(urls, s.URLs)
for k, u := range urls {
host := common.ExtractICEHost(u)
if host == "" || net.ParseIP(host) != nil {
continue
}
ip, ok := resolved[host]
if !ok {
rd, hasRD := c.dialer.(dialer.ResolveDialer)
if c.dnsRouter == nil || !hasRD {
continue
}
var addrs []netip.Addr
var err error
addrs, err = c.dnsRouter.Lookup(context.Background(), host, rd.QueryOptions())
if err != nil {
c.logger.Warn(fmt.Sprintf("[lk] resolve ICE host %s failed: %v", host, err))
continue
}
resolved[host] = addrs[0].String()
c.logger.Debug(fmt.Sprintf("[lk] resolved ICE host %s -> %s", host, addrs[0]))
}
urls[k] = strings.Replace(u, host, ip, 1)
}
ice := webrtc.ICEServer{URLs: urls}
if s.Username != "" {
ice.Username = s.Username
ice.Credential = s.Credential
}
out = append(out, ice)
}
return out
}
func (c *Client) buildPeerConnections() error {
cfg := webrtc.Configuration{ICEServers: c.iceServersAsWebRTC()}
se := webrtc.SettingEngine{}
if c.settingEngine != nil {
se = *c.settingEngine
}
se.DetachDataChannels()
api := webrtc.NewAPI(webrtc.WithSettingEngine(se))
pubPC, err := api.NewPeerConnection(cfg)
if err != nil {
return fmt.Errorf("create pub pc: %w", err)
}
subPC, err := api.NewPeerConnection(cfg)
if err != nil {
_ = pubPC.Close()
return fmt.Errorf("create sub pc: %w", err)
}
c.pubPC = pubPC
c.subPC = subPC
pubPC.OnICECandidate(func(cand *webrtc.ICECandidate) {
if cand == nil {
c.logger.Debug("[lk] pub ICE gathering complete")
return
}
c.logger.Debug(fmt.Sprintf("[lk] pub local cand: %s", cand.String()))
_ = c.SendTrickle(cand.ToJSON(), TargetPublisher)
})
subPC.OnICECandidate(func(cand *webrtc.ICECandidate) {
if cand == nil {
c.logger.Debug("[lk] sub ICE gathering complete")
return
}
c.logger.Debug(fmt.Sprintf("[lk] sub local cand: %s", cand.String()))
_ = c.SendTrickle(cand.ToJSON(), TargetSubscriber)
})
pubPC.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
c.logger.Debug(fmt.Sprintf("[lk] pub PC state: %s", state.String()))
if state == webrtc.PeerConnectionStateConnected && c.OnPubConnected != nil {
c.OnPubConnected()
}
})
subPC.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
c.logger.Debug(fmt.Sprintf("[lk] sub PC state: %s", state.String()))
})
pubPC.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
c.logger.Debug(fmt.Sprintf("[lk] pub ICE state: %s", state.String()))
})
subPC.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
c.logger.Debug(fmt.Sprintf("[lk] sub ICE state: %s", state.String()))
})
subPC.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
c.logger.Debug(fmt.Sprintf("[lk] sub remote track: %s", track.Codec().MimeType))
if c.OnTrack != nil {
c.OnTrack(track, receiver)
}
})
subPC.OnDataChannel(func(dc *webrtc.DataChannel) {
c.logger.Debug(fmt.Sprintf("[lk] sub data channel: %s", dc.Label()))
if c.OnDataChannel != nil {
c.OnDataChannel(dc)
}
})
c.logger.Debug(fmt.Sprintf("[lk] PCs created (%d ICE servers)", len(c.join.ICEServers)))
for i, s := range c.join.ICEServers {
c.logger.Debug(fmt.Sprintf("[lk] iceServer[%d]: urls=%v hasCred=%v", i, s.URLs, s.Username != ""))
}
return nil
}
func (c *Client) handleSignal(data []byte) {
sr, err := decSignalResponse(data)
if err != nil {
c.logger.Warn(fmt.Sprintf("[lk] decode signal: %v", err))
return
}
switch sr.Kind {
case signalRespJoin:
if sr.Join != nil {
c.join = *sr.Join
c.logger.Info(fmt.Sprintf("[lk] join: room=%s participant=%s subscriberPrimary=%v iceServers=%d pingTimeout=%ds pingInterval=%ds",
c.join.RoomName, c.join.ParticipantID, c.join.SubscriberPrimary, len(c.join.ICEServers),
c.join.PingTimeoutSec, c.join.PingIntervalSec))
if err := c.buildPeerConnections(); err != nil {
c.logger.Error(fmt.Sprintf("[lk] %v", err))
return
}
if c.OnReady != nil {
c.OnReady()
}
}
case signalRespAnswer:
c.logger.Debug(fmt.Sprintf("[lk] <- pub answer (%d bytes)", len(sr.SDP.SDP)))
if sr.SDP != nil {
c.applyPubAnswer(sr.SDP.SDP)
}
case signalRespOffer:
c.logger.Debug(fmt.Sprintf("[lk] <- sub offer (%d bytes)", len(sr.SDP.SDP)))
if sr.SDP != nil {
c.applySubOfferAndAnswer(sr.SDP.SDP)
}
case signalRespTrickle:
if sr.Trickle != nil {
c.logger.Debug(fmt.Sprintf("[lk] <- trickle target=%d", sr.Trickle.Target))
c.applyRemoteTrickle(*sr.Trickle)
}
case signalRespRefreshToken:
if sr.Token != "" {
c.token = sr.Token
c.logger.Debug("[lk] token refreshed")
}
case signalRespLeave:
if sr.Leave != nil {
c.logger.Debug(fmt.Sprintf("[lk] ignored leave reason=%s action=%s",
DisconnectReasonName(sr.Leave.Reason), LeaveActionName(sr.Leave.Action)))
} else {
c.logger.Debug("[lk] ignored leave")
}
case signalRespUpdate:
if c.OnParticipantUpdate != nil && len(sr.Participants) > 0 {
c.OnParticipantUpdate(sr.Participants)
}
default:
c.logger.Debug(fmt.Sprintf("[lk] <- signal kind=%d (%d bytes)", sr.Kind, len(data)))
}
}
func (c *Client) applyPubAnswer(sdp string) {
if c.OnRemoteSDP != nil {
c.OnRemoteSDP(TargetPublisher, "answer", sdp)
}
c.pubMu.Lock()
defer c.pubMu.Unlock()
if c.pubPC == nil {
return
}
if err := c.pubPC.SetRemoteDescription(webrtc.SessionDescription{Type: webrtc.SDPTypeAnswer, SDP: sdp}); err != nil {
c.logger.Warn(fmt.Sprintf("[lk] set pub remote answer: %v", err))
return
}
c.pubRemoteSet = true
}
func (c *Client) applySubOfferAndAnswer(sdp string) {
if c.OnRemoteSDP != nil {
c.OnRemoteSDP(TargetSubscriber, "offer", sdp)
}
c.subMu.Lock()
defer c.subMu.Unlock()
if c.subPC == nil {
return
}
if err := c.subPC.SetRemoteDescription(webrtc.SessionDescription{Type: webrtc.SDPTypeOffer, SDP: sdp}); err != nil {
c.logger.Warn(fmt.Sprintf("[lk] set sub remote offer: %v", err))
return
}
c.subRemoteSet = true
answer, err := c.subPC.CreateAnswer(nil)
if err != nil {
c.logger.Warn(fmt.Sprintf("[lk] create sub answer: %v", err))
return
}
if err := c.subPC.SetLocalDescription(answer); err != nil {
c.logger.Warn(fmt.Sprintf("[lk] set sub local answer: %v", err))
return
}
if err := c.SendAnswer(answer.SDP); err != nil {
c.logger.Warn(fmt.Sprintf("[lk] send answer: %v", err))
}
}
func (c *Client) applyRemoteTrickle(m trickleMsg) {
if m.CandidateInit == "" {
return
}
var ic webrtc.ICECandidateInit
if err := json.Unmarshal([]byte(m.CandidateInit), &ic); err != nil {
c.logger.Warn(fmt.Sprintf("[lk] decode trickle candidate: %v", err))
return
}
if c.OnRemoteCandidate != nil {
c.OnRemoteCandidate(m.Target, ic)
}
switch m.Target {
case TargetPublisher:
c.pubMu.Lock()
ready := c.pubRemoteSet
c.pubMu.Unlock()
if ready {
_ = c.pubPC.AddICECandidate(ic)
}
case TargetSubscriber:
c.subMu.Lock()
ready := c.subRemoteSet
c.subMu.Unlock()
if ready {
_ = c.subPC.AddICECandidate(ic)
}
}
}

View File

@@ -0,0 +1,708 @@
package livekit
import "fmt"
const (
signalReqOffer = 1
signalReqAnswer = 2
signalReqTrickle = 3
signalReqAddTrack = 4
signalReqLeave = 8
signalReqPingLegacy = 14
signalReqPingReq = 16
signalRespJoin = 1
signalRespAnswer = 2
signalRespOffer = 3
signalRespTrickle = 4
signalRespUpdate = 5
signalRespTrackPublished = 6
signalRespLeave = 8
signalRespRoomUpdate = 11
signalRespRefreshToken = 16
signalRespPongResp = 20
signalRespRequestResponse = 22
signalRespTrackSubscribed = 23
sdpFieldType = 1
sdpFieldSDP = 2
sdpFieldID = 3
trickleFieldCandidate = 1
trickleFieldTarget = 2
trickleFieldFinal = 3
addTrackFieldCID = 1
addTrackFieldName = 2
addTrackFieldType = 3
addTrackFieldWidth = 4
addTrackFieldHeight = 5
addTrackFieldSource = 8
addTrackFieldLayers = 9
videoLayerFieldQuality = 1
videoLayerFieldWidth = 2
videoLayerFieldHeight = 3
videoQualityHigh = 2
joinFieldRoom = 1
joinFieldParticipant = 2
joinFieldOtherParticipants = 3
joinFieldServerVersion = 4
joinFieldICEServers = 5
joinFieldSubscriberPrimary = 6
joinFieldServerRegion = 9
joinFieldPingTimeout = 10
joinFieldPingInterval = 11
iceServerFieldURLs = 1
iceServerFieldUsername = 2
iceServerFieldCredential = 3
pingFieldTimestamp = 1
pingFieldRTT = 2
dataPacketFieldKind = 1
dataPacketFieldUser = 2
userPacketFieldPayload = 2
DataPacketKindReliable = 0
DataPacketKindLossy = 1
leaveFieldCanReconnect = 1
leaveFieldReason = 2
leaveFieldAction = 3
roomFieldSID = 1
roomFieldName = 2
participantFieldSID = 1
participantFieldIdentity = 2
participantFieldState = 3
participantFieldName = 9
trackTypeAudio = 0
trackTypeVideo = 1
trackTypeData = 2
signalTargetPublisher = 0
signalTargetSubscriber = 1
trackSourceCamera = 1
trackSourceScreenShare = 3
)
const (
ParticipantStateJoining int32 = 0
ParticipantStateJoined int32 = 1
ParticipantStateActive int32 = 2
ParticipantStateDisconnected int32 = 3
)
var disconnectReasonNames = map[int]string{
0: "UNKNOWN",
1: "CLIENT_INITIATED",
2: "DUPLICATE_IDENTITY",
3: "SERVER_SHUTDOWN",
4: "PARTICIPANT_REMOVED",
5: "ROOM_DELETED",
6: "STATE_MISMATCH",
7: "JOIN_FAILURE",
8: "MIGRATION",
9: "SIGNAL_CLOSE",
10: "ROOM_CLOSED",
11: "USER_UNAVAILABLE",
12: "USER_REJECTED",
13: "SIP_TRUNK_FAILURE",
14: "CONNECTION_TIMEOUT",
15: "MEDIA_FAILURE",
16: "AGENT_ERROR",
}
var leaveActionNames = map[int]string{
0: "DISCONNECT",
1: "RESUME",
2: "RECONNECT",
}
type LeaveInfo struct {
Reason int
Action int
}
type sessionDescription struct {
Type string
SDP string
ID uint32
}
type trickleMsg struct {
CandidateInit string
Target int
Final bool
}
type iceServer struct {
URLs []string
Username string
Credential string
}
type joinResponse struct {
RoomSID string
RoomName string
ParticipantSID string
ParticipantID string
ServerVersion string
ServerRegion string
ICEServers []iceServer
SubscriberPrimary bool
PingTimeoutSec int32
PingIntervalSec int32
}
type signalResponse struct {
Kind int
Join *joinResponse
SDP *sessionDescription
Trickle *trickleMsg
Token string
PongTime int64
Leave *LeaveInfo
Participants []ParticipantInfo
}
type ParticipantInfo struct {
SID string
Identity string
State int32
Name string
}
func DisconnectReasonName(code int) string {
if name, ok := disconnectReasonNames[code]; ok {
return name
}
return fmt.Sprintf("CODE_%d", code)
}
func LeaveActionName(code int) string {
if name, ok := leaveActionNames[code]; ok {
return name
}
return fmt.Sprintf("CODE_%d", code)
}
func DecodeLeaveRequest(data []byte) LeaveInfo {
r := pbReader{buf: data}
var li LeaveInfo
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return li
}
switch {
case field == leaveFieldReason && wire == wireVarint:
v, _ := r.varint()
li.Reason = int(v)
case field == leaveFieldAction && wire == wireVarint:
v, _ := r.varint()
li.Action = int(v)
default:
if err := r.skipWire(wire); err != nil {
return li
}
}
}
return li
}
func EncodeDataPacketUser(payload []byte, kind int) []byte {
w := pbWriter{}
if kind != 0 {
w.int32(dataPacketFieldKind, int32(kind))
}
w.message(dataPacketFieldUser, encUserPacket(payload))
return w.buf
}
func DecodeDataPacketUser(data []byte) ([]byte, bool) {
r := pbReader{buf: data}
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return nil, false
}
if field == dataPacketFieldUser && wire == wireBytes {
inner, err := r.bytes()
if err != nil {
return nil, false
}
ur := pbReader{buf: inner}
for !ur.eof() {
ufield, uwire, uerr := ur.tag()
if uerr != nil {
return nil, false
}
if ufield == userPacketFieldPayload && uwire == wireBytes {
payload, perr := ur.bytes()
if perr != nil {
return nil, false
}
out := make([]byte, len(payload))
copy(out, payload)
return out, true
}
if err := ur.skipWire(uwire); err != nil {
return nil, false
}
}
return nil, false
}
if err := r.skipWire(wire); err != nil {
return nil, false
}
}
return nil, false
}
func DecodeParticipantInfo(data []byte) ParticipantInfo {
r := pbReader{buf: data}
var info ParticipantInfo
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return info
}
switch {
case field == participantFieldSID && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return info
}
info.SID = string(b)
case field == participantFieldIdentity && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return info
}
info.Identity = string(b)
case field == participantFieldState && wire == wireVarint:
v, err := r.varint()
if err != nil {
return info
}
info.State = int32(v)
case field == participantFieldName && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return info
}
info.Name = string(b)
default:
if err := r.skipWire(wire); err != nil {
return info
}
}
}
return info
}
func DecodeParticipantUpdate(data []byte) []ParticipantInfo {
r := pbReader{buf: data}
var out []ParticipantInfo
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return out
}
if field == 1 && wire == wireBytes {
b, err := r.bytes()
if err != nil {
return out
}
out = append(out, DecodeParticipantInfo(b))
} else {
if err := r.skipWire(wire); err != nil {
return out
}
}
}
return out
}
func encSessionDescription(sd sessionDescription) []byte {
w := pbWriter{}
if sd.Type != "" {
w.string(sdpFieldType, sd.Type)
}
if sd.SDP != "" {
w.string(sdpFieldSDP, sd.SDP)
}
if sd.ID != 0 {
w.uint32(sdpFieldID, sd.ID)
}
return w.buf
}
func encTrickle(m trickleMsg) []byte {
w := pbWriter{}
w.string(trickleFieldCandidate, m.CandidateInit)
w.int32(trickleFieldTarget, int32(m.Target))
if m.Final {
w.bool(trickleFieldFinal, true)
}
return w.buf
}
func encVideoLayer(quality, width, height uint32) []byte {
w := pbWriter{}
if quality != 0 {
w.uint32(videoLayerFieldQuality, quality)
}
if width != 0 {
w.uint32(videoLayerFieldWidth, width)
}
if height != 0 {
w.uint32(videoLayerFieldHeight, height)
}
return w.buf
}
func encAddTrack(cid, name string, trackType, source int, width, height uint32) []byte {
w := pbWriter{}
w.string(addTrackFieldCID, cid)
w.string(addTrackFieldName, name)
w.int32(addTrackFieldType, int32(trackType))
if width != 0 {
w.uint32(addTrackFieldWidth, width)
}
if height != 0 {
w.uint32(addTrackFieldHeight, height)
}
w.int32(addTrackFieldSource, int32(source))
if trackType == trackTypeVideo {
w.message(addTrackFieldLayers, encVideoLayer(videoQualityHigh, width, height))
}
return w.buf
}
func encPing(timestamp int64) []byte {
w := pbWriter{}
w.int64(pingFieldTimestamp, timestamp)
return w.buf
}
func encUserPacket(payload []byte) []byte {
w := pbWriter{}
w.bytes(userPacketFieldPayload, payload)
return w.buf
}
func encSignalRequestOffer(sd sessionDescription) []byte {
w := pbWriter{}
w.message(signalReqOffer, encSessionDescription(sd))
return w.buf
}
func encSignalRequestAnswer(sd sessionDescription) []byte {
w := pbWriter{}
w.message(signalReqAnswer, encSessionDescription(sd))
return w.buf
}
func encSignalRequestTrickle(m trickleMsg) []byte {
w := pbWriter{}
w.message(signalReqTrickle, encTrickle(m))
return w.buf
}
func encSignalRequestAddTrack(cid, name string, trackType, source int, width, height uint32) []byte {
w := pbWriter{}
w.message(signalReqAddTrack, encAddTrack(cid, name, trackType, source, width, height))
return w.buf
}
func encSignalRequestLeave() []byte {
w := pbWriter{}
w.message(signalReqLeave, []byte{})
return w.buf
}
func encSignalRequestPing(timestamp int64) []byte {
w := pbWriter{}
w.int64(signalReqPingLegacy, timestamp)
w.message(signalReqPingReq, encPing(timestamp))
return w.buf
}
func decSessionDescription(data []byte) (sessionDescription, error) {
r := pbReader{buf: data}
var sd sessionDescription
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return sd, err
}
switch {
case field == sdpFieldType && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return sd, err
}
sd.Type = string(b)
case field == sdpFieldSDP && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return sd, err
}
sd.SDP = string(b)
case field == sdpFieldID && wire == wireVarint:
v, err := r.varint()
if err != nil {
return sd, err
}
sd.ID = uint32(v)
default:
if err := r.skipWire(wire); err != nil {
return sd, err
}
}
}
return sd, nil
}
func decTrickle(data []byte) (trickleMsg, error) {
r := pbReader{buf: data}
var m trickleMsg
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return m, err
}
switch {
case field == trickleFieldCandidate && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return m, err
}
m.CandidateInit = string(b)
case field == trickleFieldTarget && wire == wireVarint:
v, err := r.varint()
if err != nil {
return m, err
}
m.Target = int(v)
case field == trickleFieldFinal && wire == wireVarint:
v, err := r.varint()
if err != nil {
return m, err
}
m.Final = v != 0
default:
if err := r.skipWire(wire); err != nil {
return m, err
}
}
}
return m, nil
}
func decICEServer(data []byte) (iceServer, error) {
r := pbReader{buf: data}
var s iceServer
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return s, err
}
switch {
case field == iceServerFieldURLs && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return s, err
}
s.URLs = append(s.URLs, string(b))
case field == iceServerFieldUsername && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return s, err
}
s.Username = string(b)
case field == iceServerFieldCredential && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return s, err
}
s.Credential = string(b)
default:
if err := r.skipWire(wire); err != nil {
return s, err
}
}
}
return s, nil
}
func decRoom(data []byte) (string, string, error) {
r := pbReader{buf: data}
var sid, name string
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return "", "", err
}
switch {
case field == roomFieldSID && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return "", "", err
}
sid = string(b)
case field == roomFieldName && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return "", "", err
}
name = string(b)
default:
if err := r.skipWire(wire); err != nil {
return "", "", err
}
}
}
return sid, name, nil
}
func decParticipant(data []byte) (string, string, error) {
info := DecodeParticipantInfo(data)
return info.SID, info.Identity, nil
}
func decJoinResponse(data []byte) (joinResponse, error) {
r := pbReader{buf: data}
var jr joinResponse
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return jr, err
}
switch {
case field == joinFieldRoom && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return jr, err
}
sid, name, _ := decRoom(b)
jr.RoomSID = sid
jr.RoomName = name
case field == joinFieldParticipant && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return jr, err
}
sid, identity, _ := decParticipant(b)
jr.ParticipantSID = sid
jr.ParticipantID = identity
case field == joinFieldServerVersion && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return jr, err
}
jr.ServerVersion = string(b)
case field == joinFieldICEServers && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return jr, err
}
s, _ := decICEServer(b)
jr.ICEServers = append(jr.ICEServers, s)
case field == joinFieldSubscriberPrimary && wire == wireVarint:
v, err := r.varint()
if err != nil {
return jr, err
}
jr.SubscriberPrimary = v != 0
case field == joinFieldServerRegion && wire == wireBytes:
b, err := r.bytes()
if err != nil {
return jr, err
}
jr.ServerRegion = string(b)
case field == joinFieldPingTimeout && wire == wireVarint:
v, err := r.varint()
if err != nil {
return jr, err
}
jr.PingTimeoutSec = int32(v)
case field == joinFieldPingInterval && wire == wireVarint:
v, err := r.varint()
if err != nil {
return jr, err
}
jr.PingIntervalSec = int32(v)
case field == joinFieldOtherParticipants && wire == wireBytes:
if _, err := r.bytes(); err != nil {
return jr, err
}
default:
if err := r.skipWire(wire); err != nil {
return jr, err
}
}
}
return jr, nil
}
func decSignalResponse(data []byte) (signalResponse, error) {
r := pbReader{buf: data}
var sr signalResponse
for !r.eof() {
field, wire, err := r.tag()
if err != nil {
return sr, err
}
if wire != wireBytes {
if err := r.skipWire(wire); err != nil {
return sr, err
}
continue
}
inner, err := r.bytes()
if err != nil {
return sr, err
}
sr.Kind = int(field)
switch field {
case signalRespJoin:
jr, err := decJoinResponse(inner)
if err != nil {
return sr, err
}
sr.Join = &jr
case signalRespAnswer, signalRespOffer:
sd, err := decSessionDescription(inner)
if err != nil {
return sr, err
}
sr.SDP = &sd
case signalRespTrickle:
tm, err := decTrickle(inner)
if err != nil {
return sr, err
}
sr.Trickle = &tm
case signalRespRefreshToken:
sr.Token = string(inner)
case signalRespLeave:
li := DecodeLeaveRequest(inner)
sr.Leave = &li
case signalRespUpdate:
sr.Participants = DecodeParticipantUpdate(inner)
}
return sr, nil
}
return sr, nil
}

View File

@@ -0,0 +1,132 @@
package livekit
import "fmt"
const (
wireVarint = 0
wireFixed64 = 1
wireBytes = 2
wireFixed32 = 5
)
type pbWriter struct{ buf []byte }
func (w *pbWriter) varint(v uint64) {
for v >= 0x80 {
w.buf = append(w.buf, byte(v)|0x80)
v >>= 7
}
w.buf = append(w.buf, byte(v))
}
func (w *pbWriter) tag(field, wire uint64) { w.varint(field<<3 | wire) }
func (w *pbWriter) string(field uint64, s string) {
w.tag(field, wireBytes)
w.varint(uint64(len(s)))
w.buf = append(w.buf, s...)
}
func (w *pbWriter) bytes(field uint64, b []byte) {
w.tag(field, wireBytes)
w.varint(uint64(len(b)))
w.buf = append(w.buf, b...)
}
func (w *pbWriter) message(field uint64, b []byte) { w.bytes(field, b) }
func (w *pbWriter) int32(field uint64, v int32) {
w.tag(field, wireVarint)
w.varint(uint64(uint32(v)))
}
func (w *pbWriter) int64(field uint64, v int64) {
w.tag(field, wireVarint)
w.varint(uint64(v))
}
func (w *pbWriter) uint32(field uint64, v uint32) {
w.tag(field, wireVarint)
w.varint(uint64(v))
}
func (w *pbWriter) bool(field uint64, v bool) {
w.tag(field, wireVarint)
if v {
w.varint(1)
} else {
w.varint(0)
}
}
type pbReader struct {
buf []byte
pos int
}
func (r *pbReader) eof() bool { return r.pos >= len(r.buf) }
func (r *pbReader) varint() (uint64, error) {
var v uint64
var shift uint
for {
if r.pos >= len(r.buf) {
return 0, fmt.Errorf("varint: unexpected eof")
}
b := r.buf[r.pos]
r.pos++
v |= uint64(b&0x7f) << shift
if b < 0x80 {
return v, nil
}
shift += 7
if shift >= 64 {
return 0, fmt.Errorf("varint: overflow")
}
}
}
func (r *pbReader) tag() (field, wire uint64, err error) {
t, err := r.varint()
if err != nil {
return 0, 0, err
}
return t >> 3, t & 7, nil
}
func (r *pbReader) bytes() ([]byte, error) {
n, err := r.varint()
if err != nil {
return nil, err
}
if r.pos+int(n) > len(r.buf) {
return nil, fmt.Errorf("bytes: short read")
}
out := r.buf[r.pos : r.pos+int(n)]
r.pos += int(n)
return out, nil
}
func (r *pbReader) skipWire(wire uint64) error {
switch wire {
case wireVarint:
_, err := r.varint()
return err
case wireFixed64:
if r.pos+8 > len(r.buf) {
return fmt.Errorf("skip: short fixed64")
}
r.pos += 8
return nil
case wireBytes:
_, err := r.bytes()
return err
case wireFixed32:
if r.pos+4 > len(r.buf) {
return fmt.Errorf("skip: short fixed32")
}
r.pos += 4
return nil
}
return fmt.Errorf("unknown wire type %d", wire)
}