Compare commits

...

2 Commits

Author SHA1 Message Date
16da4affa8
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.
2025-02-22 19:23:16 +03:00
f3083820dc
Added testing and rewrite serializer
Signed-off-by: Alexander Lazarenko <kerblif@unprism.ru>
2025-02-07 21:04:06 +03:00
14 changed files with 800 additions and 211 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea

View File

@ -1,5 +1,60 @@
package n9m
type NetConnectionType uint
const (
NetConnectionWired NetConnectionType = iota
NetConnectionWireless
)
type TimeShiftSupportFlag uint8
const (
TimeShiftNotSupported TimeShiftSupportFlag = iota
TimeShiftSupported
)
type FileSystemVersionNumber uint8
const (
FileSystemVersion4 FileSystemVersionNumber = iota
FileSystemVersion5
)
type CertificateConnectRequest struct {
Net NetConnectionType `json:"NET"`
SerialNumber string `json:"DSNO"`
DeviceName string `json:"DEVNAME"`
ChannelsNumber uint `json:"CHANNEL"`
LicensePlate string `json:"CARNUM"`
DeviceNumber string `json:"AUTONO"`
VehicleNumber string `json:"AUTOCAR"`
TimeShiftSupport TimeShiftSupportFlag `json:"TSE"`
FileSystemVersion FileSystemVersionNumber `json:"FSV"`
ICCID string `json:"ICCID"`
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{
"MODULE": "CERTIFICATE",
@ -39,3 +94,5 @@ func (e *Package) ResponseCertificateConnect(Sid string) {
"SESSION": Sid,
}
}
*/

174
cmd/dev-server/main.go Normal file
View File

@ -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(&params); 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(&params); 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)
}
}
}
}

View File

@ -1,5 +1,7 @@
package n9m
/*
// request reqistration parameters (directly to register)
func (e *Package) RequestParameters(params map[string]any, serial int, session string) {
e.Payload = map[string]any{
@ -26,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
@ -69,3 +75,5 @@ func (e *Package) ResponseConfigModelSet(Sid string) {
"SESSION": Sid,
}
}
*/

View File

@ -1,6 +1,6 @@
package n9m
import "fmt"
/*
// main server util
func (e *Package) RequestGeolocation(serial int, Sid string) {
@ -34,3 +34,5 @@ func (e *Package) ResponseGeolocation(errorCode int, errorCause string, serial i
},
}
}
*/

172
evem.go
View File

@ -1,5 +1,175 @@
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) {
e.Payload = map[string]any{
@ -16,3 +186,5 @@ func (e *Package) ResponseAlarm(alarmType int64, alarmUID int64, cmdno int64, cm
"SESSION": Sid,
}
}
*/

10
go.mod
View File

@ -2,12 +2,4 @@ module gitea.unprism.ru/KRBL/n9m
go 1.21.3
require (
github.com/icza/bitio v1.1.0
github.com/tidwall/gjson v1.17.0
)
require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
)
require github.com/icza/bitio v1.1.0

7
go.sum
View File

@ -2,10 +2,3 @@ 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 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=

245
io.go
View File

