From 991cef1605fdf2eed08f1be6055a2c99ed8b4e03 Mon Sep 17 00:00:00 2001 From: DarkStarDS9 Date: Tue, 26 May 2026 01:29:11 +0200 Subject: [PATCH] Fix: /homekit/accessories returns null when characteristic metadata contains NaN or Infinity (#2) * Fix JSON encoding crash when HomeKit characteristic values are NaN or Infinity JSONEncoder throws EncodingError.invalidValue for NaN/Infinity doubles, causing the accessories endpoint to silently return null via try?. Map these to JSON null instead so /homekit/accessories encodes correctly. Co-Authored-By: Claude Sonnet 4.6 * test: cover non-finite HomeKit numbers --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Peter Steinberger --- Casa/HomeKitServer.swift | 11 ++++++++-- CasaTests/CasaTests.swift | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Casa/HomeKitServer.swift b/Casa/HomeKitServer.swift index b3bda4a..98e14dc 100644 --- a/Casa/HomeKitServer.swift +++ b/Casa/HomeKitServer.swift @@ -793,7 +793,12 @@ enum JSONValue: Encodable { func encodedData() -> Data { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] - return (try? encoder.encode(self)) ?? Data("null".utf8) + do { + return try encoder.encode(self) + } catch { + print("[Casa] JSON encoding failed: \(error)") + return Data("null".utf8) + } } } @@ -954,7 +959,9 @@ enum HomeKitPayload { if CFGetTypeID(number) == CFBooleanGetTypeID() { return .bool(number.boolValue) } - return .number(number.doubleValue) + let d = number.doubleValue + if d.isNaN || d.isInfinite { return .null } + return .number(d) case let string as String: return .string(string) case let date as Date: diff --git a/CasaTests/CasaTests.swift b/CasaTests/CasaTests.swift index 9bf2bda..8aa173a 100644 --- a/CasaTests/CasaTests.swift +++ b/CasaTests/CasaTests.swift @@ -106,6 +106,49 @@ final class CasaTests: XCTestCase { XCTAssertEqual(entry?["maxValue"] as? Double, 100) } + func testSchemaMapsNonFiniteNumbersToNull() throws { + let metadata = CasaCharacteristicMetadata( + format: HMCharacteristicMetadataFormatFloat, + minValue: Double.nan, + maxValue: Double.infinity, + stepValue: -Double.infinity, + validValues: [Double.nan, 1], + units: "" + ) + let characteristic = CasaCharacteristic( + id: "char-3", + type: "type-3", + properties: ["read"], + metadata: metadata, + value: Double.nan + ) + let service = CasaService( + id: "svc-3", + name: "Service", + type: "type", + accessoryId: "acc-3", + characteristics: [characteristic] + ) + let accessory = CasaAccessory( + id: "acc-3", + name: "Accessory", + category: "Category", + room: "Room", + hasCameraProfile: false, + services: [service] + ) + + let data = HomeKitPayload.schema([accessory]).encodedData() + let json = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] + let entry = json?.first + XCTAssertTrue(entry?["minValue"] is NSNull) + XCTAssertTrue(entry?["maxValue"] is NSNull) + XCTAssertTrue(entry?["stepValue"] is NSNull) + let validValues = entry?["validValues"] as? [Any] + XCTAssertTrue(validValues?.first is NSNull) + XCTAssertEqual(validValues?.last as? Double, 1) + } + func testSettingsDefaultsAreOff() throws { let suiteName = "casa.tests.defaults.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suiteName)!