11 Commits

Author SHA1 Message Date
102c9bb36a Refactor network handling with SmartPackage abstraction
Introduced the SmartPackage struct to simplify and centralize logic for handling various payloads, including JSON and alarms. Moved specific handlers into dedicated functions and utilized maps for dynamic process function assignment. Improved error handling and modularity for better scalability and maintainability.
2025-02-22 21:15:03 +03:00
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
f6f762d0dc Remove github.com/tidwall/pretty v1.2.0 from go.sum 2024-01-06 14:59:00 +03:00
11b19333e1 Refactor MediaRequestRemotePlayback function to handle end_time parameter 2024-01-06 14:56:42 +03:00
d302e599ce Added parameters 2023-12-31 14:13:27 +03:00
3241d59e7b Returned env 2023-11-30 21:37:30 +03:00
71d0bc1018 commented logs 2023-11-30 21:35:52 +03:00
bfdca3ffcb File split 2023-11-30 20:05:11 +03:00
b26b32ea73 Change name 2023-11-29 15:01:15 +03:00
975812efdb Change golang package name 2023-11-29 14:45:16 +03:00
16 changed files with 1601 additions and 540 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea

98
certificate.go Normal file
View File

@ -0,0 +1,98 @@
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",
"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,
}
}
// 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,
}
}
*/

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

@ -0,0 +1,169 @@
package main
import (
"errors"
"fmt"
"gitea.unprism.ru/KRBL/n9m"
"io"
"net"
"os"
"syscall"
)
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 handleSpecialPackages(_ *n9m.SmartPackage, pack n9m.Package) error {
switch pack.SSRC {
case n9m.SpecialPayloadTypeGPS:
fmt.Printf("%+v\n", pack.GPS)
return nil
default:
return fmt.Errorf("unhandled special operation: %d", pack.SSRC)
}
}
func handleCertificateConnect(sPack *n9m.SmartPackage, pack n9m.Package) (err error) {
var params n9m.CertificateConnectRequest
if err = pack.GetParametersAs(&params); err != nil {
return fmt.Errorf("failed to get parameters: %w", err)
}
var response = n9m.CertificateConnectResponse{
ErrorCode: 0,
CommandMask: n9m.CommandMaskAll,
}
pack.SetResponse(response)
if _, err = sPack.Write(pack.PackPackage()); err != nil {
return fmt.Errorf("failed to write package: %w", err)
}
fmt.Println("Connected:", params.SerialNumber)
sPack.Storage["serial"] = params.SerialNumber
var request n9m.ConfigModelGetRequest
request.MDVR = "?"
pack.Payload.Module = "CONFIGMODEL"
pack.Payload.Operation = "GET"
pack.SetParameters(request)
sPack.Write(pack.PackPackage())
return
}
func handleKeepAlive(sPack *n9m.SmartPackage, pack n9m.Package) (err error) {
serial := sPack.Storage["serial"]
fmt.Println(serial, "still alive!")
pack.SetResponse(nil)
sPack.Write(pack.PackPackage())
return
}
func handleGetConfig(sPack *n9m.SmartPackage, pack n9m.Package) (err error) {
serial := sPack.Storage["serial"]
os.WriteFile(fmt.Sprintf("./%s.json", serial), pack.RawPayload, 0644)
return
}
func handleUselessAlarms(sPack *n9m.SmartPackage, pack n9m.Package, response n9m.SendAlarmInfoResponse) (err error) {
return nil
}
func handleVideoLossAlarm(sPack *n9m.SmartPackage, pack n9m.Package, response n9m.SendAlarmInfoResponse) (err error) {
fmt.Println("Video loss alarm!")
return nil
}
func handleCameraCoveredAlarm(sPack *n9m.SmartPackage, pack n9m.Package, response n9m.SendAlarmInfoResponse) (err error) {
fmt.Println("Camera covered alarm!")
return nil
}
func createSmartPackage(conn net.Conn) (pack *n9m.SmartPackage) {
pack = n9m.NewSmartPackage(conn)
pack.AddPayloadHandler(n9m.PayloadTypeSpecial, handleSpecialPackages)
pack.AddJSONHandler("CERTIFICATE", "CONNECT", handleCertificateConnect)
pack.AddJSONHandler("CERTIFICATE", "KEEPALIVE", handleKeepAlive)
pack.AddJSONHandler("CONFIGMODEL", "GET", handleGetConfig)
pack.AddAlarmHandler(n9m.AlarmTypeMotionDetection, handleUselessAlarms)
pack.AddAlarmHandler(n9m.AlarmTypeVideoLoss, handleVideoLossAlarm)
pack.AddAlarmHandler(n9m.AlarmTypeCameraCovered, handleCameraCoveredAlarm)
return
}
/*
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
}
}
}()
*/
func isNetConnClosedErr(err error) bool {
switch {
case
errors.Is(err, net.ErrClosed),
errors.Is(err, io.EOF),
errors.Is(err, syscall.EPIPE):
return true
default:
return false
}
}
func handle(conn net.Conn) {
pack := createSmartPackage(conn)
var err error
for {
if err = pack.Handle(); err != nil {
fmt.Println("Error:", err)
if isNetConnClosedErr(err) {
return
}
}
}
}

