surfer_wcp/proto.rs
1use num::{BigInt, FromPrimitive};
2use serde::{Deserialize, Deserializer, Serialize, de};
3use serde_json::Number;
4
5/// A reference to a currently displayed item. From the protocol perspective,
6/// This can be any integer or a string and what it is is decided by the server,
7/// in this case surfer.
8/// Since the representation is up to the server, clients cannot generate these on its
9/// own, it can only use the ones it has received from the server.
10#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
11#[serde(transparent)]
12pub struct DisplayedItemRef(pub usize);
13
14impl From<&DisplayedItemRef> for DisplayedItemRef {
15 fn from(value: &DisplayedItemRef) -> Self {
16 DisplayedItemRef(value.0)
17 }
18}
19
20#[derive(Serialize, Deserialize, Debug, PartialEq)]
21pub struct ItemInfo {
22 pub name: String,
23 #[serde(rename = "type")]
24 pub t: String,
25 pub id: DisplayedItemRef,
26}
27
28#[derive(Serialize, Deserialize, Debug, PartialEq)]
29#[serde(tag = "command")]
30#[allow(non_camel_case_types)]
31pub enum WcpResponse {
32 get_item_list { ids: Vec<DisplayedItemRef> },
33 get_item_info { results: Vec<ItemInfo> },
34 add_items { ids: Vec<DisplayedItemRef> },
35 add_variables { ids: Vec<DisplayedItemRef> },
36 add_scope { ids: Vec<DisplayedItemRef> },
37 add_markers { ids: Vec<DisplayedItemRef> },
38 ack,
39}
40
41#[derive(Serialize, Deserialize, Debug, PartialEq)]
42#[serde(tag = "event")]
43#[allow(non_camel_case_types)]
44pub enum WcpEvent {
45 waveforms_loaded { source: String },
46 goto_declaration { variable: String },
47 add_drivers { variable: String },
48 add_loads { variable: String },
49}
50
51#[derive(Serialize, Deserialize, Debug, PartialEq)]
52#[serde(tag = "type")]
53#[allow(non_camel_case_types)]
54pub enum WcpSCMessage {
55 greeting {
56 version: String,
57 commands: Vec<String>,
58 },
59 response(WcpResponse),
60 error {
61 error: String,
62 arguments: Vec<String>,
63 message: String,
64 },
65 event(WcpEvent),
66}
67
68impl WcpSCMessage {
69 #[must_use]
70 pub fn create_greeting(version: usize, commands: Vec<String>) -> Self {
71 Self::greeting {
72 version: version.to_string(),
73 commands,
74 }
75 }
76
77 #[must_use]
78 pub fn create_error(error: String, arguments: Vec<String>, message: String) -> Self {
79 Self::error {
80 error,
81 arguments,
82 message,
83 }
84 }
85}
86
87#[derive(Serialize, Deserialize, Debug, PartialEq)]
88pub struct MarkerInfo {
89 #[serde(deserialize_with = "deserialize_timestamp")]
90 pub time: BigInt,
91 pub name: Option<String>,
92 pub move_focus: bool,
93}
94
95#[derive(Serialize, Deserialize, Debug, PartialEq)]
96#[serde(tag = "command")]
97#[allow(non_camel_case_types)]
98pub enum WcpCommand {
99 /// Responds with [`WcpResponse::get_item_list`] which contains a list of the items
100 /// in the currently loaded waveforms
101 get_item_list,
102 /// Responds with [`WcpResponse::get_item_info`] which contains information about
103 /// each item specified in `ids` in the same order as in the `ids` array.
104 /// Responds with an error if any of the specified IDs are not items in the currently loaded
105 /// waveform.
106 get_item_info { ids: Vec<DisplayedItemRef> },
107 /// Changes the color of the specified item to the specified color.
108 /// Responds with [`WcpResponse::ack`]
109 /// Responds with an error if the `id` does not exist in the currently loaded waveform.
110 set_item_color { id: DisplayedItemRef, color: String },
111 // TODO -- remove add_variables and add_scope
112 /// Adds the specified variables to the view.
113 /// Responds with [`WcpResponse::add_variables`] which contains a list of the item references
114 /// that can be used to reference the added items later
115 /// Responds with an error if no waveforms are loaded
116 add_variables { variables: Vec<String> },
117 /// Adds all variables in the specified scope to the view.
118 /// Does so recursively if specified
119 /// Responds with [`WcpResponse::add_variables`] which contains a list of the item references
120 /// that can be used to reference the added items later
121 /// Responds with an error if no waveforms are loaded
122 add_scope {
123 scope: String,
124 #[serde(default)]
125 recursive: bool,
126 },
127 /// Adds the specified variables or variables in the specified scopes to the view.
128 /// Does so recursively if specified
129 /// Responds with [`WcpResponse::add_items`] which contains a list of the item references
130 /// that can be used to reference the added items later
131 /// Responds with an error if no waveforms are loaded
132 add_items {
133 items: Vec<String>,
134 #[serde(default)]
135 recursive: bool,
136 },
137 /// Adds the specified markers to the view.
138 /// Responds with [`WcpResponse::add_markers`] which contains a list of the item references
139 /// that can be used to reference the added items later
140 /// Responds with an error if no waveforms are loaded
141 add_markers { markers: Vec<MarkerInfo> },
142 /// Reloads the waveform from disk if this is possible for the current waveform format.
143 /// If it is not possible, this has no effect.
144 /// Responds instantly with [`WcpResponse::ack`]
145 /// Once the waveforms have been loaded, a separate event is triggered
146 reload,
147 /// Moves the viewport to center it on the specified timestamp. Does not affect the zoom
148 /// level.
149 /// Responds with [`WcpResponse::ack`]
150 set_viewport_to {
151 #[serde(deserialize_with = "deserialize_timestamp")]
152 timestamp: BigInt,
153 },
154 /// Moves the viewport to center it on the specified timestamps range. Does affect the zoom
155 /// level.
156 /// Responds with [`WcpResponse::ack`]
157 set_viewport_range {
158 #[serde(deserialize_with = "deserialize_timestamp")]
159 start: BigInt,
160 #[serde(deserialize_with = "deserialize_timestamp")]
161 end: BigInt,
162 },
163 /// Removes the specified items from the view.
164 /// Responds with [`WcpResponse::ack`]
165 /// Does not error if some of the IDs do not exist
166 remove_items { ids: Vec<DisplayedItemRef> },
167 /// Sets the specified ID as the _focused_ item.
168 /// Responds with [`WcpResponse::ack`]
169 /// Responds with an error if no waveforms are loaded or if the item reference
170 /// does not exist
171 // FIXME: What does this mean in the context of the protocol in general, feels kind
172 // of like a Surfer specific thing. Do we have a use case for it
173 focus_item { id: DisplayedItemRef },
174 /// Removes all currently displayed items
175 /// Responds with [`WcpResponse::ack`]
176 clear,
177 /// Loads a waveform from the specified file.
178 /// Responds instantly with [`WcpResponse::ack`]
179 /// Once the file is loaded, a [`WcpEvent::waveforms_loaded`] is emitted.
180 load { source: String },
181 /// Zooms out fully to fit the whole waveform in the view
182 /// Responds instantly with [`WcpResponse::ack`]
183 zoom_to_fit { viewport_idx: usize },
184 /// Set the cursor to the given time.
185 /// Responds instantly with [`WcpResponse::ack`]
186 set_cursor {
187 #[serde(deserialize_with = "deserialize_timestamp")]
188 timestamp: BigInt,
189 },
190 /// Shut down the WCP server.
191 // FIXME: What does this mean? Does it kill the server, the current connection or surfer itself?
192 shutdown,
193}
194
195#[derive(Serialize, Deserialize, Debug, PartialEq)]
196#[serde(tag = "type")]
197#[allow(non_camel_case_types)]
198pub enum WcpCSMessage {
199 #[serde(rename = "greeting")]
200 greeting {
201 version: String,
202 commands: Vec<String>,
203 },
204 command(WcpCommand),
205}
206
207impl WcpCSMessage {
208 #[must_use]
209 pub fn create_greeting(version: usize, commands: Vec<String>) -> Self {
210 Self::greeting {
211 version: version.to_string(),
212 commands,
213 }
214 }
215}
216
217fn deserialize_timestamp<'de, D>(deserializer: D) -> Result<BigInt, D::Error>
218where
219 D: Deserializer<'de>,
220{
221 let num = Number::deserialize(deserializer)?;
222 if let Some(timestamp) = num.as_u128() {
223 Ok(BigInt::from(timestamp))
224 } else if let Some(timestamp) = num.as_i128() {
225 Ok(BigInt::from(timestamp))
226 } else if let Some(timestamp) = num.as_f64() {
227 BigInt::from_f64(timestamp).ok_or_else(|| {
228 <D::Error as serde::de::Error>::invalid_value(
229 serde::de::Unexpected::Float(timestamp),
230 &"a finite value",
231 )
232 })
233 } else {
234 Err(de::Error::custom(format!(
235 "Error during deserialization of timestamp value {num}"
236 )))
237 }
238}