From 16da4affa8500d1a4f5af9fed0fac8b4a5384e87 Mon Sep 17 00:00:00 2001 From: Alexander Lazarenko Date: Sat, 22 Feb 2025 19:23:16 +0300 Subject: [PATCH] Refactor and enhance handling of payloads and data structures Added support for handling various payload types, including GPS and alarms, with new structures and constants. Introduced helper methods for JSON marshalling/unmarshalling of GPS data and modularized the handling of certificates, configurations, and alarms. Implemented foundational server code for testing and expanded several package functionalities. --- certificate.go | 21 ++++- cmd/dev-server/main.go | 174 +++++++++++++++++++++++++++++++++++++++++ configmodel.go | 60 +++++++------- evem.go | 169 +++++++++++++++++++++++++++++++++++++++ io.go | 53 ++++++++++++- params.go | 92 ++++++++++++++++------ scheme.go | 99 ++++++++++++++++++++++- 7 files changed, 607 insertions(+), 61 deletions(-) create mode 100644 cmd/dev-server/main.go diff --git a/certificate.go b/certificate.go index 18a0865..a6d8c60 100644 --- a/certificate.go +++ b/certificate.go @@ -23,7 +23,7 @@ const ( type CertificateConnectRequest struct { Net NetConnectionType `json:"NET"` - SerialNumber string `json:"DNSO"` + SerialNumber string `json:"DSNO"` DeviceName string `json:"DEVNAME"` ChannelsNumber uint `json:"CHANNEL"` LicensePlate string `json:"CARNUM"` @@ -35,6 +35,25 @@ type CertificateConnectRequest struct { EvidenceSupport string `json:"EV"` } +type CertificateConnectResponse struct { + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` + CommandMask uint `json:"MASKCMD"` +} + +type CommandMaskParameters uint + +const ( + CommandMaskAlarm = 1 << iota + CommandMaskScanningGun + CommandMaskPassengerFlow + CommandMaskFaceContrast + CommandMaskCard + CommandMaskShutdownReport + CommandMaskGPSReport + CommandMaskAll = 0b1111111 +) + /* func (e *Package) RequestConnect(session string, serial string, numOfCams int) { e.Payload = map[string]any{ diff --git a/cmd/dev-server/main.go b/cmd/dev-server/main.go new file mode 100644 index 0000000..fc624f3 --- /dev/null +++ b/cmd/dev-server/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "fmt" + "gitea.unprism.ru/KRBL/n9m" + "net" + "os" +) + +func main() { + ln, err := net.Listen("tcp", "0.0.0.0:5556") + + if err != nil { + panic(err) + } + + for { + var conn net.Conn + + conn, err = ln.Accept() + + if err != nil { + continue + } + + go handle(conn) + } +} + +func handle(conn net.Conn) { + var err error + var rn int + + var serial string + + packS := n9m.Package{} + tmp := make([]byte, 1024) + + for { + rn, err = conn.Read(tmp) + + if err != nil { + fmt.Println(err) + return + } + + packS.AddToAccum(tmp[:rn]) + + for packS.ReadPackage() { + switch packS.PayloadType { + case n9m.PayloadTypeData: + combined := packS.Payload.Module + ":" + packS.Payload.Operation + + switch combined { + case "CERTIFICATE:CONNECT": + var params n9m.CertificateConnectRequest + + if err = packS.GetParametersAs(¶ms); err != nil { + fmt.Println(combined, err) + return + } + + var response = n9m.CertificateConnectResponse{ + ErrorCode: 0, + CommandMask: n9m.CommandMaskAll, + } + + packS.SetResponse(response) + + if _, err = conn.Write(packS.PackPackage()); err != nil { + fmt.Println(combined, err) + return + } + + fmt.Println("Connected:", params.SerialNumber) + serial = params.SerialNumber + + var request n9m.ConfigModelGetRequest + request.MDVR = "?" + packS.Payload.Module = "CONFIGMODEL" + packS.Payload.Operation = "GET" + packS.SetParameters(request) + + conn.Write(packS.PackPackage()) + + /* + go func() { + pack := packS + pack.Payload.Module = "EVEM" + pack.Payload.Operation = "GALARMING" + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for range ticker.C { + fmt.Println("Sent!") + if _, err := conn.Write(pack.PackPackage()); err != nil { + fmt.Println("Failed to send GALARMING:", err) + return + } + } + }() + */ + case "CERTIFICATE:KEEPALIVE": + packS.SetResponse(nil) + if _, err = conn.Write(packS.PackPackage()); err != nil { + fmt.Println(combined, err) + return + } + + fmt.Println("Connection is still alive!") + case "EVEM:GGALARMING": + fmt.Println(string(packS.RawPayload)) + + var response n9m.EventModelGetAlarmingResponse + if err = packS.GetResponseAs(&response); err != nil { + fmt.Println(combined, err) + continue + } + + fmt.Printf("%+v\n", response) + case "CONFIGMODEL:GET": + os.WriteFile(fmt.Sprintf("./%s.json", serial), packS.RawPayload, 0644) + case "EVEM:SENDALARMINFO": + var params n9m.SendAlarmInfoParameters + var response n9m.SendAlarmInfoResponse + + if err = packS.GetParametersAs(¶ms); err != nil { + fmt.Printf("Error: %s\nData: %s", err, packS.RawPayload) + continue + } + + response.ErrorCode = 0 + response.AlarmType = params.AlarmType + response.CommandType = params.CommandType + response.AlarmUID = params.AlarmUID + response.NumberOfRestarts = params.NumberOfRestarts + response.InstructionSerial = params.InstructionSerial + + switch params.AlarmType { + case n9m.AlarmTypeMotionDetection: + break + case n9m.AlarmTypeVideoLoss, n9m.AlarmTypeCameraCovered: + var cameraParams n9m.SendAlarmInfoCameraParameters + + if err = packS.GetParametersAs(&cameraParams); err != nil { + fmt.Printf("Error: %s\nData: %s", err, packS.RawPayload) + continue + } + + fmt.Printf("%+v\n", cameraParams) + + packS.SetResponse(response) + conn.Write(packS.PackPackage()) + default: + fmt.Println("Unknown alarm type:", params.AlarmType) + } + default: + fmt.Println("Strange operation:", combined) + } + + case n9m.PayloadTypeSpecial: + switch packS.SSRC { + case n9m.SpecialPayloadTypeGPS: + fmt.Printf("%+v\n", packS.GPS) + default: + fmt.Println("Unhandled special operation:", packS.SSRC) + } + default: + fmt.Println("Unhandled operation:", packS.PayloadType) + } + } + } +} diff --git a/configmodel.go b/configmodel.go index 90835c2..3c74e92 100644 --- a/configmodel.go +++ b/configmodel.go @@ -28,37 +28,41 @@ func (e *Package) SetParameters(params map[string]any, serial int, session strin } // 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 - }, - }, - }, - } +type ConfigModelSetRequest struct { + MDVR Setting `json:"MDVR"` } -// main server util +type ConfigModelGetRequest struct { + MDVR interface{} `json:"MDVR"` +} + +type ConfigModelSetResponse struct { + MDVR Setting `json:"MDVR"` +} + +func (e *Package) InitialConfigModelSetRequest() { + e.SetParameters(ConfigModelSetRequest{ + MDVR: Setting{ + KEYS: KEYS{ + GV: 1, // GPS version + }, + PGDSM: PGDSM{ + PGPS: PGPS{ + EN: 1, // Real-time position monitoring + MODE: 0b10, // Enable timer + TM: 10, // Time interval + }, + }, + SUBSTRNET: SUBSTRNET{ + SM: 1, + }, + }, + }) +} + +/* func (e *Package) ResponseConfigModelSet(Sid string) { e.Payload = map[string]any{ "MODULE": "CONFIGMODUL", // it's not error diff --git a/evem.go b/evem.go index 0cde99a..d382e27 100644 --- a/evem.go +++ b/evem.go @@ -1,5 +1,174 @@ package n9m +// 3.4.1.3 +type EventModelGetAlarmStatusInfoResponse struct { + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` + StorageErrors []StorageErrorStatus `json:"ST"` + AnotherStorageErrors []AnotherStorageErrorStatus `json:"VS"` + VideoLossErrors []VideoLossErrorStatus `json:"VL"` + GPSError GPSErrorStatus `json:"GFA"` + GPSAntennaError GPSAntennaErrorStatus `json:"GPSS"` +} + +// 3.4.1.4.21 +type StorageErrorStatus struct { + CameraCovered uint `json:"ISA"` + ChannelBind uint `json:"LHC"` +} + +// 3.4.1.4.4 +type AnotherStorageErrorStatus struct { +} + +// 3.4.1.4.5 +type VideoLossErrorStatus struct { +} + +// 3.4.1.4.44 +type GPSErrorStatus struct { +} + +// 3.4.1.4.46 +type GPSAntennaErrorStatus struct { +} + +// 3.4.1.5 +// Alarm upload +type SendAlarmInfoParameters struct { + AlarmType AlarmType `json:"ALARMTYPE"` + CommandType uint `json:"CMDTYPE"` + AlarmUID uint `json:"ALARMUID"` + NumberOfRestarts uint `json:"RUN"` + AlarmLevel AlarmLevel `json:"ALARMAS"` + AlarmCount uint `json:"ALARMCOUNT"` + TriggerType TriggerType `json:"TRIGGERTYPE"` + ContinueTime uint `json:"CONTINUETIME"` + CurrentTime uint `json:"CURRENTTIME"` + Language Language `json:"L"` + GPSData GPSData `json:"P"` + RealTimeUpload uint `json:"REAL"` + InstructionSerial uint `json:"CMDNO"` +} + +// 3.4.1.5 +// Alarm upload +type SendAlarmInfoResponse struct { + ErrorCode uint `json:"ERRORCODE"` + AlarmType AlarmType `json:"ALARMTYPE"` + ErrorCause string `json:"ERRORCAUSE"` + CommandType uint `json:"CMDTYPE"` + AlarmUID uint `json:"ALARMUID"` + NumberOfRestarts uint `json:"RUN"` + InstructionSerial uint `json:"CMDNO"` +} + +// 3.4.1.5.1 +type SendAlarmInfoCameraParameters struct { + SendAlarmInfoParameters + + Channel uint `json:"CHANNEL"` + ChannelMask uint `json:"CHANNELMASK"` + LCH []uint `json:"LCH"` + Push uint `json:"PUSH"` + AlarmName string `json:"ALARMNAME"` + AlarmAbbreviation string `json:"SER"` +} + +type AlarmType uint + +const ( + AlarmTypeVideoLoss AlarmType = iota + AlarmTypeCameraCovered + AlarmTypeMotionDetection + AlarmTypeStorageAbnormal + AlarmTypeUserDefined + AlarmTypeSentriesInspection + AlarmTypeViolation + AlarmTypeEmergency + AlarmTypeSpeedAlarm + AlarmTypeLowVoltage + AlarmTypeOutOfFence = iota + 7 + AlarmTypeAccAlarm + AlarmTypePeripheralsDropped + AlarmTypeStopAnnouncement + AlarmTypeGpsAntenna + AlarmTypeDayNightSwitch + AlarmTypeProhibitDriving + AlarmTypeSerialAlarm = iota + 15 + AlarmTypeFatigueAlarm + AlarmTypeTakeOutParking + AlarmTypeGestureAlarm + AlarmTypeGreenDriving + AlarmTypeIllegalIgnition + AlarmTypeIllegalShutdown + AlarmTypeCustomExternal + AlarmTypeThinkingLKJ + AlarmTypeTAX3 + AlarmTypeOilAlarm + AlarmTypeBusLineOccupation + AlarmTypeForgottenAlarm + AlarmTypeSpecialCustomerFault + AlarmTypeTemperatureAbnormal + AlarmTypeTemperatureChangeAbnormal + AlarmTypeSmokeAlarm + AlarmTypeGBox + AlarmTypeLicensePlateRecognition + AlarmTypeAnotherSpeedAlarm + AlarmTypeWirelessSignalAbnormal + AlarmTypeArming + AlarmTypePhoneCall + AlarmTypeGPSFault + AlarmTypeDSMFault + AlarmTypeFireBox +) + +type AlarmLevel uint + +const ( + AlarmLevelImportant AlarmLevel = iota + AlarmLevelGeneral + AlarmLevelEmergency +) + +type TriggerType uint + +const ( + TriggerTypeManual TriggerType = iota + TriggerTypeAutomatic +) + +type Language uint + +const ( + LanguageSimplifiedChinese Language = iota + LanguageEnglish + LanguageKorean + LanguageItalian + LanguageGerman + LanguageThai + LanguageTurkey + LanguagePortugal + LanguageSpain + LanguageRomania + LanguageGreece + LanguageFrench + LanguageRussian + LanguageDutch + LanguageHebrew + LanguageChineseTraditional +) + +type EventModelGetAlarmingResponse struct { + CameraCoveredChannelMask uint `json:"VS_CH"` + CameraCoveredAlarmMask uint `json:"VS_AT"` + CameraCoveredStatusMask uint `json:"VS_AS"` + + VideoLossChannelMask uint `json:"VL_CH"` + VideoLossAlarmMask uint `json:"VL_AT"` + VideoLossStatusMask uint `json:"VL_AS"` +} + /* // main server util func (e *Package) ResponseAlarm(alarmType int64, alarmUID int64, cmdno int64, cmdtype int64, run int64, serial string, Sid string) { diff --git a/io.go b/io.go index d3305e2..fc92e3a 100644 --- a/io.go +++ b/io.go @@ -2,10 +2,12 @@ package n9m import ( "bytes" + "encoding/binary" "encoding/json" - "log" - + "fmt" "github.com/icza/bitio" + "log" + "time" ) // Read package @@ -20,7 +22,7 @@ func (e *Package) ReadPackage() bool { e.CompressFlag = r.TryReadBool() e.CSRCCount = uint8(r.TryReadBits(4)) e.PayloadType = PayloadType(r.TryReadBits(8)) - e.SSRC = uint16(r.TryReadBits(16)) + e.SSRC = SpecialPayloadType((r.TryReadBits(8) | (r.TryReadBits(8) << 8))) if e.EncryptionFlag && e.CompressFlag { // TODO: get snippet, that use this code @@ -60,11 +62,34 @@ func (e *Package) ReadPackage() bool { e.Accum = e.Accum[12+e.payloadLen:] - if e.PayloadType == PayloadTypeData { + switch e.PayloadType { + case PayloadTypeData: if err := json.Unmarshal(e.RawPayload, &e.Payload); err != nil { log.Printf("Error parsing JSON payload: %v", err) return false } + case PayloadTypeSpecial: + switch e.SSRC { + case SpecialPayloadTypeGPS: + e.GPS.GPSStatus = e.RawPayload[0] + e.GPS.Expand = e.RawPayload[1] + e.GPS.Real = e.RawPayload[2] + + e.GPS.Longitude = float64(binary.BigEndian.Uint32(e.RawPayload[4:8])) / 1e6 + e.GPS.Latitude = float64(binary.BigEndian.Uint32(e.RawPayload[8:12])) / 1e6 + e.GPS.Speed = float64(binary.BigEndian.Uint32(e.RawPayload[12:16])) / 100 + e.GPS.Direction = float64(binary.BigEndian.Uint32(e.RawPayload[16:20])) / 100 + e.GPS.Altitude = int32(binary.BigEndian.Uint32(e.RawPayload[20:24])) + + var err error + if e.GPS.Time, err = time.Parse("20060102150405", string(e.RawPayload[24:38])); err != nil { + log.Printf("Error parsing time: %v", err) + } + default: + fmt.Println("N9M parser warning: unknown special payload type", e.SSRC) + } + default: + fmt.Println("N9M parser warning: unknown payload type", e.PayloadType) } if r.TryError != nil { @@ -141,3 +166,23 @@ func (e *Package) GetParametersAs(parameters any) error { return json.Unmarshal(marshal, parameters) } + +func (e *Package) SetParameters(parameters any) { + e.Payload.Response = struct{}{} + e.Payload.Parameter = parameters +} + +func (e *Package) GetResponseAs(response any) error { + marshal, err := json.Marshal(e.Payload.Response) + + if err != nil { + return err + } + + return json.Unmarshal(marshal, response) +} + +func (e *Package) SetResponse(response any) { + e.Payload.Parameter = struct{}{} + e.Payload.Response = response +} diff --git a/params.go b/params.go index ff62345..9d19e49 100644 --- a/params.go +++ b/params.go @@ -1,5 +1,6 @@ package n9m +// 7.2 type RIP struct { DEVID string // ID номер устройства BN string // бортовой номер @@ -218,7 +219,8 @@ type DNS struct { } type KEYS struct { - MAC string // MAC-адрес + MAC string `json:"MAC,omitempty"` // MAC-адрес + GV uint `json:"GV,omitempty"` // GPS version } type WIFI struct { @@ -267,29 +269,67 @@ type SP struct { MUPORT int // UDP-порт медиасервера } -type Setting struct { - RIP RIP - VS VS - GSP GSP - TIMEP TIMEP - ETHERNET ETHERNET - KEYS KEYS - WIFI WIFI - M3G M3G - MCMS MCMS - ATP ATP - SSP SSP - SWUS SWUS - UMP UMP - SUBSTRNET SUBSTRNET - DOSD DOSD - AR AR - EOSD []EOSD - MAIN []VEC - IOP []IOP - SAP SAP - UAP UAP - PVLAS PVLAS - PMDAS PMDAS - DSM DSM +// 7.30 +type PGDSM struct { + PGPS PGPS `json:"PGPS,omitempty"` + PDSM PDSM `json:"PDSM,omitempty"` +} + +// 7.30.1 +// GPS (position) status parameter +type PGPS struct { + EN uint // Enable + MODE uint + SEP uint `json:"SEP,omitempty"` + TM uint `json:"TM,omitempty"` + NUM uint `json:"NUM,omitempty"` +} + +// 7.30.2 +type PDSM struct { +} + +// 7.39.3.9 +// Platform basic information +type PSI struct { + CG CG `json:"CG,omitempty"` +} + +// 7.39.3.8 +// Total record information for call +type CG struct { + ECL uint // The maximum calling time + ECML uint // The maximum calling time per month + AS uint // Terminal phonecall answering strategy + AT uint // Answer automatically when exceed time +} + +type Setting struct { + KEYS KEYS `json:"KEYS,omitempty"` + RIP RIP `json:"RIP,omitempty"` + TIMEP TIMEP `json:"TIMEP,omitempty"` + ATP ATP `json:"ATP,omitempty"` + SSP SSP `json:"SSP,omitempty"` + GSP GSP `json:"GSP,omitempty"` + UMP UMP `json:"UMP,omitempty"` + ETHERNET ETHERNET `json:"ETHERNET,omitempty"` + WIFI WIFI `json:"WIFI,omitempty"` + M3G M3G `json:"M3G,omitempty"` + MCMS MCMS `json:"MCMS,omitempty"` + SUBSTRNET SUBSTRNET `json:"SUBSTRNET,omitempty"` + DOSD DOSD `json:"DOSD,omitempty"` + AR AR `json:"AR,omitempty"` + MAIN []VEC `json:"MAIN,omitempty"` + EOSD []EOSD `json:"EOSD,omitempty"` + IOP []IOP `json:"IOP,omitempty"` + SAP SAP `json:"SAP,omitempty"` + UAP UAP `json:"UAP,omitempty"` + PGDSM PGDSM `json:"PGDSM,omitempty"` + PVLAS PVLAS `json:"PVLAS,omitempty"` + PMDAS PMDAS `json:"PMDAS,omitempty"` + VS VS `json:"VS,omitempty"` + PSI PSI `json:"PSI,omitempty"` + + SWUS SWUS `json:"SWUS,omitempty"` + DSM DSM `json:"DSM,omitempty"` } diff --git a/scheme.go b/scheme.go index 12b67ab..4f4922c 100644 --- a/scheme.go +++ b/scheme.go @@ -1,5 +1,12 @@ package n9m +import ( + "encoding/json" + "fmt" + "strconv" + "time" +) + type PayloadType uint8 const ( @@ -13,10 +20,28 @@ const ( PayloadTypeTransmissionSubStream PayloadType = 15 PayloadTypeRecordingSubStream PayloadType = 16 PayloadTypeBlackBox PayloadType = 17 - PayloadTypeGPS PayloadType = 22 + PayloadTypeSpecial PayloadType = 22 PayloadTypeMaintainData PayloadType = 30 ) +type SpecialPayloadType uint16 + +const ( + SpecialPayloadTypeHeartbeat SpecialPayloadType = iota + SpecialPayloadTypeHeartbeatWithoutBody + SpecialPayloadTypeGPS + SpecialPayloadTypeMileage + SpecialPayloadTypeEnvironmentalQuality + SpecialPayloadTypeDrivingPosture + SpecialPayloadTypeScanningGun + SpecialPayloadTypeOil + SpecialPayloadTypeGDS + SpecialPayloadTypeGPSToBWS + SpecialPayloadTypeCANBOX + SpecialPayloadTypeGSenor + SpecialPayloadTypeAckGPS +) + type Message struct { Module string `json:"MODULE"` Session string `json:"SESSION"` @@ -25,6 +50,74 @@ type Message struct { Response interface{} `json:"RESPONSE,omitempty"` } +// 3.4.5.27.1 +type GPSData struct { + GPSStatus uint8 + Expand uint8 + Real uint8 + Longitude float64 + Latitude float64 + Speed float64 + Direction float64 + Altitude int32 + Time time.Time +} + +func (g *GPSData) MarshalJSON() ([]byte, error) { + var alias struct { + GPSStatus uint8 `json:"V"` + Longitude string `json:"J"` + Latitude string `json:"W"` + Speed uint `json:"S"` + Direction uint `json:"C"` + Altitude int32 `json:"H"` + Time string `json:"T"` + } + + alias.GPSStatus = g.GPSStatus + alias.Longitude = fmt.Sprintf("%.6f", g.Longitude) + alias.Latitude = fmt.Sprintf("%.6f", g.Latitude) + alias.Speed = uint(g.Speed * 100) + alias.Direction = uint(g.Direction * 100) + alias.Altitude = g.Altitude + alias.Time = g.Time.Format("20060102150405") + + return json.Marshal(alias) +} + +func (g *GPSData) UnmarshalJSON(data []byte) (err error) { + var alias struct { + GPSStatus uint8 `json:"V"` + Longitude string `json:"J"` + Latitude string `json:"W"` + Speed uint `json:"S"` + Direction uint `json:"C"` + Altitude int32 `json:"H"` + Time string `json:"T"` + } + + if err = json.Unmarshal(data, &alias); err != nil { + return + } + + g.GPSStatus = alias.GPSStatus + + if g.Longitude, err = strconv.ParseFloat(alias.Longitude, 64); err != nil { + return fmt.Errorf("invalid longitude: %w", err) + } + + if g.Latitude, err = strconv.ParseFloat(alias.Latitude, 64); err != nil { + return fmt.Errorf("invalid latitude: %w", err) + } + + g.Speed = float64(alias.Speed) / 100.0 + g.Direction = float64(alias.Direction) / 100.0 + g.Altitude = alias.Altitude + g.Time, _ = time.Parse("20060102150405", alias.Time) + + return nil +} + type Package struct { Version uint8 EncryptionFlag bool @@ -32,10 +125,12 @@ type Package struct { CSRCCount uint8 PayloadType PayloadType - SSRC uint16 + SSRC SpecialPayloadType Reserved uint64 CSRC [16]uint64 + GPS GPSData + payloadLen uint64 Payload Message RawPayload []byte