@ -2,49 +2,14 @@ package n9m
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"log"
"github.com/icza/bitio"
"github.com/tidwall/gjson"
"log"
"time"
)
// Extract fields from JSON
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{})
}
}
// Read package
func (e *Package) ReadPackage() bool {
if len(e.Accum) < 12 {
@ -52,124 +17,172 @@ func (e *Package) ReadPackage() bool {
}
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)
e.Version = uint8(r.TryReadBits(2))
e.EncryptionFlag = r.TryReadBool()
e.CompressFlag = r.TryReadBool()
e.CSRCCount = uint8(r.TryReadBits(4))
e.PayloadType = PayloadType(r.TryReadBits(8))
e.SSRC = SpecialPayloadType((r.TryReadBits(8) | (r.TryReadBits(8) << 8)))
// log.Println(e.PayloadType)
is_special := e.Encription == 1 && e.Mark == 1
if is_special {
if e.EncryptionFlag && e.CompressFlag {
// TODO: get snippet, that use this code
r.TryReadBits(8 * 4)
e.PayloadLen = r.TryReadBits(8)
e.payloadLen = r.TryReadBits(8)
r.TryReadBits(3 * 8)
if uint64(len(e.Accum)) < e.PayloadLen+12 {
if uint64(len(e.Accum)) < e.payloadLen+12 {
return false
}
} else {
e.PayloadLen = r.TryReadBits(32)
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 e.payloadLen > 1e6 {
log.Printf("%v\n", e)
log.Panicln("CORRUPTED PACKAGE")
}
if numOfBytes != int(e.PayloadLen) {
numOfBytes := 0
if e.payloadLen != 0 {
e.RawPayload = make([]byte, e.payloadLen)
numOfBytes = r.TryRead(e.RawPayload)
} else {
e.RawPayload = []byte{}
}
if numOfBytes != int(e.payloadLen) {
return false
}
e.Raw = e.Accum[:12+e.PayloadLen]
e.Accum = e.Accum[12+e.PayloadLen:]
e.RawPayload = rawbytes
e.Accum = e.Accum[12+e.payloadLen:]
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{})
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)
}
e.SaveJsonFields()
if r.TryError != nil {
log.Printf("TryError encountered: %v", r.TryError)
return false
}
return e.PayloadLen > 0
return true
}
func (e *Package) PackPayload() (err error) {
e.RawPayload, err = json.Marshal(e.Payload)
e.payloadLen = uint64(len(e.RawPayload))
if e.payloadLen != 0 {
e.RawPayload = append(e.RawPayload, 0)
e.payloadLen++
}
return
}
func (e *Package) PackPackage() []byte {
e.SaveJsonFields()
var err error
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
if err = e.PackPayload(); err != nil {
log.Printf("Error while packing payload: %v", err)
return []byte{}
}
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()
return e.PackRawPackage()
}
func (e *Package) PackRawPackage() []byte {
var err error
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)
w.TryWriteBits(uint64(e.Version), 2)
w.TryWriteBool(e.EncryptionFlag)
w.TryWriteBool(e.CompressFlag)
w.TryWriteBits(uint64(e.CSRCCount), 4)
w.TryWriteBits(uint64(e.PayloadType), 8)
w.TryWriteBits(uint64(e.SSRC), 16)
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})
if e.payloadLen != 0 {
w.TryWrite(e.RawPayload)
}
if err = w.Close(); err != nil {
log.Printf("Error while closing writer: %v", err)
return []byte{}
}
w.Close()
return b.Bytes()
}
func (e *Package) GetParametersAs(parameters any) error {
marshal, err := json.Marshal(e.Payload.Parameter)
if err != nil {
return err
}
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
}

View File

@ -1,10 +1,6 @@
package n9m
import (
"fmt"
"os"
)
/*
var ip string = os.Getenv("SERVER_IP")
func (e *Package) MediaRequestDownloadVideo(token int, serial string, session string, camNo int, date string, begin_time string, end_time string, recordID string, serverId int) {
@ -108,3 +104,5 @@ func (e *Package) ControlRemotePlayback(token int, serial string, session string
"SESSION": session,
}
}
*/

View File

@ -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"`
}

145
scheme.go
View File

