commit b4a7f4e53ffe0eb9a5cc9d73c26da9246267664b Author: wyf5229 Date: Thu Sep 10 16:08:06 2020 +0800 master 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"` +}