From 5e95736e76147c74c19a6eeacef4dae4541252db Mon Sep 17 00:00:00 2001 From: Lois Bibehe Date: Wed, 8 Dec 2021 15:58:40 +0100 Subject: [PATCH] Initial commit of package localserver --- config.go | 104 ++ encryption_manager.go | 112 ++ go.mod | 49 + grpc_manager.pb.go | 1958 +++++++++++++++++++++++++++++++++ grpc_manager_grpc.pb.go | 422 +++++++ logMiddleware.go | 32 + p2p_fs_datachannel_manager.go | 355 ++++++ proto/grpc_manager.proto | 149 +++ server.go | 222 ++++ signalingHttpClient.go | 19 + webrtcCallChatManager.go | 136 +++ webrtcCallEventManager.go | 5 + webrtcCallFileManager.go | 311 ++++++ webrtcCallManager.go | 845 ++++++++++++++ webrtcCallSoundManager.go | 234 ++++ webrtcCallVideoManager.go | 136 +++ webrtcFsManager.go | 441 ++++++++ webrtcFsMiddleware.go | 158 +++ webrtcGrpcMiddleware.go | 195 ++++ webrtcHttpMiddleware.go | 66 ++ zoneAudioChannel.go | 759 +++++++++++++ zoneAudioChannelshandler.go | 1039 +++++++++++++++++ zoneChatsDBHandler.go | 179 +++ zoneChatsHandler.go | 1015 +++++++++++++++++ zoneFSDBhandler.go | 169 +++ zoneFSHandler.go | 1114 +++++++++++++++++++ zoneFSInstance.go | 559 ++++++++++ zoneFile.go | 1 + zoneGrpcMiddleware.go | 234 ++++ zoneManager.go | 734 ++++++++++++ zoneMediaHandler.go | 1 + zoneRequestScheduler.go | 137 +++ zoneUsersDBHandler.go | 209 ++++ zoneUsersHandler.go | 534 +++++++++ zoneVideoChannel.go | 851 ++++++++++++++ zoneVideoChannelsHandler.go | 1038 +++++++++++++++++ 36 files changed, 14522 insertions(+) create mode 100644 config.go create mode 100644 encryption_manager.go create mode 100644 go.mod create mode 100644 grpc_manager.pb.go create mode 100644 grpc_manager_grpc.pb.go create mode 100644 logMiddleware.go create mode 100644 p2p_fs_datachannel_manager.go create mode 100644 proto/grpc_manager.proto create mode 100644 server.go create mode 100644 signalingHttpClient.go create mode 100644 webrtcCallChatManager.go create mode 100644 webrtcCallEventManager.go create mode 100644 webrtcCallFileManager.go create mode 100644 webrtcCallManager.go create mode 100644 webrtcCallSoundManager.go create mode 100644 webrtcCallVideoManager.go create mode 100644 webrtcFsManager.go create mode 100644 webrtcFsMiddleware.go create mode 100644 webrtcGrpcMiddleware.go create mode 100644 webrtcHttpMiddleware.go create mode 100644 zoneAudioChannel.go create mode 100644 zoneAudioChannelshandler.go create mode 100644 zoneChatsDBHandler.go create mode 100644 zoneChatsHandler.go create mode 100644 zoneFSDBhandler.go create mode 100644 zoneFSHandler.go create mode 100644 zoneFSInstance.go create mode 100644 zoneFile.go create mode 100644 zoneGrpcMiddleware.go create mode 100644 zoneManager.go create mode 100644 zoneMediaHandler.go create mode 100644 zoneRequestScheduler.go create mode 100644 zoneUsersDBHandler.go create mode 100644 zoneUsersHandler.go create mode 100644 zoneVideoChannel.go create mode 100644 zoneVideoChannelsHandler.go diff --git a/config.go b/config.go new file mode 100644 index 0000000..99c102f --- /dev/null +++ b/config.go @@ -0,0 +1,104 @@ +package localserver + +import ( + "bytes" + "context" + "crypto/x509" + "encoding/json" + "encoding/pem" + "log" + "net/http" + "os" + "path/filepath" + + "github.com/google/uuid" +) + +type LocalServerConfig struct { + configFilePath string + NodeId string `json:"nodeId"` + PrivateKeyPath string `json:"privateKeyPath"` + Token string `json:"token"` +} + +func NewLocalServerConfig() (localServerConfig *LocalServerConfig, err error) { + logFile, err := os.OpenFile(filepath.Join("local_server.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755) + if err != nil { + return + } + logger.SetOutput(logFile) + logger.SetFlags(log.Ldate | log.LUTC | log.Lshortfile | log.Ltime | log.LstdFlags | log.Lmsgprefix) + localServerConfig = &LocalServerConfig{ + configFilePath: filepath.Join("config","node_config.json"), + } + err = localServerConfig.startup() + return +} + +func (l *LocalServerConfig) startup() (err error) { + if _, err = os.Stat(l.configFilePath); os.IsNotExist(err) { + file, fileErr := os.Create(l.configFilePath) + if fileErr != nil { + return fileErr + } + id := uuid.NewString() + e := NewEncryptionManager() + keypair, keyErr := e.GenerateKeyPair(context.Background(), id, "") + if keyErr != nil { + return keyErr + } + baseConfig := map[string]interface{}{ + "nodeId": id, + "privKeyPath": filepath.Join("config", id), + "token": "", + } + bs, marshallErr := json.Marshal(&baseConfig) + if marshallErr != nil { + return marshallErr + } + _, writeErr := file.Write(bs) + if writeErr != nil { + return writeErr + } + key_bytes := x509.MarshalPKCS1PublicKey(&keypair.PublicKey) + key := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: key_bytes, + }) + logger.Println(string(key)) + l.NodeId = id + l.PrivateKeyPath = "" + l.Token = "" + bss, marshallErr := json.Marshal(map[string]interface{}{ + "type": "create_node", + "from": id, + "to": "serv", + "token": "", + "payload": map[string]string{ + "nodeId": id, + "nodeKey": string(key), + "nodeUsername": "anonymous", + }, + }) + if marshallErr != nil { + return marshallErr + } + res, postErr := http.Post("https://dev.zippytal.com/req", "application/json", bytes.NewBuffer(bss)) + if postErr != nil { + logger.Println("e") + return postErr + } + logger.Println(res) + if err = file.Close(); err != nil { + return + } + } else if err != nil { + return + } + file, err := os.Open(l.configFilePath) + if err != nil { + return + } + err = json.NewDecoder(file).Decode(&l) + return +} diff --git a/encryption_manager.go b/encryption_manager.go new file mode 100644 index 0000000..3217050 --- /dev/null +++ b/encryption_manager.go @@ -0,0 +1,112 @@ +package localserver + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io" + "os" + "path/filepath" +) + +const PEM_PRIVATE_KEY = "RSA PRIVATE KEY" +const PEM_PUBLIC_KEY = "RSA PUBLIC KEY" +const OPENSSH_PRIVATE_KEY = "OPENSSH PRIVATE KEY" + +type EncryptionManager struct { + PrivKey *rsa.PrivateKey + PeerKeys map[string]*rsa.PublicKey +} + +func NewEncryptionManager() (eManager *EncryptionManager) { + eManager = new(EncryptionManager) + return +} + +func (em *EncryptionManager) GenerateKeyPair(ctx context.Context, name string, pwd string) (privKey *rsa.PrivateKey, err error) { + privKey, err = rsa.GenerateKey(rand.Reader, 1<<12) + if err != nil { + return + } + block := &pem.Block{ + Type: PEM_PRIVATE_KEY, + Bytes: x509.MarshalPKCS1PrivateKey(privKey), + } + if pwd != "" { + block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(pwd), x509.PEMCipherAES256) + if err != nil { + return + } + } + f, err := os.Create(filepath.Join("config", name)) + if err != nil { + return + } + _, err = f.Write(pem.EncodeToMemory(block)) + if err != nil { + return + } + pubBlock := &pem.Block{ + Type: PEM_PUBLIC_KEY, + Bytes: x509.MarshalPKCS1PublicKey(&privKey.PublicKey), + } + if pwd != "" { + pubBlock, err = x509.EncryptPEMBlock(rand.Reader, pubBlock.Type, pubBlock.Bytes, []byte(pwd), x509.PEMCipherAES256) + if err != nil { + return + } + } + pubf, err := os.Create(filepath.Join("config", fmt.Sprintf("%s.pub", name))) + if err != nil { + return + } + _, err = pubf.Write(pem.EncodeToMemory(pubBlock)) + return +} + +func (em *EncryptionManager) LoadPrivKey(privKeyPath string, password string) (err error) { + f, err := os.Open(privKeyPath) + if err != nil { + return + } + var buff []byte + buff, err = io.ReadAll(f) + if err != nil { + return + } + privPem, _ := pem.Decode(buff) + var privePemBytes []byte + if privPem.Type != PEM_PRIVATE_KEY && privPem.Type != OPENSSH_PRIVATE_KEY { + logger.Println(privPem.Type) + err = fmt.Errorf("RSA Private key is of wrong type") + return + } + if password != "" { + privePemBytes, err = x509.DecryptPEMBlock(privPem, []byte(password)) + } else { + privePemBytes = privPem.Bytes + } + var parsedKey interface{} + if parsedKey, err = x509.ParsePKCS1PrivateKey(privePemBytes); err != nil { + logger.Printf("error one %v\n", err) + if parsedKey, err = x509.ParsePKCS8PrivateKey(privePemBytes); err != nil { + logger.Printf("error on parsing %v\n", err) + if parsedKey, err = x509.ParseECPrivateKey(privePemBytes); err != nil { + return + } + } + + } + var privKey *rsa.PrivateKey + var ok bool + privKey, ok = parsedKey.(*rsa.PrivateKey) + if !ok { + err = fmt.Errorf("Unable to parse this private key") + return + } + em.PrivKey = privKey + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c02baa9 --- /dev/null +++ b/go.mod @@ -0,0 +1,49 @@ +module github.com/loisBN/zippytal_node/localserver + +go 1.17 + +require ( + github.com/dgraph-io/badger/v3 v3.2103.2 + github.com/golang/protobuf v1.5.2 + github.com/google/uuid v1.3.0 + github.com/pion/rtcp v1.2.9 + github.com/pion/webrtc/v3 v3.1.11 + google.golang.org/grpc v1.42.0 + google.golang.org/protobuf v1.27.1 +) + +require ( + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/klauspost/compress v1.12.3 // indirect + github.com/pion/datachannel v1.5.2 // indirect + github.com/pion/dtls/v2 v2.0.10 // indirect + github.com/pion/ice/v2 v2.1.14 // indirect + github.com/pion/interceptor v0.1.2 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/mdns v0.0.5 // indirect + github.com/pion/randutil v0.1.0 // indirect + github.com/pion/rtp v1.7.4 // indirect + github.com/pion/sctp v1.8.0 // indirect + github.com/pion/sdp/v3 v3.0.4 // indirect + github.com/pion/srtp/v2 v2.0.5 // indirect + github.com/pion/stun v0.3.5 // indirect + github.com/pion/transport v0.12.3 // indirect + github.com/pion/turn/v2 v2.0.5 // indirect + github.com/pion/udp v0.1.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + go.opencensus.io v0.22.5 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect + golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect +) diff --git a/grpc_manager.pb.go b/grpc_manager.pb.go new file mode 100644 index 0000000..3154792 --- /dev/null +++ b/grpc_manager.pb.go @@ -0,0 +1,1958 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.14.0 +// source: grpc_manager.proto + +package localserver + +import ( + proto "github.com/golang/protobuf/proto" + 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) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Request struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + From string `protobuf:"bytes,2,opt,name=from,proto3" json:"from,omitempty"` + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + Payload map[string]string `protobuf:"bytes,4,rep,name=payload,proto3" json:"payload,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{0} +} + +func (x *Request) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Request) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *Request) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *Request) GetPayload() map[string]string { + if x != nil { + return x.Payload + } + return nil +} + +type PeerRegisterRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PeerId string `protobuf:"bytes,1,opt,name=peerId,proto3" json:"peerId,omitempty"` + PeerKey string `protobuf:"bytes,2,opt,name=peerKey,proto3" json:"peerKey,omitempty"` + PeerUsername string `protobuf:"bytes,3,opt,name=peerUsername,proto3" json:"peerUsername,omitempty"` +} + +func (x *PeerRegisterRequest) Reset() { + *x = PeerRegisterRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerRegisterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerRegisterRequest) ProtoMessage() {} + +func (x *PeerRegisterRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeerRegisterRequest.ProtoReflect.Descriptor instead. +func (*PeerRegisterRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{1} +} + +func (x *PeerRegisterRequest) GetPeerId() string { + if x != nil { + return x.PeerId + } + return "" +} + +func (x *PeerRegisterRequest) GetPeerKey() string { + if x != nil { + return x.PeerKey + } + return "" +} + +func (x *PeerRegisterRequest) GetPeerUsername() string { + if x != nil { + return x.PeerUsername + } + return "" +} + +type PeerRegisterResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Log string `protobuf:"bytes,2,opt,name=log,proto3" json:"log,omitempty"` +} + +func (x *PeerRegisterResponse) Reset() { + *x = PeerRegisterResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerRegisterResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerRegisterResponse) ProtoMessage() {} + +func (x *PeerRegisterResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeerRegisterResponse.ProtoReflect.Descriptor instead. +func (*PeerRegisterResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{2} +} + +func (x *PeerRegisterResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *PeerRegisterResponse) GetLog() string { + if x != nil { + return x.Log + } + return "" +} + +type PeerListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` + LastIndex int32 `protobuf:"varint,2,opt,name=lastIndex,proto3" json:"lastIndex,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Filters map[string]string `protobuf:"bytes,4,rep,name=filters,proto3" json:"filters,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *PeerListRequest) Reset() { + *x = PeerListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerListRequest) ProtoMessage() {} + +func (x *PeerListRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeerListRequest.ProtoReflect.Descriptor instead. +func (*PeerListRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{3} +} + +func (x *PeerListRequest) GetNumber() int32 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *PeerListRequest) GetLastIndex() int32 { + if x != nil { + return x.LastIndex + } + return 0 +} + +func (x *PeerListRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PeerListRequest) GetFilters() map[string]string { + if x != nil { + return x.Filters + } + return nil +} + +type SquadConnectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=userId,proto3" json:"userId,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + AuthType string `protobuf:"bytes,4,opt,name=authType,proto3" json:"authType,omitempty"` + NetworkType string `protobuf:"bytes,5,opt,name=networkType,proto3" json:"networkType,omitempty"` +} + +func (x *SquadConnectRequest) Reset() { + *x = SquadConnectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadConnectRequest) ProtoMessage() {} + +func (x *SquadConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadConnectRequest.ProtoReflect.Descriptor instead. +func (*SquadConnectRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{4} +} + +func (x *SquadConnectRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SquadConnectRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SquadConnectRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *SquadConnectRequest) GetAuthType() string { + if x != nil { + return x.AuthType + } + return "" +} + +func (x *SquadConnectRequest) GetNetworkType() string { + if x != nil { + return x.NetworkType + } + return "" +} + +type ProtoSquad struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Members []string `protobuf:"bytes,3,rep,name=members,proto3" json:"members,omitempty"` + SquadType string `protobuf:"bytes,4,opt,name=squadType,proto3" json:"squadType,omitempty"` + Owner string `protobuf:"bytes,5,opt,name=owner,proto3" json:"owner,omitempty"` + Host string `protobuf:"bytes,6,opt,name=host,proto3" json:"host,omitempty"` + AuthType string `protobuf:"bytes,7,opt,name=authType,proto3" json:"authType,omitempty"` + Status bool `protobuf:"varint,8,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ProtoSquad) Reset() { + *x = ProtoSquad{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProtoSquad) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProtoSquad) ProtoMessage() {} + +func (x *ProtoSquad) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProtoSquad.ProtoReflect.Descriptor instead. +func (*ProtoSquad) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{5} +} + +func (x *ProtoSquad) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ProtoSquad) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ProtoSquad) GetMembers() []string { + if x != nil { + return x.Members + } + return nil +} + +func (x *ProtoSquad) GetSquadType() string { + if x != nil { + return x.SquadType + } + return "" +} + +func (x *ProtoSquad) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +func (x *ProtoSquad) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *ProtoSquad) GetAuthType() string { + if x != nil { + return x.AuthType + } + return "" +} + +func (x *ProtoSquad) GetStatus() bool { + if x != nil { + return x.Status + } + return false +} + +type SquadCreateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + SquadType string `protobuf:"bytes,3,opt,name=squadType,proto3" json:"squadType,omitempty"` + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *SquadCreateRequest) Reset() { + *x = SquadCreateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadCreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadCreateRequest) ProtoMessage() {} + +func (x *SquadCreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadCreateRequest.ProtoReflect.Descriptor instead. +func (*SquadCreateRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{6} +} + +func (x *SquadCreateRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SquadCreateRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SquadCreateRequest) GetSquadType() string { + if x != nil { + return x.SquadType + } + return "" +} + +func (x *SquadCreateRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type SquadListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` + LastIndex int32 `protobuf:"varint,2,opt,name=lastIndex,proto3" json:"lastIndex,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Filters map[string]string `protobuf:"bytes,4,rep,name=filters,proto3" json:"filters,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SquadType string `protobuf:"bytes,5,opt,name=squadType,proto3" json:"squadType,omitempty"` + SquadNetworkType string `protobuf:"bytes,6,opt,name=squadNetworkType,proto3" json:"squadNetworkType,omitempty"` +} + +func (x *SquadListRequest) Reset() { + *x = SquadListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadListRequest) ProtoMessage() {} + +func (x *SquadListRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadListRequest.ProtoReflect.Descriptor instead. +func (*SquadListRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{7} +} + +func (x *SquadListRequest) GetNumber() int32 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *SquadListRequest) GetLastIndex() int32 { + if x != nil { + return x.LastIndex + } + return 0 +} + +func (x *SquadListRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SquadListRequest) GetFilters() map[string]string { + if x != nil { + return x.Filters + } + return nil +} + +func (x *SquadListRequest) GetSquadType() string { + if x != nil { + return x.SquadType + } + return "" +} + +func (x *SquadListRequest) GetSquadNetworkType() string { + if x != nil { + return x.SquadNetworkType + } + return "" +} + +type SquadUpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` + Id string `protobuf:"bytes,5,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + SquadType string `protobuf:"bytes,3,opt,name=squadType,proto3" json:"squadType,omitempty"` + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *SquadUpdateRequest) Reset() { + *x = SquadUpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadUpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadUpdateRequest) ProtoMessage() {} + +func (x *SquadUpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadUpdateRequest.ProtoReflect.Descriptor instead. +func (*SquadUpdateRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{8} +} + +func (x *SquadUpdateRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SquadUpdateRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SquadUpdateRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SquadUpdateRequest) GetSquadType() string { + if x != nil { + return x.SquadType + } + return "" +} + +func (x *SquadUpdateRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type SquadDeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` + SquadId string `protobuf:"bytes,2,opt,name=squadId,proto3" json:"squadId,omitempty"` +} + +func (x *SquadDeleteRequest) Reset() { + *x = SquadDeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadDeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadDeleteRequest) ProtoMessage() {} + +func (x *SquadDeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadDeleteRequest.ProtoReflect.Descriptor instead. +func (*SquadDeleteRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{9} +} + +func (x *SquadDeleteRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SquadDeleteRequest) GetSquadId() string { + if x != nil { + return x.SquadId + } + return "" +} + +type Peer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + PubKey string `protobuf:"bytes,3,opt,name=pubKey,proto3" json:"pubKey,omitempty"` + Active bool `protobuf:"varint,4,opt,name=active,proto3" json:"active,omitempty"` +} + +func (x *Peer) Reset() { + *x = Peer{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Peer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Peer) ProtoMessage() {} + +func (x *Peer) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Peer.ProtoReflect.Descriptor instead. +func (*Peer) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{10} +} + +func (x *Peer) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Peer) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Peer) GetPubKey() string { + if x != nil { + return x.PubKey + } + return "" +} + +func (x *Peer) GetActive() bool { + if x != nil { + return x.Active + } + return false +} + +type PeerListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + LastIndex int32 `protobuf:"varint,2,opt,name=lastIndex,proto3" json:"lastIndex,omitempty"` + Peers []*Peer `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` +} + +func (x *PeerListResponse) Reset() { + *x = PeerListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerListResponse) ProtoMessage() {} + +func (x *PeerListResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeerListResponse.ProtoReflect.Descriptor instead. +func (*PeerListResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{11} +} + +func (x *PeerListResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *PeerListResponse) GetLastIndex() int32 { + if x != nil { + return x.LastIndex + } + return 0 +} + +func (x *PeerListResponse) GetPeers() []*Peer { + if x != nil { + return x.Peers + } + return nil +} + +type SquadConnectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` + Members []string `protobuf:"bytes,4,rep,name=members,proto3" json:"members,omitempty"` +} + +func (x *SquadConnectResponse) Reset() { + *x = SquadConnectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadConnectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadConnectResponse) ProtoMessage() {} + +func (x *SquadConnectResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadConnectResponse.ProtoReflect.Descriptor instead. +func (*SquadConnectResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{12} +} + +func (x *SquadConnectResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *SquadConnectResponse) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *SquadConnectResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SquadConnectResponse) GetMembers() []string { + if x != nil { + return x.Members + } + return nil +} + +type SquadLeaveRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` + SquadId string `protobuf:"bytes,2,opt,name=squadId,proto3" json:"squadId,omitempty"` +} + +func (x *SquadLeaveRequest) Reset() { + *x = SquadLeaveRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadLeaveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadLeaveRequest) ProtoMessage() {} + +func (x *SquadLeaveRequest) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadLeaveRequest.ProtoReflect.Descriptor instead. +func (*SquadLeaveRequest) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{13} +} + +func (x *SquadLeaveRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SquadLeaveRequest) GetSquadId() string { + if x != nil { + return x.SquadId + } + return "" +} + +type SquadCreateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + Squad *ProtoSquad `protobuf:"bytes,3,opt,name=squad,proto3" json:"squad,omitempty"` +} + +func (x *SquadCreateResponse) Reset() { + *x = SquadCreateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadCreateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadCreateResponse) ProtoMessage() {} + +func (x *SquadCreateResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadCreateResponse.ProtoReflect.Descriptor instead. +func (*SquadCreateResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{14} +} + +func (x *SquadCreateResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *SquadCreateResponse) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *SquadCreateResponse) GetSquad() *ProtoSquad { + if x != nil { + return x.Squad + } + return nil +} + +type SquadListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + LastIndex int32 `protobuf:"varint,2,opt,name=lastIndex,proto3" json:"lastIndex,omitempty"` + Squads []*ProtoSquad `protobuf:"bytes,3,rep,name=squads,proto3" json:"squads,omitempty"` +} + +func (x *SquadListResponse) Reset() { + *x = SquadListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadListResponse) ProtoMessage() {} + +func (x *SquadListResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadListResponse.ProtoReflect.Descriptor instead. +func (*SquadListResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{15} +} + +func (x *SquadListResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *SquadListResponse) GetLastIndex() int32 { + if x != nil { + return x.LastIndex + } + return 0 +} + +func (x *SquadListResponse) GetSquads() []*ProtoSquad { + if x != nil { + return x.Squads + } + return nil +} + +type SquadUpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + Squad *ProtoSquad `protobuf:"bytes,3,opt,name=squad,proto3" json:"squad,omitempty"` +} + +func (x *SquadUpdateResponse) Reset() { + *x = SquadUpdateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadUpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadUpdateResponse) ProtoMessage() {} + +func (x *SquadUpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadUpdateResponse.ProtoReflect.Descriptor instead. +func (*SquadUpdateResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{16} +} + +func (x *SquadUpdateResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *SquadUpdateResponse) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *SquadUpdateResponse) GetSquad() *ProtoSquad { + if x != nil { + return x.Squad + } + return nil +} + +type SquadDeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Succes bool `protobuf:"varint,1,opt,name=succes,proto3" json:"succes,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + Squad *ProtoSquad `protobuf:"bytes,3,opt,name=squad,proto3" json:"squad,omitempty"` +} + +func (x *SquadDeleteResponse) Reset() { + *x = SquadDeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadDeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadDeleteResponse) ProtoMessage() {} + +func (x *SquadDeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadDeleteResponse.ProtoReflect.Descriptor instead. +func (*SquadDeleteResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{17} +} + +func (x *SquadDeleteResponse) GetSucces() bool { + if x != nil { + return x.Succes + } + return false +} + +func (x *SquadDeleteResponse) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *SquadDeleteResponse) GetSquad() *ProtoSquad { + if x != nil { + return x.Squad + } + return nil +} + +type SquadLeaveResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + SquadId string `protobuf:"bytes,3,opt,name=squadId,proto3" json:"squadId,omitempty"` +} + +func (x *SquadLeaveResponse) Reset() { + *x = SquadLeaveResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SquadLeaveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SquadLeaveResponse) ProtoMessage() {} + +func (x *SquadLeaveResponse) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SquadLeaveResponse.ProtoReflect.Descriptor instead. +func (*SquadLeaveResponse) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{18} +} + +func (x *SquadLeaveResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *SquadLeaveResponse) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *SquadLeaveResponse) GetSquadId() string { + if x != nil { + return x.SquadId + } + return "" +} + +type Response struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` + Payload map[string]string `protobuf:"bytes,3,rep,name=payload,proto3" json:"payload,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *Response) Reset() { + *x = Response{} + if protoimpl.UnsafeEnabled { + mi := &file_grpc_manager_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_grpc_manager_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_grpc_manager_proto_rawDescGZIP(), []int{19} +} + +func (x *Response) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Response) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *Response) GetPayload() map[string]string { + if x != nil { + return x.Payload + } + return nil +} + +var File_grpc_manager_proto protoreflect.FileDescriptor + +var file_grpc_manager_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x22, 0xbc, 0x01, + 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, + 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x1a, 0x3a, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x6b, 0x0a, 0x13, + 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x65, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x65, + 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x55, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x65, 0x65, + 0x72, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x42, 0x0a, 0x14, 0x50, 0x65, 0x65, + 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6c, + 0x6f, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x22, 0xd8, 0x01, + 0x0a, 0x0f, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x61, 0x73, + 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6c, 0x61, + 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x07, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x97, 0x01, 0x0a, 0x13, 0x53, 0x71, 0x75, + 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, + 0x70, 0x65, 0x22, 0xc6, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x71, 0x75, 0x61, + 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x71, 0x75, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x71, 0x75, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54, + 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x7a, 0x0a, 0x12, 0x53, + 0x71, 0x75, 0x61, 0x64, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x71, 0x75, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x71, 0x75, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0xa4, 0x02, 0x0a, 0x10, 0x53, 0x71, 0x75, 0x61, + 0x64, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x07, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x71, 0x75, 0x61, + 0x64, 0x54, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x71, 0x75, + 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x73, 0x71, 0x75, 0x61, 0x64, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x73, 0x71, 0x75, 0x61, 0x64, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x54, 0x79, + 0x70, 0x65, 0x1a, 0x3a, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8a, + 0x01, 0x0a, 0x12, 0x53, 0x71, 0x75, 0x61, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x71, 0x75, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x71, 0x75, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x46, 0x0a, 0x12, 0x53, + 0x71, 0x75, 0x61, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x71, 0x75, + 0x61, 0x64, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x71, 0x75, 0x61, + 0x64, 0x49, 0x64, 0x22, 0x5a, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, + 0x6f, 0x0a, 0x10, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, + 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x23, 0x0a, 0x05, 0x70, + 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, + 0x22, 0x72, 0x0a, 0x14, 0x53, 0x71, 0x75, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x22, 0x45, 0x0a, 0x11, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x65, 0x61, + 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, + 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x71, 0x75, 0x61, 0x64, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x71, 0x75, 0x61, 0x64, 0x49, 0x64, 0x22, 0x72, 0x0a, 0x13, 0x53, + 0x71, 0x75, 0x61, 0x64, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x71, 0x75, 0x61, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x53, 0x71, 0x75, 0x61, 0x64, 0x52, 0x05, 0x73, 0x71, 0x75, 0x61, 0x64, 0x22, + 0x78, 0x0a, 0x11, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x1c, + 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x06, + 0x73, 0x71, 0x75, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x71, 0x75, 0x61, + 0x64, 0x52, 0x06, 0x73, 0x71, 0x75, 0x61, 0x64, 0x73, 0x22, 0x72, 0x0a, 0x13, 0x53, 0x71, 0x75, + 0x61, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x71, 0x75, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x53, 0x71, 0x75, 0x61, 0x64, 0x52, 0x05, 0x73, 0x71, 0x75, 0x61, 0x64, 0x22, 0x70, 0x0a, + 0x13, 0x53, 0x71, 0x75, 0x61, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x71, 0x75, 0x61, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x53, 0x71, 0x75, 0x61, 0x64, 0x52, 0x05, 0x73, 0x71, 0x75, 0x61, 0x64, 0x22, + 0x60, 0x0a, 0x12, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x71, 0x75, 0x61, 0x64, + 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x71, 0x75, 0x61, 0x64, 0x49, + 0x64, 0x22, 0xae, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x38, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x3a, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, + 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x32, 0x83, 0x05, 0x0a, 0x0b, 0x47, 0x72, 0x70, 0x63, 0x4d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x10, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, + 0x01, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, + 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x40, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x18, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x71, 0x75, 0x61, + 0x64, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, + 0x64, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0b, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x71, 0x75, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x71, 0x75, 0x61, 0x64, 0x12, 0x1b, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, + 0x53, 0x71, 0x75, 0x61, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, + 0x61, 0x64, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x42, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x71, 0x75, 0x61, 0x64, 0x12, 0x19, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, + 0x71, 0x75, 0x61, 0x64, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, + 0x71, 0x75, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, + 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x53, 0x71, 0x75, 0x61, 0x64, 0x12, + 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, + 0x65, 0x61, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x71, 0x75, 0x61, 0x64, 0x4c, 0x65, 0x61, 0x76, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_grpc_manager_proto_rawDescOnce sync.Once + file_grpc_manager_proto_rawDescData = file_grpc_manager_proto_rawDesc +) + +func file_grpc_manager_proto_rawDescGZIP() []byte { + file_grpc_manager_proto_rawDescOnce.Do(func() { + file_grpc_manager_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_manager_proto_rawDescData) + }) + return file_grpc_manager_proto_rawDescData +} + +var file_grpc_manager_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_grpc_manager_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: manager.Request + (*PeerRegisterRequest)(nil), // 1: manager.PeerRegisterRequest + (*PeerRegisterResponse)(nil), // 2: manager.PeerRegisterResponse + (*PeerListRequest)(nil), // 3: manager.PeerListRequest + (*SquadConnectRequest)(nil), // 4: manager.SquadConnectRequest + (*ProtoSquad)(nil), // 5: manager.ProtoSquad + (*SquadCreateRequest)(nil), // 6: manager.SquadCreateRequest + (*SquadListRequest)(nil), // 7: manager.SquadListRequest + (*SquadUpdateRequest)(nil), // 8: manager.SquadUpdateRequest + (*SquadDeleteRequest)(nil), // 9: manager.SquadDeleteRequest + (*Peer)(nil), // 10: manager.Peer + (*PeerListResponse)(nil), // 11: manager.PeerListResponse + (*SquadConnectResponse)(nil), // 12: manager.SquadConnectResponse + (*SquadLeaveRequest)(nil), // 13: manager.SquadLeaveRequest + (*SquadCreateResponse)(nil), // 14: manager.SquadCreateResponse + (*SquadListResponse)(nil), // 15: manager.SquadListResponse + (*SquadUpdateResponse)(nil), // 16: manager.SquadUpdateResponse + (*SquadDeleteResponse)(nil), // 17: manager.SquadDeleteResponse + (*SquadLeaveResponse)(nil), // 18: manager.SquadLeaveResponse + (*Response)(nil), // 19: manager.Response + nil, // 20: manager.Request.PayloadEntry + nil, // 21: manager.PeerListRequest.FiltersEntry + nil, // 22: manager.SquadListRequest.FiltersEntry + nil, // 23: manager.Response.PayloadEntry +} +var file_grpc_manager_proto_depIdxs = []int32{ + 20, // 0: manager.Request.payload:type_name -> manager.Request.PayloadEntry + 21, // 1: manager.PeerListRequest.filters:type_name -> manager.PeerListRequest.FiltersEntry + 22, // 2: manager.SquadListRequest.filters:type_name -> manager.SquadListRequest.FiltersEntry + 10, // 3: manager.PeerListResponse.peers:type_name -> manager.Peer + 5, // 4: manager.SquadCreateResponse.squad:type_name -> manager.ProtoSquad + 5, // 5: manager.SquadListResponse.squads:type_name -> manager.ProtoSquad + 5, // 6: manager.SquadUpdateResponse.squad:type_name -> manager.ProtoSquad + 5, // 7: manager.SquadDeleteResponse.squad:type_name -> manager.ProtoSquad + 23, // 8: manager.Response.payload:type_name -> manager.Response.PayloadEntry + 0, // 9: manager.GrpcManager.Link:input_type -> manager.Request + 1, // 10: manager.GrpcManager.RegisterPeer:input_type -> manager.PeerRegisterRequest + 3, // 11: manager.GrpcManager.ListPeers:input_type -> manager.PeerListRequest + 6, // 12: manager.GrpcManager.CreateSquad:input_type -> manager.SquadCreateRequest + 8, // 13: manager.GrpcManager.UpdateSquad:input_type -> manager.SquadUpdateRequest + 9, // 14: manager.GrpcManager.DeleteSquad:input_type -> manager.SquadDeleteRequest + 7, // 15: manager.GrpcManager.ListSquad:input_type -> manager.SquadListRequest + 4, // 16: manager.GrpcManager.ConnectSquad:input_type -> manager.SquadConnectRequest + 13, // 17: manager.GrpcManager.LeaveSquad:input_type -> manager.SquadLeaveRequest + 19, // 18: manager.GrpcManager.Link:output_type -> manager.Response + 2, // 19: manager.GrpcManager.RegisterPeer:output_type -> manager.PeerRegisterResponse + 11, // 20: manager.GrpcManager.ListPeers:output_type -> manager.PeerListResponse + 14, // 21: manager.GrpcManager.CreateSquad:output_type -> manager.SquadCreateResponse + 16, // 22: manager.GrpcManager.UpdateSquad:output_type -> manager.SquadUpdateResponse + 17, // 23: manager.GrpcManager.DeleteSquad:output_type -> manager.SquadDeleteResponse + 15, // 24: manager.GrpcManager.ListSquad:output_type -> manager.SquadListResponse + 12, // 25: manager.GrpcManager.ConnectSquad:output_type -> manager.SquadConnectResponse + 18, // 26: manager.GrpcManager.LeaveSquad:output_type -> manager.SquadLeaveResponse + 18, // [18:27] is the sub-list for method output_type + 9, // [9:18] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_grpc_manager_proto_init() } +func file_grpc_manager_proto_init() { + if File_grpc_manager_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_grpc_manager_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerRegisterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerRegisterResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadConnectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProtoSquad); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadCreateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadUpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadDeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Peer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadConnectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadLeaveRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadCreateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadUpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadDeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SquadLeaveResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_grpc_manager_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_grpc_manager_proto_rawDesc, + NumEnums: 0, + NumMessages: 24, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_grpc_manager_proto_goTypes, + DependencyIndexes: file_grpc_manager_proto_depIdxs, + MessageInfos: file_grpc_manager_proto_msgTypes, + }.Build() + File_grpc_manager_proto = out.File + file_grpc_manager_proto_rawDesc = nil + file_grpc_manager_proto_goTypes = nil + file_grpc_manager_proto_depIdxs = nil +} diff --git a/grpc_manager_grpc.pb.go b/grpc_manager_grpc.pb.go new file mode 100644 index 0000000..cd43f63 --- /dev/null +++ b/grpc_manager_grpc.pb.go @@ -0,0 +1,422 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package localserver + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// GrpcManagerClient is the client API for GrpcManager service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GrpcManagerClient interface { + Link(ctx context.Context, opts ...grpc.CallOption) (GrpcManager_LinkClient, error) + RegisterPeer(ctx context.Context, in *PeerRegisterRequest, opts ...grpc.CallOption) (*PeerRegisterResponse, error) + ListPeers(ctx context.Context, in *PeerListRequest, opts ...grpc.CallOption) (*PeerListResponse, error) + CreateSquad(ctx context.Context, in *SquadCreateRequest, opts ...grpc.CallOption) (*SquadCreateResponse, error) + UpdateSquad(ctx context.Context, in *SquadUpdateRequest, opts ...grpc.CallOption) (*SquadUpdateResponse, error) + DeleteSquad(ctx context.Context, in *SquadDeleteRequest, opts ...grpc.CallOption) (*SquadDeleteResponse, error) + ListSquad(ctx context.Context, in *SquadListRequest, opts ...grpc.CallOption) (*SquadListResponse, error) + ConnectSquad(ctx context.Context, in *SquadConnectRequest, opts ...grpc.CallOption) (*SquadConnectResponse, error) + LeaveSquad(ctx context.Context, in *SquadLeaveRequest, opts ...grpc.CallOption) (*SquadLeaveResponse, error) +} + +type grpcManagerClient struct { + cc grpc.ClientConnInterface +} + +func NewGrpcManagerClient(cc grpc.ClientConnInterface) GrpcManagerClient { + return &grpcManagerClient{cc} +} + +func (c *grpcManagerClient) Link(ctx context.Context, opts ...grpc.CallOption) (GrpcManager_LinkClient, error) { + stream, err := c.cc.NewStream(ctx, &GrpcManager_ServiceDesc.Streams[0], "/manager.GrpcManager/Link", opts...) + if err != nil { + return nil, err + } + x := &grpcManagerLinkClient{stream} + return x, nil +} + +type GrpcManager_LinkClient interface { + Send(*Request) error + Recv() (*Response, error) + grpc.ClientStream +} + +type grpcManagerLinkClient struct { + grpc.ClientStream +} + +func (x *grpcManagerLinkClient) Send(m *Request) error { + return x.ClientStream.SendMsg(m) +} + +func (x *grpcManagerLinkClient) Recv() (*Response, error) { + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *grpcManagerClient) RegisterPeer(ctx context.Context, in *PeerRegisterRequest, opts ...grpc.CallOption) (*PeerRegisterResponse, error) { + out := new(PeerRegisterResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/RegisterPeer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) ListPeers(ctx context.Context, in *PeerListRequest, opts ...grpc.CallOption) (*PeerListResponse, error) { + out := new(PeerListResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/ListPeers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) CreateSquad(ctx context.Context, in *SquadCreateRequest, opts ...grpc.CallOption) (*SquadCreateResponse, error) { + out := new(SquadCreateResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/CreateSquad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) UpdateSquad(ctx context.Context, in *SquadUpdateRequest, opts ...grpc.CallOption) (*SquadUpdateResponse, error) { + out := new(SquadUpdateResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/UpdateSquad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) DeleteSquad(ctx context.Context, in *SquadDeleteRequest, opts ...grpc.CallOption) (*SquadDeleteResponse, error) { + out := new(SquadDeleteResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/DeleteSquad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) ListSquad(ctx context.Context, in *SquadListRequest, opts ...grpc.CallOption) (*SquadListResponse, error) { + out := new(SquadListResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/ListSquad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) ConnectSquad(ctx context.Context, in *SquadConnectRequest, opts ...grpc.CallOption) (*SquadConnectResponse, error) { + out := new(SquadConnectResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/ConnectSquad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *grpcManagerClient) LeaveSquad(ctx context.Context, in *SquadLeaveRequest, opts ...grpc.CallOption) (*SquadLeaveResponse, error) { + out := new(SquadLeaveResponse) + err := c.cc.Invoke(ctx, "/manager.GrpcManager/LeaveSquad", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GrpcManagerServer is the server API for GrpcManager service. +// All implementations must embed UnimplementedGrpcManagerServer +// for forward compatibility +type GrpcManagerServer interface { + Link(GrpcManager_LinkServer) error + RegisterPeer(context.Context, *PeerRegisterRequest) (*PeerRegisterResponse, error) + ListPeers(context.Context, *PeerListRequest) (*PeerListResponse, error) + CreateSquad(context.Context, *SquadCreateRequest) (*SquadCreateResponse, error) + UpdateSquad(context.Context, *SquadUpdateRequest) (*SquadUpdateResponse, error) + DeleteSquad(context.Context, *SquadDeleteRequest) (*SquadDeleteResponse, error) + ListSquad(context.Context, *SquadListRequest) (*SquadListResponse, error) + ConnectSquad(context.Context, *SquadConnectRequest) (*SquadConnectResponse, error) + LeaveSquad(context.Context, *SquadLeaveRequest) (*SquadLeaveResponse, error) + mustEmbedUnimplementedGrpcManagerServer() +} + +// UnimplementedGrpcManagerServer must be embedded to have forward compatible implementations. +type UnimplementedGrpcManagerServer struct { +} + +func (UnimplementedGrpcManagerServer) Link(GrpcManager_LinkServer) error { + return status.Errorf(codes.Unimplemented, "method Link not implemented") +} +func (UnimplementedGrpcManagerServer) RegisterPeer(context.Context, *PeerRegisterRequest) (*PeerRegisterResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterPeer not implemented") +} +func (UnimplementedGrpcManagerServer) ListPeers(context.Context, *PeerListRequest) (*PeerListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListPeers not implemented") +} +func (UnimplementedGrpcManagerServer) CreateSquad(context.Context, *SquadCreateRequest) (*SquadCreateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateSquad not implemented") +} +func (UnimplementedGrpcManagerServer) UpdateSquad(context.Context, *SquadUpdateRequest) (*SquadUpdateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateSquad not implemented") +} +func (UnimplementedGrpcManagerServer) DeleteSquad(context.Context, *SquadDeleteRequest) (*SquadDeleteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteSquad not implemented") +} +func (UnimplementedGrpcManagerServer) ListSquad(context.Context, *SquadListRequest) (*SquadListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListSquad not implemented") +} +func (UnimplementedGrpcManagerServer) ConnectSquad(context.Context, *SquadConnectRequest) (*SquadConnectResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectSquad not implemented") +} +func (UnimplementedGrpcManagerServer) LeaveSquad(context.Context, *SquadLeaveRequest) (*SquadLeaveResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method LeaveSquad not implemented") +} +func (UnimplementedGrpcManagerServer) mustEmbedUnimplementedGrpcManagerServer() {} + +// UnsafeGrpcManagerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GrpcManagerServer will +// result in compilation errors. +type UnsafeGrpcManagerServer interface { + mustEmbedUnimplementedGrpcManagerServer() +} + +func RegisterGrpcManagerServer(s grpc.ServiceRegistrar, srv GrpcManagerServer) { + s.RegisterService(&GrpcManager_ServiceDesc, srv) +} + +func _GrpcManager_Link_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GrpcManagerServer).Link(&grpcManagerLinkServer{stream}) +} + +type GrpcManager_LinkServer interface { + Send(*Response) error + Recv() (*Request, error) + grpc.ServerStream +} + +type grpcManagerLinkServer struct { + grpc.ServerStream +} + +func (x *grpcManagerLinkServer) Send(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *grpcManagerLinkServer) Recv() (*Request, error) { + m := new(Request) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _GrpcManager_RegisterPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PeerRegisterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).RegisterPeer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/RegisterPeer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).RegisterPeer(ctx, req.(*PeerRegisterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_ListPeers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PeerListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).ListPeers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/ListPeers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).ListPeers(ctx, req.(*PeerListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_CreateSquad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SquadCreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).CreateSquad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/CreateSquad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).CreateSquad(ctx, req.(*SquadCreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_UpdateSquad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SquadUpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).UpdateSquad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/UpdateSquad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).UpdateSquad(ctx, req.(*SquadUpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_DeleteSquad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SquadDeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).DeleteSquad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/DeleteSquad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).DeleteSquad(ctx, req.(*SquadDeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_ListSquad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SquadListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).ListSquad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/ListSquad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).ListSquad(ctx, req.(*SquadListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_ConnectSquad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SquadConnectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).ConnectSquad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/ConnectSquad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).ConnectSquad(ctx, req.(*SquadConnectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _GrpcManager_LeaveSquad_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SquadLeaveRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrpcManagerServer).LeaveSquad(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/manager.GrpcManager/LeaveSquad", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrpcManagerServer).LeaveSquad(ctx, req.(*SquadLeaveRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// GrpcManager_ServiceDesc is the grpc.ServiceDesc for GrpcManager service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var GrpcManager_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "manager.GrpcManager", + HandlerType: (*GrpcManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterPeer", + Handler: _GrpcManager_RegisterPeer_Handler, + }, + { + MethodName: "ListPeers", + Handler: _GrpcManager_ListPeers_Handler, + }, + { + MethodName: "CreateSquad", + Handler: _GrpcManager_CreateSquad_Handler, + }, + { + MethodName: "UpdateSquad", + Handler: _GrpcManager_UpdateSquad_Handler, + }, + { + MethodName: "DeleteSquad", + Handler: _GrpcManager_DeleteSquad_Handler, + }, + { + MethodName: "ListSquad", + Handler: _GrpcManager_ListSquad_Handler, + }, + { + MethodName: "ConnectSquad", + Handler: _GrpcManager_ConnectSquad_Handler, + }, + { + MethodName: "LeaveSquad", + Handler: _GrpcManager_LeaveSquad_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Link", + Handler: _GrpcManager_Link_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpc_manager.proto", +} diff --git a/logMiddleware.go b/logMiddleware.go new file mode 100644 index 0000000..fee3e04 --- /dev/null +++ b/logMiddleware.go @@ -0,0 +1,32 @@ +package localserver + +// type ( +// LogMiddleware struct { +// menuItem *systray.MenuItem +// } +// ) + +// func NewLogMiddleware(menuItem *systray.MenuItem) *LogMiddleware { +// return &LogMiddleware{ +// menuItem: menuItem, +// } +// } + +// func (lm *LogMiddleware) Process(ctx context.Context, req *http.Request, w http.ResponseWriter) (err error) { +// done, errCh := make(chan struct{}), make(chan error) +// go func() { +// logger.Println("received a request") +// lm.menuItem.AddSubMenuItemCheckbox(filepath.Join("remote addr : %s", req.RemoteAddr), "remote", false) +// logger.Printf("from %s\n", req.RemoteAddr) +// done <- struct{}{} +// }() +// select { +// case <-ctx.Done(): +// err = ctx.Err() +// return +// case <-done: +// return nil +// case err = <-errCh: +// return +// } +// } diff --git a/p2p_fs_datachannel_manager.go b/p2p_fs_datachannel_manager.go new file mode 100644 index 0000000..81813a5 --- /dev/null +++ b/p2p_fs_datachannel_manager.go @@ -0,0 +1,355 @@ +package localserver + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "log" + "os" + "sync" + "time" + + "github.com/pion/webrtc/v3" +) + +const ( + HOME = "home" +) + +const ( + FS_GET_FOLDER = "fs_get_folder" + FS_GET_FOLDER_RESPONSE = "fs_get_folder_response" + FS_UPLOAD_FILE = "fs_upload_file" + FS_UPLOAD_FILE_INIT = "fs_upload_file_init" + FS_UPLOAD_FILE_END = "fs_upload_file_response_end" + FS_DOWNLOAD_FILE = "fs_download_file" + FS_DOWNLOAD_FILE_RESPONSE = "fs_download_file_response" + FS_DOWNLOAD_FILE_RESPONSE_INIT = "fs_download_file_response_init" + FS_DOWNLOAD_FILE_RESPONSE_END = "fs_download_file_response_end" + FS_CREATE_FOLDER = "fs_create_folder" +) + +type DataChannelManager interface { + HandleMessage(message *DatachannelMessage, channel *webrtc.DataChannel) (done chan struct{}, errChan chan error) +} + +type P2PFSDatachannelManager struct { + l *sync.Mutex + uploadFile map[string]*os.File +} + +type DatachannelMessage struct { + From string `json:"from"` + Type string `json:"type"` + Payload *DatachannelMessagePayload `json:"payload"` +} + +type DatachannelMessagePayload struct { + Path string + Content []byte +} + +type FSResultInfo struct { + Name string + Type string + Path string + Size int64 + ModTime string +} + +func NewP2PFSDatachannelManager() (manager *P2PFSDatachannelManager) { + manager = &P2PFSDatachannelManager{&sync.Mutex{}, make(map[string]*os.File)} + return +} + +func (pdm *P2PFSDatachannelManager) HandleMessage(message *DatachannelMessage, channel *webrtc.DataChannel) (done chan struct{}, errChan chan error) { + done, errChan = make(chan struct{}), make(chan error) + go func() { + switch message.Type { + case FS_GET_FOLDER: + logger.Println("try to get folder") + + done, err := pdm.handleGetFolder(message.Payload.Path, channel) + select { + case <-done: + logger.Println("operation succeed") + case e := <-err: + errChan <- e + return + } + case FS_DOWNLOAD_FILE: + logger.Println("tried to download a file") + done, err := pdm.sendFile(message.Payload.Path, channel) + select { + case <-done: + logger.Println("operation succeed") + case e := <-err: + errChan <- e + return + } + case FS_UPLOAD_FILE_INIT: + logger.Println("tried to upload a file") + if err := pdm.downloadFileInit(message.Payload.Path); err != nil { + errChan <- err + return + } + case FS_UPLOAD_FILE: + if err := pdm.downloadFile(message.Payload.Path, message.Payload.Content); err != nil { + errChan <- err + return + } + case FS_UPLOAD_FILE_END: + if err := pdm.downloadFileEnd(message.Payload.Path); err != nil { + errChan <- err + return + } + case FS_CREATE_FOLDER: + if err := pdm.createFolder(message.Payload.Path); err != nil { + errChan <- err + return + } + default: + logger.Printf("got a new message from %s with payload %v\n", message.From, message.Payload) + } + done <- struct{}{} + }() + return done, errChan +} + +func (pdm *P2PFSDatachannelManager) handleGetFolder(path string, channel *webrtc.DataChannel) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + var dirPath string + if path == HOME { + homeDir, err := os.UserHomeDir() + logger.Println(homeDir) + if err != nil { + errCh <- err + return + } + dirPath = homeDir + } else { + dirPath = path + } + dir, err := os.ReadDir(dirPath) + if err != nil { + errCh <- err + return + } + index := 1 + for i, v := range dirPath { + if i > 0 { + if v == '/' { + index = i + } + } + } + dirs := []*FSResultInfo{{Name: "..", Type: "directory", Path: dirPath[:index], Size: 0, ModTime: time.Now().Format("January 2, 2006")}} + for _, d := range dir { + f := &FSResultInfo{} + info, err := d.Info() + if err != nil { + logger.Println(err) + continue + } + f.ModTime = fmt.Sprintf("%s at %s", info.ModTime().Format("January 2, 2006"), info.ModTime().Format("15:04:05")) + f.Path = fmt.Sprintf("%s/%s", dirPath, info.Name()) + f.Size = info.Size() + f.Name = info.Name() + if info.IsDir() { + f.Type = "directory" + } else { + f.Type = "file" + } + dirs = append(dirs, f) + } + + if len(dirs) < 100 { + logger.Println("dir smaller than 100") + bs, err := json.Marshal(map[string]interface{}{ + "type": FS_GET_FOLDER_RESPONSE, + "from": "lolo_local_serv", + "payload": map[string]interface{}{ + "folderPath": dirPath, + "dirContent": dirs, + }, + }) + if err != nil { + errCh <- err + return + } + if err := channel.SendText(string(bs)); err != nil { + errCh <- err + return + } + } else { + logger.Println("dir greater than 10000") + reste := len(dirs) % 100 + x := (len(dirs) - reste) / 100 + for j := 0; j < x; j++ { + logger.Println("dir sending packet than 100") + d := dirs[j*100 : (j+1)*100] + logger.Println("length of d :", len(d)) + bs, err := json.Marshal(map[string]interface{}{ + "type": FS_GET_FOLDER_RESPONSE, + "from": "lolo_local_serv", + "payload": map[string]interface{}{ + "folderPath": dirPath, + "dirContent": d, + }, + }) + if err != nil { + errCh <- err + return + } + if err := channel.SendText(string(bs)); err != nil { + errCh <- err + return + } + } + bs, err := json.Marshal(map[string]interface{}{ + "type": FS_GET_FOLDER_RESPONSE, + "from": "lolo_local_serv", + "payload": map[string]interface{}{ + "folderPath": dirPath, + "dirContent": dirs[len(dirs)-reste:], + }, + }) + if err != nil { + errCh <- err + return + } + if err := channel.SendText(string(bs)); err != nil { + errCh <- err + return + } + } + done <- struct{}{} + }() + return +} + +func (pdm *P2PFSDatachannelManager) sendFile(path string, channel *webrtc.DataChannel) (<-chan struct{}, <-chan error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + f, err := os.Open(path) + if err != nil { + errCh <- err + return + } + bsInit, err := json.Marshal(map[string]interface{}{ + "type": FS_DOWNLOAD_FILE_RESPONSE_INIT, + "from": "lolo_local_serv", + "payload": map[string]string{ + "path": path, + }, + }) + if err != nil { + errCh <- err + return + } + if err := channel.SendText(string(bsInit)); err != nil { + errCh <- err + return + } + r := bufio.NewReader(f) + buf := make([]byte, 0, 10000) + for { + n, err := r.Read(buf[:cap(buf)]) + buf = buf[:n] + if n == 0 { + if err == nil { + continue + } + if err == io.EOF { + break + } + log.Fatal(err) + } + bs, err := json.Marshal(map[string]interface{}{ + "type": FS_DOWNLOAD_FILE_RESPONSE, + "from": "lolo_local_serv", + "payload": map[string]interface{}{ + "path": path, + "content": buf, + }, + }) + if err != nil { + errCh <- err + return + } + if err := channel.SendText(string(bs)); err != nil { + errCh <- err + return + } + } + bs, err := json.Marshal(map[string]interface{}{ + "type": FS_DOWNLOAD_FILE_RESPONSE_END, + "from": "lolo_local_serv", + "payload": map[string]string{ + "path": path, + }, + }) + if err != nil { + errCh <- err + return + } + if err := channel.SendText(string(bs)); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + return done, errCh +} + +func (pdm *P2PFSDatachannelManager) downloadFileInit(path string) (err error) { + logger.Println("upload path name is", path) + if _, ok := pdm.uploadFile[path]; ok { + err = fmt.Errorf("the file %s is already being uploaded", path) + return + } + file, e := os.Create(path) + if e != nil { + err = fmt.Errorf("an error occured in download file init : %v", e) + return + } + index := 1 + for i, v := range path { + if i > 0 { + if v == '/' { + index = i + } + } + } + logger.Println(path) + logger.Println(path[index+1:]) + pdm.uploadFile[path[index+1:]] = file + return +} + +func (pdm *P2PFSDatachannelManager) downloadFile(path string, content []byte) (err error) { + if _, ok := pdm.uploadFile[path]; !ok { + err = fmt.Errorf("no upload file open for path %s", path) + return + } + _, err = pdm.uploadFile[path].Write(content) + return +} + +func (pdm *P2PFSDatachannelManager) downloadFileEnd(path string) (err error) { + logger.Println("closing file") + if _, ok := pdm.uploadFile[path]; !ok { + err = fmt.Errorf("no upload file open for path %s", path) + return + } + err = pdm.uploadFile[path].Close() + delete(pdm.uploadFile, path) + return +} + +func (pdm *P2PFSDatachannelManager) createFolder(path string) (err error) { + err = os.Mkdir(path, 0770) + return +} diff --git a/proto/grpc_manager.proto b/proto/grpc_manager.proto new file mode 100644 index 0000000..6a3da13 --- /dev/null +++ b/proto/grpc_manager.proto @@ -0,0 +1,149 @@ +syntax = "proto3"; +package manager; +option go_package=".;localserver"; + +message Request { + string type = 1; + string from = 2; + string token = 3; + map payload = 4; +} + +message PeerRegisterRequest { + string peerId = 1; + string peerKey = 2; + string peerUsername = 3; +} + +message PeerRegisterResponse { + bool success = 1; + string log = 2; +} + +message PeerListRequest { + int32 number = 1; + int32 lastIndex = 2; + string name = 3; + map filters = 4; +} + +message SquadConnectRequest { + string id = 1; + string userId = 2; + string password = 3; + string authType = 4; + string networkType = 5; +} + +message ProtoSquad { + string id = 1; + string name = 2; + repeated string members = 3; + string squadType = 4; + string owner = 5; + string host = 6; + string authType = 7; + bool status = 8; +} + +message SquadCreateRequest { + string userId = 1; + string name = 2; + string squadType = 3; + string password = 4; +} + +message SquadListRequest { + int32 number = 1; + int32 lastIndex = 2; + string name = 3; + map filters = 4; + string squadType = 5; + string squadNetworkType = 6; +} + +message SquadUpdateRequest { + string userId = 1; + string id = 5; + string name = 2; + string squadType = 3; + string password = 4; +} + +message SquadDeleteRequest { + string userId = 1; + string squadId = 2; +} + +message Peer { + string id = 1; + string name = 2; + string pubKey = 3; + bool active = 4; +} + +message PeerListResponse { + bool success = 1; + int32 lastIndex = 2; + repeated Peer peers = 3; +} + +message SquadConnectResponse { + bool success = 1; + string reason = 2; + string id = 3; + repeated string members = 4; +} + +message SquadLeaveRequest { + string userId = 1; + string squadId = 2; +} + +message SquadCreateResponse { + bool success = 1; + string reason = 2; + ProtoSquad squad = 3; +} + +message SquadListResponse { + bool success = 1; + int32 lastIndex = 2; + repeated ProtoSquad squads = 3; +} + +message SquadUpdateResponse { + bool success = 1; + string reason = 2; + ProtoSquad squad = 3; +} + +message SquadDeleteResponse { + bool succes = 1; + string reason = 2; + ProtoSquad squad = 3; +} + +message SquadLeaveResponse { + bool success = 1; + string reason = 2; + string squadId = 3; +} + +message Response { + string type = 1; + bool success = 2; + map payload = 3; +} + +service GrpcManager { + rpc Link(stream Request) returns (stream Response); + rpc RegisterPeer (PeerRegisterRequest) returns (PeerRegisterResponse); + rpc ListPeers(PeerListRequest) returns (PeerListResponse); + rpc CreateSquad (SquadCreateRequest) returns (SquadCreateResponse); + rpc UpdateSquad (SquadUpdateRequest) returns (SquadUpdateResponse); + rpc DeleteSquad (SquadDeleteRequest) returns (SquadDeleteResponse); + rpc ListSquad (SquadListRequest) returns (SquadListResponse); + rpc ConnectSquad (SquadConnectRequest) returns (SquadConnectResponse); + rpc LeaveSquad (SquadLeaveRequest) returns (SquadLeaveResponse); +} \ No newline at end of file diff --git a/server.go b/server.go new file mode 100644 index 0000000..fe1781d --- /dev/null +++ b/server.go @@ -0,0 +1,222 @@ +package localserver + +import ( + "context" + "log" + "net/http" + "path/filepath" + "sync" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +var logger *log.Logger = new(log.Logger) +var dbLogger *BadgerLogger = &BadgerLogger{ + Logger: logger, +} + +type BadgerLogger struct { + *log.Logger +} + +func (b *BadgerLogger) Debugf(message string, data ...interface{}) { + b.Println(data...) +} + +func (b *BadgerLogger) Errorf(message string, data ...interface{}) { + b.Println(data...) +} + +func (b *BadgerLogger) Infof(message string, data ...interface{}) { + b.Println(data...) +} + +func (b *BadgerLogger) Warningf(message string, data ...interface{}) { + b.Println(data...) +} + +type ( + LocalServerHandlerMiddleware interface { + Process(ctx context.Context, req *http.Request, w http.ResponseWriter) (err error) + } + + GrpcClientManagerMiddleware interface { + Process(ctx context.Context, req *Response, stream GrpcManager_LinkClient) (err error) + } + + LocalServerHandler struct { + middlewares []LocalServerHandlerMiddleware + } + + LocalServer struct { + ID string + GrpcClientManager *GrpcClientManager + } + + GrpcClientManager struct { + GrpcConn grpc.ClientConnInterface + GrpcManagerClient GrpcManagerClient + GrpcLinkClient GrpcManager_LinkClient + middlewares []GrpcClientManagerMiddleware + } + + CustomMenuItem struct { + //MenuItem *systray.MenuItem + ID string + State bool + SubMenus []*CustomMenuItem + CallBack func(bool) + } + + ReqType string + + LocalServerRequest struct { + ReqType ReqType + Payload map[string]interface{} + } +) + +func NewLocalServer(addr string, grpcAddr string, id string, token string) (localServer *LocalServer, err error) { + webRTCCallManager, err := NewWebRTCCallManager(id, token, NewWebrtcCallSoundManager(), NewWebrtcCallChatManager(), NewWebrtcCallVideoManager(), NewWebrtcCallFileManager()) + if err != nil { + return + } + zoneManager, err := NewZoneManager(id, token) + if err != nil { + return + } + webrtcFsManager, err := NewWebrtcFsManager(NewP2PFSDatachannelManager()) + if err != nil { + return + } + webrtcGrpcMiddleware := NewWebRTCGrpcMiddleware(webRTCCallManager) + ZoneGrpcMiddleware := NewZoneGrpcMiddleware(zoneManager) + webrtcFsMiddleware := NewWebRTCFsMiddleware(webrtcFsManager) + grpcClientManager, err := NewGrpcClientManager(grpcAddr, id, webrtcGrpcMiddleware, ZoneGrpcMiddleware) + webrtcGrpcMiddleware.stream = grpcClientManager.GrpcLinkClient + webRTCCallManager.stream = grpcClientManager.GrpcLinkClient + zoneManager.stream = grpcClientManager.GrpcLinkClient + webrtcFsMiddleware.stream = grpcClientManager.GrpcLinkClient + webrtcFsManager.stream = grpcClientManager.GrpcLinkClient + ZoneGrpcMiddleware.stream = grpcClientManager.GrpcLinkClient + localServer = &LocalServer{ + ID: id, + GrpcClientManager: grpcClientManager, + } + return +} + +func NewGrpcClientManager(addr string, id string, middleware ...GrpcClientManagerMiddleware) (grpcClientManager *GrpcClientManager, err error) { + conn, grpcClient, grpcLinkClient, err := NewGrpcConn(addr, id) + if err != nil { + return + } + grpcClientManager = &GrpcClientManager{ + GrpcConn: conn, + GrpcManagerClient: grpcClient, + GrpcLinkClient: grpcLinkClient, + middlewares: middleware, + } + return +} + +func NewGrpcConn(addr string, id string) (conn grpc.ClientConnInterface, grpcClient GrpcManagerClient, grpcLinkClient GrpcManager_LinkClient, err error) { + var cert = filepath.Join("config", "cert.pem") + creds, err := credentials.NewClientTLSFromFile(cert, "dev.zippytal.com") + if err != nil { + return + } + var opts []grpc.DialOption = []grpc.DialOption{grpc.WithTransportCredentials(creds)} + conn, err = grpc.Dial(addr, opts...) + if err != nil { + return + } + grpcClient = NewGrpcManagerClient(conn) + grpcLinkClient, err = grpcClient.Link(context.Background()) + if err != nil { + logger.Println(err) + return + } + if err = grpcLinkClient.Send(&Request{ + Type: "init", + From: id, + Payload: map[string]string{}, + Token: "none", + }); err != nil { + return + } + return +} + +func NewLocalServerHandler(middlewares ...LocalServerHandlerMiddleware) (localServerHandler *LocalServerHandler) { + localServerHandler = &LocalServerHandler{ + middlewares: middlewares, + } + return +} + +func (lsh *LocalServerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + wg, done, errCh := &sync.WaitGroup{}, make(chan struct{}), make(chan error) + for _, middleware := range lsh.middlewares { + wg.Add(1) + go func(m LocalServerHandlerMiddleware) { + err := m.Process(req.Context(), req, w) + if err != nil { + errCh <- err + } + wg.Done() + }(middleware) + } + go func() { + wg.Wait() + done <- struct{}{} + }() + select { + case <-req.Context().Done(): + case <-done: + case err := <-errCh: + logger.Println(err) + } +} + +func (gcm *GrpcClientManager) Handle(ctx context.Context) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + wg := new(sync.WaitGroup) + for { + res, err := gcm.GrpcLinkClient.Recv() + if err != nil { + errCh <- err + return + } + for _, middleware := range gcm.middlewares { + wg.Add(1) + go func(m GrpcClientManagerMiddleware) { + if err := m.Process(ctx, res, gcm.GrpcLinkClient); err != nil { + logger.Println(err) + } + wg.Done() + }(middleware) + } + wg.Wait() + } + }() + select { + case <-gcm.GrpcLinkClient.Context().Done(): + logger.Println("grpc context") + err = gcm.GrpcLinkClient.Context().Err() + return + case <-ctx.Done(): + logger.Println("app context") + err = ctx.Err() + return + case <-done: + return + case err = <-errCh: + if closeErr := gcm.GrpcLinkClient.CloseSend(); closeErr != nil { + return closeErr + } + return + } +} diff --git a/signalingHttpClient.go b/signalingHttpClient.go new file mode 100644 index 0000000..e8a1c68 --- /dev/null +++ b/signalingHttpClient.go @@ -0,0 +1,19 @@ +package localserver + +type SignalingHttpClient struct{} + +func (s *SignalingHttpClient) LoadHostedSquads() { + +} + +func (s *SignalingHttpClient) CreateHostedSquad() { + +} + +func (s *SignalingHttpClient) DeleteHostedSquad() { + +} + +func (s *SignalingHttpClient) UpdateHostedSquad() { + +} diff --git a/webrtcCallChatManager.go b/webrtcCallChatManager.go new file mode 100644 index 0000000..c203949 --- /dev/null +++ b/webrtcCallChatManager.go @@ -0,0 +1,136 @@ +package localserver + +import ( + "encoding/json" + "fmt" + "sync/atomic" +) + +const ( + CHAT_MESSAGE_BROADCAST = "chat_message_broadcast" + CHAT_MESSAGE_PRIVATE = "chat_message_private" +) + +type WebrtcCallChatManager struct{} + +func NewWebrtcCallChatManager() *WebrtcCallChatManager { + return new(WebrtcCallChatManager) +} + +func (w *WebrtcCallChatManager) HandleCallEvent(from string, squadId string, eventId string, payload map[string]interface{}, data []byte, manager *WebRTCCallManager) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + logger.Println("got an event in call chat manager", from, eventId, payload) + switch eventId { + case CHAT_MESSAGE_BROADCAST: + if e := validateEvent(payload, "message"); e != nil { + errCh <- e + return + } + if e := w.sendBrodcastChatMessage(from, payload["message"].(string), squadId, manager); e != nil { + errCh <- e + return + } + case CHAT_MESSAGE_PRIVATE: + if e := validateEvent(payload, "message", "dst"); e != nil { + errCh <- e + return + } + if e := w.sendPrivateChatMessage(from, payload["message"].(string), squadId, manager, payload["message"].([]string)...); e != nil { + errCh <- e + return + } + } + done <- struct{}{} + }() + select { + case <-done: + return nil + case err = <-errCh: + return + } +} + +func (w WebrtcCallChatManager) sendBrodcastChatMessage(from string, message string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + for _, member := range manager.Squads[squadId].Members { + if _, ok := manager.DataChannels[member]; ok && member != from { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": "send_chat_message", + "from": from, + "payload": map[string]string{ + "message": message, + }, + }) + if marshalErr != nil { + logger.Println(err) + continue + } + lock: + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break lock + } else { + continue + } + } + } + } + return +} + +func (w WebrtcCallChatManager) sendPrivateChatMessage(from string, message string, squadId string, manager *WebRTCCallManager, dst ...string) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + for _, member := range manager.Squads[squadId].Members { + for _, id := range dst { + if id == member { + if _, ok := manager.DataChannels[member]; ok && member != from { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": "", + "from": from, + "payload": map[string]string{ + "message": message, + }, + }) + if marshalErr != nil { + logger.Println(err) + continue + } + lock: + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break lock + } else { + continue + } + } + } + } + } + } + return +} + +func validateEvent(event map[string]interface{}, fields ...string) (err error) { + for _, field := range fields { + if _, ok := event[field]; !ok { + err = fmt.Errorf("no field %s in req payload", field) + return + } + } + return +} diff --git a/webrtcCallEventManager.go b/webrtcCallEventManager.go new file mode 100644 index 0000000..9e5df98 --- /dev/null +++ b/webrtcCallEventManager.go @@ -0,0 +1,5 @@ +package localserver + +type WebrtcCallEventManager interface { + HandleCallEvent(from string, squadId string, eventId string, payload map[string]interface{}, data []byte, manager *WebRTCCallManager) (err error) +} diff --git a/webrtcCallFileManager.go b/webrtcCallFileManager.go new file mode 100644 index 0000000..909ffaa --- /dev/null +++ b/webrtcCallFileManager.go @@ -0,0 +1,311 @@ +package localserver + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "log" + "os" + "path/filepath" + "sync/atomic" + + "github.com/pion/webrtc/v3" +) + +const ( + UPLOAD_INIT = "upload_init" + UPLOAD = "upload" + UPLOAD_DONE = "upload_done" + DOWNLOAD_INIT = "download_init" + DOWNLOAD = "download" + DOWNLOAD_DONE = "download_done" + HOSTED_SQUAD_DOWNLOAD_FILE_RESPONSE_INIT = "hosted_squad_download_file_response_init" + HOSTED_SQUAD_DOWNLOAD_FILE_RESPONSE = "hosted_squad_download_file_response" + HOSTED_SQUAD_DOWNLOAD_FILE_RESPONSE_END = "hosted_squad_download_file_response_end" +) + +const ( + bufferedAmountLowThreshold uint64 = 512 * 1024 + //maxBufferedAmount uint64 = 1024 * 1024 +) + +type WebrtcCallFileManager struct { + files map[string]*os.File + l *int32 +} + +func NewWebrtcCallFileManager() *WebrtcCallFileManager { + l := int32(0) + return &WebrtcCallFileManager{ + files: make(map[string]*os.File), + l: &l, + } +} + +func (w *WebrtcCallFileManager) HandleCallEvent(from string, squadId string, eventId string, payload map[string]interface{}, data []byte, manager *WebRTCCallManager) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + logger.Println("got an event in call file manager", from, eventId, payload) + switch eventId { + case UPLOAD_INIT: + if _, ok := payload["filename"]; !ok { + errCh <- fmt.Errorf("no field filename in payload") + return + } + if _, ok := payload["filename"].(string); !ok { + errCh <- fmt.Errorf("field filename in payload is not a string") + return + } + if err = w.initUpload(squadId, from, payload["filename"].(string)); err != nil { + errCh <- err + return + } + case UPLOAD: + if _, ok := payload["filename"]; !ok { + errCh <- fmt.Errorf("no field filename in payload") + return + } + if _, ok := payload["filename"].(string); !ok { + errCh <- fmt.Errorf("field filename in payload is not a string") + return + } + if err = w.upload(squadId, from, payload["filename"].(string), data); err != nil { + errCh <- err + return + } + case UPLOAD_DONE: + if _, ok := payload["filename"]; !ok { + errCh <- fmt.Errorf("no field filename in payload") + return + } + if _, ok := payload["filename"].(string); !ok { + errCh <- fmt.Errorf("field filename in payload is not a string") + return + } + if _, ok := payload["targets"]; !ok { + errCh <- fmt.Errorf("no field targets in payload") + return + } + if _, ok := payload["targets"].([]interface{}); !ok { + errCh <- fmt.Errorf("field targets in payload is not a string") + return + } + channels := []*DataChannel{} + manager.DataChannelMapMux.RLock() + for _, target := range payload["targets"].([]interface{}) { + if _, ok := manager.DataChannels[target.(string)]; !ok { + manager.DataChannelMapMux.RUnlock() + errCh <- fmt.Errorf("no corresponding datachannel : %s", target.(string)) + return + } + channel := manager.DataChannels[target.(string)] + for { + if atomic.CompareAndSwapInt32(channel.l, 0, 1) { + defer atomic.SwapInt32(channel.l, 0) + break + } + } + channels = append(channels, channel) + } + manager.DataChannelMapMux.RUnlock() + if err = w.uploadDone(squadId, from, payload["filename"].(string), channels); err != nil { + errCh <- err + return + } + case DOWNLOAD: + if _, ok := payload["filename"]; !ok { + errCh <- fmt.Errorf("no field filename in payload") + return + } + if _, ok := payload["filename"].(string); !ok { + errCh <- fmt.Errorf("field filename in payload is not a string") + return + } + if _, ok := payload["peerId"]; !ok { + errCh <- fmt.Errorf("no field peerId in payload") + return + } + if _, ok := payload["peerId"].(string); !ok { + errCh <- fmt.Errorf("field peerId in payload is not a string") + return + } + manager.DataChannelMapMux.RLock() + if _, ok := manager.DataChannels[payload["peerId"].(string)]; !ok { + manager.DataChannelMapMux.RUnlock() + errCh <- fmt.Errorf("no corresponding datachannel") + return + } + channel := manager.DataChannels[payload["peerId"].(string)] + for { + if atomic.CompareAndSwapInt32(channel.l, 0, 1) { + logger.Println("atomic lock unlocked") + defer atomic.SwapInt32(channel.l, 0) + break + } + } + manager.DataChannelMapMux.RUnlock() + if err = w.download(squadId, from, payload["filename"].(string), channel.DataChannel); err != nil { + errCh <- err + return + } + } + done <- struct{}{} + }() + select { + case <-done: + return nil + case err = <-errCh: + return + } +} + +func (w *WebrtcCallFileManager) initUpload(squadId string, from string, fileName string) (err error) { + for { + if atomic.CompareAndSwapInt32(w.l, 0, 1) { + defer atomic.SwapInt32(w.l, 0) + if _, dirErr := os.Stat(filepath.Join("data", "squads", squadId)); os.IsNotExist(dirErr) { + if err = os.MkdirAll(filepath.Join("data", "squads", squadId), 0700); err != nil { + return + } + } + f, fErr := os.Create(filepath.Join("data", "squads", squadId, fileName)) + if err != nil { + return fErr + } + f.Close() + f, fErr = os.OpenFile(filepath.Join("data", "squads", squadId, fileName), os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fErr + } + w.files[fileName] = f + break + } else { + continue + } + } + return +} + +func (w *WebrtcCallFileManager) upload(squadId string, from string, fileName string, data []byte) (err error) { + for { + if atomic.CompareAndSwapInt32(w.l, 0, 1) { + defer atomic.SwapInt32(w.l, 0) + if _, ok := w.files[fileName]; !ok { + err = fmt.Errorf("no open file with name %s", fileName) + return + } + _, err = w.files[fileName].Write(data) + break + } else { + continue + } + } + return +} + +func (w *WebrtcCallFileManager) uploadDone(squadId string, from string, fileName string, channels []*DataChannel) (err error) { + for { + if atomic.CompareAndSwapInt32(w.l, 0, 1) { + defer atomic.SwapInt32(w.l, 0) + if _, ok := w.files[fileName]; !ok { + err = fmt.Errorf("no open file with name %s", fileName) + return + } + err = w.files[fileName].Close() + delete(w.files, fileName) + bsInit, jsonErr := json.Marshal(map[string]interface{}{ + "type": UPLOAD_DONE, + "from": "server", + "payload": map[string]string{ + "path": fileName, + }, + }) + if jsonErr != nil { + return jsonErr + } + for _, channel := range channels { + if err = channel.DataChannel.SendText(string(bsInit)); err != nil { + return + } + } + break + } else { + continue + } + } + return +} + +func (w *WebrtcCallFileManager) download(squadId string, dst string, fileName string, channel *webrtc.DataChannel) (err error) { + logger.Println("got called") + if _, dirErr := os.Stat(filepath.Join("data", "squads", squadId, fileName)); os.IsNotExist(dirErr) { + logger.Println("file does not exist :", filepath.Join("data", "squads", squadId, fileName)) + return + } + f, err := os.Open(filepath.Join("data", "squads", squadId, fileName)) + if err != nil { + return + } + defer f.Close() + bsInit, err := json.Marshal(map[string]interface{}{ + "type": HOSTED_SQUAD_DOWNLOAD_FILE_RESPONSE_INIT, + "from": "server", + "payload": map[string]string{ + "path": fileName, + }, + }) + if err != nil { + return + } + if err = channel.SendText(string(bsInit)); err != nil { + return + } + r := bufio.NewReader(f) + buf := make([]byte, 0, 30000) + logger.Println("start reading") + for { + n, readErr := r.Read(buf[:cap(buf)]) + buf = buf[:n] + if n == 0 { + if err == nil { + logger.Println("n is 0 weird") + break + } + if err == io.EOF { + break + } + log.Fatal(readErr) + } + bs, jsonErr := json.Marshal(map[string]interface{}{ + "type": HOSTED_SQUAD_DOWNLOAD_FILE_RESPONSE, + "from": "server", + "payload": map[string]interface{}{ + "path": fileName, + "content": buf, + }, + }) + if jsonErr != nil { + return jsonErr + } + if err = channel.SendText(string(bs)); err != nil { + return + } + } + logger.Println("stop reading") + bs, err := json.Marshal(map[string]interface{}{ + "type": HOSTED_SQUAD_DOWNLOAD_FILE_RESPONSE_END, + "from": "server", + "payload": map[string]string{ + "path": fileName, + }, + }) + if err != nil { + return + } + if err = channel.SendText(string(bs)); err != nil { + return + } + logger.Println("done") + return +} diff --git a/webrtcCallManager.go b/webrtcCallManager.go new file mode 100644 index 0000000..cfdd76b --- /dev/null +++ b/webrtcCallManager.go @@ -0,0 +1,845 @@ +package localserver + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + sync "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/pion/rtcp" + "github.com/pion/webrtc/v3" +) + +const ( + NAME = "name" + ID = "ID" + SDP = "sdp" + CANDIDATE = "webrtc_candidate" + SQUAD_ID = "squadId" + FROM = "from" + TO = "to" + STOP_CALL = "stop_call" + LIST_HOSTED_SQUADS_BY_HOST = "list_hosted_squads_by_host" +) + +type Squad struct { + ID string + Members []string +} + +type WebRTCCallManager struct { + stream GrpcManager_LinkClient + middlewares []WebrtcCallEventManager + ID string + LocalSD map[string]*webrtc.SessionDescription + RTCPeerConnections map[string]*RTCPeerConnection + AudioTransceiver map[string][]*PeerSender + VideoTransceiver map[string][]*PeerSender + DataChannels map[string]*DataChannel + PendingCandidates map[string][]*webrtc.ICECandidate + RemoteTracks map[string][]*RemoteTrack + Squads map[string]*Squad + SquadMapMux *sync.RWMutex + CandidateChannel chan *IncomingCandidate + CandidateMux *sync.RWMutex + RemoteTracksMux *sync.RWMutex + RTCPeerConnectionMapMux *sync.RWMutex + DataChannelMapMux *sync.RWMutex + LocalSDMapMux *sync.RWMutex + AudioSenderMux *sync.RWMutex + VideoSenderMux *sync.RWMutex +} + +type IncomingCandidate struct { + For string + Candidate *webrtc.ICECandidateInit +} + +type RTCPeerConnection struct { + *webrtc.PeerConnection + makingOffer bool + negotiate func(string, string) + makingOfferLock *sync.Mutex +} + +type DataChannel struct { + DataChannel *webrtc.DataChannel + bufferedAmountLowThresholdReached <-chan struct{} + l *int32 +} + +type PeerSender struct { + ID string + Transceiver *webrtc.RTPTransceiver + Sender *webrtc.RTPSender +} + +type CallEvent struct { + EventId string `json:"eventId"` + From string `json:"from"` + Data []byte `json:"data"` + Payload map[string]interface{} `json:"payload"` +} + +type RemoteTrack struct { + ID string + Track *webrtc.TrackLocalStaticRTP + rdv *int32 +} + +type OnICECandidateFunc func(string, *webrtc.ICECandidate) error + +func NewWebRTCCallManager(id string, token string, eventHandlers ...WebrtcCallEventManager) (webRTCCallManager *WebRTCCallManager, err error) { + squadList, err := loadHostedSquads(token, id) + if err != nil { + return + } + squads := make(map[string]*Squad) + for _, squad := range squadList { + squads[squad.ID] = squad + } + logger.Println(squads) + webRTCCallManager = &WebRTCCallManager{ + middlewares: eventHandlers, + ID: id, + AudioTransceiver: make(map[string][]*PeerSender), + VideoTransceiver: make(map[string][]*PeerSender), + DataChannels: make(map[string]*DataChannel), + PendingCandidates: make(map[string][]*webrtc.ICECandidate), + LocalSD: make(map[string]*webrtc.SessionDescription), + RTCPeerConnections: make(map[string]*RTCPeerConnection), + RemoteTracks: make(map[string][]*RemoteTrack), + Squads: squads, + SquadMapMux: &sync.RWMutex{}, + RTCPeerConnectionMapMux: &sync.RWMutex{}, + LocalSDMapMux: &sync.RWMutex{}, + CandidateMux: &sync.RWMutex{}, + DataChannelMapMux: &sync.RWMutex{}, + AudioSenderMux: &sync.RWMutex{}, + VideoSenderMux: &sync.RWMutex{}, + RemoteTracksMux: &sync.RWMutex{}, + } + return +} + +func loadHostedSquads(token string, hostId string) (squads []*Squad, err error) { + body, err := json.Marshal(map[string]interface{}{ + "type": LIST_HOSTED_SQUADS_BY_HOST, + "token": token, + "from": hostId, + "payload": map[string]string{ + "host": hostId, + "lastIndex": "0", + }, + }) + if err != nil { + return + } + + res, err := http.Post("https://app.zippytal.com/req", "application/json", bytes.NewBuffer(body)) + if err != nil { + logger.Println("error come from there in webrtc call manager") + return + } + bs, err := io.ReadAll(res.Body) + if err != nil { + return + } + err = json.Unmarshal(bs, &squads) + return +} + +func (wm *WebRTCCallManager) CreateOffer(ctx context.Context, target string, from string, cb OnICECandidateFunc) (err error) { + peerConnection, err := wm.createPeerConnection(target, from, "aa5da62f-2800-4dd5-bf86-423cda048120", webrtc.SDPTypeOffer, cb) + if err != nil { + return + } + logger.Println("connection created") + rawOffer, err := peerConnection.CreateOffer(nil) + if err != nil { + return + } + if err = peerConnection.SetLocalDescription(rawOffer); err != nil { + return + } + wm.RTCPeerConnectionMapMux.Lock() + logger.Println("adding for target", target) + wm.RTCPeerConnections[target] = &RTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: true, + makingOfferLock: &sync.Mutex{}, + negotiate: wm.negotiate, + } + wm.RTCPeerConnectionMapMux.Unlock() + err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_OFFER), + From: wm.ID, + Token: "none", + Payload: map[string]string{ + "to": target, + "from": wm.ID, + "sdp": rawOffer.SDP, + }, + }) + return +} + +func (wm *WebRTCCallManager) HandleOffer(ctx context.Context, req map[string]string, cb OnICECandidateFunc) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + if _, ok := wm.Squads[req[SQUAD_ID]]; !ok { + err = fmt.Errorf("no corresponding squad") + errCh <- err + return + } + peerConnection, err := wm.createPeerConnection(req[FROM], req[TO], req[SQUAD_ID], webrtc.SDPTypeAnswer, cb) + if err != nil { + errCh <- err + return + } + wm.RTCPeerConnectionMapMux.Lock() + wm.RTCPeerConnections[req[FROM]] = &RTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: false, + makingOfferLock: &sync.Mutex{}, + negotiate: wm.negotiate, + } + wm.RTCPeerConnectionMapMux.Unlock() + offer := webrtc.SessionDescription{ + Type: webrtc.SDPTypeOffer, + SDP: req[SDP], + } + if err = peerConnection.SetRemoteDescription(offer); err != nil { + errCh <- err + return + } + rawAnswer, err := peerConnection.CreateAnswer(nil) + if err != nil { + errCh <- err + return + } + wm.LocalSDMapMux.Lock() + wm.LocalSD[req[FROM]] = &rawAnswer + wm.LocalSDMapMux.Unlock() + wm.SquadMapMux.Lock() + wm.Squads[req[SQUAD_ID]].Members = append(wm.Squads[req[SQUAD_ID]].Members, req[FROM]) + wm.SquadMapMux.Unlock() + if err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_ANSWER), + From: wm.ID, + Token: "none", + Payload: map[string]string{ + "to": req[FROM], + "from": wm.ID, + "sdp": rawAnswer.SDP, + }, + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + select { + case <-done: + return + case err = <-errCh: + return + case <-ctx.Done(): + err = ctx.Err() + return + } +} + +func (wm *WebRTCCallManager) HandleAnswer(ctx context.Context, req map[string]string) (err error) { + wm.RTCPeerConnectionMapMux.Lock() + defer wm.RTCPeerConnectionMapMux.Unlock() + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic in handle answer : %v\n", r) + } + }() + if _, ok := wm.RTCPeerConnections[req[FROM]]; !ok { + err = fmt.Errorf("no corresponding peer connection for id : %s", req[FROM]) + return + } + peerConnnection := wm.RTCPeerConnections[req[FROM]] + logger.Println("---------------------") + logger.Println(req[SDP]) + logger.Println("---------------------") + if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{ + Type: webrtc.SDPTypeAnswer, + SDP: req[SDP], + }); err != nil { + logger.Println("error occured while setting remote description in handle answer") + return + } + if err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_COUNTER_OFFER), + From: wm.ID, + Token: "none", + Payload: map[string]string{ + "from": wm.ID, + "to": req[FROM], + }, + }); err != nil { + return + } + wm.CandidateMux.Lock() + for _, candidate := range wm.PendingCandidates[req[FROM]] { + logger.Println("sending candidate from answer to", req[FROM]) + if err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_CANDIDATE), + From: wm.ID, + Token: "none", + Payload: map[string]string{ + "from": wm.ID, + "to": req[FROM], + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }); err != nil { + wm.CandidateMux.Unlock() + return + } + } + wm.CandidateMux.Unlock() + wm.CandidateMux.Lock() + delete(wm.PendingCandidates, req[FROM]) + wm.CandidateMux.Unlock() + wm.LocalSDMapMux.Lock() + delete(wm.LocalSD, req[FROM]) + wm.LocalSDMapMux.Unlock() + return +} + +func (wm *WebRTCCallManager) HandleCounterOffer(ctx context.Context, req map[string]string) (err error) { + wm.RTCPeerConnectionMapMux.Lock() + if _, ok := wm.RTCPeerConnections[req[FROM]]; !ok { + err = fmt.Errorf("no field corresponding peer connection for id %s", req[FROM]) + wm.RTCPeerConnectionMapMux.Unlock() + return + } + logger.Println("handling counter offer") + connection := wm.RTCPeerConnections[req[FROM]] + wm.RTCPeerConnectionMapMux.Unlock() + wm.LocalSDMapMux.Lock() + if err = connection.SetLocalDescription(*wm.LocalSD[req[FROM]]); err != nil { + wm.LocalSDMapMux.Unlock() + return + } + wm.LocalSDMapMux.Unlock() + wm.CandidateMux.Lock() + for _, candidate := range wm.PendingCandidates[req[FROM]] { + logger.Println("sending candidate to", req[FROM]) + if err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_CANDIDATE), + From: wm.ID, + Token: "none", + Payload: map[string]string{ + "from": wm.ID, + "to": req[FROM], + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }); err != nil { + wm.CandidateMux.Unlock() + return + } + } + delete(wm.PendingCandidates, req[FROM]) + wm.CandidateMux.Unlock() + wm.LocalSDMapMux.Lock() + delete(wm.LocalSD, req[FROM]) + wm.LocalSDMapMux.Unlock() + return +} + +func (wm *WebRTCCallManager) createPeerConnection(target string, from string, squadId string, peerType webrtc.SDPType, cb OnICECandidateFunc) (peerConnection *webrtc.PeerConnection, err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"}, + }, + }, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, + } + + peerConnection, err = webrtc.NewPeerConnection(config) + if err != nil { + return + } + logger.Println("---------------------------------------------------") + if peerType == webrtc.SDPTypeAnswer { + maxRetransmits := uint16(100) + channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return nil, err + } + channel.OnOpen(func() { + logger.Println("channel opened") + if chanErr := channel.SendText("yooo man this is open"); chanErr != nil { + logger.Println(chanErr) + } + }) + channel.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + for _, handler := range wm.middlewares { + if err := handler.HandleCallEvent(event.From, squadId, event.EventId, event.Payload, event.Data, wm); err != nil { + logger.Println(err) + continue + } + } + }) + logger.Println("new channel for target : ", target) + channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) + channel.OnBufferedAmountLow(func() { + + }) + wm.DataChannelMapMux.Lock() + logger.Println(target) + l := int32(0) + wm.DataChannels[target] = &DataChannel{ + DataChannel: channel, + l: &l, + } + wm.DataChannelMapMux.Unlock() + } else { + peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { + wm.DataChannelMapMux.Lock() + l := int32(0) + wm.DataChannels[target] = &DataChannel{ + DataChannel: dc, + l: &l, + } + wm.DataChannelMapMux.Unlock() + dc.OnOpen(func() { + logger.Printf("got a new open datachannel %s\n", dc.Label()) + }) + dc.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + for _, handler := range wm.middlewares { + if err := handler.HandleCallEvent(event.From, squadId, event.EventId, event.Payload, event.Data, wm); err != nil { + logger.Println(err) + continue + } + } + }) + }) + } + wm.RemoteTracksMux.RLock() + for _, id := range wm.Squads[squadId].Members { + if id != target { + <-time.NewTimer(time.Millisecond * 50).C + if _, ok := wm.RemoteTracks[id]; !ok { + continue + } + for _, track := range wm.RemoteTracks[id] { + transceiver, err := peerConnection.AddTransceiverFromKind(track.Track.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + if err != nil { + logger.Println("add track error") + continue + } + if err := transceiver.Sender().ReplaceTrack(track.Track); err != nil { + logger.Println("add track error") + continue + } + if track.Track.Kind() == webrtc.RTPCodecTypeVideo { + wm.VideoSenderMux.Lock() + if len(wm.VideoTransceiver) == 0 { + wm.VideoTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} + } else { + wm.VideoTransceiver[id] = append(wm.VideoTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) + } + wm.VideoSenderMux.Unlock() + } else if track.Track.Kind() == webrtc.RTPCodecTypeAudio { + wm.AudioSenderMux.Lock() + if len(wm.AudioTransceiver) == 0 { + wm.AudioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} + } else { + wm.AudioTransceiver[id] = append(wm.AudioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) + } + wm.AudioSenderMux.Unlock() + } + logger.Println("track added", track) + } + } + } + wm.RemoteTracksMux.RUnlock() + peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed { + logger.Println(pcs) + wm.HandleLeavingMember(target, squadId) + } + }) + peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { + logger.Printf("ICE connection state has changed %s\n", is.String()) + if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { + logger.Println(is) + } + }) + peerConnection.OnTrack(func(tr *webrtc.TrackRemote, r *webrtc.RTPReceiver) { + logger.Println("got new track") + defer func() { + if stopErr := r.Stop(); stopErr != nil { + logger.Println(stopErr) + } + }() + go func() { + ticker := time.NewTicker(1500 * time.Millisecond) + for range ticker.C { + if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(tr.SSRC())}}); rtcpSendErr != nil { + logger.Println(rtcpSendErr) + break + } + } + }() + uniqId := uuid.New() + i := fmt.Sprintf("%s/%s", target, uniqId.String()) + localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(tr.Codec().RTPCodecCapability, i, i) + if newTrackErr != nil { + return + } + logger.Println(localTrack) + rtpbuf := make([]byte, 1400) + flag := int32(0) + remote := &RemoteTrack{ID: target, Track: localTrack, rdv: &flag} + wm.RemoteTracksMux.Lock() + if len(wm.RemoteTracks[target]) == 0 { + wm.RemoteTracks[target] = []*RemoteTrack{remote} + } else { + wm.RemoteTracks[target] = append(wm.RemoteTracks[target], remote) + } + index := len(wm.RemoteTracks[target]) + logger.Println(index, wm.RemoteTracks) + wm.RemoteTracksMux.Unlock() + wm.SquadMapMux.RLock() + for _, id := range wm.Squads[squadId].Members { + if id != target { + if _, ok := wm.RTCPeerConnections[id]; !ok { + continue + } + connection := wm.RTCPeerConnections[id] + transceiver, tranceiverErr := connection.AddTransceiverFromKind(localTrack.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + if tranceiverErr != nil { + logger.Println(tranceiverErr) + continue + } + if replaceTrackErr := transceiver.Sender().ReplaceTrack(localTrack); replaceTrackErr != nil { + logger.Println(replaceTrackErr) + continue + } + go func() { + rtcpBuf := make([]byte, 1500) + for { + if _, _, rtcpErr := transceiver.Sender().Read(rtcpBuf); rtcpErr != nil { + return + } + } + }() + if localTrack.Kind() == webrtc.RTPCodecTypeAudio { + wm.AudioSenderMux.Lock() + if len(wm.AudioTransceiver) == 0 { + wm.AudioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} + } else { + wm.AudioTransceiver[target] = append(wm.AudioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) + } + wm.AudioSenderMux.Unlock() + } else if localTrack.Kind() == webrtc.RTPCodecTypeVideo { + wm.VideoSenderMux.Lock() + if len(wm.VideoTransceiver) == 0 { + wm.VideoTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} + } else { + wm.VideoTransceiver[target] = append(wm.VideoTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) + } + wm.VideoSenderMux.Unlock() + } + go func() { + <-time.NewTimer(time.Second).C + connection.negotiate(id, squadId) + }() + } + } + wm.SquadMapMux.RUnlock() + for { + i, _, readErr := tr.Read(rtpbuf) + if readErr != nil { + logger.Println(readErr) + break + } + f := atomic.LoadInt32(remote.rdv) + if f == 0 { + if _, writeErr := localTrack.Write(rtpbuf[:i]); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) { + logger.Println(writeErr) + break + } else { + _ = rtpbuf[:i] + } + } + } + }) + peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + wm.CandidateMux.Lock() + defer wm.CandidateMux.Unlock() + desc := peerConnection.RemoteDescription() + if desc == nil { + logger.Println("generated candidate appended to list : ", i) + wm.PendingCandidates[target] = append(wm.PendingCandidates[target], i) + } else { + logger.Println("generated candidate : ", i) + if iceCandidateErr := cb(target, i); iceCandidateErr != nil { + logger.Println(iceCandidateErr) + } + } + }) + return +} + +func (wm *WebRTCCallManager) HandleRennegotiationOffer(from string, sdp string) (err error) { + wm.RTCPeerConnectionMapMux.Lock() + defer wm.RTCPeerConnectionMapMux.Unlock() + if _, ok := wm.RTCPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + wm.RTCPeerConnections[from].makingOfferLock.Lock() + if wm.RTCPeerConnections[from].makingOffer { + wm.RTCPeerConnections[from].makingOfferLock.Unlock() + return fmt.Errorf("already making an offer or state is stable") + } + wm.RTCPeerConnections[from].makingOfferLock.Unlock() + if err = wm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { + return + } + localSd, err := wm.RTCPeerConnections[from].CreateAnswer(nil) + if err != nil { + return + } + if err = wm.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil { + return + } + if err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER), + From: wm.ID, + Token: "", + Payload: map[string]string{ + "to": from, + "sdp": localSd.SDP, + }, + }); err != nil { + logger.Println(err) + return + } + return +} + +func (wm *WebRTCCallManager) HandleRennegotiationAnswer(from string, sdp string) (err error) { + wm.RTCPeerConnectionMapMux.Lock() + defer wm.RTCPeerConnectionMapMux.Unlock() + wm.RTCPeerConnections[from].makingOfferLock.Lock() + if wm.RTCPeerConnections[from].makingOffer { + wm.RTCPeerConnections[from].makingOfferLock.Unlock() + return fmt.Errorf("already making an offer or state is stable") + } + wm.RTCPeerConnections[from].makingOfferLock.Unlock() + if _, ok := wm.RTCPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + err = wm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) + return +} + +func (wm *WebRTCCallManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { + wm.RTCPeerConnectionMapMux.Lock() + defer wm.RTCPeerConnectionMapMux.Unlock() + if candidate != nil { + err = wm.RTCPeerConnections[from].AddICECandidate(*candidate) + } + return +} + +func (wm *WebRTCCallManager) HandleLeavingMember(id string, squadId string) { + wm.RTCPeerConnectionMapMux.Lock() + if _, ok := wm.RTCPeerConnections[id]; !ok { + wm.RTCPeerConnectionMapMux.Unlock() + return + } + wm.RTCPeerConnectionMapMux.Unlock() + defer func() { + wm.RTCPeerConnectionMapMux.Lock() + if _, ok := wm.RTCPeerConnections[id]; ok { + if closeErr := wm.RTCPeerConnections[id].Close(); closeErr != nil { + logger.Println("peer connection close error", closeErr) + } + } + delete(wm.RTCPeerConnections, id) + wm.RTCPeerConnectionMapMux.Unlock() + }() + logger.Printf("peer %s is leaving the squad\n", id) + wm.DataChannelMapMux.Lock() + if _, ok := wm.DataChannels[id]; ok { + wm.DataChannels[id].DataChannel.Close() + } + delete(wm.DataChannels, id) + wm.DataChannelMapMux.Unlock() + wm.LocalSDMapMux.Lock() + delete(wm.LocalSD, id) + wm.LocalSDMapMux.Unlock() + delete(wm.PendingCandidates, id) + wm.RemoteTracksMux.Lock() + delete(wm.RemoteTracks, id) + wm.RemoteTracksMux.Unlock() + wm.AudioSenderMux.Lock() + for peerId, peerSender := range wm.AudioTransceiver { + if peerId != id { + logger.Println("senders", peerSender) + c := 0 + for i, sender := range peerSender { + if sender.ID == id { + if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { + logger.Println("transceiverErr occured with video", transceiverErr) + } + peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] + c++ + } + } + wm.AudioTransceiver[peerId] = wm.AudioTransceiver[peerId][:len(peerSender)-(c)] + logger.Println(wm.AudioTransceiver[peerId]) + } + } + for _, transceiver := range wm.AudioTransceiver[id] { + if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { + logger.Println("transceiver audio stop error", stopErr) + } + } + delete(wm.AudioTransceiver, id) + wm.AudioSenderMux.Unlock() + wm.VideoSenderMux.Lock() + for peerId, peerSender := range wm.VideoTransceiver { + if peerId != id { + c := 0 + logger.Println("senders", peerSender) + for i, sender := range peerSender { + if sender.ID == id { + if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { + logger.Println("transceiverErr occured with video", transceiverErr) + } + peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] + c++ + } + } + wm.VideoTransceiver[peerId] = wm.VideoTransceiver[peerId][:len(peerSender)-(c)] + logger.Println(wm.VideoTransceiver[peerId]) + } + } + for _, transceiver := range wm.VideoTransceiver[id] { + if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { + logger.Println("transceiver video stop error", stopErr) + } + } + delete(wm.VideoTransceiver, id) + wm.VideoSenderMux.Unlock() + if _, ok := wm.Squads[squadId]; ok { + wm.SquadMapMux.Lock() + var index int + for i, v := range wm.Squads[squadId].Members { + if v == id { + index = i + break + } + } + if len(wm.Squads[squadId].Members) < 2 { + wm.Squads[squadId].Members = []string{} + } else { + wm.Squads[squadId].Members = append(wm.Squads[squadId].Members[:index], wm.Squads[squadId].Members[index+1:]...) + } + wm.SquadMapMux.Unlock() + } +} + +func (wm *WebRTCCallManager) negotiate(target string, squadId string) { + wm.RTCPeerConnectionMapMux.Lock() + defer wm.RTCPeerConnectionMapMux.Unlock() + if _, ok := wm.RTCPeerConnections[target]; !ok { + return + } + wm.RTCPeerConnections[target].makingOfferLock.Lock() + wm.RTCPeerConnections[target].makingOffer = true + wm.RTCPeerConnections[target].makingOfferLock.Unlock() + defer func() { + wm.RTCPeerConnections[target].makingOfferLock.Lock() + wm.RTCPeerConnections[target].makingOffer = false + wm.RTCPeerConnections[target].makingOfferLock.Unlock() + }() + wm.SquadMapMux.RLock() + defer wm.SquadMapMux.RUnlock() + for _, id := range wm.Squads[squadId].Members { + if _, ok := wm.RTCPeerConnections[id]; !ok { + continue + } + connection := wm.RTCPeerConnections[id] + if connection.SignalingState() == webrtc.SignalingStateStable { + localSd, err := connection.CreateOffer(nil) + if err != nil { + logger.Println(err) + return + } + if err = connection.SetLocalDescription(localSd); err != nil { + logger.Println(err) + return + } + if err = wm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER), + From: wm.ID, + Token: "", + Payload: map[string]string{ + "to": id, + "sdp": localSd.SDP, + }, + }); err != nil { + logger.Println(err) + return + } + } + } +} diff --git a/webrtcCallSoundManager.go b/webrtcCallSoundManager.go new file mode 100644 index 0000000..d009918 --- /dev/null +++ b/webrtcCallSoundManager.go @@ -0,0 +1,234 @@ +package localserver + +import ( + "encoding/json" + "fmt" + "sync/atomic" + + "github.com/pion/webrtc/v3" +) + +const ( + SPEAKING = "speaking" + STOP_SPEAKING = "stop_speaking" + MUTE = "mute" + UNMUTE = "unmute" +) + +type WebrtcCallSoundManager struct{} + +func NewWebrtcCallSoundManager() *WebrtcCallSoundManager { + return new(WebrtcCallSoundManager) +} + +func (w *WebrtcCallSoundManager) HandleCallEvent(from string, squadId string, eventId string, payload map[string]interface{}, data []byte, manager *WebRTCCallManager) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + logger.Println("got an event in call sound manager", from, eventId, payload) + switch eventId { + case UNMUTE: + if e := w.unmute(from, squadId, manager); e != nil { + errCh <- e + return + } + case MUTE: + if e := w.mute(from, squadId, manager); e != nil { + errCh <- e + return + } + case SPEAKING: + if e := w.sendSpeakingEvent(from, squadId, manager); e != nil { + errCh <- e + return + } + case STOP_SPEAKING: + if e := w.sendStopSpeakingEvent(from, squadId, manager); e != nil { + errCh <- e + return + } + } + done <- struct{}{} + }() + select { + case <-done: + return nil + case err = <-errCh: + return + } +} + +func (w *WebrtcCallSoundManager) unmute(peerId string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + logger.Println("sending unnmute event", peerId) + manager.RemoteTracksMux.RLock() + for _, v := range manager.RemoteTracks[peerId] { + if v.Track.Kind() == webrtc.RTPCodecTypeAudio { + atomic.SwapInt32(v.rdv, 0) + } + } + manager.RemoteTracksMux.RUnlock() + manager.SquadMapMux.Lock() + defer manager.SquadMapMux.Unlock() + for _, member := range manager.Squads[squadId].Members { + manager.DataChannelMapMux.Lock() + if _, ok := manager.DataChannels[member]; ok && member != peerId { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": UNMUTE, + "from": peerId, + "payload": map[string]interface{}{}, + }) + if marshalErr != nil { + logger.Println(err) + manager.DataChannelMapMux.Unlock() + continue + } + lock: + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break lock + } else { + continue + } + } + } + manager.DataChannelMapMux.Unlock() + } + return +} + +func (w *WebrtcCallSoundManager) mute(peerId string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + logger.Println("sending mute event", peerId) + manager.RemoteTracksMux.RLock() + for _, v := range manager.RemoteTracks[peerId] { + if v.Track.Kind() == webrtc.RTPCodecTypeAudio { + atomic.SwapInt32(v.rdv, 1) + } + } + manager.RemoteTracksMux.RUnlock() + manager.SquadMapMux.Lock() + defer manager.SquadMapMux.Unlock() + for _, member := range manager.Squads[squadId].Members { + manager.DataChannelMapMux.Lock() + if _, ok := manager.DataChannels[member]; ok && member != peerId { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": MUTE, + "from": peerId, + "payload": map[string]interface{}{}, + }) + if marshalErr != nil { + logger.Println(err) + manager.DataChannelMapMux.Unlock() + continue + } + lock: + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break lock + } else { + continue + } + } + } + manager.DataChannelMapMux.Unlock() + } + return +} + +func (w *WebrtcCallSoundManager) sendSpeakingEvent(peerId string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + logger.Println("sending speaking event", peerId) + manager.SquadMapMux.Lock() + defer manager.SquadMapMux.Unlock() + for _, member := range manager.Squads[squadId].Members { + manager.DataChannelMapMux.Lock() + if _, ok := manager.DataChannels[member]; ok && member != peerId { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": SPEAKING, + "from": peerId, + "payload": map[string]interface{}{ + "userId": peerId, + "speaking": true, + }, + }) + if marshalErr != nil { + logger.Println(err) + manager.DataChannelMapMux.Unlock() + continue + } + lock: + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break lock + } else { + continue + } + } + } + manager.DataChannelMapMux.Unlock() + } + return +} + +func (w *WebrtcCallSoundManager) sendStopSpeakingEvent(peerId string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + logger.Println("sending stop speaking event", peerId) + manager.SquadMapMux.Lock() + defer manager.SquadMapMux.Unlock() + for _, member := range manager.Squads[squadId].Members { + manager.DataChannelMapMux.Lock() + if _, ok := manager.DataChannels[member]; ok && member != peerId { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": STOP_SPEAKING, + "from": peerId, + "payload": map[string]interface{}{ + "userId": peerId, + "speaking": false, + }, + }) + if marshalErr != nil { + logger.Println(err) + manager.DataChannelMapMux.Unlock() + continue + } + lock: + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break lock + } else { + continue + } + } + } + manager.DataChannelMapMux.Unlock() + } + return +} diff --git a/webrtcCallVideoManager.go b/webrtcCallVideoManager.go new file mode 100644 index 0000000..6f854ac --- /dev/null +++ b/webrtcCallVideoManager.go @@ -0,0 +1,136 @@ +package localserver + +import ( + "encoding/json" + "fmt" + "sync/atomic" + + "github.com/pion/webrtc/v3" +) + +const ( + VIDEO = "video" + STOP_VIDEO = "stop_video" +) + +type WebrtcCallVideoManager struct{} + +func NewWebrtcCallVideoManager() *WebrtcCallVideoManager { + return new(WebrtcCallVideoManager) +} + +func (w *WebrtcCallVideoManager) HandleCallEvent(from string, squadId string, eventId string, payload map[string]interface{}, data []byte, manager *WebRTCCallManager) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + logger.Println("got an event in call video manager", from, eventId, payload) + switch eventId { + case VIDEO: + if e := w.video(from, squadId, manager); e != nil { + errCh <- e + return + } + case STOP_VIDEO: + if e := w.stopVideo(from, squadId, manager); e != nil { + errCh <- e + return + } + } + done <- struct{}{} + }() + select { + case <-done: + return nil + case err = <-errCh: + return + } +} + +func (w *WebrtcCallVideoManager) video(peerId string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + logger.Println("sending video event", peerId) + manager.RemoteTracksMux.RLock() + for _, v := range manager.RemoteTracks[peerId] { + if v.Track.Kind() == webrtc.RTPCodecTypeVideo { + atomic.SwapInt32(v.rdv, 0) + } + } + manager.RemoteTracksMux.RUnlock() + manager.SquadMapMux.Lock() + defer manager.SquadMapMux.Unlock() + for _, member := range manager.Squads[squadId].Members { + manager.DataChannelMapMux.Lock() + if _, ok := manager.DataChannels[member]; ok && member != peerId { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": VIDEO, + "from": peerId, + "payload": map[string]interface{}{}, + }) + if marshalErr != nil { + logger.Println(err) + manager.DataChannelMapMux.Unlock() + continue + } + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break + } else { + continue + } + } + } + manager.DataChannelMapMux.Unlock() + } + return +} + +func (w *WebrtcCallVideoManager) stopVideo(peerId string, squadId string, manager *WebRTCCallManager) (err error) { + if _, ok := manager.Squads[squadId]; !ok { + err = fmt.Errorf("no correponding squad found") + return + } + logger.Println("sending stop video event", peerId) + manager.RemoteTracksMux.RLock() + for _, v := range manager.RemoteTracks[peerId] { + if v.Track.Kind() == webrtc.RTPCodecTypeVideo { + atomic.SwapInt32(v.rdv, 1) + } + } + manager.RemoteTracksMux.RUnlock() + manager.SquadMapMux.Lock() + defer manager.SquadMapMux.Unlock() + for _, member := range manager.Squads[squadId].Members { + manager.DataChannelMapMux.Lock() + if _, ok := manager.DataChannels[member]; ok && member != peerId { + bs, marshalErr := json.Marshal(map[string]interface{}{ + "type": STOP_VIDEO, + "from": peerId, + "payload": map[string]interface{}{}, + }) + if marshalErr != nil { + logger.Println(err) + manager.DataChannelMapMux.Unlock() + continue + } + for { + if atomic.CompareAndSwapInt32(manager.DataChannels[member].l, 0, 1) { + defer atomic.SwapInt32(manager.DataChannels[member].l, 0) + if sendErr := manager.DataChannels[member].DataChannel.SendText(string(bs)); sendErr != nil { + logger.Println(sendErr) + } + break + } else { + continue + } + } + } + manager.DataChannelMapMux.Unlock() + } + return +} diff --git a/webrtcFsManager.go b/webrtcFsManager.go new file mode 100644 index 0000000..18d74f9 --- /dev/null +++ b/webrtcFsManager.go @@ -0,0 +1,441 @@ +package localserver + +import ( + context "context" + "encoding/json" + "fmt" + "io" + "strconv" + sync "sync" + + "github.com/pion/webrtc/v3" +) + +type WebrtcFsManager struct { + stream GrpcManager_LinkClient + DatachannelManager DataChannelManager + LocalSD map[string]*webrtc.SessionDescription + RTCPeerConnections map[string]*webrtc.PeerConnection + DataChannels map[string]*webrtc.DataChannel + PendingCandidates map[string][]*webrtc.ICECandidate + CandidateChannel chan *IncomingCandidate + CandidateMux *sync.RWMutex + RTCPeerConnectionMapMux *sync.RWMutex + DataChannelMapMux *sync.RWMutex + LocalSDMapMux *sync.RWMutex +} + +func NewWebrtcFsManager(dataChannelManager DataChannelManager) (webrtcFsManager *WebrtcFsManager, err error) { + webrtcFsManager = &WebrtcFsManager{ + DatachannelManager: dataChannelManager, + DataChannels: make(map[string]*webrtc.DataChannel), + PendingCandidates: make(map[string][]*webrtc.ICECandidate), + LocalSD: make(map[string]*webrtc.SessionDescription), + RTCPeerConnections: make(map[string]*webrtc.PeerConnection), + RTCPeerConnectionMapMux: &sync.RWMutex{}, + LocalSDMapMux: &sync.RWMutex{}, + CandidateMux: &sync.RWMutex{}, + DataChannelMapMux: &sync.RWMutex{}, + } + return +} + +func (wf *WebrtcFsManager) CreateOffer(ctx context.Context, target string, from string, cb OnICECandidateFunc) (err error) { + peerConnection, err := wf.createPeerConnection(target, from, webrtc.SDPTypeOffer, cb) + if err != nil { + return + } + rawOffer, err := peerConnection.CreateOffer(nil) + if err != nil { + return + } + if err = peerConnection.SetLocalDescription(rawOffer); err != nil { + return + } + wf.RTCPeerConnectionMapMux.Lock() + logger.Println("adding for target", target) + wf.RTCPeerConnections[target] = peerConnection + wf.RTCPeerConnectionMapMux.Unlock() + err = wf.stream.Send(&Request{ + Type: string(WEBRTC_OFFER_FS), + From: "lolo_local_serv", + Token: "none", + Payload: map[string]string{ + "to": target, + "from": "lolo_local_serv", + "sdp": rawOffer.SDP, + }, + }) + return +} + +func (wf *WebrtcFsManager) HandleOffer(ctx context.Context, req map[string]string, cb OnICECandidateFunc) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + peerConnection, err := wf.createPeerConnection(req[FROM], req[TO], webrtc.SDPTypeAnswer, cb) + if err != nil { + errCh <- err + return + } + wf.RTCPeerConnectionMapMux.Lock() + wf.RTCPeerConnections[req[FROM]] = peerConnection + wf.RTCPeerConnectionMapMux.Unlock() + offer := webrtc.SessionDescription{ + Type: webrtc.SDPTypeOffer, + SDP: req[SDP], + } + if err = peerConnection.SetRemoteDescription(offer); err != nil { + errCh <- err + return + } + rawAnswer, err := peerConnection.CreateAnswer(nil) + if err != nil { + errCh <- err + return + } + wf.LocalSDMapMux.Lock() + wf.LocalSD[req[FROM]] = &rawAnswer + wf.LocalSDMapMux.Unlock() + if err = wf.stream.Send(&Request{ + Type: string(WEBRTC_ANSWER_FS), + From: "lolo_local_serv", + Token: "none", + Payload: map[string]string{ + "to": req[FROM], + "from": "lolo_local_serv", + "sdp": rawAnswer.SDP, + }, + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + select { + case <-done: + return + case err = <-errCh: + return + case <-ctx.Done(): + err = ctx.Err() + return + } +} + +func (wf *WebrtcFsManager) HandleAnswer(ctx context.Context, req map[string]string) (err error) { + wf.RTCPeerConnectionMapMux.RLock() + defer wf.RTCPeerConnectionMapMux.RUnlock() + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + if _, ok := wf.RTCPeerConnections[req[FROM]]; !ok { + err = fmt.Errorf("no corresponding peer connection for id : %s", req[FROM]) + return + } + peerConnnection := wf.RTCPeerConnections[req[FROM]] + if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{ + Type: webrtc.SDPTypeAnswer, + SDP: req[SDP], + }); err != nil { + return + } + if err = wf.stream.Send(&Request{ + Type: string(WEBRTC_COUNTER_OFFER_FS), + From: "lolo_local_serv", + Token: "none", + Payload: map[string]string{ + "from": "lolo_local_serv", + "to": req[FROM], + }, + }); err != nil { + return + } + wf.CandidateMux.RLock() + for _, candidate := range wf.PendingCandidates[req[FROM]] { + logger.Println("sending candidate to", req[FROM]) + if err = wf.stream.Send(&Request{ + Type: string(WEBRTC_CANDIDATE_FS), + From: "lolo_local_serv", + Token: "none", + Payload: map[string]string{ + "from": "lolo_local_serv", + "to": req[FROM], + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }); err != nil { + return + } + } + wf.CandidateMux.RUnlock() + wf.CandidateMux.Lock() + delete(wf.PendingCandidates, req[FROM]) + wf.CandidateMux.Unlock() + wf.LocalSDMapMux.Lock() + delete(wf.LocalSD, req[FROM]) + wf.LocalSDMapMux.Unlock() + return +} + +func (wf *WebrtcFsManager) HandleCounterOffer(ctx context.Context, req map[string]string) (err error) { + wf.RTCPeerConnectionMapMux.RLock() + if _, ok := wf.RTCPeerConnections[req[FROM]]; !ok { + err = fmt.Errorf("no field corresponding peer connection for id %s", req[FROM]) + return + } + connection := wf.RTCPeerConnections[req[FROM]] + wf.RTCPeerConnectionMapMux.RUnlock() + wf.LocalSDMapMux.RLock() + if err = connection.SetLocalDescription(*wf.LocalSD[req[FROM]]); err != nil { + return + } + wf.LocalSDMapMux.RUnlock() + wf.CandidateMux.RLock() + for _, candidate := range wf.PendingCandidates[req[FROM]] { + logger.Println("sending candidate to", req[FROM]) + if err = wf.stream.Send(&Request{ + Type: string(WEBRTC_CANDIDATE_FS), + From: "lolo_local_serv", + Token: "none", + Payload: map[string]string{ + "from": "lolo_local_serv", + "to": req[FROM], + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }); err != nil { + return + } + } + wf.CandidateMux.RUnlock() + return +} + +func (wf *WebrtcFsManager) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb OnICECandidateFunc) (peerConnection *webrtc.PeerConnection, err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + s := webrtc.SettingEngine{} + s.DetachDataChannels() + + api := webrtc.NewAPI(webrtc.WithSettingEngine(s)) + + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478", "stun:stun.l.google.com:19302?transport=tcp"}, + }, + }, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlan, + } + peerConnection, err = api.NewPeerConnection(config) + if err != nil { + return + } + if peerType == webrtc.SDPTypeOffer { + maxRetransmits := uint16(100) + channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return nil, err + } + channel.OnOpen(func() { + logger.Println("channel opened") + if chanErr := channel.SendText("yooo man this is open"); chanErr != nil { + logger.Println(chanErr) + } + }) + channel.OnMessage(func(msg webrtc.DataChannelMessage) { + logger.Printf("new message %s\n", string(msg.Data)) + done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{ + From: target, + Type: "test", + Payload: &DatachannelMessagePayload{}, + }, channel) + select { + case <-done: + //logger.Println("done with success") + case e := <-errCh: + logger.Println(e) + logger.Println("this is impossible") + } + }) + wf.DataChannelMapMux.Lock() + wf.DataChannels[target] = channel + wf.DataChannelMapMux.Unlock() + } + peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { + logger.Printf("ICE connection state has changed %s\n", is.String()) + if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { + logger.Println(is) + } + }) + peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { + dc.OnOpen(func() { + logger.Printf("got a new open datachannel %s\n", dc.Label()) + dataChann, err := dc.Detach() + if err != nil { + logger.Println(err) + return + } + for { + var x []byte = make([]byte, 2<<15) + n, _, err := dataChann.ReadDataChannel(x) + if err != nil { + logger.Println(err) + if err == io.EOF { + return + } + continue + } + go func(msg []byte) { + var dataChannelMessage DatachannelMessage + if unmarshalErr := json.Unmarshal(msg, &dataChannelMessage); unmarshalErr != nil { + logger.Println(unmarshalErr) + return + } + done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{ + From: dataChannelMessage.From, + Type: dataChannelMessage.Type, + Payload: dataChannelMessage.Payload, + }, dc) + select { + case <-done: + //logger.Println("done with success") + case e := <-errCh: + logger.Println(e) + logger.Println("this is impossible") + } + }(x[:n]) + } + }) + // dc.OnMessage(func(msg webrtc.DataChannelMessage) { + // var dataChannelMessage DatachannelMessage + // if unmarshalErr := json.Unmarshal(msg.Data, &dataChannelMessage); unmarshalErr != nil { + // logger.Println(unmarshalErr) + // return + // } + // done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{ + // From: dataChannelMessage.From, + // Type: dataChannelMessage.Type, + // Payload: dataChannelMessage.Payload, + // }, dc) + // select { + // case <-done: + // //logger.Println("done with success") + // case e := <-errCh: + // logger.Println(e) + // logger.Println("this is impossible") + // } + // }) + }) + peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + wf.CandidateMux.Lock() + defer wf.CandidateMux.Unlock() + desc := peerConnection.RemoteDescription() + if desc == nil { + wf.PendingCandidates[target] = append(wf.PendingCandidates[target], i) + } else { + logger.Println(i) + if iceCandidateErr := cb(target, i); iceCandidateErr != nil { + logger.Println(iceCandidateErr) + } + } + }) + peerConnection.OnNegotiationNeeded(func() { + if peerConnection.SignalingState() != webrtc.SignalingStateHaveLocalOffer && peerConnection.SignalingState() != webrtc.SignalingStateHaveRemoteOffer { + wf.RTCPeerConnectionMapMux.Lock() + defer wf.RTCPeerConnectionMapMux.Unlock() + for id, connection := range wf.RTCPeerConnections { + if connection.SignalingState() != webrtc.SignalingStateHaveLocalOffer && connection.SignalingState() != webrtc.SignalingStateHaveRemoteOffer { + localSd, err := connection.CreateOffer(nil) + if err != nil { + logger.Println(err) + return + } + if err = connection.SetLocalDescription(localSd); err != nil { + logger.Println(err) + return + } + if err = wf.stream.Send(&Request{ + Type: string(WEBRTC_RENNEGOTIATION_OFFER_FS), + From: "lolo_local_serv", + Token: "", + Payload: map[string]string{ + "to": id, + "sdp": localSd.SDP, + }, + }); err != nil { + logger.Println(err) + return + } + } + } + } + }) + return +} + +func (wf *WebrtcFsManager) HandleRennegotiationOffer(from string, dst string, sdp string) (err error) { + wf.RTCPeerConnectionMapMux.Lock() + defer wf.RTCPeerConnectionMapMux.Unlock() + if _, ok := wf.RTCPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + if wf.RTCPeerConnections[from].SignalingState() != webrtc.SignalingStateStable { + err = fmt.Errorf("rennego called in wrong state") + return + } + if err = wf.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { + return + } + localSd, err := wf.RTCPeerConnections[from].CreateAnswer(nil) + if err != nil { + return + } + if err = wf.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil { + return + } + if err = wf.stream.Send(&Request{ + Type: string(WEBRTC_RENNEGOTIATION_ANSWER_FS), + From: "lolo_local_serv", + Token: "", + Payload: map[string]string{ + "to": from, + "sdp": localSd.SDP, + }, + }); err != nil { + logger.Println(err) + return + } + return +} + +func (wf *WebrtcFsManager) HandleRennegotiationAnswer(from string, dst string, sdp string) (err error) { + wf.RTCPeerConnectionMapMux.Lock() + defer wf.RTCPeerConnectionMapMux.Unlock() + if _, ok := wf.RTCPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + err = wf.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) + return +} + +func (wf *WebrtcFsManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { + wf.RTCPeerConnectionMapMux.Lock() + defer wf.RTCPeerConnectionMapMux.Unlock() + err = wf.RTCPeerConnections[from].AddICECandidate(*candidate) + return +} diff --git a/webrtcFsMiddleware.go b/webrtcFsMiddleware.go new file mode 100644 index 0000000..62617b5 --- /dev/null +++ b/webrtcFsMiddleware.go @@ -0,0 +1,158 @@ +package localserver + +import ( + context "context" + "strconv" + + "github.com/pion/webrtc/v3" +) + +const ( + INCOMING_PEER_FS ReqType = "incoming_peer_fs" + LEAVING_PEER_FS ReqType = "leaving_peer_fs" + WEBRTC_OFFER_FS ReqType = "offer_fs" + WEBRTC_ANSWER_FS ReqType = "answer_fs" + WEBRTC_RENNEGOTIATION_OFFER_FS ReqType = "rennegotiation_offer_fs" + WEBRTC_RENNEGOTIATION_ANSWER_FS ReqType = "rennegotiation_answer_fs" + WEBRTC_COUNTER_OFFER_FS ReqType = "webrtc_counter_offer_fs" + WEBRTC_CANDIDATE_FS ReqType = "webrtc_candidate_fs" +) + +type WebRTCFsMiddleware struct { + Manager *WebrtcFsManager + stream GrpcManager_LinkClient +} + +func NewWebRTCFsMiddleware(manager *WebrtcFsManager) (webrtcFsMiddleware *WebRTCFsMiddleware) { + webrtcFsMiddleware = &WebRTCFsMiddleware{ + Manager: manager, + } + return +} + +func (wfm *WebRTCFsMiddleware) signalCandidate(to string, candidate *webrtc.ICECandidate) (err error) { + err = wfm.stream.Send(&Request{ + Type: string(WEBRTC_CANDIDATE_FS), + From: "lolo_local_serv", + Token: "none", + Payload: map[string]string{ + "from": "lolo_local_serv", + "to": to, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }) + return +} + +func (wfm *WebRTCFsMiddleware) Process(ctx context.Context, req *Response, stream GrpcManager_LinkClient) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + switch req.Type { + case string(INCOMING_PEER_FS): + logger.Println("quit squad called") + if from, ok := req.Payload[FROM]; ok { + logger.Println(from) + //wfm.Manager.HandleLeavingMember(from) + done <- struct{}{} + } + case string(PEER_CONNECTION_REQUEST): + if err := validateRequest(req.Payload, FROM, TO); err != nil { + errCh <- err + return + } + // if err := wfm.Manager.CreateOffer(ctx, req.Payload[FROM], req.Payload[TO], wfm.signalCandidate); err != nil { + // errCh <- err + // return + // } + done <- struct{}{} + case string(WEBRTC_OFFER_FS): + if err := validateRequest(req.GetPayload(), FROM, TO, SDP); err != nil { + errCh <- err + return + } + if err := wfm.Manager.HandleOffer(ctx, req.GetPayload(), wfm.signalCandidate); err != nil { + errCh <- err + return + } + done <- struct{}{} + case string(WEBRTC_ANSWER_FS): + if err := validateRequest(req.GetPayload(), FROM, TO, SDP); err != nil { + errCh <- err + return + } + if err := wfm.Manager.HandleAnswer(ctx, req.GetPayload()); err != nil { + errCh <- err + return + } + done <- struct{}{} + case string(WEBRTC_COUNTER_OFFER_FS): + if err := validateRequest(req.GetPayload(), FROM); err != nil { + errCh <- err + return + } + if err := wfm.Manager.HandleCounterOffer(ctx, req.Payload); err != nil { + errCh <- err + return + } + done <- struct{}{} + case string(WEBRTC_RENNEGOTIATION_ANSWER_FS): + if err := validateRequest(req.GetPayload(), FROM, SDP); err != nil { + errCh <- err + return + } + if err := wfm.Manager.HandleRennegotiationAnswer(req.Payload[FROM], "lolo_local_serv", req.Payload[SDP]); err != nil { + errCh <- err + return + } + done <- struct{}{} + case string(WEBRTC_RENNEGOTIATION_OFFER_FS): + if err := validateRequest(req.GetPayload(), FROM, SDP); err != nil { + errCh <- err + return + } + if err := wfm.Manager.HandleRennegotiationOffer(req.Payload[FROM], "", req.Payload[SDP]); err != nil { + errCh <- err + return + } + done <- struct{}{} + case string(WEBRTC_CANDIDATE_FS): + if err := validateRequest(req.GetPayload(), FROM, "candidate", "sdpMlineIndex", "sdpMid"); err != nil { + errCh <- err + return + } + logger.Println(req.Payload) + i, err := strconv.Atoi(req.Payload["sdpMlineIndex"]) + if err != nil { + errCh <- err + return + } + sdpMlineIndex := uint16(i) + sdpMid := req.Payload["sdpMid"] + logger.Println(sdpMid, sdpMlineIndex) + if err := wfm.Manager.AddCandidate(&webrtc.ICECandidateInit{ + Candidate: req.Payload["candidate"], + SDPMid: &sdpMid, + SDPMLineIndex: &sdpMlineIndex, + }, req.Payload[FROM]); err != nil { + errCh <- err + return + } + done <- struct{}{} + default: + logger.Println("fs is correctly linked") + done <- struct{}{} + } + done <- struct{}{} + }() + select { + case <-ctx.Done(): + err = ctx.Err() + return + case <-done: + return + case err = <-errCh: + return + } +} diff --git a/webrtcGrpcMiddleware.go b/webrtcGrpcMiddleware.go new file mode 100644 index 0000000..346b597 --- /dev/null +++ b/webrtcGrpcMiddleware.go @@ -0,0 +1,195 @@ +package localserver + +import ( + "context" + "fmt" + "strconv" + + "github.com/pion/webrtc/v3" +) + +type GrpcRequestType string + +const ( + PEER_CONNECTION_REQUEST GrpcRequestType = "peer_connection_request" +) + +const ( + OFFER ReqType = "offer" + ANSWER ReqType = "answer" + COUNTER_OFFER ReqType = "webrtc_counter_offer" + JOIN_HOSTED_SQUAD ReqType = "join_hosted_squad" + HOSTED_SQUAD_ACCESS_DENIED ReqType = "hosted_squad_access_denied" + HOSTED_SQUAD_STOP_CALL ReqType = "hosted_squad_stop_call" + HOSTED_SQUAD_ACCESS_GRANTED ReqType = "hosted_squad_access_granted" + LEAVE_HOSTED_SQUAD ReqType = "leave_hosted_squad" + INCOMING_MEMBER_HOSTED ReqType = "incoming_member_hosted" + LEAVING_MEMBER_HOSTED ReqType = "leaving_member_hosted" + HOSTED_SQUAD_WEBRTC_OFFER ReqType = "hosted_squad_offer" + HOSTED_SQUAD_WEBRTC_ANSWER ReqType = "hosted_squad_answer" + HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER ReqType = "hosted_squad_rennegotiation_offer" + HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "hosted_squad_rennegotiation_answer" + HOSTED_SQUAD_WEBRTC_COUNTER_OFFER ReqType = "hosted_squad_webrtc_counter_offer" + HOSTED_SQUAD_WEBRTC_CANDIDATE ReqType = "hosted_squad_webrtc_candidate" + HOSTED_SQUAD_REMOVE_VIDEO ReqType = "hosted_squad_remove_video" + GET_HOSTED_SQUAD_TRACKS ReqType = "hosted_squad_get_tracks" + NEW_HOSTED_SQUAD = "new_hosted_squad" +) + +type WebRTCGrpcMiddleware struct { + Manager *WebRTCCallManager + stream GrpcManager_LinkClient +} + +func NewWebRTCGrpcMiddleware(manager *WebRTCCallManager) (webrtcGrpcMiddleware *WebRTCGrpcMiddleware) { + webrtcGrpcMiddleware = &WebRTCGrpcMiddleware{ + Manager: manager, + } + return +} + +func validateRequest(req map[string]string, entries ...string) (err error) { + for _, entry := range entries { + if _, ok := req[entry]; !ok { + err = fmt.Errorf("no field %s in req payload", entry) + return + } + } + return +} + +func (wgm *WebRTCGrpcMiddleware) signalCandidate(to string, candidate *webrtc.ICECandidate) (err error) { + err = wgm.stream.Send(&Request{ + Type: string(HOSTED_SQUAD_WEBRTC_CANDIDATE), + From: wgm.Manager.ID, + Token: "none", + Payload: map[string]string{ + "from": wgm.Manager.ID, + "to": to, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }) + return +} + +func (wgm *WebRTCGrpcMiddleware) Process(ctx context.Context, req *Response, stream GrpcManager_LinkClient) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + defer func() { + done <- struct{}{} + }() + switch req.Type { + case NEW_HOSTED_SQUAD: + if err := validateRequest(req.Payload, "ID"); err != nil { + errCh <- err + return + } + logger.Println("new squad incoming") + wgm.Manager.SquadMapMux.Lock() + wgm.Manager.Squads[req.Payload["ID"]] = &Squad{ + ID: req.Payload["ID"], + Members: []string{}, + } + wgm.Manager.SquadMapMux.Unlock() + case string(HOSTED_SQUAD_STOP_CALL): + logger.Println("quit squad called") + if err := validateRequest(req.Payload, FROM, "squadId"); err != nil { + errCh <- err + return + } + wgm.Manager.HandleLeavingMember(req.Payload[FROM], req.Payload["squadId"]) + done <- struct{}{} + case string(PEER_CONNECTION_REQUEST): + if err := validateRequest(req.Payload, FROM, TO); err != nil { + errCh <- err + return + } + logger.Println("creating offer for peer") + if err := wgm.Manager.CreateOffer(ctx, req.Payload[FROM], req.Payload[TO], wgm.signalCandidate); err != nil { + errCh <- err + return + } + case string(HOSTED_SQUAD_WEBRTC_OFFER): + if err := validateRequest(req.GetPayload(), FROM, TO, SDP, SQUAD_ID); err != nil { + errCh <- err + return + } + if err := wgm.Manager.HandleOffer(ctx, req.GetPayload(), wgm.signalCandidate); err != nil { + errCh <- err + return + } + case string(HOSTED_SQUAD_WEBRTC_ANSWER): + if err := validateRequest(req.GetPayload(), FROM, TO, SDP); err != nil { + errCh <- err + return + } + if err := wgm.Manager.HandleAnswer(ctx, req.GetPayload()); err != nil { + errCh <- err + return + } + case string(HOSTED_SQUAD_WEBRTC_COUNTER_OFFER): + if err := validateRequest(req.GetPayload(), FROM); err != nil { + errCh <- err + return + } + if err := wgm.Manager.HandleCounterOffer(ctx, req.Payload); err != nil { + errCh <- err + return + } + case string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER): + logger.Println("received negotiation answer") + if err := validateRequest(req.GetPayload(), FROM, SDP); err != nil { + errCh <- err + return + } + if err := wgm.Manager.HandleRennegotiationAnswer(req.Payload[FROM], req.Payload[SDP]); err != nil { + errCh <- err + return + } + case string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER): + logger.Println("received negotiation offer") + if err := validateRequest(req.GetPayload(), FROM, SDP); err != nil { + errCh <- err + return + } + if err := wgm.Manager.HandleRennegotiationOffer(req.Payload[FROM], req.Payload[SDP]); err != nil { + errCh <- err + return + } + case string(HOSTED_SQUAD_WEBRTC_CANDIDATE): + if err := validateRequest(req.GetPayload(), FROM, "candidate", "sdpMlineIndex", "sdpMid"); err != nil { + errCh <- err + return + } + logger.Println(req.Payload) + i, err := strconv.Atoi(req.Payload["sdpMlineIndex"]) + if err != nil { + errCh <- err + return + } + sdpMlineIndex := uint16(i) + sdpMid := req.Payload["sdpMid"] + logger.Println(sdpMid, sdpMlineIndex) + if err := wgm.Manager.AddCandidate(&webrtc.ICECandidateInit{ + Candidate: req.Payload["candidate"], + SDPMid: &sdpMid, + SDPMLineIndex: &sdpMlineIndex, + }, req.Payload[FROM]); err != nil { + errCh <- err + return + } + default: + } + }() + select { + case <-ctx.Done(): + err = ctx.Err() + return + case <-done: + return + case err = <-errCh: + return + } +} diff --git a/webrtcHttpMiddleware.go b/webrtcHttpMiddleware.go new file mode 100644 index 0000000..66485f6 --- /dev/null +++ b/webrtcHttpMiddleware.go @@ -0,0 +1,66 @@ +package localserver + +import ( + "context" + "encoding/json" + "io" + "net/http" +) + +type ( + WebRTCHttpMiddleware struct { + //menuItem *systray.MenuItem + } +) + +const ( + CREATE_HOSTED_SQUAD = "create_hosted_squad" +) + +// func NewWebRTCHttpMiddleware(menuItem *systray.MenuItem) (webRTCHttpMiddleware *WebRTCHttpMiddleware) { +// webRTCHttpMiddleware = &WebRTCHttpMiddleware{ +// menuItem: menuItem, +// } +// return +// } + +func (wm *WebRTCHttpMiddleware) Process(ctx context.Context, req *http.Request) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + localServerReq, err := wm.unmarshallBody(req) + if err != nil { + errCh <- err + return + } + switch localServerReq.ReqType { + case CREATE_HOSTED_SQUAD: + + default: + } + }() + select { + case <-done: + return + case err = <-errCh: + return + case <-ctx.Done(): + err = ctx.Err() + return + } +} + +func (wm *WebRTCHttpMiddleware) unmarshallBody(req *http.Request) (localServerReq *LocalServerRequest, err error) { + reqBody, err := req.GetBody() + if err != nil { + return + } + bs, err := io.ReadAll(reqBody) + if err != nil { + return + } + err = json.Unmarshal(bs, &localServerReq) + if err != nil { + return + } + return +} diff --git a/zoneAudioChannel.go b/zoneAudioChannel.go new file mode 100644 index 0000000..bf6b540 --- /dev/null +++ b/zoneAudioChannel.go @@ -0,0 +1,759 @@ +package localserver + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "strconv" + sync "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/pion/rtcp" + "github.com/pion/webrtc/v3" +) + +const ( + AUDIO_CHANNEL_ACCESS_DENIED ReqType = "audio_channel_access_denied" + AUDIO_CHANNEL_STOP_CALL ReqType = "audio_channel_stop_call" + AUDIO_CHANNEL_ACCESS_GRANTED ReqType = "audio_channel_access_granted" + AUDIO_CHANNEL_WEBRTC_OFFER ReqType = "audio_channel_offer" + AUDIO_CHANNEL_WEBRTC_ANSWER ReqType = "audio_channel_answer" + AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER ReqType = "audio_channel_rennegotiation_offer" + AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "audio_channel_rennegotiation_answer" + AUDIO_CHANNEL_WEBRTC_COUNTER_OFFER ReqType = "audio_channel_webrtc_counter_offer" + AUDIO_CHANNEL_WEBRTC_CANDIDATE ReqType = "audio_channel_webrtc_candidate" + AUDIO_CHANNEL_REMOVE_VIDEO ReqType = "audio_channel_remove_video" + GET_AUDIO_CHANNEL_TRACKS ReqType = "audio_channel_get_tracks" +) + +const ( + AUDIO_CHANNEL_USER_MUTE = "audio_channel_user_mute" + AUDIO_CHANNEL_USER_UNMUTE = "audio_channel_user_unmute" + AUDIO_CHANNEL_USER_SPEAKING = "audio_channel_user_speaking" + AUDIO_CHANNEL_USER_STOPPED_SPEAKING = "audio_channel_user_stopped_speaking" +) + +type ZoneRTCPeerConnection struct { + *webrtc.PeerConnection + makingOffer bool + makingOfferLock *sync.Mutex + negotiate func(string, SendDCMessageFunc) +} + +type AudioChannel struct { + ID string `json:"id"` + Owner string `json:"owner"` + ChannelType string `json:"channelType"` + Members []string `json:"members"` + CurrentMembersId []string `json:"currentMembersId"` + CurrentMembers map[string]*AudioChannelMember `json:"currentMembers"` + localSD map[string]*webrtc.SessionDescription `json:"-"` + rtcPeerConnections map[string]*ZoneRTCPeerConnection `json:"-"` + audioTransceiver map[string][]*PeerSender `json:"-"` + audiChannelsDataChannels map[string]*DataChannel `json:"-"` + pendingCandidates map[string][]*webrtc.ICECandidate `json:"-"` + remoteTracks map[string][]*RemoteTrack `json:"-"` + middlewares []interface{} `json:"-"` + candidateFlag *uint32 `json:"-"` + remoteTracksFlag *uint32 `json:"-"` + rtcPeerConnectionMapFlag *uint32 `json:"-"` + dataChannelMapFlag *uint32 `json:"-"` + localSDMapFlag *uint32 `json:"-"` + audioSenderFlag *uint32 `json:"-"` +} + +type AudioChannelOnICECandidateFunc = func(string, string, *webrtc.ICECandidate) error + +func NewAudioChannel(id string, owner string, channelType string, members []string, currentMembersId []string, currentMembers map[string]*AudioChannelMember) (audioChannel *AudioChannel) { + candidateFlag := uint32(0) + remoteTracksFlag := uint32(0) + rtcPeerConnectionMapFlag := uint32(0) + dataChannelMapFlag := uint32(0) + localSDMapFlag := uint32(0) + audioSenderFlag := uint32(0) + audioChannel = &AudioChannel{ + ID: id, + Owner: owner, + ChannelType: channelType, + Members: members, + CurrentMembersId: currentMembersId, + CurrentMembers: currentMembers, + localSD: make(map[string]*webrtc.SessionDescription), + rtcPeerConnections: make(map[string]*ZoneRTCPeerConnection), + audioTransceiver: make(map[string][]*PeerSender), + audiChannelsDataChannels: make(map[string]*DataChannel), + pendingCandidates: make(map[string][]*webrtc.ICECandidate), + remoteTracks: make(map[string][]*RemoteTrack), + middlewares: make([]interface{}, 0), + candidateFlag: &candidateFlag, + remoteTracksFlag: &remoteTracksFlag, + rtcPeerConnectionMapFlag: &rtcPeerConnectionMapFlag, + dataChannelMapFlag: &dataChannelMapFlag, + localSDMapFlag: &localSDMapFlag, + audioSenderFlag: &audioSenderFlag, + } + return +} + +func (ac *AudioChannel) HandleOffer(ctx context.Context, channelId string, userId string, sdp string, hostId string, sendDCMessage SendDCMessageFunc, cb AudioChannelOnICECandidateFunc) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + peerConnection, err := ac.createPeerConnection(userId, ac.ID, webrtc.SDPTypeAnswer, cb, sendDCMessage) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + ac.rtcPeerConnections[userId] = &ZoneRTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: false, + makingOfferLock: &sync.Mutex{}, + negotiate: ac.negotiate, + } + return + }) + offer := webrtc.SessionDescription{ + Type: webrtc.SDPTypeOffer, + SDP: sdp, + } + if err = peerConnection.SetRemoteDescription(offer); err != nil { + errCh <- err + return + } + rawAnswer, err := peerConnection.CreateAnswer(nil) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(ac.localSDMapFlag, func() (err error) { + ac.localSD[userId] = &rawAnswer + return + }) + _, _ = sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_ANSWER), hostId, userId, map[string]interface{}{ + "to": userId, + "from": ac.ID, + "channelId": channelId, + "sdp": rawAnswer.SDP, + }) + done <- struct{}{} + logger.Println("handle offer done") + }() + return +} + +func (ac *AudioChannel) HandleCounterOffer(ctx context.Context, userId string, sendDCMessage SendDCMessageFunc) (err error) { + if err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := ac.rtcPeerConnections[userId]; !ok { + err = fmt.Errorf("no field corresponding peer connection for id %s", userId) + return + } + logger.Println("handling counter offer") + connection := ac.rtcPeerConnections[userId] + err = atomicallyExecute(ac.localSDMapFlag, func() (err error) { + err = connection.SetLocalDescription(*ac.localSD[userId]) + return + }) + return + }); err != nil { + return + } + _ = atomicallyExecute(ac.localSDMapFlag, func() (err error) { + delete(ac.localSD, userId) + return + }) + if err = atomicallyExecute(ac.candidateFlag, func() (err error) { + for _, candidate := range ac.pendingCandidates[userId] { + logger.Println("sending candidate to", userId, candidate) + d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_CANDIDATE), "", userId, map[string]interface{}{ + "from": ac.ID, + "to": userId, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }) + select { + case <-d: + case err = <-e: + return + } + } + delete(ac.pendingCandidates, userId) + return + }); err != nil { + return + } + return +} + +func (ac *AudioChannel) HandleRennegotiationOffer(from string, sdp string, sendDCMessage SendDCMessageFunc) (err error) { + err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := ac.rtcPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + ac.rtcPeerConnections[from].makingOfferLock.Lock() + if ac.rtcPeerConnections[from].makingOffer { + ac.rtcPeerConnections[from].makingOfferLock.Unlock() + return fmt.Errorf("already making an offer or state is stable") + } + ac.rtcPeerConnections[from].makingOfferLock.Unlock() + if err = ac.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { + return + } + localSd, err := ac.rtcPeerConnections[from].CreateAnswer(nil) + if err != nil { + return + } + if err = ac.rtcPeerConnections[from].SetLocalDescription(localSd); err != nil { + return + } + d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER), ac.ID, from, map[string]interface{}{ + "from": ac.ID, + "to": from, + "sdp": localSd.SDP, + }) + select { + case <-d: + case err = <-e: + } + return + }) + return +} + +func (ac *AudioChannel) HandleRennegotiationAnswer(from string, sdp string) (err error) { + logger.Println("---------------------handling rennego answer") + err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + ac.rtcPeerConnections[from].makingOfferLock.Lock() + if ac.rtcPeerConnections[from].makingOffer { + ac.rtcPeerConnections[from].makingOfferLock.Unlock() + return fmt.Errorf("already making an offer or state is stable") + } + ac.rtcPeerConnections[from].makingOfferLock.Unlock() + if _, ok := ac.rtcPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + err = ac.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) + return + }) + return +} + +func (ac *AudioChannel) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { + logger.Println("adding ice candidate", candidate) + err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := ac.rtcPeerConnections[from]; ok && candidate != nil { + err = ac.rtcPeerConnections[from].AddICECandidate(*candidate) + } + return + }) + return +} + +func (ac *AudioChannel) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb AudioChannelOnICECandidateFunc, sendDCMessage SendDCMessageFunc) (peerConnection *webrtc.PeerConnection, err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"}, + }, + }, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, + } + + peerConnection, err = webrtc.NewPeerConnection(config) + if err != nil { + return + } + logger.Println("---------------------------------------------------") + if peerType == webrtc.SDPTypeAnswer { + maxRetransmits := uint16(100) + channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return nil, err + } + channel.OnOpen(func() { + logger.Println("channel opened") + if chanErr := channel.SendText("yooo man this is open"); chanErr != nil { + logger.Println(chanErr) + } + }) + channel.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + if e := ac.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil { + logger.Println("*-------------- datachannel error: ", e) + } + }) + logger.Println("new channel for target : ", target) + channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) + channel.OnBufferedAmountLow(func() { + + }) + _ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) { + logger.Println(target) + l := int32(0) + ac.audiChannelsDataChannels[target] = &DataChannel{ + DataChannel: channel, + l: &l, + } + return + }) + } else { + peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { + _ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) { + l := int32(0) + ac.audiChannelsDataChannels[target] = &DataChannel{ + DataChannel: dc, + l: &l, + } + return + }) + dc.OnOpen(func() { + logger.Printf("got a new open datachannel %s\n", dc.Label()) + }) + dc.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + if e := ac.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil { + logger.Println("*-------------- datachannel error: ", e) + } + }) + }) + } + err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) { + logger.Println("------------------", ac.CurrentMembersId) + for _, id := range ac.CurrentMembersId { + logger.Println(id) + if id != target { + if _, ok := ac.remoteTracks[id]; !ok { + continue + } + for _, track := range ac.remoteTracks[id] { + transceiver, err := peerConnection.AddTransceiverFromKind(track.Track.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + if err != nil { + logger.Println("add track error") + continue + } + if err := transceiver.Sender().ReplaceTrack(track.Track); err != nil { + logger.Println("add track error") + continue + } + + _ = atomicallyExecute(ac.audioSenderFlag, func() (err error) { + if len(ac.audioTransceiver) == 0 { + ac.audioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} + } else { + ac.audioTransceiver[id] = append(ac.audioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) + } + return + }) + logger.Println("track added", track) + } + } + } + return + }) + return + }) + peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed { + logger.Println(pcs) + //ac.HandleLeavingMember(target, squadId) + } + }) + peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { + logger.Printf("ICE connection state has changed %s\n", is.String()) + if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { + logger.Println(is) + } + }) + peerConnection.OnTrack(func(tr *webrtc.TrackRemote, r *webrtc.RTPReceiver) { + logger.Println("got new track") + defer func() { + if stopErr := r.Stop(); stopErr != nil { + logger.Println(stopErr) + } + }() + go func() { + ticker := time.NewTicker(1500 * time.Millisecond) + for range ticker.C { + if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(tr.SSRC())}}); rtcpSendErr != nil { + logger.Println(rtcpSendErr) + break + } + } + }() + uniqId := uuid.New() + i := fmt.Sprintf("%s/%s", target, uniqId.String()) + logger.Println("*************************----------------", i, "-----------------------***************") + localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(tr.Codec().RTPCodecCapability, i, i) + if newTrackErr != nil { + return + } + logger.Println(localTrack) + rtpbuf := make([]byte, 1400) + flag := int32(0) + remote := &RemoteTrack{ID: target, Track: localTrack, rdv: &flag} + _ = atomicallyExecute(ac.remoteTracksFlag, func() (err error) { + if len(ac.remoteTracks[target]) == 0 { + ac.remoteTracks[target] = []*RemoteTrack{remote} + } else { + ac.remoteTracks[target] = append(ac.remoteTracks[target], remote) + } + index := len(ac.remoteTracks[target]) + logger.Println(index, ac.remoteTracks) + return + }) + _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + for _, id := range ac.CurrentMembersId { + if id != target { + if _, ok := ac.rtcPeerConnections[id]; !ok { + continue + } + connection := ac.rtcPeerConnections[id] + transceiver, tranceiverErr := connection.AddTransceiverFromKind(localTrack.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + if tranceiverErr != nil { + logger.Println(tranceiverErr) + continue + } + if replaceTrackErr := transceiver.Sender().ReplaceTrack(localTrack); replaceTrackErr != nil { + logger.Println(replaceTrackErr) + continue + } + go func() { + rtcpBuf := make([]byte, 1500) + for { + if _, _, rtcpErr := transceiver.Sender().Read(rtcpBuf); rtcpErr != nil { + return + } + } + }() + if localTrack.Kind() == webrtc.RTPCodecTypeAudio { + _ = atomicallyExecute(ac.audioSenderFlag, func() (err error) { + if len(ac.audioTransceiver) == 0 { + ac.audioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} + } else { + ac.audioTransceiver[target] = append(ac.audioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) + } + return + }) + } else if localTrack.Kind() == webrtc.RTPCodecTypeVideo { + logger.Println("track of wrong type") + } + go func() { + <-time.After(time.Millisecond * 500) + connection.negotiate(id, sendDCMessage) + }() + } + } + return + }) + d := make(chan struct{}) + go func() { + for { + i, _, readErr := tr.Read(rtpbuf) + if readErr != nil { + logger.Println(readErr) + break + } + //logger.Println(rtpbuf[:i]) + f := atomic.LoadInt32(remote.rdv) + if f == 0 { + if _, writeErr := localTrack.Write(rtpbuf[:i]); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) { + logger.Println(writeErr) + break + } else { + _ = rtpbuf[:i] + } + } + } + d <- struct{}{} + }() + <-d + }) + peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + _ = atomicallyExecute(ac.candidateFlag, func() (err error) { + desc := peerConnection.RemoteDescription() + if desc == nil { + logger.Println("generated candidate appended to list : ", i) + ac.pendingCandidates[target] = append(ac.pendingCandidates[target], i) + } else { + logger.Println("generated candidate : ", i) + if iceCandidateErr := cb(from, target, i); iceCandidateErr != nil { + logger.Println(iceCandidateErr) + } + } + return + }) + }) + peerConnection.OnNegotiationNeeded(func() { + logger.Println("---------------- rennego is needed -----------") + // _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + // for _, id := range ac.CurrentMembersId { + // logger.Println("----------------- sending renego to peer with id", id) + // if _, ok := ac.rtcPeerConnections[id]; !ok { + // continue + // } + // if peerConnection.SignalingState() == webrtc.SignalingStateStable { + // localSd, localSdErr := peerConnection.CreateOffer(nil) + // if localSdErr != nil { + // logger.Println(localSdErr) + // return localSdErr + // } + // if err = peerConnection.SetLocalDescription(localSd); err != nil { + // logger.Println(err) + // return + // } + // d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), ac.ID, id, map[string]interface{}{ + // "from": ac.ID, + // "to": id, + // "sdp": localSd.SDP, + // }) + // select { + // case <-d: + // case err = <-e: + // logger.Println(err) + // } + // } + // } + // return + // }) + }) + return +} + +func (ac *AudioChannel) HandleLeavingMember(id string) { + if err := atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := ac.rtcPeerConnections[id]; !ok { + err = fmt.Errorf("no corresponding peerconnection for audio channel leaving member") + } + return + }); err != nil { + logger.Println(err) + } else { + defer func() { + _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := ac.rtcPeerConnections[id]; ok { + if closeErr := ac.rtcPeerConnections[id].Close(); closeErr != nil { + err = closeErr + logger.Println("peer connection close error", closeErr) + } + } + delete(ac.rtcPeerConnections, id) + return + }) + }() + } + logger.Printf("peer %s is leaving the squad\n", id) + _ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) { + if _, ok := ac.audiChannelsDataChannels[id]; ok { + ac.audiChannelsDataChannels[id].DataChannel.Close() + } + delete(ac.audiChannelsDataChannels, id) + return + }) + _ = atomicallyExecute(ac.localSDMapFlag, func() (err error) { + delete(ac.localSD, id) + return + }) + _ = atomicallyExecute(ac.candidateFlag, func() (err error) { + delete(ac.pendingCandidates, id) + return + }) + _ = atomicallyExecute(ac.audioSenderFlag, func() (err error) { + for peerId, peerSender := range ac.audioTransceiver { + if peerId != id { + logger.Println("senders", peerSender) + c := 0 + for i, sender := range peerSender { + if sender.ID == id { + if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { + logger.Println("transceiverErr occured with video", transceiverErr) + } + peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] + c++ + } + } + ac.audioTransceiver[peerId] = ac.audioTransceiver[peerId][:len(peerSender)-(c)] + logger.Println(ac.audioTransceiver[peerId]) + } + } + for _, transceiver := range ac.audioTransceiver[id] { + if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { + logger.Println("transceiver audio stop error", stopErr) + } + } + delete(ac.audioTransceiver, id) + return + }) + _ = atomicallyExecute(ac.remoteTracksFlag, func() (err error) { + delete(ac.remoteTracks, id) + return + }) +} + +func (ac *AudioChannel) negotiate(target string, sendDCMessage SendDCMessageFunc) { + logger.Println("------------------negotiate is called") + _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := ac.rtcPeerConnections[target]; !ok { + return + } + ac.rtcPeerConnections[target].makingOfferLock.Lock() + ac.rtcPeerConnections[target].makingOffer = true + ac.rtcPeerConnections[target].makingOfferLock.Unlock() + defer func() { + ac.rtcPeerConnections[target].makingOfferLock.Lock() + ac.rtcPeerConnections[target].makingOffer = false + ac.rtcPeerConnections[target].makingOfferLock.Unlock() + }() + + for _, id := range ac.CurrentMembersId { + logger.Println("----------------- sending renego to peer with id", id) + if _, ok := ac.rtcPeerConnections[id]; !ok { + continue + } + connection := ac.rtcPeerConnections[id] + if connection.SignalingState() == webrtc.SignalingStateStable { + localSd, err := connection.CreateOffer(nil) + if err != nil { + logger.Println(err) + return err + } + if err = connection.SetLocalDescription(localSd); err != nil { + logger.Println(err) + return err + } + d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), ac.ID, id, map[string]interface{}{ + "from": ac.ID, + "to": id, + "sdp": localSd.SDP, + }) + select { + case <-d: + case err = <-e: + logger.Println(err) + } + } + } + return + }) +} + +func (ac *AudioChannel) broadcastDatachannelMessage(from string, eventId string, payload map[string]interface{}) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: eventId, + From: ac.ID, + Payload: payload, + }) + if jsonErr != nil { + errCh <- jsonErr + return + } + if err := atomicallyExecute(ac.dataChannelMapFlag, func() (err error) { + for id, dc := range ac.audiChannelsDataChannels { + if from != id { + if err = dc.DataChannel.SendText(string(bs)); err != nil { + return + } + } + } + return + }); err != nil { + errCh <- err + } + done <- struct{}{} + }() + return +} + +func (ac *AudioChannel) HandleDataChannelEvents(from string, eventId string, payload map[string]interface{}) (err error) { + switch eventId { + case AUDIO_CHANNEL_USER_MUTE: + if err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) { + if _, ok := ac.remoteTracks[from]; !ok { + err = fmt.Errorf("no corresponding remote tracks entry for id %s", from) + return + } + for _, track := range ac.remoteTracks[from] { + atomic.SwapInt32(track.rdv, 1) + } + return + }); err != nil { + return + } + done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_MUTE, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case AUDIO_CHANNEL_USER_UNMUTE: + if err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) { + if _, ok := ac.remoteTracks[from]; !ok { + err = fmt.Errorf("no corresponding remote tracks entry for id %s", from) + return + } + for _, track := range ac.remoteTracks[from] { + atomic.SwapInt32(track.rdv, 0) + } + return + }); err != nil { + return + } + done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_UNMUTE, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case AUDIO_CHANNEL_USER_SPEAKING: + done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_SPEAKING, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case AUDIO_CHANNEL_USER_STOPPED_SPEAKING: + done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_STOPPED_SPEAKING, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + } + return +} diff --git a/zoneAudioChannelshandler.go b/zoneAudioChannelshandler.go new file mode 100644 index 0000000..73d0726 --- /dev/null +++ b/zoneAudioChannelshandler.go @@ -0,0 +1,1039 @@ +package localserver + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + "strconv" + + "github.com/pion/webrtc/v3" +) + +const ( + JOIN_AUDIO_CHANNEL = "join_audio_channel" + LEAVE_AUDIO_CHANNEL = "leave_audio_channel" + LIST_AUDIO_CHANNELS = "list_audio_channels" + GET_AUDIO_CHANNELS = "get_audio_channels" + GET_AUDIO_CHANNEL = "get_audio_channel" + CREATE_AUDIO_CHANNEL = "create_audio_channel" + DELETE_AUDIO_CHANNEL = "delete_audio_channel" + EDIT_AUDIO_CHANNEL_TYPE = "edit_audio_channel_type" + EDIT_AUDIO_CHANNEL_NAME = "edit_audio_channel_name" + ADD_AUDIO_CHANNEL_MEMBERS = "add_audio_channel_members" + REMOVE_AUDIO_CHANNEL_MEMBER = "remove_audio_channel_member" +) + +const ( + USER_JOINED_AUDIO_CHANNEL = "user_joined_audio_channel" + USER_LEFT_AUDIO_CHANNEL = "user_left_audio_channel" +) + +type AudioChannelMember struct { +} + +type AudioChannelConfig struct { + CurrentMembersId []string + ID string `json:"id"` + Owner string `json:"owner"` + ChannelType string `json:"channelType"` + Members []string `json:"members"` +} + +type ZoneAudioChannelsHandler struct { + HostId string + ZoneId string + ZoneMembersId []string + DataChannels map[string]*DataChannel + DataChannelsFlag *uint32 + AudioChannelsFlag *uint32 + AudioChannels map[string]*AudioChannel + reqChans []chan<- *ZoneRequest +} + +const LOBBY string = "lobby" + +func NewZoneAudioChannelsHandler(hostId string, zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (zoneAudioChannelsHandler *ZoneAudioChannelsHandler, err error) { + var dirs []fs.DirEntry + dirs, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "audioChannels")) + if err != nil { + if os.IsNotExist(err) { + logger.Printf("creating audioChannels directory for zone %s...\n", zoneId) + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zoneId, "audioChannels", LOBBY), 0700) + if mkdirErr != nil { + return nil, mkdirErr + } + file, ferr := os.Create(filepath.Join("data", "zones", zoneId, "audioChannels", LOBBY, "audioChannelConfig.json")) + if ferr != nil { + return nil, ferr + } + baseConfig := AudioChannelConfig{ + ID: LOBBY, + Owner: owner, + ChannelType: "public", + CurrentMembersId: make([]string, 0), + Members: make([]string, 0), + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return nil, jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return nil, writeErr + } + _ = file.Close() + dirs, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "audioChannels")) + if err != nil { + return nil, err + } + } else { + return + } + } + audioChannels := make(map[string]*AudioChannel) + for _, audioChannel := range dirs { + var bs []byte + bs, err = os.ReadFile(filepath.Join("data", "zones", zoneId, "audioChannels", audioChannel.Name(), "audioChannelConfig.json")) + if err != nil { + return nil, err + } + logger.Println(string(bs)) + var acc AudioChannelConfig + if err = json.Unmarshal(bs, &acc); err != nil { + return nil, err + } + logger.Println("audioChannels data :", acc.ID, acc.ChannelType, acc.Owner, acc.Members) + ac := NewAudioChannel(acc.ID, acc.Owner, acc.ChannelType, acc.Members, make([]string, 0), make(map[string]*AudioChannelMember)) + audioChannels[acc.ID] = ac + } + audioChannelFlag := uint32(0) + zoneAudioChannelsHandler = &ZoneAudioChannelsHandler{ + HostId: hostId, + ZoneId: zoneId, + ZoneMembersId: authorizedMembers, + DataChannels: dataChannels, + DataChannelsFlag: dataChannelFlag, + AudioChannels: audioChannels, + AudioChannelsFlag: &audioChannelFlag, + } + return +} + +type SendDCMessageFunc = func(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) + +func (zach *ZoneAudioChannelsHandler) sendZoneRequest(reqType string, from string, payload map[string]interface{}) { + go func() { + for _, rc := range zach.reqChans { + rc <- &ZoneRequest{ + ReqType: reqType, + From: from, + Payload: payload, + } + } + }() +} + +func (zach *ZoneAudioChannelsHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + if err := atomicallyExecute(zach.DataChannelsFlag, func() (err error) { + if _, ok := zach.DataChannels[to]; ok { + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: reqType, + From: zach.HostId, + To: to, + Payload: payload, + }) + if jsonErr != nil { + return jsonErr + } + err = zach.DataChannels[to].DataChannel.SendText(string(bs)) + } + return + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + return done, errCh +} + +func (zach *ZoneAudioChannelsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { + for _, member := range authorizedMembers { + if serr := zach.SetAllPublicAudioChannelForUser(member); serr != nil { + logger.Println(serr) + } + } + return +} + +func (zach *ZoneAudioChannelsHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { + reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error) + zach.reqChans = append(zach.reqChans, reqChan) + go func() { + for { + select { + case <-ctx.Done(): + done <- struct{}{} + return + case req := <-publisher: + if err := zach.handleZoneRequest(ctx, req); err != nil { + errCh <- err + } + } + } + }() + return +} + +func (zach *ZoneAudioChannelsHandler) signalCandidate(from string, to string, candidate *webrtc.ICECandidate) (err error) { + d, e := zach.sendDataChannelMessage(string(AUDIO_CHANNEL_WEBRTC_CANDIDATE), from, to, map[string]interface{}{ + "from": from, + "to": to, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }) + select { + case <-d: + case err = <-e: + } + return +} + +func (zach *ZoneAudioChannelsHandler) GetAudioChannels(userId string, channelsId ...interface{}) (err error) { + audioChannels := make([]*AudioChannel, 0, len(channelsId)) + for _, id := range channelsId { + if _, ok := id.(string); !ok { + err = fmt.Errorf("id of wrong type") + return + } + logger.Println("audioChannel from get audioChannels", id.(string)) + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[id.(string)]; ok { + logger.Println(zach.AudioChannels[id.(string)]) + audioChannels = append(audioChannels, zach.AudioChannels[id.(string)]) + } + return + }) + } + answer := &ZoneResponse{ + Type: "get_audio_channels_response", + From: "", + To: "", + Payload: map[string]interface{}{ + "audioChannels": audioChannels, + }, + } + bs, jsonErr := json.Marshal(answer) + if jsonErr != nil { + return jsonErr + } + err = atomicallyExecute(zach.DataChannelsFlag, func() (err error) { + if _, ok := zach.DataChannels[userId]; ok { + err = zach.DataChannels[userId].DataChannel.SendText(string(bs)) + if err != nil { + return + } + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) ListAudioChannels() (audioChannels []*AudioChannel, err error) { + audioChannels = make([]*AudioChannel, 0) + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + for _, audioChannel := range zach.AudioChannels { + audioChannels = append(audioChannels, audioChannel) + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) AddNewAudioChannel(channelName string, owner string, channelType string, members []interface{}) (err error) { + if _, ok := zach.AudioChannels[channelName]; ok { + err = fmt.Errorf("an audio channel with this name already exist") + return + } + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelName), 0700) + if mkdirErr != nil { + return mkdirErr + } + file, ferr := os.Create(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelName, "audioChannelConfig.json")) + if ferr != nil { + return ferr + } + m := make([]string, 0, len(members)) + for _, member := range members { + if _, ok := member.(string); ok { + m = append(m, member.(string)) + } + } + baseConfig := &AudioChannelConfig{ + ID: channelName, + Owner: owner, + ChannelType: channelType, + Members: m, + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return writeErr + } + err = file.Close() + if err != nil { + return err + } + var acc AudioChannelConfig + if err = json.Unmarshal(bs, &acc); err != nil { + return err + } + ac := NewAudioChannel(acc.ID, acc.Owner, acc.ChannelType, acc.Members, acc.CurrentMembersId, make(map[string]*AudioChannelMember)) + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + zach.AudioChannels[ac.ID] = ac + return + }) + newAudioChannelForMembers := func(members []string) (err error) { + for _, member := range members { + zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelName, + }) + done, e := zach.sendDataChannelMessage("get_audio_channels_response", "node", member, map[string]interface{}{ + "audioChannels": []*AudioChannel{zach.AudioChannels[channelName]}, + }) + select { + case <-done: + case err = <-e: + return + } + } + return + } + switch ac.ChannelType { + case BROADCAST: + fallthrough + case PUBLIC: + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + err = newAudioChannelForMembers(zach.ZoneMembersId) + return + }) + case PRIVATE: + err = newAudioChannelForMembers(ac.Members) + } + return +} + +func (zach *ZoneAudioChannelsHandler) DeleteAudioChannel(channelId string) (err error) { + if err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + defer delete(zach.AudioChannels, channelId) + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no corresponding audio channel") + return + } + removeKnownAudioChannels := func(members []string) (err error) { + for _, member := range members { + zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zach.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", member, map[string]interface{}{ + "channelId": channelId, + "userId": member, + }) + select { + case <-done: + continue + case err = <-e: + return + } + } + return + } + switch zach.AudioChannels[channelId].ChannelType { + case BROADCAST: + fallthrough + case PUBLIC: + err = removeKnownAudioChannels(zach.ZoneMembersId) + case PRIVATE: + err = removeKnownAudioChannels(zach.AudioChannels[channelId].Members) + } + return + }); err != nil { + return + } + if err = os.RemoveAll(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId)); err != nil { + return + } + return +} + +func (zach *ZoneAudioChannelsHandler) EditAudioChannelName(channelId string, newAudioChannelId string) (err error) { + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding audioChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json")) + if err != nil { + return + } + var audioChannelConfig AudioChannelConfig + if err = json.Unmarshal(bs, &audioChannelConfig); err != nil { + return + } + audioChannelConfig.ID = newAudioChannelId + bs, err = json.Marshal(&audioChannelConfig) + if err = os.Rename(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId), filepath.Join("data", "zones", zach.ZoneId, "audioChannels", newAudioChannelId)); err != nil { + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", newAudioChannelId, "audioChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + if e := f.Close(); e != nil { + logger.Println(e) + } + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + audioChannel := NewAudioChannel(audioChannelConfig.ID, audioChannelConfig.Owner, audioChannelConfig.ChannelType, audioChannelConfig.Members, make([]string, 0), make(map[string]*AudioChannelMember)) + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + defer delete(zach.AudioChannels, channelId) + zach.AudioChannels[newAudioChannelId] = audioChannel + updateKnownAudioChannels := func(members []string) (err error) { + for _, member := range members { + zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": newAudioChannelId, + }) + zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zach.sendDataChannelMessage(CHAT_NAME_EDITED, "node", member, map[string]interface{}{ + "formerAudioChannelId": channelId, + "newAudioChannelId": newAudioChannelId, + }) + select { + case <-done: + case channelErr := <-e: + logger.Println(channelErr) + } + } + return + } + switch audioChannel.ChannelType { + case BROADCAST: + fallthrough + case PUBLIC: + err = updateKnownAudioChannels(zach.ZoneMembersId) + case PRIVATE: + err = updateKnownAudioChannels(audioChannel.Members) + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) EditAudioChannelType(channelId string, channelType string) (err error) { + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding audioChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId)) + if err != nil { + return + } + var audioChannelConfig AudioChannelConfig + if err = json.Unmarshal(bs, &audioChannelConfig); err != nil { + return + } + audioChannelConfig.ChannelType = channelType + bs, err = json.Marshal(&audioChannelConfig) + f, err := os.OpenFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + audioChannel := NewAudioChannel(audioChannelConfig.ID, audioChannelConfig.Owner, audioChannelConfig.ChannelType, audioChannelConfig.Members, make([]string, 0), make(map[string]*AudioChannelMember)) + switch channelType { + case BROADCAST: + fallthrough + case PUBLIC: + var members = []string{} + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + members = append(members, zach.ZoneMembersId...) + return + }) + for _, member := range zach.ZoneMembersId { + if pubErr := zach.SetAudioChannelPublicForUser(channelId, member); pubErr != nil { + logger.Println(pubErr) + } + } + case PRIVATE: + var members = []string{} + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + members = append(members, zach.ZoneMembersId...) + return + }) + for _, member := range zach.ZoneMembersId { + if pubErr := zach.SetAudioChannelPrivateForUser(channelId, member); pubErr != nil { + logger.Println(pubErr) + } + } + } + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + zach.AudioChannels[channelId] = audioChannel + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) AddAudioChannelsMembers(channelId string, members []interface{}) (err error) { + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding audioChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json")) + if err != nil { + return + } + var audioChannelConfig AudioChannelConfig + if err = json.Unmarshal(bs, &audioChannelConfig); err != nil { + return + } + logger.Printf("%s - %s - %s -%v\n", audioChannelConfig.ID, audioChannelConfig.ChannelType, audioChannelConfig.Owner, members) + addedMembers := make([]string, 0) +memberLoop: + for _, audioChannelMember := range members { + logger.Println("entering broadcast loop") + if _, ok := audioChannelMember.(string); !ok { + continue + } + for _, member := range audioChannelConfig.Members { + if member == audioChannelMember { + continue memberLoop + } + } + audioChannelConfig.Members = append(audioChannelConfig.Members, audioChannelMember.(string)) + addedMembers = append(addedMembers, audioChannelMember.(string)) + logger.Println("sending zone request", ADD_KNOWN_AUDIO_CHANNEL) + zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": audioChannelMember, + "channelId": channelId, + }) + logger.Println("--------------done") + } + bs, err = json.Marshal(&audioChannelConfig) + f, err := os.OpenFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + ac := NewAudioChannel(audioChannelConfig.ID, audioChannelConfig.Owner, audioChannelConfig.ChannelType, audioChannelConfig.Members, make([]string, 0), make(map[string]*AudioChannelMember)) + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + zach.AudioChannels[channelId] = ac + return + }) +broadcastLoop: + for _, member := range ac.Members { + for _, m := range addedMembers { + if member == m { + done, e := zach.sendDataChannelMessage(ADDED_IN_CHAT, "node", member, map[string]interface{}{ + "audioChannel": ac, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + continue broadcastLoop + } + if _, ok := zach.DataChannels[member]; ok { + done, e := zach.sendDataChannelMessage(CHAT_MEMBER_ADDED, "node", member, map[string]interface{}{ + "userId": m, + "channelId": ac.ID, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + } + } + } + return +} + +func (zach *ZoneAudioChannelsHandler) RemoveAudioChannelMember(channelId string, channelMember string) (err error) { + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding audioChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json")) + if err != nil { + return + } + var audioChannelConfig AudioChannelConfig + if err = json.Unmarshal(bs, &audioChannelConfig); err != nil { + logger.Println(string(bs)) + logger.Println("json error right here") + return + } + if channelMember == audioChannelConfig.Owner { + err = fmt.Errorf("you cannot remove the owner from the audioChannel") + return + } + var index int + var contain bool + for i, member := range audioChannelConfig.Members { + if member == channelMember { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("member %s not in the channel %s", channelMember, channelId) + return + } + audioChannelConfig.Members = append(audioChannelConfig.Members[:index], audioChannelConfig.Members[index+1:]...) + bs, err = json.Marshal(&audioChannelConfig) + if err != nil { + logger.Println("json error there") + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return + } + logger.Println(string(bs)) + if _, err = f.Write(bs); err != nil { + return + } + var ac *AudioChannel + _ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + ac := NewAudioChannel(audioChannelConfig.ID, audioChannelConfig.Owner, audioChannelConfig.ChannelType, audioChannelConfig.Members, make([]string, 0), make(map[string]*AudioChannelMember)) + zach.AudioChannels[channelId] = ac + return + }) + zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": channelMember, + "channelId": channelId, + }) + done, e := zach.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", channelMember, map[string]interface{}{ + "channelId": channelId, + "userId": channelMember, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } +broadcastLoop: + for _, member := range ac.Members { + if member == channelMember { + continue broadcastLoop + } + done, e := zach.sendDataChannelMessage(CHAT_MEMBER_REMOVED, "node", member, map[string]interface{}{ + "userId": channelMember, + "channelId": ac.ID, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + } + return +} + +func (zach *ZoneAudioChannelsHandler) SetAudioChannelPrivateForUser(channelId string, member string) (err error) { + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if audioChannel, ok := zach.AudioChannels[channelId]; ok { + var contain bool + for _, m := range audioChannel.Members { + if m == member { + contain = true + break + } + } + if !contain { + zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zach.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", member, map[string]interface{}{ + "channelId": channelId, + "userId": member, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) SetAudioChannelPublicForUser(channelId string, member string) (err error) { + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if audioChannel, ok := zach.AudioChannels[channelId]; ok { + var contain bool + for _, m := range audioChannel.Members { + if m == member { + contain = true + break + } + } + if !contain { + zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zach.sendDataChannelMessage(ADDED_IN_CHAT, "node", member, map[string]interface{}{ + "audioChannel": audioChannel, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) SetAllPublicAudioChannelForUser(userId string) (err error) { + audioChannels, err := zach.ListAudioChannels() + if err != nil { + return + } + for _, audioChannel := range audioChannels { + if audioChannel.ChannelType == PUBLIC || audioChannel.ChannelType == BROADCAST { + if audioChannel.ID == LOBBY { + if err = zach.AddAudioChannelsMembers(audioChannel.ID, []interface{}{userId}); err != nil { + logger.Println(err) + } + continue + } + if err = zach.SetAudioChannelPublicForUser(audioChannel.ID, userId); err != nil { + continue + } + } + } + return +} + +func (zach *ZoneAudioChannelsHandler) RemoveUserFromAllAudioChannels(userId string) (err error) { + audioChannels, err := zach.ListAudioChannels() + if err != nil { + return + } + for _, audioChannel := range audioChannels { + if err = zach.RemoveAudioChannelMember(audioChannel.ID, userId); err != nil { + continue + } + } + return +} + +func (zach *ZoneAudioChannelsHandler) RemoveAllUserAudioChannels(userId string) (err error) { + audioChannels, err := zach.ListAudioChannels() + if err != nil { + return + } + for _, audioChannel := range audioChannels { + if audioChannel.Owner == userId { + if derr := zach.DeleteAudioChannel(audioChannel.ID); derr != nil { + logger.Println("**************", derr) + } + } + } + return +} + +func (zach *ZoneAudioChannelsHandler) JoinAudioChannel(channelId string, userId string, sdp string) (err error) { + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no audio channel with corresponding id") + return + } + audioChannel := zach.AudioChannels[channelId] + audioChannel.CurrentMembersId = append(audioChannel.CurrentMembersId, userId) + audioChannel.CurrentMembers[userId] = &AudioChannelMember{} + signalMembers := func(members []string) { + for _, u := range audioChannel.Members { + done, e := zach.sendDataChannelMessage(USER_JOINED_AUDIO_CHANNEL, "node", u, map[string]interface{}{ + "userId": userId, + "channelId": channelId, + }) + select { + case <-done: + case err := <-e: + logger.Println(err) + } + } + } + if audioChannel.ChannelType == PRIVATE { + signalMembers(audioChannel.Members) + } else { + signalMembers(zach.ZoneMembersId) + } + d, e := audioChannel.HandleOffer(context.Background(), channelId, userId, sdp, zach.HostId, zach.sendDataChannelMessage, zach.signalCandidate) + select { + case <-d: + case err = <-e: + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) LeaveAudioChannel(channelId string, userId string) (err error) { + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[channelId]; !ok { + err = fmt.Errorf("no audio channel with corresponding id") + return + } + audioChannel := zach.AudioChannels[channelId] + var index int + var contain bool + for i, v := range audioChannel.CurrentMembersId { + if v == userId { + index = i + contain = true + } + } + if !contain { + err = fmt.Errorf("this channel does not contain the provided user Id") + return + } + defer audioChannel.HandleLeavingMember(userId) + if len(audioChannel.CurrentMembersId) <= 1 { + audioChannel.CurrentMembersId = make([]string, 0) + } else { + audioChannel.CurrentMembersId = append(audioChannel.CurrentMembersId[:index], audioChannel.CurrentMembersId[index+1:]...) + } + delete(audioChannel.CurrentMembers, userId) + signalMembers := func(members []string) { + for _, u := range audioChannel.Members { + done, e := zach.sendDataChannelMessage(USER_LEFT_AUDIO_CHANNEL, "node", u, map[string]interface{}{ + "userId": userId, + "channelId": channelId, + }) + select { + case <-done: + case err := <-e: + logger.Println(err) + } + } + } + if audioChannel.ChannelType == PRIVATE { + signalMembers(audioChannel.Members) + } else { + signalMembers(zach.ZoneMembersId) + } + return + }) + return +} + +func (zach *ZoneAudioChannelsHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) { + switch req.ReqType { + case LEAVE_ZONE: + logger.Println("*-----------------handling leaving zone---------------*") + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + for _, ac := range zach.AudioChannels { + var contain bool + var id string + for _, member := range ac.CurrentMembersId { + if member == req.Payload["userId"].(string) { + id = member + contain = true + logger.Printf("*------------------id is %s--------------*\n", id) + break + } + } + if contain { + err = zach.LeaveAudioChannel(ac.ID, id) + break + } + } + case string(REMOVED_ZONE_AUTHORIZED_MEMBER): + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + var index int + for i, m := range zach.ZoneMembersId { + if m == req.Payload["userId"].(string) { + index = i + break + } + } + zach.ZoneMembersId = append(zach.ZoneMembersId[:index], zach.ZoneMembersId[index+1:]...) + if err = zach.RemoveAllUserAudioChannels(req.Payload["userId"].(string)); err != nil { + logger.Println("****______________", err) + } + err = zach.RemoveUserFromAllAudioChannels(req.Payload["userId"].(string)) + case string(NEW_AUTHORIZED_ZONE_MEMBER): + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + zach.ZoneMembersId = append(zach.ZoneMembersId, req.Payload["userId"].(string)) + err = zach.SetAllPublicAudioChannelForUser(req.Payload["userId"].(string)) + case JOIN_AUDIO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = zach.JoinAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string), req.Payload["sdp"].(string)) + case LEAVE_AUDIO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zach.LeaveAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + case ADD_AUDIO_CHANNEL_MEMBERS: + if err = verifyFieldsString(req.Payload, "channelId"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zach.AddAudioChannelsMembers(req.Payload["channelId"].(string), req.Payload["members"].([]interface{})) + case REMOVE_AUDIO_CHANNEL_MEMBER: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zach.RemoveAudioChannelMember(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + if err != nil { + logger.Println("an error occured in add known audioChannel", err) + return + } + case EDIT_AUDIO_CHANNEL_NAME: + if err = verifyFieldsString(req.Payload, "channelId", "newAudioChannelId"); err != nil { + return + } + if err = zach.EditAudioChannelName(req.Payload["channelId"].(string), req.Payload["newAudioChannelId"].(string)); err != nil { + return + } + case EDIT_AUDIO_CHANNEL_TYPE: + if err = verifyFieldsString(req.Payload, "channelId", "channelType"); err != nil { + return + } + if err = zach.EditAudioChannelType(req.Payload["channelId"].(string), req.Payload["channelType"].(string)); err != nil { + return + } + case DELETE_AUDIO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId"); err != nil { + return + } + err = zach.DeleteAudioChannel(req.Payload["channelId"].(string)) + case CREATE_AUDIO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "owner", "channelType"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zach.AddNewAudioChannel(req.Payload["channelId"].(string), req.Payload["owner"].(string), req.Payload["channelType"].(string), req.Payload["members"].([]interface{})) + case GET_AUDIO_CHANNELS: + if err = verifyFieldsSliceInterface(req.Payload, "channelsId"); err != nil { + return + } + err = zach.GetAudioChannels(req.From, req.Payload["channelsId"].([]interface{})...) + case string(AUDIO_CHANNEL_WEBRTC_COUNTER_OFFER): + logger.Println("handling audio channel counter offer") + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zach.AudioChannels[req.Payload["channelId"].(string)].HandleCounterOffer(ctx, req.Payload["userId"].(string), zach.sendDataChannelMessage) + return + }) + case string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zach.AudioChannels[req.Payload["channelId"].(string)].HandleRennegotiationOffer(req.Payload["userId"].(string), req.Payload["sdp"].(string), zach.sendDataChannelMessage) + return + }) + case string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zach.AudioChannels[req.Payload["channelId"].(string)].HandleRennegotiationAnswer(req.Payload["userId"].(string), req.Payload["sdp"].(string)) + return + }) + case string(AUDIO_CHANNEL_WEBRTC_CANDIDATE): + logger.Println("handling audio channel webrtc candidate") + logger.Println(req.Payload) + if err = verifyFieldsString(req.Payload, FROM, "candidate", "sdpMlineIndex", "sdpMid", "channelId", "userId"); err != nil { + return + } + logger.Println(req.Payload) + i, convErr := strconv.Atoi(req.Payload["sdpMlineIndex"].(string)) + if convErr != nil { + return convErr + } + sdpMlineIndex := uint16(i) + sdpMid := req.Payload["sdpMid"].(string) + logger.Println(sdpMid, sdpMlineIndex) + err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) { + if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zach.AudioChannels[req.Payload["channelId"].(string)].AddCandidate(&webrtc.ICECandidateInit{ + Candidate: req.Payload["candidate"].(string), + SDPMid: &sdpMid, + SDPMLineIndex: &sdpMlineIndex, + }, req.Payload["userId"].(string)) + return + }) + return + default: + logger.Println("audio channel handler still in process of implementation") + } + return +} diff --git a/zoneChatsDBHandler.go b/zoneChatsDBHandler.go new file mode 100644 index 0000000..47c58dc --- /dev/null +++ b/zoneChatsDBHandler.go @@ -0,0 +1,179 @@ +package localserver + +import ( + "encoding/binary" + "encoding/json" + "path/filepath" + + "github.com/dgraph-io/badger/v3" +) + +type ChatFile struct { + Path string `json:"path"` + Name string `json:"name"` + Size int `json:"size"` + UploadTime string `json:"uploadTime` +} + +type ChatMessage struct { + ID uint64 `json:"id"` + From string `json:"from"` + ResponseOf *ChatMessage `json:"ResponseOf"` + Tags []string `json:"tags"` + Content string `json:"content"` + Date string `json:"date"` + File *ChatFile `json:"file"` +} + +type ZoneChatDBHandler struct { + ChatID string + ZoneID string + PreviousId uint64 + db func(func(*badger.DB) (err error)) (err error) +} + +func NewZoneChatDBHandler(zoneId string, chatID string) (zoneChatDBHandler *ZoneChatDBHandler, err error) { + zoneChatDBHandler = &ZoneChatDBHandler{ + db: func(f func(*badger.DB) (err error)) (err error) { + db, err := badger.Open(badger.DefaultOptions(filepath.Join("data", "zones", zoneId, "chats", chatID)).WithLogger(dbLogger)) + if err != nil { + return + } + defer db.Close() + err = f(db) + return + }, + ChatID: chatID, + ZoneID: zoneId, + } + err = zoneChatDBHandler.db(func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) error { + opt := badger.DefaultIteratorOptions + it := txn.NewIterator(opt) + defer it.Close() + counter := 0 + for it.Rewind(); it.Valid(); it.Next() { + counter++ + } + logger.Println(counter) + zoneChatDBHandler.PreviousId = uint64(counter) + return nil + }) + return + }) + return +} + +func (zcdbh *ZoneChatDBHandler) updateDbCallbackFolder(newChatId string) { + zcdbh.db = func(f func(*badger.DB) (err error)) (err error) { + db, err := badger.Open(badger.DefaultOptions(filepath.Join("data", "zones", zcdbh.ZoneID, "chats", newChatId)).WithLogger(dbLogger)) + if err != nil { + return + } + defer db.Close() + err = f(db) + return + } +} + +func (zcdbh *ZoneChatDBHandler) AddNewChatMessage(chatMessage *ChatMessage) (err error) { + b := make([]byte, 100) + binary.LittleEndian.PutUint64(b, zcdbh.PreviousId+1) + chatMessage.ID = zcdbh.PreviousId + zcdbh.PreviousId++ + bs, err := json.Marshal(chatMessage) + if err != nil { + return + } + if err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + if updateErr := txn.Set(b, bs); updateErr != nil { + return updateErr + } + return nil + }) + return + }); err != nil { + return + } + return +} + +func (zcdbh *ZoneChatDBHandler) DeleteChatMessage(key uint64) (err error) { + return +} + +func (zcdbh *ZoneChatDBHandler) ListChatMessages(lastIndex int, limit int) (chatMessages []*ChatMessage, l int, err error) { + err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) (err error) { + opt := badger.DefaultIteratorOptions + opt.Reverse = true + it := txn.NewIterator(opt) + b := make([]byte, 100) + if lastIndex <= 0 { + binary.LittleEndian.PutUint64(b, uint64(zcdbh.PreviousId)) + } else { + binary.LittleEndian.PutUint64(b, uint64(lastIndex)) + } + x := 0 + defer it.Close() + defer func() { + if lastIndex > limit { + l = lastIndex - limit - 1 + } else if lastIndex == 0 { + if zcdbh.PreviousId > uint64(limit) { + l = int(zcdbh.PreviousId) - limit - 1 + } else { + l = 0 + } + } else { + l = 0 + } + }() + chatMessages = make([]*ChatMessage, 0) + for it.Seek(b); it.Valid(); it.Next() { + if x >= limit { + break + } + item := it.Item() + if err = item.Value(func(val []byte) (err error) { + var chatMessage *ChatMessage + if err = json.Unmarshal(val, &chatMessage); err != nil { + return err + } + chatMessages = append(chatMessages, chatMessage) + return + }); err != nil { + return err + } + x++ + } + return + }) + return + }) + return +} + +func (zcdbh *ZoneChatDBHandler) GetChatMessage(index uint64) (chatMessage *ChatMessage, err error) { + err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) (err error) { + b := make([]byte, 100) + binary.LittleEndian.PutUint64(b, uint64(zcdbh.PreviousId)) + item, err := txn.Get(b) + if err != nil { + return + } + err = item.Value(func(val []byte) error { + return json.Unmarshal(val, &chatMessage) + }) + return + }) + return + }) + return +} + +func (zcdbh *ZoneChatDBHandler) ModifyChatMessage(id string, chatMessage *ChatMessage) (err error) { + return +} diff --git a/zoneChatsHandler.go b/zoneChatsHandler.go new file mode 100644 index 0000000..1cbe982 --- /dev/null +++ b/zoneChatsHandler.go @@ -0,0 +1,1015 @@ +package localserver + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + "time" + + "github.com/dgraph-io/badger/v3" +) + +const ( + LIST_LATEST_CHATS = "list_latest_chats" + LIST_CHATS = "list_chats" + GET_CHATS = "get_chats" + GET_CHAT = "get_chat" + CREATE_CHAT = "create_chat" + DELETE_CHAT = "delete_chat" + EDIT_CHAT_TYPE = "edit_chat_type" + EDIT_CHAT_NAME = "edit_chat_name" + ADD_CHAT_MEMBERS = "add_chat_members" + REMOVE_CHAT_MEMBER = "remove_chat_member" + ADD_CHAT_MESSAGE = "add_chat_message" +) + +const ( + CHAT_NAME_EDITED = "chat_name_edited" + CHAT_MEMBER_LIST = "chat_messages_list" + CHAT_MEMBER_ADDED = "chat_member_added" + CHAT_MEMBER_REMOVED = "chat_member_removed" + REMOVED_FROM_CHAT = "removed_from_chat" + ADDED_IN_CHAT = "added_in_chat" + GET_CHATS_RESPONSE = "get_chats_response" + NEW_CHAT_MESSAGE = "new_chat_message" +) + +const ( + GENERAL = "general" + PUBLIC = "public" + PRIVATE = "private" + BROADCAST = "broadcast" +) + +type ChatConfig struct { + ChatId string `json:"chatId"` + ChatType string `json:"chatType"` + Owner string `json:"owner"` + Members []string `json:"members"` +} + +type Chat struct { + ChatId string `json:"chatId"` + ChatType string `json:"chatType"` + Owner string `json:"owner"` + Members []string `json:"members"` + DB *ZoneChatDBHandler `json:"-"` +} + +type ZoneChatsHandler struct { + ZoneId string + ZoneMembersId []string + DataChannels map[string]*DataChannel + Flag *uint32 + ChatFlag *uint32 + Chats map[string]*Chat + reqChans []chan<- *ZoneRequest + init bool +} + +func NewZoneChatsHandler(zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, flag *uint32) (zoneChatsHandler *ZoneChatsHandler, err error) { + var dirs []fs.DirEntry + dirs, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "chats")) + if err != nil { + if os.IsNotExist(err) { + logger.Printf("creating chat directory for zone %s...\n", zoneId) + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zoneId, "chats", "general"), 0700) + if mkdirErr != nil { + return nil, mkdirErr + } + file, ferr := os.Create(filepath.Join("data", "zones", zoneId, "chats", "general", "chatConfig.json")) + if ferr != nil { + return nil, ferr + } + baseConfig := ChatConfig{ + ChatId: GENERAL, + Owner: owner, + ChatType: "public", + Members: authorizedMembers, + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return nil, jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return nil, writeErr + } + _ = file.Close() + dirs, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "chats")) + if err != nil { + return nil, err + } + } else { + return + } + } + chats := make(map[string]*Chat) + for _, chat := range dirs { + zoneChatDBHandler, err := NewZoneChatDBHandler(zoneId, chat.Name()) + if err != nil { + return nil, err + } + var bs []byte + bs, err = os.ReadFile(filepath.Join("data", "zones", zoneId, "chats", chat.Name(), "chatConfig.json")) + if err != nil { + return nil, err + } + logger.Println(string(bs)) + var c Chat + if err = json.Unmarshal(bs, &c); err != nil { + return nil, err + } + logger.Println("chats data :", c.ChatId, c.ChatType, c.Owner, c.Members) + c.DB = zoneChatDBHandler + chats[c.ChatId] = &c + } + chatFlag := uint32(0) + zoneChatsHandler = &ZoneChatsHandler{ + ZoneId: zoneId, + ZoneMembersId: authorizedMembers, + DataChannels: dataChannels, + Flag: flag, + Chats: chats, + ChatFlag: &chatFlag, + init: false, + } + return +} + +func (zch *ZoneChatsHandler) sendZoneRequest(reqType string, from string, payload map[string]interface{}) { + go func() { + for _, rc := range zch.reqChans { + rc <- &ZoneRequest{ + ReqType: reqType, + From: from, + Payload: payload, + } + } + }() +} + +func (zch *ZoneChatsHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + if err := atomicallyExecute(zch.Flag, func() (err error) { + if _, ok := zch.DataChannels[to]; ok { + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: reqType, + From: from, + To: to, + Payload: payload, + }) + if jsonErr != nil { + return jsonErr + } + err = zch.DataChannels[to].DataChannel.SendText(string(bs)) + } + return + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + return done, errCh +} + +func (zch *ZoneChatsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { + for _, member := range authorizedMembers { + if serr := zch.SetAllPublicChatForUser(member); serr != nil { + logger.Println(serr) + } + } + zch.init = true + return +} + +func (zch *ZoneChatsHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { + reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error) + zch.reqChans = append(zch.reqChans, reqChan) + go func() { + for { + select { + case <-ctx.Done(): + done <- struct{}{} + return + case req := <-publisher: + if err := zch.handleZoneRequest(ctx, req); err != nil { + errCh <- err + } + } + } + }() + return +} + +func (zch *ZoneChatsHandler) GetChats(userId string, chatsId ...interface{}) (err error) { + chats := make([]*Chat, 0, len(chatsId)) + for _, id := range chatsId { + if _, ok := id.(string); !ok { + err = fmt.Errorf("id of wrong type") + return + } + logger.Println("chat from get chats", id.(string)) + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + if _, ok := zch.Chats[id.(string)]; ok { + logger.Println(zch.Chats[id.(string)]) + chats = append(chats, zch.Chats[id.(string)]) + } + return + }) + } + answer := &ZoneResponse{ + Type: GET_CHATS_RESPONSE, + From: "", + To: "", + Payload: map[string]interface{}{ + "chats": chats, + }, + } + bs, jsonErr := json.Marshal(answer) + if jsonErr != nil { + return jsonErr + } + _ = atomicallyExecute(zch.Flag, func() (err error) { + if _, ok := zch.DataChannels[userId]; ok { + err = zch.DataChannels[userId].DataChannel.SendText(string(bs)) + if err != nil { + return + } + } + return + }) + return +} + +func (zch *ZoneChatsHandler) ListChats() (chats []*Chat, err error) { + chats = make([]*Chat, 0) + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + for _, chat := range zch.Chats { + chats = append(chats, chat) + } + return + }) + return +} + +func (zch *ZoneChatsHandler) AddNewChat(chatName string, owner string, chatType string, members []interface{}) (err error) { + if _, ok := zch.Chats[chatName]; ok { + err = fmt.Errorf("a chat with this name already exist") + return + } + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zch.ZoneId, "chats", chatName), 0700) + if mkdirErr != nil { + return mkdirErr + } + file, ferr := os.Create(filepath.Join("data", "zones", zch.ZoneId, "chats", chatName, "chatConfig.json")) + if ferr != nil { + return ferr + } + m := make([]string, 0, len(members)) + for _, member := range members { + if _, ok := member.(string); ok { + m = append(m, member.(string)) + } + } + baseConfig := &ChatConfig{ + ChatId: chatName, + Owner: owner, + ChatType: chatType, + Members: m, + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return writeErr + } + err = file.Close() + if err != nil { + return err + } + zoneChatDBHandler, err := NewZoneChatDBHandler(zch.ZoneId, chatName) + if err != nil { + return err + } + var c Chat + if err = json.Unmarshal(bs, &c); err != nil { + return err + } + c.DB = zoneChatDBHandler + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + zch.Chats[c.ChatId] = &c + return + }) + newChatForMembers := func(members []string) (err error) { + for _, member := range members { + zch.sendZoneRequest(ADD_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": member, + "chatId": chatName, + }) + done, e := zch.sendDataChannelMessage(GET_CHATS_RESPONSE, "node", member, map[string]interface{}{ + "chats": []*Chat{zch.Chats[chatName]}, + }) + select { + case <-done: + case err = <-e: + return + } + } + return + } + switch c.ChatType { + case BROADCAST: + fallthrough + case PUBLIC: + err = atomicallyExecute(zch.ChatFlag, func() (err error) { + err = newChatForMembers(zch.ZoneMembersId) + return + }) + case PRIVATE: + err = newChatForMembers(c.Members) + } + return +} + +func (zch *ZoneChatsHandler) DeleteChat(chatId string) (err error) { + if err = atomicallyExecute(zch.ChatFlag, func() (err error) { + defer delete(zch.Chats, chatId) + if _, ok := zch.Chats[chatId]; !ok { + err = fmt.Errorf("no corresponding chat") + return + } + removeKnownChats := func(members []string) (err error) { + for _, member := range members { + zch.sendZoneRequest(REMOVE_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": member, + "chatId": chatId, + }) + done, e := zch.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", member, map[string]interface{}{ + "chatId": chatId, + "userId": member, + }) + select { + case <-done: + continue + case err = <-e: + return + } + } + return + } + switch zch.Chats[chatId].ChatType { + case BROADCAST: + fallthrough + case PUBLIC: + err = removeKnownChats(zch.ZoneMembersId) + case PRIVATE: + err = removeKnownChats(zch.Chats[chatId].Members) + } + return + }); err != nil { + return + } + if err = os.RemoveAll(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId)); err != nil { + return + } + return +} + +func (zch *ZoneChatsHandler) EditChatName(chatId string, newChatId string) (err error) { + if _, ok := zch.Chats[chatId]; !ok { + err = fmt.Errorf("no coresponding chat") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json")) + if err != nil { + return + } + var chatConfig ChatConfig + if err = json.Unmarshal(bs, &chatConfig); err != nil { + return + } + chatConfig.ChatId = newChatId + bs, err = json.Marshal(&chatConfig) + if err = os.Rename(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId), filepath.Join("data", "zones", zch.ZoneId, "chats", chatId)); err != nil { + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + chat := &Chat{ + Owner: chatConfig.Owner, + ChatId: chatConfig.ChatId, + ChatType: chatConfig.ChatType, + Members: chatConfig.Members, + DB: zch.Chats[chatId].DB, + } + chat.DB.updateDbCallbackFolder(newChatId) + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + defer delete(zch.Chats, chatId) + zch.Chats[newChatId] = chat + updateKnownChats := func(members []string) (err error) { + for _, member := range members { + zch.sendZoneRequest(ADD_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": member, + "chatId": newChatId, + }) + zch.sendZoneRequest(REMOVE_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": member, + "chatId": chatId, + }) + done, e := zch.sendDataChannelMessage(CHAT_NAME_EDITED, "node", member, map[string]interface{}{ + "formerChatId": chatId, + "newChatId": newChatId, + }) + select { + case <-done: + case channelErr := <-e: + logger.Println(channelErr) + } + } + return + } + switch chat.ChatType { + case BROADCAST: + fallthrough + case PUBLIC: + err = updateKnownChats(zch.ZoneMembersId) + case PRIVATE: + err = updateKnownChats(chat.Members) + } + return + }) + return +} + +func (zch *ZoneChatsHandler) EditChatType(chatId string, chatType string) (err error) { + if _, ok := zch.Chats[chatId]; !ok { + err = fmt.Errorf("no coresponding chat") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json")) + if err != nil { + return + } + var chatConfig ChatConfig + if err = json.Unmarshal(bs, &chatConfig); err != nil { + return + } + chatConfig.ChatType = chatType + bs, err = json.Marshal(&chatConfig) + f, err := os.OpenFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + chat := &Chat{ + Owner: chatConfig.Owner, + ChatId: chatConfig.ChatId, + ChatType: chatConfig.ChatType, + Members: chatConfig.Members, + DB: zch.Chats[chatId].DB, + } + switch chatType { + case BROADCAST: + fallthrough + case PUBLIC: + var members = []string{} + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + members = append(members, zch.ZoneMembersId...) + return + }) + for _, member := range zch.ZoneMembersId { + if pubErr := zch.SetChatPublicForUser(chatId, member); pubErr != nil { + logger.Println(pubErr) + } + } + case PRIVATE: + var members = []string{} + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + members = append(members, zch.ZoneMembersId...) + return + }) + for _, member := range zch.ZoneMembersId { + if pubErr := zch.SetChatPrivateForUser(chatId, member); pubErr != nil { + logger.Println(pubErr) + } + } + } + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + zch.Chats[chatId] = chat + return + }) + return +} + +func (zch *ZoneChatsHandler) AddChatMembers(chatId string, chatMembers []interface{}) (err error) { + if _, ok := zch.Chats[chatId]; !ok { + err = fmt.Errorf("no coresponding chat") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json")) + if err != nil { + return + } + var chatConfig ChatConfig + if err = json.Unmarshal(bs, &chatConfig); err != nil { + return + } + logger.Printf("%s - %s - %s -%v\n", chatConfig.ChatId, chatConfig.ChatType, chatConfig.Owner, chatMembers) + addedMembers := make([]string, 0) +memberLoop: + for _, chatMember := range chatMembers { + if _, ok := chatMember.(string); !ok { + continue + } + for _, member := range chatConfig.Members { + if member == chatMember { + zch.sendZoneRequest(ADD_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": chatMember, + "chatId": chatId, + }) + continue memberLoop + } + } + chatConfig.Members = append(chatConfig.Members, chatMember.(string)) + addedMembers = append(addedMembers, chatMember.(string)) + zch.sendZoneRequest(ADD_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": chatMember, + "chatId": chatId, + }) + } + bs, err = json.Marshal(&chatConfig) + f, err := os.OpenFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + chat := &Chat{ + Owner: chatConfig.Owner, + ChatId: chatConfig.ChatId, + ChatType: chatConfig.ChatType, + Members: chatConfig.Members, + DB: zch.Chats[chatId].DB, + } + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + zch.Chats[chatId] = chat + return + }) +broadcastLoop: + for _, member := range chat.Members { + for _, m := range addedMembers { + if member == m { + done, e := zch.sendDataChannelMessage(ADDED_IN_CHAT, "node", member, map[string]interface{}{ + "chat": chat, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + continue broadcastLoop + } + if _, ok := zch.DataChannels[member]; ok { + done, e := zch.sendDataChannelMessage(CHAT_MEMBER_ADDED, "node", member, map[string]interface{}{ + "userId": m, + "chatId": chat.ChatId, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + } + } + } + return +} + +func (zch *ZoneChatsHandler) RemoveChatMember(chatId string, chatMember string) (err error) { + if _, ok := zch.Chats[chatId]; !ok { + err = fmt.Errorf("no coresponding chat") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json")) + if err != nil { + return + } + var chatConfig ChatConfig + if err = json.Unmarshal(bs, &chatConfig); err != nil { + logger.Println(string(bs)) + logger.Println("json error right here") + return + } + if chatMember == chatConfig.Owner { + err = fmt.Errorf("you cannot remove the owner from the chat") + return + } + var index int + var contain bool + for i, member := range chatConfig.Members { + if member == chatMember { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("member %s not in the channel %s", chatMember, chatId) + return + } + chatConfig.Members = append(chatConfig.Members[:index], chatConfig.Members[index+1:]...) + bs, err = json.Marshal(&chatConfig) + if err != nil { + logger.Println("json error there") + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zch.ZoneId, "chats", chatId, "chatConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return + } + logger.Println(string(bs)) + if _, err = f.Write(bs); err != nil { + return + } + var chat *Chat + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + chat = &Chat{ + Owner: chatConfig.Owner, + ChatId: chatConfig.ChatId, + ChatType: chatConfig.ChatType, + Members: chatConfig.Members, + DB: zch.Chats[chatId].DB, + } + zch.Chats[chatId] = chat + return + }) + zch.sendZoneRequest(REMOVE_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": chatMember, + "chatId": chatId, + }) + done, e := zch.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", chatMember, map[string]interface{}{ + "chatId": chatId, + "userId": chatMember, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } +broadcastLoop: + for _, member := range chat.Members { + if member == chatMember { + continue broadcastLoop + } + done, e := zch.sendDataChannelMessage(CHAT_MEMBER_REMOVED, "node", member, map[string]interface{}{ + "userId": chatMember, + "chatId": chat.ChatId, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + } + return +} + +func (zch *ZoneChatsHandler) ListLatestChatMessages(userId string, chatId string, lastIndex float64, limit float64) (err error) { + var list []*ChatMessage + var i int + if err = atomicallyExecute(zch.ChatFlag, func() (err error) { + if _, ok := zch.Chats[chatId]; ok { + list, i, err = zch.Chats[chatId].DB.ListChatMessages(int(lastIndex), int(limit)) + } + return + }); err != nil { + return + } + done, e := zch.sendDataChannelMessage(CHAT_MEMBER_LIST, "node", userId, map[string]interface{}{ + "done": i == 0, + "lastIndex": i, + "chatId": chatId, + "chatMessages": list, + }) + select { + case <-done: + case err = <-e: + } + return +} + +func (zch *ZoneChatsHandler) AddChatMessage(userId string, chatId string, content string, chatResponseId uint64, file *ChatFile) (err error) { + chatMessage := &ChatMessage{ + Content: content, + From: userId, + ResponseOf: nil, + File: file, + Tags: make([]string, 0), + Date: time.Now().Format("Mon, 02 Jan 2006 15:04:05 MST"), + } + if err = atomicallyExecute(zch.ChatFlag, func() (err error) { + if _, ok := zch.Chats[chatId]; ok { + parentMessage, getErr := zch.Chats[chatId].DB.GetChatMessage(chatResponseId) + if err != nil { + if getErr != badger.ErrKeyNotFound { + return getErr + } + } + chatMessage.ResponseOf = parentMessage + if err = zch.Chats[chatId].DB.AddNewChatMessage(chatMessage); err != nil { + return + } + } else { + err = fmt.Errorf("no corresponding chats") + } + return + }); err != nil { + return + } + chatMessage.ID = zch.Chats[chatId].DB.PreviousId + _ = atomicallyExecute(zch.ChatFlag, func() (err error) { + chat := zch.Chats[chatId] + logger.Println(chat.ChatType) + switch chat.ChatType { + case BROADCAST: + fallthrough + case PUBLIC: + for _, v := range zch.ZoneMembersId { + done, e := zch.sendDataChannelMessage(NEW_CHAT_MESSAGE, "node", v, map[string]interface{}{ + "chatMessage": chatMessage, + "chatId": chatId, + }) + select { + case <-done: + case err = <-e: + } + } + case PRIVATE: + for _, v := range chat.Members { + done, e := zch.sendDataChannelMessage(NEW_CHAT_MESSAGE, "node", v, map[string]interface{}{ + "chatMessage": chatMessage, + "chatId": chatId, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zch *ZoneChatsHandler) SetChatPrivateForUser(chatId string, member string) (err error) { + err = atomicallyExecute(zch.ChatFlag, func() (err error) { + if chat, ok := zch.Chats[chatId]; ok { + var contain bool + for _, m := range chat.Members { + if m == member { + contain = true + break + } + } + if !contain { + zch.sendZoneRequest(REMOVE_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": member, + "chatId": chatId, + }) + done, e := zch.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", member, map[string]interface{}{ + "chatId": chatId, + "userId": member, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zch *ZoneChatsHandler) SetChatPublicForUser(chatId string, member string) (err error) { + err = atomicallyExecute(zch.ChatFlag, func() (err error) { + if chat, ok := zch.Chats[chatId]; ok { + var contain bool + for _, m := range chat.Members { + if m == member { + contain = true + break + } + } + if !contain { + zch.sendZoneRequest(ADD_KNOWN_CHAT, "node", map[string]interface{}{ + "userId": member, + "chatId": chatId, + }) + done, e := zch.sendDataChannelMessage(ADDED_IN_CHAT, "node", member, map[string]interface{}{ + "chat": chat, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zch *ZoneChatsHandler) SetAllPublicChatForUser(userId string) (err error) { + for _, chat := range zch.Chats { + logger.Println("--------------- public chat for all : ", chat) + if chat.ChatType == PUBLIC || chat.ChatType == BROADCAST { + if chat.ChatId == GENERAL { + if err = zch.AddChatMembers(chat.ChatId, []interface{}{userId}); err != nil { + logger.Println(err) + } + continue + } + if err = zch.SetChatPublicForUser(chat.ChatId, userId); err != nil { + continue + } + } + } + return +} + +func (zch *ZoneChatsHandler) RemoveUserFromAllChats(userId string) (err error) { + chats, err := zch.ListChats() + if err != nil { + logger.Println("an error occured -----------------*") + return + } + for _, chat := range chats { + if err = zch.RemoveChatMember(chat.ChatId, userId); err != nil { + logger.Println("-------------------*", err) + continue + } + } + return +} + +func (zch *ZoneChatsHandler) RemoveAllUserChat(userId string) (err error) { + chats, err := zch.ListChats() + if err != nil { + return + } + for _, chat := range chats { + logger.Println("$$$$$$$$$$ chat owner :", chat.Owner, userId) + if chat.Owner == userId { + if derr := zch.DeleteChat(chat.ChatId); derr != nil { + logger.Println("**************", derr) + } + logger.Println("deleted chat", chat.ChatId) + } + } + return +} + +func (zch *ZoneChatsHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) { + logger.Println("got request in zone chat handler", req) + switch req.ReqType { + case string(REMOVED_ZONE_AUTHORIZED_MEMBER): + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + var index int + for i, m := range zch.ZoneMembersId { + if m == req.Payload["userId"].(string) { + index = i + break + } + } + zch.ZoneMembersId = append(zch.ZoneMembersId[:index], zch.ZoneMembersId[index+1:]...) + if err = zch.RemoveAllUserChat(req.Payload["userId"].(string)); err != nil { + logger.Println("****______________", err) + } + err = zch.RemoveUserFromAllChats(req.Payload["userId"].(string)) + case string(NEW_AUTHORIZED_ZONE_MEMBER): + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + var contain bool + for _, m := range zch.ZoneMembersId { + if m == req.Payload["userId"].(string) { + contain = true + break + } + } + if !contain { + zch.ZoneMembersId = append(zch.ZoneMembersId, req.Payload["userId"].(string)) + } + err = zch.SetAllPublicChatForUser(req.Payload["userId"].(string)) + case ADD_CHAT_MEMBERS: + if err = verifyFieldsString(req.Payload, "chatId"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zch.AddChatMembers(req.Payload["chatId"].(string), req.Payload["members"].([]interface{})) + case REMOVE_CHAT_MEMBER: + if err = verifyFieldsString(req.Payload, "chatId", "userId"); err != nil { + return + } + err = zch.RemoveChatMember(req.Payload["chatId"].(string), req.Payload["userId"].(string)) + if err != nil { + logger.Println("an error occured in add known chat", err) + return + } + case EDIT_CHAT_NAME: + if err = verifyFieldsString(req.Payload, "chatId", "newChatId"); err != nil { + return + } + if err = zch.EditChatName(req.Payload["chatId"].(string), req.Payload["newChatId"].(string)); err != nil { + return + } + case EDIT_CHAT_TYPE: + if err = verifyFieldsString(req.Payload, "chatId", "chatType"); err != nil { + return + } + if err = zch.EditChatType(req.Payload["chatId"].(string), req.Payload["chatType"].(string)); err != nil { + return + } + case DELETE_CHAT: + if err = verifyFieldsString(req.Payload, "chatId"); err != nil { + return + } + err = zch.DeleteChat(req.Payload["chatId"].(string)) + case CREATE_CHAT: + if err = verifyFieldsString(req.Payload, "chatId", "owner", "chatType"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zch.AddNewChat(req.Payload["chatId"].(string), req.Payload["owner"].(string), req.Payload["chatType"].(string), req.Payload["members"].([]interface{})) + case GET_CHATS: + if err = verifyFieldsSliceInterface(req.Payload, "chatsId"); err != nil { + return + } + err = zch.GetChats(req.From, req.Payload["chatsId"].([]interface{})...) + case LIST_LATEST_CHATS: + if err = verifyFieldsString(req.Payload, "chatId"); err != nil { + return + } + if err = verifyFieldsFloat64(req.Payload, "lastIndex", "limit"); err != nil { + return + } + err = zch.ListLatestChatMessages(req.From, req.Payload["chatId"].(string), req.Payload["lastIndex"].(float64), req.Payload["limit"].(float64)) + return + case ADD_CHAT_MESSAGE: + logger.Println("got request in zone chat handler", req) + if err = verifyFieldsString(req.Payload, "chatId", "content"); err != nil { + return + } + if err = verifyFieldsFloat64(req.Payload, "parentChatId"); err != nil { + return + } + var file *ChatFile = nil + if _,ok := req.Payload["file"]; ok { + bs, jsonErr := json.Marshal(req.Payload["file"]) + if jsonErr != nil { + return jsonErr + } + var f ChatFile + if err = json.Unmarshal(bs, &f); err != nil { + err = fmt.Errorf("the file payload dont match ChatFile struct pattern : %v", err) + return + } + file = &f + } + err = zch.AddChatMessage(req.From, req.Payload["chatId"].(string), req.Payload["content"].(string),uint64(req.Payload["parentChatId"].(float64)),file) + } + return +} diff --git a/zoneFSDBhandler.go b/zoneFSDBhandler.go new file mode 100644 index 0000000..bc092c3 --- /dev/null +++ b/zoneFSDBhandler.go @@ -0,0 +1,169 @@ +package localserver + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/dgraph-io/badger/v3" +) + +type ZoneFilesDBHandler struct { + CurrentLocation string + ZoneID string + rootPath string + db func(string, func(*badger.DB) (err error)) (err error) +} + +func NewZoneFilesDBHandler(zoneId string) (zoneFilesDBHandler *ZoneFilesDBHandler, err error) { + zoneFilesDBHandler = &ZoneFilesDBHandler{ + db: func(path string, f func(*badger.DB) (err error)) (err error) { + root := filepath.Join("data", "zones", zoneId, "fs", path) + db, err := badger.Open(badger.DefaultOptions(root).WithLogger(dbLogger)) + if err != nil { + return + } + defer db.Close() + err = f(db) + return + }, + ZoneID: zoneId, + } + zoneFilesDBHandler.rootPath = filepath.Join("data", "zones", zoneId, "fs") + return +} + +func (zcdbh *ZoneFilesDBHandler) AddNewFSEntity(path string, fsEntity *ZoneFSEntity) (err error) { + bs, err := json.Marshal(fsEntity) + if err != nil { + return + } + err = zcdbh.db(path, func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + if updateErr := txn.Set([]byte(fsEntity.Name), bs); updateErr != nil { + return updateErr + } + if fsEntity.Folder { + err = os.Mkdir(filepath.Join("data", "zones", zcdbh.ZoneID, "fs", path, fsEntity.Name), 0700) + } + return + }) + return + }) + return +} + +func (zcdbh *ZoneFilesDBHandler) DeleteFolder(path, name string) (err error) { + if err = zcdbh.db(path, func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + err = txn.Delete([]byte(name)) + return + }) + return + }); err != nil { + return + } + err = os.RemoveAll(filepath.Join(zcdbh.rootPath, path, name)) + return +} + +func (zcdbh *ZoneFilesDBHandler) DeleteFile(path, name string) (err error) { + if err = zcdbh.db(path, func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + err = txn.Delete([]byte(name)) + return + }) + return + }); err != nil { + return + } + err = os.Remove(filepath.Join(zcdbh.rootPath, path, "__files__", name)) + return +} + +func (zcdbh *ZoneFilesDBHandler) ListZoneFSEntity(path string, userId string, lastIndex int, limit int) (fsEntities []*ZoneFSEntity, l int, err error) { + err = zcdbh.db(path, func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) (err error) { + opt := badger.DefaultIteratorOptions + opt.Reverse = true + it := txn.NewIterator(opt) + defer it.Close() + fsEntities = make([]*ZoneFSEntity, 0) + splittedPath := filepath.SplitList(path) + var parent *ZoneFSEntity + if len(splittedPath) >= 2 { + parent, err = zcdbh.GetFSEntity(filepath.Dir(path), filepath.Base(path)) + } else if len(splittedPath) > 0 { + parent = nil + } + if err != nil { + return + } + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + if err = item.Value(func(val []byte) (err error) { + var fsEntity *ZoneFSEntity + if err = json.Unmarshal(val, &fsEntity); err != nil { + return err + } + logger.Println("------------------", fsEntity.Name) + if _, ok := fsEntity.Members[userId]; ok || fsEntity.Type == "public" { + fsEntities = append(fsEntities, fsEntity) + } else if parent != nil { + if _, ok := parent.Members[userId]; ok && fsEntity.Type != "private" { + if parent.Members[userId].Read { + fsEntities = append(fsEntities, fsEntity) + } + } + } + return + }); err != nil { + return err + } + } + return + }) + return + }) + return +} + +func (zcdbh *ZoneFilesDBHandler) GetFSEntity(path string, folderName string) (folder *ZoneFSEntity, err error) { + err = zcdbh.db(path, func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) (err error) { + item, err := txn.Get([]byte(folderName)) + if err != nil { + return + } + err = item.Value(func(val []byte) (err error) { + err = json.Unmarshal(val, &folder) + return + }) + return + }) + return + }) + return +} + +func (zcdbh *ZoneFilesDBHandler) SetFSEntity(path string, oldName string, fsEntity *ZoneFSEntity) (err error) { + bs, err := json.Marshal(fsEntity) + if err != nil { + return + } + if err = zcdbh.db(path, func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + if dErr := txn.Delete([]byte(oldName)); err != nil { + logger.Println("error from here:", dErr) + } + if updateErr := txn.Set([]byte(fsEntity.Name), bs); updateErr != nil { + return updateErr + } + return + }) + return + }); err != nil { + return + } + return +} diff --git a/zoneFSHandler.go b/zoneFSHandler.go new file mode 100644 index 0000000..f4e6737 --- /dev/null +++ b/zoneFSHandler.go @@ -0,0 +1,1114 @@ +package localserver + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/pion/webrtc/v3" +) + +const ( + CREATE_FOLDER = "create_folder" + COPY_FOLDER = "copy_folder" + COPY_FILE = "copy_file" + CUT_FILE = "cut_file" + CUT_FOLDER = "cut_folder" + RENAME_FOLDER = "rename_folder" + RENAME_FILE = "rename_file" + DELETE_FOLDER = "delete_folder" + DELETE_FILE = "delete_file" + UPDATE_PERMISSIONS = "update_permissions" + GET_FS_ENTITIES = "get_fs_entities" + ADD_FS_ENTITY_MEMBERS = "add_fs_entity_members" + REMOVE_FS_ENTITY_MEMBERS = "remove_fs_entity_members" + ZONE_UPLOAD_FILE = "zone_upload_file" + ZONE_DOWNLOAD_FILE = "zone_download_file" +) + +const ( + CREATE_FOLDER_DONE = "create_folder_done" + CREATE_FILE_DONE = "create_file_done" + COPY_FOLDER_DONE = "copy_folder_done" + COPY_FILE_DONE = "copy_file_done" + CUT_FILE_DONE = "cut_file_done" + CUT_FOLDER_DONE = "cut_folder_done" + RENAME_FOLDER_DONE = "rename_folder_done" + RENAME_FILE_DONE = "rename_file_done" + DELETE_FOLDER_DONE = "delete_folder_done" + DELETE_FILE_DONE = "delete_file_done" + UPDATE_PERMISSIONS_DONE = "update_permissions_done" + GET_FS_ENTITIES_DONE = "get_fs_entities_done" + ADD_FS_ENTITY_MEMBERS_DONE = "add_fs_entity_members_done" + REMOVE_FS_ENTITY_MEMBERS_DONE = "remove_fs_entity_members_done" + ZONE_DOWNLOAD_FILE_DONE = "zone_download_file_done" + ZONE_UPLOAD_FILE_DONE = "zone_upload_file_done" +) + +const ( + PARENT = "parent" +) + +// ZoneFileHandler : Handle all interaction of the node zone related to the file system +type ZoneFileHandler struct { + ZoneId string + HostId string + ZoneMembersId []string + DataChannels map[string]*DataChannel + Flag *uint32 + FSInstanceFlag *uint32 + Publishers []<-chan *ZoneRequest + FSInstance *FSInstance + reqChans []chan<- *ZoneRequest + DB *ZoneFilesDBHandler +} + +// FSEntityAccessRights : representation of the different rights users can have on a ZoneFSEntity +type FSEntityAccessRights struct { + // Read : this field for folder determine if the member is allowed to see this folder only because he is allowed to read a child folder or because he actually is authorized to read this folder (hard to explain) + Read bool `json:"read"` + // Write : this field for folder determine if the user can upload file in this folder and for file if the user can edit the file (future feature). + Write bool `json:"write"` + // Download : this field for folder determine if the user can download a zip of the folder (future feature) and for file if he can download the file. + Download bool `json:"download"` + // Rename : determine the ability of the user to change the name of the fsEntity + Rename bool `json:"rename"` +} + +// ZoneFSEntity : representation of a FSEntity (file or folder) for the zone fs feature +type ZoneFSEntity struct { + Name string `json:"name"` + Owner string `json:"owner"` + Type string `json:"type"` + Folder bool `json:"folder"` + ModTime string `json:"modTime"` + CreationTime string `json:"creationTime"` + Size uint64 `json:"size"` + Members map[string]*FSEntityAccessRights `json:"members"` +} + +// NewZoneFileHandler: factory function to create a zone file handler the name of the parameters are explicit, this is the only way to safely create a ZoneFSEntity +func NewZoneFileHandler(hostId string, zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, flag *uint32) (zoneFileHandler *ZoneFileHandler, err error) { + db, err := NewZoneFilesDBHandler(zoneId) + if err != nil { + return + } + initFsDirectory := func(u string) (err error) { + logger.Printf("creating fs directory for user %s...\n", u) + baseConfig := ZoneFSEntity{ + Name: u, + Owner: u, + Type: "private", + Folder: true, + Members: map[string]*FSEntityAccessRights{ + u: { + Read: true, + Write: true, + Download: true, + Rename: true, + }, + }, + } + err = db.AddNewFSEntity("", &baseConfig) + return + } + if _, dirErr := os.ReadDir(filepath.Join("data", "zones", zoneId, "fs")); os.IsNotExist(dirErr) { + dirErr := os.MkdirAll(filepath.Join("data", "zones", zoneId, "fs"), 0700) + if dirErr != nil { + return + } + } + for _, user := range authorizedMembers { + if _, dirErr := os.ReadDir(filepath.Join("data", "zones", zoneId, "fs", user)); os.IsNotExist(dirErr) { + if e := initFsDirectory(user); e != nil { + logger.Println(e) + } + } + } + fSInstanceFlag := uint32(0) + fsInstance := NewFSInstance(zoneId, owner, authorizedMembers) + zoneFileHandler = &ZoneFileHandler{ + ZoneId: zoneId, + ZoneMembersId: authorizedMembers, + DataChannels: dataChannels, + FSInstanceFlag: &fSInstanceFlag, + FSInstance: fsInstance, + DB: db, + Flag: flag, + } + return +} + +// func (zch *ZoneFileHandler) sendZoneRequest(reqType string, from string, payload map[string]interface{}) { +// go func() { +// for _, rc := range zch.reqChans { +// rc <- &ZoneRequest{ +// ReqType: reqType, +// From: from, +// Payload: payload, +// } +// } +// }() +// } + +func (zch *ZoneFileHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { + for _, member := range authorizedMembers { + if serr := zch.SetPublicRootFolders(member); serr != nil { + logger.Println(serr) + } + } + return +} + +func (zch *ZoneFileHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + if err := atomicallyExecute(zch.Flag, func() (err error) { + if _, ok := zch.DataChannels[to]; ok { + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: reqType, + From: from, + To: to, + Payload: payload, + }) + if jsonErr != nil { + return jsonErr + } + err = zch.DataChannels[to].DataChannel.SendText(string(bs)) + } + return + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + return done, errCh +} + +func (zch *ZoneFileHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { + reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error) + zch.reqChans = append(zch.reqChans, reqChan) + go func() { + for { + select { + case <-ctx.Done(): + done <- struct{}{} + return + case req := <-publisher: + if err := zch.handleZoneRequest(ctx, req); err != nil { + errCh <- err + } + } + } + }() + return +} + +func (zfh *ZoneFileHandler) signalCandidate(from string, to string, candidate *webrtc.ICECandidate) (err error) { + d, e := zfh.sendDataChannelMessage(string(ZONE_FS_WEBRTC_CANDIDATE), from, to, map[string]interface{}{ + "from": from, + "to": to, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }) + select { + case <-d: + case err = <-e: + } + return +} + +// GetFSEntities: Return a list of ZoneFSEntity to the user with the provided userId +func (zfh *ZoneFileHandler) GetFSEntities(userId string, section string, basePath string, limit int, lastIndex int, backward bool) (err error) { + var fsEntities []*ZoneFSEntity + var currentPath string = basePath + var listFSEntities func(string, string, int, int, bool) + listFSEntities = func(userId, path string, limit, lastIndex int, backward bool) { + fsEntities, _, err = zfh.DB.ListZoneFSEntity(path, userId, lastIndex, limit) + if err != nil { + return + } + currentPath = path + if len(fsEntities) == 1 { + if rights, ok := fsEntities[0].Members[userId]; ok { + if !rights.Read { + if !backward { + listFSEntities(userId, filepath.Join(path, fsEntities[0].Name), 100, 0, backward) + } else { + splittedPath := filepath.SplitList(path) + if len(splittedPath) > 1 { + listFSEntities(userId, filepath.Dir(path), 100, 0, backward) + } else { + listFSEntities(userId, "", 100, 0, backward) + } + } + } + } + } + } + listFSEntities(userId, basePath, 100, 0, backward) + var parent *ZoneFSEntity + if len(filepath.SplitList(basePath)) > 0 { + parent, err = zfh.DB.GetFSEntity(filepath.Dir(basePath), filepath.Base(basePath)) + if err != nil { + return + } + logger.Println("------------------ parent is --------------") + logger.Println(parent) + } + done, errCh := zfh.sendDataChannelMessage(GET_FS_ENTITIES, "node", userId, map[string]interface{}{ + "fsEntities": fsEntities, + "parent": parent, + "currentPath": currentPath, + "section": section, + }) + select { + case <-done: + case err = <-errCh: + } + return +} + +// CreateFolder: create a ZoneFSEntity folder mapped to a real folder on the disk +func (zfh *ZoneFileHandler) CreateFolder(path string, config *ZoneFSEntity) (err error) { + if config.Type == PARENT { + parentPath := filepath.Dir(path) + parentFolderName := filepath.Base(path) + logger.Println(parentPath, "-------", parentFolderName) + var folder *ZoneFSEntity + if len(parentPath) > 1 { + folder, err = zfh.DB.GetFSEntity(parentPath, parentFolderName) + } else { + folder, err = zfh.DB.GetFSEntity("", parentFolderName) + } + if err != nil { + return + } + config.Members = make(map[string]*FSEntityAccessRights) + for k, v := range folder.Members { + config.Members[k] = &FSEntityAccessRights{ + Read: v.Read, + Write: v.Write, + Download: v.Download, + Rename: v.Rename, + } + } + } + if err = zfh.DB.AddNewFSEntity(path, config); err != nil { + return + } + for member := range config.Members { + done, errCh := zfh.sendDataChannelMessage(CREATE_FOLDER_DONE, "node", member, map[string]interface{}{ + "path": path, + "fsEntity": config, + }) + select { + case <-done: + case err = <-errCh: + logger.Println(err) + } + } + return +} + +// CreateFile: create a ZoneFSEntity file mapped to a real file on the disk +func (zfh *ZoneFileHandler) CreateFile(path, fileName string, config *ZoneFSEntity) (err error) { + splittedPath := strings.Split(path, "/") + if config.Type == PARENT { + logger.Println(strings.Join(splittedPath[:len(splittedPath)-1], "/"), "-------", splittedPath[len(splittedPath)-1]) + var folder *ZoneFSEntity + if len(splittedPath) > 1 { + folder, err = zfh.DB.GetFSEntity(strings.Join(splittedPath[:len(splittedPath)-1], "/"), splittedPath[len(splittedPath)-1]) + } else { + folder, err = zfh.DB.GetFSEntity("", splittedPath[0]) + } + if err != nil { + return + } + config.Members = make(map[string]*FSEntityAccessRights) + for k, v := range folder.Members { + config.Members[k] = &FSEntityAccessRights{ + Read: v.Read, + Write: v.Write, + Download: v.Download, + Rename: v.Rename, + } + } + } + if err = zfh.DB.AddNewFSEntity(path, config); err != nil { + return + } + for member := range config.Members { + done, errCh := zfh.sendDataChannelMessage(CREATE_FILE_DONE, "node", member, map[string]interface{}{ + "path": path, + "fsEntity": config, + }) + select { + case <-done: + case err = <-errCh: + logger.Println(err) + } + } + return +} + +// EditFolderType: change the type of the folder and modify rights of the users according to the new ZoneFSEntity type provided +func (zfh *ZoneFileHandler) EditFolderType(userId string, path string, folderName string, newFolderType string) (err error) { + folder, err := zfh.DB.GetFSEntity(path, folderName) + if err != nil { + return + } + folder.Type = newFolderType + if err = zfh.DB.SetFSEntity(path, folderName, folder); err != nil { + return + } + done, errCh := zfh.sendDataChannelMessage("", "node", userId, map[string]interface{}{ + "newFolder": folder, + }) + select { + case <-done: + case err = <-errCh: + } + return +} + +func (zfh *ZoneFileHandler) EditFileType(path string, fileName string, newFileType string) (err error) { + return +} + +// AddFolderMember: add a list of members to the map of users allowed to access the folder with the provided folderName and set the rights +func (zfh *ZoneFileHandler) AddFolderMember(userId string, path string, folderName string, members []interface{}, read bool, write bool, download bool, rename bool) (err error) { + var addMembersDb func(string, string, bool, bool, bool, bool, bool) (folder *ZoneFSEntity, err error) + addMembersDb = func(path string, folderName string, read bool, write bool, download bool, rename bool, init bool) (folder *ZoneFSEntity, err error) { + folder, err = zfh.DB.GetFSEntity(path, folderName) + if err != nil { + return + } + childrens := make([]*ZoneFSEntity, 0) + if init { + childrens, _, err = zfh.DB.ListZoneFSEntity(filepath.Join(path, folderName), userId, 0, 100) + if err != nil { + logger.Println("cannot get siblings folders:", err) + return + } + } + for _, newMember := range members { + if m, ok := newMember.(string); ok { + if _, ok := folder.Members[m]; !ok { + folder.Members[m] = &FSEntityAccessRights{ + Read: read, + Write: write, + Download: download, + Rename: rename, + } + } + if init { + go func() { + for _, children := range childrens { + logger.Println("------:", filepath.Join(path, folderName), "-", children.Name) + if children.Type != "private" { + if _, err = addMembersDb(filepath.Join(path, folderName), children.Name, read, write, download, rename, true); err != nil { + logger.Println("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*") + logger.Println("error from ttttthheeere:", err) + logger.Println("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*") + } + } + } + }() + } + } + } + err = zfh.DB.SetFSEntity(path, folderName, folder) + return + } + folder, err := addMembersDb(path, folderName, read, write, download, rename, true) + if err != nil { + return + } + splittedPath := strings.Split(path, "/") + for i := 1; i <= len(splittedPath); i++ { + logger.Println(i) + var addErr error + if i == len(splittedPath) { + _, addErr = addMembersDb("", splittedPath[0], false, false, false, false, false) + + } else { + _, addErr = addMembersDb(strings.Join(splittedPath[:len(splittedPath)-i], "/"), splittedPath[len(splittedPath)-i], false, false, false, false, false) + + } + if addErr != nil { + return addErr + } + } + for member := range folder.Members { + done, errCh := zfh.sendDataChannelMessage(ADD_FS_ENTITY_MEMBERS_DONE, "node", member, map[string]interface{}{ + "path": path, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) AddFileMember(path string, filename string, members []interface{}, read bool, write bool, download bool, rename bool) (err error) { + return +} + +func (zfh *ZoneFileHandler) RemoveFolderMember(userId string, path string, folderName string, members []interface{}) (err error) { + membersM := make(map[string]bool) + staticMembersM := make(map[string]bool) + for _, member := range members { + if m, ok := member.(string); ok { + membersM[m] = true + staticMembersM[m] = true + } + } + var removeChildMemberDb func(string, string) (folder *ZoneFSEntity, err error) + removeChildMemberDb = func(path, folderName string) (folder *ZoneFSEntity, err error) { + childrens, _, err := zfh.DB.ListZoneFSEntity(filepath.Join(path, folderName), userId, 0, 100) + if err != nil { + logger.Println("cannot get siblings folders:", err) + return + } + go func() { + for _, children := range childrens { + for member := range staticMembersM { + if _, ok := children.Members[member]; ok && member != children.Owner { + delete(children.Members, member) + } + } + logger.Println("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*") + logger.Println(filepath.Join(path, folderName), ":", children.Name) + logger.Println("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*") + _, _ = removeChildMemberDb(filepath.Join(path, folderName), children.Name) + err = zfh.DB.SetFSEntity(filepath.Join(path, folderName), children.Name, children) + } + }() + return + } + removeMemberDb := func(path, folderName string, init bool) (folder *ZoneFSEntity, err error) { + folder, err = zfh.DB.GetFSEntity(path, folderName) + if err != nil { + return + } + siblings, _, err := zfh.DB.ListZoneFSEntity(path, userId, 0, 100) + if err != nil { + logger.Println("cannot get siblings folders:", err) + return + } + if init { + _, _ = removeChildMemberDb(path, folderName) + } + for member := range membersM { + if _, ok := folder.Members[member]; ok && member != folder.Owner { + if !folder.Members[member].Read || init { + delete(folder.Members, member) + } else { + delete(membersM, member) + } + for _, sibling := range siblings { + if sibling.Name != folder.Name { + if _, ok := sibling.Members[member]; ok { + delete(membersM, member) + } + } + } + } + } + err = zfh.DB.SetFSEntity(path, folderName, folder) + return + } + folder, err := removeMemberDb(path, folderName, true) + if err != nil { + return + } + splittedPath := strings.Split(path, "/") + for i := 1; i <= len(splittedPath) && len(membersM) > 0; i++ { + logger.Println(i) + var addErr error + if i == len(splittedPath) { + _, addErr = removeMemberDb("", splittedPath[0], false) + + } else { + _, addErr = removeMemberDb(strings.Join(splittedPath[:len(splittedPath)-i], "/"), splittedPath[len(splittedPath)-i], false) + + } + if addErr != nil { + return addErr + } + } + for member := range folder.Members { + done, errCh := zfh.sendDataChannelMessage(REMOVE_FS_ENTITY_MEMBERS_DONE, "node", member, map[string]interface{}{ + "path": filepath.Join(path, folderName), + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) RemoveFileMember(path, filename, members, read, write, download, rename bool) (err error) { + return +} + +func (zfh *ZoneFileHandler) RenameFolder(path, currentName, newName string) (err error) { + fsEntity, err := zfh.DB.GetFSEntity(path, currentName) + if err != nil { + return + } + fsEntity.Name = newName + if err = os.Rename(filepath.Join(filepath.Join("data", "zones", zfh.ZoneId, "fs"), path, currentName), filepath.Join(filepath.Join("data", "zones", zfh.ZoneId, "fs"), path, newName)); err != nil { + return + } + if err = zfh.DB.SetFSEntity(path, currentName, fsEntity); err != nil { + return + } + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(RENAME_FOLDER_DONE, "node", member, map[string]interface{}{ + "path": filepath.Join(path, newName), + "folderId": newName, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) RenameFile(path, currentName, newName string) (err error) { + fsEntity, err := zfh.DB.GetFSEntity(path, currentName) + if err != nil { + return + } + fsEntity.Name = newName + if err = os.Rename(filepath.Join(filepath.Join("data", "zones", zfh.ZoneId, "fs"), path, "__files__", currentName), filepath.Join(filepath.Join("data", "zones", zfh.ZoneId, "fs"), path, "__files__", newName)); err != nil { + return + } + if err = zfh.DB.SetFSEntity(path, currentName, fsEntity); err != nil { + return + } + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(RENAME_FILE_DONE, "node", member, map[string]interface{}{ + "path": filepath.Join(path, newName), + "folderId": newName, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) UpdatePermissions(path, name, userId string, newPermission *FSEntityAccessRights) (err error) { + var updatePermission func(string, string) error + updatePermission = func(path, name string) (err error) { + fsEntity, err := zfh.DB.GetFSEntity(path, name) + if err != nil { + return + } + if fsEntity.Folder { + splittedPath := append(filepath.SplitList(path), name) + childrens, _, err := zfh.DB.ListZoneFSEntity(filepath.Join(splittedPath...), userId, 0, 100) + if err != nil { + return err + } + go func() { + for _, children := range childrens { + if children.Type == PARENT { + if err = updatePermission(filepath.Join(splittedPath...), children.Name); err != nil { + return + } + } + } + }() + } + fsEntity.Members[userId] = newPermission + if err = zfh.DB.SetFSEntity(path, name, fsEntity); err != nil { + return + } + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(UPDATE_PERMISSIONS_DONE, "node", member, map[string]interface{}{ + "path": filepath.Join(path, name), + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return + } + err = updatePermission(path, name) + return +} + +func (zfh *ZoneFileHandler) DeleteFolder(path, name string) (err error) { + fsEntity, err := zfh.DB.GetFSEntity(path, name) + if err != nil { + return + } + if err = zfh.DB.DeleteFolder(path, name); err != nil { + return + } + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(DELETE_FOLDER_DONE, "node", member, map[string]interface{}{ + "path": filepath.Join(path, name), + "folderId": name, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) DeleteFile(path, name string) (err error) { + fsEntity, err := zfh.DB.GetFSEntity(path, name) + if err != nil { + return + } + if err = zfh.DB.DeleteFile(path, name); err != nil { + return + } + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(DELETE_FOLDER_DONE, "node", member, map[string]interface{}{ + "path": filepath.Join(path, name), + "folderId": name, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) CopyFile(src, dst string, init bool, fsEntity *ZoneFSEntity) (err error) { + var srcfd *os.File + var dstfd *os.File + var srcinfo os.FileInfo + var srcPath string + var dstPath string + if init { + srcPath = filepath.Join("data", "zones", zfh.ZoneId, "fs", filepath.Dir(src), "__files__", filepath.Base(src)) + dstPath = filepath.Join("data", "zones", zfh.ZoneId, "fs", filepath.Dir(dst), "__files__", filepath.Base(dst)) + } else { + srcPath = filepath.Join("data", "zones", zfh.ZoneId, "fs", src) + dstPath = filepath.Join("data", "zones", zfh.ZoneId, "fs", dst) + } + if err = os.MkdirAll(filepath.Dir(dstPath), 0700); err != nil && !os.IsExist(err) { + return + } + if srcfd, err = os.Open(srcPath); err != nil { + return + } + defer srcfd.Close() + if dstfd, err = os.Create(dstPath); err != nil { + return + } + defer dstfd.Close() + if _, err = io.Copy(dstfd, srcfd); err != nil { + return + } + if srcinfo, err = os.Stat(srcPath); err != nil { + return + } + if err = os.Chmod(dstPath, srcinfo.Mode()); err != nil { + return + } + if init { + err = zfh.DB.AddNewFSEntity(filepath.Dir(dst), fsEntity) + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(COPY_FILE_DONE, "node", member, map[string]interface{}{ + "path": dst, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + } + return +} + +func (zfh *ZoneFileHandler) CopyDir(src, dst string, init bool, fsEntity *ZoneFSEntity) (err error) { + var fds []os.FileInfo + var srcinfo os.FileInfo + srcPath := filepath.Join("data", "zones", zfh.ZoneId, "fs", src) + dstPath := filepath.Join("data", "zones", zfh.ZoneId, "fs", dst) + if srcinfo, err = os.Stat(srcPath); err != nil { + return + } + if init { + logger.Println("---------------------adding new fs entity---------------------") + if err = zfh.DB.AddNewFSEntity(filepath.Dir(dst), fsEntity); err != nil { + return + } + } else { + if err = os.MkdirAll(dstPath, srcinfo.Mode()); err != nil { + return + } + } + if fds, err = ioutil.ReadDir(srcPath); err != nil { + return + } + for _, fd := range fds { + srcfp := path.Join(src, fd.Name()) + dstfp := path.Join(dst, fd.Name()) + if fd.IsDir() { + if err = zfh.CopyDir(srcfp, dstfp, false, nil); err != nil { + logger.Println(err) + } + } else { + if err = zfh.CopyFile(srcfp, dstfp, false, nil); err != nil { + logger.Println(err) + } + } + } + for member := range fsEntity.Members { + done, errCh := zfh.sendDataChannelMessage(COPY_FOLDER_DONE, "node", member, map[string]interface{}{ + "path": dst, + }) + select { + case <-done: + case sendErr := <-errCh: + logger.Println(sendErr) + } + } + return +} + +func (zfh *ZoneFileHandler) CutFolder(id, path, dstPath string, init bool, fsEntity *ZoneFSEntity) (err error) { + if err = zfh.CopyDir(path, dstPath, init, fsEntity); err != nil { + return + } + err = zfh.DeleteFolder(filepath.Dir(path), filepath.Base(path)) + return +} + +func (zfh *ZoneFileHandler) CutFile(id, path, dstPath string, init bool, fsEntity *ZoneFSEntity) (err error) { + if err = zfh.CopyFile(path, dstPath, init, fsEntity); err != nil { + return + } + err = zfh.DeleteFile(filepath.Dir(path), filepath.Base(path)) + return +} + +func (zfh *ZoneFileHandler) ConnectToFSInstance(channelId string, userId string, sdp string) (err error) { + err = atomicallyExecute(zfh.FSInstanceFlag, func() (err error) { + d, e := zfh.FSInstance.HandleOffer(context.Background(), channelId, userId, sdp, zfh.HostId, zfh.sendDataChannelMessage, zfh.signalCandidate) + select { + case <-d: + case err = <-e: + } + return + }) + return +} + +func (zfh *ZoneFileHandler) LeaveFSInstance( + userId string) (err error) { + err = atomicallyExecute(zfh.FSInstanceFlag, func() (err error) { + zfh.FSInstance.HandleLeavingMember(userId) + return + }) + return +} + +func (zch *ZoneFileHandler) SetPublicRootFolders(user string) (err error) { + return +} + +func (zfh *ZoneFileHandler) handleZoneRequest(c context.Context, req *ZoneRequest) (err error) { + switch req.ReqType { + case LEAVE_ZONE: + logger.Println("*-----------------handling leaving zone---------------*") + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + err = zfh.LeaveFSInstance(req.Payload["userId"].(string)) + case ZONE_UPLOAD_FILE_DONE: + if err = verifyFieldsString(req.Payload, "path", "userId", "fileName", "type"); err != nil { + return + } + if err = verifyFieldsFloat64(req.Payload, "size"); err != nil { + return + } + err = zfh.CreateFile(req.Payload["path"].(string), req.Payload["fileName"].(string), &ZoneFSEntity{ + Name: req.Payload["fileName"].(string), + Owner: req.Payload["userId"].(string), + Type: req.Payload["type"].(string), + Size: uint64(req.Payload["size"].(float64)), + ModTime: time.Now().Format(time.UnixDate), + CreationTime: time.Now().Format(time.UnixDate), + Folder: false, + Members: map[string]*FSEntityAccessRights{ + req.Payload["userId"].(string): { + Read: true, + Write: true, + Download: true, + Rename: true, + }, + }, + }) + case ZONE_UPLOAD_FILE: + if err = verifyFieldsString(req.Payload, "path", "userId", "fileName"); err != nil { + return + } + err = zfh.FSInstance.SetupFileUpload(req.Payload["path"].(string), req.Payload["fileName"].(string), req.Payload["userId"].(string)) + case ZONE_DOWNLOAD_FILE: + if err = verifyFieldsString(req.Payload, "path", "userId", "fileName"); err != nil { + return + } + err = zfh.FSInstance.SetupFileDownload(req.Payload["path"].(string), req.Payload["fileName"].(string), req.Payload["userId"].(string)) + case GET_FS_ENTITIES: + logger.Println("getting files entities") + if err = verifyFieldsString(req.Payload, "path", "userId", "section"); err != nil { + return + } + var backward bool + if err = verifyFieldsBool(req.Payload, "backward"); err == nil { + backward = req.Payload["backward"].(bool) + } + if err = verifyFieldsFloat64(req.Payload, "lastIndex", "limit"); err != nil { + return + } + err = zfh.GetFSEntities(req.Payload["userId"].(string), req.Payload["section"].(string), req.Payload["path"].(string), 0, 100, backward) + case CREATE_FOLDER: + logger.Println("creating folder") + if err = verifyFieldsString(req.Payload, "path"); err != nil { + return + } + if _, ok := req.Payload["config"]; !ok { + err = fmt.Errorf("no field config in request payload") + return + } + bs, jsonErr := json.Marshal(req.Payload["config"]) + if jsonErr != nil { + return jsonErr + } + var config ZoneFSEntity + if err = json.Unmarshal(bs, &config); err != nil { + err = fmt.Errorf("the config payload dont match ZoneFSEntity struct pattern : %v", err) + return + } + err = zfh.CreateFolder(req.Payload["path"].(string), &config) + case RENAME_FOLDER: + if err = verifyFieldsString(req.Payload, "path", "name", "newName"); err != nil { + return + } + err = zfh.RenameFolder(req.Payload["path"].(string), req.Payload["name"].(string), req.Payload["newName"].(string)) + case RENAME_FILE: + if err = verifyFieldsString(req.Payload, "path", "name", "newName"); err != nil { + return + } + err = zfh.RenameFile(req.Payload["path"].(string), req.Payload["name"].(string), req.Payload["newName"].(string)) + case COPY_FOLDER: + if err = verifyFieldsString(req.Payload, "userId", "path", "dstPath"); err != nil { + return + } + if _, ok := req.Payload["config"]; !ok { + err = fmt.Errorf("no field config in request payload") + return + } + bs, jsonErr := json.Marshal(req.Payload["config"]) + if jsonErr != nil { + return jsonErr + } + var config ZoneFSEntity + if err = json.Unmarshal(bs, &config); err != nil { + err = fmt.Errorf("the config payload dont match ZoneFSEntity struct pattern : %v", err) + return + } + go func() { + err = zfh.CopyDir(req.Payload["path"].(string), req.Payload["dstPath"].(string), true, &config) + logger.Println(err) + }() + case COPY_FILE: + if err = verifyFieldsString(req.Payload, "userId", "path", "dstPath"); err != nil { + return + } + if _, ok := req.Payload["config"]; !ok { + err = fmt.Errorf("no field config in request payload") + return + } + bs, jsonErr := json.Marshal(req.Payload["config"]) + if jsonErr != nil { + return jsonErr + } + var config ZoneFSEntity + if err = json.Unmarshal(bs, &config); err != nil { + err = fmt.Errorf("the config payload dont match ZoneFSEntity struct pattern : %v", err) + return + } + go func() { + err = zfh.CopyFile(req.Payload["path"].(string), req.Payload["dstPath"].(string), true, &config) + logger.Println(err) + }() + case CUT_FOLDER: + if err = verifyFieldsString(req.Payload, "userId", "path", "dstPath"); err != nil { + return + } + if _, ok := req.Payload["config"]; !ok { + err = fmt.Errorf("no field config in request payload") + return + } + bs, jsonErr := json.Marshal(req.Payload["config"]) + if jsonErr != nil { + return jsonErr + } + var config ZoneFSEntity + if err = json.Unmarshal(bs, &config); err != nil { + err = fmt.Errorf("the config payload dont match ZoneFSEntity struct pattern : %v", err) + return + } + go func() { + err = zfh.CutFolder(req.Payload["userId"].(string), req.Payload["path"].(string), req.Payload["dstPath"].(string), true, &config) + logger.Println(err) + }() + case CUT_FILE: + if err = verifyFieldsString(req.Payload, "userId", "path", "dstPath"); err != nil { + return + } + if _, ok := req.Payload["config"]; !ok { + err = fmt.Errorf("no field config in request payload") + return + } + bs, jsonErr := json.Marshal(req.Payload["config"]) + if jsonErr != nil { + return jsonErr + } + var config ZoneFSEntity + if err = json.Unmarshal(bs, &config); err != nil { + err = fmt.Errorf("the config payload dont match ZoneFSEntity struct pattern : %v", err) + return + } + go func() { + err = zfh.CutFile(req.Payload["userId"].(string), req.Payload["path"].(string), req.Payload["dstPath"].(string), true, &config) + logger.Println(err) + }() + case UPDATE_PERMISSIONS: + if err = verifyFieldsString(req.Payload, "path", "name", "userId"); err != nil { + return + } + if err = verifyFieldsBool(req.Payload, "read", "write", "download", "rename"); err != nil { + return + } + fsAcessRights := &FSEntityAccessRights{ + Read: req.Payload["read"].(bool), + Write: req.Payload["write"].(bool), + Download: req.Payload["download"].(bool), + Rename: req.Payload["rename"].(bool), + } + err = zfh.UpdatePermissions(req.Payload["path"].(string), req.Payload["name"].(string), req.Payload["userId"].(string), fsAcessRights) + case DELETE_FOLDER: + if err = verifyFieldsString(req.Payload, "path", "name"); err != nil { + return + } + err = zfh.DeleteFolder(req.Payload["path"].(string), req.Payload["name"].(string)) + case DELETE_FILE: + if err = verifyFieldsString(req.Payload, "path", "name"); err != nil { + return + } + err = zfh.DeleteFile(req.Payload["path"].(string), req.Payload["name"].(string)) + case ADD_FS_ENTITY_MEMBERS: + if err = verifyFieldsString(req.Payload, "path", "userId", "folderName"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + if err = verifyFieldsBool(req.Payload, "read", "write", "download", "rename"); err != nil { + return + } + err = zfh.AddFolderMember(req.Payload["userId"].(string), req.Payload["path"].(string), req.Payload["folderName"].(string), req.Payload["members"].([]interface{}), req.Payload["read"].(bool), req.Payload["write"].(bool), req.Payload["download"].(bool), req.Payload["rename"].(bool)) + case REMOVE_FS_ENTITY_MEMBERS: + if err = verifyFieldsString(req.Payload, "path", "userId", "folderName"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zfh.RemoveFolderMember(req.Payload["userId"].(string), req.Payload["path"].(string), req.Payload["folderName"].(string), req.Payload["members"].([]interface{})) + case string(ZONE_FS_WEBRTC_OFFER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = zfh.ConnectToFSInstance(req.Payload["channelId"].(string), req.Payload["userId"].(string), req.Payload["sdp"].(string)) + case string(ZONE_FS_WEBRTC_COUNTER_OFFER): + logger.Println("handling fs instance counter offer") + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = atomicallyExecute(zfh.FSInstanceFlag, func() (err error) { + err = zfh.FSInstance.HandleCounterOffer(context.Background(), req.Payload["userId"].(string), zfh.sendDataChannelMessage) + return + }) + case string(ZONE_FS_WEBRTC_RENNEGOTIATION_OFFER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = atomicallyExecute(zfh.FSInstanceFlag, func() (err error) { + err = zfh.FSInstance.HandleRennegotiationOffer(req.Payload["userId"].(string), req.Payload["sdp"].(string), zfh.sendDataChannelMessage) + return + }) + case string(ZONE_FS_WEBRTC_RENNEGOTIATION_ANSWER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = atomicallyExecute(zfh.FSInstanceFlag, func() (err error) { + err = zfh.FSInstance.HandleRennegotiationAnswer(req.Payload["userId"].(string), req.Payload["sdp"].(string)) + return + }) + case string(ZONE_FS_WEBRTC_CANDIDATE): + logger.Println("handling fs instance webrtc candidate") + logger.Println(req.Payload) + if err = verifyFieldsString(req.Payload, FROM, "candidate", "sdpMlineIndex", "sdpMid", "channelId", "userId"); err != nil { + return + } + logger.Println(req.Payload) + i, convErr := strconv.Atoi(req.Payload["sdpMlineIndex"].(string)) + if convErr != nil { + return convErr + } + sdpMlineIndex := uint16(i) + sdpMid := req.Payload["sdpMid"].(string) + logger.Println(sdpMid, sdpMlineIndex) + err = atomicallyExecute(zfh.FSInstanceFlag, func() (err error) { + err = zfh.FSInstance.AddCandidate(&webrtc.ICECandidateInit{ + Candidate: req.Payload["candidate"].(string), + SDPMid: &sdpMid, + SDPMLineIndex: &sdpMlineIndex, + }, req.Payload["userId"].(string)) + return + }) + default: + logger.Println("got your request in zone file handler", req) + return + } + return +} diff --git a/zoneFSInstance.go b/zoneFSInstance.go new file mode 100644 index 0000000..559e6af --- /dev/null +++ b/zoneFSInstance.go @@ -0,0 +1,559 @@ +package localserver + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + sync "sync" + "time" + + "github.com/pion/webrtc/v3" +) + +const ( + ZONE_FS_WEBRTC_OFFER ReqType = "zone_fs_offer" + ZONE_FS_WEBRTC_ANSWER ReqType = "zone_fs_answer" + ZONE_FS_WEBRTC_RENNEGOTIATION_OFFER ReqType = "zone_fs_rennegotiation_offer" + ZONE_FS_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "zone_fs_rennegotiation_answer" + ZONE_FS_WEBRTC_COUNTER_OFFER ReqType = "zone_fs_webrtc_counter_offer" + ZONE_FS_WEBRTC_CANDIDATE ReqType = "zone_fs_webrtc_candidate" +) + +const () + +type FSInstance struct { + ZoneID string `json:"id"` + Owner string `json:"owner"` + Members []string `json:"members"` + OpenFiles map[string]*os.File `json:"-"` + localSD map[string]*webrtc.SessionDescription `json:"-"` + rtcPeerConnections map[string]*ZoneRTCPeerConnection `json:"-"` + zoneFSDataChannels map[string]map[string]*DataChannel `json:"-"` + pendingCandidates map[string][]*webrtc.ICECandidate `json:"-"` + middlewares []interface{} `json:"-"` + candidateFlag *uint32 `json:"-"` + filesFlag *uint32 `json:"-"` + rtcPeerConnectionMapFlag *uint32 `json:"-"` + dataChannelMapFlag *uint32 `json:"-"` + localSDMapFlag *uint32 `json:"-"` +} + +type FSInstanceOnICECandidateFunc = func(string, string, *webrtc.ICECandidate) error + +func NewFSInstance(id, owner string, members []string) (audioChannel *FSInstance) { + candidateFlag := uint32(0) + rtcPeerConnectionMapFlag := uint32(0) + dataChannelMapFlag := uint32(0) + filesFlag := uint32(0) + localSDMapFlag := uint32(0) + audioChannel = &FSInstance{ + ZoneID: id, + Owner: owner, + Members: members, + OpenFiles: make(map[string]*os.File), + localSD: make(map[string]*webrtc.SessionDescription), + rtcPeerConnections: make(map[string]*ZoneRTCPeerConnection), + zoneFSDataChannels: make(map[string]map[string]*DataChannel), + pendingCandidates: make(map[string][]*webrtc.ICECandidate), + middlewares: make([]interface{}, 0), + candidateFlag: &candidateFlag, + filesFlag: &filesFlag, + rtcPeerConnectionMapFlag: &rtcPeerConnectionMapFlag, + dataChannelMapFlag: &dataChannelMapFlag, + localSDMapFlag: &localSDMapFlag, + } + return +} + +func (fs *FSInstance) SetupFileUpload(path, filename, userId string) (err error) { + concretePath := filepath.Join("data", "zones", fs.ZoneID, "fs", path, "__files__", filename) + if _, rErr := os.ReadDir(filepath.Join("data", "zones", fs.ZoneID, "fs", path, "__files__")); os.IsNotExist(rErr) { + if err = os.MkdirAll(filepath.Join("data", "zones", fs.ZoneID, "fs", path, "__files__"), 0700); err != nil { + return + } + } else if rErr != nil { + return rErr + } + file, err := os.OpenFile(concretePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755) + if err != nil { + return + } + _ = atomicallyExecute(fs.filesFlag, func() (err error) { + fs.OpenFiles[filename] = file + return + }) + err = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if pc, ok := fs.rtcPeerConnections[userId]; ok { + maxRetransmits := uint16(100) + var dc *webrtc.DataChannel + dc, err = pc.CreateDataChannel(filename, &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return + } + dc.OnOpen(func() { + logger.Println("!-----------------------------!") + logger.Printf("datachannel with id %s is now open\n", dc.Label()) + logger.Println("!-----------------------------!") + }) + dc.OnMessage(func(msg webrtc.DataChannelMessage) { + _, _ = file.Write(msg.Data) + }) + dc.OnClose(func() { + _ = atomicallyExecute(fs.filesFlag, func() (err error) { + if f, ok := fs.OpenFiles[filename]; ok { + err = f.Close() + } + delete(fs.OpenFiles, filename) + return + }) + }) + err = atomicallyExecute(fs.dataChannelMapFlag, func() (err error) { + if _, ok := fs.zoneFSDataChannels[userId]; !ok { + err = fmt.Errorf("no corresponding map entry in zoneFSDataChannels for id %s", userId) + return + } + l := int32(0) + fs.zoneFSDataChannels[userId][dc.Label()] = &DataChannel{ + DataChannel: dc, + l: &l, + } + return + }) + } else { + err = fmt.Errorf("no peerconnection for id %s", userId) + } + return + }) + return +} + +func (fs *FSInstance) SetupFileDownload(path, filename, userId string) (err error) { + concretePath := filepath.Join("data", "zones", fs.ZoneID, "fs", path, "__files__", filename) + file, err := os.OpenFile(concretePath, os.O_RDONLY, 0755) + if err != nil { + return + } + _ = atomicallyExecute(fs.filesFlag, func() (err error) { + fs.OpenFiles[filename] = file + return + }) + err = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if pc, ok := fs.rtcPeerConnections[userId]; ok { + maxRetransmits := uint16(100) + var dc *webrtc.DataChannel + dc, err = pc.CreateDataChannel(filename, &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return + } + dc.SetBufferedAmountLowThreshold(16000000) + bufferedAmountLock := make(chan struct{}) + done := make(chan struct{}) + dc.OnOpen(func() { + go func() { + defer func() { + bufferedAmountLock = nil + }() + r := bufio.NewReader(file) + buf := make([]byte, 0, 60000) + for { + n, readErr := r.Read(buf[:cap(buf)]) + buf = buf[:n] + if n == 0 { + if err == nil { + logger.Println("n is 0 weird") + break + } + if err == io.EOF { + break + } + logger.Println(readErr) + return + } + if err = dc.Send(buf); err != nil { + logger.Println(err) + } + if dc.BufferedAmount() > dc. + BufferedAmountLowThreshold() { + <-bufferedAmountLock + } + } + logger.Println("done") + _ = dc.SendText("done") + <-time.After(time.Second * 5) + _ = dc.Close() + }() + }) + dc.OnBufferedAmountLow(func() { + bufferedAmountLock <- struct{}{} + }) + dc.OnClose(func() { + done <- struct{}{} + defer close(done) + _ = atomicallyExecute(fs.filesFlag, func() (err error) { + if f, ok := fs.OpenFiles[filename]; ok { + err = f.Close() + } + delete(fs.OpenFiles, filename) + return + }) + + }) + } else { + err = fmt.Errorf("no peerconnection for id %s", userId) + } + return + }) + return +} + +func (fs *FSInstance) HandleOffer(ctx context.Context, channelId, userId, sdp, hostId string, sendDCMessage SendDCMessageFunc, cb FSInstanceOnICECandidateFunc) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + peerConnection, err := fs.createPeerConnection(userId, fs.ZoneID, webrtc.SDPTypeAnswer, cb, sendDCMessage) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + fs.rtcPeerConnections[userId] = &ZoneRTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: false, + makingOfferLock: &sync.Mutex{}, + } + return + }) + offer := webrtc.SessionDescription{ + Type: webrtc.SDPTypeOffer, + SDP: sdp, + } + if err = peerConnection.SetRemoteDescription(offer); err != nil { + errCh <- err + return + } + rawAnswer, err := peerConnection.CreateAnswer(nil) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(fs.localSDMapFlag, func() (err error) { + fs.localSD[userId] = &rawAnswer + return + }) + _, _ = sendDCMessage(string(ZONE_FS_WEBRTC_ANSWER), hostId, userId, map[string]interface{}{ + "to": userId, + "from": fs.ZoneID, + "channelId": channelId, + "sdp": rawAnswer.SDP, + }) + done <- struct{}{} + logger.Println("handle offer done") + }() + return +} + +func (fs *FSInstance) HandleCounterOffer(ctx context.Context, userId string, sendDCMessage SendDCMessageFunc) (err error) { + if err = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := fs.rtcPeerConnections[userId]; !ok { + err = fmt.Errorf("no field corresponding peer connection for id %s", userId) + return + } + logger.Println("handling counter offer") + connection := fs.rtcPeerConnections[userId] + err = atomicallyExecute(fs.localSDMapFlag, func() (err error) { + err = connection.SetLocalDescription(*fs.localSD[userId]) + return + }) + return + }); err != nil { + return + } + _ = atomicallyExecute(fs.localSDMapFlag, func() (err error) { + delete(fs.localSD, userId) + return + }) + if err = atomicallyExecute(fs.candidateFlag, func() (err error) { + for _, candidate := range fs.pendingCandidates[userId] { + logger.Println("sending candidate to", userId, candidate) + d, e := sendDCMessage(string(ZONE_FS_WEBRTC_CANDIDATE), "", userId, map[string]interface{}{ + "from": fs.ZoneID, + "to": userId, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }) + select { + case <-d: + case err = <-e: + return + } + } + delete(fs.pendingCandidates, userId) + return + }); err != nil { + return + } + return +} + +func (fs *FSInstance) HandleRennegotiationOffer(from, sdp string, sendDCMessage SendDCMessageFunc) (err error) { + err = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := fs.rtcPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + fs.rtcPeerConnections[from].makingOfferLock.Lock() + if fs.rtcPeerConnections[from].makingOffer { + fs.rtcPeerConnections[from].makingOfferLock.Unlock() + return fmt.Errorf("already making an offer or state is stable") + } + fs.rtcPeerConnections[from].makingOfferLock.Unlock() + if err = fs.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { + return + } + localSd, err := fs.rtcPeerConnections[from].CreateAnswer(nil) + if err != nil { + return + } + if err = fs.rtcPeerConnections[from].SetLocalDescription(localSd); err != nil { + return + } + d, e := sendDCMessage(string(ZONE_FS_WEBRTC_RENNEGOTIATION_ANSWER), fs.ZoneID, from, map[string]interface{}{ + "from": fs.ZoneID, + "to": from, + "sdp": localSd.SDP, + }) + select { + case <-d: + case err = <-e: + } + return + }) + return +} + +func (fs *FSInstance) HandleRennegotiationAnswer(from, sdp string) (err error) { + logger.Println("---------------------handling rennego answer") + err = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + err = fs.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) + return + }) + return +} + +func (fs *FSInstance) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { + logger.Println("adding ice candidate", candidate) + err = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := fs.rtcPeerConnections[from]; ok && candidate != nil { + err = fs.rtcPeerConnections[from].AddICECandidate(*candidate) + } + return + }) + return +} + +func (fs *FSInstance) createPeerConnection(target, from string, peerType webrtc.SDPType, cb FSInstanceOnICECandidateFunc, sendDCMessage SendDCMessageFunc) (peerConnection *webrtc.PeerConnection, err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"}, + }, + }, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, + } + + peerConnection, err = webrtc.NewPeerConnection(config) + if err != nil { + return + } + logger.Println("---------------------------------------------------") + if peerType == webrtc.SDPTypeAnswer { + maxRetransmits := uint16(100) + channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return nil, err + } + channel.OnOpen(func() { + logger.Println("channel opened") + if chanErr := channel.SendText("yooo man this is open"); chanErr != nil { + logger.Println(chanErr) + } + }) + channel.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + if e := fs.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil { + logger.Println("*-------------- datachannel error: ", e) + } + }) + logger.Println("new channel for target : ", target) + channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) + channel.OnBufferedAmountLow(func() { + + }) + _ = atomicallyExecute(fs.dataChannelMapFlag, func() (err error) { + logger.Println(target) + l := int32(0) + if _, ok := fs.zoneFSDataChannels[target]; !ok { + fs.zoneFSDataChannels[target] = make(map[string]*DataChannel) + } + fs.zoneFSDataChannels[target][channel.Label()] = &DataChannel{ + DataChannel: channel, + l: &l, + } + return + }) + } else { + peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { + _ = atomicallyExecute(fs.dataChannelMapFlag, func() (err error) { + l := int32(0) + if _, ok := fs.zoneFSDataChannels[target]; !ok { + fs.zoneFSDataChannels[target] = make(map[string]*DataChannel) + } + fs.zoneFSDataChannels[target][dc.Label()] = &DataChannel{ + DataChannel: dc, + l: &l, + } + return + }) + dc.OnOpen(func() { + logger.Printf("got a new open datachannel %s\n", dc.Label()) + }) + dc.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + if e := fs.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil { + logger.Println("*-------------- datachannel error: ", e) + } + }) + }) + } + peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed { + logger.Println(pcs) + fs.HandleLeavingMember(target) + } + }) + peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { + logger.Printf("ICE connection state has changed %s\n", is.String()) + if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { + logger.Println(is) + } + }) + peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + _ = atomicallyExecute(fs.candidateFlag, func() (err error) { + desc := peerConnection.RemoteDescription() + if desc == nil { + logger.Println("generated candidate appended to list : ", i) + fs.pendingCandidates[target] = append(fs.pendingCandidates[target], i) + } else { + logger.Println("generated candidate : ", i) + if iceCandidateErr := cb(from, target, i); iceCandidateErr != nil { + logger.Println(iceCandidateErr) + } + } + return + }) + }) + peerConnection.OnNegotiationNeeded(func() { + logger.Println("---------------- rennego is needed -----------") + _ = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + logger.Println("----------------- sending renego to peer with id", target) + if peerConnection.SignalingState() == webrtc.SignalingStateStable { + localSd, localSdErr := peerConnection.CreateOffer(nil) + if localSdErr != nil { + logger.Println(localSdErr) + return localSdErr + } + if err = peerConnection.SetLocalDescription(localSd); err != nil { + logger.Println(err) + return + } + d, e := sendDCMessage(string(ZONE_FS_WEBRTC_RENNEGOTIATION_OFFER), fs.ZoneID, target, map[string]interface{}{ + "from": fs.ZoneID, + "to": target, + "sdp": localSd.SDP, + }) + select { + case <-d: + case err = <-e: + logger.Println(err) + } + } + return + }) + }) + return +} + +func (fs *FSInstance) HandleLeavingMember(id string) { + if err := atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := fs.rtcPeerConnections[id]; !ok { + err = fmt.Errorf("no corresponding peerconnection for audio channel leaving member") + } + return + }); err != nil { + logger.Println(err) + } else { + defer func() { + _ = atomicallyExecute(fs.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := fs.rtcPeerConnections[id]; ok { + if closeErr := fs.rtcPeerConnections[id].Close(); closeErr != nil { + err = closeErr + logger.Println("peer connection close error", closeErr) + } + } + delete(fs.rtcPeerConnections, id) + return + }) + }() + } + logger.Printf("peer %s is leaving the squad\n", id) + _ = atomicallyExecute(fs.dataChannelMapFlag, func() (err error) { + if _, ok := fs.zoneFSDataChannels[id]; ok { + for _, dc := range fs.zoneFSDataChannels[id] { + dc.DataChannel.Close() + } + } + delete(fs.zoneFSDataChannels, id) + return + }) + _ = atomicallyExecute(fs.localSDMapFlag, func() (err error) { + delete(fs.localSD, id) + return + }) + _ = atomicallyExecute(fs.candidateFlag, func() (err error) { + delete(fs.pendingCandidates, id) + return + }) +} + +func (fs *FSInstance) HandleDataChannelEvents(from, eventId string, payload map[string]interface{}) (err error) { + switch eventId { + } + return +} diff --git a/zoneFile.go b/zoneFile.go new file mode 100644 index 0000000..3b3f8fe --- /dev/null +++ b/zoneFile.go @@ -0,0 +1 @@ +package localserver diff --git a/zoneGrpcMiddleware.go b/zoneGrpcMiddleware.go new file mode 100644 index 0000000..6b20c4b --- /dev/null +++ b/zoneGrpcMiddleware.go @@ -0,0 +1,234 @@ +package localserver + +import ( + "context" + "strconv" + + "github.com/pion/webrtc/v3" +) + +const ( + ZONE_OFFER ReqType = "zone_offer" + ZONE_ANSWER ReqType = "zone_answer" + ZONE_COUNTER_OFFER ReqType = "zone_counter_offer" + JOIN_ZONE ReqType = "join_hosted_squad" + ZONE_ACCESS_DENIED ReqType = "zone_squad_access_denied" + QUIT_ZONE ReqType = "zone_stop_call" + ZONE_ACCESS_GRANTED ReqType = "zone_access_granted" + INCOMING_ZONE_MEMBER ReqType = "incoming_zone_member" + LEAVING_ZONE_MEMBER ReqType = "leaving_zone_member" + ZONE_WEBRTC_RENNEGOTIATION_OFFER ReqType = "zone_rennegotiation_offer" + ZONE_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "zone_rennegotiation_answer" + ZONE_WEBRTC_CANDIDATE ReqType = "zone_webrtc_candidate" + NEW_ZONE ReqType = "new_zone" + NEW_AUTHORIZED_ZONE_MEMBER ReqType = "new_authorized_zone_member" + REMOVED_ZONE_AUTHORIZED_MEMBER ReqType = "removed_zone_authorized_member" +) + +type ZoneGrpcMiddleware struct { + Manager *ZoneManager + stream GrpcManager_LinkClient +} + +func NewZoneGrpcMiddleware(manager *ZoneManager) (zoneGrpcMiddleware *ZoneGrpcMiddleware) { + zoneGrpcMiddleware = &ZoneGrpcMiddleware{ + Manager: manager, + } + return +} + +func (zm *ZoneGrpcMiddleware) signalCandidate(to string, candidate *webrtc.ICECandidate) (err error) { + err = zm.stream.Send(&Request{ + Type: string(ZONE_WEBRTC_CANDIDATE), + From: zm.Manager.ID, + Token: "none", + Payload: map[string]string{ + "from": zm.Manager.ID, + "to": to, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }) + return +} + +func (zm *ZoneGrpcMiddleware) Process(ctx context.Context, req *Response, stream GrpcManager_LinkClient) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + switch req.Type { + case string(INCOMING_ZONE_MEMBER): + case string(LEAVING_ZONE_MEMBER): + if err := validateRequest(req.GetPayload(), "zoneId", "userId"); err != nil { + return + } + if err := zm.Manager.HandleLeavingMember(req.Payload["userId"], req.Payload["zoneId"]); err != nil { + errCh <- err + return + } + case string(REMOVED_ZONE_AUTHORIZED_MEMBER): + if err := validateRequest(req.GetPayload(), "zoneId", "userId"); err != nil { + return + } + if err = atomicallyExecute(zm.Manager.zoneFlag, func() (err error) { + reqChan := make(chan *ZoneRequest) + done, e := zm.Manager.Zones[req.Payload["zoneId"]].ZoneRequestScheduler.Schedule(reqChan) + go func() { + defer close(reqChan) + reqChan <- &ZoneRequest{ + ReqType: string(REMOVE_USER), + From: req.Payload["userId"], + Payload: map[string]interface{}{ + "userId": req.Payload["userId"], + }, + } + reqChan <- &ZoneRequest{ + ReqType: string(REMOVED_ZONE_AUTHORIZED_MEMBER), + From: req.Payload["userId"], + Payload: map[string]interface{}{ + "userId": req.Payload["userId"], + }, + } + }() + select { + case <-done: + case err = <-e: + } + return + }); err != nil { + errCh <- err + return + } + case string(NEW_AUTHORIZED_ZONE_MEMBER): + if err := validateRequest(req.GetPayload(), "zoneId", "userId"); err != nil { + return + } + if err = atomicallyExecute(zm.Manager.zoneFlag, func() (err error) { + reqChan := make(chan *ZoneRequest) + done, e := zm.Manager.Zones[req.Payload["zoneId"]].ZoneRequestScheduler.Schedule(reqChan) + go func() { + defer close(reqChan) + reqChan <- &ZoneRequest{ + ReqType: string(ADD_USER), + From: req.Payload["userId"], + Payload: map[string]interface{}{ + "userId": req.Payload["userId"], + }, + } + reqChan <- &ZoneRequest{ + ReqType: string(NEW_AUTHORIZED_ZONE_MEMBER), + From: req.Payload["userId"], + Payload: map[string]interface{}{ + "userId": req.Payload["userId"], + }, + } + }() + select { + case <-done: + case err = <-e: + } + return + }); err != nil { + errCh <- err + return + } + case string(NEW_ZONE): + logger.Println(req.Payload) + if err := validateRequest(req.GetPayload(), "zoneId", "zoneName", "zoneImageURL", "zoneOwner", "zoneCreationDate"); err != nil { + errCh <- err + return + } + zone, err := NewZone(zm.Manager.ID, req.Payload["zoneId"], req.Payload["zoneName"], req.Payload["zoneImageURL"], req.Payload["zoneOwner"], req.Payload["zoneCreationDate"], true, []string{req.Payload["zoneOwner"]}) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(zm.Manager.zoneFlag, func() (err error) { + zm.Manager.Zones[zone.ID] = zone + return + }) + case string(ZONE_OFFER): + if err := validateRequest(req.GetPayload(), FROM, TO, SDP); err != nil { + errCh <- err + return + } + if err := zm.Manager.HandleOffer(ctx, req.GetPayload(), zm.signalCandidate); err != nil { + errCh <- err + return + } + case string(ZONE_ANSWER): + if err := validateRequest(req.GetPayload(), FROM, TO, SDP); err != nil { + errCh <- err + return + } + if err := zm.Manager.HandleAnswer(ctx, req.GetPayload()); err != nil { + errCh <- err + return + } + case string(ZONE_COUNTER_OFFER): + if err := validateRequest(req.GetPayload(), FROM); err != nil { + errCh <- err + return + } + if err := zm.Manager.HandleCounterOffer(ctx, req.Payload); err != nil { + errCh <- err + return + } + case string(ZONE_WEBRTC_RENNEGOTIATION_OFFER): + logger.Println("received negotiation offer") + if err := validateRequest(req.GetPayload(), FROM, SDP); err != nil { + errCh <- err + return + } + if err := zm.Manager.HandleRennegotiationOffer(req.Payload[FROM], req.Payload[SDP]); err != nil { + errCh <- err + return + } + case string(ZONE_WEBRTC_RENNEGOTIATION_ANSWER): + logger.Println("received negotiation answer") + if err := validateRequest(req.GetPayload(), FROM, SDP); err != nil { + errCh <- err + return + } + if err := zm.Manager.HandleRennegotiationAnswer(req.Payload[FROM], req.Payload[SDP]); err != nil { + errCh <- err + return + } + case string(ZONE_WEBRTC_CANDIDATE): + if err := validateRequest(req.GetPayload(), FROM, "candidate", "sdpMlineIndex", "sdpMid"); err != nil { + errCh <- err + return + } + logger.Println(req.Payload) + i, err := strconv.Atoi(req.Payload["sdpMlineIndex"]) + if err != nil { + errCh <- err + return + } + sdpMlineIndex := uint16(i) + sdpMid := req.Payload["sdpMid"] + logger.Println(sdpMid, sdpMlineIndex) + if err := zm.Manager.AddCandidate(&webrtc.ICECandidateInit{ + Candidate: req.Payload["candidate"], + SDPMid: &sdpMid, + SDPMLineIndex: &sdpMlineIndex, + }, req.Payload[FROM]); err != nil { + errCh <- err + return + } + default: + logger.Println("no request for zon grpc middleware") + logger.Println(req.GetPayload()) + logger.Println(req.Type) + } + done <- struct{}{} + }() + select { + case <-ctx.Done(): + return ctx.Err() + case err = <-errCh: + return + case <-done: + return + } +} diff --git a/zoneManager.go b/zoneManager.go new file mode 100644 index 0000000..c66cae1 --- /dev/null +++ b/zoneManager.go @@ -0,0 +1,734 @@ +package localserver + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + sync "sync" + "sync/atomic" + + "github.com/pion/webrtc/v3" +) + +const ( + LIST_ZONES_BY_HOST = "list_zones_by_host" + LEAVE_ZONE = "leave_zone" +) + +type ZoneManager struct { + ID string + Zones map[string]*Zone + LocalSD map[string]*webrtc.SessionDescription + RTCPeerConnections map[string]*RTCPeerConnection + DataChannels map[string]*DataChannel + PendingCandidates map[string][]*webrtc.ICECandidate + stream GrpcManager_LinkClient + zoneFlag *uint32 + peerConnectionFlag *uint32 + localSDFlag *uint32 + dataChannelFlag *uint32 + candidateFlag *uint32 +} + +type Zone struct { + ID string + Name string + ImageURL string + Owner string + CreationDate string + AuthorizedMembers []string + DataChannels map[string]*DataChannel + DataChannelsFlag *uint32 + ZoneRequestScheduler *ZoneRequestScheduler + Initialized bool +} + +func NewZone(hostId string, zoneId string, zoneName string, imageUrl string, owner string, creationDate string, initialized bool, authorizedMembers []string) (zone *Zone, err error) { + dataChannels, dataChannelFlag := make(map[string]*DataChannel), uint32(0) + zoneChatHandler, err := NewZoneChatsHandler(zoneId, owner, authorizedMembers, dataChannels, &dataChannelFlag) + if err != nil { + return nil, err + } + zoneUsersHandler, err := NewZoneUsersHandler(zoneId, owner, authorizedMembers, dataChannels, &dataChannelFlag) + if err != nil { + return + } + zoneAudioChannelsHandler, err := NewZoneAudioChannelsHandler(hostId, zoneId, owner, authorizedMembers, dataChannels, &dataChannelFlag) + if err != nil { + return + } + zoneVideoChannelsHandler, err := NewZoneVideoChannelsHandler(hostId, zoneId, owner, authorizedMembers, dataChannels, &dataChannelFlag) + if err != nil { + return + } + zoneFileHandler, err := NewZoneFileHandler(hostId, zoneId, owner, authorizedMembers, dataChannels, &dataChannelFlag) + if err != nil { + return + } + zoneScheduler, e := NewZoneRequestScheduler(authorizedMembers, zoneUsersHandler, zoneChatHandler, zoneAudioChannelsHandler, zoneVideoChannelsHandler, zoneFileHandler) + go func() { + for schedErr := range e { + logger.Println("from scheduler :", schedErr) + } + }() + zone = &Zone{ + ID: zoneId, + Name: zoneName, + ImageURL: imageUrl, + Owner: owner, + CreationDate: creationDate, + Initialized: initialized, + ZoneRequestScheduler: zoneScheduler, + DataChannels: dataChannels, + DataChannelsFlag: &dataChannelFlag, + } + return +} + +func NewZoneManager(id string, token string) (zoneManager *ZoneManager, err error) { + zoneFlag := uint32(0) + peerConnectionFlag := uint32(0) + localSDFlag := uint32(0) + dataChannelFlag := uint32(0) + candidateFlag := uint32(0) + dataChannels := make(map[string]*DataChannel) + zoneMap := make(map[string]*Zone) + zones, err := zoneManager.fetchZones(id, token) + if err != nil { + return + } + for _, zone := range zones { + z, err := NewZone(id, zone.ID, zone.Name, zone.ImageURL, zone.Owner, zone.CreationDate, true, zone.AuthorizedMembers) + if err != nil { + return nil, err + } + zoneMap[zone.ID] = z + } + logger.Println(zoneMap) + zoneManager = &ZoneManager{ + ID: id, + Zones: zoneMap, + LocalSD: make(map[string]*webrtc.SessionDescription), + RTCPeerConnections: make(map[string]*RTCPeerConnection), + DataChannels: dataChannels, + PendingCandidates: make(map[string][]*webrtc.ICECandidate), + zoneFlag: &zoneFlag, + peerConnectionFlag: &peerConnectionFlag, + localSDFlag: &localSDFlag, + dataChannelFlag: &dataChannelFlag, + candidateFlag: &candidateFlag, + } + return +} + +func atomicallyExecute(flag *uint32, job func() (err error)) (err error) { + for { + if atomic.CompareAndSwapUint32(flag, 0, 1) { + defer atomic.SwapUint32(flag, 0) + err = job() + break + } + } + return +} + +func (zm *ZoneManager) DeleteZone(zoneId string) {} + +func (zm *ZoneManager) fetchZones(nodeId string, token string) (zones []*Zone, err error) { + body, err := json.Marshal(map[string]interface{}{ + "type": LIST_ZONES_BY_HOST, + "token": token, + "from": nodeId, + "payload": map[string]string{ + "host": nodeId, + "lastIndex": "0", + }, + }) + if err != nil { + return + } + res, err := http.Post("https://app.zippytal.com/req", "application/json", bytes.NewBuffer(body)) + if err != nil { + logger.Println("error come from there inn zone manager") + return + } + bs, err := io.ReadAll(res.Body) + if err != nil { + return + } + err = json.Unmarshal(bs, &zones) + return +} + +func (zm *ZoneManager) CreateOffer(ctx context.Context, target string, from string, zoneId string, cb OnICECandidateFunc) (err error) { + peerConnection, err := zm.createPeerConnection(target, from, zoneId, webrtc.SDPTypeOffer, cb) + if err != nil { + return + } + logger.Println("connection created") + rawOffer, err := peerConnection.CreateOffer(nil) + if err != nil { + return + } + if err = peerConnection.SetLocalDescription(rawOffer); err != nil { + return + } + _ = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + logger.Println("adding for target", target) + zm.RTCPeerConnections[target] = &RTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: true, + makingOfferLock: &sync.Mutex{}, + negotiate: zm.negotiate, + } + return + }) + err = zm.stream.Send(&Request{ + Type: string(ZONE_OFFER), + From: zm.ID, + Token: "none", + Payload: map[string]string{ + "to": target, + "from": zm.ID, + "sdp": rawOffer.SDP, + }, + }) + return +} + +func (zm *ZoneManager) HandleOffer(ctx context.Context, req map[string]string, cb OnICECandidateFunc) (err error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + if _, ok := zm.Zones[req["zoneId"]]; !ok { + err = fmt.Errorf("no corresponding zone") + errCh <- err + return + } + logger.Println("handling zone offer") + peerConnection, err := zm.createPeerConnection(req[FROM], req[TO], req["zoneId"], webrtc.SDPTypeAnswer, cb) + if err != nil { + errCh <- err + return + } + logger.Println("peer connection created") + _ = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + zm.RTCPeerConnections[req[FROM]] = &RTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: false, + makingOfferLock: &sync.Mutex{}, + negotiate: zm.negotiate, + } + return + }) + offer := webrtc.SessionDescription{ + Type: webrtc.SDPTypeOffer, + SDP: req[SDP], + } + if err = peerConnection.SetRemoteDescription(offer); err != nil { + errCh <- err + return + } + rawAnswer, err := peerConnection.CreateAnswer(nil) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(zm.localSDFlag, func() (err error) { + zm.LocalSD[req[FROM]] = &rawAnswer + return + }) + + _ = atomicallyExecute(zm.zoneFlag, func() (err error) { + //zm.Zones[req[SQUAD_ID]].Members = append(zm.Squads[req[SQUAD_ID]].Members, req[FROM]) + return + }) + if err = zm.stream.Send(&Request{ + Type: string(ZONE_ANSWER), + From: zm.ID, + Token: "none", + Payload: map[string]string{ + "to": req[FROM], + "from": zm.ID, + "sdp": rawAnswer.SDP, + }, + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + select { + case <-done: + return + case err = <-errCh: + return + case <-ctx.Done(): + err = ctx.Err() + return + } +} + +func (zm *ZoneManager) HandleAnswer(ctx context.Context, req map[string]string) (err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic in handle answer : %v\n", r) + } + }() + if err = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + if _, ok := zm.RTCPeerConnections[req[FROM]]; !ok { + err = fmt.Errorf("no corresponding peer connection for id : %s", req[FROM]) + return + } + peerConnnection := zm.RTCPeerConnections[req[FROM]] + logger.Println("---------------------") + logger.Println(req[SDP]) + logger.Println("---------------------") + if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{ + Type: webrtc.SDPTypeAnswer, + SDP: req[SDP], + }); err != nil { + logger.Println("error occured while setting remote description in handle answer") + return + } + return + }); err != nil { + return + } + if err = zm.stream.Send(&Request{ + Type: string(ZONE_COUNTER_OFFER), + From: zm.ID, + Token: "none", + Payload: map[string]string{ + "from": zm.ID, + "to": req[FROM], + }, + }); err != nil { + return + } + _ = atomicallyExecute(zm.candidateFlag, func() (err error) { + for _, candidate := range zm.PendingCandidates[req[FROM]] { + logger.Println("sending candidate from answer to", req[FROM]) + if err = zm.stream.Send(&Request{ + Type: string(ZONE_WEBRTC_CANDIDATE), + From: zm.ID, + Token: "none", + Payload: map[string]string{ + "from": zm.ID, + "to": req[FROM], + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }); err != nil { + logger.Println(err) + continue + } + } + delete(zm.PendingCandidates, req[FROM]) + return + }) + _ = atomicallyExecute(zm.localSDFlag, func() (err error) { + delete(zm.LocalSD, req[FROM]) + return + }) + return +} + +func (zm *ZoneManager) HandleCounterOffer(ctx context.Context, req map[string]string) (err error) { + logger.Println("handling counter offer 1") + if err = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + logger.Println("start job") + if _, ok := zm.RTCPeerConnections[req[FROM]]; !ok { + logger.Println("error here") + err = fmt.Errorf("no field corresponding peer connection for id %s", req[FROM]) + return + } + logger.Println("handling counter offer") + connection := zm.RTCPeerConnections[req[FROM]] + err = atomicallyExecute(zm.localSDFlag, func() (err error) { + if err = connection.SetLocalDescription(*zm.LocalSD[req[FROM]]); err != nil { + logger.Println(err) + return + } + return + }) + return + }); err != nil { + return + } + logger.Println("handling counter offer 2") + _ = atomicallyExecute(zm.candidateFlag, func() (err error) { + for _, candidate := range zm.PendingCandidates[req[FROM]] { + logger.Println("sending candidate to", req[FROM]) + if err = zm.stream.Send(&Request{ + Type: string(ZONE_WEBRTC_CANDIDATE), + From: zm.ID, + Token: "none", + Payload: map[string]string{ + "from": zm.ID, + "to": req[FROM], + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }, + }); err != nil { + return + } + } + delete(zm.PendingCandidates, req[FROM]) + return + }) + _ = atomicallyExecute(zm.localSDFlag, func() (err error) { + delete(zm.LocalSD, req[FROM]) + return + }) + return +} + +func (zm *ZoneManager) createPeerConnection(target string, from string, zoneId string, peerType webrtc.SDPType, cb OnICECandidateFunc) (peerConnection *webrtc.PeerConnection, err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"}, + }, + }, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, + } + + peerConnection, err = webrtc.NewPeerConnection(config) + if err != nil { + return + } + logger.Println("---------------------------------------------------") + if peerType == webrtc.SDPTypeAnswer { + maxRetransmits := uint16(100) + channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return nil, err + } + reqChan := make(chan *ZoneRequest) + channel.OnOpen(func() { + logger.Println(zoneId) + if _, ok := zm.Zones[zoneId]; ok { + logger.Println("this zone exist") + _ = atomicallyExecute(zm.Zones[zoneId].DataChannelsFlag, func() (err error) { + x := int32(0) + zm.Zones[zoneId].DataChannels[target] = &DataChannel{DataChannel: channel, bufferedAmountLowThresholdReached: make(<-chan struct{}), l: &x} + return + }) + if _, ok := zm.Zones[zoneId]; !ok { + err = fmt.Errorf("no corresponding zones") + return + } + done, err := zm.Zones[zoneId].ZoneRequestScheduler.Schedule(reqChan) + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: "user_zone_init", + From: zoneId, + To: target, + Payload: map[string]interface{}{}, + }) + if jsonErr != nil { + logger.Println("error in open channel", jsonErr) + return + } + if sendErr := channel.SendText(string(bs)); sendErr != nil { + logger.Println("error in open channel send", sendErr) + return + } + for { + select { + case <-done: + return + case <-err: + } + } + } + }) + channel.OnClose(func() { + close(reqChan) + }) + channel.OnMessage(func(msg webrtc.DataChannelMessage) { + var req ZoneRequest + if err := json.Unmarshal(msg.Data, &req); err != nil { + logger.Println(err) + return + } + logger.Println("incoming request", req) + reqChan <- &req + }) + logger.Println("new channel for target : ", target) + logger.Println(target) + _ = atomicallyExecute(zm.dataChannelFlag, func() (err error) { + l := int32(0) + zm.DataChannels[target] = &DataChannel{ + DataChannel: channel, + bufferedAmountLowThresholdReached: make(<-chan struct{}), + l: &l, + } + return + }) + } else { + peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { + _ = atomicallyExecute(zm.dataChannelFlag, func() (err error) { + l := int32(0) + zm.DataChannels[target] = &DataChannel{ + DataChannel: dc, + l: &l, + } + return + }) + reqChan := make(chan *ZoneRequest) + dc.OnOpen(func() { + logger.Println(zoneId) + if _, ok := zm.Zones[zoneId]; ok { + logger.Println("this zone exist") + _ = atomicallyExecute(zm.Zones[zoneId].DataChannelsFlag, func() (err error) { + x := int32(0) + zm.Zones[zoneId].DataChannels[target] = &DataChannel{DataChannel: dc, bufferedAmountLowThresholdReached: make(<-chan struct{}), l: &x} + return + }) + done, err := zm.Zones[zoneId].ZoneRequestScheduler.Schedule(reqChan) + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: "user_zone_init", + From: zoneId, + To: target, + Payload: map[string]interface{}{}, + }) + if jsonErr != nil { + logger.Println("error in open channel", jsonErr) + return + } + if sendErr := dc.SendText(string(bs)); sendErr != nil { + logger.Println("error in open channel send", sendErr) + return + } + for { + select { + case <-done: + return + case <-err: + } + } + } + }) + dc.OnClose(func() { + close(reqChan) + }) + dc.OnMessage(func(msg webrtc.DataChannelMessage) { + var req ZoneRequest + if err := json.Unmarshal(msg.Data, &req); err != nil { + logger.Println(err) + return + } + logger.Println("incoming request", req) + reqChan <- &req + }) + }) + } + peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed { + logger.Println(pcs) + if err = zm.HandleLeavingMember(target, zoneId); err != nil { + logger.Println(err) + } + } + }) + peerConnection.OnNegotiationNeeded(func() { + logger.Println("------------------- negotiation is needed --------------------") + if pc, ok := zm.RTCPeerConnections[target]; ok { + if pc.SignalingState() == webrtc.ICETransportStateConnected { + localSd, err := pc.CreateOffer(nil) + if err != nil { + logger.Println(err) + return + } + if err = pc.SetLocalDescription(localSd); err != nil { + logger.Println(err) + return + } + if err = zm.stream.Send(&Request{ + Type: string(ZONE_WEBRTC_RENNEGOTIATION_OFFER), + From: zm.ID, + Token: "", + Payload: map[string]string{ + "to": target, + "sdp": localSd.SDP, + }, + }); err != nil { + logger.Println(err) + return + } + } + } + }) + peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { + logger.Printf("ICE connection state has changed %s\n", is.String()) + if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { + logger.Println(is) + } + }) + peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + _ = atomicallyExecute(zm.candidateFlag, func() (err error) { + desc := peerConnection.RemoteDescription() + if desc == nil { + logger.Println("generated candidate appended to list : ", i) + zm.PendingCandidates[target] = append(zm.PendingCandidates[target], i) + } else { + logger.Println("generated candidate : ", i) + if iceCandidateErr := cb(target, i); iceCandidateErr != nil { + logger.Println(iceCandidateErr) + } + } + return + }) + }) + return +} + +func (zm *ZoneManager) HandleRennegotiationOffer(from string, sdp string) (err error) { + err = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + if _, ok := zm.RTCPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + zm.RTCPeerConnections[from].makingOfferLock.Lock() + if zm.RTCPeerConnections[from].makingOffer { + zm.RTCPeerConnections[from].makingOfferLock.Unlock() + err = fmt.Errorf("already making an offer or state is stable") + return + } + zm.RTCPeerConnections[from].makingOfferLock.Unlock() + if err = zm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { + return + } + localSd, err := zm.RTCPeerConnections[from].CreateAnswer(nil) + if err != nil { + return + } + if err = zm.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil { + return + } + if err = zm.stream.Send(&Request{ + Type: string(ZONE_WEBRTC_RENNEGOTIATION_ANSWER), + From: zm.ID, + Token: "", + Payload: map[string]string{ + "to": from, + "sdp": localSd.SDP, + }, + }); err != nil { + logger.Println(err) + return + } + return + }) + return +} + +func (zm *ZoneManager) HandleRennegotiationAnswer(from string, sdp string) (err error) { + _ = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + if _, ok := zm.RTCPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + err = zm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) + return + }) + return +} + +func (zm *ZoneManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { + _ = atomicallyExecute(zm.candidateFlag, func() (err error) { + if candidate != nil { + if connection, ok := zm.RTCPeerConnections[from]; ok { + err = connection.AddICECandidate(*candidate) + } + } + return + }) + return +} + +func (zm *ZoneManager) HandleLeavingMember(id string, zoneId string) (err error) { + if err = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + if _, ok := zm.RTCPeerConnections[id]; !ok { + err = fmt.Errorf("no correponding peerconnection for id %s", id) + return + } + return + }); err != nil { + return + } + if err = atomicallyExecute(zm.zoneFlag, func() (err error) { + if zone, ok := zm.Zones[zoneId]; ok { + for _, handlersPublishers := range zone.ZoneRequestScheduler.handlersPublishers { + handlersPublishers <- &ZoneRequest{ + ReqType: LEAVE_ZONE, + From: "node", + Payload: map[string]interface{}{ + "userId": id, + }, + } + } + if err = atomicallyExecute(zone.DataChannelsFlag, func() (err error) { + defer delete(zone.DataChannels, id) + if dataChannel, ok := zone.DataChannels[id]; ok { + if err = dataChannel.DataChannel.Close(); err != nil { + return + } + } + return + }); err != nil { + return + } + } else { + err = fmt.Errorf("no corresponding zone for zoneId %s", zoneId) + } + return + }); err != nil { + return + } + if err = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + if _, ok := zm.RTCPeerConnections[id]; ok { + defer delete(zm.RTCPeerConnections, id) + if err = zm.RTCPeerConnections[id].Close(); err != nil { + return + } + } + return + }); err != nil { + return + } + return +} + +func (zm *ZoneManager) negotiate(target string, zoneId string) { + _ = atomicallyExecute(zm.peerConnectionFlag, func() (err error) { + if _, ok := zm.RTCPeerConnections[target]; !ok { + return + } + zm.RTCPeerConnections[target].makingOfferLock.Lock() + zm.RTCPeerConnections[target].makingOffer = true + zm.RTCPeerConnections[target].makingOfferLock.Unlock() + defer func() { + zm.RTCPeerConnections[target].makingOfferLock.Lock() + zm.RTCPeerConnections[target].makingOffer = false + zm.RTCPeerConnections[target].makingOfferLock.Unlock() + }() + return + }) +} diff --git a/zoneMediaHandler.go b/zoneMediaHandler.go new file mode 100644 index 0000000..3b3f8fe --- /dev/null +++ b/zoneMediaHandler.go @@ -0,0 +1 @@ +package localserver diff --git a/zoneRequestScheduler.go b/zoneRequestScheduler.go new file mode 100644 index 0000000..9f8bf7b --- /dev/null +++ b/zoneRequestScheduler.go @@ -0,0 +1,137 @@ +package localserver + +import ( + "context" + "fmt" +) + +type ZoneRequestScheduler struct { + handlersPublishers []chan<- *ZoneRequest +} + +type ZoneRequest struct { + ReqType string `json:"reqType"` + Payload map[string]interface{} `json:"payload"` + From string `json:"from"` +} + +type ZoneResponse struct { + Type string `json:"type"` + To string `json:"to"` + From string `json:"from"` + Payload map[string]interface{} `json:"payload"` +} + +type ZoneRequestHandler interface { + Init(ctx context.Context, authorizedMembers []string) (err error) + Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) + handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) +} + +func verifyFieldsString(payload map[string]interface{}, fields ...string) (err error) { + for _, field := range fields { + if _, ok := payload[field]; !ok { + err = fmt.Errorf("no field %s in payload", field) + return + } else if _, ok := payload[field].(string); !ok { + err = fmt.Errorf("field %s in payload is not a string", field) + return + } + } + return +} + +func verifyFieldsBool(payload map[string]interface{}, fields ...string) (err error) { + for _, field := range fields { + if _, ok := payload[field]; !ok { + err = fmt.Errorf("no field %s in payload", field) + return + } else if _, ok := payload[field].(bool); !ok { + err = fmt.Errorf("field %s in payload is not a bool", field) + return + } + } + return +} + +func verifyFieldsFloat64(payload map[string]interface{}, fields ...string) (err error) { + for _, field := range fields { + if _, ok := payload[field]; !ok { + err = fmt.Errorf("no field %s in payload", field) + return + } else if _, ok := payload[field].(float64); !ok { + err = fmt.Errorf("field %s in payload is not a float64", field) + return + } + } + return +} + +func verifyFieldsSliceInterface(payload map[string]interface{}, fields ...string) (err error) { + for _, field := range fields { + if _, ok := payload[field]; !ok { + err = fmt.Errorf("no field %s in payload", field) + return + } else if _, ok := payload[field].([]interface{}); !ok { + err = fmt.Errorf("field %s in payload is not a []interface{}", field) + return + } + } + return +} + +func NewZoneRequestScheduler(authorizedMembers []string, handlers ...ZoneRequestHandler) (zoneRequestScheduler *ZoneRequestScheduler, handlerErrCh chan error) { + zoneRequestScheduler = new(ZoneRequestScheduler) + zoneRequestScheduler.handlersPublishers = make([]chan<- *ZoneRequest, 0) + handlerErrCh = make(chan error) + reqChans := []chan *ZoneRequest{} + for _, handler := range handlers { + publisher := make(chan *ZoneRequest) + zoneRequestScheduler.handlersPublishers = append(zoneRequestScheduler.handlersPublishers, publisher) + reqChan, done, errCh := handler.Subscribe(context.Background(), publisher) + go func(done <-chan struct{}, errCh <-chan error) { + for { + select { + case <-done: + return + case handlerErrCh <- <-errCh: + } + } + }(done, errCh) + reqChans = append(reqChans, reqChan) + } + + for _, reqChan := range reqChans { + go func(reqChan <-chan *ZoneRequest) { + done, errCh := zoneRequestScheduler.Schedule(reqChan) + for { + select { + case <-done: + return + case e := <-errCh: + logger.Println("from there", e) + } + } + }(reqChan) + } + for i, handler := range handlers { + if ierr := handler.Init(context.Background(), authorizedMembers); ierr != nil { + logger.Println(ierr) + } + logger.Println("init done for handler", i) + } + return +} + +func (zrs *ZoneRequestScheduler) Schedule(reqChan <-chan *ZoneRequest) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + for req := range reqChan { + for _, publisher := range zrs.handlersPublishers { + publisher <- req + } + } + done <- struct{}{} + }() + return +} diff --git a/zoneUsersDBHandler.go b/zoneUsersDBHandler.go new file mode 100644 index 0000000..9cfba67 --- /dev/null +++ b/zoneUsersDBHandler.go @@ -0,0 +1,209 @@ +package localserver + +import ( + "encoding/json" + "path/filepath" + + "github.com/dgraph-io/badger/v3" +) + +type User struct { + ID string `json:"id"` + Name string `json:"name"` + ChatRights string `json:"chatRights"` + AudioChannelRights string `json:"audioChannelRights"` + VideoChannelRights string `json:"videoChannelRights"` + MediaRights string `json:"mediaRights"` + FileRights string `json:"fileRights"` + KnownChatsId []string `json:"knownChatsId"` + KnownAudioChannelsId []string `json:"knownAudioChannelsId"` + KnownVideoChannelsId []string `json:"knownVideoChannelsId"` + KnownMediaFolderId []string `json:"knownMediaFolderId"` + KnownFileFolderId []string `json:"knownFileFolderId"` + Admin bool `json:"admin"` + Owner bool `json:"owner"` +} + +type ZoneUsersDBHandler struct { + ZoneID string + db func(func(*badger.DB) (err error)) (err error) +} + +func NewZoneUsersDBHandler(zoneId string, owner string, authorizedMembers []string, init bool) (zoneChatDBHandler *ZoneUsersDBHandler, err error) { + db := func(f func(*badger.DB) (err error)) (err error) { + db, err := badger.Open(badger.DefaultOptions(filepath.Join("data", "zones", zoneId, "users")).WithLogger(dbLogger)) + if err != nil { + return + } + defer db.Close() + err = f(db) + return + } + if init { + admin := &User{ + ID: owner, + Name: owner, + ChatRights: "", + AudioChannelRights: "", + VideoChannelRights: "", + MediaRights: "", + FileRights: "", + KnownChatsId: []string{}, + KnownAudioChannelsId: []string{}, + KnownVideoChannelsId: []string{}, + KnownMediaFolderId: make([]string, 0), + KnownFileFolderId: make([]string, 0), + Admin: true, + Owner: true, + } + bs, jsonErr := json.Marshal(admin) + if jsonErr != nil { + return nil, jsonErr + } + if err = db(func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + err = txn.Set([]byte(owner), bs) + return + }) + return + }); err != nil { + return + } + } + if err = db(func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + for _, member := range authorizedMembers { + if member != owner { + if _, berr := txn.Get([]byte(member)); berr == badger.ErrKeyNotFound { + logger.Println("creating new user", member) + user := &User{ + ID: member, + Name: member, + ChatRights: "", + AudioChannelRights: "", + VideoChannelRights: "", + MediaRights: "", + FileRights: "", + KnownChatsId: []string{}, + KnownAudioChannelsId: []string{}, + KnownVideoChannelsId: []string{}, + KnownMediaFolderId: make([]string, 0), + KnownFileFolderId: make([]string, 0), + Admin: false, + Owner: false, + } + bs, jsonErr := json.Marshal(user) + if jsonErr != nil { + return jsonErr + } + if err = txn.Set([]byte(member), bs); err != nil { + return + } + } + } + } + return nil + }) + return + }); err != nil { + return + } + zoneChatDBHandler = &ZoneUsersDBHandler{ + db: db, + ZoneID: zoneId, + } + return +} + +func (zcdbh *ZoneUsersDBHandler) AddNewUser(user *User) (err error) { + bs, err := json.Marshal(user) + if err != nil { + return + } + if err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) error { + if _, notFoundErr := txn.Get([]byte(user.ID)); notFoundErr == badger.ErrKeyNotFound { + if updateErr := txn.Set([]byte(user.ID), bs); updateErr != nil { + return updateErr + } + } + return nil + }) + return + }); err != nil { + return + } + return +} + +func (zcdbh *ZoneUsersDBHandler) DeleteUser(id string) (err error) { + err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + err = txn.Delete([]byte(id)) + return + }) + return + }) + return +} + +func (zcdbh *ZoneUsersDBHandler) ListUsers(lastIndex int, limit int) (users []*User, err error) { + err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) error { + opt := badger.DefaultIteratorOptions + opt.Reverse = true + it := txn.NewIterator(opt) + defer it.Close() + users = make([]*User, 0) + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + if err = item.Value(func(val []byte) error { + var user *User + if jsonErr := json.Unmarshal(val, &user); jsonErr != nil { + return jsonErr + } + users = append(users, user) + return nil + }); err != nil { + return err + } + } + return nil + }) + return + }) + return +} + +func (zcdbh *ZoneUsersDBHandler) GetUser(id string) (user *User, err error) { + err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(id)) + if err != nil { + return err + } + err = item.Value(func(val []byte) (err error) { + err = json.Unmarshal(val, &user) + return + }) + return err + }) + return + }) + return +} + +func (zcdbh *ZoneUsersDBHandler) ModifyUser(id string, user *User) (err error) { + bs, err := json.Marshal(user) + if err != nil { + return + } + err = zcdbh.db(func(d *badger.DB) (err error) { + err = d.Update(func(txn *badger.Txn) (err error) { + err = txn.Set([]byte(id), bs) + return + }) + return + }) + return +} diff --git a/zoneUsersHandler.go b/zoneUsersHandler.go new file mode 100644 index 0000000..dc724ae --- /dev/null +++ b/zoneUsersHandler.go @@ -0,0 +1,534 @@ +package localserver + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +const ( + LIST_ZONE_MEMBERS = "list_zone_members" + LIST_ZONE_MEMBERS_RESPONSE = "list_zone_members_response" + GET_USER = "get_user" + MODIFY_USER_CHAT_RIGHTS = "modify_user_chat_rights" + ADD_KNOWN_CHAT = "add_known_chat" + REMOVE_KNOWN_CHAT = "remove_known_chat" + ADD_KNOWN_AUDIO_CHANNEL = "add_known_audio_channel" + REMOVE_KNOWN_AUDIO_CHANNEL = "remove_known_audio_channel" + ADD_KNOWN_VIDEO_CHANNEL = "add_known_video_channel" + REMOVE_KNOWN_VIDEO_CHANNEL = "remove_known_video_channel" + ADD_USER = "add_user" + REMOVE_USER = "remove_user" +) + +const ( + NEW_ZONE_USER = "new_zone_user" + REMOVED_ZONE_USER = "removed_zone_user" +) + +type ZoneUsersHandler struct { + ZoneId string + ZoneMembersId []string + DataChannels map[string]*DataChannel + Flag *uint32 + Publishers []<-chan *ZoneRequest + DB *ZoneUsersDBHandler + reqChans []chan<- *ZoneRequest +} + +type ZoneUserConfig struct { + DefaultChatsRights string `json:"defaultChatsRights"` + DefaultAudioChannelsRights string `json:"defaultAudioChannelsRights"` + DefaultVideoChannelsRights string `json:"defaultVideoChannelsRights"` + DefaultMediaRights string `json:"defaultMediaRights"` + DefaultFileRights string `json:"defaultFileRights"` + AdminRights string `json:"adminRights"` +} + +func NewZoneUsersHandler(zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, flag *uint32) (zoneUsersHandler *ZoneUsersHandler, err error) { + _, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "users")) + var zoneUsersDBHandler *ZoneUsersDBHandler + if err != nil { + if os.IsNotExist(err) { + logger.Printf("creating chat directory for zone %s...\n", zoneId) + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zoneId, "users"), 0700) + if mkdirErr != nil { + return nil, mkdirErr + } + zoneUsersDBHandler, err = NewZoneUsersDBHandler(zoneId, owner, authorizedMembers, true) + if err != nil { + return + } + file, ferr := os.Create(filepath.Join("data", "zones", zoneId, "users", "usersConfig.json")) + if ferr != nil { + return nil, ferr + } + baseConfig := ZoneUserConfig{ + DefaultChatsRights: "", + DefaultAudioChannelsRights: "", + DefaultVideoChannelsRights: "", + DefaultMediaRights: "", + DefaultFileRights: "", + AdminRights: "", + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return nil, jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return nil, writeErr + } + _ = file.Close() + if err != nil { + return + } + } else { + return + } + } else { + zoneUsersDBHandler, err = NewZoneUsersDBHandler(zoneId, owner, authorizedMembers, false) + } + if err != nil { + return + } + zoneUsersHandler = &ZoneUsersHandler{ + ZoneId: zoneId, + DataChannels: dataChannels, + ZoneMembersId: authorizedMembers, + Flag: flag, + DB: zoneUsersDBHandler, + } + return +} + +func (zuh *ZoneUsersHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { + users, err := zuh.DB.ListUsers(0, 1000) + if err != nil { + return + } + logger.Println(authorizedMembers) + for _, user := range users { + var contain bool + for _, member := range authorizedMembers { + if user.ID == member { + contain = true + break + } + } + if !contain { + logger.Println("userId", user.ID) + if rerr := zuh.RemoveUser(user.ID); rerr != nil { + logger.Println(rerr) + } + go func(userId string) { + for _, rc := range zuh.reqChans { + logger.Println("------------------------ sending to req chan ------------------------") + rc <- &ZoneRequest{ + ReqType: string(REMOVED_ZONE_AUTHORIZED_MEMBER), + From: "node", + Payload: map[string]interface{}{ + "userId": userId, + }, + } + } + }(user.ID) + } + } + return +} + +func (zuh *ZoneUsersHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { + reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error) + zuh.reqChans = append(zuh.reqChans, reqChan) + go func() { + for { + select { + case <-ctx.Done(): + done <- struct{}{} + return + case req := <-publisher: + if err := zuh.handleZoneRequest(ctx, req); err != nil { + errCh <- err + } + } + } + }() + return +} + +func (zuh *ZoneUsersHandler) AddUser(userId string) (err error) { + newUser := &User{ + ID: userId, + Name: userId, + ChatRights: "", + AudioChannelRights: "", + VideoChannelRights: "", + MediaRights: "", + FileRights: "", + KnownChatsId: []string{}, + KnownAudioChannelsId: []string{}, + KnownVideoChannelsId: make([]string, 0), + KnownMediaFolderId: make([]string, 0), + KnownFileFolderId: make([]string, 0), + } + if err = zuh.DB.AddNewUser(newUser); err != nil { + return + } + err = atomicallyExecute(zuh.Flag, func() (err error) { + users, err := zuh.DB.ListUsers(0, 10000) + if err != nil { + return + } + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: NEW_ZONE_USER, + From: "node", + To: "user", + Payload: map[string]interface{}{ + "user": newUser, + }, + }) + if jsonErr != nil { + return jsonErr + } + for _, user := range users { + if _, ok := zuh.DataChannels[user.ID]; ok { + _ = zuh.DataChannels[user.ID].DataChannel.SendText(string(bs)) + } + } + return + }) + return +} + +func (zuh *ZoneUsersHandler) RemoveUser(userId string) (err error) { + if err = zuh.DB.DeleteUser(userId); err != nil { + return + } + err = atomicallyExecute(zuh.Flag, func() (err error) { + users, err := zuh.DB.ListUsers(0, 10000) + if err != nil { + return + } + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: REMOVED_ZONE_USER, + From: "node", + To: "user", + Payload: map[string]interface{}{ + "userId": userId, + }, + }) + if jsonErr != nil { + return jsonErr + } + if _, ok := zuh.DataChannels[userId]; ok { + _ = zuh.DataChannels[userId].DataChannel.SendText(string(bs)) + } + for _, user := range users { + if _, ok := zuh.DataChannels[user.ID]; ok { + _ = zuh.DataChannels[user.ID].DataChannel.SendText(string(bs)) + } + } + return + }) + return +} + +func (zuh *ZoneUsersHandler) AddKnownChat(chatId string, userId string) (err error) { + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + for _, id := range user.KnownChatsId { + if id == chatId { + err = fmt.Errorf("user already know channel %s", chatId) + return + } + } + user.KnownChatsId = append(user.KnownChatsId, chatId) + if err = zuh.DB.ModifyUser(userId, user); err != nil { + return + } + return +} + +func (zuh *ZoneUsersHandler) RemoveKnownChat(chatId string, userId string) (err error) { + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + var index int + var contain bool + for i, id := range user.KnownChatsId { + if id == chatId { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("does not know this chat") + return + } + if len(user.KnownChatsId) < 2 { + user.KnownChatsId = make([]string, 0) + } else { + user.KnownChatsId = append(user.KnownChatsId[:index], user.KnownChatsId[index+1:]...) + } + err = zuh.DB.ModifyUser(userId, user) + return +} + +func (zuh *ZoneUsersHandler) AddKnownAudioChannel(channelId string, userId string) (err error) { + logger.Println("added new audio channel", channelId, userId) + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + for _, id := range user.KnownAudioChannelsId { + if id == channelId { + err = fmt.Errorf("user already know channel %s", channelId) + return + } + } + user.KnownAudioChannelsId = append(user.KnownAudioChannelsId, channelId) + if err = zuh.DB.ModifyUser(userId, user); err != nil { + return + } + return +} + +func (zuh *ZoneUsersHandler) RemoveKnownAudioChannel(channelId string, userId string) (err error) { + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + var index int + var contain bool + for i, id := range user.KnownAudioChannelsId { + if id == channelId { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("does not know this chat") + return + } + if len(user.KnownAudioChannelsId) < 2 { + user.KnownAudioChannelsId = make([]string, 0) + } else { + user.KnownAudioChannelsId = append(user.KnownAudioChannelsId[:index], user.KnownAudioChannelsId[index+1:]...) + } + err = zuh.DB.ModifyUser(userId, user) + return +} + +func (zuh *ZoneUsersHandler) AddKnownVideoChannel(channelId string, userId string) (err error) { + logger.Println("added new audio channel", channelId, userId) + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + for _, id := range user.KnownVideoChannelsId { + if id == channelId { + err = fmt.Errorf("user already know channel %s", channelId) + return + } + } + user.KnownVideoChannelsId = append(user.KnownVideoChannelsId, channelId) + if err = zuh.DB.ModifyUser(userId, user); err != nil { + return + } + return +} + +func (zuh *ZoneUsersHandler) RemoveKnownVideoChannel(channelId string, userId string) (err error) { + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + var index int + var contain bool + for i, id := range user.KnownVideoChannelsId { + if id == channelId { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("does not know this chat") + return + } + if len(user.KnownVideoChannelsId) < 2 { + user.KnownVideoChannelsId = make([]string, 0) + } else { + user.KnownVideoChannelsId = append(user.KnownVideoChannelsId[:index], user.KnownVideoChannelsId[index+1:]...) + } + err = zuh.DB.ModifyUser(userId, user) + return +} + +func (zuh *ZoneUsersHandler) AddKnownFolder(folderId string, userId string) (err error) { + logger.Println("added new known folder", folderId, userId) + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + for _, id := range user.KnownFileFolderId { + if id == folderId { + err = fmt.Errorf("user already know channel %s", folderId) + return + } + } + user.KnownVideoChannelsId = append(user.KnownVideoChannelsId, folderId) + if err = zuh.DB.ModifyUser(userId, user); err != nil { + return + } + return +} + +func (zuh *ZoneUsersHandler) RemoveKnownFolder(folderId string, userId string) (err error) { + user, err := zuh.DB.GetUser(userId) + if err != nil { + return + } + var index int + var contain bool + for i, id := range user.KnownFileFolderId { + if id == folderId { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("does not know this chat") + return + } + if len(user.KnownFileFolderId) < 2 { + user.KnownFileFolderId = make([]string, 0) + } else { + user.KnownFileFolderId = append(user.KnownFileFolderId[:index], user.KnownFileFolderId[index+1:]...) + } + err = zuh.DB.ModifyUser(userId, user) + return +} + +func (zuh *ZoneUsersHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) { + logger.Println("got request in zone users handler", req) + switch req.ReqType { + case REMOVE_USER: + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + err = zuh.RemoveUser(req.Payload["userId"].(string)) + case ADD_USER: + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + err = zuh.AddUser(req.Payload["userId"].(string)) + case REMOVE_KNOWN_CHAT: + if err = verifyFieldsString(req.Payload, "chatId", "userId"); err != nil { + return + } + err = zuh.RemoveKnownChat(req.Payload["chatId"].(string), req.Payload["userId"].(string)) + case ADD_KNOWN_CHAT: + if err = verifyFieldsString(req.Payload, "chatId", "userId"); err != nil { + return + } + err = zuh.AddKnownChat(req.Payload["chatId"].(string), req.Payload["userId"].(string)) + case REMOVE_KNOWN_AUDIO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zuh.RemoveKnownAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + case ADD_KNOWN_AUDIO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zuh.AddKnownAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + case REMOVE_KNOWN_VIDEO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zuh.RemoveKnownVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + case ADD_KNOWN_VIDEO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zuh.AddKnownVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + case GET_USER: + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + if _, ok := req.Payload["init"]; !ok { + err = fmt.Errorf("no field init in req payload for get user") + return + } + if _, ok := req.Payload["init"].(bool); !ok { + err = fmt.Errorf("field init is of wrong type") + return + } + user, err := zuh.DB.GetUser(req.Payload["userId"].(string)) + if err != nil { + return err + } + var answer *ZoneResponse + if req.Payload["init"].(bool) { + answer = &ZoneResponse{ + Type: "get_current_user_response", + To: "", + From: "", + Payload: map[string]interface{}{ + "user": user, + }, + } + } else { + answer = &ZoneResponse{ + Type: "get_user_response", + To: "", + From: "", + Payload: map[string]interface{}{ + "user": user, + }, + } + } + bs, err := json.Marshal(answer) + if err != nil { + return err + } + logger.Println(string(bs)) + if _, ok := zuh.DataChannels[req.From]; ok { + if err = zuh.DataChannels[req.From].DataChannel.SendText(string(bs)); err != nil { + return err + } + } + case LIST_ZONE_MEMBERS: + users, err := zuh.DB.ListUsers(0, 0) + if err != nil { + return err + } + bs, err := json.Marshal(&ZoneResponse{ + Type: LIST_ZONE_MEMBERS_RESPONSE, + To: "", + From: "", + Payload: map[string]interface{}{ + "users": users, + }, + }) + if err != nil { + return err + } + if _, ok := zuh.DataChannels[req.From]; ok { + if err = zuh.DataChannels[req.From].DataChannel.SendText(string(bs)); err != nil { + return err + } + } + case MODIFY_USER_CHAT_RIGHTS: + } + return +} diff --git a/zoneVideoChannel.go b/zoneVideoChannel.go new file mode 100644 index 0000000..4fc7fb9 --- /dev/null +++ b/zoneVideoChannel.go @@ -0,0 +1,851 @@ +package localserver + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "strconv" + sync "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/pion/rtcp" + "github.com/pion/webrtc/v3" +) + +const ( + VIDEO_CHANNEL_ACCESS_DENIED ReqType = "video_channel_access_denied" + VIDEO_CHANNEL_STOP_CALL ReqType = "video_channel_stop_call" + VIDEO_CHANNEL_ACCESS_GRANTED ReqType = "video_channel_access_granted" + VIDEO_CHANNEL_WEBRTC_OFFER ReqType = "video_channel_offer" + VIDEO_CHANNEL_WEBRTC_ANSWER ReqType = "video_channel_answer" + VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER ReqType = "video_channel_rennegotiation_offer" + VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "video_channel_rennegotiation_answer" + VIDEO_CHANNEL_WEBRTC_COUNTER_OFFER ReqType = "video_channel_webrtc_counter_offer" + VIDEO_CHANNEL_WEBRTC_CANDIDATE ReqType = "video_channel_webrtc_candidate" + VIDEO_CHANNEL_REMOVE_VIDEO ReqType = "video_channel_remove_video" + GET_VIDEO_CHANNEL_TRACKS ReqType = "video_channel_get_tracks" +) + +const ( + VIDEO_CHANNEL_USER_VIDEO_STOP = "video_channel_user_video_stop" + VIDEO_CHANNEL_USER_VIDEO_RESUME = "video_channel_user_video_resume" + VIDEO_CHANNEL_USER_MUTE = "video_channel_user_mute" + VIDEO_CHANNEL_USER_UNMUTE = "video_channel_user_unmute" + VIDEO_CHANNEL_USER_SPEAKING = "video_channel_user_speaking" + VIDEO_CHANNEL_USER_STOPPED_SPEAKING = "video_channel_user_stopped_speaking" +) + +type VideoChannel struct { + ID string `json:"id"` + Owner string `json:"owner"` + ChannelType string `json:"channelType"` + Members []string `json:"members"` + CurrentMembersId []string `json:"currentMembersId"` + CurrentMembers map[string]*VideoChannelMember `json:"currentMembers"` + localSD map[string]*webrtc.SessionDescription `json:"-"` + rtcPeerConnections map[string]*ZoneRTCPeerConnection `json:"-"` + audioTransceiver map[string][]*PeerSender `json:"-"` + videoTransceiver map[string][]*PeerSender `json:"-"` + videoChannelDataChannels map[string]*DataChannel `json:"-"` + pendingCandidates map[string][]*webrtc.ICECandidate `json:"-"` + remoteTracks map[string][]*RemoteTrack `json:"-"` + middlewares []interface{} `json:"-"` + candidateFlag *uint32 `json:"-"` + remoteTracksFlag *uint32 `json:"-"` + rtcPeerConnectionMapFlag *uint32 `json:"-"` + dataChannelMapFlag *uint32 `json:"-"` + localSDMapFlag *uint32 `json:"-"` + audioSenderFlag *uint32 `json:"-"` + videoSenderFlag *uint32 `json:"-"` +} + +type VideoChannelOnICECandidateFunc = func(string, string, *webrtc.ICECandidate) error + +func NewVideoChannel(id string, owner string, channelType string, members []string, currentMembersId []string, currentMembers map[string]*VideoChannelMember) (audioChannel *VideoChannel) { + candidateFlag := uint32(0) + remoteTracksFlag := uint32(0) + rtcPeerConnectionMapFlag := uint32(0) + dataChannelMapFlag := uint32(0) + localSDMapFlag := uint32(0) + audioSenderFlag := uint32(0) + videoSenderFlag := uint32(0) + audioChannel = &VideoChannel{ + ID: id, + Owner: owner, + ChannelType: channelType, + Members: members, + CurrentMembersId: currentMembersId, + CurrentMembers: currentMembers, + localSD: make(map[string]*webrtc.SessionDescription), + videoTransceiver: make(map[string][]*PeerSender), + rtcPeerConnections: make(map[string]*ZoneRTCPeerConnection), + audioTransceiver: make(map[string][]*PeerSender), + videoChannelDataChannels: make(map[string]*DataChannel), + pendingCandidates: make(map[string][]*webrtc.ICECandidate), + remoteTracks: make(map[string][]*RemoteTrack), + middlewares: make([]interface{}, 0), + candidateFlag: &candidateFlag, + remoteTracksFlag: &remoteTracksFlag, + rtcPeerConnectionMapFlag: &rtcPeerConnectionMapFlag, + dataChannelMapFlag: &dataChannelMapFlag, + localSDMapFlag: &localSDMapFlag, + audioSenderFlag: &audioSenderFlag, + videoSenderFlag: &videoSenderFlag, + } + return +} + +func (vc *VideoChannel) HandleOffer(ctx context.Context, channelId string, userId string, sdp string, hostId string, sendDCMessage SendDCMessageFunc, cb VideoChannelOnICECandidateFunc) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + peerConnection, err := vc.createPeerConnection(userId, vc.ID, webrtc.SDPTypeAnswer, cb, sendDCMessage) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + vc.rtcPeerConnections[userId] = &ZoneRTCPeerConnection{ + PeerConnection: peerConnection, + makingOffer: false, + makingOfferLock: &sync.Mutex{}, + negotiate: vc.negotiate, + } + return + }) + offer := webrtc.SessionDescription{ + Type: webrtc.SDPTypeOffer, + SDP: sdp, + } + if err = peerConnection.SetRemoteDescription(offer); err != nil { + errCh <- err + return + } + rawAnswer, err := peerConnection.CreateAnswer(nil) + if err != nil { + errCh <- err + return + } + _ = atomicallyExecute(vc.localSDMapFlag, func() (err error) { + vc.localSD[userId] = &rawAnswer + return + }) + _, _ = sendDCMessage(string(VIDEO_CHANNEL_WEBRTC_ANSWER), hostId, userId, map[string]interface{}{ + "to": userId, + "from": vc.ID, + "channelId": channelId, + "sdp": rawAnswer.SDP, + }) + done <- struct{}{} + logger.Println("handle offer done") + }() + return +} + +func (vc *VideoChannel) HandleCounterOffer(ctx context.Context, userId string, sendDCMessage SendDCMessageFunc) (err error) { + if err = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := vc.rtcPeerConnections[userId]; !ok { + err = fmt.Errorf("no field corresponding peer connection for id %s", userId) + return + } + logger.Println("handling counter offer") + connection := vc.rtcPeerConnections[userId] + err = atomicallyExecute(vc.localSDMapFlag, func() (err error) { + err = connection.SetLocalDescription(*vc.localSD[userId]) + return + }) + return + }); err != nil { + return + } + _ = atomicallyExecute(vc.localSDMapFlag, func() (err error) { + delete(vc.localSD, userId) + return + }) + if err = atomicallyExecute(vc.candidateFlag, func() (err error) { + for _, candidate := range vc.pendingCandidates[userId] { + logger.Println("sending candidate to", userId, candidate) + d, e := sendDCMessage(string(VIDEO_CHANNEL_WEBRTC_CANDIDATE), "", userId, map[string]interface{}{ + "from": vc.ID, + "to": userId, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }) + select { + case <-d: + case err = <-e: + return + } + } + delete(vc.pendingCandidates, userId) + return + }); err != nil { + return + } + return +} + +func (vc *VideoChannel) HandleRennegotiationOffer(from string, sdp string, sendDCMessage SendDCMessageFunc) (err error) { + err = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := vc.rtcPeerConnections[from]; !ok { + err = fmt.Errorf("no corresponding peer connection for id %s", from) + return + } + vc.rtcPeerConnections[from].makingOfferLock.Lock() + if vc.rtcPeerConnections[from].makingOffer { + vc.rtcPeerConnections[from].makingOfferLock.Unlock() + return fmt.Errorf("already making an offer or state is stable") + } + vc.rtcPeerConnections[from].makingOfferLock.Unlock() + if err = vc.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { + return + } + localSd, err := vc.rtcPeerConnections[from].CreateAnswer(nil) + if err != nil { + return + } + if err = vc.rtcPeerConnections[from].SetLocalDescription(localSd); err != nil { + return + } + d, e := sendDCMessage(string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER), vc.ID, from, map[string]interface{}{ + "from": vc.ID, + "to": from, + "sdp": localSd.SDP, + }) + select { + case <-d: + case err = <-e: + } + return + }) + return +} + +func (vc *VideoChannel) HandleRennegotiationAnswer(from string, sdp string) (err error) { + logger.Println("---------------------handling rennego answer") + err = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + // vc.rtcPeerConnections[from].makingOfferLock.Lock() + // if vc.rtcPeerConnections[from].makingOffer { + // vc.rtcPeerConnections[from].makingOfferLock.Unlock() + // return fmt.Errorf("already making an offer or state is stable") + // } + // vc.rtcPeerConnections[from].makingOfferLock.Unlock() + // if _, ok := vc.rtcPeerConnections[from]; !ok { + // err = fmt.Errorf("no corresponding peer connection for id %s", from) + // return + // } + err = vc.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) + return + }) + return +} + +func (vc *VideoChannel) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { + logger.Println("adding ice candidate", candidate) + err = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := vc.rtcPeerConnections[from]; ok && candidate != nil { + err = vc.rtcPeerConnections[from].AddICECandidate(*candidate) + } + return + }) + return +} + +func (vc *VideoChannel) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb VideoChannelOnICECandidateFunc, sendDCMessage SendDCMessageFunc) (peerConnection *webrtc.PeerConnection, err error) { + defer func() { + if r := recover(); err != nil { + logger.Printf("recover from panic : %v\n", r) + } + }() + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"}, + }, + }, + SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, + } + + peerConnection, err = webrtc.NewPeerConnection(config) + if err != nil { + return + } + logger.Println("---------------------------------------------------") + if peerType == webrtc.SDPTypeAnswer { + maxRetransmits := uint16(100) + channel, err := peerConnection.CreateDataChannel("video-channel", &webrtc.DataChannelInit{ + MaxRetransmits: &maxRetransmits, + }) + if err != nil { + return nil, err + } + channel.OnOpen(func() { + logger.Println("channel opened") + if chanErr := channel.SendText("yooo man this is open"); chanErr != nil { + logger.Println(chanErr) + } + }) + channel.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + if e := vc.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil { + logger.Println("*-------------- datachannel error: ", e) + } + }) + logger.Println("new channel for target : ", target) + _ = atomicallyExecute(vc.dataChannelMapFlag, func() (err error) { + logger.Println(target) + l := int32(0) + vc.videoChannelDataChannels[target] = &DataChannel{ + DataChannel: channel, + l: &l, + } + return + }) + } else { + peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { + _ = atomicallyExecute(vc.dataChannelMapFlag, func() (err error) { + l := int32(0) + vc.videoChannelDataChannels[target] = &DataChannel{ + DataChannel: dc, + l: &l, + } + return + }) + dc.OnOpen(func() { + logger.Printf("got a new open datachannel %s\n", dc.Label()) + }) + dc.OnMessage(func(msg webrtc.DataChannelMessage) { + var event CallEvent + if err := json.Unmarshal(msg.Data, &event); err != nil { + logger.Println(err) + return + } + if e := vc.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil { + logger.Println("*-------------- datachannel error: ", e) + } + }) + }) + } + err = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + err = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + logger.Println("------------------", vc.CurrentMembersId) + for _, id := range vc.CurrentMembersId { + logger.Println(id) + if id != target { + if _, ok := vc.remoteTracks[id]; !ok { + continue + } + for _, track := range vc.remoteTracks[id] { + transceiver, err := peerConnection.AddTransceiverFromKind(track.Track.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + if err != nil { + logger.Println("add track error") + continue + } + if err := transceiver.Sender().ReplaceTrack(track.Track); err != nil { + logger.Println("add track error") + continue + } + if track.Track.Kind() == webrtc.RTPCodecTypeVideo { + _ = atomicallyExecute(vc.videoSenderFlag, func() (err error) { + if len(vc.videoTransceiver) == 0 { + vc.videoTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} + } else { + vc.videoTransceiver[id] = append(vc.videoTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) + } + return + }) + } else if track.Track.Kind() == webrtc.RTPCodecTypeAudio { + _ = atomicallyExecute(vc.audioSenderFlag, func() (err error) { + if len(vc.audioTransceiver) == 0 { + vc.audioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} + } else { + vc.audioTransceiver[id] = append(vc.audioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) + } + return + }) + } + logger.Println("track added", track) + } + } + } + return + }) + return + }) + peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { + if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed { + logger.Println(pcs) + //vc.HandleLeavingMember(target, squadId) + } + }) + peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { + logger.Printf("ICE connection state has changed %s\n", is.String()) + if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { + logger.Println(is) + } + }) + peerConnection.OnTrack(func(tr *webrtc.TrackRemote, r *webrtc.RTPReceiver) { + logger.Println("got new track") + defer func() { + if stopErr := r.Stop(); stopErr != nil { + logger.Println(stopErr) + } + }() + go func() { + ticker := time.NewTicker(1500 * time.Millisecond) + for range ticker.C { + if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(tr.SSRC())}}); rtcpSendErr != nil { + logger.Println(rtcpSendErr) + break + } + } + }() + uniqId := uuid.New() + i := fmt.Sprintf("%s/%s", target, uniqId.String()) + logger.Println("*************************----------------", i, "-----------------------***************") + localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(tr.Codec().RTPCodecCapability, i, i) + if newTrackErr != nil { + return + } + logger.Println(localTrack) + rtpbuf := make([]byte, 1400) + flag := int32(0) + remote := &RemoteTrack{ID: target, Track: localTrack, rdv: &flag} + _ = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + if len(vc.remoteTracks[target]) == 0 { + vc.remoteTracks[target] = []*RemoteTrack{remote} + } else { + vc.remoteTracks[target] = append(vc.remoteTracks[target], remote) + } + index := len(vc.remoteTracks[target]) + logger.Println(index, vc.remoteTracks) + return + }) + _ = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + for _, id := range vc.CurrentMembersId { + if id != target { + if _, ok := vc.rtcPeerConnections[id]; !ok { + continue + } + connection := vc.rtcPeerConnections[id] + transceiver, tranceiverErr := connection.AddTransceiverFromKind(localTrack.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + if tranceiverErr != nil { + logger.Println(tranceiverErr) + continue + } + if replaceTrackErr := transceiver.Sender().ReplaceTrack(localTrack); replaceTrackErr != nil { + logger.Println(replaceTrackErr) + continue + } + // go func() { + // rtcpBuf := make([]byte, 1500) + // for { + // if _, _, rtcpErr := transceiver.Sender().Read(rtcpBuf); rtcpErr != nil { + // return + // } + // } + // }() + if localTrack.Kind() == webrtc.RTPCodecTypeAudio { + _ = atomicallyExecute(vc.audioSenderFlag, func() (err error) { + if len(vc.audioTransceiver) == 0 { + vc.audioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} + } else { + vc.audioTransceiver[target] = append(vc.audioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) + } + return + }) + } else if localTrack.Kind() == webrtc.RTPCodecTypeVideo { + _ = atomicallyExecute(vc.videoSenderFlag, func() (err error) { + if len(vc.videoTransceiver) == 0 { + vc.videoTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} + } else { + vc.videoTransceiver[target] = append(vc.videoTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) + } + return + }) + } + go func() { + <-time.After(time.Millisecond * 500) + connection.negotiate(id, sendDCMessage) + }() + } + } + return + }) + d := make(chan struct{}) + go func() { + for { + i, _, readErr := tr.Read(rtpbuf) + if readErr != nil { + logger.Println(readErr) + break + } + f := atomic.LoadInt32(remote.rdv) + if f == 0 { + if _, writeErr := localTrack.Write(rtpbuf[:i]); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) { + logger.Println(writeErr) + break + } else { + _ = rtpbuf[:i] + } + } + } + d <- struct{}{} + }() + <-d + }) + peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { + if i == nil { + return + } + _ = atomicallyExecute(vc.candidateFlag, func() (err error) { + desc := peerConnection.RemoteDescription() + if desc == nil { + logger.Println("generated candidate appended to list : ", i) + vc.pendingCandidates[target] = append(vc.pendingCandidates[target], i) + } else { + logger.Println("generated candidate : ", i) + if iceCandidateErr := cb(from, target, i); iceCandidateErr != nil { + logger.Println(iceCandidateErr) + } + } + return + }) + }) + peerConnection.OnNegotiationNeeded(func() { + logger.Println("---------------- rennego is needed -----------") + // _ = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + // for _, id := range vc.CurrentMembersId { + // logger.Println("----------------- sending renego to peer with id", id) + // if _, ok := vc.rtcPeerConnections[id]; !ok { + // continue + // } + // if peerConnection.SignalingState() == webrtc.SignalingStateStable { + // localSd, localSdErr := peerConnection.CreateOffer(nil) + // if localSdErr != nil { + // logger.Println(localSdErr) + // return localSdErr + // } + // if err = peerConnection.SetLocalDescription(localSd); err != nil { + // logger.Println(err) + // return + // } + // d, e := sendDCMessage(string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), vc.ID, id, map[string]interface{}{ + // "from": vc.ID, + // "to": id, + // "sdp": localSd.SDP, + // }) + // select { + // case <-d: + // case err = <-e: + // logger.Println(err) + // } + // } + // } + // return + // }) + }) + return +} + +func (vc *VideoChannel) HandleLeavingMember(id string) { + if err := atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := vc.rtcPeerConnections[id]; !ok { + err = fmt.Errorf("no corresponding peerconnection for audio channel leaving member") + } + return + }); err != nil { + logger.Println(err) + } else { + defer func() { + _ = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := vc.rtcPeerConnections[id]; ok { + if closeErr := vc.rtcPeerConnections[id].Close(); closeErr != nil { + err = closeErr + logger.Println("peer connection close error", closeErr) + } + } + delete(vc.rtcPeerConnections, id) + return + }) + }() + } + logger.Printf("peer %s is leaving the squad\n", id) + _ = atomicallyExecute(vc.dataChannelMapFlag, func() (err error) { + if _, ok := vc.videoChannelDataChannels[id]; ok { + vc.videoChannelDataChannels[id].DataChannel.Close() + } + delete(vc.videoChannelDataChannels, id) + return + }) + _ = atomicallyExecute(vc.localSDMapFlag, func() (err error) { + delete(vc.localSD, id) + return + }) + _ = atomicallyExecute(vc.candidateFlag, func() (err error) { + delete(vc.pendingCandidates, id) + return + }) + _ = atomicallyExecute(vc.audioSenderFlag, func() (err error) { + for peerId, peerSender := range vc.audioTransceiver { + if peerId != id { + logger.Println("senders", peerSender) + c := 0 + for i, sender := range peerSender { + if sender.ID == id { + if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { + logger.Println("transceiverErr occured with video", transceiverErr) + } + peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] + c++ + } + } + vc.audioTransceiver[peerId] = vc.audioTransceiver[peerId][:len(peerSender)-(c)] + logger.Println(vc.audioTransceiver[peerId]) + } + } + for _, transceiver := range vc.audioTransceiver[id] { + if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { + logger.Println("transceiver audio stop error", stopErr) + } + } + delete(vc.audioTransceiver, id) + return + }) + _ = atomicallyExecute(vc.videoSenderFlag, func() (err error) { + for peerId, peerSender := range vc.videoTransceiver { + if peerId != id { + logger.Println("senders", peerSender) + c := 0 + for i, sender := range peerSender { + if sender.ID == id { + if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { + logger.Println("transceiverErr occured with video", transceiverErr) + } + peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] + c++ + } + } + vc.videoTransceiver[peerId] = vc.videoTransceiver[peerId][:len(peerSender)-(c)] + logger.Println(vc.videoTransceiver[peerId]) + } + } + for _, transceiver := range vc.videoTransceiver[id] { + if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { + logger.Println(senderErr) + } + if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { + logger.Println("transceiver video stop error", stopErr) + } + } + delete(vc.videoTransceiver, id) + return + }) + _ = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + delete(vc.remoteTracks, id) + return + }) +} + +func (vc *VideoChannel) negotiate(target string, sendDCMessage SendDCMessageFunc) { + logger.Println("------------------negotiate is called") + _ = atomicallyExecute(vc.rtcPeerConnectionMapFlag, func() (err error) { + if _, ok := vc.rtcPeerConnections[target]; !ok { + return + } + vc.rtcPeerConnections[target].makingOfferLock.Lock() + vc.rtcPeerConnections[target].makingOffer = true + vc.rtcPeerConnections[target].makingOfferLock.Unlock() + defer func() { + vc.rtcPeerConnections[target].makingOfferLock.Lock() + vc.rtcPeerConnections[target].makingOffer = false + vc.rtcPeerConnections[target].makingOfferLock.Unlock() + }() + + for _, id := range vc.CurrentMembersId { + logger.Println("----------------- sending renego to peer with id", id) + if _, ok := vc.rtcPeerConnections[id]; !ok { + continue + } + connection := vc.rtcPeerConnections[id] + if connection.SignalingState() == webrtc.SignalingStateStable { + localSd, err := connection.CreateOffer(nil) + if err != nil { + logger.Println(err) + return err + } + if err = connection.SetLocalDescription(localSd); err != nil { + logger.Println(err) + return err + } + d, e := sendDCMessage(string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), vc.ID, id, map[string]interface{}{ + "from": vc.ID, + "to": id, + "sdp": localSd.SDP, + }) + select { + case <-d: + case err = <-e: + logger.Println(err) + } + } + } + return + }) +} + +func (vc *VideoChannel) broadcastDatachannelMessage(from string, eventId string, payload map[string]interface{}) (done chan struct{}, errCh chan error) { + done, errCh = make(chan struct{}), make(chan error) + go func() { + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: eventId, + From: vc.ID, + Payload: payload, + }) + if jsonErr != nil { + errCh <- jsonErr + return + } + if err := atomicallyExecute(vc.dataChannelMapFlag, func() (err error) { + for id, dc := range vc.videoChannelDataChannels { + if from != id { + if err = dc.DataChannel.SendText(string(bs)); err != nil { + return + } + } + } + return + }); err != nil { + errCh <- err + } + done <- struct{}{} + }() + return +} + +func (vc *VideoChannel) HandleDataChannelEvents(from string, eventId string, payload map[string]interface{}) (err error) { + switch eventId { + case VIDEO_CHANNEL_USER_VIDEO_STOP: + if err = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + if _, ok := vc.remoteTracks[from]; !ok { + err = fmt.Errorf("no corresponding remote tracks entry for id %s", from) + return + } + for _, track := range vc.remoteTracks[from] { + if track.Track.Kind() == webrtc.RTPCodecTypeVideo { + atomic.SwapInt32(track.rdv, 1) + } + } + return + }); err != nil { + return + } + done, errCh := vc.broadcastDatachannelMessage(from, VIDEO_CHANNEL_USER_VIDEO_STOP, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case VIDEO_CHANNEL_USER_VIDEO_RESUME: + if err = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + if _, ok := vc.remoteTracks[from]; !ok { + err = fmt.Errorf("no corresponding remote tracks entry for id %s", from) + return + } + for _, track := range vc.remoteTracks[from] { + if track.Track.Kind() == webrtc.RTPCodecTypeVideo { + atomic.SwapInt32(track.rdv, 0) + } + } + return + }); err != nil { + return + } + done, errCh := vc.broadcastDatachannelMessage(from, VIDEO_CHANNEL_USER_VIDEO_RESUME, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case VIDEO_CHANNEL_USER_MUTE: + if err = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + if _, ok := vc.remoteTracks[from]; !ok { + err = fmt.Errorf("no corresponding remote tracks entry for id %s", from) + return + } + for _, track := range vc.remoteTracks[from] { + if track.Track.Kind() == webrtc.RTPCodecTypeAudio { + atomic.SwapInt32(track.rdv, 1) + } + } + return + }); err != nil { + return + } + done, errCh := vc.broadcastDatachannelMessage(from, VIDEO_CHANNEL_USER_MUTE, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case VIDEO_CHANNEL_USER_UNMUTE: + if err = atomicallyExecute(vc.remoteTracksFlag, func() (err error) { + if _, ok := vc.remoteTracks[from]; !ok { + err = fmt.Errorf("no corresponding remote tracks entry for id %s", from) + return + } + for _, track := range vc.remoteTracks[from] { + if track.Track.Kind() == webrtc.RTPCodecTypeAudio { + atomic.SwapInt32(track.rdv, 0) + } + } + return + }); err != nil { + return + } + done, errCh := vc.broadcastDatachannelMessage(from, VIDEO_CHANNEL_USER_UNMUTE, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case VIDEO_CHANNEL_USER_SPEAKING: + done, errCh := vc.broadcastDatachannelMessage(from, VIDEO_CHANNEL_USER_SPEAKING, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + case VIDEO_CHANNEL_USER_STOPPED_SPEAKING: + done, errCh := vc.broadcastDatachannelMessage(from, VIDEO_CHANNEL_USER_STOPPED_SPEAKING, map[string]interface{}{ + "userId": from, + }) + select { + case <-done: + case err = <-errCh: + } + } + return +} diff --git a/zoneVideoChannelsHandler.go b/zoneVideoChannelsHandler.go new file mode 100644 index 0000000..d7b2077 --- /dev/null +++ b/zoneVideoChannelsHandler.go @@ -0,0 +1,1038 @@ +package localserver + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + "strconv" + + "github.com/pion/webrtc/v3" +) + +const ( + JOIN_VIDEO_CHANNEL = "join_video_channel" + LEAVE_VIDEO_CHANNEL = "leave_video_channel" + LIST_VIDEO_CHANNELS = "list_video_channels" + GET_VIDEO_CHANNELS = "get_video_channels" + GET_VIDEO_CHANNEL = "get_video_channel" + CREATE_VIDEO_CHANNEL = "create_video_channel" + DELETE_VIDEO_CHANNEL = "delete_video_channel" + EDIT_VIDEO_CHANNEL_TYPE = "edit_video_channel_type" + EDIT_VIDEO_CHANNEL_NAME = "edit_video_channel_name" + ADD_VIDEO_CHANNEL_MEMBERS = "add_video_channel_members" + REMOVE_VIDEO_CHANNEL_MEMBER = "remove_video_channel_member" +) + +const ( + USER_JOINED_VIDEO_CHANNEL = "user_joined_video_channel" + USER_LEFT_VIDEO_CHANNEL = "user_left_video_channel" +) + +type VideoChannelMember struct { +} + +type VideoChannelConfig struct { + ID string `json:"id"` + Owner string `json:"owner"` + ChannelType string `json:"channelType"` + CurrentMembersId []string + Members []string `json:"members"` +} + +type ZoneVideoChannelsHandler struct { + ZoneId string + HostId string + ZoneMembersId []string + DataChannels map[string]*DataChannel + DataChannelsFlag *uint32 + VideoChannelsFlag *uint32 + VideoChannels map[string]*VideoChannel + reqChans []chan<- *ZoneRequest +} + +const MEETING string = "meeting" + +func NewZoneVideoChannelsHandler(hostId string, zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (zoneVideoChannelsHandler *ZoneVideoChannelsHandler, err error) { + var dirs []fs.DirEntry + dirs, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "videoChannels")) + if err != nil { + if os.IsNotExist(err) { + logger.Printf("creating videoChannels directory for zone %s...\n", zoneId) + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zoneId, "videoChannels", MEETING), 0700) + if mkdirErr != nil { + return nil, mkdirErr + } + file, ferr := os.Create(filepath.Join("data", "zones", zoneId, "videoChannels", MEETING, "videoChannelConfig.json")) + if ferr != nil { + return nil, ferr + } + baseConfig := VideoChannelConfig{ + ID: MEETING, + Owner: owner, + ChannelType: "public", + CurrentMembersId: make([]string, 0), + Members: make([]string, 0), + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return nil, jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return nil, writeErr + } + _ = file.Close() + dirs, err = os.ReadDir(filepath.Join("data", "zones", zoneId, "videoChannels")) + if err != nil { + return nil, err + } + } else { + return + } + } + videoChannels := make(map[string]*VideoChannel) + for _, videoChannel := range dirs { + var bs []byte + bs, err = os.ReadFile(filepath.Join("data", "zones", zoneId, "videoChannels", videoChannel.Name(), "videoChannelConfig.json")) + if err != nil { + return nil, err + } + logger.Println(string(bs)) + var vcc VideoChannelConfig + if err = json.Unmarshal(bs, &vcc); err != nil { + return nil, err + } + logger.Println("videoChannels data :", vcc.ID, vcc.ChannelType, vcc.Owner, vcc.Members) + vc := NewVideoChannel(vcc.ID, vcc.Owner, vcc.ChannelType, vcc.Members, make([]string, 0), make(map[string]*VideoChannelMember)) + videoChannels[vc.ID] = vc + } + videoChannelFlag := uint32(0) + zoneVideoChannelsHandler = &ZoneVideoChannelsHandler{ + ZoneId: zoneId, + HostId: hostId, + ZoneMembersId: authorizedMembers, + DataChannels: dataChannels, + DataChannelsFlag: dataChannelFlag, + VideoChannels: videoChannels, + VideoChannelsFlag: &videoChannelFlag, + } + return +} + +func (zvch *ZoneVideoChannelsHandler) sendZoneRequest(reqType string, from string, payload map[string]interface{}) { + go func() { + for _, rc := range zvch.reqChans { + rc <- &ZoneRequest{ + ReqType: reqType, + From: from, + Payload: payload, + } + } + }() +} + +func (zvch *ZoneVideoChannelsHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) { + done, errCh := make(chan struct{}), make(chan error) + go func() { + if err := atomicallyExecute(zvch.DataChannelsFlag, func() (err error) { + if _, ok := zvch.DataChannels[to]; ok { + bs, jsonErr := json.Marshal(&ZoneResponse{ + Type: reqType, + From: from, + To: to, + Payload: payload, + }) + if jsonErr != nil { + return jsonErr + } + err = zvch.DataChannels[to].DataChannel.SendText(string(bs)) + } + return + }); err != nil { + errCh <- err + return + } + done <- struct{}{} + }() + return done, errCh +} + +func (zvch *ZoneVideoChannelsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { + for _, member := range authorizedMembers { + if serr := zvch.SetAllPublicVideoChannelForUser(member); serr != nil { + logger.Println(serr) + } + } + return +} + +func (zvch *ZoneVideoChannelsHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { + reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error) + zvch.reqChans = append(zvch.reqChans, reqChan) + go func() { + for { + select { + case <-ctx.Done(): + done <- struct{}{} + return + case req := <-publisher: + if err := zvch.handleZoneRequest(ctx, req); err != nil { + errCh <- err + } + } + } + }() + return +} + +func (zvch *ZoneVideoChannelsHandler) signalCandidate(from string, to string, candidate *webrtc.ICECandidate) (err error) { + d, e := zvch.sendDataChannelMessage(string(VIDEO_CHANNEL_WEBRTC_CANDIDATE), from, to, map[string]interface{}{ + "from": from, + "to": to, + "candidate": candidate.ToJSON().Candidate, + "sdpMid": *candidate.ToJSON().SDPMid, + "sdpMlineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), + }) + select { + case <-d: + case err = <-e: + } + return +} + +func (zvch *ZoneVideoChannelsHandler) GetVideoChannels(userId string, channelsId ...interface{}) (err error) { + videoChannels := make([]*VideoChannel, 0, len(channelsId)) + for _, id := range channelsId { + if _, ok := id.(string); !ok { + err = fmt.Errorf("id of wrong type") + return + } + logger.Println("videoChannel from get videoChannels", id.(string)) + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[id.(string)]; ok { + logger.Println(zvch.VideoChannels[id.(string)]) + videoChannels = append(videoChannels, zvch.VideoChannels[id.(string)]) + } + return + }) + } + answer := &ZoneResponse{ + Type: "get_video_channels_response", + From: "", + To: "", + Payload: map[string]interface{}{ + "videoChannels": videoChannels, + }, + } + bs, jsonErr := json.Marshal(answer) + if jsonErr != nil { + return jsonErr + } + _ = atomicallyExecute(zvch.DataChannelsFlag, func() (err error) { + if _, ok := zvch.DataChannels[userId]; ok { + err = zvch.DataChannels[userId].DataChannel.SendText(string(bs)) + if err != nil { + return + } + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) ListVideoChannels() (videoChannels []*VideoChannel, err error) { + videoChannels = make([]*VideoChannel, 0) + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + for _, videoChannel := range zvch.VideoChannels { + videoChannels = append(videoChannels, videoChannel) + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) AddNewVideoChannel(channelName string, owner string, channelType string, members []interface{}) (err error) { + if _, ok := zvch.VideoChannels[channelName]; ok { + err = fmt.Errorf("an video channel with this name already exist") + return + } + mkdirErr := os.MkdirAll(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelName), 0700) + if mkdirErr != nil { + return mkdirErr + } + file, ferr := os.Create(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelName, "videoChannelConfig.json")) + if ferr != nil { + return ferr + } + m := make([]string, 0, len(members)) + for _, member := range members { + if _, ok := member.(string); ok { + m = append(m, member.(string)) + } + } + baseConfig := &VideoChannelConfig{ + ID: channelName, + Owner: owner, + ChannelType: channelType, + Members: m, + } + bs, jsonErr := json.Marshal(baseConfig) + if jsonErr != nil { + return jsonErr + } + if _, writeErr := file.WriteString(string(bs)); writeErr != nil { + return writeErr + } + err = file.Close() + if err != nil { + return err + } + var vcc VideoChannelConfig + if err = json.Unmarshal(bs, &vcc); err != nil { + return err + } + vc := NewVideoChannel(vcc.ID, vcc.Owner, vcc.ChannelType, vcc.Members, make([]string, 0), make(map[string]*VideoChannelMember)) + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + vc.CurrentMembers = make(map[string]*VideoChannelMember) + zvch.VideoChannels[vc.ID] = vc + return + }) + newVideoChannelForMembers := func(members []string) (err error) { + for _, member := range members { + zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelName, + }) + done, e := zvch.sendDataChannelMessage("get_video_channels_response", "node", member, map[string]interface{}{ + "videoChannels": []*VideoChannel{zvch.VideoChannels[channelName]}, + }) + select { + case <-done: + case err = <-e: + return + } + } + return + } + switch vc.ChannelType { + case BROADCAST: + fallthrough + case PUBLIC: + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + err = newVideoChannelForMembers(zvch.ZoneMembersId) + return + }) + case PRIVATE: + err = newVideoChannelForMembers(vc.Members) + } + return +} + +func (zvch *ZoneVideoChannelsHandler) DeleteVideoChannel(channelId string) (err error) { + if err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + defer delete(zvch.VideoChannels, channelId) + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no corresponding video channel") + return + } + removeKnownVideoChannels := func(members []string) (err error) { + for _, member := range members { + zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zvch.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", member, map[string]interface{}{ + "channelId": channelId, + "userId": member, + }) + select { + case <-done: + continue + case err = <-e: + return + } + } + return + } + switch zvch.VideoChannels[channelId].ChannelType { + case BROADCAST: + fallthrough + case PUBLIC: + err = removeKnownVideoChannels(zvch.ZoneMembersId) + case PRIVATE: + err = removeKnownVideoChannels(zvch.VideoChannels[channelId].Members) + } + return + }); err != nil { + return + } + if err = os.RemoveAll(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId)); err != nil { + return + } + return +} + +func (zvch *ZoneVideoChannelsHandler) EditVideoChannelName(channelId string, newVideoChannelId string) (err error) { + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding videoChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId)) + if err != nil { + return + } + var videoChannelConfig VideoChannelConfig + if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { + return + } + videoChannelConfig.ID = newVideoChannelId + bs, err = json.Marshal(&videoChannelConfig) + if err = os.Rename(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId), filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", newVideoChannelId)); err != nil { + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", newVideoChannelId), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + videoChannel := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + defer delete(zvch.VideoChannels, channelId) + zvch.VideoChannels[newVideoChannelId] = videoChannel + updateKnownVideoChannels := func(members []string) (err error) { + for _, member := range members { + zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": newVideoChannelId, + }) + zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zvch.sendDataChannelMessage(CHAT_NAME_EDITED, "node", member, map[string]interface{}{ + "formerVideoChannelId": channelId, + "newVideoChannelId": newVideoChannelId, + }) + select { + case <-done: + case channelErr := <-e: + logger.Println(channelErr) + } + } + return + } + switch videoChannel.ChannelType { + case BROADCAST: + fallthrough + case PUBLIC: + err = updateKnownVideoChannels(zvch.ZoneMembersId) + case PRIVATE: + err = updateKnownVideoChannels(videoChannel.Members) + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) EditVideoChannelType(channelId string, channelType string) (err error) { + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding videoChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) + if err != nil { + return + } + var videoChannelConfig VideoChannelConfig + if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { + return + } + videoChannelConfig.ChannelType = channelType + bs, err = json.Marshal(&videoChannelConfig) + f, err := os.OpenFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + videoChannel := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) + switch channelType { + case BROADCAST: + fallthrough + case PUBLIC: + var members = []string{} + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + members = append(members, zvch.ZoneMembersId...) + return + }) + for _, member := range zvch.ZoneMembersId { + if pubErr := zvch.SetVideoChannelPublicForUser(channelId, member); pubErr != nil { + logger.Println(pubErr) + } + } + case PRIVATE: + var members = []string{} + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + members = append(members, zvch.ZoneMembersId...) + return + }) + for _, member := range zvch.ZoneMembersId { + if pubErr := zvch.SetVideoChannelPrivateForUser(channelId, member); pubErr != nil { + logger.Println(pubErr) + } + } + } + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + zvch.VideoChannels[channelId] = videoChannel + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) AddVideoChannelsMembers(channelId string, members []interface{}) (err error) { + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding videoChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) + if err != nil { + return + } + var videoChannelConfig VideoChannelConfig + if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { + return + } + logger.Printf("%s - %s - %s -%v\n", videoChannelConfig.ID, videoChannelConfig.ChannelType, videoChannelConfig.Owner, members) + addedMembers := make([]string, 0) +memberLoop: + for _, videoChannelMember := range members { + logger.Println("entering broadcast loop") + if _, ok := videoChannelMember.(string); !ok { + continue + } + for _, member := range videoChannelConfig.Members { + if member == videoChannelMember { + continue memberLoop + } + } + videoChannelConfig.Members = append(videoChannelConfig.Members, videoChannelMember.(string)) + addedMembers = append(addedMembers, videoChannelMember.(string)) + logger.Println("sending zone request", ADD_KNOWN_VIDEO_CHANNEL) + zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": videoChannelMember, + "channelId": channelId, + }) + logger.Println("--------------done") + } + bs, err = json.Marshal(&videoChannelConfig) + if err != nil { + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return err + } + if _, err = f.Write(bs); err != nil { + return err + } + vc := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + zvch.VideoChannels[channelId] = vc + return + }) +broadcastLoop: + for _, member := range vc.Members { + for _, m := range addedMembers { + if member == m { + done, e := zvch.sendDataChannelMessage(ADDED_IN_CHAT, "node", member, map[string]interface{}{ + "videoChannel": vc, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + continue broadcastLoop + } + if _, ok := zvch.DataChannels[member]; ok { + done, e := zvch.sendDataChannelMessage(CHAT_MEMBER_ADDED, "node", member, map[string]interface{}{ + "userId": m, + "channelId": vc.ID, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + } + } + } + return +} + +func (zvch *ZoneVideoChannelsHandler) RemoveVideoChannelMember(channelId string, channelMember string) (err error) { + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no coresponding videoChannel") + return + } + bs, err := os.ReadFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) + if err != nil { + return + } + var videoChannelConfig VideoChannelConfig + if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { + logger.Println(string(bs)) + logger.Println("json error right here") + return + } + if channelMember == videoChannelConfig.Owner { + err = fmt.Errorf("you cannot remove the owner from the videoChannel") + return + } + var index int + var contain bool + for i, member := range videoChannelConfig.Members { + if member == channelMember { + index = i + contain = true + break + } + } + if !contain { + err = fmt.Errorf("member %s not in the channel %s", channelMember, channelId) + return + } + videoChannelConfig.Members = append(videoChannelConfig.Members[:index], videoChannelConfig.Members[index+1:]...) + bs, err = json.Marshal(&videoChannelConfig) + if err != nil { + logger.Println("json error there") + return + } + f, err := os.OpenFile(filepath.Join("data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + defer func() { + _ = f.Close() + }() + if err != nil { + return + } + logger.Println(string(bs)) + if _, err = f.Write(bs); err != nil { + return + } + var vc *VideoChannel + _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + vc := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) + zvch.VideoChannels[channelId] = vc + return + }) + zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": channelMember, + "channelId": channelId, + }) + done, e := zvch.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", channelMember, map[string]interface{}{ + "channelId": channelId, + "userId": channelMember, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } +broadcastLoop: + for _, member := range vc.Members { + if member == channelMember { + continue broadcastLoop + } + done, e := zvch.sendDataChannelMessage(CHAT_MEMBER_REMOVED, "node", member, map[string]interface{}{ + "userId": channelMember, + "channelId": vc.ID, + }) + select { + case <-done: + case err = <-e: + logger.Println(err) + } + } + return +} + +func (zvch *ZoneVideoChannelsHandler) SetVideoChannelPrivateForUser(channelId string, member string) (err error) { + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if videoChannel, ok := zvch.VideoChannels[channelId]; ok { + var contain bool + for _, m := range videoChannel.Members { + if m == member { + contain = true + break + } + } + if !contain { + zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zvch.sendDataChannelMessage(REMOVED_FROM_CHAT, "node", member, map[string]interface{}{ + "channelId": channelId, + "userId": member, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) SetVideoChannelPublicForUser(channelId string, member string) (err error) { + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if videoChannel, ok := zvch.VideoChannels[channelId]; ok { + var contain bool + for _, m := range videoChannel.Members { + if m == member { + contain = true + break + } + } + if !contain { + zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ + "userId": member, + "channelId": channelId, + }) + done, e := zvch.sendDataChannelMessage(ADDED_IN_CHAT, "node", member, map[string]interface{}{ + "videoChannel": videoChannel, + }) + select { + case <-done: + case err = <-e: + } + } + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) SetAllPublicVideoChannelForUser(userId string) (err error) { + videoChannels, err := zvch.ListVideoChannels() + if err != nil { + return + } + for _, videoChannel := range videoChannels { + if videoChannel.ChannelType == PUBLIC || videoChannel.ChannelType == BROADCAST { + if videoChannel.ID == MEETING { + if err = zvch.AddVideoChannelsMembers(videoChannel.ID, []interface{}{userId}); err != nil { + logger.Println(err) + } + continue + } + if err = zvch.SetVideoChannelPublicForUser(videoChannel.ID, userId); err != nil { + continue + } + } + } + return +} + +func (zvch *ZoneVideoChannelsHandler) RemoveUserFromAllVideoChannels(userId string) (err error) { + videoChannels, err := zvch.ListVideoChannels() + if err != nil { + return + } + for _, videoChannel := range videoChannels { + if err = zvch.RemoveVideoChannelMember(videoChannel.ID, userId); err != nil { + continue + } + } + return +} + +func (zvch *ZoneVideoChannelsHandler) RemoveAllUserVideoChannels(userId string) (err error) { + videoChannels, err := zvch.ListVideoChannels() + if err != nil { + return + } + for _, videoChannel := range videoChannels { + if videoChannel.Owner == userId { + if derr := zvch.DeleteVideoChannel(videoChannel.ID); derr != nil { + logger.Println("**************", derr) + } + } + } + return +} + +func (zvch *ZoneVideoChannelsHandler) JoinVideoChannel(channelId string, userId string, sdp string) (err error) { + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no video channel with corresponding id") + return + } + videoChannel := zvch.VideoChannels[channelId] + videoChannel.CurrentMembersId = append(videoChannel.CurrentMembersId, userId) + videoChannel.CurrentMembers[userId] = &VideoChannelMember{} + signalMembers := func(members []string) { + for _, u := range videoChannel.Members { + done, e := zvch.sendDataChannelMessage(USER_JOINED_VIDEO_CHANNEL, "node", u, map[string]interface{}{ + "userId": userId, + "channelId": channelId, + }) + select { + case <-done: + case err := <-e: + logger.Println(err) + } + } + } + if videoChannel.ChannelType == PRIVATE { + signalMembers(videoChannel.Members) + } else { + signalMembers(zvch.ZoneMembersId) + } + d, e := videoChannel.HandleOffer(context.Background(), channelId, userId, sdp, zvch.HostId, zvch.sendDataChannelMessage, zvch.signalCandidate) + select { + case <-d: + case err = <-e: + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) LeaveVideoChannel(channelId string, userId string) (err error) { + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[channelId]; !ok { + err = fmt.Errorf("no video channel with corresponding id") + return + } + videoChannel := zvch.VideoChannels[channelId] + var index int + var contain bool + for i, v := range videoChannel.CurrentMembersId { + if v == userId { + index = i + contain = true + } + } + if !contain { + err = fmt.Errorf("this channel does not contain the provided user Id") + return + } + defer videoChannel.HandleLeavingMember(userId) + if len(videoChannel.CurrentMembersId) <= 1 { + videoChannel.CurrentMembersId = make([]string, 0) + } else { + videoChannel.CurrentMembersId = append(videoChannel.CurrentMembersId[:index], videoChannel.CurrentMembersId[index+1:]...) + } + delete(videoChannel.CurrentMembers, userId) + signalMembers := func(members []string) { + for _, u := range videoChannel.Members { + done, e := zvch.sendDataChannelMessage(USER_LEFT_VIDEO_CHANNEL, "node", u, map[string]interface{}{ + "userId": userId, + "channelId": channelId, + }) + select { + case <-done: + case err := <-e: + logger.Println(err) + } + } + } + if videoChannel.ChannelType == PRIVATE { + signalMembers(videoChannel.Members) + } else { + signalMembers(zvch.ZoneMembersId) + } + return + }) + return +} + +func (zvch *ZoneVideoChannelsHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) { + switch req.ReqType { + case LEAVE_ZONE: + logger.Println("*-----------------handling leaving zone---------------*") + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + for _, vc := range zvch.VideoChannels { + var contain bool + var id string + for _, member := range vc.CurrentMembersId { + if member == req.Payload["userId"].(string) { + id = member + contain = true + logger.Printf("*------------------id is %s--------------*\n", id) + break + } + } + if contain { + err = zvch.LeaveVideoChannel(vc.ID, id) + break + } + } + case string(REMOVED_ZONE_AUTHORIZED_MEMBER): + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + var index int + for i, m := range zvch.ZoneMembersId { + if m == req.Payload["userId"].(string) { + index = i + break + } + } + zvch.ZoneMembersId = append(zvch.ZoneMembersId[:index], zvch.ZoneMembersId[index+1:]...) + if err = zvch.RemoveAllUserVideoChannels(req.Payload["userId"].(string)); err != nil { + logger.Println("****______________", err) + } + err = zvch.RemoveUserFromAllVideoChannels(req.Payload["userId"].(string)) + case string(NEW_AUTHORIZED_ZONE_MEMBER): + if err = verifyFieldsString(req.Payload, "userId"); err != nil { + return + } + zvch.ZoneMembersId = append(zvch.ZoneMembersId, req.Payload["userId"].(string)) + err = zvch.SetAllPublicVideoChannelForUser(req.Payload["userId"].(string)) + case JOIN_VIDEO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = zvch.JoinVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string), req.Payload["sdp"].(string)) + case LEAVE_VIDEO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zvch.LeaveVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + case ADD_VIDEO_CHANNEL_MEMBERS: + if err = verifyFieldsString(req.Payload, "channelId"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zvch.AddVideoChannelsMembers(req.Payload["channelId"].(string), req.Payload["members"].([]interface{})) + case REMOVE_VIDEO_CHANNEL_MEMBER: + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = zvch.RemoveVideoChannelMember(req.Payload["channelId"].(string), req.Payload["userId"].(string)) + if err != nil { + logger.Println("an error occured in add known videoChannel", err) + return + } + case EDIT_VIDEO_CHANNEL_NAME: + if err = verifyFieldsString(req.Payload, "channelId", "newVideoChannelId"); err != nil { + return + } + if err = zvch.EditVideoChannelName(req.Payload["channelId"].(string), req.Payload["newVideoChannelId"].(string)); err != nil { + return + } + case EDIT_VIDEO_CHANNEL_TYPE: + if err = verifyFieldsString(req.Payload, "channelId", "channelType"); err != nil { + return + } + if err = zvch.EditVideoChannelType(req.Payload["channelId"].(string), req.Payload["channelType"].(string)); err != nil { + return + } + case DELETE_VIDEO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId"); err != nil { + return + } + err = zvch.DeleteVideoChannel(req.Payload["channelId"].(string)) + case CREATE_VIDEO_CHANNEL: + if err = verifyFieldsString(req.Payload, "channelId", "owner", "channelType"); err != nil { + return + } + if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { + return + } + err = zvch.AddNewVideoChannel(req.Payload["channelId"].(string), req.Payload["owner"].(string), req.Payload["channelType"].(string), req.Payload["members"].([]interface{})) + case GET_VIDEO_CHANNELS: + if err = verifyFieldsSliceInterface(req.Payload, "channelsId"); err != nil { + return + } + err = zvch.GetVideoChannels(req.From, req.Payload["channelsId"].([]interface{})...) + case string(VIDEO_CHANNEL_WEBRTC_COUNTER_OFFER): + logger.Println("handling video channel counter offer") + if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { + return + } + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zvch.VideoChannels[req.Payload["channelId"].(string)].HandleCounterOffer(ctx, req.Payload["userId"].(string), zvch.sendDataChannelMessage) + return + }) + case string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zvch.VideoChannels[req.Payload["channelId"].(string)].HandleRennegotiationOffer(req.Payload["userId"].(string), req.Payload["sdp"].(string), zvch.sendDataChannelMessage) + return + }) + case string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER): + if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { + return + } + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zvch.VideoChannels[req.Payload["channelId"].(string)].HandleRennegotiationAnswer(req.Payload["userId"].(string), req.Payload["sdp"].(string)) + return + }) + case string(VIDEO_CHANNEL_WEBRTC_CANDIDATE): + logger.Println("handling video channel webrtc candidate") + logger.Println(req.Payload) + if err = verifyFieldsString(req.Payload, FROM, "candidate", "sdpMlineIndex", "sdpMid", "channelId", "userId"); err != nil { + return + } + logger.Println(req.Payload) + i, convErr := strconv.Atoi(req.Payload["sdpMlineIndex"].(string)) + if convErr != nil { + return convErr + } + sdpMlineIndex := uint16(i) + sdpMid := req.Payload["sdpMid"].(string) + logger.Println(sdpMid, sdpMlineIndex) + err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { + if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { + err = fmt.Errorf("no channel corresponging the one requested") + return + } + err = zvch.VideoChannels[req.Payload["channelId"].(string)].AddCandidate(&webrtc.ICECandidateInit{ + Candidate: req.Payload["candidate"].(string), + SDPMid: &sdpMid, + SDPMLineIndex: &sdpMlineIndex, + }, req.Payload["userId"].(string)) + return + }) + default: + logger.Println("video channel handler still in process of implementation") + } + return +}