refactor: http module

This commit is contained in:
Tien Do Nam
2025-10-31 04:17:32 +01:00
parent 574a54acdb
commit f9aa783669
14 changed files with 304 additions and 214 deletions
+2
View File
@@ -51,6 +51,8 @@ pub fn public_key_from_cert_pem(cert: String) -> anyhow::Result<String> {
public_key_from_cert(parsed_cert)
}
/// Extracts the public key from the certificate which is in DER format.
/// Encodes the public key in PEM format.
pub fn public_key_from_cert_der(cert: &[u8]) -> anyhow::Result<String> {
let (_, parsed_cert) = X509Certificate::from_der(&cert)?;
public_key_from_cert(parsed_cert)
+32 -24
View File
@@ -1,8 +1,9 @@
mod url;
use crate::http::{dto, StatusCodeError};
use crate::model::discovery::{ProtocolType, RegisterDto, RegisterResponseDto};
use crate::model::transfer::{PrepareUploadRequestDto, PrepareUploadResponseDto};
use crate::http;
use crate::http::client::url::{ApiVersion, TargetUrl};
use crate::http::dto::ProtocolType;
use crate::http::StatusCodeError;
use crate::{crypto, util};
use futures_util::StreamExt;
use lru::LruCache;
@@ -12,7 +13,6 @@ use std::num::NonZeroUsize;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tokio_stream::wrappers::ReceiverStream;
use crate::http::client::url::{ApiVersion, TargetUrl};
const BASE_PATH: &str = "/api/localsend/v3";
@@ -33,7 +33,7 @@ pub struct RegisterResult {
pub public_key: Option<String>,
/// The response body from the register request.
pub body: RegisterResponseDto,
pub body: http::dto::RegisterResponseDto,
}
impl LsHttpClient {
@@ -73,19 +73,22 @@ impl LsHttpClient {
let generated_nonce = crypto::nonce::generate_nonce();
let generated_nonce_base64 = util::base64::encode(&generated_nonce);
let request_body = dto::NonceRequest {
let request_body = http::dto::NonceRequest {
nonce: generated_nonce_base64,
};
let res = self
.client
.post(TargetUrl {
version: ApiVersion::V3,
protocol: protocol.clone(),
host: ip.to_string(),
port,
path: "/nonce",
}.to_string())
.post(
TargetUrl {
version: ApiVersion::V3,
protocol: protocol.clone(),
host: ip.to_string(),
port,
path: "/nonce",
}
.to_string(),
)
.body(serde_json::to_string(&request_body)?)
.send()
.await?;
@@ -94,8 +97,8 @@ impl LsHttpClient {
return Err(status_code_error_from_res(res).await?);
}
let remote_key = to_remote_key(&res, protocol == &ProtocolType::Https, None)?;
let body = res.json::<dto::NonceResponse>().await?;
let remote_key = to_identifier(&res, protocol == &ProtocolType::Https, None)?;
let body = res.json::<http::dto::NonceResponse>().await?;
// Save the response nonce and our generated nonce
let response_nonce = util::base64::decode(&body.nonce)?;
@@ -125,7 +128,7 @@ impl LsHttpClient {
protocol: &ProtocolType,
ip: &str,
port: u16,
payload: RegisterDto,
payload: http::dto::RegisterDto,
) -> anyhow::Result<RegisterResult> {
let res = self
.client
@@ -145,7 +148,7 @@ impl LsHttpClient {
_ => None,
};
let body = res.json::<RegisterResponseDto>().await?;
let body = res.json::<http::dto::RegisterResponseDto>().await?;
Ok(RegisterResult { public_key, body })
}
@@ -156,8 +159,8 @@ impl LsHttpClient {
ip: &str,
port: u16,
public_key: Option<String>,
payload: PrepareUploadRequestDto,
) -> anyhow::Result<PrepareUploadResponseDto> {
payload: http::dto::PrepareUploadRequestDto,
) -> anyhow::Result<http::dto::PrepareUploadResponseDto> {
let res = self
.client
.post(format!(
@@ -179,7 +182,7 @@ impl LsHttpClient {
return Err(status_code_error_from_res(res).await?);
}
let body = res.json::<PrepareUploadResponseDto>().await?;
let body = res.json::<http::dto::PrepareUploadResponseDto>().await?;
Ok(body)
}
@@ -266,12 +269,17 @@ async fn status_code_error_from_res(response: Response) -> anyhow::Result<anyhow
}))
}
fn to_remote_key(response: &Response, require_cert: bool, public_key: Option<String>) -> anyhow::Result<String> {
fn to_identifier(
response: &Response,
require_cert: bool,
public_key: Option<String>,
) -> anyhow::Result<String> {
match require_cert {
true => verify_cert_from_res(response, public_key),
false => response.remote_addr().map(|addr| addr.ip().to_string()).ok_or_else(|| {
anyhow::anyhow!("Remote address not found in response")
}),
false => response
.remote_addr()
.map(|addr| addr.ip().to_string())
.ok_or_else(|| anyhow::anyhow!("Remote address not found in response")),
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::model::discovery::ProtocolType;
use crate::http::dto::ProtocolType;
use std::borrow::Cow;
pub struct TargetUrl {
+80 -5
View File
@@ -1,5 +1,7 @@
use crate::model::discovery::{RegisterDto, RegisterResponseDto};
use crate::model::discovery::DeviceType;
use crate::model::transfer::FileDto;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct NonceRequest {
@@ -13,12 +15,85 @@ pub struct NonceResponse {
pub nonce: String,
}
pub type RegisterRequest = RegisterDto;
pub type RegisterResponse = RegisterResponseDto;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct ErrorResponse {
/// The error message.
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterDto {
pub alias: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_type: Option<DeviceType>,
pub token: String,
pub port: u16,
pub protocol: ProtocolType,
#[serde(default, skip_serializing_if = "is_default")]
pub has_web_interface: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ProtocolType {
Http,
Https,
}
impl ProtocolType {
pub fn as_str(&self) -> &str {
match self {
ProtocolType::Http => "http",
ProtocolType::Https => "https",
}
}
}
/// Similar to `RegisterDto`, but without `port` and `protocol` (those are already known).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterResponseDto {
pub alias: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_type: Option<DeviceType>,
pub token: String,
#[serde(default, skip_serializing_if = "is_default")]
pub has_web_interface: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrepareUploadRequestDto {
pub info: RegisterDto,
pub files: HashMap<String, FileDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrepareUploadResponseDto {
pub session_id: String,
pub files: HashMap<String, String>,
}
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
t == &T::default()
}
+2 -2
View File
@@ -1,9 +1,9 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub mod client;
pub mod server;
pub mod dto;
pub mod server;
pub mod state;
#[derive(Debug, Error)]
#[error("{status};{message:?}")]
+4 -12
View File
@@ -11,17 +11,9 @@ pub(crate) trait CollectToJson {
impl CollectToJson for Incoming {
async fn collect_to_json<T: DeserializeOwned>(self) -> Result<T, AppError> {
let bytes = self.collect().await?.to_bytes();
let request = match serde_json::from_slice::<T>(&bytes) {
Ok(json) => json,
Err(err) => {
tracing::warn!("Failed to parse JSON body: {err:#}");
return Err(AppError::status(
StatusCode::BAD_REQUEST,
Some("Invalid JSON body".to_string()),
));
}
};
Ok(request)
serde_json::from_slice::<T>(&bytes).map_err(|err| {
tracing::warn!("Failed to parse JSON body: {err:#}");
AppError::status(StatusCode::BAD_REQUEST, Some("Invalid JSON body".to_string()))
})
}
}
+2
View File
@@ -0,0 +1,2 @@
pub(crate) mod v3;
pub(crate) mod web;
+78
View File
@@ -0,0 +1,78 @@
use hyper::body::Incoming;
use hyper::StatusCode;
use crate::http::dto::{NonceRequest, NonceResponse, RegisterDto, RegisterResponseDto};
use crate::http::server::{AppState, RequestClientInfo, JsonResponse};
use crate::http::server::error::AppError;
use crate::{crypto, util};
use crate::http::server::collect_to_json::CollectToJson;
pub(crate) async fn nonce_exchange(
body: Incoming,
state: AppState,
client_info: RequestClientInfo,
) -> Result<JsonResponse<NonceResponse>, AppError> {
let payload = body.collect_to_json::<NonceRequest>().await?;
let nonce = util::base64::decode(&payload.nonce).map_err(|_| {
tracing::warn!("Failed to decode nonce from base64");
AppError::status(
StatusCode::BAD_REQUEST,
Some("Invalid nonce format".to_string()),
)
})?;
if !crypto::nonce::validate_nonce(&nonce) {
tracing::warn!("Invalid nonce received");
return Err(AppError::status(
StatusCode::BAD_REQUEST,
Some("Invalid nonce".to_string()),
));
}
// Save the nonce
let remote_key = client_info.identifier();
let mut received_nonce_map = state.received_nonce_map.lock().await;
received_nonce_map.put(remote_key.clone(), nonce);
// Generate new nonce for the client
let new_nonce = crypto::nonce::generate_nonce();
let new_nonce_base64 = util::base64::encode(&new_nonce);
let mut generated_nonce_map = state.generated_nonce_map.lock().await;
generated_nonce_map.put(remote_key.clone(), new_nonce);
tracing::info!(
"Nonce exchange successful for client: {} (ID: {})",
client_info.ip,
remote_key
);
Ok(JsonResponse {
status: StatusCode::OK,
body: NonceResponse {
nonce: new_nonce_base64,
},
})
}
pub(crate) async fn register(
body: Incoming,
state: AppState,
client_info: RequestClientInfo,
) -> Result<JsonResponse<RegisterResponseDto>, AppError> {
let payload = body.collect_to_json::<RegisterDto>().await?;
let info = state.info.lock().await.clone();
let has_web_interface = state.web.lock().await.is_some();
Ok(JsonResponse {
status: StatusCode::OK,
body: RegisterResponseDto {
alias: info.alias,
version: info.version,
device_model: info.device_model,
device_type: info.device_type,
token: info.token,
has_web_interface,
},
})
}
+2
View File
@@ -0,0 +1,2 @@
#[derive(Clone, Debug)]
pub struct WebPageState;
+63 -104
View File
@@ -1,34 +1,39 @@
mod client_cert_verifier;
mod error;
mod collect_to_json;
mod controller;
mod error;
use crate::crypto::cert::public_key_from_cert_der;
use crate::http::dto::{ErrorResponse, NonceRequest, NonceResponse, RegisterRequest, RegisterResponse};
use crate::http::dto::ErrorResponse;
use crate::http::server::client_cert_verifier::CustomClientCertVerifier;
use crate::http::server::error::AppError;
use crate::{crypto, util};
use crate::http::state::ClientInfo;
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::{Body, Incoming};
use hyper::body::Incoming;
use hyper::{http, Method, Request, Response, StatusCode};
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder;
use lru::LruCache;
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::sync::Arc;
use tokio::sync::{oneshot, Mutex};
use uuid::Uuid;
use x509_parser::nom::Parser;
use crate::http::server::collect_to_json::CollectToJson;
use crate::http::server::controller::web::WebPageState;
#[derive(Clone)]
struct AppState {
/// Information about server's device.
info: Arc<Mutex<ClientInfo>>,
/// State for serving web pages.
web: Arc<Mutex<Option<WebPageState>>>,
/// Maps client identifiers to nonces that have been received from remote.
received_nonce_map: Arc<Mutex<LruCache<String, Vec<u8>>>>,
@@ -37,8 +42,10 @@ struct AppState {
}
impl AppState {
fn new() -> Self {
fn new(info: Arc<Mutex<ClientInfo>>) -> Self {
Self {
info,
web: Arc::new(Mutex::new(None)),
received_nonce_map: Arc::new(Mutex::new(LruCache::new(
NonZeroUsize::new(200).unwrap(),
))),
@@ -50,37 +57,46 @@ impl AppState {
}
pub struct LsHttpServer {
state: AppState,
stop_tx: Arc<Mutex<Option<oneshot::Sender<()>>>>,
}
impl LsHttpServer {
/// Binds the server to the specified port on both IPv4 and IPv6 addresses.
pub async fn start_with_port(
port: u16,
tls_config: Option<TlsConfig>,
info: ClientInfo,
) -> anyhow::Result<LsHttpServer> {
let ipv4_socket_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port);
let ipv6_socket_addr = SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port);
let info = Arc::new(Mutex::new(info));
let state = AppState::new(info.clone());
let (stop_tx, stop_rx) = oneshot::channel::<()>();
tokio::spawn(async move {
tokio::select! {
result = start_server_with_addr(ipv4_socket_addr, tls_config.clone()) => {
tracing::info!("Server stopped on: {}, {:?}", ipv4_socket_addr, result);
}
_ = async {
if start_server_with_addr(ipv6_socket_addr, tls_config).await.is_err() {
tracing::warn!("Failed to start server on: {}", ipv6_socket_addr);
// Keep the future running forever, so we continue using ipv4 only even if ipv6 fails.
tokio::time::sleep(std::time::Duration::from_secs(u64::MAX)).await;
tokio::spawn({
let state = state.clone();
async move {
tokio::select! {
_ = start_server_with_addr(ipv4_socket_addr, tls_config.clone(), state.clone()) => {
tracing::info!("Server stopped on: {}", ipv4_socket_addr);
}
} => {}
_ = stop_rx => {}
_ = async {
if start_server_with_addr(ipv6_socket_addr, tls_config, state).await.is_err() {
tracing::warn!("Failed to start server on: {}", ipv6_socket_addr);
// Keep the future running forever, so we continue using "ipv4 only" even if ipv6 fails.
tokio::time::sleep(std::time::Duration::from_secs(u64::MAX)).await;
}
} => {}
_ = stop_rx => {}
}
}
});
Ok(LsHttpServer {
state,
stop_tx: Arc::new(Mutex::new(Some(stop_tx))),
})
}
@@ -107,11 +123,11 @@ pub struct TlsConfig {
async fn start_server_with_addr(
socket_addr: SocketAddr,
tls_config: Option<TlsConfig>,
app_state: AppState,
) -> anyhow::Result<()> {
let _ = rustls::crypto::ring::default_provider().install_default();
let incoming = tokio::net::TcpListener::bind(socket_addr).await?;
let app_state = AppState::new();
let tls_acceptor = match tls_config {
Some(tls_config) => Some(create_tls_config(&tls_config).inspect_err(|err| {
@@ -144,7 +160,7 @@ async fn start_server_with_addr(
let client_info = {
let (_, server_connection) = tls_stream.get_ref();
ClientInfo {
RequestClientInfo {
ip: remote_addr.ip(),
cert: server_connection
.deref()
@@ -159,7 +175,7 @@ async fn start_server_with_addr(
TokioIo::new(tls_stream),
hyper::service::service_fn(move |mut req: Request<Incoming>| {
req.extensions_mut()
.insert::<ClientInfo>(client_info.clone());
.insert::<RequestClientInfo>(client_info.clone());
req.extensions_mut().insert::<AppState>(app_state.clone());
handle_request(req)
}),
@@ -171,10 +187,12 @@ async fn start_server_with_addr(
.serve_connection(
TokioIo::new(tcp_stream),
hyper::service::service_fn(move |mut req: Request<Incoming>| {
req.extensions_mut().insert::<ClientInfo>(ClientInfo {
ip: remote_addr.ip(),
cert: None,
});
req.extensions_mut().insert::<RequestClientInfo>(
RequestClientInfo {
ip: remote_addr.ip(),
cert: None,
},
);
req.extensions_mut().insert::<AppState>(app_state.clone());
handle_request(req)
}),
@@ -205,7 +223,7 @@ fn create_tls_config(tls_config: &TlsConfig) -> anyhow::Result<tokio_rustls::Tls
}
#[derive(Clone, Debug)]
struct ClientInfo {
struct RequestClientInfo {
/// The IP address of the client.
ip: IpAddr,
@@ -213,7 +231,7 @@ struct ClientInfo {
cert: Option<Vec<u8>>,
}
impl ClientInfo {
impl RequestClientInfo {
fn extract_public_key(&self) -> Option<String> {
match &self.cert {
Some(cert) => match public_key_from_cert_der(cert) {
@@ -227,7 +245,7 @@ impl ClientInfo {
}
}
fn to_remote_key(&self) -> String {
fn identifier(&self) -> String {
self.extract_public_key()
.unwrap_or_else(|| self.ip.to_string())
}
@@ -278,21 +296,25 @@ async fn handle_request_inner(
return Err(AppError::status(StatusCode::INTERNAL_SERVER_ERROR, None));
};
let Some(client_info) = req.extensions_mut().remove::<ClientInfo>() else {
let Some(client_info) = req.extensions_mut().remove::<RequestClientInfo>() else {
return Err(AppError::status(StatusCode::INTERNAL_SERVER_ERROR, None));
};
match (req.method(), req.uri().path()) {
(&Method::POST, "/api/localsend/v3/nonce") => {
Ok(nonce_exchange(req.into_body(), state, client_info)
.await?
.into_response())
Ok(
controller::v3::nonce_exchange(req.into_body(), state, client_info)
.await?
.into_response(),
)
}
(&Method::POST, "/api/localsend/v3/register") => {
Ok(
controller::v3::register(req.into_body(), state, client_info)
.await?
.into_response(),
)
}
// (&Method::POST, "/api/localsend/v3/register") => {
// Ok(register(req.into_body(), state, client_info)
// .await?
// .into_response())
// }
_ => {
let mut res = Response::new(Full::default());
*res.status_mut() = StatusCode::NOT_FOUND;
@@ -300,66 +322,3 @@ async fn handle_request_inner(
}
}
}
async fn nonce_exchange(
body: Incoming,
state: AppState,
client_info: ClientInfo,
) -> Result<JsonResponse<NonceResponse>, AppError> {
let payload = body.collect_to_json::<NonceRequest>().await?;
let nonce = util::base64::decode(&payload.nonce).map_err(|_| {
tracing::warn!("Failed to decode nonce from base64");
AppError::status(
StatusCode::BAD_REQUEST,
Some("Invalid nonce format".to_string()),
)
})?;
if !crypto::nonce::validate_nonce(&nonce) {
tracing::warn!("Invalid nonce received");
return Err(AppError::status(
StatusCode::BAD_REQUEST,
Some("Invalid nonce".to_string()),
));
}
// Save the nonce
let remote_key = client_info.to_remote_key();
let mut received_nonce_map = state.received_nonce_map.lock().await;
received_nonce_map.put(remote_key.clone(), nonce);
// Generate new nonce for the client
let new_nonce = crypto::nonce::generate_nonce();
let new_nonce_base64 = util::base64::encode(&new_nonce);
let mut generated_nonce_map = state.generated_nonce_map.lock().await;
generated_nonce_map.put(remote_key.clone(), new_nonce);
tracing::info!(
"Nonce exchange successful for client: {} (ID: {})",
client_info.ip,
remote_key
);
Ok(JsonResponse {
status: StatusCode::OK,
body: NonceResponse {
nonce: new_nonce_base64,
},
})
}
// async fn register(
// body: Incoming,
// state: AppState,
// client_info: ClientInfo,
// ) -> Result<JsonResponse<RegisterResponse>, AppError> {
// let payload = body.collect_to_json::<RegisterRequest>().await?;
//
// Ok(JsonResponse {
// status: StatusCode::OK,
// body: RegisterResponse {
// token: payload.token,
// },
// })
// }
+25
View File
@@ -0,0 +1,25 @@
use serde::{Deserialize, Serialize};
use crate::model::discovery::DeviceType;
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClientInfo {
/// The name of the peer.
pub alias: String,
/// Client Protocol Version (major.minor)
pub version: String,
/// The device model of the peer.
/// Windows, macOS, iPhone, Samsung, etc.
#[serde(skip_serializing_if = "Option::is_none")]
pub device_model: Option<String>,
/// The device type of the peer.
#[serde(skip_serializing_if = "Option::is_none")]
pub device_type: Option<DeviceType>,
/// A token generated by the client.
/// Used to merge the same peers detected on different channels (LAN, WebRTC, etc.).
pub token: String,
}
+13 -5
View File
@@ -7,8 +7,7 @@ mod webrtc;
use crate::crypto::token;
use crate::http::client::LsHttpClient;
use crate::http::server::TlsConfig;
use crate::model::discovery::{DeviceType, ProtocolType, RegisterDto};
use crate::model::transfer::PrepareUploadRequestDto;
use crate::model::discovery::DeviceType;
use crate::webrtc::signaling::{ClientInfo, WsServerMessage};
use crate::webrtc::webrtc::{PinConfig, RTCFile, RTCFileError, RTCSendFileResponse, RTCStatus};
use anyhow::Result;
@@ -20,6 +19,7 @@ use tokio::io;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::{mpsc, oneshot};
use tracing::Level;
use crate::http::dto::{PrepareUploadRequestDto, ProtocolType, RegisterDto};
#[tokio::main]
#[cfg(feature = "full")]
@@ -127,12 +127,20 @@ MCowBQYDK2VwAyEAZmdXP230oqK92o65ra3XaF2F8r3+fK5DEBK4c40qVts=
}
async fn server_test() -> Result<()> {
let client_info = http::state::ClientInfo {
alias: "Server-Test".to_string(),
version: "1.2.3".to_string(),
device_model: None,
device_type: None,
token: "456".to_string(),
};
let server = http::server::LsHttpServer::start_with_port(
53317,
Some(TlsConfig {
cert: CERT.to_string(),
private_key: PRIVATE_KEY.to_string(),
}),
client_info,
)
.await?;
tokio::time::sleep(std::time::Duration::from_secs(u64::MAX)).await;
@@ -156,10 +164,10 @@ async fn client_test() -> Result<()> {
version: "2.3".to_string(),
device_model: Some("test".to_string()),
device_type: Some(DeviceType::Headless),
fingerprint: "test".to_string(),
token: "test".to_string(),
port: 53317,
protocol: ProtocolType::Https,
download: false,
has_web_interface: false,
};
let response = client
@@ -179,7 +187,7 @@ async fn client_test() -> Result<()> {
files: {
let mut map = HashMap::new();
let id = "test-123-id".to_string();
let file = crate::model::transfer::FileDto {
let file = model::transfer::FileDto {
id: id.clone(),
file_name: "test.mp4".to_string(),
size: 1000,
-45
View File
@@ -1,34 +1,5 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterDto {
pub alias: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_type: Option<DeviceType>,
pub fingerprint: String,
pub port: u16,
pub protocol: ProtocolType,
pub download: bool,
}
/// Similar to `RegisterDto`, but without `port` and `protocol` (those are already known).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterResponseDto {
pub alias: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_type: Option<DeviceType>,
pub fingerprint: String,
pub download: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeviceType {
@@ -38,19 +9,3 @@ pub enum DeviceType {
Headless,
Server,
}
#[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ProtocolType {
Http,
Https,
}
impl ProtocolType {
pub fn as_str(&self) -> &str {
match self {
ProtocolType::Http => "http",
ProtocolType::Https => "https",
}
}
}
-16
View File
@@ -1,6 +1,4 @@
use crate::model::discovery::RegisterDto;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -24,17 +22,3 @@ pub struct FileMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub accessed: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrepareUploadRequestDto {
pub info: RegisterDto,
pub files: HashMap<String, FileDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PrepareUploadResponseDto {
pub session_id: String,
pub files: HashMap<String, String>,
}