From b4a7f4e53ffe0eb9a5cc9d73c26da9246267664b Mon Sep 17 00:00:00 2001 From: wyf5229 Date: Thu, 10 Sep 2020 16:08:06 +0800 Subject: [PATCH] master --- .gitignore | 2 + cert.crt | 58 +++++ common.go | 14 ++ login.go | 25 ++ management.go | 251 +++++++++++++++++++ monflo.go | 583 +++++++++++++++++++++++++++++++++++++++++++++ private.key | 27 +++ request_struct.go | 29 +++ response_struct.go | 60 +++++ welcome_struct.go | 57 +++++ 10 files changed, 1106 insertions(+) create mode 100644 .gitignore create mode 100644 cert.crt create mode 100644 common.go create mode 100644 login.go create mode 100644 management.go create mode 100644 monflo.go create mode 100644 private.key create mode 100644 request_struct.go create mode 100644 response_struct.go create mode 100644 welcome_struct.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b877a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.exe +.idea diff --git a/cert.crt b/cert.crt new file mode 100644 index 0000000..4fe1dd3 --- /dev/null +++ b/cert.crt @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIEeDCCAmCgAwIBAgIIEtc4Hk5jNvIwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE +BhMCQ04xDzANBgNVBAcTBlN1emhvdTEQMA4GA1UECgwHRXZhbl9DQTEQMA4GA1UE +CwwHRXZhbl9DQTEQMA4GA1UEAwwHRXZhbl9DQTAeFw0yMDA5MDMwMTA1MDBaFw0y +MTA5MDMwMTA1MDBaMBkxFzAVBgNVBAMMDioubWlyaWxsaXMuY29tMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwcf0gqkGW79baJ39995n1YirD4x5KtnC +3CsD1gftE/va3GZMToBsP3jdohi5TRvB/CnF6Ngv22kszDWTwLKg/K73D+5dfu8m +xVyxx2kU4uzC0NR0Z2AtumWIS05N/S53PWESBbCgPBJLcGKqLULYXf9EvEPtXebh +i68C4aGnCmWOcMCLH/VrCcwFT15eR6zsD3whv8Oqpdj5aneItpEaWd8dzBrHO69k +byfqK/GL14eLtPClVoY0EQ8z6hNqNTil6eseBdEuwytND5LJnfzarXCBxdnh74CE +u4nxZcgEIx3hoSpLJVtHiZfGXPZgtIech3LgcAcAmcaI6wtD94Z5pQIDAQABo4GI +MIGFMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFK5Br4x8HWHZOIAYSdYxEj1JW/TF +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +JwYDVR0RBCAwHoIOKi5taXJpbGxpcy5jb22CDG1pcmlsbGlzLmNvbTANBgkqhkiG +9w0BAQsFAAOCAgEARlaPieyqlQgp9xsMd6Qc59Wx90NJF8BbkXOxxP9A84Pf93D/ +oYhSWJQepDl6Q9aw4BuXBYMOrBJiOpMwbm1cv8YdCiNZUAS3mOwhgffBUwcJPnGt +/YjOulNDYu9sq09MlS6MALMZs7Dqfzh3RHzaWLsSEXZrsFUw2ONCKxjCT33Zv30v +1uKpWONRRcfPQV/b72uYrJf+ByX7gYZKSssolj1aq3qtxBitZR05EV4iWCPBp7tz +y0wQcz9+vbfE9tdo+VECuD8Rldl770VE6GZnL9/jAwLsv8VB+9z7jlvbcfwdatvv +FYE1dSPkw2eQq0/urrFv2thqISKv/qO8SsHYnXfA6gbHuz8WHH6DYc57ArLAO329 +5bdVEn5hL/rWxUx/QipjCmtlIBG1eqUSfdAKOWK3PVUC3NqwoUSBoNLxJ9xU9rJB +7xdOEP0TdXyCOhaiLYCv3FoHRfgTwrtHiSoi3hyHftxjwKGtyrFo1rS3FTdLHret +IRWIzIV7NP9gGYNZaCG+V2fQqxOFjH+lR7hvrOHP+oX2M8D6rnIPWvJtZLpnxIyF +kDPS2ch0TR5UtohXiGfO5mx/m4CVKcrcRTJGVQSkGv0VZ1cHSvxZayGgTfkOIUBu +YPHJeQBt6agjGzAUOxHzRa4xdyYIUMC6/nv8SMAIPTUEBq1GvE4kPmB48MI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFfDCCA2SgAwIBAgIIYMu9eb5hc/gwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE +BhMCQ04xDzANBgNVBAcTBlN1emhvdTEQMA4GA1UECgwHRXZhbl9DQTEQMA4GA1UE +CwwHRXZhbl9DQTEQMA4GA1UEAwwHRXZhbl9DQTAeFw0xOTExMjgwNjM3MDBaFw0y +OTExMjgwNjM3MDBaMFQxCzAJBgNVBAYTAkNOMQ8wDQYDVQQHEwZTdXpob3UxEDAO +BgNVBAoMB0V2YW5fQ0ExEDAOBgNVBAsMB0V2YW5fQ0ExEDAOBgNVBAMMB0V2YW5f +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDRsAGo/do3sqN4IQjt +QGfCebwA/DZT1xRDPbo/AuxB7KprpKEW+7sP5PnFUAavMRyX7gs9fCYS0+ed1rnx +/bZkGE3ARKR9njlCTHBQbAEBdKXL7OgqDnnvTmG2HBlS1id87TLBdKijxoCmVDoB +Y/KXPTzfBvmAW+h79h6lK05eecne0BIlq5nFuI77HZYckvspZ4KBn9STE1Sg+xRw +9RmjFFI9Dzvnd/SkvmRicCvR4h5NNviIqQp12ulUUHwzAHmbpq9HQT/9ddsrhymt +zrZJYLlflDZTAhvAalcICbNFhmA/koBPu9x7xjACK1ylkvcmLCu+vIq563lmrmgg +yYBxgSk55WFPFMxoOviDKva7kcr+OoFCu1iDGSNxS4UAnxDRxHiy+RNkLIIRTXU6 +87C8q2cwdUm8jmJiuOwEUwUdeiyNk4VyvLWNGzcTQSyz5vkijlwSpwKPDFnvRivY +6CX8Avj2ep3MXd8OVO4e2xdrxVpC7N2rTJG8m+uLeHly0MJbGoGMZrEfFvuQiCRA +dp90yOr+wioVuUqdnWWSY3h8o3Hxh0nU4RsDG3DE/e10s2TVEEWfJFt/0v0GIfki +0nNa/pqQRyr2rp/G+nC97xYqJDUr+mzKkUn1oqyZ2CBfmpIkC9A7Ex2RsT5IDpDu +vOIEY4nXXpcEsCO94OGgcAkMgwIDAQABo1IwUDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBR475cDo1XAKOtfI0l+07cgFoO3MzALBgNVHQ8EBAMCAQYwEQYJYIZI +AYb4QgEBBAQDAgAHMA0GCSqGSIb3DQEBCwUAA4ICAQCSD3MVSmIgPYYVL2RlZTAg +05ywU+jKs20xhMtzErDodWpIoUDbkeNsXACXsUOfruPJs2ey/yro4+7ioSaDeIrA +tGiNhBuZq9cIgX2vM9Dro0w/8HEQxZEBOyRqRMfbj6oFo5k5nr1ruc2kQzuFxp2p +bi19IoeIb4+rGIFE+d2HKqPKfIbwFv3X9+arrVsEWF2MV5WVsGOGV4dX+aCQ6V3D +XlvZAUg+ru2OQZ0LgCfJNzXa7wGLGC5IUGVaHEuquL+6TowTsAoN0/Jdpo2Et6jN +hRcb4rwrdvBA0bvubPjfIIVXjymb5hqbX5hdUCI8csFY3QjxoM4hPgxZmkCoxv9R +KmUo/vIsR1ZC/t2rnZjAeEMOS/F+mNt465Arab15Xw4SMd0ZXA2C058RElwGZSxO +OriiOZNCaHxGA6dCHnwFSAlQ0hJyOtPcF0VpcGLSpSESXffX3qD4miQi9HWcLdph +OlUN/Txqb6Jov6MvAbHMaCvsUE3Qdm8LxFBOn9/SPlcBveApbqSsH6Ac5xhG5tnU +A3BR6X/cn9bQveZggNZtMV/NRyqQ0F3oA9cCW7WEWuQWTse+0eLWg9eloKx+pDR4 +6qVuHGtQeqvy+fBPInjovk9REo6GoH9qM8BAWWuhOv+v85K+MSybvIBA/LM0/uvF +a2PBtACsbvJx70oiDxZ1yw== +-----END CERTIFICATE----- diff --git a/common.go b/common.go new file mode 100644 index 0000000..7340e90 --- /dev/null +++ b/common.go @@ -0,0 +1,14 @@ +package main + +import ( + "bytes" + "encoding/json" +) + +func formatJson(v interface{}) []byte { + marshal, _ := json.Marshal(v) + var b bytes.Buffer + json.Indent(&b, marshal, "", " ") + + return b.Bytes() +} diff --git a/login.go b/login.go new file mode 100644 index 0000000..f55d61e --- /dev/null +++ b/login.go @@ -0,0 +1,25 @@ +package main + +type LoginRequest struct { + Login string `json:"login"` + Password string `json:"password"` + Fingerprint string `json:"fingerprint"` +} + +type LoginResponse struct { + CreatedAt int64 `json:"created_at,omitempty"` //1599031125 + ApiKey string `json:"apikey,omitempty"` //"apikey-af461547-da12-5e33-9a99-c63d9000762b" + MacAddress string `json:"mac_address,omitempty"` //"04ea56b3f380" + Metadata string `json:"metadata,omitempty"` //"{\"os\":\"1\",\"state\":\"online\",\"streamer\":\"0\",\"type\":\"2\"}" + Fingerprint string `json:"fingerprint,omitempty"` //"fingerprint": "45c9dd6c-6925-a994-3fc3-c26cdec786ce", + Id string `json:"id,omitempty"` // "id": "device-4d4c9fba-4d11-5b10-b3b3-05346a7bd4d2", + Client string `json:"client,omitempty"` //"client": "client-8c220f6e-3432-5f41-80f2-b830f7b60658", + Name string `json:"name,omitemptys"` //"name": "DESKTOP-3H0CLPE" +} + +type AddDeviceRequest struct { + Name string `json:"name"` //DESKTOP-3H0CLPE + MacAddress string `json:"mac_address"` //04ea56b3f380 + Fingerprint string `json:"fingerprint"` + Metadata string `json:"metadata"` +} diff --git a/management.go b/management.go new file mode 100644 index 0000000..451b4c1 --- /dev/null +++ b/management.go @@ -0,0 +1,251 @@ +package main + +import ( + "github.com/gorilla/websocket" + uuid "github.com/satori/go.uuid" + "math/rand" + "strconv" + "strings" + "time" +) + +type MonfloManagement struct { + clients []*ClientInfo +} + +type ClientInfo struct { + UserName string `json:"user_name"` + Metadata string `json:"metadata"` + Name string `json:"name"` + PrivateAddr string `json:"private_addr"` + PublicAddr string `json:"public_addr"` + Key string `json:"key"` + Bitrate int `json:"bitrate"` + Formats []string `json:"formats"` //h264 h265 + Stream string `json:"stream"` + ClientStatus string `json:"client_status"` //online offline + StreamStatus string `json:"stream_status"` //ready busy + CreatedAt int64 `json:"created_at"` //1599031125 + ApiKey string `json:"apikey"` //"apikey-af461547-da12-5e33-9a99-c63d9000762b" + MacAddress string `json:"mac_address"` //"04ea56b3f380" + Fingerprint string `json:"fingerprint"` //"fingerprint": "45c9dd6c-6925-a994-3fc3-c26cdec786ce", + Id string `json:"id"` // "id": "device-4d4c9fba-4d11-5b10-b3b3-05346a7bd4d2", + Client string `json:"client"` //"client": "client-8c220f6e-3432-5f41-80f2-b830f7b60658", + wsConn *websocket.Conn + StreamId string `json:"stream_id"` //40832596627 + ClientType string `json:"client_type"` //client server +} + +func (m *MonfloManagement) checkExist(userName string, fingerprint string) bool { + if m.clients == nil { + return false + } + for _, client := range m.clients { + if client.Fingerprint == fingerprint && client.UserName == userName { + return true + } + } + return false +} + +func (m *MonfloManagement) getClient(userName string, fingerprint string) *ClientInfo { + + for _, client := range m.clients { + if client.Fingerprint == fingerprint && client.UserName == userName { + return client + } + } + return nil +} + +func (m *MonfloManagement) getClientByApiKey(apiKey string) *ClientInfo { + for _, client := range m.clients { + if client.ApiKey == apiKey { + return client + } + } + return nil +} + +func (m *MonfloManagement) getClientByFingerprint(fingerprint string) *ClientInfo { + + for _, client := range m.clients { + if client.Fingerprint == fingerprint { + return client + } + } + return nil +} + +func (m *MonfloManagement) addClient(userName string, fingerprint string) { + if !m.checkExist(userName, fingerprint) { + client := &ClientInfo{ + UserName: userName, + CreatedAt: time.Now().Unix(), + ApiKey: "apikey-" + fingerprint, + Fingerprint: fingerprint, + Id: "device-" + fingerprint, + Client: "client-" + fingerprint, + ClientStatus: "offline", + } + m.clients = append(m.clients, client) + } +} + +func (m *MonfloManagement) addServer(clientStr string) *ClientInfo { + for _, c := range m.clients { + if c.Client == clientStr { + return c + } + } + client := &ClientInfo{ + Client: clientStr, + CreatedAt: time.Now().Unix(), + } + m.clients = append(m.clients, client) + return client +} + +func (m *MonfloManagement) addDevice(request AddDeviceRequest) { + client := m.getClientByFingerprint(request.Fingerprint) + client.Name = request.Name + client.Fingerprint = request.Fingerprint + client.Metadata = request.Metadata + client.MacAddress = request.MacAddress + if strings.Contains(request.Metadata, "\"streamer\":\"1\",") { + client.ClientType = "server" + } else { + client.ClientType = "client" + } +} + +func (m *MonfloManagement) getPeers(userName string) map[string]PeerInfo { + peers := make(map[string]PeerInfo) + + if m.clients == nil { + return peers + } + for _, client := range m.clients { + if client.UserName == userName { + streams := make(map[string]StreamInfo) + var formats = []string{"h264", "h265"} + if client.Stream != "" { + streams[client.Stream] = StreamInfo{ + Id: client.Stream, + Status: client.StreamStatus, + OwnerClient: client.Client, + Name: client.Name, + PrivateAddr: client.PrivateAddr, + PublicAddr: client.PublicAddr, + Key: client.Key, + Bitrate: 500000, + Formats: formats, + } + } + peers[client.Client] = PeerInfo{ + Name: client.Name, + Status: client.ClientStatus, //online;offline + LastIP: IPInfo{ + PrivateAddr: client.PrivateAddr, + PublicAddr: client.PublicAddr, + }, + Streams: streams, + } + + } + } + return peers +} + +func (m *MonfloManagement) getDevices(userName string) []LoginResponse { + var devices []LoginResponse + + if m.clients == nil { + return devices + } + for _, client := range m.clients { + if client.UserName == userName { + device := LoginResponse{ + CreatedAt: client.CreatedAt, + ApiKey: client.ApiKey, + MacAddress: client.MacAddress, + Metadata: client.Metadata, + Fingerprint: client.Fingerprint, + Id: client.Id, + Client: client.Client, + Name: client.Name, + } + devices = append(devices, device) + } + } + return devices +} + +func (m *MonfloManagement) genId(client *ClientInfo) { + id := 10000000009 + 8*rand.Int63n(5) + for { + if !m.checkIdExist(strconv.FormatInt(id, 10)) { + break + } + id = 10000000009 + 8*rand.Int63n(10000) + } + client.StreamId = strconv.FormatInt(id, 10) +} + +func (m *MonfloManagement) genStream(client *ClientInfo) { + uid, _ := uuid.NewV4() + client.Stream = "stream-" + uid.String() + client.StreamStatus = "ready" +} + +func (m *MonfloManagement) checkIdExist(id string) bool { + for _, c := range m.clients { + if c.Id == id { + return true + } + } + return false +} + +func (m *MonfloManagement) setOffline(clientId string) { + del := -1 + for i, c := range m.clients { + if c.Client == clientId { + if c.ApiKey == "" && c.Id == "" { + del = i + } + c.ClientStatus = "offline" + } + } + if del >= 0 { + m.clients = append(m.clients[:del], m.clients[del+1:]...) + } +} + +func (m *MonfloManagement) getClientStream(stream string) *ClientInfo { + for _, c := range m.clients { + if c.Stream == stream { + return c + } + } + return nil +} + +func (m *MonfloManagement) getClientStreamId(streamId string) *ClientInfo { + for _, c := range m.clients { + if c.StreamId == streamId { + return c + } + } + return nil +} + +func (m *MonfloManagement) getUserAllClient(userName string) []*ClientInfo { + var clients []*ClientInfo + for _, c := range m.clients { + if c.UserName == userName { + clients = append(clients, c) + } + } + return clients +} diff --git a/monflo.go b/monflo.go new file mode 100644 index 0000000..ce5380b --- /dev/null +++ b/monflo.go @@ -0,0 +1,583 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/websocket" + uuid "github.com/satori/go.uuid" + "io/ioutil" + "math/rand" + "net" + "net/http" + "strings" + "sync" + "time" +) + +var ( + upgrader = websocket.Upgrader{ + //允许跨域访问 + CheckOrigin: func(r *http.Request) bool { + return true + }, + EnableCompression: false, + } + managementService = MonfloManagement{} +) + +func main() { + rand.Seed(time.Now().UnixNano()) + go stun() + http.HandleFunc("/2015-10-26/verification", verification) + http.HandleFunc("/2015-10-26/devices", device) + http.HandleFunc("/2015-10-26", wsHandler) + http.HandleFunc("/config", configHandler) + http.HandleFunc("/set", configSetHandler) + http.HandleFunc("/peer", peerHandler) + err := http.ListenAndServeTLS("", "cert.crt", "private.key", nil) + if err != nil { + fmt.Print(err) + } +} + +func configHandler(w http.ResponseWriter, r *http.Request) { + marshal, _ := json.Marshal(managementService.clients) + w.Write(marshal) +} + +func configSetHandler(w http.ResponseWriter, r *http.Request) { + var clients []*ClientInfo + data, _ := ioutil.ReadAll(r.Body) + + json.Unmarshal(data, &clients) + managementService.clients = clients +} + +func peerHandler(w http.ResponseWriter, r *http.Request) { + peers := managementService.getPeers(r.FormValue("username")) + marshal, _ := json.Marshal(peers) + w.Write(marshal) +} + +func stun() { + address := "0.0.0.0:3478" + addr, err := net.ResolveUDPAddr("udp", address) + if err != nil { + fmt.Println(err) + } + + conn, err := net.ListenUDP("udp", addr) + if err != nil { + fmt.Println(err) + } + + defer conn.Close() + + for { + // Here must use make and give the lenth of buffer + data := make([]byte, 4096) + _, rAddr, err := conn.ReadFromUDP(data) + if err != nil { + fmt.Println(err) + continue + } + //file, _ := ioutil.ReadFile("data.bin") + //_, err = conn.WriteToUDP(file, rAddr) + _, err = conn.WriteToUDP([]byte{254, 239, 1, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 6, 58, 210, 98, 46, 174, 198, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, rAddr) + if err != nil { + fmt.Println(err) + continue + } + } +} + +func wsHandler(w http.ResponseWriter, r *http.Request) { + var ( + wsConn *websocket.Conn + err error + mutex sync.Mutex + client *ClientInfo + ) + //Upgrade websocket(返回给客户端的消息) + if wsConn, err = upgrader.Upgrade(w, r, nil); err != nil { + //报错了,直接返回底层的websocket链接就会终断掉 + return + } + go func() { + for { + mutex.Lock() + err2 := wsConn.WriteMessage(websocket.PingMessage, []byte{}) + mutex.Unlock() + if err2 != nil { + wsConn.Close() + return + } + time.Sleep(1 * time.Second) + } + defer func() { + if r := recover(); r != nil { + fmt.Printf("Runtime error caught: %v", r) + } + }() + + }() + + defer func() { + if r := recover(); r != nil { + fmt.Printf("Runtime error caught: %v", r) + } + if nil != client { + managementService.setOffline(client.Client) + } + }() + mutex.Lock() + wsConn.WriteMessage(websocket.TextMessage, []byte("{\n \"event\": \"cookie_created\",\n \"data\": \"5f0389ae63c5c5962de0bf1fa7edbb9e7605da388a8ffb2f69af4c47b1fde020\"\n}")) + mutex.Unlock() + client = new(ClientInfo) + for { + messageType, p, err := wsConn.ReadMessage() + if err != nil { + fmt.Printf("WebSocket异常断开," + wsConn.RemoteAddr().String() + ",异常信息:" + err.Error() + "\n") + wsConn.Close() + return + } + if messageType == websocket.CloseMessage { + fmt.Printf("WebSocket断开," + wsConn.RemoteAddr().String() + "\n") + wsConn.Close() + return + } + fmt.Printf(string(p) + "\n") + mutex.Lock() + tempClient := wsProcess(wsConn, p, client) + mutex.Unlock() + if tempClient != nil { + client = tempClient + } + + } +} + +func wsProcess(wsConn *websocket.Conn, data []byte, client *ClientInfo) *ClientInfo { + + str := string(data) + + if len(str) == 0 { + return nil + } + if str[0:1] == "{" { + + var request MonfloRequest + json.Unmarshal(data, &request) + + if request.Uri == "/" { + if request.Headers.ApiKey != "" { + client = managementService.getClientByApiKey(request.Headers.ApiKey) + client.wsConn = wsConn + client.ClientStatus = "online" + response := WelcomeInfo{ + MonfloResponse: MonfloResponse{ + Event: "welcome", + }, + Data: WelcomeData{ + Client: client.Client, + Features: Features{ + MaxUserProfiles: 1, + MaxStreams: 128, + MaxDevices: 128, + MaxP2pResolution: 2160, + MaxP2pFrames: 60, + MaxRelayResolution: 2160, + MaxRelayFrames: 60, + Hevc: true, + SessionRecording: true, + ManagementConsole: true, + ConnectAnywhere: false, + Support: false, + CommercialUse: false, + DeviceThumbnails: false, + FileTransfer: true, + }, + Peers: managementService.getPeers(client.UserName), + Subscription: 3, + Timestamp: time.Now().Unix(), + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + wsConn.WriteMessage(websocket.TextMessage, []byte("{\"status\":200}")) + + //如果是客户端登陆,需要通知该用户下所有的服务端 + //if client.ClientType == "client" { + // streamClients := managementService.getUserAllClient(client.UserName) + // if streamClients != nil { + // for _, streamClient := range streamClients { + // if streamClient.ClientStatus == "online" && streamClient.ClientType == "server" { + // eventResponse := &ClientConnectedEvent{ + // MonfloResponse: MonfloResponse{ + // Event: "client_connected", + // }, + // Data: client.Client, + // } + // streamClient.wsConn.WriteMessage(websocket.TextMessage, formatJson(eventResponse)) + // } + // } + // } + //} + return client + } else { + //服务端匿名登录 + uid, _ := uuid.NewV4() + + response := WelcomeInfo{ + MonfloResponse: MonfloResponse{ + Event: "welcome", + }, + Data: WelcomeData{ + Client: "client-" + uid.String(), + Features: Features{ + MaxUserProfiles: 1, + MaxStreams: 128, + MaxDevices: 128, + MaxP2pResolution: 2160, + MaxP2pFrames: 60, + MaxRelayResolution: 2160, + MaxRelayFrames: 60, + Hevc: true, + SessionRecording: true, + ManagementConsole: true, + ConnectAnywhere: false, + Support: false, + CommercialUse: false, + DeviceThumbnails: false, + FileTransfer: false, + }, + Subscription: 3, + Peers: make(map[string]PeerInfo), + Timestamp: time.Now().Unix(), + }, + } + client = managementService.addServer(response.Data.Client) + client.wsConn = wsConn + client.ClientType = "server" + client.ClientStatus = "online" + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + wsConn.WriteMessage(websocket.TextMessage, []byte("{\"status\":200}")) + return client + } + } + + if request.Uri == "/devices/current" && request.Method == "PATCH" { + client.Metadata = request.Data.Metadata + response := PatchDeviceResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: LoginResponse{ + Metadata: client.Metadata, + Id: client.Id, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + } + + if request.Method == "PATCH" && strings.Contains(request.Uri, "/streams/stream-") { + streamId := request.Uri[9:] + client.Key = request.Data.Key + client.PrivateAddr = request.Data.PrivateAddr + client.PublicAddr = request.Data.PublicAddr + client.StreamStatus = request.Data.Status + response := MonfloResponse{ + Status: 200, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + + //通知所有该账号的客户端更新服务端信息 + + streamClients := managementService.getUserAllClient(client.UserName) + if streamClients != nil { + for _, streamClient := range streamClients { + if streamClient.ClientStatus == "online" && streamClient.ClientType == "client" { + response2 := &StreamResponse{ + MonfloResponse: MonfloResponse{ + Event: "stream_updated", + }, + Data: StreamInfo{ + Id: streamId, + Key: request.Data.Key, + Status: request.Data.Status, + OwnerClient: client.Client, + PrivateAddr: request.Data.PrivateAddr, + PublicAddr: request.Data.PublicAddr, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response2)) + } + } + } + + } + if request.Uri == "/devices" && request.Method == "GET" { + response := DeviceResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: LoginResponse{ + Metadata: client.Metadata, + Id: client.Id, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + } + if request.Uri == "/peers" && request.Method == "GET" { + peers := managementService.getPeers(client.UserName) + response := PeersResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: peers, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + } + + if request.Method == "POST" && request.Uri == "/incognitos" { + if request.Data.Stream != "" { + //登录账户模式 + managementService.genId(client) + } else { + //服务端匿名模式 + client.PrivateAddr = request.Data.PrivateAddr + client.PublicAddr = request.Data.PublicAddr + client.Bitrate = request.Data.Bitrate + client.Key = request.Data.Key + client.Formats = request.Data.Formats + //分配Id + managementService.genId(client) + client.Id = client.StreamId + //分配StreamId + managementService.genStream(client) + } + + response := &IncognitosResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: IncognitosData{ + Id: client.StreamId, + Stream: client.Stream, + Client: client.Client, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + } + + if request.Method == "POST" && request.Uri == "/streams" { + client.PrivateAddr = request.Data.PrivateAddr + client.PublicAddr = request.Data.PublicAddr + client.Bitrate = request.Data.Bitrate + client.Key = request.Data.Key + client.Formats = request.Data.Formats + + managementService.genStream(client) + + response := &StreamResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: StreamInfo{ + Id: client.Stream, + Status: "ready", + OwnerClient: client.Client, + Name: "0", + PrivateAddr: client.PrivateAddr, + PublicAddr: client.PublicAddr, + Key: client.Key, + Bitrate: client.Bitrate, + Formats: client.Formats, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + } + + if request.Method == "GET" && (request.Uri == "/invitations/logins" || request.Uri == "/invitations" || request.Uri == "/shortcuts") { + response := &MonfloResponse{ + Status: 200, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + } + + if request.Method == "GET" && strings.Contains(request.Uri, "/streams/stream-") { + streamCode := request.Uri[9:] + stream := managementService.getClientStream(streamCode) + if stream != nil { + response := &StreamResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: StreamInfo{ + Id: stream.Stream, + Status: "ready", + OwnerClient: stream.Client, + Name: "0", + PrivateAddr: stream.PrivateAddr, + PublicAddr: stream.PublicAddr, + Key: stream.Key, + Bitrate: stream.Bitrate, + Formats: stream.Formats, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + + response2 := &StreamReadResponse{ + MonfloResponse: MonfloResponse{ + Event: "_stream_read", + }, + Data: StreamReadData{ + Client: client.Client, + Format: request.Headers.Format, + Id: stream.Stream, + PrivateAddr: request.Headers.PrivateAddr, + PublicAddr: request.Headers.PublicAddr, + Type: request.Headers.Type, + }, + } + stream.wsConn.WriteMessage(websocket.TextMessage, formatJson(response2)) + } + + } + + if request.Method == "GET" && strings.Contains(request.Uri, "/incognitos/") { + stream := managementService.getClientStreamId(request.Uri[12:]) + if stream != nil { + response := &StreamResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: StreamInfo{ + Id: stream.Stream, + Status: "ready", + OwnerClient: stream.Client, + Name: stream.StreamId, + PrivateAddr: stream.PrivateAddr, + PublicAddr: stream.PublicAddr, + Key: stream.Key, + Bitrate: stream.Bitrate, + Formats: stream.Formats, + }, + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(response)) + + response2 := &StreamReadResponse{ + MonfloResponse: MonfloResponse{ + Event: "_stream_read", + }, + Data: StreamReadData{ + Client: client.Client, + Format: request.Headers.Format, + Id: stream.Stream, + PrivateAddr: request.Headers.PrivateAddr, + PublicAddr: request.Headers.PublicAddr, + Type: request.Headers.Type, + }, + } + + stream.wsConn.WriteMessage(websocket.TextMessage, formatJson(response2)) + } + + } + + } else if str[0:1] == "[" { + var requests []MonfloRequest + json.Unmarshal(data, &requests) + + var datas []interface{} + for _, request := range requests { + if request.Uri == "/devices" && request.Method == "GET" { + device := managementService.getDevices(client.UserName) + response := DevicesResponse{ + MonfloResponse: MonfloResponse{ + Status: 200, + }, + Data: device, + } + datas = append(datas, response) + } + if request.Uri == "/info" && request.Method == "GET" { + + response := MonfloResponse{ + Status: 200, + } + datas = append(datas, response) + } + } + wsConn.WriteMessage(websocket.TextMessage, formatJson(datas)) + } + return nil +} + +func verification(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Server", "MonfloHTTPServer") + w.Header().Set("Content-Type", "application/json") + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + fmt.Print(err.Error()) + return + } + + fmt.Print("verification:" + string(data) + "\n") + var loginRequest LoginRequest + json.Unmarshal(data, &loginRequest) + + if !managementService.checkExist(loginRequest.Login, loginRequest.Fingerprint) { + w.WriteHeader(404) + return + } + client := managementService.getClient(loginRequest.Login, loginRequest.Fingerprint) + client.UserName = loginRequest.Login + response := LoginResponse{ + CreatedAt: client.CreatedAt, + ApiKey: client.ApiKey, + MacAddress: client.MacAddress, + Metadata: client.Metadata, + Fingerprint: client.Fingerprint, + Id: client.Id, + Client: client.Client, + Name: client.Name, + } + + marshal, _ := json.Marshal(response) + w.Write(marshal) +} + +func device(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Server", "MonfloHTTPServer") + w.Header().Set("Content-Type", "application/json") + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + fmt.Print(err.Error()) + return + } + fmt.Print("Add Device:" + string(data) + "\n") + var addDeviceRequest AddDeviceRequest + json.Unmarshal(data, &addDeviceRequest) + + userName := r.Header.Get("Monflo-login") + managementService.addClient(userName, addDeviceRequest.Fingerprint) + managementService.addDevice(addDeviceRequest) + + client := managementService.getClientByFingerprint(addDeviceRequest.Fingerprint) + + response := LoginResponse{ + CreatedAt: client.CreatedAt, + ApiKey: client.ApiKey, + MacAddress: client.MacAddress, + Metadata: client.Metadata, + Fingerprint: client.Fingerprint, + Id: client.Id, + Client: client.Client, + Name: client.Name, + } + + marshal, _ := json.Marshal(response) + w.Write(marshal) +} diff --git a/private.key b/private.key new file mode 100644 index 0000000..01eac91 --- /dev/null +++ b/private.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwcf0gqkGW79baJ39995n1YirD4x5KtnC3CsD1gftE/va3GZM +ToBsP3jdohi5TRvB/CnF6Ngv22kszDWTwLKg/K73D+5dfu8mxVyxx2kU4uzC0NR0 +Z2AtumWIS05N/S53PWESBbCgPBJLcGKqLULYXf9EvEPtXebhi68C4aGnCmWOcMCL +H/VrCcwFT15eR6zsD3whv8Oqpdj5aneItpEaWd8dzBrHO69kbyfqK/GL14eLtPCl +VoY0EQ8z6hNqNTil6eseBdEuwytND5LJnfzarXCBxdnh74CEu4nxZcgEIx3hoSpL +JVtHiZfGXPZgtIech3LgcAcAmcaI6wtD94Z5pQIDAQABAoIBAAJmo9TqmzWPzWYi +bv8fNlIi+1uZ9fZd9FgeAFIqjvlsaW3JprBiTvUKXlSf0cvuyByDt/wGkbE6QF/X +WhlNHUmEMXN1FJt6AxT27Qz3dFbLcC5+M2MEggyJLYMhWT4F0VxlU3/WjGWyJFUk +I8+jwGKJwyRCAzLipXDBnluFUTiDvBbw3SpS+C6zn6wcHCfO0waDUc6Gsn9/Von/ +4/AXx6tQfGYXhGYsBR/MxNQpbNol0CsAdTpi5wwZ5LUQf2Y/JlUGqQ1UwBouSZPu +k/djzrzBqsFrMqxKE4+M1TuKluawlDWPEqKgcMooegFoopYfLavKtX8lLQFDE2xo +7pIEvZECgYEA4nOc9W6iwP821p7rkUjnaaLth9jlt05t/sipN8eE8vX70ITHlNda +vQVLsAbyY3BWzyEtg+KTsDinEyzdfKuqnBRbxda/7PwZ/VfOwSLVbgkXL2No68eV +x1CzigJo7EERk+gx/zjCYlSpDVk3ycIimYkVWkIU9tmqtEXnFFdx1ocCgYEA2xEE +Bi9jh3cY2QUCewz3Dz5rMKZkkEj3lBAD7pMTy53ZTbocL1ZTnWGyacW0byiOlnzd +iny71iFAx4fdgXrhFmO/3x9duy25HJMlKyvG6U+6lD/Gr7JmX5WXGlgT61d+JfR3 +mtYwaM0f2ngxWYuwvfVq/WXaB7iESNOwfOHfzXMCgYADdXKbSRJRUlSbGJhOgseO +FH/+SDDSCO+jKZt0D6cXMuyitbR6sINhSbhrOt/u5uNcjIwubIKG+YaLw26qndCg +S6tPLUWHMB6RgQrWZlrOMHNbNPCAUW8XOUNUw06o9SF4md5RoKNPby2Z15gDi+SN +Zcuesk2xq4dw83RhGijR9wKBgBiCGtUmUBhDtr/w04o4tRs7fHqA4xdRUoF6GTaD +td891aXggG67VbdxyqgSulEFVI55gb+QnOMj7T9lb96ghLYgisLHm5DpWKBdxfbC +ewp3JQSY7f2SE+n1rmYAHJpju3U7mHX2KIxRBpNGhx7hhfB6mHGpB299sS8En+YY +zxUJAoGBAKXCJqyEwDkZfov312UNQ5S7PzkuM6eTNa0Lrbo/lrn/srdMN8Iawpqs +R27AXbHEMuHjTFVIM77ju735XFn2gYdP3n5nPKWmiyd3GqSso07C3dxdTYjKPFT8 +wmIf+gNAN/0r/j7G0p+pIQQ6EWOfTGmXJvdYm1ZMyUvEkKZxmO6B +-----END RSA PRIVATE KEY----- diff --git a/request_struct.go b/request_struct.go new file mode 100644 index 0000000..f1f203c --- /dev/null +++ b/request_struct.go @@ -0,0 +1,29 @@ +package main + +type MonfloRequest struct { + Method string `json:"method,omitempty"` + Uri string `json:"uri,omitempty"` + Headers MonfloHeader `json:"headers,omitempty"` + Data MonfloRequestData `json:"data,omitempty"` +} + +type MonfloHeader struct { + ApiKey string `json:"apikey,omitempty"` + PrivateAddr string `json:"private_addr,omitempty"` + PublicAddr string `json:"public_addr,omitempty"` + Type string `json:"type,omitempty"` //p2p + Format string `json:"format,omitempty"` //h264 +} + +type MonfloRequestData struct { + Metadata string `json:"metadata,omitempty"` + Name string `json:"name,omitempty"` + PrivateAddr string `json:"private_addr,omitempty"` + PublicAddr string `json:"public_addr,omitempty"` + Key string `json:"key,omitempty"` + Bitrate int `json:"bitrate,omitempty"` + Formats []string `json:"formats,omitempty"` //h264 h265 + Stream string `json:"stream,omitempty"` + Status string `json:"status,omitempty"` + Client string `json:"client,omitempty"` +} diff --git a/response_struct.go b/response_struct.go new file mode 100644 index 0000000..52c2518 --- /dev/null +++ b/response_struct.go @@ -0,0 +1,60 @@ +package main + +type MonfloResponse struct { + Event string `json:"event,omitempty"` + Status int `json:"status,omitempty"` +} + +type DevicesResponse struct { + MonfloResponse + Data []LoginResponse `json:"data"` +} + +type DeviceResponse struct { + MonfloResponse + Data LoginResponse `json:"data"` +} + +type PatchDeviceResponse struct { + MonfloResponse + Data LoginResponse `json:"data"` +} + +type PeersResponse struct { + MonfloResponse + Data map[string]PeerInfo `json:"data"` +} + +type IncognitosResponse struct { + MonfloResponse + Data IncognitosData `json:"data"` +} +type IncognitosData struct { + Id string `json:"id"` + Stream string `json:"stream"` + Client string `json:"client"` +} + +type StreamResponse struct { + MonfloResponse + Data StreamInfo `json:"data"` +} + +type StreamReadResponse struct { + MonfloResponse + Data StreamReadData `json:"data"` +} + +type StreamReadData struct { + Client string `json:"client"` //client-528591bf-bc07-59c5-8f24-a543bf383680 + Format string `json:"format"` //h264 + Id string `json:"id"` //stream-16f25ad7-be2b-5d45-a777-47d8f4c77179 + PrivateAddr string `json:"private_addr"` //172.19.10.175:58169 + PublicAddr string `json:"public_addr"` //58.210.98.46:44742 + Type string `json:"type"` //p2p +} + +type ClientConnectedEvent struct { + MonfloResponse + Data string `json:"data"` +} diff --git a/welcome_struct.go b/welcome_struct.go new file mode 100644 index 0000000..5b2561e --- /dev/null +++ b/welcome_struct.go @@ -0,0 +1,57 @@ +package main + + +type Features struct { + MaxUserProfiles int `json:"max_user_profiles"` + MaxStreams int `json:"max_streams"` + MaxDevices int `json:"max_devices"` + MaxP2pResolution int `json:"max_p2p_resolution"` + MaxP2pFrames int `json:"max_p2p_frames"` + MaxRelayResolution int `json:"max_relay_resolution"` + MaxRelayFrames int `json:"max_relay_frames"` + Hevc bool `json:"hevc"` + SessionRecording bool `json:"session_recording"` + ManagementConsole bool `json:"management_console"` + ConnectAnywhere bool `json:"connect_anywhere"` + Support bool `json:"support"` + CommercialUse bool `json:"commercial_use"` + DeviceThumbnails bool `json:"device_thumbnails"` + FileTransfer bool `json:"file_transfer"` +} + +type IPInfo struct { + PrivateAddr string `json:"private_addr"` + PublicAddr string `json:"public_addr"` +} + +type StreamInfo struct { + Id string `json:"id,omitempty"` + Status string `json:"status,omitempty"` //ready busy + OwnerClient string `json:"owner_client,omitempty"` + Name string `json:"name,omitempty"` + PrivateAddr string `json:"private_addr,omitempty"` + PublicAddr string `json:"public_addr,omitempty"` + Key string `json:"key,omitempty"` + Bitrate int `json:"bitrate,omitempty"` + Formats []string `json:"formats,omitempty"` //h264 h265 +} + +type WelcomeData struct { + Client string `json:"client,omitempty"` + Features Features `json:"features,omitempty"` + Peers map[string]PeerInfo `json:"peers,omitempty"` + Subscription int `json:"subscription,omitempty"` //1=免费 2=专业 3=旗舰 + Timestamp int64 `json:"timestamp,omitempty"` //1599118479 +} + +type WelcomeInfo struct { + MonfloResponse + Data WelcomeData `json:"data,omitempty"` +} + +type PeerInfo struct { + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` + LastIP IPInfo `json:"last_ip,omitempty"` + Streams map[string]StreamInfo `json:"streams"` +}