79
configmodel.go Normal file
View File

@ -0,0 +1,79 @@
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{
"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
*/
type ConfigModelSetRequest struct {
MDVR Setting `json:"MDVR"`
}
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
"OPERATION": "SET",
"RESPONSE": map[string]any{
"ERRORCODE": 0,
"ERRORCAUSE": "None",
"ERRORDESCRIPTION": "None",
},
"SESSION": Sid,
}
}
*/

38
devemm.go Normal file
View File

@ -0,0 +1,38 @@
package n9m
/*
// 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
},
},
}
}
*/

190
evem.go Normal file
View File

@ -0,0 +1,190 @@
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{
"MODULE": "EVEM",
"OPERATION": "SENDALARMINFO",
"RESPONSE": map[string]any{
"ALARMTYPE": alarmType,
"ALARMUID": alarmUID,
"CMDNO": cmdno,
"CMDTYPE": cmdtype,
"ERRORCODE": 0,
"RUN": run,
},
"SESSION": Sid,
}
}
*/

9
go.mod
View File

@ -1,10 +1,5 @@
module n9m
module gitea.unprism.ru/KRBL/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
)
require github.com/icza/bitio v1.1.0

7
go.sum
View File

@ -1,9 +1,4 @@
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 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=

188
io.go Normal file
View File

@ -0,0 +1,188 @@
package n9m
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/icza/bitio"
"log"
"time"
)
// Read package
func (e *Package) ReadPackage() bool {
if len(e.Accum) < 12 {
return false
}
r := bitio.NewReader(bytes.NewBuffer(e.Accum))
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)))
if e.EncryptionFlag && e.CompressFlag {
// TODO: get snippet, that use this code
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)
}
}
if e.payloadLen > 1e6 {
log.Printf("%v\n", e)
log.Panicln("CORRUPTED PACKAGE")
}
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.Accum = e.Accum[12+e.payloadLen:]
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 {
log.Printf("TryError encountered: %v", r.TryError)
return false
}
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 {
var err error
if err = e.PackPayload(); err != nil {
log.Printf("Error while packing payload: %v", err)
return []byte{}
}
return e.PackRawPackage()
}
func (e *Package) PackRawPackage() []byte {
var err error
b := &bytes.Buffer{}
w := bitio.NewWriter(b)
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.TryWrite(e.RawPayload)
}
if err = w.Close(); err != nil {
log.Printf("Error while closing writer: %v", err)
return []byte{}
}
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
}

108
mediastreammodel.go Normal file
View File

@ -0,0 +1,108 @@
package n9m
/*
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) {
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) 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) {
if end_time == "" {
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,
"IPANDPORT": ip + ":12092",
"SERIAL": token,
"PBST": 0,
},
"SESSION": session,
}
} else {
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,
}
}
*/

335
params.go Normal file
View File

