APID-based virtual switch for SpaceOS inter-guest routing
1open Space_net
2
3let camera : Config.tenant =
4 {
5 name = "camera";
6 apids = Config.apid_range 0x010 0x01F;
7 can_send_to = [ Config.apid_range 0x020 0x02F ];
8 }
9
10let processor : Config.tenant =
11 {
12 name = "processor";
13 apids = Config.apid_range 0x020 0x02F;
14 can_send_to = [ Config.apid_range 0x010 0x01F ];
15 }
16
17let test_config socket_dir : Config.t =
18 { tenants = [ camera; processor ]; socket_dir }
19
20let routable_frame ~src_apid ~dest_apid typ payload =
21 let base = Space_wire.Msg.v typ ~apid:src_apid payload in
22 { base with reserved = dest_apid }
23
24let read_frame reader =
25 let s = Eio.Buf_read.take Space_wire.Msg.frame_size reader in
26 Wire.Codec.decode Space_wire.Msg.codec (Bytes.of_string s) 0
27
28let write_frame dst frame =
29 let buf = Bytes.make Space_wire.Msg.frame_size '\x00' in
30 Wire.Codec.encode Space_wire.Msg.codec frame buf 0;
31 Eio.Flow.copy_string (Bytes.unsafe_to_string buf) dst
32
33let with_timeout clock f =
34 match Eio.Time.with_timeout clock 2.0 (fun () -> Ok (f ())) with
35 | Ok v -> v
36 | Error `Timeout -> Alcotest.fail "timeout"
37
38let socket_dir prefix =
39 let dir =
40 Filename.concat
41 (Filename.get_temp_dir_name ())
42 (Fmt.str "%s-%d" prefix (Unix.getpid ()))
43 in
44 (try Unix.mkdir dir 0o755 with Unix.Unix_error (Unix.EEXIST, _, _) -> ());
45 dir
46
47let setup_switch ~sw ~net ~clock config =
48 let switch = Switch.v ~config () in
49 Switch.run switch ~sw ~net;
50 let cam_path = Config.socket_path config camera in
51 let proc_path = Config.socket_path config processor in
52 let cam_sock = Eio.Net.connect ~sw net (`Unix cam_path) in
53 let proc_sock = Eio.Net.connect ~sw net (`Unix proc_path) in
54 Eio.Time.sleep clock 0.1;
55 (switch, cam_sock, proc_sock)
56
57let test_inter_guest () =
58 Eio_main.run @@ fun env ->
59 Eio.Switch.run @@ fun sw ->
60 let clock = Eio.Stdenv.clock env in
61 let net = Eio.Stdenv.net env in
62 let sd = socket_dir "space-net-inter" in
63 let config = test_config sd in
64 let _switch, cam_sock, proc_sock = setup_switch ~sw ~net ~clock config in
65 let proc_reader =
66 Eio.Buf_read.of_flow
67 ~max_size:(Space_wire.Msg.frame_size * 10)
68 (proc_sock :> _ Eio.Flow.source)
69 in
70 let frame =
71 routable_frame ~src_apid:0x010 ~dest_apid:0x020 TM "hello processor"
72 in
73 write_frame (cam_sock :> _ Eio.Flow.sink) frame;
74 with_timeout clock (fun () ->
75 let received = read_frame proc_reader in
76 Alcotest.(check string)
77 "payload" "hello processor"
78 (Space_wire.Msg.payload_bytes received))
79
80let test_source_enforcement () =
81 Eio_main.run @@ fun env ->
82 Eio.Switch.run @@ fun sw ->
83 let clock = Eio.Stdenv.clock env in
84 let net = Eio.Stdenv.net env in
85 let sd = socket_dir "space-net-src" in
86 let config = test_config sd in
87 let _switch, cam_sock, _proc_sock = setup_switch ~sw ~net ~clock config in
88 let cam_reader =
89 Eio.Buf_read.of_flow
90 ~max_size:(Space_wire.Msg.frame_size * 10)
91 (cam_sock :> _ Eio.Flow.source)
92 in
93 let bad_frame =
94 routable_frame ~src_apid:0x020 ~dest_apid:0x020 TM "spoofed"
95 in
96 write_frame (cam_sock :> _ Eio.Flow.sink) bad_frame;
97 with_timeout clock (fun () ->
98 let response = read_frame cam_reader in
99 Alcotest.(check int)
100 "error type"
101 (Space_wire.Msg.kind_to_int ERROR)
102 response.Space_wire.Msg.msg_type)
103
104let test_inject () =
105 Eio_main.run @@ fun env ->
106 Eio.Switch.run @@ fun sw ->
107 let clock = Eio.Stdenv.clock env in
108 let net = Eio.Stdenv.net env in
109 let sd = socket_dir "space-net-inj" in
110 let config = test_config sd in
111 let switch, cam_sock, _proc_sock = setup_switch ~sw ~net ~clock config in
112 let cam_reader =
113 Eio.Buf_read.of_flow
114 ~max_size:(Space_wire.Msg.frame_size * 10)
115 (cam_sock :> _ Eio.Flow.source)
116 in
117 let injected =
118 { (Space_wire.Msg.v TC ~apid:0 "from DTN") with reserved = 0x010 }
119 in
120 Switch.inject switch injected;
121 with_timeout clock (fun () ->
122 let received = read_frame cam_reader in
123 Alcotest.(check string)
124 "injected payload" "from DTN"
125 (Space_wire.Msg.payload_bytes received))
126
127let test_system_handler () =
128 Eio_main.run @@ fun env ->
129 Eio.Switch.run @@ fun sw ->
130 let clock = Eio.Stdenv.clock env in
131 let net = Eio.Stdenv.net env in
132 let sd = socket_dir "space-net-sys" in
133 let config = test_config sd in
134 let system_called = ref false in
135 let on_system _frame =
136 system_called := true;
137 Some (Space_wire.Msg.v Space_wire.Msg.EVR ~apid:0x001 "system ack")
138 in
139 let switch = Switch.v ~config ~on_system () in
140 Switch.run switch ~sw ~net;
141 let cam_path = Config.socket_path config camera in
142 let proc_path = Config.socket_path config processor in
143 let cam_sock = Eio.Net.connect ~sw net (`Unix cam_path) in
144 let _proc_sock = Eio.Net.connect ~sw net (`Unix proc_path) in
145 Eio.Time.sleep clock 0.1;
146 let cam_reader =
147 Eio.Buf_read.of_flow
148 ~max_size:(Space_wire.Msg.frame_size * 10)
149 (cam_sock :> _ Eio.Flow.source)
150 in
151 let frame =
152 routable_frame ~src_apid:0x010 ~dest_apid:0x001 TM "ping system"
153 in
154 write_frame (cam_sock :> _ Eio.Flow.sink) frame;
155 with_timeout clock (fun () ->
156 let response = read_frame cam_reader in
157 Alcotest.(check string)
158 "system response" "system ack"
159 (Space_wire.Msg.payload_bytes response));
160 Alcotest.(check bool) "system handler called" true !system_called
161
162let suite =
163 ( "switch",
164 [
165 Alcotest.test_case "inter-guest" `Quick test_inter_guest;
166 Alcotest.test_case "source enforcement" `Quick test_source_enforcement;
167 Alcotest.test_case "inject" `Quick test_inject;
168 Alcotest.test_case "system handler" `Quick test_system_handler;
169 ] )