1use busrt::rpc::{RpcError, RpcEvent, RpcResult};
2use eva_common::{
3 acl::Acl,
4 payload::{pack, unpack},
5 prelude::*,
6 services,
7};
8pub use logicline::global::{ingress, processor, set_recording};
9pub use logicline::{Processor, Snapshot, action};
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
12use std::sync::OnceLock;
13
14use crate::hmi::XParamsOwned;
15
16static SNAPSHOT_ACL_MAPPING: OnceLock<SnapshotAclMapping> = OnceLock::new();
17
18pub fn set_snapshot_acl_mapping(mapping: SnapshotAclMapping) -> EResult<()> {
19 SNAPSHOT_ACL_MAPPING
20 .set(mapping)
21 .map_err(|_| Error::core("Snapshot ACL mapping already set"))
22}
23
24pub(crate) fn api_ll_info() -> EResult<Vec<u8>> {
25 #[derive(Serialize)]
26 struct Response {
27 recording: bool,
28 }
29 let current_recording_state = logicline::global::is_recording();
30 pack(&Response {
31 recording: current_recording_state,
32 })
33}
34
35pub(crate) fn api_ll_snapshot(params: Option<Value>, acl: Option<&Acl>) -> EResult<Vec<u8>> {
36 #[derive(Deserialize)]
37 #[serde(deny_unknown_fields)]
38 struct Params {
39 #[serde(default)]
40 line_filter: String,
41 }
42 let mut line_filter = String::new();
43 if let Some(p) = params
44 && !p.is_unit()
45 {
46 line_filter = Params::deserialize(p)?.line_filter;
47 }
48 if line_filter == "#" || line_filter == "*" {
49 line_filter.clear();
50 }
51 let mut snapshot = logicline::global::snapshot_filtered(|l| l.name().starts_with(&line_filter));
52 if let Some(acl) = acl
53 && let Some(mapping) = SNAPSHOT_ACL_MAPPING.get()
54 {
55 snapshot = mapping.format_snapshot(snapshot, acl);
56 }
57 pack(&snapshot)
58}
59
60pub(crate) fn api_ll_set_recording(params: Value) -> EResult<Vec<u8>> {
61 #[derive(Deserialize)]
62 #[serde(deny_unknown_fields)]
63 struct Params {
64 recording: bool,
65 }
66 let params: Params = Params::deserialize(params)?;
67 logicline::global::set_recording(params.recording);
68 Ok(vec![])
69}
70
71#[derive(Clone, Serialize, Deserialize, Default)]
72pub struct SnapshotAclMapping {
73 inner: BTreeMap<String, OID>,
74}
75
76impl SnapshotAclMapping {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn add(&mut self, source: impl AsRef<str>, oid: OID) {
83 self.inner.insert(source.as_ref().to_owned(), oid);
84 }
85
86 pub fn format_snapshot(
87 &self,
88 mut snapshot: logicline::Snapshot,
89 acl: &Acl,
90 ) -> logicline::Snapshot {
91 for line in snapshot.lines_mut().values_mut() {
92 let line_name = line.name().to_owned();
93 for step in line.steps_mut() {
94 for i in step.info_mut() {
95 let source = format!("{}/{}", line_name, i.name());
96 let Some(oid) = self.inner.get(&source) else {
97 continue;
98 };
99 if acl.check_item_read(oid) {
100 continue;
101 }
102 *i = i.to_modified(
103 None,
104 Some(serde_json::Value::String("<hidden>".to_owned())),
105 None,
106 None,
107 );
108 }
109 }
110 }
111 snapshot
112 }
113}
114
115pub fn handle_default_rpc(event: RpcEvent, info: &services::ServiceInfo) -> RpcResult {
116 let method = event.parse_method()?;
117 let payload = event.payload();
118 match method {
119 "ll.info" => Ok(Some(api_ll_info()?)),
120 "ll.snapshot" => Ok(Some(api_ll_snapshot(
121 if payload.is_empty() {
122 None
123 } else {
124 unpack(payload)?
125 },
126 None,
127 )?)),
128 "ll.set_recording" => {
129 if payload.is_empty() {
130 return Err(RpcError::params(None));
131 }
132 Ok(Some(api_ll_set_recording(unpack(payload)?)?))
133 }
134 "x" => {
135 if payload.is_empty() {
136 Err(RpcError::params(None))
137 } else {
138 let xp: XParamsOwned = unpack(payload)?;
139 match xp.method() {
140 "ll.info" => Ok(Some(api_ll_info()?)),
141 "ll.snapshot" => Ok(Some(api_ll_snapshot(Some(xp.params), Some(&xp.acl))?)),
142 "ll.set_recording" => {
143 xp.acl.require_admin()?;
144 Ok(Some(api_ll_set_recording(xp.params)?))
145 }
146 _ => Err(RpcError::method(None)),
147 }
148 }
149 }
150 _ => crate::service::svc_handle_default_rpc(method, info),
151 }
152}