diff --git a/cmd/dev-server/main.go b/cmd/dev-server/main.go index fc624f3..eca8062 100644 --- a/cmd/dev-server/main.go +++ b/cmd/dev-server/main.go @@ -1,10 +1,13 @@ package main import ( + "errors" "fmt" "gitea.unprism.ru/KRBL/n9m" + "io" "net" "os" + "syscall" ) func main() { @@ -27,147 +30,139 @@ func main() { } } -func handle(conn net.Conn) { - var err error - var rn int +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) + } +} - var serial string +func handleCertificateConnect(sPack *n9m.SmartPackage, pack n9m.Package) (err error) { + var params n9m.CertificateConnectRequest - packS := n9m.Package{} - tmp := make([]byte, 1024) + if err = pack.GetParametersAs(¶ms); err != nil { + return fmt.Errorf("failed to get parameters: %w", err) + } - for { - rn, err = conn.Read(tmp) + var response = n9m.CertificateConnectResponse{ + ErrorCode: 0, + CommandMask: n9m.CommandMaskAll, + } - if err != nil { - fmt.Println(err) - return + 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 + } } + }() +*/ - packS.AddToAccum(tmp[:rn]) +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 + } +} - for packS.ReadPackage() { - switch packS.PayloadType { - case n9m.PayloadTypeData: - combined := packS.Payload.Module + ":" + packS.Payload.Operation +func handle(conn net.Conn) { + pack := createSmartPackage(conn) - switch combined { - case "CERTIFICATE:CONNECT": - var params n9m.CertificateConnectRequest + var err error + for { + if err = pack.Handle(); err != nil { + fmt.Println("Error:", err) - 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) + if isNetConnClosedErr(err) { + return } } } diff --git a/scheme.go b/scheme.go index 4f4922c..e82c72e 100644 --- a/scheme.go +++ b/scheme.go @@ -3,6 +3,7 @@ package n9m import ( "encoding/json" "fmt" + "net" "strconv" "time" ) @@ -137,3 +138,18 @@ type Package struct { 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{} +} diff --git a/smart.go b/smart.go new file mode 100644 index 0000000..401880f --- /dev/null +++ b/smart.go @@ -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(¶ms); 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) +}