commit 0f3377d2b8201a8af28b3674e9556edf6c4b5542 Author: Alexander Lazarenko Date: Wed Nov 29 14:16:00 2023 +0300 Initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f0cba4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# N9M \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ab75002 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module n9m + +go 1.21.3 + +require ( + github.com/icza/bitio v1.1.0 // indirect + github.com/tidwall/gjson v1.17.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..82c3304 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +github.com/icza/bitio v1.1.0 h1:ysX4vtldjdi3Ygai5m1cWy4oLkhWTAi+SyO6HC8L9T0= +github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A= +github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..e422658 --- /dev/null +++ b/utils.go @@ -0,0 +1,551 @@ +package n9m + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "os" + + "log" + + "github.com/icza/bitio" + "github.com/tidwall/gjson" +) + +var ip string = os.Getenv("SERVER_IP") + +type PayloadJson struct { + Module string + Key string + Operation string + Parameters map[string]interface{} + Response map[string]interface{} +} + +type Package struct { + Version uint64 + Encription uint64 + Mark uint64 + CC uint64 + PayloadType uint64 + SSRC uint64 + Reserved uint64 + CSRC [16]uint64 + + PayloadLen uint64 + GPayload gjson.Result + Payload map[string]interface{} + RawPayload []byte + + Raw []byte + Accum []byte + + Json PayloadJson +} + +// todo разобраться зачем оно ъ +func (e *Package) AddToAccum(data []byte) { + e.Accum = append(e.Accum, data...) +} + +func (e *Package) SaveJsonFields() { + if value, exist := e.Payload["MODULE"]; exist { + e.Json.Module = value.(string) + } else { + e.Json.Module = "" + } + + if value, exist := e.Payload["KEY"]; exist { + e.Json.Key = value.(string) + } else { + e.Json.Key = "" + } + + if value, exist := e.Payload["OPERATION"]; exist { + e.Json.Operation = value.(string) + } else { + e.Json.Operation = "" + } + + if value, exist := e.Payload["RESPONSE"]; exist { + e.Json.Response = value.(map[string]interface{}) + } else { + e.Json.Response = make(map[string]interface{}) + } + + if value, exist := e.Payload["PARAMETER"]; exist { + e.Json.Parameters = value.(map[string]interface{}) + } else if value, exist := e.Payload["PARAMETERS"]; exist { + e.Json.Parameters = value.(map[string]interface{}) + } else { + e.Json.Parameters = make(map[string]interface{}) + } +} + +func (e *Package) ReadPackage() bool { + if len(e.Accum) < 12 { + return false + } + + r := bitio.NewReader(bytes.NewBuffer(e.Accum)) + e.Version = r.TryReadBits(2) + e.Encription = r.TryReadBits(1) + e.Mark = r.TryReadBits(1) + e.CC = r.TryReadBits(4) + e.PayloadType = r.TryReadBits(8) + e.SSRC = r.TryReadBits(16) + + log.Println(e.PayloadType) + + is_special := e.Encription == 1 && e.Mark == 1 + if is_special { + r.TryReadBits(8 * 4) + e.PayloadLen = r.TryReadBits(8) + r.TryReadBits(3 * 8) + + if uint64(len(e.Accum)) < e.PayloadLen+12 { + return false + } + } else { + e.PayloadLen = r.TryReadBits(32) + // WTF: e.CC is useless + for i := uint64(0); i < 1; i++ { + e.CSRC[i] = r.TryReadBits(32) + } + } + + numOfBytes := 0 + rawbytes := []byte{} + if e.PayloadLen != 0 { + if e.PayloadLen > 1000000 { + log.Printf("%v\n", e) + log.Panicln("CORRUPTED PACKAGE") + } + + rawbytes = make([]byte, e.PayloadLen) + numOfBytes, _ = r.Read(rawbytes) + } + + if numOfBytes != int(e.PayloadLen) { + return false + } + + e.Raw = e.Accum[:12+e.PayloadLen] + e.Accum = e.Accum[12+e.PayloadLen:] + e.RawPayload = rawbytes + + var ok bool + e.GPayload = gjson.Parse(string(rawbytes)) + e.Payload, ok = e.GPayload.Value().(map[string]interface{}) + if !ok { + e.Payload = gjson.Parse("{}").Value().(map[string]interface{}) + } + + e.SaveJsonFields() + + return e.PayloadLen > 0 +} + +func (e *Package) PackPackage() []byte { + e.SaveJsonFields() + + b := &bytes.Buffer{} + w := bitio.NewWriter(b) + + w.TryWriteBits(e.Version, 2) + w.TryWriteBits(e.Encription, 1) + w.TryWriteBits(e.Mark, 1) + w.TryWriteBits(e.CC, 4) + w.TryWriteBits(e.PayloadType, 8) + w.TryWriteBits(e.SSRC, 16) + conv, err := json.Marshal(e.Payload) + + if err != nil { + fmt.Println(err) + return nil + } + + e.PayloadLen = uint64(len(conv)) + if e.PayloadLen != 0 { + e.PayloadLen++ + } + w.TryWriteBits(e.PayloadLen, 32) + + // WTF: e.CC is useless + for i := uint64(0); i < 1; i++ { + w.TryWriteBits(e.CSRC[i], 32) + } + if e.PayloadLen != 0 { + w.Write(conv) + w.Write([]byte{0}) + } + w.Close() + + return b.Bytes() +} + +func (e *Package) PackRawPackage() []byte { + b := &bytes.Buffer{} + w := bitio.NewWriter(b) + + w.TryWriteBits(e.Version, 2) + w.TryWriteBits(e.Encription, 1) + w.TryWriteBits(e.Mark, 1) + w.TryWriteBits(e.CC, 4) + w.TryWriteBits(e.PayloadType, 8) + w.TryWriteBits(e.SSRC, 16) + e.PayloadLen = uint64(len(e.RawPayload) + 1) + w.TryWriteBits(e.PayloadLen, 32) + + // WTF: e.CC is useless + for i := uint64(0); i < 1; i++ { + w.TryWriteBits(e.CSRC[i], 32) + } + if e.PayloadLen != 0 { + w.Write(e.RawPayload) + w.Write([]byte{0}) + } + w.Close() + + return b.Bytes() +} + +// main server util +func (e *Package) RequestGeolocation(serial int, Sid string) { + e.Payload = map[string]any{ + "MODULE": "DEVEMM", + "OPERATION": "GETPOS", + "PARAMETER": map[string]any{ + "SERIAL": serial, + }, + "SESSION": Sid, + } +} + +func (e *Package) ResponseGeolocation(errorCode int, errorCause string, serial int, longitude float32, latitude float32, altitude float32, speed int, course int, time string) { + e.Payload = map[string]any{ + "MODULE": "DEVEMM", + "OPERATION": "GETPOS", + "RESPONSE": map[string]any{ + "ERRORCODE": errorCode, + "ERRORCAUSE": errorCause, + "SERIAL": serial, + "P": map[string]any{ + "V": errorCode == 0, + "J": fmt.Sprintf("%4.6v", longitude), + "W": fmt.Sprintf("%4.6v", latitude), + "H": fmt.Sprintf("%4.6v", altitude), + "S": speed, // unit - 0.01 km/h + "C": course, // direction (angle from north) + "T": time, // yyyymmddhhmmss + }, + }, + } +} + +func (e *Package) RequestConnect(session string, serial string, numOfCams int) { + e.Payload = map[string]any{ + "MODULE": "CERTIFICATE", + "OPERATION": "CONNECT", + "PARAMETER": map[string]any{ + "DSNO": serial, + "CHANNEL": numOfCams, + }, + "SESSION": session, + } +} + +// video server util +func (e *Package) ResponseConnect(Sid string, streamName string) { + e.Payload = map[string]any{ + "MODULE": "CERTIFICATE", + "OPERATION": "CREATESTREAM", + "RESPONSE": map[string]any{ + "ERRORCODE": 0, + "STREAMNAME": streamName, + }, + "SESSION": Sid, + } +} + +func (e *Package) ResponseCalendar(errorCode int, errorCause string, serial int, dates []string) { + e.Payload = map[string]any{ + "MODULE": "STORM", + "OPERATION": "GETCALENDAR", + "RESPONSE": map[string]any{ + "ERRORCODE": errorCode, + "ERRORCAUSE": errorCause, + "SERIAL": serial, + "COUNT": len(dates), + "CALENDER": dates, + // no CHCALENDER[COUNT] + // no T[COUNT] + }, + } +} + +func (e *Package) RequestCalendar(queryTime string, serial int, session string, camNo int64) { + channel := 1 << (camNo - 1) + e.Payload = map[string]any{ + "MODULE": "STORM", + "OPERATION": "GETCALENDAR", + "PARAMETER": map[string]any{ + "CALENDARTYPE": 1, // Month data + "STREAMTYPE": 1, // Main Stream + "FILETYPE": 0b111111, // get file type + "PICMTYPE": 0b10, // fixed timing pictures (fixed framerate) + "APT0": 0xFFFFFF, // get every alarm + "APT1": 0xFFFF, // get every alarm + "AUDIOTYPE": 0b111, // normal recording, passenger complaints, alarm recording + "CHANNEL": channel, // request all channels + "QUERYTIME": queryTime, // year + month = xxxxxx + "SERIAL": serial, + "NEWSTREAMTYPE": 0b111, // master stream (bit1) + "RFSTORAGE": 0, // 0 - hdd, 1 - sd + }, + "SESSION": session, + } +} + +// filenames without fileextension +func (e *Package) ResponseFileList(errorCode int, errorCause string, serial int, filenames []string, fileextensions []int, ids []string) { + e.Payload = map[string]any{ + "MODULE": "STORM", + "OPERATION": "QUERYFILELIST", + "RESPONSE": map[string]any{ + "ERRORCODE": errorCode, + "ERRORCAUSE": errorCause, + "SERIAL": serial, + "SENDFILECOUNT": len(filenames), + "RECORD": filenames, + "FILETYPE": fileextensions, + "RECORDID": ids, + }, + } +} + +func (e *Package) RequestFileList(queryTime string, serial int, session string, camNo int64) { + channel := 1 << (camNo - 1) + e.Payload = map[string]any{ + "MODULE": "STORM", + "OPERATION": "QUERYFILELIST", + "PARAMETER": map[string]any{ + "STREAMTYPE": 1, // Main Stream + "FILETYPE": 0b111111, // get all filetypes + "PICMTYPE": 0b10, // fixed timing pictures (fixed framerate) + "APT0": 0xFFFFFF, // get every alarm + "APT1": 0xFFFF, // get every alarm + "AUDIOTYPE": 0b111, // normal recording, passenger complaints, alarm recording + "CHANNEL": channel, // request all channels + "STARTTIME": queryTime + "000000", + "ENDTIME": queryTime + "235959", + "SERIAL": serial, + "NEWSTREAMTYPE": 0b10, // master stream (bit1) + "RFSTORAGE": 0, // 0 - hdd, 1 - sd + }, + "SESSION": session, + } +} + +// main server util +func (e *Package) ResponseCertificateConnect(Sid string) { + e.Payload = map[string]any{ + "MODULE": "CERTIFICATE", + "OPERATION": "CONNECT", + "RESPONSE": map[string]any{ + "ERRORCAUSE": "", + "ERRORCODE": 0, + "MASKCMD": 5, + "PRO": "1.0.5", + }, + "SESSION": Sid, + } +} + +// request reqistration parameters (directly to register) +func (e *Package) RequestParameters(params map[string]any, serial int, session string) { + e.Payload = map[string]any{ + "MODULE": "CONFIGMODEL", + "OPERATION": "GET", + "PARAMETER": map[string]any{ + "MDVR": params["MDVR"], + "SERIAL": serial, + }, + "SESSION": session, + } +} // end of 'RequestParameters' function + +// set reigeter parameters (directly to register) +func (e *Package) SetParameters(params map[string]any, serial int, session string) { + e.Payload = map[string]any{ + "MODULE": "CONFIGMODEL", + "OPERATION": "SET", + "PARAMETER": map[string]any{ + "MDVR": params["MDVR"], + "SERIAL": serial, + }, + "SESSION": session, + } + log.Println(e.Payload) +} // end of 'SetParameters' function + +// todo al1 +func (e *Package) ConfigeModel(Sid string) { + e.Payload = map[string]any{ + "MODULE": "CONFIGMODEL", + "OPERATION": "SET", + "PARAMETER": map[string]any{ + "MDVR": map[string]any{ + "KEYS": map[string]any{ // KEY parameters + "GV": 1, // GPS version + }, + "PGDSM": map[string]any{ // Network monitoring status parameters + "PGPS": map[string]any{ // GPS position + "EN": 1, // Real-time position monitoring + "TM": 10, // Time interval + }, + }, + "PSI": map[string]any{ // Platform basic information + "CG": map[string]any{ // Call information + "AS": 0, // Automatic answer + }, + }, + "SUBSTRNET": map[string]any{ + "SM": 1, // 0-Smooth .. 4-Clear + }, + }, + }, + } +} + +// main server util +func (e *Package) MediaRequestAliveVideo(token int, camNo int64, Sid string, serial string, quality int64) { + + channel := 0 + + if camNo == 1 { + channel = 1 + } else { + channel = 1 << (camNo - 1) + } + e.Payload = map[string]any{ + "MODULE": "MEDIASTREAMMODEL", + "OPERATION": "REQUESTALIVEVIDEO", + "PARAMETER": map[string]any{ + "AUDIOVALID": 1, + "CHANNEL": channel, + "FRAMEMODE": 0, + "IPANDPORT": ip + ":12092", + "STREAMNAME": "LIVE" + "_" + serial + "_" + fmt.Sprint(camNo), + "STREAMTYPE": quality, + "SERIAL": token, + }, + "SESSION": Sid, + } +} + +// main server util +func (e *Package) MediaRequestRemotePlayback(token int, serial string, session string, camNo int, date string, begin_time string, end_time string, serverId int) { + e.Payload = map[string]any{ + "MODULE": "MEDIASTREAMMODEL", + "OPERATION": "REQUESTREMOTEPLAYBACK", + "PARAMETER": map[string]any{ + "STREAMNAME": "PLAYBACK" + "_" + fmt.Sprint(serial) + "_" + fmt.Sprint(camNo) + "_" + fmt.Sprint(serverId), + "STREAMTYPE": 1, // main stream + "VIDEOTYPE": 2, // common files + "CHANNEL": 1 << (camNo - 1), + "STARTTIME": date + begin_time, + // "ENDTIME": date + end_time, + "IPANDPORT": ip + ":12092", + "SERIAL": token, + "PBST": 0, + }, + "SESSION": session, + } +} + +// main server util +func (e *Package) ControlRemotePlayback(token int, serial string, session string, camNo int, date string, begin_time string, end_time string, serverId int) { + e.Payload = map[string]any{ + "MODULE": "MEDIASTREAMMODEL", + "OPERATION": "CONTROLREMOTEPLAYBACK", + "PARAMETER": map[string]any{ + "STREAMNAME": fmt.Sprint(serial) + "_" + fmt.Sprint(camNo) + "_" + fmt.Sprint(serverId), + "SERIAL": token, + "PALYBACKCMD": 5, // main stream + "CHANNEL": 268435455, // common files + }, + "SESSION": session, + } +} + +func (e *Package) MediaRequestDownloadVideo(token int, serial string, session string, camNo int, date string, begin_time string, end_time string, recordID string, serverId int) { + e.Payload = map[string]any{ + "MODULE": "MEDIASTREAMMODEL", + "OPERATION": "REQUESTDOWNLOADVIDEO", + "PARAMETER": map[string]any{ + "PT": 3, + "SSRC": 1, + "STREAMNAME": "DOWNLOAD" + "_" + serial + "_" + fmt.Sprint(camNo) + "_" + fmt.Sprint(serverId), + "STREAMTYPE": 1, // main stream + "RECORDID": recordID, + "CHANNEL": 1 << (camNo - 1), + "STARTTIME": date + begin_time, + "ENDTIME": date + end_time, + "OFFSETFLAG": 1, + "OFFSET": 0, + "IPANDPORT": ip + ":12092", + "SERIAL": token, + "DT": 1, // high speed download + }, + "SESSION": session, + } +} + +// main server util +func (e *Package) ResponseAlarm(alarmType int64, alarmUID int64, cmdno int64, cmdtype int64, run int64, serial string, Sid string) { + e.Payload = map[string]any{ + "MODULE": "EVEM", + "OPERATION": "SENDALARMINFO", + "RESPONSE": map[string]any{ + "ALARMTYPE": alarmType, + "ALARMUID": alarmUID, + "CMDNO": cmdno, + "CMDTYPE": cmdtype, + "ERRORCODE": 0, + "RUN": run, + }, + "SESSION": Sid, + } +} + +// main server util +func (e *Package) ResponseConfigModelSet(Sid string) { + e.Payload = map[string]any{ + "MODULE": "CONFIGMODUL", + "OPERATION": "SET", + "RESPONSE": map[string]any{ + "ERRORCODE": 0, + "ERRORCAUSE": "None", + "ERRORDESCRIPTION": "None", + }, + "SESSION": Sid, + } +} + +// todo ъ +// why store a string and constantly change it to the same thing +// the stored string is not used +func (e *Package) GetToken() { + hexStream := "3876431502000010380000007b224b4559223a22434c49454e544c4f47494e222c22524553504f4e5345223a7b22434c49454e544944223a2237643531323030227d7d00" + + e.RawPayload, _ = hex.DecodeString(hexStream) +} + +// the same +func (e *Package) RequestGetTokenDop() { + hexStream := "3876431501000010360000007b224b4559223a224c4f47494e222c22504152414d223a7b224355534552223a2231222c22505744223a22313233343536227d7d0a00" + + e.RawPayload, _ = hex.DecodeString(hexStream) +}