Skip to content

Commit

Permalink
Merge pull request #41 from shm11C3/feature/networkinfo
Browse files Browse the repository at this point in the history
Add: Get Network Info
  • Loading branch information
shm11C3 authored Jan 1, 2025
2 parents 5f89d81 + aa4441e commit 4ef99bb
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 27 deletions.
62 changes: 62 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-label": "^2.1.1",
Expand Down
11 changes: 11 additions & 0 deletions src-tauri/src/commands/hardware.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::enums::error::BackendError;
use crate::services::directx_gpu_service;
use crate::services::nvidia_gpu_service;
use crate::services::system_info_service;
use crate::services::wmi_service;
use crate::structs::hardware::NetworkInfo;
use crate::structs::hardware::{GraphicInfo, MemoryInfo, StorageInfo};
use crate::{log_error, log_internal};
use serde::{Deserialize, Serialize, Serializer};
Expand Down Expand Up @@ -314,6 +316,15 @@ pub fn get_gpu_usage_history(
.collect()
}

///
/// ## ネットワーク情報を取得
///
#[command]
#[specta::specta]
pub fn get_network_info() -> Result<Vec<NetworkInfo>, BackendError> {
wmi_service::get_network_info().map_err(|_| BackendError::UnexpectedError)
}

///
/// ## システム情報の初期化
///
Expand Down
34 changes: 34 additions & 0 deletions src-tauri/src/enums/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use serde::{Serialize, Serializer};
use specta::Type;

#[derive(Debug, PartialEq, Eq, Clone, Type)]
#[serde(rename_all = "camelCase")]
pub enum BackendError {
CpuInfoNotAvailable,
StorageInfoNotAvailable,
MemoryInfoNotAvailable,
GraphicInfoNotAvailable,
NetworkInfoNotAvailable,
NetworkUsageNotAvailable,
UnexpectedError,
// SystemError(String),
}

impl Serialize for BackendError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = match *self {
BackendError::CpuInfoNotAvailable => "cpuInfoNotAvailable",
BackendError::StorageInfoNotAvailable => "storageInfoNotAvailable",
BackendError::MemoryInfoNotAvailable => "memoryInfoNotAvailable",
BackendError::GraphicInfoNotAvailable => "graphicInfoNotAvailable",
BackendError::NetworkInfoNotAvailable => "networkInfoNotAvailable",
BackendError::NetworkUsageNotAvailable => "networkUsageNotAvailable",
BackendError::UnexpectedError => "unexpectedError",
// BackendError::SystemError(ref e) => e,
};
serializer.serialize_str(s)
}
}
1 change: 1 addition & 0 deletions src-tauri/src/enums/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod config;
pub mod error;
pub mod hardware;
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub fn run() {
hardware::get_cpu_usage_history,
hardware::get_memory_usage_history,
hardware::get_gpu_usage_history,
hardware::get_network_info,
config::commands::get_settings,
config::commands::set_language,
config::commands::set_theme,
Expand Down
90 changes: 88 additions & 2 deletions src-tauri/src/services/wmi_service.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::structs::hardware::MemoryInfo;
use crate::structs::hardware::{MemoryInfo, NetworkInfo};
use crate::utils;
use crate::utils::formatter;
use crate::{log_error, log_info, log_internal};

use regex::Regex;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use std::error::Error;
use std::net::IpAddr;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use wmi::{COMLibrary, WMIConnection};
Expand Down Expand Up @@ -105,8 +107,92 @@ pub async fn get_gpu_usage_by_device_and_engine(
})
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct NetworkAdapterConfiguration {
description: Option<String>,
#[serde(rename = "MACAddress")]
mac_address: Option<String>,
#[serde(rename = "IPAddress")]
ip_address: Option<Vec<String>>,
#[serde(rename = "IPSubnet")]
ip_subnet: Option<Vec<String>>,
#[serde(rename = "DefaultIPGateway")]
default_ip_gateway: Option<Vec<String>>,
}

