···91919292let pp_frame ppf frame = Ax25.pp ppf frame
93939494+(* Verify AX.25 2.2 Section 3.12.3 extension bit invariant:
9595+ exactly the last callsign in the address field has bit 0 set. *)
9696+let check_extension_bits encoded =
9797+ let len = Bytes.length encoded in
9898+ if len < 14 then () (* too short for address — decode will catch it *)
9999+ else
100100+ let n_callsigns = ref 0 in
101101+ let found_last = ref false in
102102+ let i = ref 0 in
103103+ while !i + 6 < len && not !found_last do
104104+ let ssid_byte = Char.code (Bytes.get encoded (!i + 6)) in
105105+ incr n_callsigns;
106106+ if ssid_byte land 0x01 <> 0 then found_last := true;
107107+ i := !i + 7
108108+ done;
109109+ if not !found_last then
110110+ failf "no extension bit set in %d callsigns" !n_callsigns;
111111+ (* Check that NO callsign before the last has extension=1 *)
112112+ for j = 0 to !n_callsigns - 2 do
113113+ let ssid_byte = Char.code (Bytes.get encoded ((j * 7) + 6)) in
114114+ if ssid_byte land 0x01 <> 0 then
115115+ failf "callsign %d has extension=1 but is not last (of %d)" j
116116+ !n_callsigns
117117+ done
118118+94119(* Test encode-decode roundtrip for UI frames *)
95120let test_ui_encode_decode_roundtrip frame =
96121 let encoded = Ax25.encode frame in
122122+ (* Check extension bit invariant on the wire bytes *)
123123+ let frame_data = Bytes.sub encoded 0 (Bytes.length encoded - 2) in
124124+ check_extension_bits frame_data;
97125 match Ax25.decode encoded with
98126 | Ok decoded -> check_eq ~eq:frame_equal ~pp:pp_frame frame decoded
99127 | Error e -> failf "decode failed: %a" Ax25.pp_error e
+121-102
lib/ax25.ml
···306306 done;
307307 Buffer.contents buf
308308309309-let encode_callsign_to_bytes (cs : callsign) ~is_last ~has_been_repeated =
310310- let cw =
311311- {
312312- call_bytes = encode_call_string cs.call;
313313- ssid_byte =
314314- {
315315- ch_flag = has_been_repeated;
316316- reserved = 0x03;
317317- ssid_val = cs.ssid land 0x0F;
318318- extension = is_last;
319319- };
320320- }
309309+let callsign_wire_of_callsign (cs : callsign) ~is_last ~has_been_repeated =
310310+ {
311311+ call_bytes = encode_call_string cs.call;
312312+ ssid_byte =
313313+ {
314314+ ch_flag = has_been_repeated;
315315+ reserved = 0x03;
316316+ ssid_val = cs.ssid land 0x0F;
317317+ extension = is_last;
318318+ };
319319+ }
320320+321321+let callsign_of_callsign_wire (cw : callsign_wire) =
322322+ let call = decode_call_string cw.call_bytes in
323323+ let ssid = cw.ssid_byte.ssid_val in
324324+ match callsign ~call ~ssid with
325325+ | Some cs -> Ok cs
326326+ | None -> Error (Invalid_callsign call)
327327+328328+(** Scan for the extension bit to compute the address field byte count. Returns
329329+ [None] if the buffer is too short or no extension bit found within
330330+ reasonable bounds. *)
331331+let scan_address_len buf off len =
332332+ let rec scan i =
333333+ let ssid_off = off + (i * 7) + 6 in
334334+ if ssid_off >= len then None
335335+ else if Char.code (Bytes.get buf ssid_off) land 0x01 <> 0 then
336336+ Some ((i + 1) * 7)
337337+ else scan (i + 1)
321338 in
322322- let buf = Bytes.create callsign_wire_size in
323323- Wire.Codec.encode callsign_codec cw buf 0;
324324- buf
339339+ scan 0
325340326326-let decode_callsign_from_bytes buf off =
327327- match Wire.Codec.decode callsign_codec buf off with
328328- | Error _ -> Error (Invalid_callsign "<decode error>")
329329- | Ok cw -> (
330330- let call = decode_call_string cw.call_bytes in
331331- let ssid = cw.ssid_byte.ssid_val in
332332- let is_last = cw.ssid_byte.extension in
333333- match callsign ~call ~ssid with
334334- | Some cs -> Ok (cs, is_last)
335335- | None -> Error (Invalid_callsign call))
341341+(** Wire type for decoding a list of callsign_wire values occupying [n] bytes.
342342+*)
343343+let address_list_typ n =
344344+ Wire.repeat ~size:(Wire.int n) (Wire.codec callsign_codec)
336345337346(* {1 Encoding/Decoding} *)
338347348348+let encode_address_to_bytes addr =
349349+ let all =
350350+ let n_digis = List.length addr.digipeaters in
351351+ let no_digis = n_digis = 0 in
352352+ let dst =
353353+ callsign_wire_of_callsign addr.destination ~is_last:false
354354+ ~has_been_repeated:false
355355+ in
356356+ let src =
357357+ callsign_wire_of_callsign addr.source ~is_last:no_digis
358358+ ~has_been_repeated:false
359359+ in
360360+ let digis =
361361+ List.mapi
362362+ (fun i d ->
363363+ callsign_wire_of_callsign d
364364+ ~is_last:(i = n_digis - 1)
365365+ ~has_been_repeated:false)
366366+ addr.digipeaters
367367+ in
368368+ dst :: src :: digis
369369+ in
370370+ let n = List.length all * callsign_wire_size in
371371+ Wire.encode_to_bytes (address_list_typ n) all
372372+373373+let decode_address_from_bytes data off len =
374374+ match scan_address_len data off len with
375375+ | None -> Error (Truncated { need = off + 14; have = len })
376376+ | Some addr_len -> (
377377+ let region = Bytes.sub data off addr_len in
378378+ match Wire.decode_bytes (address_list_typ addr_len) region with
379379+ | Error _ -> Error (Invalid_callsign "<decode error>")
380380+ | Ok cws -> (
381381+ match cws with
382382+ | [] -> Error (Truncated { need = 14; have = 0 })
383383+ | [ _ ] -> Error (Truncated { need = 14; have = 7 })
384384+ | dst_w :: src_w :: digi_ws ->
385385+ let* destination = callsign_of_callsign_wire dst_w in
386386+ let* source = callsign_of_callsign_wire src_w in
387387+ let* digipeaters =
388388+ List.fold_left
389389+ (fun acc dw ->
390390+ let* acc = acc in
391391+ let* cs = callsign_of_callsign_wire dw in
392392+ Ok (cs :: acc))
393393+ (Ok []) digi_ws
394394+ in
395395+ let digipeaters = List.rev digipeaters in
396396+ Ok ({ destination; source; digipeaters }, addr_len)))
397397+339398let write_to_buf frame =
340399 let buf = Buffer.create 512 in
341341- (* Destination *)
342342- let dst_bytes =
343343- encode_callsign_to_bytes frame.address.destination
344344- ~is_last:(frame.address.source.ssid = 0 && frame.address.digipeaters = [])
345345- ~has_been_repeated:false
346346- in
347347- Buffer.add_bytes buf dst_bytes;
348348- (* Source *)
349349- let src_bytes =
350350- encode_callsign_to_bytes frame.address.source
351351- ~is_last:(frame.address.digipeaters = [])
352352- ~has_been_repeated:false
353353- in
354354- Buffer.add_bytes buf src_bytes;
355355- (* Digipeaters *)
356356- let rec write_digis = function
357357- | [] -> ()
358358- | [ d ] ->
359359- Buffer.add_bytes buf
360360- (encode_callsign_to_bytes d ~is_last:true ~has_been_repeated:false)
361361- | d :: rest ->
362362- Buffer.add_bytes buf
363363- (encode_callsign_to_bytes d ~is_last:false ~has_been_repeated:false);
364364- write_digis rest
365365- in
366366- write_digis frame.address.digipeaters;
400400+ (* Address field *)
401401+ let addr_bytes = encode_address_to_bytes frame.address in
402402+ Buffer.add_bytes buf addr_bytes;
367403 (* Control *)
368404 let ctrl_buf = Wire.encode_to_bytes control_typ frame.control in
369405 Buffer.add_bytes buf ctrl_buf;
···379415380416let read_from_bytes data =
381417 let len = Bytes.length data in
382382- let ensure pos n =
383383- if pos + n <= len then Ok ()
384384- else Error (Truncated { need = n; have = len - pos })
385385- in
386418 (* Need at least 14 bytes for addresses (dest + src) + 1 for control *)
387387- let* () = ensure 0 15 in
388388- let pos = ref 0 in
389389- (* Destination *)
390390- let* destination, _ = decode_callsign_from_bytes data !pos in
391391- pos := !pos + callsign_wire_size;
392392- (* Source *)
393393- let* source, is_last = decode_callsign_from_bytes data !pos in
394394- pos := !pos + callsign_wire_size;
395395- (* Digipeaters (if any) *)
396396- let rec read_digis acc =
397397- let* () = ensure !pos 7 in
398398- let* digi, is_last_digi = decode_callsign_from_bytes data !pos in
399399- pos := !pos + callsign_wire_size;
400400- if is_last_digi then Ok (List.rev (digi :: acc))
401401- else read_digis (digi :: acc)
402402- in
403403- let* digipeaters = if is_last then Ok [] else read_digis [] in
404404- let address = { destination; source; digipeaters } in
405405- (* Control *)
406406- let* () = ensure !pos 1 in
407407- let control =
408408- match Wire.decode_bytes control_typ (Bytes.sub data !pos 1) with
409409- | Ok c -> c
410410- | Error _ -> control_of_byte (Bytes.get_uint8 data !pos)
411411- in
412412- pos := !pos + 1;
413413- (* PID (only for I and UI frames) *)
414414- let pid =
415415- match control with
416416- | UI | I _ ->
417417- if !pos < len then (
418418- let p =
419419- match Wire.decode_bytes pid_typ (Bytes.sub data !pos 1) with
420420- | Ok p -> p
421421- | Error _ -> pid_of_byte (Bytes.get_uint8 data !pos)
422422- in
423423- pos := !pos + 1;
424424- Some p)
425425- else None
426426- | _ -> None
427427- in
428428- (* Remaining is info field *)
429429- let info_len = len - !pos in
430430- let info =
431431- if info_len > 0 then Bytes.sub data !pos info_len else Bytes.empty
432432- in
433433- Ok { address; control; pid; info }
419419+ if len < 15 then Error (Truncated { need = 15; have = len })
420420+ else
421421+ let* address, addr_len = decode_address_from_bytes data 0 len in
422422+ let pos = ref addr_len in
423423+ (* Control *)
424424+ if !pos >= len then Error (Truncated { need = !pos + 1; have = len })
425425+ else
426426+ let control =
427427+ match Wire.decode_bytes control_typ (Bytes.sub data !pos 1) with
428428+ | Ok c -> c
429429+ | Error _ -> control_of_byte (Bytes.get_uint8 data !pos)
430430+ in
431431+ pos := !pos + 1;
432432+ (* PID (only for I and UI frames) *)
433433+ let pid =
434434+ match control with
435435+ | UI | I _ ->
436436+ if !pos < len then (
437437+ let p =
438438+ match Wire.decode_bytes pid_typ (Bytes.sub data !pos 1) with
439439+ | Ok p -> p
440440+ | Error _ -> pid_of_byte (Bytes.get_uint8 data !pos)
441441+ in
442442+ pos := !pos + 1;
443443+ Some p)
444444+ else None
445445+ | _ -> None
446446+ in
447447+ (* Remaining is info field *)
448448+ let info_len = len - !pos in
449449+ let info =
450450+ if info_len > 0 then Bytes.sub data !pos info_len else Bytes.empty
451451+ in
452452+ Ok { address; control; pid; info }
434453435454let encode frame =
436455 let data = write_to_buf frame in