Skip to main content

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}