mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-08 11:35:16 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
472
transport/call/livekit/client.go
Normal file
472
transport/call/livekit/client.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
708
transport/call/livekit/messages.go
Normal file
708
transport/call/livekit/messages.go
Normal 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
|
||||
}
|
||||
132
transport/call/livekit/wire.go
Normal file
132
transport/call/livekit/wire.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user