@ -0,0 +1,335 @@
package n9m
// 7.2
type RIP struct {
DEVID string // ID номер устройства
BN string // бортовой номер
BID string // гос. номер
LN string // Название линии
DID string // номер водительского удостоверения
DNAME string // ФИО водителя
}
type VS struct {
VIN string // VIN номер
}
type TIMEP struct {
DATEM int // формат даты
TIMEM int // формат времени
TIMEZ string // часовой пояс
}
type ATP struct {
GE int // Синхронизация времени при помощи ГНСС
NE int // Синхронизация времени при помощи NTP
NS string // NTP сервер для синхронизации
}
type SSP struct {
UPT int // вкл/выкл
DDS int // задержка перед выключением (при выборе зажигания)
UH int // час включения
UM int // минута включения
US int // минута включения
DH int // час выключения
DM int // минута выключения
DS int // секунда выключения
CPM int // время выключения света
}
type SWUS struct {
IOWP int // Включение через IO
GSWP int // включение через G-сенсор
TX string // порог по X для G-сенсорa
TY string // порог по Y для G-сенсорa
TZ string // порог по Z для G-сенсорa
RTWP int // удаленное включение
PHWP int // включение через телефон
PTWP int // включение через SMS
}
type UMP struct {
UIF []UIF // список юзеров
}
type UIF struct {
UN string // username
PW string // password
UR int // level
}
type SUBSTRNET struct {
NEC []NEC
RLE int
SAE int
SM int // Качество дополнительных потоков
TOTALBW int // Максимальная скорость интернета
}
type NEC struct {
AEN int // звук (вкл/выкл)
VEN int // видео (вкл/выкл)
BR int // битрейт
ECT int // кодировка
FR int // фреймрейт
FT int // тип кадров
QLT int // качество трансляции
RST int // разрешение
}
type DOSD struct {
CHN []string // названия каналов
}
type AR struct {
HDAE int // Двойная запись на жесткий диск
HID int // выбрать место для записи резервного потока
RM int // режим записи резервного потока
VEC []VEC // параметры суб. потоков
}
type VEC struct {
VEN int // видео (вкл/выкл)
RST int // разрешение видеопотока
ECT int // кодировка
FR int // фреймрейт
QLT int // качество записи
AEN int // звук (вкл/выкл)
ALT int // качество видео при тревоге
}
type EOSD struct {
COSD []COSD
DE int // показывать ли ID регистратора
DTE int
DX int
DY int
GE int // показывать ли GPS
GX int
GY int
NE int // показывать ли название канала
NX int
NY int
SE int // показывать ли скорость
SX int
SY int
TE int // показывать ли время
TX int
TY int
VE int // показывать ли номер устройства
VX int
VY int
WME int
}
type COSD struct {
EN int
F int
ID int
L int
T string
TP int
X int
Y int
}
type IOP struct {
EN int
AS int
EL int
SDT int
APR APR
}
type SAP struct {
EN int
AS int
WP int
SV int
AT int
APR APR
UN int
}
type APR struct {
AR ARR
SS SS
ET int
}
type ARR struct {
CH int
D int
}
type SS struct {
EN int
}
type UAP struct {
EN int
AS int
VT int
SDT int
}
type PVLAS struct {
EN int
AS int
SDT int
CH int
}
type PMDAS struct {
EN int
AS int
SDT int
CH int
}
type DSM struct {
DSMA int
DSMFE int
RWFE int
}
type GSP struct {
LANT int // Язык системы
GM int // Система геопозиционирования
}
type ETHERNET struct {
IPMODE int // Тип IP
PIP PIP // Параметры IP
DNSMODE int // Автоматическое получение DNS
DNS DNS // Параметры DNS
KEYS KEYS
}
type PIP struct {
IPADDR string // IP адрес
SUBMASK string // Маска подсети
GATEWAY string // Шлюз сети
}
type DNS struct {
PDNS string // Основной DNS
ADNS string // Дополнительный DNS
}
type KEYS struct {
MAC string `json:"MAC,omitempty"` // MAC-адрес
GV uint `json:"GV,omitempty"` // GPS version
}
type WIFI struct {
ENABLE int // Статус WIFI
ESSID string // Название точки доступа
ECRYPTTYPE int // Тип защиты WIFI
PWD string // Пароль от WIFI
IPMODE int // Вид IP
PIP PIP // Параметры IP
}
type M3G struct {
M3M M3M // Управление активацией
MP MP // Параметры первого модуля связи
M4G MP // Параметры второго модуля связи
}
type M3M struct {
AT int // Режим работы модуля связи
TN1 string // Номер активации 1
TN2 string // Номер активации 2
TN3 string // Номер активации 3
}
type MP struct {
NM int // Выбор соединения
APN string // APN
UN string // Логин
PW string // Пароль
}
type MCMS struct {
M int // Битовая маска включенных серверов
SP []SP // Параметры сервера
}
type SP struct {
EN int // Статус сервера
CP int // Протокол подключения
NWT int // Способ подключения
CS string // Адрес сервера
MS string // Адрес медиасервера
CPORT int // TCP-порт сервера
MPORT int // TCP-порт медиасервера
CUPORT int // UDP-порт сервера
MUPORT int // UDP-порт медиасервера
}
// 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"`
}

155
scheme.go Normal file
View File

