obscuravpn_api/
types.rs

1use std::fmt::{Debug, Display, Formatter};
2use std::str::FromStr;
3use std::{fmt, net};
4
5use ipnetwork;
6use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
7use thiserror::Error;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AccountInfo {
11    pub id: String,
12    pub active: bool,
13    pub top_up: Option<TopUp>,
14    pub subscription: Option<Subscription>,
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone)]
18pub struct TopUp {
19    pub credit_expires_at: i64,
20}
21
22#[derive(Debug, Serialize, Deserialize, Clone)]
23pub struct Subscription {
24    /// string repr of [`stripe::SubscriptionStatus`][https://docs.rs/async-stripe/latest/stripe/enum.SubscriptionStatus.html]
25    status: String,
26    /// period start in seconds since unix epoch
27    current_period_start: i64,
28    /// period end in seconds since unix epoch
29    current_period_end: i64,
30    /// whether the subscription will end at this period
31    cancel_at_period_end: bool,
32}
33
34impl Subscription {
35    pub fn new(status: String, current_period_start: i64, current_period_end: i64, cancel_at_period_end: bool) -> Self {
36        Self {
37            status,
38            current_period_start,
39            current_period_end,
40            cancel_at_period_end,
41        }
42    }
43}
44
45#[derive(Serialize, Deserialize, Clone, Debug)]
46pub struct OneTunnel {
47    pub id: String,
48    pub status: TunnelStatus,
49    pub config: TunnelConfig,
50    pub relay: OneRelay,
51    pub exit: OneExit,
52}
53
54#[derive(Serialize, Deserialize, Clone, Debug)]
55#[serde(tag = "type", rename_all = "snake_case")]
56pub enum TunnelStatus {
57    Created { when: i64 },
58    Connected { when: i64 },
59    Disconnected { when: i64 },
60}
61
62#[derive(Serialize, Deserialize, Clone, Debug)]
63#[serde(tag = "type", rename_all = "snake_case")]
64pub enum TunnelConfig {
65    UdpPort { client: WgClientConfig, server: WgServerConfig },
66    Obfuscated(ObfuscatedTunnelConfig),
67}
68
69#[derive(Serialize, Deserialize, Clone, Debug)]
70pub struct WgClientConfig {
71    pub wg_pubkey: WgPubkey,
72    pub addresses: Vec<ipnetwork::IpNetwork>,
73}
74
75#[derive(Serialize, Deserialize, Clone, Debug)]
76pub struct WgServerConfig {
77    pub wg_pubkey: WgPubkey,
78    pub endpoints: Vec<net::SocketAddr>,
79    pub dnses: Vec<net::IpAddr>,
80}
81
82#[derive(Serialize, Deserialize, Clone, Debug)]
83pub struct ObfuscatedTunnelConfig {
84    pub client_pubkey: WgPubkey,
85    pub client_ips_v4: Vec<ipnetwork::Ipv4Network>,
86    pub client_ips_v6: Vec<ipnetwork::Ipv6Network>,
87    pub dns: Vec<net::IpAddr>,
88    pub relay_addr_v4: net::SocketAddrV4,
89    pub relay_addr_v6: net::SocketAddrV6,
90    pub relay_cert: String,
91    pub exit_pubkey: WgPubkey,
92}
93
94const WG_PUBKEY_LENGTH: usize = 32;
95#[derive(Deserialize, Clone, Eq, PartialEq)]
96pub struct WgPubkey(#[serde(deserialize_with = "deserialize_base64")] pub [u8; WG_PUBKEY_LENGTH]);
97
98impl std::fmt::Debug for WgPubkey {
99    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100        use base64::{engine::general_purpose::STANDARD, Engine as _};
101        f.debug_tuple("WgPubKey").field(&STANDARD.encode(self.0)).finish()
102    }
103}
104
105#[derive(Error, Debug)]
106pub enum ParseWgPubkeyError {
107    #[error("expected {} bytes, found {}", WG_PUBKEY_LENGTH, .0)]
108    InvalidLength(usize),
109    #[error("base64 decode err: {:?}", .0)]
110    NotBase64(#[from] base64::DecodeError),
111}
112
113impl FromStr for WgPubkey {
114    type Err = ParseWgPubkeyError;
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        use base64::{engine::general_purpose::STANDARD, Engine as _};
117        let decoded = STANDARD.decode(s)?;
118        let bytes = decoded.try_into().map_err(|d: Vec<u8>| ParseWgPubkeyError::InvalidLength(d.len()))?;
119        Ok(WgPubkey(bytes))
120    }
121}
122
123impl Display for WgPubkey {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        use base64::{display::Base64Display, engine::general_purpose::STANDARD};
126        Base64Display::new(&self.0, &STANDARD).fmt(f)
127    }
128}
129
130impl Serialize for WgPubkey {
131    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
132    where
133        S: Serializer,
134    {
135        serializer.serialize_str(&self.to_string())
136    }
137}
138
139fn deserialize_base64<'de, D>(deserializer: D) -> Result<[u8; WG_PUBKEY_LENGTH], D::Error>
140where
141    D: Deserializer<'de>,
142{
143    let encoded = String::deserialize(deserializer)?;
144    match WgPubkey::from_str(&encoded) {
145        Ok(pk) => Ok(pk.0),
146        Err(ParseWgPubkeyError::InvalidLength(n)) => {
147            let error_string = format!("a base64 string representing {} bytes", WG_PUBKEY_LENGTH);
148            Err(D::Error::invalid_length(n, &error_string.as_str()))
149        }
150        Err(ParseWgPubkeyError::NotBase64(err)) => Err(D::Error::custom(err)),
151    }
152}
153
154#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
155pub struct OneRelay {
156    pub id: String,
157    pub ip_v4: net::Ipv4Addr,
158    pub ip_v6: net::Ipv6Addr,
159}
160
161#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
162pub struct OneExit {
163    pub id: String,
164    pub country_code: String,
165    pub city_name: String,
166}
167
168#[derive(Serialize, Deserialize, Clone, Debug)]
169pub struct Prices {
170    pub subscription: Vec<Price>,
171    pub top_up: Vec<Price>,
172
173    /// A global sale description.
174    ///
175    /// The individual prices may also have specific reasons (which may be the same).
176    pub sale: Option<Sale>,
177}
178
179#[derive(Serialize, Deserialize, Clone, Debug)]
180pub struct Price {
181    pub months: u16,
182    pub usd_cents: u32,
183
184    /// The "regular" price which discounts can be contrasted against.
185    /// This field is purely informational.
186    pub regular_usd_cents: u32,
187
188    pub sale: Option<Sale>,
189}
190
191#[derive(Serialize, Deserialize, Clone, Debug)]
192pub struct Sale {
193    /// Example: "Launch Sale"
194    pub title: String,
195
196    /// Example: "Thanks for being an early Obscura user. Sign up now and get a special price."
197    pub summary: String,
198}
199
200#[test]
201fn serde_wg_enc_dec() {
202    let pk = WgPubkey([
203        197, 50, 1, 3, 154, 219, 161, 75, 231, 31, 136, 109, 161, 216, 219, 233, 238, 189, 237, 8, 203, 17, 27, 117, 52, 0, 120, 153, 154, 169, 240,
204        56,
205    ]);
206
207    let json = "\"xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=\"";
208    let pk_from_json: WgPubkey = serde_json::from_str(json).unwrap();
209    assert_eq!(pk_from_json, pk);
210    let json_from_pk = serde_json::to_string(&pk).unwrap();
211    assert_eq!(json_from_pk, json);
212
213    let base64 = &json[1..json.len() - 1];
214    let pk_from_base64: WgPubkey = base64.parse().unwrap();
215    assert_eq!(pk_from_base64, pk);
216    let base64_from_pk = pk.to_string();
217    assert_eq!(base64_from_pk, base64);
218}
219
220#[derive(Clone, PartialEq, Eq)]
221pub struct AuthToken(String);
222
223impl AuthToken {
224    pub fn as_str(&self) -> &str {
225        &self.0
226    }
227}
228
229impl Debug for AuthToken {
230    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
231        f.debug_tuple("AuthToken").field(&format_args!("_")).finish()
232    }
233}
234
235impl From<String> for AuthToken {
236    fn from(s: String) -> Self {
237        Self(s)
238    }
239}
240
241impl From<AuthToken> for String {
242    fn from(value: AuthToken) -> Self {
243        value.0
244    }
245}