@ -1,32 +1,139 @@
package n9m
import "github.com/tidwall/gjson"
import (
"encoding/json"
"fmt"
"strconv"
"time"
)
type PayloadJson struct {
Module string
Key string
Operation string
Parameters map[string]interface{}
Response map[string]interface{}
type PayloadType uint8
const (
PayloadTypeData PayloadType = 0
PayloadTypeLive PayloadType = 2
PayloadTypeDownload PayloadType = 3
PayloadTypePlayback PayloadType = 4
PayloadTypeCapturedPhotos PayloadType = 6
PayloadTypeParameterImport PayloadType = 10
PayloadTypeParameterExport PayloadType = 11
PayloadTypeTransmissionSubStream PayloadType = 15
PayloadTypeRecordingSubStream PayloadType = 16
PayloadTypeBlackBox PayloadType = 17
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"`
Operation string `json:"OPERATION"`
Parameter interface{} `json:"PARAMETER,omitempty"`
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 uint64
Encription uint64
Mark uint64
CC uint64
PayloadType uint64
SSRC uint64
Version uint8
EncryptionFlag bool
CompressFlag bool
CSRCCount uint8
PayloadType PayloadType
SSRC SpecialPayloadType
Reserved uint64
CSRC [16]uint64
PayloadLen uint64
GPayload gjson.Result
Payload map[string]interface{}
GPS GPSData
payloadLen uint64
Payload Message
RawPayload []byte
Raw []byte
Accum []byte
Json PayloadJson
}

View File

@ -1,5 +1,7 @@
package n9m
/*
func (e *Package) ResponseCalendar(errorCode int, errorCause string, serial int, dates []string) {
e.Payload = map[string]any{
"MODULE": "STORM",
@ -78,3 +80,5 @@ func (e *Package) RequestFileList(queryTime string, serial int, session string,
"SESSION": session,
}
}
*/

28
test/certificate_test.go Normal file
View File

@ -0,0 +1,28 @@
package test
import (
"fmt"
"gitea.unprism.ru/KRBL/n9m"
"testing"
)
func TestCertificateConnection(t *testing.T) {
dump := []byte{0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xe9, 0x52, 0x0, 0x0, 0x0, 0x7b, 0x22, 0x4d, 0x4f, 0x44, 0x55, 0x4c, 0x45, 0x22, 0x3a, 0x22, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x22, 0x2c, 0x22, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x22, 0x3a, 0x22, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x22, 0x2c, 0x22, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x22, 0x3a, 0x7b, 0x22, 0x41, 0x55, 0x54, 0x4f, 0x43, 0x41, 0x52, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x41, 0x55, 0x54, 0x4f, 0x4e, 0x4f, 0x22, 0x3a, 0x22, 0x30, 0x22, 0x2c, 0x22, 0x43, 0x41, 0x52, 0x4e, 0x55, 0x4d, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x22, 0x3a, 0x31, 0x32, 0x2c, 0x22, 0x43, 0x49, 0x44, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x43, 0x4e, 0x41, 0x4d, 0x45, 0x22, 0x3a, 0x22, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x45, 0x52, 0x5f, 0x4e, 0x55, 0x4c, 0x4c, 0x22, 0x2c, 0x22, 0x44, 0x45, 0x56, 0x43, 0x4c, 0x41, 0x53, 0x53, 0x22, 0x3a, 0x34, 0x2c, 0x22, 0x44, 0x45, 0x56, 0x4e, 0x41, 0x4d, 0x45, 0x22, 0x3a, 0x22, 0x4d, 0x44, 0x56, 0x52, 0x22, 0x2c, 0x22, 0x44, 0x45, 0x56, 0x54, 0x59, 0x50, 0x45, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x44, 0x4c, 0x49, 0x50, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x4c, 0x49, 0x50, 0x22, 0x3a, 0x22, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x22, 0x2c, 0x22, 0x4d, 0x54, 0x22, 0x3a, 0x22, 0x65, 0x74, 0x68, 0x30, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x44, 0x4c, 0x50, 0x22, 0x3a, 0x5b, 0x38, 0x30, 0x2c, 0x39, 0x30, 0x30, 0x36, 0x5d, 0x2c, 0x22, 0x44, 0x53, 0x4e, 0x4f, 0x22, 0x3a, 0x22, 0x30, 0x30, 0x38, 0x38, 0x30, 0x33, 0x36, 0x31, 0x42, 0x38, 0x22, 0x2c, 0x22, 0x45, 0x49, 0x44, 0x22, 0x3a, 0x22, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x22, 0x2c, 0x22, 0x45, 0x56, 0x22, 0x3a, 0x22, 0x56, 0x31, 0x2e, 0x31, 0x22, 0x2c, 0x22, 0x46, 0x53, 0x56, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x4c, 0x49, 0x4e, 0x45, 0x4e, 0x4f, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x4d, 0x41, 0x43, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x49, 0x4d, 0x41, 0x43, 0x22, 0x3a, 0x22, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x22, 0x2c, 0x22, 0x4d, 0x54, 0x22, 0x3a, 0x22, 0x65, 0x74, 0x68, 0x30, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x4d, 0x4f, 0x44, 0x45, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x4d, 0x54, 0x59, 0x50, 0x45, 0x22, 0x3a, 0x33, 0x31, 0x2c, 0x22, 0x4e, 0x45, 0x54, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x50, 0x52, 0x4f, 0x22, 0x3a, 0x22, 0x31, 0x2e, 0x30, 0x2e, 0x35, 0x22, 0x2c, 0x22, 0x53, 0x54, 0x59, 0x50, 0x45, 0x22, 0x3a, 0x35, 0x34, 0x2c, 0x22, 0x54, 0x53, 0x45, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x55, 0x4e, 0x41, 0x4d, 0x45, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x55, 0x4e, 0x4f, 0x22, 0x3a, 0x22, 0x22, 0x7d, 0x2c, 0x22, 0x53, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x22, 0x3a, 0x22, 0x36, 0x63, 0x65, 0x64, 0x35, 0x63, 0x66, 0x37, 0x2d, 0x61, 0x34, 0x35, 0x63, 0x2d, 0x34, 0x61, 0x63, 0x61, 0x2d, 0x62, 0x39, 0x64, 0x35, 0x2d, 0x66, 0x65, 0x39, 0x34, 0x61, 0x62, 0x36, 0x32, 0x66, 0x39, 0x65, 0x61, 0x22, 0x7d, 0xa}
pack := n9m.Package{}
pack.AddToAccum(dump)
if !pack.ReadPackage() {
t.Error("Package wasn't read!")
return
}
var connectionParams n9m.CertificateConnectRequest
if err := pack.GetParametersAs(&connectionParams); err != nil {
t.Error(err)
return
}
fmt.Println(connectionParams)
}