libsurfer/wcp/
proto.rs

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