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