From 42e91867ecbea18939d02785e1eb0605aee74b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Verstraeten?= Date: Mon, 13 Jul 2026 21:10:55 +0200 Subject: [PATCH] Refactor JSON handling and enhance motion detection Replace unsafe fmt.Sprintf JSON formatting with proper struct marshalling in cloud.go. Add rawJSONOrEmptyArray() helper to safely handle json.RawMessage with fallback to empty arrays. Enhance motion detection overlay data in computervision by adding main stream dimensions (mainWidth/mainHeight) alongside motion frame dimensions, enabling accurate live-view scaling of detected regions. --- machinery/src/cloud/cloud.go | 283 +++++++++++++++++++-------- machinery/src/computervision/main.go | 21 +- 2 files changed, 210 insertions(+), 94 deletions(-) diff --git a/machinery/src/cloud/cloud.go b/machinery/src/cloud/cloud.go index 84630b3..c460507 100644 --- a/machinery/src/cloud/cloud.go +++ b/machinery/src/cloud/cloud.go @@ -6,7 +6,6 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" - "fmt" "io" "os" "strings" @@ -219,6 +218,16 @@ func GetSystemInfo() (models.System, error) { return system, nil } +// rawJSONOrEmptyArray returns pre-marshalled JSON bytes as a json.RawMessage. +// When the input is empty it falls back to an empty JSON array so the +// surrounding payload always stays valid JSON. +func rawJSONOrEmptyArray(b []byte) json.RawMessage { + if len(b) == 0 { + return json.RawMessage("[]") + } + return json.RawMessage(b) +} + func HandleHeartBeat(configuration *models.Configuration, communication *models.Communication, uptimeStart time.Time) { log.Log.Debug("cloud.HandleHeartBeat(): started") @@ -504,49 +513,98 @@ loop: // We need a hub URI and hub public key before we will send a heartbeat if hubURI != "" && key != "" { - var object = fmt.Sprintf(`{ - "key" : "%s", - "version" : "%s", - "hub_encryption": "%s", - "e2e_encryption": "%s", - "release" : "%s", - "cpuid" : "%s", - "clouduser" : "%s", - "cloudpublickey" : "%s", - "cameraname" : "%s", - "enterprise" : %t, - "hostname" : "%s", - "architecture" : "%s", - "totalMemory" : "%d", - "usedMemory" : "%d", - "freeMemory" : "%d", - "processMemory" : "%d", - "mac_list" : %s, - "ip_list" : %s, - "board" : "", - "disk1size" : "%s", - "disk3size" : "%s", - "diskvdasize" : "%s", - "uptime" : "%s", - "boot_time" : "%s", - "siteID" : "%s", - "onvif" : "%s", - "onvif_zoom" : "%s", - "onvif_pantilt" : "%s", - "onvif_presets": "%s", - "onvif_presets_list": %s, - "onvif_events_list": %s, - "cameraConnected": "%s", - "hasBackChannel": "%s", - "continuousRecording": "%s", - "livePreviewHttp": true, - "numberoffiles" : "33", - "timestamp" : 1564747908, - "cameratype" : "IPCamera", - "docker" : true, - "kios" : false, - "raspberrypi" : false - }`, config.Key, kerberosAgentVersion, hub_encryption, e2e_encryption, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, onvifEventsList, cameraConnected, hasBackChannel, continuousRecording) + heartbeat := struct { + Key string `json:"key"` + Version string `json:"version"` + HubEncryption string `json:"hub_encryption"` + E2EEncryption string `json:"e2e_encryption"` + Release string `json:"release"` + CPUId string `json:"cpuid"` + CloudUser string `json:"clouduser"` + CloudPublicKey string `json:"cloudpublickey"` + CameraName string `json:"cameraname"` + Enterprise bool `json:"enterprise"` + Hostname string `json:"hostname"` + Architecture string `json:"architecture"` + TotalMemory string `json:"totalMemory"` + UsedMemory string `json:"usedMemory"` + FreeMemory string `json:"freeMemory"` + ProcessMemory string `json:"processMemory"` + MacList json.RawMessage `json:"mac_list"` + IPList json.RawMessage `json:"ip_list"` + Board string `json:"board"` + Disk1Size string `json:"disk1size"` + Disk3Size string `json:"disk3size"` + DiskVdaSize string `json:"diskvdasize"` + Uptime string `json:"uptime"` + BootTime string `json:"boot_time"` + SiteID string `json:"siteID"` + Onvif string `json:"onvif"` + OnvifZoom string `json:"onvif_zoom"` + OnvifPanTilt string `json:"onvif_pantilt"` + OnvifPresets string `json:"onvif_presets"` + OnvifPresetsList json.RawMessage `json:"onvif_presets_list"` + OnvifEventsList json.RawMessage `json:"onvif_events_list"` + CameraConnected string `json:"cameraConnected"` + HasBackChannel string `json:"hasBackChannel"` + ContinuousRecording string `json:"continuousRecording"` + LivePreviewHTTP bool `json:"livePreviewHttp"` + NumberOfFiles string `json:"numberoffiles"` + Timestamp int64 `json:"timestamp"` + CameraType string `json:"cameratype"` + Docker bool `json:"docker"` + Kios bool `json:"kios"` + RaspberryPi bool `json:"raspberrypi"` + }{ + Key: config.Key, + Version: kerberosAgentVersion, + HubEncryption: hub_encryption, + E2EEncryption: e2e_encryption, + Release: system.Version, + CPUId: system.CPUId, + CloudUser: username, + CloudPublicKey: key, + CameraName: name, + Enterprise: isEnterprise, + Hostname: system.Hostname, + Architecture: system.Architecture, + TotalMemory: strconv.FormatUint(system.TotalMemory, 10), + UsedMemory: strconv.FormatUint(system.UsedMemory, 10), + FreeMemory: strconv.FormatUint(system.FreeMemory, 10), + ProcessMemory: strconv.FormatUint(system.ProcessUsedMemory, 10), + MacList: rawJSONOrEmptyArray(macs), + IPList: rawJSONOrEmptyArray(ips), + Board: "", + Disk1Size: "0", + Disk3Size: "0", + DiskVdaSize: "0", + Uptime: uptimeString, + BootTime: boottimeString, + SiteID: config.HubSite, + Onvif: onvifEnabled, + OnvifZoom: onvifZoom, + OnvifPanTilt: onvifPanTilt, + OnvifPresets: onvifPresets, + OnvifPresetsList: rawJSONOrEmptyArray(onvifPresetsList), + OnvifEventsList: rawJSONOrEmptyArray(onvifEventsList), + CameraConnected: cameraConnected, + HasBackChannel: hasBackChannel, + ContinuousRecording: continuousRecording, + LivePreviewHTTP: true, + NumberOfFiles: "33", + Timestamp: 1564747908, + CameraType: "IPCamera", + Docker: true, + Kios: false, + RaspberryPi: false, + } + + objectBytes, err := json.Marshal(heartbeat) + if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while marshalling heartbeat: " + err.Error()) + objectBytes = []byte("{}") + } + object := string(objectBytes) // Get the private key to encrypt the data using symmetric encryption: AES. privateKey := config.HubPrivateKey @@ -560,11 +618,21 @@ loop: // Base64 encode the encrypted data. encryptedBase64 := base64.StdEncoding.EncodeToString(encrypted) - object = fmt.Sprintf(`{ - "cloudpublicKey": "%s", - "encrypted" : %t, - "encryptedData" : "%s" - }`, config.HubKey, true, encryptedBase64) + encryptedPayload := struct { + CloudPublicKey string `json:"cloudpublicKey"` + Encrypted bool `json:"encrypted"` + EncryptedData string `json:"encryptedData"` + }{ + CloudPublicKey: config.HubKey, + Encrypted: true, + EncryptedData: encryptedBase64, + } + encryptedBytes, err := json.Marshal(encryptedPayload) + if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while marshalling encrypted heartbeat: " + err.Error()) + encryptedBytes = []byte("{}") + } + object = string(encryptedBytes) } var jsonStr = []byte(object) @@ -595,43 +663,86 @@ loop: secretAccessKey := config.KStorage.SecretAccessKey if vaultURI != "" && accessKey != "" && secretAccessKey != "" { - var object = fmt.Sprintf(`{ - "key" : "%s", - "version" : "%s", - "release" : "%s", - "cpuid" : "%s", - "clouduser" : "%s", - "cloudpublickey" : "%s", - "cameraname" : "%s", - "enterprise" : %t, - "hostname" : "%s", - "architecture" : "%s", - "totalMemory" : "%d", - "usedMemory" : "%d", - "freeMemory" : "%d", - "processMemory" : "%d", - "mac_list" : %s, - "ip_list" : %s, - "board" : "", - "disk1size" : "%s", - "disk3size" : "%s", - "diskvdasize" : "%s", - "uptime" : "%s", - "boot_time" : "%s", - "siteID" : "%s", - "onvif" : "%s", - "onvif_zoom" : "%s", - "onvif_pantilt" : "%s", - "onvif_presets": "%s", - "onvif_presets_list": %s, - "cameraConnected": "%s", - "numberoffiles" : "33", - "timestamp" : 1564747908, - "cameratype" : "IPCamera", - "docker" : true, - "kios" : false, - "raspberrypi" : false - }`, config.Key, kerberosAgentVersion, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, cameraConnected) + heartbeat := struct { + Key string `json:"key"` + Version string `json:"version"` + Release string `json:"release"` + CPUId string `json:"cpuid"` + CloudUser string `json:"clouduser"` + CloudPublicKey string `json:"cloudpublickey"` + CameraName string `json:"cameraname"` + Enterprise bool `json:"enterprise"` + Hostname string `json:"hostname"` + Architecture string `json:"architecture"` + TotalMemory string `json:"totalMemory"` + UsedMemory string `json:"usedMemory"` + FreeMemory string `json:"freeMemory"` + ProcessMemory string `json:"processMemory"` + MacList json.RawMessage `json:"mac_list"` + IPList json.RawMessage `json:"ip_list"` + Board string `json:"board"` + Disk1Size string `json:"disk1size"` + Disk3Size string `json:"disk3size"` + DiskVdaSize string `json:"diskvdasize"` + Uptime string `json:"uptime"` + BootTime string `json:"boot_time"` + SiteID string `json:"siteID"` + Onvif string `json:"onvif"` + OnvifZoom string `json:"onvif_zoom"` + OnvifPanTilt string `json:"onvif_pantilt"` + OnvifPresets string `json:"onvif_presets"` + OnvifPresetsList json.RawMessage `json:"onvif_presets_list"` + CameraConnected string `json:"cameraConnected"` + NumberOfFiles string `json:"numberoffiles"` + Timestamp int64 `json:"timestamp"` + CameraType string `json:"cameratype"` + Docker bool `json:"docker"` + Kios bool `json:"kios"` + RaspberryPi bool `json:"raspberrypi"` + }{ + Key: config.Key, + Version: kerberosAgentVersion, + Release: system.Version, + CPUId: system.CPUId, + CloudUser: username, + CloudPublicKey: key, + CameraName: name, + Enterprise: isEnterprise, + Hostname: system.Hostname, + Architecture: system.Architecture, + TotalMemory: strconv.FormatUint(system.TotalMemory, 10), + UsedMemory: strconv.FormatUint(system.UsedMemory, 10), + FreeMemory: strconv.FormatUint(system.FreeMemory, 10), + ProcessMemory: strconv.FormatUint(system.ProcessUsedMemory, 10), + MacList: rawJSONOrEmptyArray(macs), + IPList: rawJSONOrEmptyArray(ips), + Board: "", + Disk1Size: "0", + Disk3Size: "0", + DiskVdaSize: "0", + Uptime: uptimeString, + BootTime: boottimeString, + SiteID: config.HubSite, + Onvif: onvifEnabled, + OnvifZoom: onvifZoom, + OnvifPanTilt: onvifPanTilt, + OnvifPresets: onvifPresets, + OnvifPresetsList: rawJSONOrEmptyArray(onvifPresetsList), + CameraConnected: cameraConnected, + NumberOfFiles: "33", + Timestamp: 1564747908, + CameraType: "IPCamera", + Docker: true, + Kios: false, + RaspberryPi: false, + } + + objectBytes, err := json.Marshal(heartbeat) + if err != nil { + log.Log.Error("cloud.HandleHeartBeat(): error while marshalling vault heartbeat: " + err.Error()) + objectBytes = []byte("{}") + } + object := string(objectBytes) var jsonStr = []byte(object) buffy := bytes.NewBuffer(jsonStr) diff --git a/machinery/src/computervision/main.go b/machinery/src/computervision/main.go index d7b06f8..82c76d3 100644 --- a/machinery/src/computervision/main.go +++ b/machinery/src/computervision/main.go @@ -204,14 +204,19 @@ func ProcessMotion(motionCursor *packets.QueueCursor, configuration *models.Conf DeviceId: configuration.Config.Key, Value: map[string]interface{}{ "timestamp": time.Now().Unix(), - // Live-view motion-debug overlay data: the frame - // dimensions the boxes/region are expressed in, the - // detected motion bounding boxes and the detection - // region polygon(s). - "width": imageCols, - "height": imageRows, - "regions": motionRectangles, - "polygon": regionPolygons, + // Live-view motion-debug overlay data. The boxes/region + // are in the MOTION frame's pixel space (width/height = + // the stream motion ran on, i.e. the sub stream when + // set). mainWidth/mainHeight are the MAIN stream's + // dimensions so the live view can extrapolate the + // boxes/region onto the high-res main view it shows — + // we know both, so no guessing from the