mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-09 03:04:31 +00:00
Compare commits
6 Commits
main
...
ohos_new_api
| Author | SHA1 | Date | |
|---|---|---|---|
| 16eb3e5f78 | |||
| 1d652bac08 | |||
| bf427a5d6f | |||
| f030e3ab36 | |||
| 006783f0f9 | |||
| 09b4db5d3f |
+544
-132
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,10 @@ edition = "2024"
|
|||||||
crate-type=["cdylib"]
|
crate-type=["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1"
|
||||||
|
base64 = "0.22"
|
||||||
|
flate2 = "1.1"
|
||||||
|
gethostname = "1.1"
|
||||||
ohos-hilog-binding = {version = "*", features = ["redirect"]}
|
ohos-hilog-binding = {version = "*", features = ["redirect"]}
|
||||||
easytier = { path = "../../easytier" }
|
easytier = { path = "../../easytier" }
|
||||||
napi-derive-ohos = "1.1"
|
napi-derive-ohos = "1.1"
|
||||||
@@ -26,10 +30,16 @@ napi-ohos = { version = "1.1", default-features = false, features = [
|
|||||||
"web_stream",
|
"web_stream",
|
||||||
] }
|
] }
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
ipnet = "2.10"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.125"
|
serde_json = "1.0.125"
|
||||||
|
prost-reflect = { version = "0.14.5", default-features = false, features = ["derive"] }
|
||||||
|
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
tracing-core = "0.1.33"
|
tracing-core = "0.1.33"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
|
||||||
|
url = "2.5"
|
||||||
uuid = { version = "1.5.0", features = [
|
uuid = { version = "1.5.0", features = [
|
||||||
"v4",
|
"v4",
|
||||||
"fast-rng",
|
"fast-rng",
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub(crate) mod repository;
|
||||||
|
pub(crate) mod services;
|
||||||
|
pub(crate) mod storage;
|
||||||
|
pub(crate) mod types;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#[path = "../../config_repo/field_store.rs"]
|
||||||
|
mod field_store;
|
||||||
|
#[path = "../../config_repo/import_export.rs"]
|
||||||
|
mod import_export;
|
||||||
|
#[path = "../../config_repo/legacy_migration.rs"]
|
||||||
|
mod legacy_migration;
|
||||||
|
#[path = "../../config_repo/validation.rs"]
|
||||||
|
mod validation;
|
||||||
|
|
||||||
|
#[path = "../../config_repo.rs"]
|
||||||
|
mod repo;
|
||||||
|
|
||||||
|
pub use repo::*;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) mod schema_service;
|
||||||
|
pub(crate) mod share_link_service;
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
use easytier::proto::ALL_DESCRIPTOR_BYTES;
|
||||||
|
use napi_derive_ohos::napi;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use prost_reflect::{Cardinality, DescriptorPool, FieldDescriptor, Kind, MessageDescriptor};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct FieldOption {
|
||||||
|
pub label: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct ValidationRule {
|
||||||
|
pub rule_type: String,
|
||||||
|
pub arg: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct NetworkConfigSchema {
|
||||||
|
pub node_kind: String,
|
||||||
|
pub name: String,
|
||||||
|
pub field_number: i32,
|
||||||
|
pub type_name: Option<String>,
|
||||||
|
pub semantic_type: Option<String>,
|
||||||
|
pub value_kind: String,
|
||||||
|
pub is_list: bool,
|
||||||
|
pub required: bool,
|
||||||
|
pub default_value_text: Option<String>,
|
||||||
|
pub enum_options: Vec<FieldOption>,
|
||||||
|
pub validations: Vec<ValidationRule>,
|
||||||
|
pub children: Vec<NetworkConfigSchema>,
|
||||||
|
pub definitions: Vec<NetworkConfigSchema>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct ConfigFieldMapping {
|
||||||
|
pub field_name: String,
|
||||||
|
pub field_number: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static DESCRIPTOR_POOL: Lazy<DescriptorPool> = Lazy::new(|| {
|
||||||
|
DescriptorPool::decode(ALL_DESCRIPTOR_BYTES)
|
||||||
|
.expect("easytier descriptor pool should decode from embedded protobuf descriptors")
|
||||||
|
});
|
||||||
|
|
||||||
|
const NETWORK_CONFIG_MESSAGE_NAME: &str = "api.manage.NetworkConfig";
|
||||||
|
|
||||||
|
fn descriptor_pool() -> &'static DescriptorPool {
|
||||||
|
&DESCRIPTOR_POOL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network_config_descriptor() -> MessageDescriptor {
|
||||||
|
descriptor_pool()
|
||||||
|
.get_message_by_name(NETWORK_CONFIG_MESSAGE_NAME)
|
||||||
|
.expect("api.manage.NetworkConfig descriptor should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_default_value_text(field: &FieldDescriptor) -> Option<String> {
|
||||||
|
if field.is_list() || field.is_map() {
|
||||||
|
return Some("[]".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
match field.kind() {
|
||||||
|
Kind::Bool => Some("false".to_string()),
|
||||||
|
Kind::String => Some("\"\"".to_string()),
|
||||||
|
Kind::Bytes => Some("\"\"".to_string()),
|
||||||
|
Kind::Int32
|
||||||
|
| Kind::Sint32
|
||||||
|
| Kind::Sfixed32
|
||||||
|
| Kind::Int64
|
||||||
|
| Kind::Sint64
|
||||||
|
| Kind::Sfixed64
|
||||||
|
| Kind::Uint32
|
||||||
|
| Kind::Fixed32
|
||||||
|
| Kind::Uint64
|
||||||
|
| Kind::Fixed64
|
||||||
|
| Kind::Float
|
||||||
|
| Kind::Double => Some("0".to_string()),
|
||||||
|
Kind::Enum(enum_desc) => enum_desc
|
||||||
|
.get_value(0)
|
||||||
|
.map(|value| value.number().to_string()),
|
||||||
|
Kind::Message(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_type_name(field: &FieldDescriptor) -> Option<String> {
|
||||||
|
match field.kind() {
|
||||||
|
Kind::Enum(enum_desc) => Some(enum_desc.full_name().to_string()),
|
||||||
|
Kind::Message(message_desc) => Some(message_desc.full_name().to_string()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_semantic_type(field: &FieldDescriptor) -> Option<String> {
|
||||||
|
match field.name() {
|
||||||
|
"virtual_ipv4" => Some("cidr_ip".to_string()),
|
||||||
|
"network_length" => Some("cidr_mask".to_string()),
|
||||||
|
"peer_urls" => Some("peer[]".to_string()),
|
||||||
|
"proxy_cidrs" => Some("cidr[]".to_string()),
|
||||||
|
"listener_urls" => Some("listener[]".to_string()),
|
||||||
|
"routes" => Some("route[]".to_string()),
|
||||||
|
"exit_nodes" => Some("ip[]".to_string()),
|
||||||
|
"relay_network_whitelist" => Some("network_name[]".to_string()),
|
||||||
|
"mapped_listeners" => Some("mapped_listener[]".to_string()),
|
||||||
|
"port_forwards" => Some("port_forward[]".to_string()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum_options(kind: Kind) -> Vec<FieldOption> {
|
||||||
|
match kind {
|
||||||
|
Kind::Enum(enum_desc) => enum_desc
|
||||||
|
.values()
|
||||||
|
.map(|value| FieldOption {
|
||||||
|
label: value.name().to_string(),
|
||||||
|
value: value.number().to_string(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_expose_field(field: &FieldDescriptor) -> bool {
|
||||||
|
match field.containing_oneof() {
|
||||||
|
Some(_) => field
|
||||||
|
.field_descriptor_proto()
|
||||||
|
.proto3_optional
|
||||||
|
.unwrap_or(false),
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_validations(field: &FieldDescriptor) -> Vec<ValidationRule> {
|
||||||
|
if field.cardinality() == Cardinality::Required {
|
||||||
|
return vec![ValidationRule {
|
||||||
|
rule_type: "required".to_string(),
|
||||||
|
arg: String::new(),
|
||||||
|
message: format!("{} is required", field.name()),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind_to_value_kind(field: &FieldDescriptor) -> String {
|
||||||
|
if field.is_map() {
|
||||||
|
return "object".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
match field.kind() {
|
||||||
|
Kind::Bool => "boolean".to_string(),
|
||||||
|
Kind::String | Kind::Bytes => "string".to_string(),
|
||||||
|
Kind::Int32
|
||||||
|
| Kind::Sint32
|
||||||
|
| Kind::Sfixed32
|
||||||
|
| Kind::Int64
|
||||||
|
| Kind::Sint64
|
||||||
|
| Kind::Sfixed64
|
||||||
|
| Kind::Uint32
|
||||||
|
| Kind::Fixed32
|
||||||
|
| Kind::Uint64
|
||||||
|
| Kind::Fixed64
|
||||||
|
| Kind::Float
|
||||||
|
| Kind::Double => "number".to_string(),
|
||||||
|
Kind::Enum(_) => "enum".to_string(),
|
||||||
|
Kind::Message(_) => "object".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_node(
|
||||||
|
node_kind: &str,
|
||||||
|
name: String,
|
||||||
|
field_number: i32,
|
||||||
|
type_name: Option<String>,
|
||||||
|
semantic_type: Option<String>,
|
||||||
|
value_kind: String,
|
||||||
|
is_list: bool,
|
||||||
|
required: bool,
|
||||||
|
default_value_text: Option<String>,
|
||||||
|
enum_options: Vec<FieldOption>,
|
||||||
|
validations: Vec<ValidationRule>,
|
||||||
|
children: Vec<NetworkConfigSchema>,
|
||||||
|
definitions: Vec<NetworkConfigSchema>,
|
||||||
|
) -> NetworkConfigSchema {
|
||||||
|
NetworkConfigSchema {
|
||||||
|
node_kind: node_kind.to_string(),
|
||||||
|
name,
|
||||||
|
field_number,
|
||||||
|
type_name,
|
||||||
|
semantic_type,
|
||||||
|
value_kind,
|
||||||
|
is_list,
|
||||||
|
required,
|
||||||
|
default_value_text,
|
||||||
|
enum_options,
|
||||||
|
validations,
|
||||||
|
children,
|
||||||
|
definitions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_map_entry_node(message_desc: &MessageDescriptor) -> NetworkConfigSchema {
|
||||||
|
let key_field = message_desc.map_entry_key_field();
|
||||||
|
let value_field = message_desc.map_entry_value_field();
|
||||||
|
|
||||||
|
build_node(
|
||||||
|
"object",
|
||||||
|
message_desc.name().to_string(),
|
||||||
|
0,
|
||||||
|
Some(message_desc.full_name().to_string()),
|
||||||
|
None,
|
||||||
|
"object".to_string(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
vec![
|
||||||
|
build_schema_field_node(&key_field),
|
||||||
|
build_schema_field_node(&value_field),
|
||||||
|
],
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_children(field: &FieldDescriptor) -> Vec<NetworkConfigSchema> {
|
||||||
|
if field.is_map() {
|
||||||
|
if let Kind::Message(message_desc) = field.kind() {
|
||||||
|
return vec![build_map_entry_node(&message_desc)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match field.kind() {
|
||||||
|
Kind::Message(message_desc) => build_message_children(&message_desc),
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_message_children(message_desc: &MessageDescriptor) -> Vec<NetworkConfigSchema> {
|
||||||
|
message_desc
|
||||||
|
.fields()
|
||||||
|
.filter(should_expose_field)
|
||||||
|
.map(|field| build_schema_field_node(&field))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_schema_field_node(field: &FieldDescriptor) -> NetworkConfigSchema {
|
||||||
|
build_node(
|
||||||
|
"field",
|
||||||
|
field.name().to_string(),
|
||||||
|
field.number() as i32,
|
||||||
|
field_type_name(field),
|
||||||
|
field_semantic_type(field),
|
||||||
|
kind_to_value_kind(field),
|
||||||
|
field.is_list() || field.is_map(),
|
||||||
|
field.cardinality() == Cardinality::Required,
|
||||||
|
field_default_value_text(field),
|
||||||
|
enum_options(field.kind()),
|
||||||
|
build_validations(field),
|
||||||
|
field_children(field),
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_definitions() -> Vec<NetworkConfigSchema> {
|
||||||
|
let mut definitions = Vec::new();
|
||||||
|
|
||||||
|
for message_desc in descriptor_pool().all_messages() {
|
||||||
|
let full_name = message_desc.full_name();
|
||||||
|
if full_name == NETWORK_CONFIG_MESSAGE_NAME || message_desc.is_map_entry() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
definitions.push(build_node(
|
||||||
|
"object",
|
||||||
|
full_name.to_string(),
|
||||||
|
0,
|
||||||
|
Some(full_name.to_string()),
|
||||||
|
None,
|
||||||
|
"object".to_string(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
build_message_children(&message_desc),
|
||||||
|
Vec::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for enum_desc in descriptor_pool().all_enums() {
|
||||||
|
definitions.push(build_node(
|
||||||
|
"enum",
|
||||||
|
enum_desc.full_name().to_string(),
|
||||||
|
0,
|
||||||
|
Some(enum_desc.full_name().to_string()),
|
||||||
|
None,
|
||||||
|
"enum".to_string(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
enum_options(Kind::Enum(enum_desc.clone())),
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
definitions.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
definitions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_network_config_schema() -> NetworkConfigSchema {
|
||||||
|
let network_config = network_config_descriptor();
|
||||||
|
build_node(
|
||||||
|
"schema",
|
||||||
|
network_config.name().to_string(),
|
||||||
|
0,
|
||||||
|
Some(network_config.full_name().to_string()),
|
||||||
|
None,
|
||||||
|
"object".to_string(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
Vec::new(),
|
||||||
|
Vec::new(),
|
||||||
|
build_message_children(&network_config),
|
||||||
|
collect_definitions(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_network_config_field_mappings() -> Vec<ConfigFieldMapping> {
|
||||||
|
network_config_descriptor()
|
||||||
|
.fields()
|
||||||
|
.filter(should_expose_field)
|
||||||
|
.map(|field| ConfigFieldMapping {
|
||||||
|
field_name: field.name().to_string(),
|
||||||
|
field_number: field.number() as i32,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_network_config_schema() -> NetworkConfigSchema {
|
||||||
|
build_network_config_schema()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_network_config_field_mappings() -> Vec<ConfigFieldMapping> {
|
||||||
|
build_network_config_field_mappings()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn schema_is_exposed_as_single_tree_type() {
|
||||||
|
let schema = get_network_config_schema();
|
||||||
|
assert_eq!(schema.node_kind, "schema");
|
||||||
|
assert_eq!(schema.name, "NetworkConfig");
|
||||||
|
assert_eq!(
|
||||||
|
schema.type_name.as_deref(),
|
||||||
|
Some("api.manage.NetworkConfig")
|
||||||
|
);
|
||||||
|
|
||||||
|
let virtual_ipv4 = schema
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.find(|field| field.name == "virtual_ipv4")
|
||||||
|
.expect("virtual_ipv4 field");
|
||||||
|
assert_eq!(virtual_ipv4.semantic_type.as_deref(), Some("cidr_ip"));
|
||||||
|
|
||||||
|
let secure_mode = schema
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.find(|field| field.name == "secure_mode")
|
||||||
|
.expect("secure_mode field");
|
||||||
|
assert!(
|
||||||
|
secure_mode
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.any(|field| field.name == "enabled")
|
||||||
|
);
|
||||||
|
|
||||||
|
let secure_mode_definition = schema
|
||||||
|
.definitions
|
||||||
|
.iter()
|
||||||
|
.find(|definition| definition.name == "common.SecureModeConfig")
|
||||||
|
.expect("secure mode definition");
|
||||||
|
assert!(
|
||||||
|
secure_mode_definition
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.any(|field| field.name == "local_private_key")
|
||||||
|
);
|
||||||
|
|
||||||
|
let networking_method_definition = schema
|
||||||
|
.definitions
|
||||||
|
.iter()
|
||||||
|
.find(|definition| definition.name == "api.manage.NetworkingMethod")
|
||||||
|
.expect("networking method enum definition");
|
||||||
|
assert!(
|
||||||
|
networking_method_definition
|
||||||
|
.enum_options
|
||||||
|
.iter()
|
||||||
|
.any(|option| option.label == "PublicServer")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
use crate::config::repository::{get_config_record, save_config_record};
|
||||||
|
use crate::config::services::schema_service::get_network_config_field_mappings;
|
||||||
|
use crate::config::types::stored_config::SharedConfigLinkPayload;
|
||||||
|
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||||
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
use flate2::{Compression, read::ZlibDecoder, write::ZlibEncoder};
|
||||||
|
use gethostname::gethostname;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use url::Url;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
const SHARE_LINK_HOST: &str = "easytier.cn";
|
||||||
|
const SHARE_LINK_PATH: &str = "/comp_cfg";
|
||||||
|
|
||||||
|
fn field_name_to_id_map() -> HashMap<String, String> {
|
||||||
|
get_network_config_field_mappings()
|
||||||
|
.into_iter()
|
||||||
|
.map(|mapping| (mapping.field_name, mapping.field_number.to_string()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_id_to_name_map() -> HashMap<String, String> {
|
||||||
|
get_network_config_field_mappings()
|
||||||
|
.into_iter()
|
||||||
|
.map(|mapping| (mapping.field_number.to_string(), mapping.field_name))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prune_empty(value: &serde_json::Value) -> Option<serde_json::Value> {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => None,
|
||||||
|
serde_json::Value::Array(values) if values.is_empty() => None,
|
||||||
|
_ => Some(value.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_config_json(config: &NetworkConfig) -> Result<String, String> {
|
||||||
|
let field_name_to_id = field_name_to_id_map();
|
||||||
|
let raw = serde_json::to_value(config).map_err(|err| err.to_string())?;
|
||||||
|
let mut mapped = serde_json::Map::new();
|
||||||
|
|
||||||
|
for (key, value) in raw.as_object().cloned().unwrap_or_default() {
|
||||||
|
let Some(value) = prune_empty(&value) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mapped_key = field_name_to_id.get(&key).cloned().unwrap_or(key);
|
||||||
|
mapped.insert(mapped_key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serde_json::to_string(&mapped).map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmap_config_json(raw: &str) -> Result<NetworkConfig, String> {
|
||||||
|
let field_id_to_name = field_id_to_name_map();
|
||||||
|
let value = serde_json::from_str::<serde_json::Value>(raw).map_err(|err| err.to_string())?;
|
||||||
|
let mut mapped = serde_json::Map::new();
|
||||||
|
for (key, value) in value.as_object().cloned().unwrap_or_default() {
|
||||||
|
let field_name = field_id_to_name.get(&key).cloned().unwrap_or(key);
|
||||||
|
mapped.insert(field_name, value);
|
||||||
|
}
|
||||||
|
serde_json::from_value(serde_json::Value::Object(mapped)).map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress_to_base64url(raw: &str) -> Result<String, String> {
|
||||||
|
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());
|
||||||
|
encoder
|
||||||
|
.write_all(raw.as_bytes())
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let compressed = encoder.finish().map_err(|err| err.to_string())?;
|
||||||
|
Ok(URL_SAFE_NO_PAD.encode(compressed))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompress_from_base64url(raw: &str) -> Result<String, String> {
|
||||||
|
let compressed = URL_SAFE_NO_PAD.decode(raw).map_err(|err| err.to_string())?;
|
||||||
|
let mut decoder = ZlibDecoder::new(compressed.as_slice());
|
||||||
|
let mut out = String::new();
|
||||||
|
decoder
|
||||||
|
.read_to_string(&mut out)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_config_share_link(
|
||||||
|
config_id: &str,
|
||||||
|
display_name: Option<String>,
|
||||||
|
only_start: bool,
|
||||||
|
) -> Option<String> {
|
||||||
|
let record = get_config_record(config_id)?;
|
||||||
|
let config = serde_json::from_str::<NetworkConfig>(&record.config_json).ok()?;
|
||||||
|
let mapped_json = map_config_json(&config).ok()?;
|
||||||
|
let compressed = compress_to_base64url(&mapped_json).ok()?;
|
||||||
|
let final_name = display_name
|
||||||
|
.or(Some(record.meta.display_name))
|
||||||
|
.filter(|name| !name.is_empty());
|
||||||
|
|
||||||
|
let mut url = Url::parse(&format!("https://{SHARE_LINK_HOST}{SHARE_LINK_PATH}")).ok()?;
|
||||||
|
url.query_pairs_mut().append_pair("cfg", &compressed);
|
||||||
|
if let Some(name) = final_name {
|
||||||
|
url.query_pairs_mut().append_pair("name", &name);
|
||||||
|
}
|
||||||
|
if only_start {
|
||||||
|
url.query_pairs_mut().append_pair("only_start", "true");
|
||||||
|
}
|
||||||
|
Some(url.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_config_share_link(share_link: &str) -> Option<SharedConfigLinkPayload> {
|
||||||
|
let url = Url::parse(share_link).ok()?;
|
||||||
|
if url.host_str()? != SHARE_LINK_HOST || url.path() != SHARE_LINK_PATH {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cfg = url
|
||||||
|
.query_pairs()
|
||||||
|
.find(|(key, _)| key == "cfg")?
|
||||||
|
.1
|
||||||
|
.to_string();
|
||||||
|
let mapped_json = decompress_from_base64url(&cfg).ok()?;
|
||||||
|
let mut config = unmap_config_json(&mapped_json).ok()?;
|
||||||
|
config.instance_id = Some(Uuid::new_v4().to_string());
|
||||||
|
let hostname = gethostname().to_string_lossy().to_string();
|
||||||
|
if !hostname.is_empty() {
|
||||||
|
config.hostname = Some(hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_json = serde_json::to_string(&config).ok()?;
|
||||||
|
let display_name = url
|
||||||
|
.query_pairs()
|
||||||
|
.find(|(key, _)| key == "name")
|
||||||
|
.map(|(_, value)| value.to_string())
|
||||||
|
.filter(|name| !name.is_empty());
|
||||||
|
let only_start = url
|
||||||
|
.query_pairs()
|
||||||
|
.find(|(key, _)| key == "only_start")
|
||||||
|
.map(|(_, value)| value == "true")
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
Some(SharedConfigLinkPayload {
|
||||||
|
config_json,
|
||||||
|
display_name,
|
||||||
|
only_start,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_config_share_link(
|
||||||
|
share_link: &str,
|
||||||
|
display_name_override: Option<String>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let payload = parse_config_share_link(share_link)?;
|
||||||
|
let config = serde_json::from_str::<NetworkConfig>(&payload.config_json).ok()?;
|
||||||
|
let config_id = config.instance_id.clone()?;
|
||||||
|
let display_name = display_name_override
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.or(payload.display_name)
|
||||||
|
.unwrap_or_else(|| config_id.clone());
|
||||||
|
|
||||||
|
save_config_record(config_id.clone(), display_name, payload.config_json)?;
|
||||||
|
Some(config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::config_repo::{create_config_record, init_config_store};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
fn test_root() -> String {
|
||||||
|
let unique = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos();
|
||||||
|
std::env::temp_dir()
|
||||||
|
.join(format!("easytier_ohrs_share_test_{unique}"))
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn share_link_roundtrip_works() {
|
||||||
|
assert!(init_config_store(test_root()));
|
||||||
|
create_config_record("cfg-share".to_string(), "share-demo".to_string())
|
||||||
|
.expect("create config");
|
||||||
|
|
||||||
|
let link = build_config_share_link("cfg-share", None, true).expect("share link");
|
||||||
|
let payload = parse_config_share_link(&link).expect("parse link");
|
||||||
|
let config =
|
||||||
|
serde_json::from_str::<NetworkConfig>(&payload.config_json).expect("config json");
|
||||||
|
|
||||||
|
assert!(payload.only_start);
|
||||||
|
assert_eq!(payload.display_name.as_deref(), Some("share-demo"));
|
||||||
|
assert_ne!(config.instance_id.as_deref(), Some("cfg-share"));
|
||||||
|
|
||||||
|
let imported_id = import_config_share_link(&link, None).expect("import link");
|
||||||
|
assert_ne!(imported_id, "cfg-share");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
use crate::config::types::stored_config::{StoredConfigList, StoredConfigMeta};
|
||||||
|
use ohos_hilog_binding::{hilog_debug, hilog_error};
|
||||||
|
use rusqlite::{Connection, OptionalExtension, params};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
static CONFIG_DB_PATH: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||||
|
const CONFIG_DB_FILE_NAME: &str = "easytier-config-store.db";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct StoredConfigMetaRecord {
|
||||||
|
config_id: String,
|
||||||
|
display_name: String,
|
||||||
|
created_at: String,
|
||||||
|
updated_at: String,
|
||||||
|
favorite: bool,
|
||||||
|
temporary: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn now_ts_string() -> String {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map(|d| d.as_secs().to_string())
|
||||||
|
.unwrap_or_else(|_| "0".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn db_file_path() -> Option<PathBuf> {
|
||||||
|
CONFIG_DB_PATH
|
||||||
|
.lock()
|
||||||
|
.ok()
|
||||||
|
.and_then(|guard| guard.as_ref().cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_schema(conn: &Connection) -> rusqlite::Result<()> {
|
||||||
|
conn.execute_batch(
|
||||||
|
"PRAGMA foreign_keys = ON;
|
||||||
|
CREATE TABLE IF NOT EXISTS stored_configs (
|
||||||
|
config_id TEXT PRIMARY KEY,
|
||||||
|
display_name TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
favorite INTEGER NOT NULL DEFAULT 0,
|
||||||
|
temporary INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS stored_config_fields (
|
||||||
|
config_id TEXT NOT NULL,
|
||||||
|
field_name TEXT NOT NULL,
|
||||||
|
field_json TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (config_id, field_name),
|
||||||
|
FOREIGN KEY (config_id) REFERENCES stored_configs(config_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_stored_config_fields_config_id
|
||||||
|
ON stored_config_fields(config_id);",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_db() -> Option<Connection> {
|
||||||
|
let path = db_file_path()?;
|
||||||
|
let conn = match Connection::open(&path) {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] failed to open config db {}: {}", path.display(), e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = init_schema(&conn) {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] failed to initialize config db {}: {}",
|
||||||
|
path.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_to_meta(row: &rusqlite::Row<'_>) -> rusqlite::Result<StoredConfigMetaRecord> {
|
||||||
|
Ok(StoredConfigMetaRecord {
|
||||||
|
config_id: row.get(0)?,
|
||||||
|
display_name: row.get(1)?,
|
||||||
|
created_at: row.get(2)?,
|
||||||
|
updated_at: row.get(3)?,
|
||||||
|
favorite: row.get::<_, i64>(4)? != 0,
|
||||||
|
temporary: row.get::<_, i64>(5)? != 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_meta_record(conn: &Connection, config_id: &str) -> Option<StoredConfigMetaRecord> {
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT config_id, display_name, created_at, updated_at, favorite, temporary
|
||||||
|
FROM stored_configs WHERE config_id = ?1",
|
||||||
|
params![config_id],
|
||||||
|
row_to_meta,
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_meta(record: StoredConfigMetaRecord) -> StoredConfigMeta {
|
||||||
|
StoredConfigMeta {
|
||||||
|
config_id: record.config_id,
|
||||||
|
display_name: record.display_name,
|
||||||
|
created_at: record.created_at,
|
||||||
|
updated_at: record.updated_at,
|
||||||
|
favorite: record.favorite,
|
||||||
|
temporary: record.temporary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_config_meta_store(root_dir: String) -> bool {
|
||||||
|
let root = PathBuf::from(root_dir);
|
||||||
|
if let Err(e) = std::fs::create_dir_all(&root) {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] failed to create config db dir {}: {}",
|
||||||
|
root.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db_path = root.join(CONFIG_DB_FILE_NAME);
|
||||||
|
match CONFIG_DB_PATH.lock() {
|
||||||
|
Ok(mut guard) => {
|
||||||
|
*guard = Some(db_path.clone());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] failed to lock config db path: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if open_db().is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hilog_debug!("[Rust] initialized config db at {}", db_path.display());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_config_meta_entries() -> StoredConfigList {
|
||||||
|
let Some(conn) = open_db() else {
|
||||||
|
return StoredConfigList { configs: vec![] };
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stmt = match conn.prepare(
|
||||||
|
"SELECT config_id, display_name, created_at, updated_at, favorite, temporary
|
||||||
|
FROM stored_configs
|
||||||
|
ORDER BY updated_at DESC, display_name ASC",
|
||||||
|
) {
|
||||||
|
Ok(stmt) => stmt,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] failed to prepare list meta query: {}", e);
|
||||||
|
return StoredConfigList { configs: vec![] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rows = match stmt.query_map([], row_to_meta) {
|
||||||
|
Ok(rows) => rows,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] failed to list config meta rows: {}", e);
|
||||||
|
return StoredConfigList { configs: vec![] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let configs = rows.filter_map(Result::ok).map(to_meta).collect();
|
||||||
|
StoredConfigList { configs }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_display_name(config_id: &str) -> Option<String> {
|
||||||
|
let conn = open_db()?;
|
||||||
|
load_meta_record(&conn, config_id).map(|record| record.display_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_meta(config_id: &str) -> Option<StoredConfigMeta> {
|
||||||
|
let conn = open_db()?;
|
||||||
|
load_meta_record(&conn, config_id).map(to_meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upsert_config_meta(
|
||||||
|
config_id: String,
|
||||||
|
display_name: String,
|
||||||
|
favorite: bool,
|
||||||
|
temporary: bool,
|
||||||
|
) -> StoredConfigMeta {
|
||||||
|
let now = now_ts_string();
|
||||||
|
let Some(conn) = open_db() else {
|
||||||
|
return StoredConfigMeta {
|
||||||
|
config_id,
|
||||||
|
display_name,
|
||||||
|
created_at: now.clone(),
|
||||||
|
updated_at: now,
|
||||||
|
favorite,
|
||||||
|
temporary,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let created_at = load_meta_record(&conn, &config_id)
|
||||||
|
.map(|record| record.created_at)
|
||||||
|
.unwrap_or_else(|| now.clone());
|
||||||
|
|
||||||
|
if let Err(e) = conn.execute(
|
||||||
|
"INSERT INTO stored_configs (
|
||||||
|
config_id, display_name, created_at, updated_at, favorite, temporary
|
||||||
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||||
|
ON CONFLICT(config_id) DO UPDATE SET
|
||||||
|
display_name = excluded.display_name,
|
||||||
|
updated_at = excluded.updated_at,
|
||||||
|
favorite = excluded.favorite,
|
||||||
|
temporary = excluded.temporary",
|
||||||
|
params![
|
||||||
|
config_id,
|
||||||
|
display_name,
|
||||||
|
created_at,
|
||||||
|
now,
|
||||||
|
if favorite { 1 } else { 0 },
|
||||||
|
if temporary { 1 } else { 0 }
|
||||||
|
],
|
||||||
|
) {
|
||||||
|
hilog_error!("[Rust] failed to upsert config meta: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_config_meta(&config_id).unwrap_or(StoredConfigMeta {
|
||||||
|
config_id,
|
||||||
|
display_name,
|
||||||
|
created_at,
|
||||||
|
updated_at: now,
|
||||||
|
favorite,
|
||||||
|
temporary,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn upsert_config_meta_in_tx(
|
||||||
|
tx: &rusqlite::Transaction<'_>,
|
||||||
|
config_id: String,
|
||||||
|
display_name: String,
|
||||||
|
favorite: bool,
|
||||||
|
temporary: bool,
|
||||||
|
) -> Option<StoredConfigMeta> {
|
||||||
|
let now = now_ts_string();
|
||||||
|
let created_at = tx
|
||||||
|
.query_row(
|
||||||
|
"SELECT config_id, display_name, created_at, updated_at, favorite, temporary
|
||||||
|
FROM stored_configs WHERE config_id = ?1",
|
||||||
|
params![config_id],
|
||||||
|
row_to_meta,
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(|record| record.created_at)
|
||||||
|
.unwrap_or_else(|| now.clone());
|
||||||
|
|
||||||
|
tx.execute(
|
||||||
|
"INSERT INTO stored_configs (
|
||||||
|
config_id, display_name, created_at, updated_at, favorite, temporary
|
||||||
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
||||||
|
ON CONFLICT(config_id) DO UPDATE SET
|
||||||
|
display_name = excluded.display_name,
|
||||||
|
updated_at = excluded.updated_at,
|
||||||
|
favorite = excluded.favorite,
|
||||||
|
temporary = excluded.temporary",
|
||||||
|
params![
|
||||||
|
config_id,
|
||||||
|
display_name,
|
||||||
|
created_at,
|
||||||
|
now,
|
||||||
|
if favorite { 1 } else { 0 },
|
||||||
|
if temporary { 1 } else { 0 }
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
tx.query_row(
|
||||||
|
"SELECT config_id, display_name, created_at, updated_at, favorite, temporary
|
||||||
|
FROM stored_configs WHERE config_id = ?1",
|
||||||
|
params![config_id],
|
||||||
|
row_to_meta,
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(to_meta)
|
||||||
|
.or(Some(StoredConfigMeta {
|
||||||
|
config_id,
|
||||||
|
display_name,
|
||||||
|
created_at,
|
||||||
|
updated_at: now,
|
||||||
|
favorite,
|
||||||
|
temporary,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_config_display_name(
|
||||||
|
config_id: String,
|
||||||
|
display_name: String,
|
||||||
|
) -> Option<StoredConfigMeta> {
|
||||||
|
let conn = open_db()?;
|
||||||
|
let mut record = load_meta_record(&conn, &config_id)?;
|
||||||
|
record.display_name = display_name;
|
||||||
|
record.updated_at = now_ts_string();
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE stored_configs
|
||||||
|
SET display_name = ?2, updated_at = ?3
|
||||||
|
WHERE config_id = ?1",
|
||||||
|
params![config_id, record.display_name, record.updated_at],
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(to_meta(record))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_config_meta(config_id: &str) -> bool {
|
||||||
|
let Some(conn) = open_db() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute(
|
||||||
|
"DELETE FROM stored_configs WHERE config_id = ?1",
|
||||||
|
params![config_id],
|
||||||
|
) {
|
||||||
|
Ok(rows) => rows > 0,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] failed to delete config meta {}: {}", config_id, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod config_meta;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod stored_config;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use napi_derive_ohos::napi;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct StoredConfigMeta {
|
||||||
|
pub config_id: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub created_at: String,
|
||||||
|
pub updated_at: String,
|
||||||
|
pub favorite: bool,
|
||||||
|
pub temporary: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct StoredConfigRecord {
|
||||||
|
pub meta: StoredConfigMeta,
|
||||||
|
pub config_json: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct StoredConfigList {
|
||||||
|
pub configs: Vec<StoredConfigMeta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct ExportTomlResult {
|
||||||
|
pub toml_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct StoredConfigSummary {
|
||||||
|
pub config_id: String,
|
||||||
|
pub display_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct SharedConfigLinkPayload {
|
||||||
|
pub config_json: String,
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub only_start: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct LocalSocketSyncMessage {
|
||||||
|
pub message_type: String,
|
||||||
|
pub payload_json: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct KeyValuePair {
|
||||||
|
pub key: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
use super::{field_store, import_export, legacy_migration, validation};
|
||||||
|
use crate::config::storage::config_meta::{
|
||||||
|
delete_config_meta, get_config_meta, init_config_meta_store, list_config_meta_entries, open_db,
|
||||||
|
upsert_config_meta_in_tx,
|
||||||
|
};
|
||||||
|
use crate::config::types::stored_config::{ExportTomlResult, StoredConfigRecord};
|
||||||
|
use easytier::common::config::ConfigLoader;
|
||||||
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
use ohos_hilog_binding::{hilog_debug, hilog_error};
|
||||||
|
use rusqlite::params;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
static CONFIG_ROOT_DIR: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||||
|
pub(crate) const CONFIG_DIR_NAME: &str = "easytier-configs";
|
||||||
|
pub(crate) const KERNEL_SOCKET_FILE_NAME: &str = "easytier-kernel.sock";
|
||||||
|
|
||||||
|
pub(crate) fn config_root_dir() -> Option<PathBuf> {
|
||||||
|
CONFIG_ROOT_DIR
|
||||||
|
.lock()
|
||||||
|
.ok()
|
||||||
|
.and_then(|guard| guard.as_ref().cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn kernel_socket_path() -> Option<PathBuf> {
|
||||||
|
config_root_dir().map(|root| root.join(KERNEL_SOCKET_FILE_NAME))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn legacy_config_file_path(config_id: &str) -> Option<PathBuf> {
|
||||||
|
legacy_migration::legacy_config_file_path(&config_root_dir(), CONFIG_DIR_NAME, config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_config_store(root_dir: String) -> bool {
|
||||||
|
let root = PathBuf::from(root_dir);
|
||||||
|
let configs_dir = root.join(CONFIG_DIR_NAME);
|
||||||
|
if let Err(e) = std::fs::create_dir_all(&configs_dir) {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] failed to create config dir {}: {}",
|
||||||
|
configs_dir.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match CONFIG_ROOT_DIR.lock() {
|
||||||
|
Ok(mut guard) => {
|
||||||
|
*guard = Some(root.clone());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] failed to lock config root dir: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !init_config_meta_store(root.to_string_lossy().into_owned()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hilog_debug!(
|
||||||
|
"[Rust] initialized config repo at {}",
|
||||||
|
configs_dir.display()
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn migrate_legacy_file_if_needed(config_id: &str) -> Option<()> {
|
||||||
|
legacy_migration::migrate_legacy_file_if_needed(
|
||||||
|
&config_root_dir(),
|
||||||
|
CONFIG_DIR_NAME,
|
||||||
|
config_id,
|
||||||
|
save_config_record,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config_record(
|
||||||
|
config_id: String,
|
||||||
|
display_name: String,
|
||||||
|
config_json: String,
|
||||||
|
) -> Option<StoredConfigRecord> {
|
||||||
|
let config = match validation::validate_config_json(&config_json, config_id.clone()) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] save_config_record failed {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let normalized_json = match serde_json::to_string(&config) {
|
||||||
|
Ok(raw) => raw,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] failed to serialize normalized config {}: {}",
|
||||||
|
config_id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let fields = match validation::config_to_top_level_map(&config) {
|
||||||
|
Some(fields) => fields,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn = open_db()?;
|
||||||
|
let tx = conn.unchecked_transaction().ok()?;
|
||||||
|
let existing_meta = get_config_meta(&config_id);
|
||||||
|
let favorite = existing_meta
|
||||||
|
.as_ref()
|
||||||
|
.map(|meta| meta.favorite)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let temporary = existing_meta
|
||||||
|
.as_ref()
|
||||||
|
.map(|meta| meta.temporary)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let meta = upsert_config_meta_in_tx(&tx, config_id.clone(), display_name, favorite, temporary)?;
|
||||||
|
|
||||||
|
field_store::replace_config_fields(&tx, &config_id, fields)?;
|
||||||
|
|
||||||
|
tx.commit().ok()?;
|
||||||
|
|
||||||
|
if let Some(legacy_path) = legacy_config_file_path(&config_id) {
|
||||||
|
if legacy_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(legacy_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(StoredConfigRecord {
|
||||||
|
meta,
|
||||||
|
config_json: normalized_json,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config_json(config_id: &str) -> Option<String> {
|
||||||
|
migrate_legacy_file_if_needed(config_id)?;
|
||||||
|
let object = field_store::load_config_map_from_db(config_id)?;
|
||||||
|
serde_json::to_string(&Value::Object(object)).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_record(config_id: &str) -> Option<StoredConfigRecord> {
|
||||||
|
let config_json = load_config_json(config_id)?;
|
||||||
|
let meta = get_config_meta(config_id)?;
|
||||||
|
Some(StoredConfigRecord { meta, config_json })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_field_value(config_id: &str, field: &str) -> Option<String> {
|
||||||
|
migrate_legacy_file_if_needed(config_id)?;
|
||||||
|
let conn = open_db()?;
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT field_json FROM stored_config_fields
|
||||||
|
WHERE config_id = ?1 AND field_name = ?2",
|
||||||
|
params![config_id, field],
|
||||||
|
|row| row.get::<_, String>(0),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_config_field_value(config_id: &str, field: &str, json_value: &str) -> bool {
|
||||||
|
if field.contains('.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = match load_config_json(config_id) {
|
||||||
|
Some(raw) => raw,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
let mut value = match serde_json::from_str::<Value>(&raw) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
let new_field_value = match serde_json::from_str::<Value>(json_value) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
let object = match value.as_object_mut() {
|
||||||
|
Some(object) => object,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
object.insert(field.to_string(), new_field_value);
|
||||||
|
|
||||||
|
let normalized = match serde_json::to_string(&value) {
|
||||||
|
Ok(raw) => raw,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let display_name = get_config_meta(config_id)
|
||||||
|
.map(|meta| meta.display_name)
|
||||||
|
.unwrap_or_else(|| config_id.to_string());
|
||||||
|
|
||||||
|
save_config_record(config_id.to_string(), display_name, normalized).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_display_name(config_id: &str) -> Option<String> {
|
||||||
|
get_config_meta(config_id).map(|meta| meta.display_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_default_config_json() -> Option<String> {
|
||||||
|
crate::build_default_network_config_json().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_config_record(config_id: String, display_name: String) -> Option<StoredConfigRecord> {
|
||||||
|
let raw = get_default_config_json()?;
|
||||||
|
let mut config = serde_json::from_str::<NetworkConfig>(&raw).ok()?;
|
||||||
|
config.instance_id = Some(config_id.clone());
|
||||||
|
let normalized_json = serde_json::to_string(&config).ok()?;
|
||||||
|
save_config_record(config_id, display_name, normalized_json)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_kernel_with_config_id(config_id: &str) -> bool {
|
||||||
|
let raw = match load_config_json(config_id) {
|
||||||
|
Some(raw) => raw,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
crate::run_network_instance_from_json(&raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_config_meta_json() -> String {
|
||||||
|
serde_json::to_string(&list_config_meta_entries().configs).unwrap_or_else(|_| "[]".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_config_record(config_id: &str) -> bool {
|
||||||
|
if let Some(path) = legacy_config_file_path(config_id) {
|
||||||
|
if path.exists() {
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let conn = match open_db() {
|
||||||
|
Some(conn) => conn,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if let Err(e) = conn.execute(
|
||||||
|
"DELETE FROM stored_config_fields WHERE config_id = ?1",
|
||||||
|
params![config_id],
|
||||||
|
) {
|
||||||
|
hilog_error!("[Rust] failed to delete config fields {}: {}", config_id, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_config_meta(config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_config_toml(config_id: &str) -> Option<ExportTomlResult> {
|
||||||
|
let record = get_config_record(config_id)?;
|
||||||
|
import_export::export_config_toml_from_record(&record)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_toml_config(
|
||||||
|
toml_text: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
) -> Option<StoredConfigRecord> {
|
||||||
|
import_export::import_toml_to_record(toml_text, display_name, save_config_record)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rusqlite::params;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
fn test_root() -> String {
|
||||||
|
let unique = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos();
|
||||||
|
let dir = std::env::temp_dir().join(format!("easytier_ohrs_test_{}", unique));
|
||||||
|
dir.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_get_export_delete_roundtrip() {
|
||||||
|
let root = test_root();
|
||||||
|
assert!(init_config_store(root.clone()));
|
||||||
|
|
||||||
|
let config_json = crate::build_default_network_config_json().expect("default config");
|
||||||
|
let saved = save_config_record("cfg-1".to_string(), "test-config".to_string(), config_json)
|
||||||
|
.expect("save config");
|
||||||
|
|
||||||
|
assert_eq!(saved.meta.config_id, "cfg-1");
|
||||||
|
assert_eq!(saved.meta.display_name, "test-config");
|
||||||
|
|
||||||
|
let loaded = get_config_record("cfg-1").expect("load config");
|
||||||
|
assert_eq!(loaded.meta.display_name, "test-config");
|
||||||
|
assert!(loaded.config_json.contains("cfg-1"));
|
||||||
|
|
||||||
|
let legacy_json_path = PathBuf::from(&root)
|
||||||
|
.join(CONFIG_DIR_NAME)
|
||||||
|
.join("cfg-1.json");
|
||||||
|
assert!(
|
||||||
|
!legacy_json_path.exists(),
|
||||||
|
"config should no longer be persisted as a per-config json file"
|
||||||
|
);
|
||||||
|
|
||||||
|
let conn = open_db().expect("db should be open");
|
||||||
|
let field_count: i64 = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM stored_config_fields WHERE config_id = ?1",
|
||||||
|
params!["cfg-1"],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.expect("count config fields");
|
||||||
|
assert!(field_count > 0, "config fields should be stored in sqlite");
|
||||||
|
|
||||||
|
let exported = export_config_toml("cfg-1").expect("export toml");
|
||||||
|
assert!(exported.toml_text.contains("instance_id"));
|
||||||
|
|
||||||
|
assert!(delete_config_record("cfg-1"));
|
||||||
|
assert!(get_config_record("cfg-1").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_config_field_updates_only_requested_top_level_field() {
|
||||||
|
let root = test_root();
|
||||||
|
assert!(init_config_store(root));
|
||||||
|
|
||||||
|
let config_json = crate::build_default_network_config_json().expect("default config");
|
||||||
|
save_config_record(
|
||||||
|
"cfg-field".to_string(),
|
||||||
|
"field-config".to_string(),
|
||||||
|
config_json,
|
||||||
|
)
|
||||||
|
.expect("save config");
|
||||||
|
|
||||||
|
let before_network_name = get_config_field_value("cfg-field", "network_name");
|
||||||
|
let before_instance_id = get_config_field_value("cfg-field", "instance_id")
|
||||||
|
.expect("instance id field should exist");
|
||||||
|
|
||||||
|
assert!(set_config_field_value(
|
||||||
|
"cfg-field",
|
||||||
|
"network_name",
|
||||||
|
"\"changed-network\""
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_config_field_value("cfg-field", "network_name"),
|
||||||
|
Some("\"changed-network\"".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_config_field_value("cfg-field", "instance_id"),
|
||||||
|
Some(before_instance_id)
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
get_config_field_value("cfg-field", "network_name"),
|
||||||
|
before_network_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
use crate::config::storage::config_meta::{now_ts_string, open_db};
|
||||||
|
use ohos_hilog_binding::hilog_error;
|
||||||
|
use rusqlite::{Connection, params};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
pub(super) fn load_config_map_from_db(config_id: &str) -> Option<Map<String, Value>> {
|
||||||
|
let conn = open_db()?;
|
||||||
|
let mut stmt = conn
|
||||||
|
.prepare(
|
||||||
|
"SELECT field_name, field_json
|
||||||
|
FROM stored_config_fields
|
||||||
|
WHERE config_id = ?1",
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
let rows = stmt
|
||||||
|
.query_map(params![config_id], |row| {
|
||||||
|
let field_name: String = row.get(0)?;
|
||||||
|
let field_json: String = row.get(1)?;
|
||||||
|
Ok((field_name, field_json))
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let mut object = Map::new();
|
||||||
|
for row in rows {
|
||||||
|
let (field_name, field_json) = row.ok()?;
|
||||||
|
let value = serde_json::from_str::<Value>(&field_json).ok()?;
|
||||||
|
object.insert(field_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn replace_config_fields(
|
||||||
|
tx: &Connection,
|
||||||
|
config_id: &str,
|
||||||
|
fields: Map<String, Value>,
|
||||||
|
) -> Option<()> {
|
||||||
|
if let Err(e) = tx.execute(
|
||||||
|
"DELETE FROM stored_config_fields WHERE config_id = ?1",
|
||||||
|
params![config_id],
|
||||||
|
) {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] failed to clear existing config fields {}: {}",
|
||||||
|
config_id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (field_name, value) in fields {
|
||||||
|
let field_json = serde_json::to_string(&value).ok()?;
|
||||||
|
if let Err(e) = tx.execute(
|
||||||
|
"INSERT INTO stored_config_fields (config_id, field_name, field_json, updated_at)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
|
params![config_id, field_name, field_json, now_ts_string()],
|
||||||
|
) {
|
||||||
|
hilog_error!("[Rust] failed to persist config field {}: {}", config_id, e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use crate::config::types::stored_config::{ExportTomlResult, StoredConfigRecord};
|
||||||
|
use easytier::common::config::{ConfigLoader, TomlConfigLoader};
|
||||||
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
|
||||||
|
pub(super) fn export_config_toml_from_record(
|
||||||
|
record: &StoredConfigRecord,
|
||||||
|
) -> Option<ExportTomlResult> {
|
||||||
|
let config = serde_json::from_str::<NetworkConfig>(&record.config_json).ok()?;
|
||||||
|
let toml = config.gen_config().ok()?;
|
||||||
|
Some(ExportTomlResult {
|
||||||
|
toml_text: toml.dump(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn import_toml_to_record(
|
||||||
|
toml_text: String,
|
||||||
|
display_name: Option<String>,
|
||||||
|
save_config_record: impl Fn(String, String, String) -> Option<StoredConfigRecord>,
|
||||||
|
) -> Option<StoredConfigRecord> {
|
||||||
|
let config =
|
||||||
|
NetworkConfig::new_from_config(TomlConfigLoader::new_from_str(&toml_text).ok()?).ok()?;
|
||||||
|
|
||||||
|
let config_id = config.instance_id.clone()?;
|
||||||
|
let name_from_toml = toml_text
|
||||||
|
.lines()
|
||||||
|
.find_map(|line| {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if !trimmed.starts_with("instance_name") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
trimmed.split_once('=').map(|(_, value)| {
|
||||||
|
value
|
||||||
|
.trim()
|
||||||
|
.trim_matches('"')
|
||||||
|
.trim_matches('\'')
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.filter(|name| !name.is_empty());
|
||||||
|
|
||||||
|
let final_name = display_name
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.or(name_from_toml)
|
||||||
|
.unwrap_or_else(|| config_id.clone());
|
||||||
|
|
||||||
|
let config_json = serde_json::to_string(&config).ok()?;
|
||||||
|
save_config_record(config_id, final_name, config_json)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use crate::config::storage::config_meta::get_config_meta;
|
||||||
|
use ohos_hilog_binding::hilog_error;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub(super) fn legacy_config_file_path(
|
||||||
|
root_dir: &Option<PathBuf>,
|
||||||
|
config_dir_name: &str,
|
||||||
|
config_id: &str,
|
||||||
|
) -> Option<PathBuf> {
|
||||||
|
root_dir.as_ref().map(|root| {
|
||||||
|
root.join(config_dir_name)
|
||||||
|
.join(format!("{}.json", config_id))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn migrate_legacy_file_if_needed(
|
||||||
|
root_dir: &Option<PathBuf>,
|
||||||
|
config_dir_name: &str,
|
||||||
|
config_id: &str,
|
||||||
|
save_config_record: impl Fn(
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
) -> Option<crate::config::types::stored_config::StoredConfigRecord>,
|
||||||
|
) -> Option<()> {
|
||||||
|
let legacy_path = legacy_config_file_path(root_dir, config_dir_name, config_id)?;
|
||||||
|
if !legacy_path.exists() {
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = std::fs::read_to_string(&legacy_path).ok()?;
|
||||||
|
let display_name = get_config_meta(config_id)
|
||||||
|
.map(|meta| meta.display_name)
|
||||||
|
.unwrap_or_else(|| config_id.to_string());
|
||||||
|
save_config_record(config_id.to_string(), display_name, raw)?;
|
||||||
|
|
||||||
|
if let Err(e) = std::fs::remove_file(&legacy_path) {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] failed to remove legacy config file {}: {}",
|
||||||
|
legacy_path.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
pub(super) fn normalize_config_id(
|
||||||
|
mut config: NetworkConfig,
|
||||||
|
requested_id: String,
|
||||||
|
) -> Result<NetworkConfig, String> {
|
||||||
|
if requested_id.is_empty() {
|
||||||
|
return Err("config_id is required".to_string());
|
||||||
|
}
|
||||||
|
config.instance_id = Some(requested_id);
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn validate_config_json(
|
||||||
|
config_json: &str,
|
||||||
|
config_id: String,
|
||||||
|
) -> Result<NetworkConfig, String> {
|
||||||
|
let config = serde_json::from_str::<NetworkConfig>(config_json)
|
||||||
|
.map_err(|e| format!("parse config json failed: {}", e))?;
|
||||||
|
let config = normalize_config_id(config, config_id)?;
|
||||||
|
config
|
||||||
|
.gen_config()
|
||||||
|
.map_err(|e| format!("generate toml failed: {}", e))?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn config_to_top_level_map(config: &NetworkConfig) -> Option<Map<String, Value>> {
|
||||||
|
serde_json::to_value(config).ok()?.as_object().cloned()
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) mod config_api;
|
||||||
|
pub(crate) mod runtime_api;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use crate::config;
|
||||||
|
|
||||||
|
pub(crate) fn init_config_store(root_dir: String) -> bool {
|
||||||
|
config::repository::init_config_store(root_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn list_configs() -> String {
|
||||||
|
config::repository::list_config_meta_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_config(config_id: String, display_name: String, config_json: String) -> bool {
|
||||||
|
config::repository::save_config_record(config_id, display_name, config_json).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_config(config_id: String, display_name: String) -> bool {
|
||||||
|
config::repository::create_config_record(config_id, display_name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn delete_stored_config_meta(config_id: String) -> bool {
|
||||||
|
config::repository::delete_config_record(&config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_config(config_id: String) -> Option<String> {
|
||||||
|
config::repository::load_config_json(&config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_default_config() -> Option<String> {
|
||||||
|
config::repository::get_default_config_json()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_config_field(config_id: String, field: String) -> Option<String> {
|
||||||
|
config::repository::get_config_field_value(&config_id, &field)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_config_field(config_id: String, field: String, json_value: String) -> bool {
|
||||||
|
config::repository::set_config_field_value(&config_id, &field, &json_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn import_toml(toml_text: String, display_name: Option<String>) -> Option<String> {
|
||||||
|
config::repository::import_toml_config(toml_text, display_name)
|
||||||
|
.map(|record| record.meta.config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn export_toml(config_id: String) -> Option<String> {
|
||||||
|
config::repository::export_config_toml(&config_id).map(|ret| ret.toml_text)
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
use crate::config::repository::load_config_json;
|
||||||
|
use crate::config::storage::config_meta::get_config_display_name;
|
||||||
|
use crate::config::types::stored_config::KeyValuePair;
|
||||||
|
use crate::kernel_bridge::{
|
||||||
|
aggregate_requested_tun_routes, start_local_socket_server as start_local_socket_server_inner,
|
||||||
|
stop_local_socket_server as stop_local_socket_server_inner,
|
||||||
|
};
|
||||||
|
use crate::runtime::state::runtime_state::{
|
||||||
|
RuntimeAggregateState, TunAggregateState, clear_tun_attached, mark_tun_attached,
|
||||||
|
runtime_instance_from_running_info,
|
||||||
|
};
|
||||||
|
use crate::{ASYNC_RUNTIME, EASYTIER_VERSION, INSTANCE_MANAGER, WEB_CLIENTS};
|
||||||
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
use ohos_hilog_binding::{hilog_error, hilog_info};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub(crate) fn start_kernel(
|
||||||
|
config_id: String,
|
||||||
|
start_kernel_with_config_id: impl Fn(&str) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
start_kernel_with_config_id(&config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stop_kernel(
|
||||||
|
config_id: String,
|
||||||
|
stop_web_client: impl Fn(&str) -> bool,
|
||||||
|
parse_instance_uuid: impl Fn(&str) -> Option<uuid::Uuid>,
|
||||||
|
maybe_stop_local_socket_server: impl Fn(),
|
||||||
|
) -> bool {
|
||||||
|
clear_tun_attached(&config_id);
|
||||||
|
if stop_web_client(&config_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(instance_id) = parse_instance_uuid(&config_id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret = INSTANCE_MANAGER
|
||||||
|
.delete_network_instance(vec![instance_id])
|
||||||
|
.map(|_| true)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
hilog_error!("[Rust] stop_kernel failed {}: {}", config_id, err);
|
||||||
|
false
|
||||||
|
});
|
||||||
|
maybe_stop_local_socket_server();
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stop_network_instance(
|
||||||
|
config_ids: Vec<String>,
|
||||||
|
stop_kernel: impl Fn(String) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
let mut ok = true;
|
||||||
|
for config_id in config_ids {
|
||||||
|
ok = stop_kernel(config_id) && ok;
|
||||||
|
}
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn collect_network_infos() -> Vec<KeyValuePair> {
|
||||||
|
let infos = match INSTANCE_MANAGER.collect_network_infos_sync() {
|
||||||
|
Ok(infos) => infos,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] collect network infos failed {}", err);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
infos
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(key, value)| {
|
||||||
|
serde_json::to_string(&value)
|
||||||
|
.ok()
|
||||||
|
.map(|value_json| KeyValuePair {
|
||||||
|
key: key.to_string(),
|
||||||
|
value: value_json,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_tun_fd(
|
||||||
|
config_id: String,
|
||||||
|
fd: i32,
|
||||||
|
parse_instance_uuid: impl Fn(&str) -> Option<uuid::Uuid>,
|
||||||
|
) -> bool {
|
||||||
|
let Some(instance_id) = parse_instance_uuid(&config_id) else {
|
||||||
|
hilog_error!("[Rust] set_tun_fd invalid instance id: {}", config_id);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
INSTANCE_MANAGER
|
||||||
|
.set_tun_fd(&instance_id, fd)
|
||||||
|
.map(|_| {
|
||||||
|
mark_tun_attached(&config_id);
|
||||||
|
hilog_info!(
|
||||||
|
"[Rust] set_tun_fd success instance={} fd={} marked_attached=true",
|
||||||
|
config_id,
|
||||||
|
fd
|
||||||
|
);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
hilog_error!("[Rust] set_tun_fd failed {}: {}", config_id, err);
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_runtime_snapshot() -> RuntimeAggregateState {
|
||||||
|
get_runtime_snapshot_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_runtime_snapshot_inner() -> RuntimeAggregateState {
|
||||||
|
let infos = match INSTANCE_MANAGER.collect_network_infos_sync() {
|
||||||
|
Ok(infos) => infos,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] collect network infos failed {}", err);
|
||||||
|
return RuntimeAggregateState {
|
||||||
|
instances: vec![],
|
||||||
|
tun: TunAggregateState {
|
||||||
|
active: false,
|
||||||
|
attached_instance_ids: vec![],
|
||||||
|
aggregated_routes: vec![],
|
||||||
|
dns_servers: vec![],
|
||||||
|
need_rebuild: false,
|
||||||
|
},
|
||||||
|
running_instance_count: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instances = Vec::with_capacity(infos.len());
|
||||||
|
for (instance_uuid, info) in infos {
|
||||||
|
let config_id = instance_uuid.to_string();
|
||||||
|
let display_name = get_config_display_name(&config_id).unwrap_or_else(|| config_id.clone());
|
||||||
|
let config_json = load_config_json(&config_id);
|
||||||
|
let stored_config = config_json
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|raw| serde_json::from_str::<NetworkConfig>(raw).ok());
|
||||||
|
let magic_dns_enabled = stored_config
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|cfg| cfg.enable_magic_dns)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let need_exit_node = stored_config
|
||||||
|
.as_ref()
|
||||||
|
.map(|cfg| !cfg.exit_nodes.is_empty())
|
||||||
|
.unwrap_or(false);
|
||||||
|
instances.push(runtime_instance_from_running_info(
|
||||||
|
config_id,
|
||||||
|
display_name,
|
||||||
|
magic_dns_enabled,
|
||||||
|
need_exit_node,
|
||||||
|
info,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
instances.sort_by(|a, b| {
|
||||||
|
a.display_name
|
||||||
|
.cmp(&b.display_name)
|
||||||
|
.then_with(|| a.instance_id.cmp(&b.instance_id))
|
||||||
|
});
|
||||||
|
let attached_instance_ids = instances
|
||||||
|
.iter()
|
||||||
|
.filter(|instance| instance.tun_required)
|
||||||
|
.map(|instance| instance.instance_id.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let aggregated_routes = aggregate_requested_tun_routes(&instances);
|
||||||
|
let running_instance_count =
|
||||||
|
instances.iter().filter(|instance| instance.running).count() as i32;
|
||||||
|
let tun_active = !attached_instance_ids.is_empty();
|
||||||
|
|
||||||
|
RuntimeAggregateState {
|
||||||
|
instances,
|
||||||
|
tun: TunAggregateState {
|
||||||
|
active: tun_active,
|
||||||
|
attached_instance_ids,
|
||||||
|
aggregated_routes,
|
||||||
|
dns_servers: vec![],
|
||||||
|
need_rebuild: false,
|
||||||
|
},
|
||||||
|
running_instance_count,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
mod protocol;
|
||||||
|
mod routing;
|
||||||
|
mod socket_server;
|
||||||
|
|
||||||
|
pub(crate) use routing::aggregate_requested_tun_routes;
|
||||||
|
pub use socket_server::{start_local_socket_server, stop_local_socket_server};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use crate::config::types::stored_config::LocalSocketSyncMessage;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::io::{Error, ErrorKind, Write};
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct TunRequestPayload {
|
||||||
|
pub config_id: String,
|
||||||
|
pub instance_id: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub virtual_ipv4: Option<String>,
|
||||||
|
pub virtual_ipv4_cidr: Option<String>,
|
||||||
|
pub aggregated_routes: Vec<String>,
|
||||||
|
pub magic_dns_enabled: bool,
|
||||||
|
pub need_exit_node: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send_local_socket_message(
|
||||||
|
stream: &mut UnixStream,
|
||||||
|
message_type: &str,
|
||||||
|
payload_json: String,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let message = LocalSocketSyncMessage {
|
||||||
|
message_type: message_type.to_string(),
|
||||||
|
payload_json,
|
||||||
|
};
|
||||||
|
let mut raw = serde_json::to_vec(&message)
|
||||||
|
.map_err(|err| Error::new(ErrorKind::InvalidData, err.to_string()))?;
|
||||||
|
raw.push(b'\n');
|
||||||
|
stream.write_all(&raw)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn broadcast_local_socket_message(
|
||||||
|
clients: &mut Vec<UnixStream>,
|
||||||
|
message_type: &str,
|
||||||
|
payload_json: &str,
|
||||||
|
) -> bool {
|
||||||
|
let mut active_clients = Vec::with_capacity(clients.len());
|
||||||
|
let mut delivered = false;
|
||||||
|
for mut client in clients.drain(..) {
|
||||||
|
if send_local_socket_message(&mut client, message_type, payload_json.to_string()).is_ok() {
|
||||||
|
delivered = true;
|
||||||
|
active_clients.push(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*clients = active_clients;
|
||||||
|
delivered
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
use crate::config::repository::load_config_json;
|
||||||
|
use crate::runtime::state::runtime_state::RuntimeInstanceState;
|
||||||
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
use ipnet::IpNet;
|
||||||
|
use ohos_hilog_binding::hilog_debug;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
pub(crate) fn load_manual_routes(config_id: &str) -> Vec<String> {
|
||||||
|
load_config_json(config_id)
|
||||||
|
.and_then(|raw| serde_json::from_str::<NetworkConfig>(&raw).ok())
|
||||||
|
.map(|config| config.routes)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_route_cidr(route: &str) -> Option<String> {
|
||||||
|
route
|
||||||
|
.parse::<IpNet>()
|
||||||
|
.ok()
|
||||||
|
.map(|network| match network {
|
||||||
|
IpNet::V4(net) => net.trunc().to_string(),
|
||||||
|
IpNet::V6(net) => net.trunc().to_string(),
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
route.parse::<IpAddr>().ok().map(|addr| match addr {
|
||||||
|
IpAddr::V4(ip) => format!("{}/32", ip),
|
||||||
|
IpAddr::V6(ip) => format!("{}/128", ip),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simplify_routes(routes: Vec<String>) -> Vec<String> {
|
||||||
|
let mut parsed = routes
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|route| normalize_route_cidr(&route))
|
||||||
|
.filter_map(|route| route.parse::<IpNet>().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
parsed.sort_by(|left, right| {
|
||||||
|
left.prefix_len()
|
||||||
|
.cmp(&right.prefix_len())
|
||||||
|
.then_with(|| left.network().to_string().cmp(&right.network().to_string()))
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut simplified = Vec::<IpNet>::new();
|
||||||
|
'outer: for route in parsed {
|
||||||
|
for existing in &simplified {
|
||||||
|
if existing.contains(&route.network()) && existing.prefix_len() <= route.prefix_len() {
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
simplified.retain(|existing| {
|
||||||
|
!(route.contains(&existing.network()) && route.prefix_len() <= existing.prefix_len())
|
||||||
|
});
|
||||||
|
simplified.push(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
simplified
|
||||||
|
.into_iter()
|
||||||
|
.map(|route| route.to_string())
|
||||||
|
.filter(|route| seen.insert(route.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn aggregate_tun_routes(instance: &RuntimeInstanceState) -> Vec<String> {
|
||||||
|
let virtual_ipv4_cidr = instance
|
||||||
|
.my_node_info
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|info| info.virtual_ipv4_cidr.clone());
|
||||||
|
let manual_routes = load_manual_routes(&instance.config_id);
|
||||||
|
let proxy_cidrs = instance
|
||||||
|
.routes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|route| route.proxy_cidrs.iter().cloned())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut raw_routes = Vec::new();
|
||||||
|
|
||||||
|
if let Some(cidr) = virtual_ipv4_cidr.clone() {
|
||||||
|
raw_routes.push(cidr);
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_routes.extend(manual_routes.iter().cloned());
|
||||||
|
raw_routes.extend(proxy_cidrs.iter().cloned());
|
||||||
|
let aggregated_routes = simplify_routes(raw_routes);
|
||||||
|
hilog_debug!(
|
||||||
|
"[Rust] aggregate_tun_routes instance={} proxy_cidrs={:?} aggregated_routes={:?}",
|
||||||
|
instance.instance_id,
|
||||||
|
proxy_cidrs,
|
||||||
|
aggregated_routes
|
||||||
|
);
|
||||||
|
aggregated_routes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn aggregate_requested_tun_routes(instances: &[RuntimeInstanceState]) -> Vec<String> {
|
||||||
|
let mut aggregated_routes = Vec::new();
|
||||||
|
let mut seen_routes = HashSet::new();
|
||||||
|
for instance in instances.iter().filter(|instance| instance.tun_required) {
|
||||||
|
for route in aggregate_tun_routes(instance) {
|
||||||
|
if seen_routes.insert(route.clone()) {
|
||||||
|
aggregated_routes.push(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aggregated_routes
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
use super::protocol::{TunRequestPayload, broadcast_local_socket_message};
|
||||||
|
use crate::config::repository::kernel_socket_path;
|
||||||
|
use crate::get_runtime_snapshot_inner;
|
||||||
|
use crate::kernel_bridge::routing::aggregate_tun_routes;
|
||||||
|
use ohos_hilog_binding::{hilog_error, hilog_info};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::os::unix::net::{UnixListener, UnixStream};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
struct LocalSocketState {
|
||||||
|
stop_flag: std::sync::Arc<AtomicBool>,
|
||||||
|
socket_path: PathBuf,
|
||||||
|
worker: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOCAL_SOCKET_STATE: Lazy<Mutex<Option<LocalSocketState>>> = Lazy::new(|| Mutex::new(None));
|
||||||
|
|
||||||
|
pub fn start_local_socket_server() -> bool {
|
||||||
|
let socket_path = match kernel_socket_path() {
|
||||||
|
Some(path) => path,
|
||||||
|
None => {
|
||||||
|
hilog_error!("[Rust] kernel socket path unavailable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match LOCAL_SOCKET_STATE.lock() {
|
||||||
|
Ok(guard) if guard.is_some() => return true,
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] lock localsocket state failed: {}", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if socket_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(&socket_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let listener = match UnixListener::bind(&socket_path) {
|
||||||
|
Ok(listener) => listener,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] bind localsocket failed {}: {}",
|
||||||
|
socket_path.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(err) = listener.set_nonblocking(true) {
|
||||||
|
hilog_error!("[Rust] set localsocket nonblocking failed: {}", err);
|
||||||
|
let _ = std::fs::remove_file(&socket_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stop_flag = std::sync::Arc::new(AtomicBool::new(false));
|
||||||
|
let worker_stop_flag = stop_flag.clone();
|
||||||
|
let worker = thread::spawn(move || {
|
||||||
|
let mut last_snapshot_json = String::new();
|
||||||
|
let mut delivered_tun_requests = HashSet::new();
|
||||||
|
let mut last_tun_route_signatures = HashMap::<String, String>::new();
|
||||||
|
let mut clients = Vec::<UnixStream>::new();
|
||||||
|
|
||||||
|
while !worker_stop_flag.load(Ordering::Relaxed) {
|
||||||
|
let mut accepted_client = false;
|
||||||
|
loop {
|
||||||
|
match listener.accept() {
|
||||||
|
Ok((stream, _addr)) => {
|
||||||
|
accepted_client = true;
|
||||||
|
clients.push(stream);
|
||||||
|
}
|
||||||
|
Err(err) if err.kind() == ErrorKind::WouldBlock => break,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] accept localsocket failed: {}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let snapshot = get_runtime_snapshot_inner();
|
||||||
|
let snapshot_json = match serde_json::to_string(&snapshot) {
|
||||||
|
Ok(json) => json,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] serialize runtime snapshot failed: {}", err);
|
||||||
|
thread::sleep(Duration::from_millis(250));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if accepted_client || snapshot_json != last_snapshot_json {
|
||||||
|
let _ = broadcast_local_socket_message(
|
||||||
|
&mut clients,
|
||||||
|
"runtime_snapshot",
|
||||||
|
&snapshot_json,
|
||||||
|
);
|
||||||
|
last_snapshot_json = snapshot_json;
|
||||||
|
}
|
||||||
|
|
||||||
|
for instance in snapshot.instances.iter() {
|
||||||
|
if instance.running && instance.tun_required {
|
||||||
|
let virtual_ipv4 = instance
|
||||||
|
.my_node_info
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|info| info.virtual_ipv4.clone());
|
||||||
|
let virtual_ipv4_cidr = instance
|
||||||
|
.my_node_info
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|info| info.virtual_ipv4_cidr.clone());
|
||||||
|
if clients.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if virtual_ipv4.is_none() || virtual_ipv4_cidr.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let aggregated_routes = aggregate_tun_routes(instance);
|
||||||
|
let route_signature = serde_json::to_string(&aggregated_routes)
|
||||||
|
.unwrap_or_else(|_| "[]".to_string());
|
||||||
|
let should_send = !delivered_tun_requests.contains(&instance.instance_id)
|
||||||
|
|| last_tun_route_signatures
|
||||||
|
.get(&instance.instance_id)
|
||||||
|
.map(|value| value != &route_signature)
|
||||||
|
.unwrap_or(true);
|
||||||
|
if !should_send {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let payload = TunRequestPayload {
|
||||||
|
config_id: instance.config_id.clone(),
|
||||||
|
instance_id: instance.instance_id.clone(),
|
||||||
|
display_name: instance.display_name.clone(),
|
||||||
|
virtual_ipv4,
|
||||||
|
virtual_ipv4_cidr,
|
||||||
|
aggregated_routes,
|
||||||
|
magic_dns_enabled: instance.magic_dns_enabled,
|
||||||
|
need_exit_node: instance.need_exit_node,
|
||||||
|
};
|
||||||
|
let payload_json = match serde_json::to_string(&payload) {
|
||||||
|
Ok(json) => json,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] serialize tun request failed: {}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if broadcast_local_socket_message(&mut clients, "tun_request", &payload_json) {
|
||||||
|
delivered_tun_requests.insert(instance.instance_id.clone());
|
||||||
|
last_tun_route_signatures
|
||||||
|
.insert(instance.instance_id.clone(), route_signature);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delivered_tun_requests.remove(&instance.instance_id);
|
||||||
|
last_tun_route_signatures.remove(&instance.instance_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_millis(250));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match LOCAL_SOCKET_STATE.lock() {
|
||||||
|
Ok(mut guard) => {
|
||||||
|
*guard = Some(LocalSocketState {
|
||||||
|
stop_flag,
|
||||||
|
socket_path,
|
||||||
|
worker,
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] lock localsocket state failed: {}", err);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_local_socket_server() -> bool {
|
||||||
|
let state = match LOCAL_SOCKET_STATE.lock() {
|
||||||
|
Ok(mut guard) => guard.take(),
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] lock localsocket state failed: {}", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(state) = state {
|
||||||
|
state.stop_flag.store(true, Ordering::Relaxed);
|
||||||
|
let _ = state.worker.join();
|
||||||
|
let _ = std::fs::remove_file(state.socket_path);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
@@ -1,185 +1,482 @@
|
|||||||
mod native_log;
|
mod config;
|
||||||
|
mod exports;
|
||||||
|
mod kernel_bridge;
|
||||||
|
mod platform;
|
||||||
|
mod runtime;
|
||||||
|
|
||||||
|
use config::repository::{
|
||||||
|
create_config_record, delete_config_record, export_config_toml, get_config_field_value,
|
||||||
|
get_default_config_json, import_toml_config, init_config_store as init_repo_store,
|
||||||
|
list_config_meta_json, save_config_record, set_config_field_value, start_kernel_with_config_id,
|
||||||
|
};
|
||||||
|
use config::services::schema_service::{
|
||||||
|
ConfigFieldMapping, NetworkConfigSchema,
|
||||||
|
get_network_config_field_mappings as build_network_config_field_mappings,
|
||||||
|
get_network_config_schema as build_network_config_schema,
|
||||||
|
};
|
||||||
|
use config::services::share_link_service::{
|
||||||
|
build_config_share_link as build_config_share_link_inner,
|
||||||
|
import_config_share_link as import_config_share_link_inner,
|
||||||
|
parse_config_share_link as parse_config_share_link_inner,
|
||||||
|
};
|
||||||
|
use config::storage::config_meta::get_config_display_name;
|
||||||
|
use config::types::stored_config::{KeyValuePair, SharedConfigLinkPayload};
|
||||||
use easytier::common::config::{ConfigFileControl, ConfigLoader, TomlConfigLoader};
|
use easytier::common::config::{ConfigFileControl, ConfigLoader, TomlConfigLoader};
|
||||||
use easytier::common::constants::EASYTIER_VERSION;
|
use easytier::common::constants::EASYTIER_VERSION;
|
||||||
use easytier::instance_manager::NetworkInstanceManager;
|
use easytier::instance_manager::NetworkInstanceManager;
|
||||||
use easytier::proto::api::manage::NetworkConfig;
|
use easytier::proto::api::manage::NetworkConfig;
|
||||||
|
use easytier::proto::api::manage::NetworkingMethod;
|
||||||
|
use easytier::web_client::{WebClient, WebClientHooks, run_web_client};
|
||||||
|
use kernel_bridge::{
|
||||||
|
aggregate_requested_tun_routes, start_local_socket_server as start_local_socket_server_inner,
|
||||||
|
stop_local_socket_server as stop_local_socket_server_inner,
|
||||||
|
};
|
||||||
use napi_derive_ohos::napi;
|
use napi_derive_ohos::napi;
|
||||||
use ohos_hilog_binding::{hilog_debug, hilog_error};
|
use ohos_hilog_binding::{hilog_error, hilog_info};
|
||||||
|
use runtime::state::runtime_state::{
|
||||||
|
RuntimeAggregateState, TunAggregateState, clear_tun_attached, mark_tun_attached,
|
||||||
|
runtime_instance_from_running_info,
|
||||||
|
};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::format;
|
use std::format;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tokio::runtime::{Builder, Runtime};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
static INSTANCE_MANAGER: once_cell::sync::Lazy<NetworkInstanceManager> =
|
pub(crate) static INSTANCE_MANAGER: once_cell::sync::Lazy<Arc<NetworkInstanceManager>> =
|
||||||
once_cell::sync::Lazy::new(NetworkInstanceManager::new);
|
once_cell::sync::Lazy::new(|| Arc::new(NetworkInstanceManager::new()));
|
||||||
|
static ASYNC_RUNTIME: once_cell::sync::Lazy<Runtime> = once_cell::sync::Lazy::new(|| {
|
||||||
|
Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("tokio runtime for easytier-ohrs")
|
||||||
|
});
|
||||||
|
static WEB_CLIENTS: once_cell::sync::Lazy<Mutex<HashMap<String, ManagedWebClient>>> =
|
||||||
|
once_cell::sync::Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
#[napi(object)]
|
#[derive(Default)]
|
||||||
pub struct KeyValuePair {
|
struct TrackedWebClientHooks {
|
||||||
pub key: String,
|
instance_ids: Mutex<HashSet<Uuid>>,
|
||||||
pub value: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
struct ManagedWebClient {
|
||||||
pub fn easytier_version() -> String {
|
_client: WebClient,
|
||||||
EASYTIER_VERSION.to_string()
|
hooks: Arc<TrackedWebClientHooks>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[async_trait::async_trait]
|
||||||
pub fn set_tun_fd(inst_id: String, fd: i32) -> bool {
|
impl WebClientHooks for TrackedWebClientHooks {
|
||||||
match Uuid::try_parse(&inst_id) {
|
async fn post_run_network_instance(&self, id: &Uuid) -> Result<(), String> {
|
||||||
Ok(uuid) => match INSTANCE_MANAGER.set_tun_fd(&uuid, fd) {
|
self.instance_ids
|
||||||
Ok(_) => {
|
.lock()
|
||||||
hilog_debug!("[Rust] set tun fd {} to {}.", fd, inst_id);
|
.map_err(|err| err.to_string())?
|
||||||
true
|
.insert(*id);
|
||||||
}
|
Ok(())
|
||||||
Err(e) => {
|
}
|
||||||
hilog_error!("[Rust] cant set tun fd {} to {}. {}", fd, inst_id, e);
|
|
||||||
false
|
async fn post_remove_network_instances(&self, ids: &[Uuid]) -> Result<(), String> {
|
||||||
}
|
let mut guard = self.instance_ids.lock().map_err(|err| err.to_string())?;
|
||||||
},
|
for id in ids {
|
||||||
Err(e) => {
|
guard.remove(id);
|
||||||
hilog_error!("[Rust] cant covert {} to uuid. {}", inst_id, e);
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_config_server_config(config: &NetworkConfig) -> bool {
|
||||||
|
matches!(
|
||||||
|
NetworkingMethod::try_from(config.networking_method.unwrap_or_default())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
NetworkingMethod::PublicServer
|
||||||
|
) && config
|
||||||
|
.public_server_url
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|url| !url.trim().is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_web_client(config_id: &str) -> bool {
|
||||||
|
let managed = match WEB_CLIENTS.lock() {
|
||||||
|
Ok(mut guard) => guard.remove(config_id),
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] stop_web_client lock failed {}", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(managed) = managed else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let tracked_ids = managed
|
||||||
|
.hooks
|
||||||
|
.instance_ids
|
||||||
|
.lock()
|
||||||
|
.map(|guard| guard.iter().copied().collect::<Vec<_>>())
|
||||||
|
.unwrap_or_default();
|
||||||
|
drop(managed);
|
||||||
|
|
||||||
|
if tracked_ids.is_empty() {
|
||||||
|
maybe_stop_local_socket_server();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = INSTANCE_MANAGER
|
||||||
|
.delete_network_instance(tracked_ids)
|
||||||
|
.map(|_| true)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
hilog_error!(
|
||||||
|
"[Rust] stop config server instances failed {}: {}",
|
||||||
|
config_id,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
false
|
||||||
|
});
|
||||||
|
maybe_stop_local_socket_server();
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_local_socket_server_started() -> bool {
|
||||||
|
start_local_socket_server_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_stop_local_socket_server() {
|
||||||
|
let no_local_instances = INSTANCE_MANAGER.list_network_instance_ids().is_empty();
|
||||||
|
let no_web_clients = WEB_CLIENTS
|
||||||
|
.lock()
|
||||||
|
.map(|guard| guard.is_empty())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if no_local_instances && no_web_clients {
|
||||||
|
let _ = stop_local_socket_server_inner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_config_server_instance(config_id: &str, config: &NetworkConfig) -> bool {
|
||||||
|
if INSTANCE_MANAGER
|
||||||
|
.list_network_instance_ids()
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
hilog_error!("[Rust] there is a running instance!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(config_server_url) = config.public_server_url.clone() else {
|
||||||
|
hilog_error!("[Rust] public_server_url missing for config server mode");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let hooks = Arc::new(TrackedWebClientHooks::default());
|
||||||
|
let secure_mode = config
|
||||||
|
.secure_mode
|
||||||
|
.as_ref()
|
||||||
|
.map(|mode| mode.enabled)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let hostname = config.hostname.clone();
|
||||||
|
|
||||||
|
if !ensure_local_socket_server_started() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = ASYNC_RUNTIME.block_on(run_web_client(
|
||||||
|
&config_server_url,
|
||||||
|
None,
|
||||||
|
hostname,
|
||||||
|
secure_mode,
|
||||||
|
INSTANCE_MANAGER.clone(),
|
||||||
|
Some(hooks.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
let client = match client {
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] start config server failed {}", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match WEB_CLIENTS.lock() {
|
||||||
|
Ok(mut guard) => {
|
||||||
|
guard.insert(
|
||||||
|
config_id.to_string(),
|
||||||
|
ManagedWebClient {
|
||||||
|
_client: client,
|
||||||
|
hooks,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] store config server client failed {}", err);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
pub(crate) fn build_default_network_config_json() -> Result<String, String> {
|
||||||
pub fn default_network_config() -> String {
|
let config = NetworkConfig::new_from_config(TomlConfigLoader::default())
|
||||||
match NetworkConfig::new_from_config(TomlConfigLoader::default()) {
|
.map_err(|e| format!("default_network_config failed {}", e))?;
|
||||||
Ok(result) => serde_json::to_string(&result).unwrap_or_else(|e| format!("ERROR {}", e)),
|
serde_json::to_string(&config).map_err(|e| format!("default_network_config failed {}", e))
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] default_network_config failed {}", e);
|
|
||||||
format!("ERROR {}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
fn convert_toml_to_network_config_inner(toml_text: &str) -> Result<String, String> {
|
||||||
pub fn convert_toml_to_network_config(cfg_str: String) -> String {
|
let config = NetworkConfig::new_from_config(
|
||||||
match TomlConfigLoader::new_from_str(&cfg_str) {
|
TomlConfigLoader::new_from_str(toml_text).map_err(|e| e.to_string())?,
|
||||||
Ok(cfg) => match NetworkConfig::new_from_config(cfg) {
|
)
|
||||||
Ok(result) => serde_json::to_string(&result).unwrap_or_else(|e| format!("ERROR {}", e)),
|
.map_err(|e| e.to_string())?;
|
||||||
Err(e) => {
|
serde_json::to_string(&config).map_err(|e| e.to_string())
|
||||||
hilog_error!("[Rust] convert_toml_to_network_config failed {}", e);
|
|
||||||
format!("ERROR {}", e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] convert_toml_to_network_config failed {}", e);
|
|
||||||
format!("ERROR {}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
fn parse_network_config_inner(cfg_json: &str) -> bool {
|
||||||
pub fn parse_network_config(cfg_json: String) -> bool {
|
serde_json::from_str::<NetworkConfig>(cfg_json)
|
||||||
match serde_json::from_str::<NetworkConfig>(&cfg_json) {
|
.ok()
|
||||||
Ok(cfg) => match cfg.gen_config() {
|
.and_then(|cfg| cfg.gen_config().ok())
|
||||||
Ok(toml) => {
|
.is_some()
|
||||||
hilog_debug!("[Rust] Convert to Toml {}", toml.dump());
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] parse config failed {}", e);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] parse config failed {}", e);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
pub(crate) fn run_network_instance_from_json(cfg_json: &str) -> bool {
|
||||||
pub fn run_network_instance(cfg_json: String) -> bool {
|
let config = match serde_json::from_str::<NetworkConfig>(cfg_json) {
|
||||||
let cfg = match serde_json::from_str::<NetworkConfig>(&cfg_json) {
|
Ok(cfg) => cfg,
|
||||||
Ok(cfg) => match cfg.gen_config() {
|
|
||||||
Ok(toml) => toml,
|
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] parse config failed {}", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
hilog_error!("[Rust] parse config failed {}", e);
|
hilog_error!("[Rust] parse config failed {}", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if INSTANCE_MANAGER.list_network_instance_ids().len() > 0 {
|
if is_config_server_config(&config) {
|
||||||
|
let Some(config_id) = config.instance_id.as_deref() else {
|
||||||
|
hilog_error!("[Rust] config server config missing instance id");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return run_config_server_instance(config_id, &config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cfg = match config.gen_config() {
|
||||||
|
Ok(toml) => toml,
|
||||||
|
Err(e) => {
|
||||||
|
hilog_error!("[Rust] parse config failed {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !INSTANCE_MANAGER.list_network_instance_ids().is_empty() {
|
||||||
hilog_error!("[Rust] there is a running instance!");
|
hilog_error!("[Rust] there is a running instance!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ensure_local_socket_server_started() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let inst_id = cfg.get_id();
|
let inst_id = cfg.get_id();
|
||||||
if INSTANCE_MANAGER
|
if INSTANCE_MANAGER
|
||||||
.list_network_instance_ids()
|
.list_network_instance_ids()
|
||||||
.contains(&inst_id)
|
.contains(&inst_id)
|
||||||
{
|
{
|
||||||
|
hilog_error!("[Rust] instance {} already exists", inst_id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
INSTANCE_MANAGER
|
|
||||||
.run_network_instance(cfg, false, ConfigFileControl::STATIC_CONFIG)
|
|
||||||
.unwrap();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
match INSTANCE_MANAGER.run_network_instance(cfg, false, ConfigFileControl::STATIC_CONFIG) {
|
||||||
pub fn stop_network_instance(inst_names: Vec<String>) {
|
Ok(_) => true,
|
||||||
INSTANCE_MANAGER
|
Err(err) => {
|
||||||
.delete_network_instance(
|
hilog_error!("[Rust] start_kernel failed for {}: {}", inst_id, err);
|
||||||
inst_names
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|s| Uuid::parse_str(&s).ok())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
hilog_debug!("[Rust] stop_network_instance");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn collect_network_infos() -> Vec<KeyValuePair> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
match INSTANCE_MANAGER.collect_network_infos_sync() {
|
|
||||||
Ok(map) => {
|
|
||||||
for (uuid, info) in map.iter() {
|
|
||||||
// convert value to json string
|
|
||||||
let value = match serde_json::to_string(&info) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] failed to serialize instance {} info: {}", uuid, e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
result.push(KeyValuePair {
|
|
||||||
key: uuid.clone().to_string(),
|
|
||||||
value: value.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn collect_running_network() -> Vec<String> {
|
|
||||||
INSTANCE_MANAGER
|
|
||||||
.list_network_instance_ids()
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(|id| id.to_string())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn is_running_network(inst_id: String) -> bool {
|
|
||||||
match Uuid::try_parse(&inst_id) {
|
|
||||||
Ok(uuid) => INSTANCE_MANAGER.list_network_instance_ids().contains(&uuid),
|
|
||||||
Err(e) => {
|
|
||||||
hilog_error!("[Rust] cant covert {} to uuid. {}", inst_id, e);
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_instance_uuid(config_id: &str) -> Option<Uuid> {
|
||||||
|
match Uuid::parse_str(config_id) {
|
||||||
|
Ok(uuid) => Some(uuid),
|
||||||
|
Err(err) => {
|
||||||
|
hilog_error!("[Rust] invalid config_id {}: {}", config_id, err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn init_config_store(root_dir: String) -> bool {
|
||||||
|
exports::config_api::init_config_store(root_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn list_configs() -> String {
|
||||||
|
exports::config_api::list_configs()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_config_display_name_by_id(config_id: String) -> Option<String> {
|
||||||
|
get_config_display_name(&config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn save_config(config_id: String, display_name: String, config_json: String) -> bool {
|
||||||
|
exports::config_api::save_config(config_id, display_name, config_json)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn create_config(config_id: String, display_name: String) -> bool {
|
||||||
|
exports::config_api::create_config(config_id, display_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn rename_stored_config(config_id: String, display_name: String) -> bool {
|
||||||
|
config::storage::config_meta::set_config_display_name(config_id, display_name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn delete_stored_config_meta(config_id: String) -> bool {
|
||||||
|
exports::config_api::delete_stored_config_meta(config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_config(config_id: String) -> Option<String> {
|
||||||
|
exports::config_api::get_config(config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_default_config() -> Option<String> {
|
||||||
|
exports::config_api::get_default_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_config_field(config_id: String, field: String) -> Option<String> {
|
||||||
|
exports::config_api::get_config_field(config_id, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn set_config_field(config_id: String, field: String, json_value: String) -> bool {
|
||||||
|
exports::config_api::set_config_field(config_id, field, json_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn import_toml(toml_text: String, display_name: Option<String>) -> Option<String> {
|
||||||
|
exports::config_api::import_toml(toml_text, display_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn export_toml(config_id: String) -> Option<String> {
|
||||||
|
exports::config_api::export_toml(config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn start_kernel(config_id: String) -> bool {
|
||||||
|
exports::runtime_api::start_kernel(config_id, start_kernel_with_config_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn stop_kernel(config_id: String) -> bool {
|
||||||
|
exports::runtime_api::stop_kernel(
|
||||||
|
config_id,
|
||||||
|
stop_web_client,
|
||||||
|
parse_instance_uuid,
|
||||||
|
maybe_stop_local_socket_server,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn stop_network_instance(config_ids: Vec<String>) -> bool {
|
||||||
|
exports::runtime_api::stop_network_instance(config_ids, stop_kernel)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn easytier_version() -> String {
|
||||||
|
EASYTIER_VERSION.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn default_network_config() -> String {
|
||||||
|
get_default_config().unwrap_or_else(|| "{}".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn convert_toml_to_network_config(toml_text: String) -> String {
|
||||||
|
convert_toml_to_network_config_inner(&toml_text).unwrap_or_else(|err| format!("ERROR: {err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn parse_network_config(cfg_json: String) -> bool {
|
||||||
|
parse_network_config_inner(&cfg_json)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn run_network_instance(cfg_json: String) -> bool {
|
||||||
|
run_network_instance_from_json(&cfg_json)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn collect_network_infos() -> Vec<KeyValuePair> {
|
||||||
|
exports::runtime_api::collect_network_infos()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn set_tun_fd(config_id: String, fd: i32) -> bool {
|
||||||
|
exports::runtime_api::set_tun_fd(config_id, fd, parse_instance_uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_network_config_schema() -> NetworkConfigSchema {
|
||||||
|
build_network_config_schema()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_network_config_field_mappings() -> Vec<ConfigFieldMapping> {
|
||||||
|
build_network_config_field_mappings()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exported_plain_object_schema_contains_core_networkconfig_metadata() {
|
||||||
|
let schema = get_network_config_schema();
|
||||||
|
assert_eq!(schema.name, "NetworkConfig");
|
||||||
|
assert_eq!(schema.node_kind, "schema");
|
||||||
|
assert!(
|
||||||
|
schema
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.any(|field| field.name == "network_name")
|
||||||
|
);
|
||||||
|
let secure_mode = schema
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.find(|field| field.name == "secure_mode")
|
||||||
|
.expect("secure_mode field");
|
||||||
|
assert!(
|
||||||
|
secure_mode
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.any(|field| field.name == "enabled")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn get_runtime_snapshot() -> RuntimeAggregateState {
|
||||||
|
exports::runtime_api::get_runtime_snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_runtime_snapshot_inner() -> RuntimeAggregateState {
|
||||||
|
exports::runtime_api::get_runtime_snapshot_inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn build_config_share_link(config_id: String, only_start: Option<bool>) -> Option<String> {
|
||||||
|
build_config_share_link_inner(&config_id, None, only_start.unwrap_or(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn parse_config_share_link(share_link: String) -> Option<SharedConfigLinkPayload> {
|
||||||
|
parse_config_share_link_inner(&share_link)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn import_config_share_link(
|
||||||
|
share_link: String,
|
||||||
|
display_name_override: Option<String>,
|
||||||
|
) -> Option<String> {
|
||||||
|
import_config_share_link_inner(&share_link, display_name_override)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod logging;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod native_log;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod state;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod runtime_state;
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
use easytier::proto::{api, common};
|
||||||
|
use napi_derive_ohos::napi;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
static ATTACHED_TUN_INSTANCE_IDS: once_cell::sync::Lazy<Mutex<HashSet<String>>> =
|
||||||
|
once_cell::sync::Lazy::new(|| Mutex::new(HashSet::new()));
|
||||||
|
|
||||||
|
pub fn mark_tun_attached(instance_id: &str) {
|
||||||
|
if let Ok(mut guard) = ATTACHED_TUN_INSTANCE_IDS.lock() {
|
||||||
|
guard.insert(instance_id.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_tun_attached(instance_id: &str) {
|
||||||
|
if let Ok(mut guard) = ATTACHED_TUN_INSTANCE_IDS.lock() {
|
||||||
|
guard.remove(instance_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_tun_attached(instance_id: &str) -> bool {
|
||||||
|
ATTACHED_TUN_INSTANCE_IDS
|
||||||
|
.lock()
|
||||||
|
.map(|guard| guard.contains(instance_id))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct PeerConnStats {
|
||||||
|
pub rx_bytes: i64,
|
||||||
|
pub tx_bytes: i64,
|
||||||
|
pub rx_packets: i64,
|
||||||
|
pub tx_packets: i64,
|
||||||
|
pub latency_us: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct PeerConnInfo {
|
||||||
|
pub conn_id: String,
|
||||||
|
pub my_peer_id: i64,
|
||||||
|
pub peer_id: i64,
|
||||||
|
pub features: Vec<String>,
|
||||||
|
pub tunnel_type: Option<String>,
|
||||||
|
pub local_addr: Option<String>,
|
||||||
|
pub remote_addr: Option<String>,
|
||||||
|
pub resolved_remote_addr: Option<String>,
|
||||||
|
pub stats: Option<PeerConnStats>,
|
||||||
|
pub loss_rate: Option<f64>,
|
||||||
|
pub is_client: bool,
|
||||||
|
pub network_name: Option<String>,
|
||||||
|
pub is_closed: bool,
|
||||||
|
pub secure_auth_level: Option<i32>,
|
||||||
|
pub peer_identity_type: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct PeerInfo {
|
||||||
|
pub peer_id: i64,
|
||||||
|
pub default_conn_id: Option<String>,
|
||||||
|
pub directly_connected_conns: Vec<String>,
|
||||||
|
pub conns: Vec<PeerConnInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct RouteView {
|
||||||
|
pub peer_id: i64,
|
||||||
|
pub hostname: Option<String>,
|
||||||
|
pub ipv4: Option<String>,
|
||||||
|
pub ipv4_cidr: Option<String>,
|
||||||
|
pub ipv6_cidr: Option<String>,
|
||||||
|
pub proxy_cidrs: Vec<String>,
|
||||||
|
pub next_hop_peer_id: Option<i64>,
|
||||||
|
pub cost: Option<i32>,
|
||||||
|
pub path_latency: Option<i64>,
|
||||||
|
pub udp_nat_type: Option<i32>,
|
||||||
|
pub tcp_nat_type: Option<i32>,
|
||||||
|
pub inst_id: Option<String>,
|
||||||
|
pub version: Option<String>,
|
||||||
|
pub is_public_server: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct MyNodeInfo {
|
||||||
|
pub virtual_ipv4: Option<String>,
|
||||||
|
pub virtual_ipv4_cidr: Option<String>,
|
||||||
|
pub hostname: Option<String>,
|
||||||
|
pub version: Option<String>,
|
||||||
|
pub peer_id: Option<i64>,
|
||||||
|
pub listeners: Vec<String>,
|
||||||
|
pub vpn_portal_cfg: Option<String>,
|
||||||
|
pub udp_nat_type: Option<i32>,
|
||||||
|
pub tcp_nat_type: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct RuntimeInstanceState {
|
||||||
|
pub config_id: String,
|
||||||
|
pub instance_id: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub running: bool,
|
||||||
|
pub tun_required: bool,
|
||||||
|
pub tun_attached: bool,
|
||||||
|
pub magic_dns_enabled: bool,
|
||||||
|
pub need_exit_node: bool,
|
||||||
|
pub error_message: Option<String>,
|
||||||
|
pub my_node_info: Option<MyNodeInfo>,
|
||||||
|
pub events: Vec<String>,
|
||||||
|
pub routes: Vec<RouteView>,
|
||||||
|
pub peers: Vec<PeerInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct TunAggregateState {
|
||||||
|
pub active: bool,
|
||||||
|
pub attached_instance_ids: Vec<String>,
|
||||||
|
pub aggregated_routes: Vec<String>,
|
||||||
|
pub dns_servers: Vec<String>,
|
||||||
|
pub need_rebuild: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct RuntimeAggregateState {
|
||||||
|
pub instances: Vec<RuntimeInstanceState>,
|
||||||
|
pub tun: TunAggregateState,
|
||||||
|
pub running_instance_count: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringify_ipv4_inet(value: Option<common::Ipv4Inet>) -> Option<String> {
|
||||||
|
value.map(|v| v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringify_ipv6_inet(value: Option<common::Ipv6Inet>) -> Option<String> {
|
||||||
|
value.map(|v| v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringify_url(value: Option<common::Url>) -> Option<String> {
|
||||||
|
value.map(|v| v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringify_uuid(value: Option<common::Uuid>) -> Option<String> {
|
||||||
|
value.map(|v| v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_u32_to_i64(value: Option<u32>) -> Option<i64> {
|
||||||
|
value.map(|v| v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_i32_to_i64(value: Option<i32>) -> Option<i64> {
|
||||||
|
value.map(|v| v as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn route_to_view(route: api::instance::Route) -> RouteView {
|
||||||
|
let stun = route.stun_info;
|
||||||
|
let feature_flag = route.feature_flag;
|
||||||
|
RouteView {
|
||||||
|
peer_id: route.peer_id as i64,
|
||||||
|
hostname: (!route.hostname.is_empty()).then_some(route.hostname),
|
||||||
|
ipv4: route
|
||||||
|
.ipv4_addr
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|inet| inet.address.as_ref())
|
||||||
|
.map(|addr| addr.to_string()),
|
||||||
|
ipv4_cidr: stringify_ipv4_inet(route.ipv4_addr),
|
||||||
|
ipv6_cidr: stringify_ipv6_inet(route.ipv6_addr),
|
||||||
|
proxy_cidrs: route.proxy_cidrs,
|
||||||
|
next_hop_peer_id: optional_u32_to_i64(route.next_hop_peer_id_latency_first)
|
||||||
|
.or_else(|| Some(route.next_hop_peer_id as i64)),
|
||||||
|
cost: Some(route.cost),
|
||||||
|
path_latency: optional_i32_to_i64(route.path_latency_latency_first)
|
||||||
|
.or_else(|| Some(route.path_latency as i64)),
|
||||||
|
udp_nat_type: stun.as_ref().map(|info| info.udp_nat_type),
|
||||||
|
tcp_nat_type: stun.as_ref().map(|info| info.tcp_nat_type),
|
||||||
|
inst_id: (!route.inst_id.is_empty()).then_some(route.inst_id),
|
||||||
|
version: (!route.version.is_empty()).then_some(route.version),
|
||||||
|
is_public_server: feature_flag.map(|flag| flag.is_public_server),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peer_conn_to_view(conn: api::instance::PeerConnInfo) -> PeerConnInfo {
|
||||||
|
let stats = conn.stats.map(|stats| PeerConnStats {
|
||||||
|
rx_bytes: stats.rx_bytes as i64,
|
||||||
|
tx_bytes: stats.tx_bytes as i64,
|
||||||
|
rx_packets: stats.rx_packets as i64,
|
||||||
|
tx_packets: stats.tx_packets as i64,
|
||||||
|
latency_us: stats.latency_us as i64,
|
||||||
|
});
|
||||||
|
|
||||||
|
PeerConnInfo {
|
||||||
|
conn_id: conn.conn_id,
|
||||||
|
my_peer_id: conn.my_peer_id as i64,
|
||||||
|
peer_id: conn.peer_id as i64,
|
||||||
|
features: conn.features,
|
||||||
|
tunnel_type: conn.tunnel.as_ref().map(|t| t.tunnel_type.clone()),
|
||||||
|
local_addr: conn
|
||||||
|
.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| stringify_url(t.local_addr.clone())),
|
||||||
|
remote_addr: conn
|
||||||
|
.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| stringify_url(t.remote_addr.clone())),
|
||||||
|
resolved_remote_addr: conn
|
||||||
|
.tunnel
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| stringify_url(t.resolved_remote_addr.clone())),
|
||||||
|
stats,
|
||||||
|
loss_rate: Some(conn.loss_rate as f64),
|
||||||
|
is_client: conn.is_client,
|
||||||
|
network_name: (!conn.network_name.is_empty()).then_some(conn.network_name),
|
||||||
|
is_closed: conn.is_closed,
|
||||||
|
secure_auth_level: Some(conn.secure_auth_level),
|
||||||
|
peer_identity_type: Some(conn.peer_identity_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peer_to_view(peer: api::instance::PeerInfo) -> PeerInfo {
|
||||||
|
PeerInfo {
|
||||||
|
peer_id: peer.peer_id as i64,
|
||||||
|
default_conn_id: stringify_uuid(peer.default_conn_id),
|
||||||
|
directly_connected_conns: peer
|
||||||
|
.directly_connected_conns
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| id.to_string())
|
||||||
|
.collect(),
|
||||||
|
conns: peer.conns.into_iter().map(peer_conn_to_view).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn my_node_info_to_view(info: api::manage::MyNodeInfo) -> MyNodeInfo {
|
||||||
|
MyNodeInfo {
|
||||||
|
virtual_ipv4: info
|
||||||
|
.virtual_ipv4
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|inet| inet.address.as_ref())
|
||||||
|
.map(|addr| addr.to_string()),
|
||||||
|
virtual_ipv4_cidr: stringify_ipv4_inet(info.virtual_ipv4),
|
||||||
|
hostname: (!info.hostname.is_empty()).then_some(info.hostname),
|
||||||
|
version: (!info.version.is_empty()).then_some(info.version),
|
||||||
|
peer_id: Some(info.peer_id as i64),
|
||||||
|
listeners: info
|
||||||
|
.listeners
|
||||||
|
.into_iter()
|
||||||
|
.map(|url| url.to_string())
|
||||||
|
.collect(),
|
||||||
|
vpn_portal_cfg: info.vpn_portal_cfg,
|
||||||
|
udp_nat_type: info.stun_info.as_ref().map(|stun| stun.udp_nat_type),
|
||||||
|
tcp_nat_type: info.stun_info.as_ref().map(|stun| stun.tcp_nat_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtime_instance_from_running_info(
|
||||||
|
config_id: String,
|
||||||
|
display_name: String,
|
||||||
|
magic_dns_enabled: bool,
|
||||||
|
need_exit_node: bool,
|
||||||
|
info: api::manage::NetworkInstanceRunningInfo,
|
||||||
|
) -> RuntimeInstanceState {
|
||||||
|
let tun_attached = info.running && is_tun_attached(&config_id);
|
||||||
|
let tun_required = info.running && (info.dev_name != "no_tun" || tun_attached);
|
||||||
|
|
||||||
|
RuntimeInstanceState {
|
||||||
|
config_id: config_id.clone(),
|
||||||
|
instance_id: config_id,
|
||||||
|
display_name,
|
||||||
|
running: info.running,
|
||||||
|
tun_required,
|
||||||
|
tun_attached,
|
||||||
|
magic_dns_enabled,
|
||||||
|
need_exit_node,
|
||||||
|
error_message: info.error_msg,
|
||||||
|
my_node_info: info.my_node_info.map(my_node_info_to_view),
|
||||||
|
events: info.events,
|
||||||
|
routes: info.routes.into_iter().map(route_to_view).collect(),
|
||||||
|
peers: info.peers.into_iter().map(peer_to_view).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,5 +14,8 @@ pub mod web;
|
|||||||
pub mod tests;
|
pub mod tests;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
const DESCRIPTOR_POOL_BYTES: &[u8] =
|
pub const DESCRIPTOR_POOL_BYTES: &[u8] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin"));
|
||||||
|
|
||||||
|
pub const ALL_DESCRIPTOR_BYTES: &[u8] =
|
||||||
|
include_bytes!(concat!(env!("OUT_DIR"), "/descriptors.bin"));
|
||||||
|
|||||||
Reference in New Issue
Block a user