1use std::{
2 sync::{Arc, Mutex},
3 time::Duration,
4};
5
6use sim_citizen_derive::non_citizen;
7use sim_kernel::{
8 CapabilityName, ClassRef, Consistency, Cx, Error, EvalFabric, EvalMode, EvalReply, EvalRequest,
9 Expr, Object, Result, Symbol, Value, eval_remote_capability,
10};
11
12use crate::{
13 EvalSite, FrameKind, FrameRouter, IsolationPolicy, ServerAddress, ServerFrame,
14 eval_reply_from_frame, server_frame_from_request, symbol_list_value,
15};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct Session {
20 pub id: u64,
22 pub negotiated_codec: Symbol,
24 pub isolation: IsolationPolicy,
26 pub closed: bool,
28}
29
30impl Session {
31 pub(crate) fn as_value(&self, cx: &mut Cx) -> Result<Value> {
32 let isolation = self.isolation.as_value(cx)?;
33 cx.factory().table(vec![
34 (Symbol::new("id"), cx.factory().string(self.id.to_string())?),
35 (
36 Symbol::new("negotiated-codec"),
37 cx.factory().symbol(self.negotiated_codec.clone())?,
38 ),
39 (Symbol::new("isolation"), isolation),
40 (Symbol::new("closed"), cx.factory().bool(self.closed)?),
41 ])
42 }
43}
44
45#[derive(Clone)]
46#[non_citizen(
47 reason = "live server connection handle; reconstruct via server/Address descriptor and connect ops",
48 kind = "handle",
49 descriptor = "server/Address"
50)]
51pub struct Connection {
53 address: ServerAddress,
54 default_codec: Symbol,
55 supported_codecs: Vec<Symbol>,
56 site: Arc<dyn EvalSite>,
57 session: Arc<Mutex<Session>>,
58 router: Arc<FrameRouter>,
59 role: Option<Symbol>,
60}
61
62impl Connection {
63 pub fn new(
65 address: ServerAddress,
66 default_codec: Symbol,
67 supported_codecs: Vec<Symbol>,
68 site: Arc<dyn EvalSite>,
69 ) -> Result<Self> {
70 Self::with_session(
71 address,
72 default_codec,
73 supported_codecs,
74 site,
75 None,
76 IsolationPolicy::default(),
77 )
78 }
79
80 pub fn with_session(
83 address: ServerAddress,
84 default_codec: Symbol,
85 supported_codecs: Vec<Symbol>,
86 site: Arc<dyn EvalSite>,
87 role: Option<Symbol>,
88 isolation: IsolationPolicy,
89 ) -> Result<Self> {
90 address.ensure_transport_available()?;
91 Ok(Self {
92 address,
93 default_codec: default_codec.clone(),
94 supported_codecs,
95 site,
96 session: Arc::new(Mutex::new(Session {
97 id: 0,
98 negotiated_codec: default_codec,
99 isolation,
100 closed: false,
101 })),
102 router: Arc::new(FrameRouter::default()),
103 role,
104 })
105 }
106
107 pub fn address(&self) -> &ServerAddress {
109 &self.address
110 }
111
112 pub fn default_codec(&self) -> &Symbol {
114 &self.default_codec
115 }
116
117 pub fn supported_codecs(&self) -> &[Symbol] {
119 &self.supported_codecs
120 }
121
122 pub fn site(&self) -> &Arc<dyn EvalSite> {
124 &self.site
125 }
126
127 pub fn session(&self) -> Session {
129 self.session
130 .lock()
131 .map(|session| session.clone())
132 .unwrap_or(Session {
133 id: 0,
134 negotiated_codec: self.default_codec.clone(),
135 isolation: IsolationPolicy::default(),
136 closed: true,
137 })
138 }
139
140 pub fn role(&self) -> Option<&Symbol> {
142 self.role.as_ref()
143 }
144
145 pub fn request(
148 &self,
149 cx: &mut Cx,
150 expr: Expr,
151 timeout: Option<Duration>,
152 required_capabilities: Vec<CapabilityName>,
153 ) -> Result<Value> {
154 self.request_with_consistency(
155 cx,
156 expr,
157 timeout,
158 required_capabilities,
159 self.default_consistency(),
160 )
161 }
162
163 pub(crate) fn request_with_consistency(
164 &self,
165 cx: &mut Cx,
166 expr: Expr,
167 timeout: Option<Duration>,
168 required_capabilities: Vec<CapabilityName>,
169 consistency: Consistency,
170 ) -> Result<Value> {
171 self.enforce_consistency(consistency)?;
172 let request = EvalRequest {
173 expr,
174 result_shape: None,
175 required_capabilities,
176 deadline: timeout,
177 consistency,
178 mode: EvalMode::Eval,
179 answer_limit: None,
180 stream_buffer: None,
181 stream: false,
182 trace: false,
183 };
184 let reply = self.realize(cx, request)?;
185 Ok(reply.value)
186 }
187
188 pub fn send(
191 &self,
192 cx: &mut Cx,
193 expr: Expr,
194 codec: Symbol,
195 deadline: Option<Duration>,
196 required_capabilities: Vec<CapabilityName>,
197 reply_codec_hint: Option<Symbol>,
198 ) -> Result<u64> {
199 self.send_with_consistency(
200 cx,
201 expr,
202 codec,
203 deadline,
204 required_capabilities,
205 reply_codec_hint,
206 self.default_consistency(),
207 )
208 }
209
210 #[allow(clippy::too_many_arguments)]
211 pub(crate) fn send_with_consistency(
212 &self,
213 cx: &mut Cx,
214 expr: Expr,
215 codec: Symbol,
216 deadline: Option<Duration>,
217 required_capabilities: Vec<CapabilityName>,
218 reply_codec_hint: Option<Symbol>,
219 consistency: Consistency,
220 ) -> Result<u64> {
221 self.enforce_consistency(consistency)?;
222 self.enforce_remote_capability(cx)?;
223 let msg_id = self.router.fresh_msg_id();
224 let mut frame = server_frame_from_request(
225 cx,
226 &codec,
227 EvalRequest {
228 expr,
229 result_shape: None,
230 required_capabilities,
231 deadline,
232 consistency,
233 mode: EvalMode::Eval,
234 answer_limit: None,
235 stream_buffer: None,
236 stream: false,
237 trace: false,
238 },
239 )?;
240 frame.kind = FrameKind::Request;
241 frame.envelope.reply_codec_hint = reply_codec_hint;
242 frame.envelope.role = self.role.clone();
243 frame.msg_id = Some(msg_id);
244
245 let mut reply = self.site.answer(cx, frame)?;
246 if reply.correlate.is_none() {
247 reply.correlate = Some(msg_id);
248 }
249 self.router.push_inbound(reply)?;
250 Ok(msg_id)
251 }
252
253 pub fn notify(
255 &self,
256 cx: &mut Cx,
257 expr: Expr,
258 codec: Symbol,
259 deadline: Option<Duration>,
260 required_capabilities: Vec<CapabilityName>,
261 ) -> Result<()> {
262 self.enforce_remote_capability(cx)?;
263 let mut frame = ServerFrame::from_expr(
264 cx,
265 codec,
266 FrameKind::Notify,
267 &expr,
268 self.default_consistency(),
269 required_capabilities,
270 false,
271 )?;
272 frame.envelope.deadline = deadline;
273 frame.envelope.role = self.role.clone();
274 let _ = self.site.answer(cx, frame)?;
275 Ok(())
276 }
277
278 pub fn receive(&self, _timeout: Option<Duration>) -> Result<Option<ServerFrame>> {
283 self.router.pop_inbound()
284 }
285
286 pub fn close(&self, cx: &mut Cx) -> Result<()> {
288 self.site.close_connection(cx)?;
289 if let Ok(mut session) = self.session.lock() {
290 session.closed = true;
291 }
292 Ok(())
293 }
294}
295
296impl Object for Connection {
297 fn display(&self, _cx: &mut Cx) -> Result<String> {
298 Ok("#<server-connection>".to_owned())
299 }
300
301 fn as_any(&self) -> &dyn std::any::Any {
302 self
303 }
304}
305
306impl sim_kernel::ObjectCompat for Connection {
307 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
308 cx.factory().class_stub(
309 sim_kernel::ClassId(0),
310 Symbol::qualified("server", "Connection"),
311 )
312 }
313 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
314 self.as_table(cx)?.object().as_expr(cx)
315 }
316 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
317 let address = self.address.as_value(cx)?;
318 let default_codec = cx.factory().symbol(self.default_codec.clone())?;
319 let supported_codecs = symbol_list_value(cx, &self.supported_codecs)?;
320 let site_kind = cx.factory().string(self.site.site_kind().to_owned())?;
321 let site_address = self.site.address().as_value(cx)?;
322 let site_codecs = symbol_list_value(cx, self.site.codecs())?;
323 let session = self
324 .session
325 .lock()
326 .map_err(|_| Error::HostError("connection session mutex poisoned".to_owned()))?
327 .as_value(cx)?;
328 let next_msg_id = cx
329 .factory()
330 .string(self.router.peek_next_msg_id().to_string())?;
331 let role = match &self.role {
332 Some(role) => cx.factory().symbol(role.clone())?,
333 None => cx.factory().nil()?,
334 };
335 cx.factory().table(vec![
336 (
337 Symbol::new("kind"),
338 cx.factory().symbol(Symbol::new("connection"))?,
339 ),
340 (Symbol::new("address"), address),
341 (Symbol::new("default-codec"), default_codec),
342 (Symbol::new("supported-codecs"), supported_codecs),
343 (Symbol::new("site-kind"), site_kind),
344 (Symbol::new("site-address"), site_address),
345 (Symbol::new("site-codecs"), site_codecs),
346 (Symbol::new("session"), session),
347 (Symbol::new("role"), role),
348 (Symbol::new("next-msg-id"), next_msg_id),
349 ])
350 }
351 fn as_eval_fabric(&self) -> Option<&dyn EvalFabric> {
352 Some(self)
353 }
354}
355
356impl EvalFabric for Connection {
357 fn realize(&self, cx: &mut Cx, request: EvalRequest) -> Result<EvalReply> {
358 self.enforce_remote_capability(cx)?;
359 let mut frame = server_frame_from_request(cx, self.default_codec(), request)?;
360 frame.msg_id = Some(self.router.fresh_msg_id());
361 frame.envelope.role = self.role.clone();
362 let reply = self
363 .site
364 .answer_with_timeout(cx, frame.clone(), frame.envelope.deadline)?;
365 eval_reply_from_frame(cx, &reply)
366 }
367}
368
369impl Connection {
370 fn requires_remote_capability(&self) -> bool {
371 self.address.is_remote_like() || self.site.address().is_remote_like()
372 }
373
374 pub(crate) fn default_consistency(&self) -> Consistency {
375 if self.requires_remote_capability() {
376 Consistency::RemoteOnly
377 } else {
378 Consistency::LocalFirst
379 }
380 }
381
382 fn enforce_remote_capability(&self, cx: &mut Cx) -> Result<()> {
383 if self.requires_remote_capability() {
384 cx.require(&eval_remote_capability())?;
385 }
386 Ok(())
387 }
388
389 pub(crate) fn enforce_consistency(&self, consistency: Consistency) -> Result<()> {
390 match consistency {
391 Consistency::LocalOnly if self.requires_remote_capability() => {
392 Err(Error::Eval(format!(
393 "consistency local-only refuses remote address kind {}",
394 self.address.kind_symbol()
395 )))
396 }
397 Consistency::RemoteOnly if !self.requires_remote_capability() => {
398 Err(Error::Eval(format!(
399 "consistency remote-only refuses local address kind {}",
400 self.address.kind_symbol()
401 )))
402 }
403 _ => Ok(()),
404 }
405 }
406}