fundamentum_portforwarding_proto_rust/
lib.rs

1//! Rust bindings to Fundamentum portforwarding protocol.
2//! A library that provides a Rust representation of the basic types, interfaces and
3//! other components required to define and interact with portforwarding
4//! defined by [`fundamentum-portforwarding-proto`][repo-proto].
5//!
6//! [repo-proto]: https://bitbucket.org/amotus/fundamentum-portforwarding-proto
7
8pub mod errors;
9/// The rust bindings generated from the protobuf files under `./proto/`.
10///
11/// Mirrors original protobuf top level 'com' namespace.
12pub mod com {
13    /// Mirrors original protobuf namespace at 'com.fundamentum'.
14    ///
15    /// Exposes Fundamentum related protobuf symbols.
16    pub mod fundamentum {
17        /// Mirrors original protobuf namespace at 'com.fundamentum.portforwarding'.
18        ///
19        /// Exposes Fundamentum Portforwarding related protobuf symbols.
20        pub mod portforwarding {
21            pub use v1 as latest;
22            /// Mirrors original protobuf namespace at 'com.fundamentum.portforwarding.v1'.
23            ///
24            /// Exposes Fundamentum Portforwarding related protobuf symbols as of version 1
25            /// or the services.
26            pub mod v1 {
27                use crate::Version;
28
29                include!(concat!(
30                    env!("OUT_DIR"),
31                    "/com.fundamentum.portforwarding.v1.rs"
32                ));
33
34                /// Get current version
35                pub fn get_version() -> Version {
36                    Version::V1
37                }
38            }
39        }
40    }
41}
42use com::fundamentum::portforwarding::v1;
43use derive_new::new;
44use enum_tags::Tag;
45pub use enum_tags_traits::TaggedEnum;
46pub use errors::Error;
47use prost::Message;
48use serde::{Deserialize, Serialize};
49use std::{
50    fmt::{self, Display},
51    str::FromStr,
52};
53use uuid::Uuid;
54
55#[derive(PartialEq, Eq, Debug, new, Clone, Copy)]
56pub struct TargetPort(pub u32);
57
58impl Display for TargetPort {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "{}", self.0)
61    }
62}
63
64#[derive(PartialEq, Eq, Debug, new, Serialize, Deserialize, Clone, Copy)]
65pub struct ProxyPort(pub u32);
66
67impl Display for ProxyPort {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{}", self.0)
70    }
71}
72
73/// Protocol version
74#[derive(Debug, Clone)]
75pub enum Version {
76    V1,
77}
78
79impl Version {
80    pub fn get_latest() -> Self {
81        com::fundamentum::portforwarding::latest::get_version()
82    }
83}
84
85impl FromStr for Version {
86    type Err = errors::Error;
87
88    fn from_str(input: &str) -> Result<Version, Self::Err> {
89        match input {
90            "1" => Ok(Version::V1),
91            _ => Err(errors::Error::InvalidVersion),
92        }
93    }
94}
95
96impl Display for Version {
97    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98        match self {
99            Version::V1 => write!(f, "1"),
100        }
101    }
102}
103
104#[derive(Clone, new)]
105pub struct PortForwardingMsg {
106    pub instance_id: Uuid,
107    pub operation: PortforwardingOperation,
108    pub version: Version,
109}
110
111/// Layer that represent portforwarding device message with good typing
112#[derive(Clone, Debug, PartialEq, Eq, Tag)]
113pub enum PortforwardingOperation {
114    Handshake,
115    TransferData(TransferData),
116    Error(ErrorOperation),
117    Status(Status),
118    OpenConnection(OpenConnection),
119    CloseConnection,
120}
121
122#[derive(Clone, Debug, PartialEq, Eq)]
123pub struct TransferData {
124    pub payload: Vec<u8>,
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub struct ErrorOperation {
129    pub code: ErrorCode,
130    pub message: String,
131}
132
133#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct Tty {
135    pub cmd: String,
136    pub rows: u32,
137    pub cols: u32,
138}
139
140#[derive(Clone, Debug, PartialEq, Eq)]
141pub enum OpenConnection {
142    Tty(Tty),
143    Port(TargetPort),
144}
145
146/// Represent each possible status
147#[derive(PartialEq, Eq, Clone, Debug)]
148pub enum Status {
149    ConnectionEstablished,
150}
151
152impl PortforwardingOperation {
153    /// Error payload.
154    #[must_use]
155    pub const fn new_error(code: ErrorCode, message: String) -> Self {
156        Self::Error(ErrorOperation { code, message })
157    }
158
159    /// Close a connection payload
160    #[must_use]
161    pub const fn new_close_connection() -> Self {
162        Self::CloseConnection
163    }
164
165    /// generate open port connection message
166    #[must_use]
167    pub const fn new_open_port_connection(port: TargetPort) -> Self {
168        Self::OpenConnection(OpenConnection::Port(port))
169    }
170
171    /// generate open tty connection message
172    #[must_use]
173    pub const fn new_open_tty_connection(cmd: String, row: u32, cols: u32) -> Self {
174        Self::OpenConnection(OpenConnection::Tty(Tty {
175            cmd,
176            rows: row,
177            cols,
178        }))
179    }
180
181    /// generate transfer data message
182    #[must_use]
183    pub const fn new_transfer_data(payload: Vec<u8>) -> Self {
184        Self::TransferData(TransferData { payload })
185    }
186
187    /// generate transfer data message
188    #[must_use]
189    pub const fn new_connection_established() -> Self {
190        Self::Status(Status::ConnectionEstablished)
191    }
192
193    pub fn encode(self, version: &Version) -> Vec<u8> {
194        match version {
195            Version::V1 => match self {
196                Self::TransferData(transfer_data_msg) => v1::TransferData {
197                    payload: transfer_data_msg.payload,
198                }
199                .encode_to_vec(),
200                Self::Error(error_msg) => v1::Error {
201                    code: error_msg.code as i32,
202                    message: error_msg.message,
203                }
204                .encode_to_vec(),
205                Self::Status(status) => match status {
206                    Status::ConnectionEstablished => v1::Status {
207                        value: v1::StatusValue::StatusConnectionEstablished.into(),
208                    }
209                    .encode_to_vec(),
210                },
211                Self::OpenConnection(open_connection_msg) => v1::OpenConnection {
212                    r#type: match open_connection_msg {
213                        OpenConnection::Tty(tty) => {
214                            Some(v1::open_connection::Type::Tty(v1::open_connection::Tty {
215                                cmd: tty.cmd,
216                                rows: tty.rows,
217                                cols: tty.cols,
218                            }))
219                        }
220                        OpenConnection::Port(port) => {
221                            Some(v1::open_connection::Type::TargetPort(port.0))
222                        }
223                    },
224                }
225                .encode_to_vec(),
226                Self::CloseConnection => v1::CloseConnection {}.encode_to_vec(),
227                Self::Handshake => Vec::new(),
228            },
229        }
230    }
231
232    pub fn decode(
233        version: Version,
234        payload: &[u8],
235        operation: PortforwardingOperationTag,
236    ) -> Result<Self, errors::Error> {
237        use PortforwardingOperationTag as Tag;
238
239        if operation == Tag::Handshake {
240            return Ok(Self::Handshake);
241        }
242        match version {
243            Version::V1 => match operation {
244                Tag::OpenConnection => {
245                    let decoded = v1::OpenConnection::decode(payload)?;
246                    match decoded.r#type {
247                        Some(v1::open_connection::Type::Tty(tty)) => {
248                            Ok(Self::OpenConnection(OpenConnection::Tty(Tty {
249                                cmd: tty.cmd,
250                                rows: tty.rows,
251                                cols: tty.cols,
252                            })))
253                        }
254                        Some(v1::open_connection::Type::TargetPort(port)) => {
255                            Ok(Self::OpenConnection(OpenConnection::Port(TargetPort(port))))
256                        }
257                        None => Err(errors::Error::InvalidOperation),
258                    }
259                }
260                Tag::CloseConnection => Ok(Self::CloseConnection),
261                Tag::TransferData => Ok(Self::TransferData(TransferData {
262                    payload: v1::TransferData::decode(payload)?.payload,
263                })),
264                Tag::Error => {
265                    let v1_error = v1::Error::decode(payload)?;
266                    Ok(Self::Error(ErrorOperation {
267                        code: ErrorCode::from(v1_error.code),
268                        message: v1_error.message,
269                    }))
270                }
271                Tag::Status => {
272                    let v1_status = v1::Status::decode(payload)?;
273                    Ok(Self::Status(match v1_status.value() {
274                        v1::StatusValue::StatusConnectionEstablished => {
275                            Status::ConnectionEstablished
276                        }
277                        _ => return Err(errors::Error::InvalidStatus),
278                    }))
279                }
280                _ => Err(errors::Error::InvalidOperation),
281            },
282        }
283    }
284}
285
286impl fmt::Display for PortforwardingOperationTag {
287    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288        match self {
289            Self::CloseConnection => write!(f, "close"),
290            Self::OpenConnection => write!(f, "open"),
291            Self::TransferData => write!(f, "tx"),
292            Self::Handshake => write!(f, "hsk"),
293            Self::Error => write!(f, "error"),
294            Self::Status => write!(f, "status"),
295        }
296    }
297}
298
299impl FromStr for PortforwardingOperationTag {
300    type Err = errors::Error;
301
302    fn from_str(s: &str) -> Result<Self, Self::Err> {
303        match s {
304            "error" => Ok(Self::Error),
305            "status" => Ok(Self::Status),
306            "close" => Ok(Self::CloseConnection),
307            "open" => Ok(Self::OpenConnection),
308            "hsk" => Ok(Self::Handshake),
309            "tx" => Ok(Self::TransferData),
310            _ => Err(errors::Error::InvalidOperation),
311        }
312    }
313}
314
315#[derive(Clone, Debug, PartialEq, Eq)]
316pub enum ErrorCode {
317    /// Unspecified error code.
318    Unknown = 0,
319    /// Instance not found inside the hashmap.
320    InstanceNotFound = 1,
321    /// Cannot send the message to the instance. Maybe the instance is corrupted.
322    /// It can happen when the instance's channel is closed and instance is not removed from the hashmap.
323    InstanceCorrupted = 2,
324    /// Cannot spawn a tty.
325    SpawnTty = 3,
326    /// Cannot open desired port.
327    SpawnTcp = 4,
328    /// Cannot parse the header of the message.
329    HeaderParsing = 5,
330    /// Cannot converse rows and columns value for the tty. It should be a u16 value (0-65535).
331    InvalidRowsColsValueForTty = 6,
332    /// Invalid port number. It should be a u16 value (0-65535).
333    InvalidPort = 7,
334    /// Cannot send the payload to the server.
335    WriteDataToServer = 8,
336    /// Non unix platform are not supported for tty feature.
337    UnsupportedPlatform = 9,
338    /// Unexpected operation received from the cloud. It could be an error or a status message which is not expected in the current context.
339    UnexpectedOperation = 10,
340    /// Resize TTY operation failed.
341    ResizeTty = 11,
342    /// Spawn command failed.
343    SpawnTtyCommand = 12,
344}
345
346impl std::convert::From<i32> for ErrorCode {
347    fn from(value: i32) -> Self {
348        match value {
349            0 => ErrorCode::Unknown,
350            1 => ErrorCode::InstanceNotFound,
351            2 => ErrorCode::InstanceCorrupted,
352            3 => ErrorCode::SpawnTty,
353            4 => ErrorCode::SpawnTcp,
354            5 => ErrorCode::HeaderParsing,
355            6 => ErrorCode::InvalidRowsColsValueForTty,
356            7 => ErrorCode::InvalidPort,
357            8 => ErrorCode::WriteDataToServer,
358            9 => ErrorCode::UnsupportedPlatform,
359            10 => ErrorCode::UnexpectedOperation,
360            11 => ErrorCode::ResizeTty,
361            12 => ErrorCode::SpawnTtyCommand,
362            _ => ErrorCode::Unknown,
363        }
364    }
365}