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.
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}