pub fn get_network_info() -> Result<Vec<NetworkInfo>, String> {
let results: Vec<NetworkAdapterConfiguration> = wmi_query_in_thread(
"SELECT Description, MACAddress, IPAddress, IPSubnet, DefaultIPGateway FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE".to_string(),
)?;

log_info!(
&format!("Network adapter configuration data: {:?}", results),
"get_network_info",
None::<&str>
);

let network_info: Result<Vec<NetworkInfo>, String> = results
.into_iter()
.map(|adapter| -> Result<NetworkInfo, String> {
// IPv4とIPv6を分ける
let (ipv4, ipv6): (Vec<_>, Vec<_>) = adapter
.ip_address
.unwrap_or_else(|| vec![])
.into_iter()
.filter_map(|ip| ip.parse::<IpAddr>().ok())
.partition(|ip| matches!(ip, IpAddr::V4(_))); // IPv4とIPv6に分ける

// IPv6を分割する
let (link_local_ipv6, normal_ipv6): (Vec<_>, Vec<_>) =
ipv6.into_iter().partition(|ip| match ip {
IpAddr::V6(v6) if utils::ip::is_unicast_link_local(v6) => true, // リンクローカル
_ => false,
});

// IPv4サブネットを取得
let ipv4_subnet: Vec<String> = adapter
.ip_subnet
.unwrap_or_else(|| vec![])
.into_iter()
.filter(|subnet| subnet.contains('.')) // IPv4形式を確認
.collect();

// IPv4とIPv6のデフォルトゲートウェイを分割する
let (default_ipv4_gateway, default_ipv6_gateway): (Vec<_>, Vec<_>) = adapter
.default_ip_gateway
.unwrap_or_else(|| vec![])
.into_iter()
.filter_map(|ip| ip.parse::<IpAddr>().ok())
.partition(|ip| matches!(ip, IpAddr::V4(_)));

Ok(NetworkInfo {
description: Some(adapter.description.unwrap_or_default()),
mac_address: Some(adapter.mac_address.unwrap_or_default()),
ipv4: ipv4.into_iter().map(|ip| ip.to_string()).collect(),
ipv6: normal_ipv6.into_iter().map(|ip| ip.to_string()).collect(),
link_local_ipv6: link_local_ipv6
.into_iter()
.map(|ip| ip.to_string())
.collect(),
ip_subnet: ipv4_subnet,
default_ipv4_gateway: default_ipv4_gateway
.into_iter()
.map(|ip| ip.to_string())
.collect(),
default_ipv6_gateway: default_ipv6_gateway
.into_iter()
.map(|ip| ip.to_string())
.collect(),
})
})
.collect();

network_info
}

///
/// ## WMIから別スレッドで¥クエリ実行する(WMIを使用)
/// ## 別スレッドでWMIクエリ実行する
///
fn wmi_query_in_thread<T>(query: String) -> Result<Vec<T>, String>
where
Expand Down
23 changes: 23 additions & 0 deletions src-tauri/src/structs/hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,26 @@ pub struct StorageInfo {
pub storage_type: DiskKind,
pub file_system: String,
}

#[derive(Serialize, Deserialize, Type)]
#[serde(rename_all = "camelCase")]
pub struct NetworkInfo {
pub description: Option<String>,
pub mac_address: Option<String>,
pub ipv4: Vec<String>,
pub ipv6: Vec<String>,
pub link_local_ipv6: Vec<String>,
pub ip_subnet: Vec<String>,
pub default_ipv4_gateway: Vec<String>,
pub default_ipv6_gateway: Vec<String>,
}

#[derive(Serialize, Deserialize, Type)]
#[serde(rename_all = "camelCase")]
pub struct NetworkUsage {
pub ip: String,
pub sent: f32,
pub sent_unit: SizeUnit,
pub received: f32,
pub received_unit: SizeUnit,
}
53 changes: 53 additions & 0 deletions src-tauri/src/tests/utils/ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#[cfg(test)]
mod tests {
use crate::utils::ip::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

#[test]
fn test_link_local_ipv6() {
// リンクローカルアドレス
let ip = IpAddr::V6("fe80::1".parse::<Ipv6Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), true);

// グローバルIPv6アドレス
let ip = IpAddr::V6("2400:4051::1".parse::<Ipv6Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), false);

// IPv6マルチキャスト
let ip = IpAddr::V6("ff02::1".parse::<Ipv6Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), false);

// 未指定アドレス
let ip = IpAddr::V6("::".parse::<Ipv6Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), false);
}

#[test]
fn test_ipv4_not_link_local() {
// IPv4アドレス(リンクローカルではない)
let ip = IpAddr::V4("192.168.1.1".parse::<Ipv4Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), false);

// IPv4アドレス(リンクローカルではない)
let ip = IpAddr::V4("127.0.0.1".parse::<Ipv4Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), false);
}

#[test]
fn test_with_direct_ipv6() {
// 直接Ipv6Addr型でテスト
let ip = "fe80::1".parse::<Ipv6Addr>().unwrap();
assert_eq!(is_unicast_link_local(&ip), true);

let ip = "2400:4051::1".parse::<Ipv6Addr>().unwrap();
assert_eq!(is_unicast_link_local(&ip), false);
}

#[test]
fn test_with_invalid_format() {
// 無効なアドレス文字列はパースエラーを引き起こすためここではテストしないが、
// 型制約に沿ってテストする例
let ip = IpAddr::V4("255.255.255.255".parse::<Ipv4Addr>().unwrap());
assert_eq!(is_unicast_link_local(&ip), false);
}
}
2 changes: 2 additions & 0 deletions src-tauri/src/tests/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
pub mod color;
#[cfg(test)]
pub mod formatter;
#[cfg(test)]
pub mod ip;
11 changes: 11 additions & 0 deletions src-tauri/src/utils/ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::net::IpAddr;

pub fn is_unicast_link_local<T>(ip: &T) -> bool
where
T: Into<IpAddr> + Clone,
{
match ip.clone().into() {
IpAddr::V6(v6) => v6.segments()[0] & 0xffc0 == 0xfe80,
_ => false,
}
}
1 change: 1 addition & 0 deletions src-tauri/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod color;
pub mod file;
pub mod formatter;
pub mod ip;
pub mod logger;
pub mod tauri;
Loading

0 comments on commit 4ef99bb

Please sign in to comment.