CCSDS Frame Security Report (FSR)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Port remaining borealis libraries to monorepo

New packages:
- ocaml-fsr: Frame Security Report (Wire codec for 32-bit FSR word)
- ocaml-hkdf: HMAC-based Key Derivation (RFC 5869)
- ocaml-ccsds-time: CCSDS time formats (CUC, CDS) with Wire codecs
- ocaml-transport: CLTU/ASM framing and COP-1 state machine
- ocaml-chunk: Zstd compression and application-layer chunking
- ocaml-sdls: Space Data Link Security (AES-GCM/CCM/CMAC frame
protection, SA management, key lifecycle, EP/MC protocols)

All binary protocol headers use Wire codecs. Dependencies on
borealis Binary module replaced with Bytesrw or Wire. Uses
ocaml-crypto instead of mirage-crypto.

+320
+1
.ocamlformat
··· 1 + version = 0.28.1
+15
dune-project
··· 1 + (lang dune 3.21) 2 + (name fsr) 3 + (generate_opam_files true) 4 + (license ISC) 5 + (authors "Thomas Gazagnaire <thomas@gazagnaire.org>") 6 + (maintainers "Thomas Gazagnaire <thomas@gazagnaire.org>") 7 + (source (tangled gazagnaire.org/ocaml-fsr)) 8 + (package 9 + (name fsr) 10 + (synopsis "CCSDS Frame Security Report (FSR)") 11 + (depends 12 + (ocaml (>= 5.1)) 13 + (wire (>= 0.9)) 14 + (fmt (>= 0.9)) 15 + (alcotest :with-test)))
+32
fsr.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "CCSDS Frame Security Report (FSR)" 4 + maintainer: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 5 + authors: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 6 + license: "ISC" 7 + homepage: "https://tangled.org/gazagnaire.org/ocaml-fsr" 8 + bug-reports: "https://tangled.org/gazagnaire.org/ocaml-fsr/issues" 9 + depends: [ 10 + "dune" {>= "3.21"} 11 + "ocaml" {>= "5.1"} 12 + "wire" {>= "0.9"} 13 + "fmt" {>= "0.9"} 14 + "alcotest" {with-test} 15 + "odoc" {with-doc} 16 + ] 17 + build: [ 18 + ["dune" "subst"] {dev} 19 + [ 20 + "dune" 21 + "build" 22 + "-p" 23 + name 24 + "-j" 25 + jobs 26 + "@install" 27 + "@runtest" {with-test} 28 + "@doc" {with-doc} 29 + ] 30 + ] 31 + dev-repo: "git+https://tangled.org/gazagnaire.org/ocaml-fsr" 32 + x-maintenance-intent: ["(latest)"]
+4
lib/dune
··· 1 + (library 2 + (name fsr) 3 + (public_name fsr) 4 + (libraries wire fmt))
+169
lib/fsr.ml
··· 1 + (** Frame Security Report (CCSDS 355.1-B-1 Section 4.2). 2 + 3 + The FSR is a 32-bit status word transmitted from the Recipient to the 4 + Initiator of an SDLS secured link. It provides real-time reporting of frame 5 + acceptance status and security events. 6 + 7 + {b Frame structure} 8 + 9 + {v 10 + +-----+-----+-------+-------+-------+-------+---------+---------+ 11 + | CWT | Ver | Alarm | BadSN | BadMAC| BadSA | SPI | ARSN_LSB| 12 + | (1) | (3) | (1) | (1) | (1) | (1) | (16) | (8) | 13 + +-----+-----+-------+-------+-------+-------+---------+---------+ 14 + v} 15 + 16 + - CWT (Control Word Type): Always 1 (distinguishes from CLCW which is 0) 17 + - Ver (Version): 0b100 = Type-2 OCF, FSR Version 1 18 + - Alarm: Persistent flag, set when any frame rejected since last reset 19 + - BadSN: Non-persistent, last frame had invalid sequence number 20 + - BadMAC: Non-persistent, last frame failed MAC verification 21 + - BadSA: Non-persistent, last frame had invalid SA/SPI 22 + - SPI: SPI from last received frame 23 + - ARSN_LSB: 8 LSBs of ARSN from last received frame *) 24 + 25 + type t = { 26 + alarm : bool; 27 + bad_sn : bool; 28 + bad_mac : bool; 29 + bad_sa : bool; 30 + spi : int; 31 + arsn_lsb : int; 32 + } 33 + 34 + (* {1 Constants} *) 35 + 36 + (* Control Word Type = 1 for FSR (bit 0) *) 37 + let cwt_fsr = 1 38 + 39 + (* FSR Version = 0b100 (bits 1-3): Type-2 OCF, Version 1 *) 40 + let version = 0b100 41 + 42 + (* {1 Wire Codec} *) 43 + 44 + let f_cwt = Wire.Field.v "CWT" (Wire.bits ~width:1 Wire.U32be) 45 + let f_version = Wire.Field.v "Version" (Wire.bits ~width:3 Wire.U32be) 46 + let f_alarm = Wire.Field.v "Alarm" (Wire.bool (Wire.bits ~width:1 Wire.U32be)) 47 + let f_bad_sn = Wire.Field.v "BadSN" (Wire.bool (Wire.bits ~width:1 Wire.U32be)) 48 + 49 + let f_bad_mac = 50 + Wire.Field.v "BadMAC" (Wire.bool (Wire.bits ~width:1 Wire.U32be)) 51 + 52 + let f_bad_sa = Wire.Field.v "BadSA" (Wire.bool (Wire.bits ~width:1 Wire.U32be)) 53 + let f_spi = Wire.Field.v "SPI" (Wire.bits ~width:16 Wire.U32be) 54 + let f_arsn_lsb = Wire.Field.v "ARSN_LSB" (Wire.bits ~width:8 Wire.U32be) 55 + 56 + let codec = 57 + Wire.Codec.v "FSR" 58 + (fun _cwt _version alarm bad_sn bad_mac bad_sa spi arsn_lsb -> 59 + { alarm; bad_sn; bad_mac; bad_sa; spi; arsn_lsb }) 60 + Wire.Codec. 61 + [ 62 + (f_cwt $ fun _ -> cwt_fsr); 63 + (f_version $ fun _ -> version); 64 + (f_alarm $ fun t -> t.alarm); 65 + (f_bad_sn $ fun t -> t.bad_sn); 66 + (f_bad_mac $ fun t -> t.bad_mac); 67 + (f_bad_sa $ fun t -> t.bad_sa); 68 + (f_spi $ fun t -> t.spi); 69 + (f_arsn_lsb $ fun t -> t.arsn_lsb); 70 + ] 71 + 72 + let typ = 73 + Wire.map 74 + ~decode:(fun s -> 75 + let buf = Bytes.of_string s in 76 + match Wire.Codec.decode codec buf 0 with 77 + | Ok v -> v 78 + | Error e -> Fmt.failwith "Fsr: decode failed: %a" Wire.pp_parse_error e) 79 + ~encode:(fun t -> 80 + let buf = Bytes.create 4 in 81 + Wire.Codec.encode codec t buf 0; 82 + Bytes.to_string buf) 83 + (Wire.byte_array ~size:(Wire.int 4)) 84 + 85 + (* {1 Constructors} *) 86 + 87 + let initial = 88 + { 89 + alarm = false; 90 + bad_sn = false; 91 + bad_mac = false; 92 + bad_sa = false; 93 + spi = 0; 94 + arsn_lsb = 0; 95 + } 96 + 97 + let v ~alarm ~bad_sn ~bad_mac ~bad_sa ~spi ~arsn_lsb = 98 + { alarm; bad_sn; bad_mac; bad_sa; spi; arsn_lsb = arsn_lsb land 0xFF } 99 + 100 + (* {1 Encoding/Decoding} *) 101 + 102 + let encode t = 103 + let buf = Bytes.create 4 in 104 + Wire.Codec.encode codec t buf 0; 105 + (* Read back as big-endian int32 *) 106 + let b0 = Char.code (Bytes.get buf 0) in 107 + let b1 = Char.code (Bytes.get buf 1) in 108 + let b2 = Char.code (Bytes.get buf 2) in 109 + let b3 = Char.code (Bytes.get buf 3) in 110 + Int32.logor 111 + (Int32.logor 112 + (Int32.shift_left (Int32.of_int b0) 24) 113 + (Int32.shift_left (Int32.of_int b1) 16)) 114 + (Int32.logor (Int32.shift_left (Int32.of_int b2) 8) (Int32.of_int b3)) 115 + 116 + let decode word = 117 + (* Check CWT (bit 0) = 1 for FSR *) 118 + let cwt = Int32.to_int (Int32.shift_right_logical word 31) land 1 in 119 + if cwt <> cwt_fsr then Error `Not_fsr 120 + else 121 + (* Check version (bits 1-3) = 0b100 *) 122 + let ver = Int32.to_int (Int32.shift_right_logical word 28) land 0b111 in 123 + if ver <> version then Error (`Invalid_version ver) 124 + else 125 + (* Decode via Wire codec *) 126 + let buf = Bytes.create 4 in 127 + Bytes.set buf 0 128 + (Char.chr (Int32.to_int (Int32.shift_right_logical word 24) land 0xFF)); 129 + Bytes.set buf 1 130 + (Char.chr (Int32.to_int (Int32.shift_right_logical word 16) land 0xFF)); 131 + Bytes.set buf 2 132 + (Char.chr (Int32.to_int (Int32.shift_right_logical word 8) land 0xFF)); 133 + Bytes.set buf 3 (Char.chr (Int32.to_int word land 0xFF)); 134 + match Wire.Codec.decode codec buf 0 with 135 + | Ok t -> Ok t 136 + | Error _ -> Error (`Invalid_version ver) 137 + 138 + (* {1 Operations} *) 139 + 140 + let reset_alarm t = { t with alarm = false } 141 + let set_alarm t = { t with alarm = true } 142 + 143 + let update ?(bad_sn = false) ?(bad_mac = false) ?(bad_sa = false) ~spi ~arsn_lsb 144 + t = 145 + let has_error = bad_sn || bad_mac || bad_sa in 146 + { 147 + alarm = t.alarm || has_error; 148 + bad_sn; 149 + bad_mac; 150 + bad_sa; 151 + spi; 152 + arsn_lsb = arsn_lsb land 0xFF; 153 + } 154 + 155 + let has_error t = t.bad_sn || t.bad_mac || t.bad_sa 156 + 157 + (* {1 Pretty-printing} *) 158 + 159 + let pp ppf t = 160 + Fmt.pf ppf 161 + "@[<hov 2>FSR {@ alarm=%b;@ bad_sn=%b;@ bad_mac=%b;@ bad_sa=%b;@ spi=%d;@ \ 162 + arsn_lsb=0x%02X@ }@]" 163 + t.alarm t.bad_sn t.bad_mac t.bad_sa t.spi t.arsn_lsb 164 + 165 + type error = [ `Not_fsr | `Invalid_version of int ] 166 + 167 + let pp_error ppf = function 168 + | `Not_fsr -> Fmt.pf ppf "not an FSR (CWT=0, is CLCW)" 169 + | `Invalid_version v -> Fmt.pf ppf "invalid FSR version: %d" v
+99
lib/fsr.mli
··· 1 + (** Frame Security Report (CCSDS 355.1-B-1 Section 4.2). 2 + 3 + The FSR is a 32-bit status word transmitted from the Recipient to the 4 + Initiator of an SDLS secured link. It provides real-time reporting of frame 5 + acceptance status and security events. 6 + 7 + {b Frame structure} 8 + 9 + {v 10 + +-----+-----+-------+-------+-------+-------+---------+---------+ 11 + | CWT | Ver | Alarm | BadSN | BadMAC| BadSA | SPI | ARSN_LSB| 12 + | (1) | (3) | (1) | (1) | (1) | (1) | (16) | (8) | 13 + +-----+-----+-------+-------+-------+-------+---------+---------+ 14 + v} 15 + 16 + - CWT (Control Word Type): Always 1 (distinguishes from CLCW which is 0) 17 + - Ver (Version): 0b100 = Type-2 OCF, FSR Version 1 18 + - Alarm: Persistent flag, set when any frame rejected since last reset 19 + - BadSN: Non-persistent, last frame had invalid sequence number 20 + - BadMAC: Non-persistent, last frame failed MAC verification 21 + - BadSA: Non-persistent, last frame had invalid SA/SPI 22 + - SPI: SPI from last received frame 23 + - ARSN_LSB: 8 LSBs of ARSN from last received frame *) 24 + 25 + type t = { 26 + alarm : bool; (** Persistent alarm flag *) 27 + bad_sn : bool; (** Bad sequence number (non-persistent) *) 28 + bad_mac : bool; (** Bad MAC (non-persistent) *) 29 + bad_sa : bool; (** Bad SA/SPI (non-persistent) *) 30 + spi : int; (** SPI from last frame (16-bit) *) 31 + arsn_lsb : int; (** 8 LSBs of ARSN from last frame *) 32 + } 33 + 34 + (** {1 Constants} *) 35 + 36 + val version : int 37 + (** FSR version number (0b100 = Type-2 OCF, Version 1). *) 38 + 39 + (** {1 Wire Codec} *) 40 + 41 + val typ : t Wire.typ 42 + (** Wire type description for a 32-bit FSR word. *) 43 + 44 + (** {1 Constructors} *) 45 + 46 + val initial : t 47 + (** Initial FSR state: no alarm, no events, SPI=0, ARSN=0. *) 48 + 49 + val v : 50 + alarm:bool -> 51 + bad_sn:bool -> 52 + bad_mac:bool -> 53 + bad_sa:bool -> 54 + spi:int -> 55 + arsn_lsb:int -> 56 + t 57 + (** [v ~alarm ~bad_sn ~bad_mac ~bad_sa ~spi ~arsn_lsb] creates an FSR. *) 58 + 59 + (** {1 Encoding/Decoding} *) 60 + 61 + val encode : t -> int32 62 + (** [encode fsr] encodes the FSR to a 32-bit word. *) 63 + 64 + val decode : int32 -> (t, [ `Not_fsr | `Invalid_version of int ]) result 65 + (** [decode word] decodes a 32-bit word to an FSR. Returns [Error `Not_fsr] if 66 + Control Word Type is 0 (CLCW). Returns [Error `Invalid_version] if version 67 + bits are not 0b100. *) 68 + 69 + (** {1 Operations} *) 70 + 71 + val reset_alarm : t -> t 72 + (** [reset_alarm fsr] clears the persistent alarm flag. *) 73 + 74 + val set_alarm : t -> t 75 + (** [set_alarm fsr] sets the persistent alarm flag. *) 76 + 77 + val update : 78 + ?bad_sn:bool -> 79 + ?bad_mac:bool -> 80 + ?bad_sa:bool -> 81 + spi:int -> 82 + arsn_lsb:int -> 83 + t -> 84 + t 85 + (** [update ~bad_sn ~bad_mac ~bad_sa ~spi ~arsn_lsb fsr] updates the FSR with 86 + results from processing a frame. If any error flag is set, also sets the 87 + persistent alarm flag. Non-persistent flags default to false. *) 88 + 89 + val has_error : t -> bool 90 + (** [has_error fsr] returns true if any error flag is set (bad_sn, bad_mac, or 91 + bad_sa). *) 92 + 93 + (** {1 Pretty-printing} *) 94 + 95 + val pp : Format.formatter -> t -> unit 96 + 97 + type error = [ `Not_fsr | `Invalid_version of int ] 98 + 99 + val pp_error : Format.formatter -> error -> unit