From 51308a2395af41140c55d2a4b4f8eb47c7636351 Mon Sep 17 00:00:00 2001 From: Alexander Lazarenko Date: Sat, 26 Apr 2025 20:11:26 +0300 Subject: [PATCH] Working video receiver --- certificate.go | 70 +++++++++ cmd/dev-client/main.go | 279 +++++++++++++++++++++++++++++++++++ cmd/dev-client/utils.go | 15 ++ cmd/dev-client/utils_test.go | 9 ++ mediastreammodel.go | 27 ++++ 5 files changed, 400 insertions(+) create mode 100644 cmd/dev-client/main.go create mode 100644 cmd/dev-client/utils.go create mode 100644 cmd/dev-client/utils_test.go diff --git a/certificate.go b/certificate.go index a6d8c60..7869ab9 100644 --- a/certificate.go +++ b/certificate.go @@ -35,12 +35,64 @@ type CertificateConnectRequest struct { EvidenceSupport string `json:"EV"` } +type CertificateConnectClientRequest struct { + UK string `json:"UK"` +} + type CertificateConnectResponse struct { ErrorCode uint `json:"ERRORCODE"` ErrorCause string `json:"ERRORCAUSE"` CommandMask uint `json:"MASKCMD"` } +type CertificateConnectClientResponse struct { + S0 string `json:"S0"` +} + +type CertificateVerificationRequest struct { + S0 string `json:"S0"` +} + +type CertificateVerificationResponse struct { + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` + ReturnFlag bool `json:"RETURN"` +} + +type CertificateLoginRequest struct { + ClientID uint `json:"CID,omitempty"` + MAC string `json:"MAC,omitempty"` + User string `json:"USER"` + Password string `json:"PASSWD"` + PlayDevID uint `json:"PLAYDEVID"` +} + +type CertificateLoginResponse struct { + SerialNumber string `json:"DSNO"` + DeviceName string `json:"DEVNAME"` + ChannelsNumber uint `json:"CHANNEL"` + UID string `json:"UID"` + AlarmInputNumber uint `json:"ALARMIN"` + AlarmOutputNumber uint `json:"ALARMOUT"` + DeviceType string `json:"TYPE"` + DeviceClass DeviceType `json:"DEVCLASS"` + CurrentVersion string `json:"PRO"` + LicensePlate string `json:"CARNUM"` + UserLever UserAccessLevel `json:"LEVEL"` + CompanyName string `json:"CPN"` + CustomerName string `json:"CNAME"` + AudioChannelsNumber uint `json:"ACHN"` +} + +type CertificateCreateStreamRequest struct { + StreamName string `json:"STREAMNAME"` +} + +type CertificateCreateStreamResponse struct { + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` +} + type CommandMaskParameters uint const ( @@ -54,6 +106,24 @@ const ( CommandMaskAll = 0b1111111 ) +type DeviceType uint + +const ( + DeviceTypeDVR DeviceType = 1 + iota + DeviceTypeIPC + DeviceTypeNVR + DeviceTypeMIPC + DeviceTypeMDVR +) + +type UserAccessLevel uint + +const ( + UserAccessLevelSuperAdmin UserAccessLevel = iota + UserAccessLevelAdministrator + UserAccessLeverUser +) + /* func (e *Package) RequestConnect(session string, serial string, numOfCams int) { e.Payload = map[string]any{ diff --git a/cmd/dev-client/main.go b/cmd/dev-client/main.go new file mode 100644 index 0000000..970c32d --- /dev/null +++ b/cmd/dev-client/main.go @@ -0,0 +1,279 @@ +package main + +import ( + "errors" + "fmt" + "gitea.unprism.ru/KRBL/n9m/v2" + "io" + "net" + "os" + "syscall" +) + +var mainPack *n9m.SmartPackage +var videoPack *n9m.SmartPackage + +func main() { + conn, err := net.Dial("tcp", "10.100.100.99:9006") + + if err != nil { + panic(err) + } + + var pack = n9m.Package{} + + pack.Payload.Module = "CERTIFICATE" + pack.Payload.Operation = "CONNECT" + pack.SetParameters(n9m.CertificateConnectClientRequest{}) + conn.Write(pack.PackPackage()) + + handle(conn, &mainPack) +} + +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.CertificateConnectClientResponse + + if err = pack.GetResponseAs(¶ms); err != nil { + return fmt.Errorf("failed to get response: %w", err) + } + + var response = n9m.CertificateVerificationRequest{ + S0: GenerateVerifyKey(params.S0), + } + + pack.Payload.Operation = "VERIFY" + pack.SetParameters(response) + + sPack.Write(pack.PackPackage()) + + return +} + +func handleVerify(sPack *n9m.SmartPackage, pack n9m.Package) (err error) { + var params n9m.CertificateVerificationResponse + + if err = pack.GetResponseAs(¶ms); err != nil { + return fmt.Errorf("failed to get response: %w", err) + } + + if params.ErrorCode == 0 { + fmt.Println("ШАЛОСТЬ УДАЛАСЬ!") + } else { + fmt.Println("шалость НЕ удалась(((") + } + + var request = n9m.CertificateLoginRequest{ + ClientID: 0, + MAC: "", + User: "admin", + Password: "", + } + + pack.Payload.Operation = "LOGIN" + pack.SetParameters(request) + + sPack.Write(pack.PackPackage()) + + return +} + +func handleLogin(sPack *n9m.SmartPackage, pack n9m.Package) (err error) { + var params n9m.CertificateLoginResponse + + if err = pack.GetResponseAs(¶ms); err != nil { + return fmt.Errorf("failed to get response: %w", err) + } + + conn, err := net.Dial("tcp", "10.100.100.99:9006") + + var request = n9m.CertificateCreateStreamRequest{ + StreamName: "KRBL", + } + + pack.Payload.Operation = "CREATESTREAM" + pack.SetParameters(request) + + conn.Write(pack.PackPackage()) + + go handle(conn, &videoPack) + + return +} + +func handleCreateStream(sPack *n9m.SmartPackage, pack n9m.Package) (err error) { + var params n9m.CertificateCreateStreamResponse + + if err = pack.GetResponseAs(¶ms); err != nil { + return fmt.Errorf("failed to get response: %w", err) + } + + if params.ErrorCode != 0 { + fmt.Println("Create stream error:", params.ErrorCode, params.ErrorCause) + return + } + + var request = n9m.MediaStreamModelRequestLiveVideoRequest{ + StreamName: "KRBL", + StreamType: 2, + Channel: 1, + AudioValid: 1, + FrameMode: 0, + } + + pack.Payload.Module = "MEDIASTREAMMODEL" + pack.Payload.Operation = "REQUESTALIVEVIDEO" + pack.SetParameters(request) + + mainPack.Write(pack.PackPackage()) + + return +} + +func handleRequestLiveVideo(sPack *n9m.SmartPackage, pack n9m.Package) (err error) { + var params n9m.MediaStreamModelRequestLiveVideoResponse + + if err = pack.GetResponseAs(¶ms); err != nil { + return fmt.Errorf("failed to get response: %w", err) + } + + if params.ErrorCode != 0 { + fmt.Println("Request live stream error:", params.ErrorCode, params.ErrorCause) + return + } + + 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) + + var request n9m.ConfigModelSetRequest + + if err = pack.GetParametersAs(&request); err != nil { + fmt.Println(err) + return err + } + + 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 handleSPI(_ *n9m.SmartPackage, pack n9m.Package) (err error) { + var params n9m.SpiParameters + + if err = pack.GetParametersAs(¶ms); err != nil { + return + } + + fmt.Printf("%+v\n", params) + + return +} + +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", "VERIFY", handleVerify) + pack.AddJSONHandler("CERTIFICATE", "LOGIN", handleLogin) + pack.AddJSONHandler("CERTIFICATE", "CREATESTREAM", handleCreateStream) + pack.AddJSONHandler("CERTIFICATE", "KEEPALIVE", handleKeepAlive) + pack.AddJSONHandler("MEDIASTREAMMODEL", "REQUESTALIVEVIDEO", handleRequestLiveVideo) + pack.AddJSONHandler("CONFIGMODEL", "GET", handleGetConfig) + pack.AddJSONHandler("DEVEMM", "SPI", handleSPI) + + 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 **n9m.SmartPackage) { + if pack != nil { + *pack = createSmartPackage(conn) + } else { + tmp := createSmartPackage(conn) + pack = &tmp + } + + var err error + for { + if err = (*pack).Handle(); err != nil { + fmt.Println("Error:", err) + + if isNetConnClosedErr(err) { + return + } + } + } +} diff --git a/cmd/dev-client/utils.go b/cmd/dev-client/utils.go new file mode 100644 index 0000000..224c747 --- /dev/null +++ b/cmd/dev-client/utils.go @@ -0,0 +1,15 @@ +package main + +import ( + "crypto/hmac" + "crypto/md5" + "encoding/hex" +) + +func GenerateVerifyKey(key string) string { + mac := hmac.New(md5.New, []byte(key)) + + mac.Write([]byte(key)) + + return hex.EncodeToString(mac.Sum(nil)) +} diff --git a/cmd/dev-client/utils_test.go b/cmd/dev-client/utils_test.go new file mode 100644 index 0000000..4bdce81 --- /dev/null +++ b/cmd/dev-client/utils_test.go @@ -0,0 +1,9 @@ +package main + +import "testing" + +func TestGenerateVerifyKey(t *testing.T) { + if GenerateVerifyKey("45792d55-0844-4053-848a-64fd0685fb32") != "fae5f19b8452c7bd03dc4f3471966c42" { + t.Error() + } +} diff --git a/mediastreammodel.go b/mediastreammodel.go index 3f5b536..02d2b82 100644 --- a/mediastreammodel.go +++ b/mediastreammodel.go @@ -1,5 +1,32 @@ package n9m +type StreamType uint + +const ( + StreamTypeSub StreamType = iota + StreamTypeMain + StreamTypeMobile +) + +type MediaStreamModelRequestLiveVideoRequest struct { + SSRC uint `json:"SSRC,omitempty"` + StreamName string `json:"STREAMNAME"` + StreamType StreamType `json:"STREAMTYPE"` + Channel uint `json:"CHANNEL"` + AudioValid uint `json:"AUDIOVALID"` + Destination string `json:"IPANDPORT,omitempty"` + FrameCount uint `json:"FRAMECOUNT,omitempty"` + FrameMode uint `json:"FRAMEMODE"` +} + +type MediaStreamModelRequestLiveVideoResponse struct { + SSRC uint `json:"SSRC"` + StreamName string `json:"STREAMNAME"` + StreamType StreamType `json:"STREAMTYPE"` + ErrorCode uint `json:"ERRORCODE"` + ErrorCause string `json:"ERRORCAUSE"` +} + /* var ip string = os.Getenv("SERVER_IP")