commit 5e95736e76147c74c19a6eeacef4dae4541252db Author: Lois Bibehe Date: Wed Dec 8 15:58:40 2021 +0100 Initial commit of package localserver 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 +}