mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2026-03-14 14:35:44 +01:00
Encode session ids using protobufs.
This will reduce their size and improve encoding / decoding performance. Also fixes GO-2024-3106 (Stack exhaustion in Decoder.Decode in encoding/gob).
This commit is contained in:
parent
b90913ce0a
commit
9aeb57d74e
12 changed files with 468 additions and 111 deletions
12
Makefile
12
Makefile
|
|
@ -13,8 +13,10 @@ VERSION := $(shell "$(CURDIR)/scripts/get-version.sh")
|
|||
TARVERSION := $(shell "$(CURDIR)/scripts/get-version.sh" --tar)
|
||||
PACKAGENAME := github.com/strukturag/nextcloud-spreed-signaling
|
||||
ALL_PACKAGES := $(PACKAGENAME) $(PACKAGENAME)/client $(PACKAGENAME)/proxy $(PACKAGENAME)/server
|
||||
PROTO_FILES := $(basename $(wildcard *.proto))
|
||||
PROTO_GO_FILES := $(addsuffix .pb.go,$(PROTO_FILES)) $(addsuffix _grpc.pb.go,$(PROTO_FILES))
|
||||
GRPC_PROTO_FILES := $(basename $(wildcard grpc_*.proto))
|
||||
PROTO_FILES := $(filter-out $(GRPC_PROTO_FILES),$(basename $(wildcard *.proto)))
|
||||
PROTO_GO_FILES := $(addsuffix .pb.go,$(PROTO_FILES))
|
||||
GRPC_PROTO_GO_FILES := $(addsuffix .pb.go,$(GRPC_PROTO_FILES)) $(addsuffix _grpc.pb.go,$(GRPC_PROTO_FILES))
|
||||
EASYJSON_GO_FILES := \
|
||||
api_async_easyjson.go \
|
||||
api_backend_easyjson.go \
|
||||
|
|
@ -22,7 +24,7 @@ EASYJSON_GO_FILES := \
|
|||
api_proxy_easyjson.go \
|
||||
api_signaling_easyjson.go
|
||||
TEST_GO_FILES := $(wildcard *_test.go))
|
||||
COMMON_GO_FILES := $(filter-out continentmap.go $(PROTO_GO_FILES) $(EASYJSON_GO_FILES) $(TEST_GO_FILES),$(wildcard *.go))
|
||||
COMMON_GO_FILES := $(filter-out continentmap.go $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES) $(EASYJSON_GO_FILES) $(TEST_GO_FILES),$(wildcard *.go))
|
||||
CLIENT_TEST_GO_FILES := $(wildcard client/*_test.go))
|
||||
CLIENT_GO_FILES := $(filter-out $(CLIENT_TEST_GO_FILES),$(wildcard client/*.go))
|
||||
SERVER_TEST_GO_FILES := $(wildcard server/*_test.go))
|
||||
|
|
@ -139,7 +141,7 @@ coverhtml: vet
|
|||
$*.proto
|
||||
sed -i -e '1h;2,$$H;$$!d;g' -re 's|// versions.+// source:|// source:|' $*_grpc.pb.go
|
||||
|
||||
common: $(EASYJSON_GO_FILES) $(PROTO_GO_FILES)
|
||||
common: $(EASYJSON_GO_FILES) $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES)
|
||||
|
||||
$(BINDIR):
|
||||
mkdir -p "$(BINDIR)"
|
||||
|
|
@ -166,7 +168,7 @@ clean:
|
|||
rm -f "$(BINDIR)/proxy"
|
||||
|
||||
clean-generated: clean
|
||||
rm -f $(EASYJSON_GO_FILES) $(PROTO_GO_FILES)
|
||||
rm -f $(EASYJSON_GO_FILES) $(PROTO_GO_FILES) $(GRPC_PROTO_GO_FILES)
|
||||
|
||||
build: server proxy
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
|
@ -43,7 +42,6 @@ import (
|
|||
|
||||
"github.com/dlintw/goconf"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mailru/easyjson"
|
||||
|
||||
|
|
@ -75,9 +73,6 @@ const (
|
|||
|
||||
// Maximum message size allowed from peer.
|
||||
maxMessageSize = 64 * 1024
|
||||
|
||||
privateSessionName = "private-session"
|
||||
publicSessionName = "public-session"
|
||||
)
|
||||
|
||||
type Stats struct {
|
||||
|
|
@ -120,7 +115,7 @@ type MessagePayload struct {
|
|||
|
||||
type SignalingClient struct {
|
||||
readyWg *sync.WaitGroup
|
||||
cookie *securecookie.SecureCookie
|
||||
cookie *signaling.SessionIdCodec
|
||||
|
||||
conn *websocket.Conn
|
||||
|
||||
|
|
@ -135,7 +130,7 @@ type SignalingClient struct {
|
|||
userId string
|
||||
}
|
||||
|
||||
func NewSignalingClient(cookie *securecookie.SecureCookie, url string, stats *Stats, readyWg *sync.WaitGroup, doneWg *sync.WaitGroup) (*SignalingClient, error) {
|
||||
func NewSignalingClient(cookie *signaling.SessionIdCodec, url string, stats *Stats, readyWg *sync.WaitGroup, doneWg *sync.WaitGroup) (*SignalingClient, error) {
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -215,19 +210,15 @@ func (c *SignalingClient) processMessage(message *signaling.ServerMessage) {
|
|||
}
|
||||
|
||||
func (c *SignalingClient) privateToPublicSessionId(privateId string) string {
|
||||
var data signaling.SessionIdData
|
||||
if err := c.cookie.Decode(privateSessionName, privateId, &data); err != nil {
|
||||
data, err := c.cookie.DecodePrivate(privateId)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not decode private session id: %s", err))
|
||||
}
|
||||
encoded, err := c.cookie.Encode(publicSessionName, data)
|
||||
publicId, err := c.cookie.EncodePublic(data)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not encode public id: %s", err))
|
||||
}
|
||||
reversed, err := reverseSessionId(encoded)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("could not reverse session id: %s", err))
|
||||
}
|
||||
return reversed
|
||||
return publicId
|
||||
}
|
||||
|
||||
func (c *SignalingClient) processHelloMessage(message *signaling.ServerMessage) {
|
||||
|
|
@ -493,19 +484,6 @@ func getLocalIP() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func reverseSessionId(s string) (string, error) {
|
||||
// Note that we are assuming base64 encoded strings here.
|
||||
decoded, err := base64.URLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i, j := 0, len(decoded)-1; i < j; i, j = i+1, j-1 {
|
||||
decoded[i], decoded[j] = decoded[j], decoded[i]
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(decoded), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
|
|
@ -537,7 +515,7 @@ func main() {
|
|||
default:
|
||||
log.Fatalf("The sessions block key must be 16, 24 or 32 bytes but is %d bytes", len(blockKey))
|
||||
}
|
||||
cookie := securecookie.New([]byte(hashKey), blockBytes).MaxAge(0)
|
||||
cookie := signaling.NewSessionIdCodec([]byte(hashKey), blockBytes)
|
||||
|
||||
cpus := runtime.NumCPU()
|
||||
runtime.GOMAXPROCS(cpus)
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -6,6 +6,7 @@ require (
|
|||
github.com/dlintw/goconf v0.0.0-20120228082610-dcc070983490
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
|
|
@ -40,7 +41,6 @@ require (
|
|||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
|
|
|
|||
98
hub.go
98
hub.go
|
|
@ -49,8 +49,8 @@ import (
|
|||
"github.com/dlintw/goconf"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/websocket"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -114,11 +114,6 @@ var (
|
|||
DefaultTrustedProxies = DefaultPrivateIps()
|
||||
)
|
||||
|
||||
const (
|
||||
privateSessionName = "private-session"
|
||||
publicSessionName = "public-session"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterHubStats()
|
||||
}
|
||||
|
|
@ -127,7 +122,7 @@ type Hub struct {
|
|||
version string
|
||||
events AsyncEvents
|
||||
upgrader websocket.Upgrader
|
||||
cookie *securecookie.SecureCookie
|
||||
cookie *SessionIdCodec
|
||||
info *WelcomeServerMessage
|
||||
infoInternal *WelcomeServerMessage
|
||||
welcome atomic.Value // *ServerMessage
|
||||
|
|
@ -325,7 +320,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer
|
|||
ReadBufferSize: websocketReadBufferSize,
|
||||
WriteBufferSize: websocketWriteBufferSize,
|
||||
},
|
||||
cookie: securecookie.New([]byte(hashKey), blockBytes).MaxAge(0),
|
||||
cookie: NewSessionIdCodec([]byte(hashKey), blockBytes),
|
||||
info: NewWelcomeServerMessage(version, DefaultFeatures...),
|
||||
infoInternal: NewWelcomeServerMessage(version, DefaultFeaturesInternal...),
|
||||
|
||||
|
|
@ -531,35 +526,6 @@ func (h *Hub) Reload(config *goconf.ConfigFile) {
|
|||
h.rpcClients.Reload(config)
|
||||
}
|
||||
|
||||
func reverseSessionId(s string) (string, error) {
|
||||
// Note that we are assuming base64 encoded strings here.
|
||||
decoded, err := base64.URLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i, j := 0, len(decoded)-1; i < j; i, j = i+1, j-1 {
|
||||
decoded[i], decoded[j] = decoded[j], decoded[i]
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(decoded), nil
|
||||
}
|
||||
|
||||
func (h *Hub) encodeSessionId(data *SessionIdData, sessionType string) (string, error) {
|
||||
encoded, err := h.cookie.Encode(sessionType, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if sessionType == publicSessionName {
|
||||
// We are reversing the public session ids because clients compare them
|
||||
// to decide who calls whom. The prefix of the session id is increasing
|
||||
// (a timestamp) but the suffix the (random) hash.
|
||||
// By reversing we move the hash to the front, making the comparison of
|
||||
// session ids "random".
|
||||
encoded, err = reverseSessionId(encoded)
|
||||
}
|
||||
return encoded, err
|
||||
}
|
||||
|
||||
func (h *Hub) getDecodeCache(cache_key string) *LruCache {
|
||||
hash := fnv.New32a()
|
||||
hash.Write([]byte(cache_key)) // nolint
|
||||
|
|
@ -587,36 +553,48 @@ func (h *Hub) setDecodedSessionId(id string, sessionType string, data *SessionId
|
|||
cache.Set(cache_key, data)
|
||||
}
|
||||
|
||||
func (h *Hub) decodeSessionId(id string, sessionType string) *SessionIdData {
|
||||
func (h *Hub) decodePrivateSessionId(id string) *SessionIdData {
|
||||
if len(id) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cache_key := id + "|" + sessionType
|
||||
cache_key := id + "|" + privateSessionName
|
||||
cache := h.getDecodeCache(cache_key)
|
||||
if result := cache.Get(cache_key); result != nil {
|
||||
return result.(*SessionIdData)
|
||||
}
|
||||
|
||||
if sessionType == publicSessionName {
|
||||
var err error
|
||||
id, err = reverseSessionId(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var data SessionIdData
|
||||
if h.cookie.Decode(sessionType, id, &data) != nil {
|
||||
data, err := h.cookie.DecodePrivate(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cache.Set(cache_key, &data)
|
||||
return &data
|
||||
cache.Set(cache_key, data)
|
||||
return data
|
||||
}
|
||||
|
||||
func (h *Hub) decodePublicSessionId(id string) *SessionIdData {
|
||||
if len(id) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cache_key := id + "|" + publicSessionName
|
||||
cache := h.getDecodeCache(cache_key)
|
||||
if result := cache.Get(cache_key); result != nil {
|
||||
return result.(*SessionIdData)
|
||||
}
|
||||
|
||||
data, err := h.cookie.DecodePublic(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cache.Set(cache_key, data)
|
||||
return data
|
||||
}
|
||||
|
||||
func (h *Hub) GetSessionByPublicId(sessionId string) Session {
|
||||
data := h.decodeSessionId(sessionId, publicSessionName)
|
||||
data := h.decodePublicSessionId(sessionId)
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -632,7 +610,7 @@ func (h *Hub) GetSessionByPublicId(sessionId string) Session {
|
|||
}
|
||||
|
||||
func (h *Hub) GetSessionByResumeId(resumeId string) Session {
|
||||
data := h.decodeSessionId(resumeId, privateSessionName)
|
||||
data := h.decodePrivateSessionId(resumeId)
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -834,7 +812,7 @@ func (h *Hub) newSessionIdData(backend *Backend) *SessionIdData {
|
|||
}
|
||||
sessionIdData := &SessionIdData{
|
||||
Sid: sid,
|
||||
Created: time.Now(),
|
||||
Created: timestamppb.Now(),
|
||||
BackendId: backend.Id(),
|
||||
}
|
||||
return sessionIdData
|
||||
|
|
@ -862,12 +840,12 @@ func (h *Hub) processRegister(c HandlerClient, message *ClientMessage, backend *
|
|||
}
|
||||
|
||||
sessionIdData := h.newSessionIdData(backend)
|
||||
privateSessionId, err := h.encodeSessionId(sessionIdData, privateSessionName)
|
||||
privateSessionId, err := h.cookie.EncodePrivate(sessionIdData)
|
||||
if err != nil {
|
||||
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
||||
return
|
||||
}
|
||||
publicSessionId, err := h.encodeSessionId(sessionIdData, publicSessionName)
|
||||
publicSessionId, err := h.cookie.EncodePublic(sessionIdData)
|
||||
if err != nil {
|
||||
client.SendMessage(message.NewWrappedErrorServerMessage(err))
|
||||
return
|
||||
|
|
@ -1172,7 +1150,7 @@ func (h *Hub) processHello(client HandlerClient, message *ClientMessage) {
|
|||
return
|
||||
}
|
||||
|
||||
data := h.decodeSessionId(resumeId, privateSessionName)
|
||||
data := h.decodePrivateSessionId(resumeId)
|
||||
if data == nil {
|
||||
statsHubSessionResumeFailed.Inc()
|
||||
if h.tryProxyResume(client, resumeId, message) {
|
||||
|
|
@ -2165,7 +2143,7 @@ func (h *Hub) processControlMsg(session Session, message *ClientMessage) {
|
|||
var room *Room
|
||||
switch msg.Recipient.Type {
|
||||
case RecipientTypeSession:
|
||||
data := h.decodeSessionId(msg.Recipient.SessionId, publicSessionName)
|
||||
data := h.decodePublicSessionId(msg.Recipient.SessionId)
|
||||
if data != nil {
|
||||
if msg.Recipient.SessionId == session.PublicId() {
|
||||
// Don't loop messages to the sender.
|
||||
|
|
@ -2285,12 +2263,12 @@ func (h *Hub) processInternalMsg(sess Session, message *ClientMessage) {
|
|||
}
|
||||
|
||||
sessionIdData := h.newSessionIdData(session.Backend())
|
||||
privateSessionId, err := h.encodeSessionId(sessionIdData, privateSessionName)
|
||||
privateSessionId, err := h.cookie.EncodePrivate(sessionIdData)
|
||||
if err != nil {
|
||||
log.Printf("Could not encode private virtual session id: %s", err)
|
||||
return
|
||||
}
|
||||
publicSessionId, err := h.encodeSessionId(sessionIdData, publicSessionName)
|
||||
publicSessionId, err := h.cookie.EncodePublic(sessionIdData)
|
||||
if err != nil {
|
||||
log.Printf("Could not encode public virtual session id: %s", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -907,7 +907,7 @@ func TestClientHelloV2(t *testing.T) {
|
|||
assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello)
|
||||
assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello)
|
||||
|
||||
data := hub.decodeSessionId(hello.Hello.SessionId, publicSessionName)
|
||||
data := hub.decodePublicSessionId(hello.Hello.SessionId)
|
||||
require.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId)
|
||||
|
||||
hub.mu.RLock()
|
||||
|
|
@ -1281,7 +1281,7 @@ func TestSessionIdsUnordered(t *testing.T) {
|
|||
assert.Equal(testDefaultUserId, hello.Hello.UserId, "%+v", hello.Hello)
|
||||
assert.NotEmpty(hello.Hello.SessionId, "%+v", hello.Hello)
|
||||
|
||||
data := hub.decodeSessionId(hello.Hello.SessionId, publicSessionName)
|
||||
data := hub.decodePublicSessionId(hello.Hello.SessionId)
|
||||
if !assert.NotNil(data, "Could not decode session id: %s", hello.Hello.SessionId) {
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,10 +46,10 @@ import (
|
|||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/notedit/janus-go"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
signaling "github.com/strukturag/nextcloud-spreed-signaling"
|
||||
)
|
||||
|
|
@ -128,7 +128,7 @@ type ProxyServer struct {
|
|||
trustedProxies atomic.Pointer[signaling.AllowedIps]
|
||||
|
||||
sid atomic.Uint64
|
||||
cookie *securecookie.SecureCookie
|
||||
cookie *signaling.SessionIdCodec
|
||||
sessions map[uint64]*ProxySession
|
||||
sessionsLock sync.RWMutex
|
||||
|
||||
|
|
@ -350,7 +350,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
|
|||
|
||||
tokens: tokens,
|
||||
|
||||
cookie: securecookie.New(hashKey, blockKey).MaxAge(0),
|
||||
cookie: signaling.NewSessionIdCodec(hashKey, blockKey),
|
||||
sessions: make(map[uint64]*ProxySession),
|
||||
|
||||
clients: make(map[string]signaling.McuClient),
|
||||
|
|
@ -742,8 +742,7 @@ func (s *ProxyServer) processMessage(client *ProxyClient, data []byte) {
|
|||
|
||||
var session *ProxySession
|
||||
if resumeId := message.Hello.ResumeId; resumeId != "" {
|
||||
var data signaling.SessionIdData
|
||||
if s.cookie.Decode("session", resumeId, &data) == nil {
|
||||
if data, err := s.cookie.DecodePublic(resumeId); err == nil {
|
||||
session = s.GetSession(data.Sid)
|
||||
}
|
||||
|
||||
|
|
@ -1331,10 +1330,10 @@ func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*Pro
|
|||
|
||||
sessionIdData := &signaling.SessionIdData{
|
||||
Sid: sid,
|
||||
Created: time.Now(),
|
||||
Created: timestamppb.Now(),
|
||||
}
|
||||
|
||||
encoded, err := s.cookie.Encode("session", sessionIdData)
|
||||
encoded, err := s.cookie.EncodePublic(sessionIdData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import (
|
|||
"encoding/json"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Permission string
|
||||
|
|
@ -48,12 +47,6 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
type SessionIdData struct {
|
||||
Sid uint64
|
||||
Created time.Time
|
||||
BackendId string
|
||||
}
|
||||
|
||||
type Session interface {
|
||||
Context() context.Context
|
||||
PrivateId() string
|
||||
|
|
|
|||
173
session.pb.go
Normal file
173
session.pb.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
//*
|
||||
// Standalone signaling server for the Nextcloud Spreed app.
|
||||
// Copyright (C) 2024 struktur AG
|
||||
//
|
||||
// @author Joachim Bauch <bauch@struktur.de>
|
||||
//
|
||||
// @license GNU AGPL version 3 or any later version
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: session.proto
|
||||
|
||||
package signaling
|
||||
|
||||
import (
|
||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type SessionIdData struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Sid uint64 `protobuf:"varint,1,opt,name=Sid,proto3" json:"Sid,omitempty"`
|
||||
Created *timestamp.Timestamp `protobuf:"bytes,2,opt,name=Created,proto3" json:"Created,omitempty"`
|
||||
BackendId string `protobuf:"bytes,3,opt,name=BackendId,proto3" json:"BackendId,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SessionIdData) Reset() {
|
||||
*x = SessionIdData{}
|
||||
mi := &file_session_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SessionIdData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SessionIdData) ProtoMessage() {}
|
||||
|
||||
func (x *SessionIdData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_session_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SessionIdData.ProtoReflect.Descriptor instead.
|
||||
func (*SessionIdData) Descriptor() ([]byte, []int) {
|
||||
return file_session_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *SessionIdData) GetSid() uint64 {
|
||||
if x != nil {
|
||||
return x.Sid
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SessionIdData) GetCreated() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.Created
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SessionIdData) GetBackendId() string {
|
||||
if x != nil {
|
||||
return x.BackendId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_session_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_session_proto_rawDesc = []byte{
|
||||
0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x75, 0x0a, 0x0d, 0x53,
|
||||
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x53, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x69, 0x64, 0x12, 0x34,
|
||||
0x0a, 0x07, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x49,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
|
||||
0x49, 0x64, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x73, 0x74, 0x72, 0x75, 0x6b, 0x74, 0x75, 0x72, 0x61, 0x67, 0x2f, 0x6e, 0x65, 0x78, 0x74,
|
||||
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2d, 0x73, 0x70, 0x72, 0x65, 0x65, 0x64, 0x2d, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x69, 0x6e, 0x67,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_session_proto_rawDescOnce sync.Once
|
||||
file_session_proto_rawDescData = file_session_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_session_proto_rawDescGZIP() []byte {
|
||||
file_session_proto_rawDescOnce.Do(func() {
|
||||
file_session_proto_rawDescData = protoimpl.X.CompressGZIP(file_session_proto_rawDescData)
|
||||
})
|
||||
return file_session_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_session_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_session_proto_goTypes = []any{
|
||||
(*SessionIdData)(nil), // 0: signaling.SessionIdData
|
||||
(*timestamp.Timestamp)(nil), // 1: google.protobuf.Timestamp
|
||||
}
|
||||
var file_session_proto_depIdxs = []int32{
|
||||
1, // 0: signaling.SessionIdData.Created:type_name -> google.protobuf.Timestamp
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_session_proto_init() }
|
||||
func file_session_proto_init() {
|
||||
if File_session_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_session_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_session_proto_goTypes,
|
||||
DependencyIndexes: file_session_proto_depIdxs,
|
||||
MessageInfos: file_session_proto_msgTypes,
|
||||
}.Build()
|
||||
File_session_proto = out.File
|
||||
file_session_proto_rawDesc = nil
|
||||
file_session_proto_goTypes = nil
|
||||
file_session_proto_depIdxs = nil
|
||||
}
|
||||
34
session.proto
Normal file
34
session.proto
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Standalone signaling server for the Nextcloud Spreed app.
|
||||
* Copyright (C) 2024 struktur AG
|
||||
*
|
||||
* @author Joachim Bauch <bauch@struktur.de>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
option go_package = "github.com/strukturag/nextcloud-spreed-signaling;signaling";
|
||||
|
||||
package signaling;
|
||||
|
||||
message SessionIdData {
|
||||
uint64 Sid = 1;
|
||||
google.protobuf.Timestamp Created = 2;
|
||||
string BackendId = 3;
|
||||
}
|
||||
121
sessionid_codec.go
Normal file
121
sessionid_codec.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Standalone signaling server for the Nextcloud Spreed app.
|
||||
* Copyright (C) 2024 struktur AG
|
||||
*
|
||||
* @author Joachim Bauch <bauch@struktur.de>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package signaling
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type protoSerializer struct {
|
||||
}
|
||||
|
||||
func (s *protoSerializer) Serialize(src interface{}) ([]byte, error) {
|
||||
msg, ok := src.(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't serialize type %T", src)
|
||||
}
|
||||
return proto.Marshal(msg)
|
||||
}
|
||||
|
||||
func (s *protoSerializer) Deserialize(src []byte, dst interface{}) error {
|
||||
msg, ok := dst.(proto.Message)
|
||||
if !ok {
|
||||
return fmt.Errorf("can't deserialize type %T", src)
|
||||
}
|
||||
return proto.Unmarshal(src, msg)
|
||||
}
|
||||
|
||||
const (
|
||||
privateSessionName = "private-session"
|
||||
publicSessionName = "public-session"
|
||||
)
|
||||
|
||||
type SessionIdCodec struct {
|
||||
cookie *securecookie.SecureCookie
|
||||
}
|
||||
|
||||
func NewSessionIdCodec(hashKey []byte, blockKey []byte) *SessionIdCodec {
|
||||
cookie := securecookie.New(hashKey, blockKey).
|
||||
MaxAge(0).
|
||||
SetSerializer(&protoSerializer{})
|
||||
return &SessionIdCodec{
|
||||
cookie: cookie,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SessionIdCodec) EncodePrivate(sessionData *SessionIdData) (string, error) {
|
||||
return c.cookie.Encode(privateSessionName, sessionData)
|
||||
}
|
||||
|
||||
func reverseSessionId(s string) (string, error) {
|
||||
// Note that we are assuming base64 encoded strings here.
|
||||
decoded, err := base64.URLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i, j := 0, len(decoded)-1; i < j; i, j = i+1, j-1 {
|
||||
decoded[i], decoded[j] = decoded[j], decoded[i]
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(decoded), nil
|
||||
}
|
||||
|
||||
func (c *SessionIdCodec) EncodePublic(sessionData *SessionIdData) (string, error) {
|
||||
encoded, err := c.cookie.Encode(publicSessionName, sessionData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// We are reversing the public session ids because clients compare them
|
||||
// to decide who calls whom. The prefix of the session id is increasing
|
||||
// (a timestamp) but the suffix the (random) hash.
|
||||
// By reversing we move the hash to the front, making the comparison of
|
||||
// session ids "random".
|
||||
return reverseSessionId(encoded)
|
||||
}
|
||||
|
||||
func (c *SessionIdCodec) DecodePrivate(encodedData string) (*SessionIdData, error) {
|
||||
var data SessionIdData
|
||||
if err := c.cookie.Decode(privateSessionName, encodedData, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *SessionIdCodec) DecodePublic(encodedData string) (*SessionIdData, error) {
|
||||
encodedData, err := reverseSessionId(encodedData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data SessionIdData
|
||||
if err := c.cookie.Decode(publicSessionName, encodedData, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
79
sessionid_codec_test.go
Normal file
79
sessionid_codec_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Standalone signaling server for the Nextcloud Spreed app.
|
||||
* Copyright (C) 2024 struktur AG
|
||||
*
|
||||
* @author Joachim Bauch <bauch@struktur.de>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package signaling
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestReverseSessionId(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
a := base64.URLEncoding.EncodeToString([]byte("12345"))
|
||||
ar, err := reverseSessionId(a)
|
||||
require.NoError(err)
|
||||
require.NotEqual(a, ar)
|
||||
b := base64.URLEncoding.EncodeToString([]byte("54321"))
|
||||
br, err := reverseSessionId(b)
|
||||
require.NoError(err)
|
||||
require.NotEqual(b, br)
|
||||
assert.Equal(b, ar)
|
||||
assert.Equal(a, br)
|
||||
|
||||
// Invalid base64.
|
||||
if s, err := reverseSessionId("hello world!"); !assert.Error(err) {
|
||||
assert.Fail("should have failed but got %s", s)
|
||||
}
|
||||
// Invalid base64 length.
|
||||
if s, err := reverseSessionId("123"); !assert.Error(err) {
|
||||
assert.Fail("should have failed but got %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicPrivate(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
sd := &SessionIdData{
|
||||
Sid: 1,
|
||||
Created: timestamppb.Now(),
|
||||
BackendId: "foo",
|
||||
}
|
||||
|
||||
codec := NewSessionIdCodec([]byte("0123456789012345"), []byte("0123456789012345"))
|
||||
private, err := codec.EncodePrivate(sd)
|
||||
require.NoError(err)
|
||||
public, err := codec.EncodePublic(sd)
|
||||
require.NoError(err)
|
||||
assert.NotEqual(private, public)
|
||||
|
||||
if data, err := codec.DecodePublic(private); !assert.Error(err) {
|
||||
assert.Fail("should have failed but got %+v", data)
|
||||
}
|
||||
if data, err := codec.DecodePrivate(public); !assert.Error(err) {
|
||||
assert.Fail("should have failed but got %+v", data)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ func getWebsocketUrl(url string) string {
|
|||
}
|
||||
|
||||
func getPubliceSessionIdData(h *Hub, publicId string) *SessionIdData {
|
||||
decodedPublic := h.decodeSessionId(publicId, publicSessionName)
|
||||
decodedPublic := h.decodePublicSessionId(publicId)
|
||||
if decodedPublic == nil {
|
||||
panic("invalid public session id")
|
||||
}
|
||||
|
|
@ -333,7 +333,7 @@ func (c *TestClient) WaitForClientRemoved(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (c *TestClient) WaitForSessionRemoved(ctx context.Context, sessionId string) error {
|
||||
data := c.hub.decodeSessionId(sessionId, publicSessionName)
|
||||
data := c.hub.decodePublicSessionId(sessionId)
|
||||
if data == nil {
|
||||
return fmt.Errorf("Invalid session id passed")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue