HomeKit Accessory Protocol (HAP) for OCaml

fix(hap): make TLV decoder robust to truncated input

- Tlv.decode now stops gracefully on truncated data instead of raising
- Fuzz test for multi roundtrip deduplicates keys (last-write-wins)

+16 -10
+10 -6
fuzz/fuzz_hap.ml
··· 7 7 8 8 (* TLV roundtrip: encode(decode(x)) should produce valid TLV *) 9 9 let test_tlv_decode_encode input = 10 - (* Decoding arbitrary bytes should not crash *) 11 10 let decoded = Hap.Tlv.decode input in 12 - (* Re-encoding should produce valid TLV *) 13 11 let _ = Hap.Tlv.encode decoded in 14 12 check true 15 13 ··· 21 19 let retrieved = Hap.Tlv.get typ decoded in 22 20 check_eq ~pp:Format.pp_print_bool (retrieved = Some value) true 23 21 24 - (* Multiple TLV entries roundtrip *) 22 + (* Multiple TLV entries roundtrip - deduplicate types since later adds overwrite *) 25 23 let test_tlv_multi_roundtrip entries = 24 + (* Keep only the last value for each type, matching add semantics *) 25 + let deduped = 26 + List.fold_left 27 + (fun acc (typ, value) -> 28 + List.filter (fun (t, _) -> t <> typ) acc @ [ (typ, value) ]) 29 + [] entries 30 + in 26 31 let tlv = 27 32 List.fold_left 28 33 (fun acc (typ, value) -> Hap.Tlv.add typ value acc) 29 - Hap.Tlv.empty entries 34 + Hap.Tlv.empty deduped 30 35 in 31 36 let encoded = Hap.Tlv.encode tlv in 32 37 let decoded = Hap.Tlv.decode encoded in 33 - (* Check that all entries can be retrieved *) 34 38 List.iter 35 39 (fun (typ, value) -> 36 40 let retrieved = Hap.Tlv.get typ decoded in 37 41 check_eq ~pp:Format.pp_print_bool (retrieved = Some value) true) 38 - entries 42 + deduped 39 43 40 44 (* Category name should never crash *) 41 45 let test_category_name code =
+4 -3
lib/hap.ml
··· 71 71 (List.rev tlv); 72 72 Buffer.contents buf 73 73 74 - (* Decode TLV from bytes - concatenate split values *) 74 + (* Decode TLV from bytes - concatenate split values. 75 + Stops gracefully on truncated input, returning entries parsed so far. *) 75 76 let decode data = 76 77 let len = String.length data in 77 78 let rec parse offset acc = 78 79 if offset >= len then List.rev acc 79 - else if offset + 2 > len then failwith "Invalid TLV: truncated" 80 + else if offset + 2 > len then List.rev acc 80 81 else begin 81 82 let typ = Char.code data.[offset] in 82 83 let vlen = Char.code data.[offset + 1] in 83 - if offset + 2 + vlen > len then failwith "Invalid TLV: value truncated" 84 + if offset + 2 + vlen > len then List.rev acc 84 85 else begin 85 86 let value = String.sub data (offset + 2) vlen in 86 87 (* Concatenate with previous if same type *)
+2 -1
lib/hap.mli
··· 206 206 207 207 val decode : string -> t 208 208 (** [decode s] deserializes bytes into a TLV container. Consecutive same-type 209 - chunks are concatenated. Raises [Failure] on malformed input. *) 209 + chunks are concatenated. Truncated input is handled gracefully by 210 + returning entries parsed so far. *) 210 211 end 211 212 212 213 (** TLV type codes. *)