(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *) (* please read this before changing this module: Data encoded by this module is persisted in (a) log entry (b) dump file (c) certificates (and subCA). It is important to be aware of backward and forward compatibility when modifying this module. There are various version fields around which are mostly useless in retrospect. On a server deployment, upgrades are supported while downgrades are not (there could be a separate tool reading newer data and dumping it for older albatross versions). The assumption is that a server deployment moves forward. For the clients, older clients should best be support smoothly, or an error from the server should be issued informing about a too old version. Clients which support newer wire version should as well be notified (it may be suitable to have a --use-version command-line flag - so new clients can talk to old servers). The log (a) is append-only, whenever a new log entry is added, the choice log_entry should be extended. New entries just use the new choice. The dump on disk (dumped via log_to_disk, restored logs_of_disk) prepends a (rather useless) version field. Restoring a new log entry with an old albatross_log will result in a warning (but restores the other log entries). It should be ensured that old unikernels dumped to disk (b) can be read by new albatross daemons. The functions unikernels_to_cstruct and unikernels_of_cstruct are used for dump and restore, each an explicit choice. They use the trie of unikernel_config, dump always uses the latest version in the explicit choice. There's no version field involved. The data in transit (certificates and commands) is out of control of a single operator. This means that best effort should be done to support old clients (and old servers - eventually with a command-line argument --use-version). If a server receives a command (via TLS cert_extension), this is prefixed by a version. The non-TLS command is a sequence of header and payload, where the header includes a version. At the moment, the commands are all explicit choices, adding new ones by extending the choice works in a backwards-compatible way. *) (* The version field could be used (at the moment, decoding a newer version leads to a decoding failure): Now, to achieve version-dependent parsing, what is missing is a way to decode the first element of a sequence only (i.e. treat the second element as "any"). This is something missing for PKCS12 from the asn1 library. A "quick hack" is to extract length information of the first element, and use that decoder on the sub-buffer. The following implements this. let seq_hd cs = decode_seq_len cs >>= fun (l, off) -> Ok (Cstruct.sub cs off l) let decode_version cs = let c = Asn.codec Asn.der version in match Asn.decode c cs with | Ok (a, _) -> Ok a | Error (`Parse msg) -> Error (`Msg msg) *) open Vmm_core open Vmm_commands open Rresult open Astring let oid = Asn.OID.(base 1 3 <| 6 <| 1 <| 4 <| 1 <| 49836 <| 42) open Rresult.R.Infix let guard p err = if p then Ok () else Error err let decode_seq_len cs = (* we assume a ASN.1 DER/BER encoded sequence starting in cs: - 0x30 - length (definite length field - not 0x80) - (of length length) *) guard (Cstruct.len cs > 2) (`Msg "buffer too short") >>= fun () -> guard (Cstruct.get_uint8 cs 0 = 0x30) (`Msg "not a sequence") >>= fun () -> let l1 = Cstruct.get_uint8 cs 1 in (if l1 < 0x80 then Ok (2, l1) else if l1 = 0x80 then Error (`Msg "indefinite length") else let octets = l1 land 0x7F in guard (Cstruct.len cs > octets + 2) (`Msg "data too short") >>= fun () -> let rec go off acc = if off = octets then Ok (2 + octets, acc) else go (succ off) (Cstruct.get_uint8 cs (off + 2) + acc lsl 8) in go 0 0) >>= fun (off, l) -> guard (Cstruct.len cs >= l + off) (`Msg "buffer too small") >>= fun () -> Ok (l, off) let decode_strict codec cs = match Asn.decode codec cs with | Ok (a, cs) -> guard (Cstruct.len cs = 0) (`Msg "trailing bytes") >>= fun () -> Ok a | Error (`Parse msg) -> Error (`Msg msg) let projections_of asn = let c = Asn.codec Asn.der asn in (decode_strict c, Asn.encode c) let ipv4 = let f cs = Ipaddr.V4.of_octets_exn (Cstruct.to_string cs) and g ip = Cstruct.of_string (Ipaddr.V4.to_octets ip) in Asn.S.map f g Asn.S.octet_string let policy = let f (cpuids, vms, memory, block, bridges) = let bridges = String.Set.of_list bridges and cpuids = IS.of_list cpuids in Policy.{ vms ; cpuids ; memory ; block ; bridges } and g policy = (IS.elements policy.Policy.cpuids, policy.Policy.vms, policy.Policy.memory, policy.Policy.block, String.Set.elements policy.Policy.bridges) in Asn.S.map f g @@ Asn.S.(sequence5 (required ~label:"cpuids" Asn.S.(sequence_of int)) (required ~label:"vms" int) (required ~label:"memory" int) (optional ~label:"block" int) (required ~label:"bridges" Asn.S.(sequence_of utf8_string))) let console_cmd = let f = function | `C1 () -> `Console_add | `C2 `C1 ts -> `Console_subscribe (`Since ts) | `C2 `C2 c -> `Console_subscribe (`Count c) and g = function | `Console_add -> `C1 () | `Console_subscribe `Since ts -> `C2 (`C1 ts) | `Console_subscribe `Count c -> `C2 (`C2 c) in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 null) (explicit 1 (choice2 (explicit 0 utc_time) (explicit 1 int)))) (* TODO is this good? *) let int64 = let f cs = Cstruct.BE.get_uint64 cs 0 and g data = let buf = Cstruct.create 8 in Cstruct.BE.set_uint64 buf 0 data ; buf in Asn.S.map f g Asn.S.octet_string let timeval = Asn.S.(sequence2 (required ~label:"seconds" int64) (required ~label:"microseconds" int)) let ru = let open Stats in let f (utime, (stime, (maxrss, (ixrss, (idrss, (isrss, (minflt, (majflt, (nswap, (inblock, (outblock, (msgsnd, (msgrcv, (nsignals, (nvcsw, nivcsw))))))))))))))) = { utime ; stime ; maxrss ; ixrss ; idrss ; isrss ; minflt ; majflt ; nswap ; inblock ; outblock ; msgsnd ; msgrcv ; nsignals ; nvcsw ; nivcsw } and g ru = (ru.utime, (ru.stime, (ru.maxrss, (ru.ixrss, (ru.idrss, (ru.isrss, (ru.minflt, (ru.majflt, (ru.nswap, (ru.inblock, (ru.outblock, (ru.msgsnd, (ru.msgrcv, (ru.nsignals, (ru.nvcsw, ru.nivcsw))))))))))))))) in Asn.S.map f g @@ Asn.S.(sequence @@ (required ~label:"utime" timeval) @ (required ~label:"stime" timeval) @ (required ~label:"maxrss" int64) @ (required ~label:"ixrss" int64) @ (required ~label:"idrss" int64) @ (required ~label:"isrss" int64) @ (required ~label:"minflt" int64) @ (required ~label:"majflt" int64) @ (required ~label:"nswap" int64) @ (required ~label:"inblock" int64) @ (required ~label:"outblock" int64) @ (required ~label:"msgsnd" int64) @ (required ~label:"msgrcv" int64) @ (required ~label:"nsignals" int64) @ (required ~label:"nvcsw" int64) -@ (required ~label:"nivcsw" int64)) (* although this changed (+runtime + cow + start) from V3 to V4, since it's not persistent, no need to care about it *) let kinfo_mem = let open Stats in let f (vsize, (rss, (tsize, (dsize, (ssize, (runtime, (cow, start))))))) = { vsize ; rss ; tsize ; dsize ; ssize ; runtime ; cow ; start } and g t = (t.vsize, (t.rss, (t.tsize, (t.dsize, (t.ssize, (t.runtime, (t.cow, t.start))))))) in Asn.S.map f g @@ Asn.S.(sequence @@ (required ~label:"bsize" int64) @ (required ~label:"rss" int64) @ (required ~label:"tsize" int64) @ (required ~label:"dsize" int64) @ (required ~label:"ssize" int64) @ (required ~label:"runtime" int64) @ (required ~label:"cow" int) -@ (required ~label:"start" timeval)) (* TODO is this good? *) let int32 = let f i = Int32.of_int i and g i = Int32.to_int i in Asn.S.map f g Asn.S.int let ifdata = let open Stats in let f (bridge, (flags, (send_length, (max_send_length, (send_drops, (mtu, (baudrate, (input_packets, (input_errors, (output_packets, (output_errors, (collisions, (input_bytes, (output_bytes, (input_mcast, (output_mcast, (input_dropped, output_dropped))))))))))))))))) = { bridge ; flags; send_length; max_send_length; send_drops; mtu; baudrate; input_packets; input_errors; output_packets; output_errors; collisions; input_bytes; output_bytes; input_mcast; output_mcast; input_dropped; output_dropped } and g i = (i.bridge, (i.flags, (i.send_length, (i.max_send_length, (i.send_drops, (i.mtu, (i.baudrate, (i.input_packets, (i.input_errors, (i.output_packets, (i.output_errors, (i.collisions, (i.input_bytes, (i.output_bytes, (i.input_mcast, (i.output_mcast, (i.input_dropped, i.output_dropped))))))))))))))))) in Asn.S.map f g @@ Asn.S.(sequence @@ (required ~label:"bridge" utf8_string) @ (required ~label:"flags" int32) @ (required ~label:"send_length" int32) @ (required ~label:"max_send_length" int32) @ (required ~label:"send_drops" int32) @ (required ~label:"mtu" int32) @ (required ~label:"baudrate" int64) @ (required ~label:"input_packets" int64) @ (required ~label:"input_errors" int64) @ (required ~label:"output_packets" int64) @ (required ~label:"output_errors" int64) @ (required ~label:"collisions" int64) @ (required ~label:"input_bytes" int64) @ (required ~label:"output_bytes" int64) @ (required ~label:"input_mcast" int64) @ (required ~label:"output_mcast" int64) @ (required ~label:"input_dropped" int64) -@ (required ~label:"output_dropped" int64)) let stats_cmd = let f = function | `C1 (name, pid, taps) -> `Stats_add (name, pid, taps) | `C2 () -> `Stats_remove | `C3 () -> `Stats_subscribe and g = function | `Stats_add (name, pid, taps) -> `C1 (name, pid, taps) | `Stats_remove -> `C2 () | `Stats_subscribe -> `C3 () in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 (sequence3 (required ~label:"vmmdev" utf8_string) (required ~label:"pid" int) (required ~label:"network" (sequence_of (sequence2 (required ~label:"bridge" utf8_string) (required ~label:"tap" utf8_string)))))) (explicit 1 null) (explicit 2 null)) let of_name, to_name = Name.to_list, fun list -> match Name.of_list list with | Error (`Msg msg) -> Asn.S.error (`Parse msg) | Ok name -> name let log_event = (* this is stored on disk persistently -- be aware when changing the grammar below to only ever extend it! *) let f = function | `C1 `C1 () -> `Startup | `C1 `C2 (name, ip, port) -> `Login (to_name name, ip, port) | `C1 `C3 (name, ip, port) -> `Logout (to_name name, ip, port) | `C1 `C4 (name, pid, taps, block) -> let name = to_name name in let blocks = match block with | None -> [] | Some block -> [ block, Name.block_name name block ] and taps = List.map (fun tap -> tap, tap) taps in `Unikernel_start (name, pid, taps, blocks) | `C2 `C1 (name, pid, taps, blocks) -> let blocks = List.map (fun (name, dev) -> name, match Name.of_string dev with | Error `Msg msg -> Asn.S.error (`Parse msg) | Ok id -> id) blocks in `Unikernel_start (to_name name, pid, taps, blocks) | `C1 `C5 (name, pid, status) -> let status' = match status with | `C1 n -> `Exit n | `C2 n -> `Signal n | `C3 n -> `Stop n in `Unikernel_stop (to_name name, pid, status') | `C1 `C6 () -> `Hup | `C2 `C2 () -> assert false (* placeholder *) and g = function | `Startup -> `C1 (`C1 ()) | `Login (name, ip, port) -> `C1 (`C2 (of_name name, ip, port)) | `Logout (name, ip, port) -> `C1 (`C3 (of_name name, ip, port)) | `Unikernel_start (name, pid, taps, blocks) -> let blocks = List.map (fun (name, dev) -> name, Name.to_string dev) blocks in `C2 (`C1 (of_name name, pid, taps, blocks)) | `Unikernel_stop (name, pid, status) -> let status' = match status with | `Exit n -> `C1 n | `Signal n -> `C2 n | `Stop n -> `C3 n in `C1 (`C5 (of_name name, pid, status')) | `Hup -> `C1 (`C6 ()) in let endp = Asn.S.(sequence3 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"ip" ipv4) (required ~label:"port" int)) in Asn.S.map f g @@ Asn.S.(choice2 (choice6 (explicit 0 null) (explicit 1 endp) (explicit 2 endp) (* the old V3 unikernel start *) (explicit 3 (sequence4 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"pid" int) (required ~label:"taps" (sequence_of utf8_string)) (optional ~label:"block" utf8_string))) (explicit 4 (sequence3 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"pid" int) (required ~label:"status" (choice3 (explicit 0 int) (explicit 1 int) (explicit 2 int))))) (explicit 5 null)) (choice2 (* the new V4 unikernel start*) (explicit 6 (sequence4 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"pid" int) (required ~label:"taps" (sequence_of (sequence2 (required ~label:"bridge" utf8_string) (required ~label:"tap" utf8_string)))) (required ~label:"blocks" (sequence_of (sequence2 (required ~label:"name" utf8_string) (required ~label:"device" utf8_string)))))) (explicit 7 null (* placeholder *) ))) let log_cmd = let f = function | `C1 since -> `Log_subscribe (`Since since) | `C2 n -> `Log_subscribe (`Count n) and g = function | `Log_subscribe `Since since -> `C1 since | `Log_subscribe `Count n -> `C2 n in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 utc_time) (explicit 1 int)) let typ = let f = function | `C1 () -> `Solo5 | `C2 () -> assert false and g = function | `Solo5 -> `C1 () in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 null) (explicit 1 null)) let fail_behaviour = let f = function | `C1 () -> `Quit | `C2 xs -> let exit_codes = match xs with | [] -> None | xs -> Some (IS.of_list xs) in `Restart exit_codes and g = function | `Quit -> `C1 () | `Restart xs -> let exit_codes = match xs with | None -> [] | Some i -> IS.elements i in `C2 exit_codes in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 null) (explicit 1 (set_of int))) (* this is part of the state file! *) let v0_unikernel_config = let image = let f = function | `C1 x -> `Hvt_amd64, x | `C2 x -> `Hvt_arm64, x | `C3 x -> `Hvt_amd64_compressed, x and g = function | `Hvt_amd64, x -> `C1 x | `Hvt_arm64, x -> `C2 x | `Hvt_amd64_compressed, x -> `C3 x in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 octet_string) (explicit 1 octet_string) (explicit 2 octet_string)) in let open Unikernel in let f (cpuid, memory, block_device, network_interfaces, image, argv) = let bridges = match network_interfaces with None -> [] | Some xs -> List.map (fun n -> n, None) xs and block_devices = match block_device with None -> [] | Some b -> [ b ] in let typ = `Solo5 and compressed = match fst image with `Hvt_amd64_compressed -> true | _ -> false and image = snd image and fail_behaviour = `Quit (* TODO maybe set to restart by default :) *) in { typ ; compressed ; image ; fail_behaviour ; cpuid ; memory ; block_devices ; bridges ; argv } and g _vm = failwith "cannot encode v0 unikernel configs" in Asn.S.map f g @@ Asn.S.(sequence6 (required ~label:"cpu" int) (required ~label:"memory" int) (optional ~label:"block" utf8_string) (optional ~label:"network_interfaces" (sequence_of utf8_string)) (required ~label:"image" image) (optional ~label:"arguments" (sequence_of utf8_string))) (* this is part of the state file (and unikernel_create command) be aware if this (or a dependent grammar) is changed! *) let v1_unikernel_config = let open Unikernel in let f (typ, (compressed, (image, (fail_behaviour, (cpuid, (memory, (blocks, (bridges, argv)))))))) = let bridges = match bridges with None -> [] | Some xs -> List.map (fun b -> b, None) xs and block_devices = match blocks with None -> [] | Some xs -> xs in { typ ; compressed ; image ; fail_behaviour ; cpuid ; memory ; block_devices ; bridges ; argv } and g _vm = failwith "cannot encode v1 unikernel configs" in Asn.S.(map f g @@ sequence @@ (required ~label:"typ" typ) @ (required ~label:"compressed" bool) @ (required ~label:"image" octet_string) @ (required ~label:"fail behaviour" fail_behaviour) @ (required ~label:"cpuid" int) @ (required ~label:"memory" int) @ (optional ~label:"blocks" (explicit 0 (set_of utf8_string))) @ (optional ~label:"bridges" (explicit 1 (set_of utf8_string))) -@ (optional ~label:"arguments"(explicit 2 (sequence_of utf8_string)))) let unikernel_config = let open Unikernel in let f (typ, (compressed, (image, (fail_behaviour, (cpuid, (memory, (blocks, (bridges, argv)))))))) = let bridges = match bridges with None -> [] | Some xs -> xs and block_devices = match blocks with None -> [] | Some xs -> xs in { typ ; compressed ; image ; fail_behaviour ; cpuid ; memory ; block_devices ; bridges ; argv } and g vm = let bridges = match vm.bridges with [] -> None | xs -> Some xs and blocks = match vm.block_devices with [] -> None | xs -> Some xs in (vm.typ, (vm.compressed, (vm.image, (vm.fail_behaviour, (vm.cpuid, (vm.memory, (blocks, (bridges, vm.argv)))))))) in Asn.S.(map f g @@ sequence @@ (required ~label:"typ" typ) @ (required ~label:"compressed" bool) @ (required ~label:"image" octet_string) @ (required ~label:"fail behaviour" fail_behaviour) @ (required ~label:"cpuid" int) @ (required ~label:"memory" int) @ (optional ~label:"blocks" (explicit 0 (set_of utf8_string))) @ (optional ~label:"bridges" (explicit 1 (sequence_of (sequence2 (required ~label:"netif" utf8_string) (optional ~label:"bridge" utf8_string))))) -@ (optional ~label:"arguments"(explicit 2 (sequence_of utf8_string)))) let unikernel_cmd = let f = function | `C1 `C1 () -> `Unikernel_info | `C1 `C2 vm -> `Unikernel_create vm | `C1 `C3 vm -> `Unikernel_force_create vm | `C1 `C4 () -> `Unikernel_destroy | `C1 `C5 vm -> `Unikernel_create vm | `C1 `C6 vm -> `Unikernel_force_create vm | `C2 `C1 () -> `Unikernel_get | `C2 `C2 () -> assert false (* placeholder *) and g = function | `Unikernel_info -> `C1 (`C1 ()) | `Unikernel_create vm -> `C1 (`C5 vm) | `Unikernel_force_create vm -> `C1 (`C6 vm) | `Unikernel_destroy -> `C1 (`C4 ()) | `Unikernel_get -> `C2 (`C1 ()) in Asn.S.map f g @@ Asn.S.(choice2 (choice6 (explicit 0 null) (explicit 1 v1_unikernel_config) (explicit 2 v1_unikernel_config) (explicit 3 null) (explicit 4 unikernel_config) (explicit 5 unikernel_config)) (choice2 (explicit 6 null) (explicit 7 null (* placeholder *) ))) let policy_cmd = let f = function | `C1 () -> `Policy_info | `C2 policy -> `Policy_add policy | `C3 () -> `Policy_remove and g = function | `Policy_info -> `C1 () | `Policy_add policy -> `C2 policy | `Policy_remove -> `C3 () in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 null) (explicit 1 policy) (explicit 2 null)) let block_cmd = let f = function | `C1 () -> `Block_info | `C2 size -> `Block_add size | `C3 () -> `Block_remove and g = function | `Block_info -> `C1 () | `Block_add size -> `C2 size | `Block_remove -> `C3 () in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 null) (explicit 1 int) (explicit 2 null)) let version = let f data = match data with | 4 -> `AV4 | 3 -> `AV3 | x -> Asn.S.error (`Parse (Printf.sprintf "unknown version number 0x%X" x)) and g = function | `AV4 -> 4 | `AV3 -> 3 in Asn.S.map f g Asn.S.int let wire_command = let f = function | `C1 console -> `Console_cmd console | `C2 stats -> `Stats_cmd stats | `C3 log -> `Log_cmd log | `C4 vm -> `Unikernel_cmd vm | `C5 policy -> `Policy_cmd policy | `C6 block -> `Block_cmd block and g = function | `Console_cmd c -> `C1 c | `Stats_cmd c -> `C2 c | `Log_cmd c -> `C3 c | `Unikernel_cmd c -> `C4 c | `Policy_cmd c -> `C5 c | `Block_cmd c -> `C6 c in Asn.S.map f g @@ Asn.S.(choice6 (explicit 0 console_cmd) (explicit 1 stats_cmd) (explicit 2 log_cmd) (explicit 3 unikernel_cmd) (explicit 4 policy_cmd) (explicit 5 block_cmd)) let data = let f = function | `C1 (timestamp, data) -> `Console_data (timestamp, data) | `C2 (ru, ifs, vmm, mem) -> `Stats_data (ru, mem, vmm, ifs) | `C3 (timestamp, event) -> `Log_data (timestamp, event) and g = function | `Console_data (timestamp, data) -> `C1 (timestamp, data) | `Stats_data (ru, mem, ifs, vmm) -> `C2 (ru, vmm, ifs, mem) | `Log_data (timestamp, event) -> `C3 (timestamp, event) in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 (sequence2 (required ~label:"timestamp" utc_time) (required ~label:"data" utf8_string))) (explicit 1 (sequence4 (required ~label:"resource_usage" ru) (required ~label:"ifdata" (sequence_of ifdata)) (optional ~label:"vmm_stats" @@ explicit 0 (sequence_of (sequence2 (required ~label:"key" utf8_string) (required ~label:"value" int64)))) (optional ~label:"kinfo_mem" @@ implicit 1 kinfo_mem))) (explicit 2 (sequence2 (required ~label:"timestamp" utc_time) (required ~label:"event" log_event)))) let header = let f (version, sequence, name) = { version ; sequence ; name = to_name name } and g h = h.version, h.sequence, of_name h.name in Asn.S.map f g @@ Asn.S.(sequence3 (required ~label:"version" version) (required ~label:"sequence" int64) (required ~label:"name" (sequence_of utf8_string))) let success = let f = function | `C1 () -> `Empty | `C2 str -> `String str | `C3 policies -> `Policies (List.map (fun (name, p) -> to_name name, p) policies) | `C4 vms -> `Unikernels (List.map (fun (name, vm) -> to_name name, vm) vms) | `C5 blocks -> `Block_devices (List.map (fun (name, s, a) -> to_name name, s, a) blocks) and g = function | `Empty -> `C1 () | `String s -> `C2 s | `Policies ps -> `C3 (List.map (fun (name, p) -> of_name name, p) ps) | `Unikernels vms -> `C4 (List.map (fun (name, v) -> of_name name, v) vms) | `Block_devices blocks -> `C5 (List.map (fun (name, s, a) -> of_name name, s, a) blocks) in Asn.S.map f g @@ Asn.S.(choice5 (explicit 0 null) (explicit 1 utf8_string) (explicit 2 (sequence_of (sequence2 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"policy" policy)))) (explicit 3 (sequence_of (sequence2 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"config" unikernel_config)))) (explicit 4 (sequence_of (sequence3 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"size" int) (required ~label:"active" bool))))) let payload = let f = function | `C1 cmd -> `Command cmd | `C2 s -> `Success s | `C3 str -> `Failure str | `C4 data -> `Data data and g = function | `Command cmd -> `C1 cmd | `Success s -> `C2 s | `Failure str -> `C3 str | `Data d -> `C4 d in Asn.S.map f g @@ Asn.S.(choice4 (explicit 0 wire_command) (explicit 1 success) (explicit 2 utf8_string) (explicit 3 data)) let wire = Asn.S.(sequence2 (required ~label:"header" header) (required ~label:"payload" payload)) let wire_of_cstruct, wire_to_cstruct = projections_of wire let log_entry = Asn.S.(sequence2 (required ~label:"timestamp" utc_time) (required ~label:"event" log_event)) let log_entry_of_cstruct, log_entry_to_cstruct = projections_of log_entry let log_disk = (* data is persisted to disk, we need to ensure to be able to decode (and encode) properly without conflicts! *) Asn.S.(sequence2 (required ~label:"version" version) (required ~label:"entry" log_entry)) let log_disk_of_cstruct, log_disk_to_cstruct = let c = Asn.codec Asn.der log_disk in (Asn.decode c, Asn.encode c) let log_to_disk entry = log_disk_to_cstruct (current, entry) let skip_seq cs = decode_seq_len cs >>= fun (l, off) -> Ok (Cstruct.shift cs (l + off)) let logs_of_disk buf = let rec next acc buf = match log_disk_of_cstruct buf with | Ok ((version, entry), cs) -> Logs.info (fun m -> m "read a log entry version %a" pp_version version) ; next (entry :: acc) cs | Error (`Parse msg) -> Logs.warn (fun m -> m "parse error %s while parsing log" msg) ; match skip_seq buf with | Ok cs' -> next acc cs' | Error _ -> acc (* ignore *) in next [] buf let trie e = let f elts = List.fold_left (fun trie (key, value) -> match Name.of_string key with | Error (`Msg m) -> invalid_arg m | Ok name -> let trie, ret = Vmm_trie.insert name value trie in assert (ret = None); trie) Vmm_trie.empty elts and g trie = List.map (fun (k, v) -> Name.to_string k, v) (Vmm_trie.all trie) in Asn.S.map f g @@ Asn.S.(sequence_of (sequence2 (required ~label:"name" utf8_string) (required ~label:"value" e))) let version0_unikernels = trie v0_unikernel_config let version1_unikernels = trie v1_unikernel_config let version2_unikernels = trie unikernel_config let unikernels = (* the choice is the implicit version + migration... be aware when any dependent data layout changes .oO(/o\) *) let f = function | `C1 data -> data | `C2 data -> data | `C3 data -> data and g data = `C3 data in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 version1_unikernels) (explicit 1 version0_unikernels) (explicit 2 version2_unikernels)) let unikernels_of_cstruct, unikernels_to_cstruct = projections_of unikernels let cert_extension = (* note that subCAs are deployed out there, thus modifying the encoding of commands may break them. *) Asn.S.(sequence2 (required ~label:"version" version) (required ~label:"command" wire_command)) let of_cert_extension, to_cert_extension = let a, b = projections_of cert_extension in a, (fun d -> b (current, d))