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 <noreply@anthropic.com>

* test: cover non-finite HomeKit numbers

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
DarkStarDS9
2026-05-26 01:29:11 +02:00
committed by GitHub
parent 66d84d91c2
commit 991cef1605
2 changed files with 52 additions and 2 deletions
+9 -2
View File
@@ -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:
+43
View File
@@ -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)!