@ -0,0 +1,155 @@
package n9m
import (
"encoding/json"
"fmt"
"net"
"strconv"
"time"
)
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 uint8
EncryptionFlag bool
CompressFlag bool
CSRCCount uint8
PayloadType PayloadType
SSRC SpecialPayloadType
Reserved uint64
CSRC [16]uint64
GPS GPSData
payloadLen uint64
Payload Message
RawPayload []byte
Accum []byte
}
type ProcessFunc func(*SmartPackage, Package) error
type AlarmProcessFunc func(*SmartPackage, Package, SendAlarmInfoResponse) error
type SmartPackage struct {
pack Package
conn net.Conn
buff []byte
payloadProcess map[PayloadType]ProcessFunc
jsonProcess map[string]ProcessFunc
alarmProcess map[AlarmType]AlarmProcessFunc
Storage map[string]interface{}
}

124
smart.go Normal file
View File

@ -0,0 +1,124 @@
package n9m
import (
"fmt"
"net"
)
func NewSmartPackage(conn net.Conn) *SmartPackage {
return &SmartPackage{
pack: Package{},
conn: conn,
buff: make([]byte, 1024),
payloadProcess: make(map[PayloadType]ProcessFunc),
jsonProcess: make(map[string]ProcessFunc),
alarmProcess: make(map[AlarmType]AlarmProcessFunc),
Storage: make(map[string]interface{}),
}
}
func (pack *SmartPackage) AddPayloadHandler(payloadType PayloadType, processFunc ProcessFunc) {
pack.payloadProcess[payloadType] = processFunc
}
func (pack *SmartPackage) AddJSONHandler(module, operation string, processFunc ProcessFunc) {
pack.jsonProcess[fmt.Sprintf("%s:%s", module, operation)] = processFunc
}
func (pack *SmartPackage) AddAlarmHandler(alarmType AlarmType, processFunc AlarmProcessFunc) {
pack.alarmProcess[alarmType] = processFunc
}
func (pack *SmartPackage) handleAlarm() (err error) {
if !(pack.pack.PayloadType == PayloadTypeData && pack.pack.Payload.Module == "EVEM" && pack.pack.Payload.Operation == "SENDALARMINFO") {
return fmt.Errorf("invalid payload type or operation for alarm handling")
}
var params SendAlarmInfoParameters
if err = pack.pack.GetParametersAs(&params); err != nil {
return fmt.Errorf("invalid payload")
}
var processFunc AlarmProcessFunc
var ok bool
if processFunc, ok = pack.alarmProcess[params.AlarmType]; !ok {
return fmt.Errorf("unhanled alarm")
}
var response SendAlarmInfoResponse
response.ErrorCode = 0
response.AlarmType = params.AlarmType
response.CommandType = params.CommandType
response.AlarmUID = params.AlarmUID
response.NumberOfRestarts = params.NumberOfRestarts
response.InstructionSerial = params.InstructionSerial
return processFunc(pack, pack.pack, response)
}
func (pack *SmartPackage) handleJson() (err error) {
if pack.pack.PayloadType != PayloadTypeData {
return fmt.Errorf("invalid json payload type")
}
if err = pack.handleAlarm(); err == nil {
return
}
var processFunc ProcessFunc
var ok bool
if processFunc, ok = pack.jsonProcess[fmt.Sprintf("%s:%s", pack.pack.Payload.Module, pack.pack.Payload.Operation)]; !ok {
return fmt.Errorf("unhanled operation")
}
return processFunc(pack, pack.pack)
}
func (pack *SmartPackage) handle() (err error) {
if err = pack.handleJson(); err == nil {
return
}
var processFunc ProcessFunc
var ok bool
if processFunc, ok = pack.payloadProcess[pack.pack.PayloadType]; !ok {
return fmt.Errorf("unhanled payload type")
}
return processFunc(pack, pack.pack)
}
func (pack *SmartPackage) handleLoop() (err error) {
for pack.pack.ReadPackage() {
if err = pack.handle(); err != nil {
return err
}
}
return
}
func (pack *SmartPackage) Handle() (err error) {
if err = pack.handleLoop(); err != nil {
return err
}
var rn int
if rn, err = pack.conn.Read(pack.buff); err != nil {
return
}
pack.pack.AddToAccum(pack.buff[:rn])
return pack.handleLoop()
}
func (pack *SmartPackage) GetPackage() Package {
return pack.pack
}
func (pack *SmartPackage) Write(data []byte) (int, error) {
return pack.conn.Write(data)
}

84
storm.go Normal file
View File

@ -0,0 +1,84 @@
package n9m
/*
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,
}
}
*/

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)
}

528
utils.go
View File

@ -1,540 +1,14 @@
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 разобраться зачем оно ъ
// add bytes to accum
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() {