mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-08-18 02:06:45 +00:00
fix(shared-tun): close mobile routing gaps
Translate IPv6 traffic across shared mobile members, including transport checksums and reverse-flow handling. Normalize saved Android TUN configs and detach runtimes before the VPN fd closes. Restore the mainline lockfile after resolving the merge.
This commit is contained in:
@@ -73,7 +73,7 @@ static RPC_SERVER: once_cell::sync::Lazy<Mutex<Option<RpcServer>>> =
|
||||
static WEB_CLIENT: once_cell::sync::Lazy<RwLock<Option<WebClient>>> =
|
||||
once_cell::sync::Lazy::new(|| RwLock::new(None));
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "android", test))]
|
||||
const ANDROID_SHARED_TUN_DEV_NAME: &str = "easytier-shared";
|
||||
|
||||
macro_rules! get_client_manager {
|
||||
@@ -88,15 +88,18 @@ macro_rules! get_client_manager {
|
||||
|
||||
fn normalize_network_config_for_runtime(cfg: &mut NetworkConfig) {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
if !cfg.no_tun() && cfg.dev_name.as_deref().map(str::is_empty).unwrap_or(true) {
|
||||
cfg.dev_name = Some(ANDROID_SHARED_TUN_DEV_NAME.to_owned());
|
||||
}
|
||||
}
|
||||
normalize_android_network_config(cfg);
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let _ = cfg;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", test))]
|
||||
fn normalize_android_network_config(cfg: &mut NetworkConfig) {
|
||||
if !cfg.no_tun() && cfg.dev_name.as_deref().map(str::is_empty).unwrap_or(true) {
|
||||
cfg.dev_name = Some(ANDROID_SHARED_TUN_DEV_NAME.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn easytier_version() -> Result<String, String> {
|
||||
Ok(easytier::VERSION.to_string())
|
||||
@@ -252,6 +255,31 @@ async fn set_tun_fd(
|
||||
return Err("duplicate instance id in TUN fd attachment group".to_string());
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
if fd <= 0 {
|
||||
let mut errors = Vec::new();
|
||||
for instance_id in target_ids {
|
||||
if let Err(error) = attach_mobile_tun_fd(
|
||||
instance_manager.as_ref(),
|
||||
instance_id,
|
||||
fd,
|
||||
easytier::instance::virtual_nic::MobileTunSources::default(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
errors.push(format!("{instance_id}: {error}"));
|
||||
}
|
||||
}
|
||||
return if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"failed to detach tun fd from the instance group: {}",
|
||||
errors.join("; ")
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let mut source_map = parse_tun_fd_instance_sources(instance_sources.unwrap_or_default())?;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -955,9 +983,10 @@ mod manager {
|
||||
&self,
|
||||
app: &AppHandle,
|
||||
inst_id: Uuid,
|
||||
cfg: NetworkConfig,
|
||||
mut cfg: NetworkConfig,
|
||||
source: PersistedConfigSource,
|
||||
) -> anyhow::Result<()> {
|
||||
normalize_network_config_for_runtime(&mut cfg);
|
||||
let source = self
|
||||
.network_configs
|
||||
.get(&inst_id)
|
||||
@@ -1436,8 +1465,33 @@ mod manager {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{PersistedConfigSource, StoredGuiConfig};
|
||||
use crate::{ANDROID_SHARED_TUN_DEV_NAME, normalize_android_network_config};
|
||||
use easytier::proto::api::manage::NetworkConfig;
|
||||
|
||||
#[test]
|
||||
fn android_default_tun_config_uses_shared_device_name() {
|
||||
let mut config = NetworkConfig::default();
|
||||
|
||||
normalize_android_network_config(&mut config);
|
||||
|
||||
assert_eq!(
|
||||
config.dev_name.as_deref(),
|
||||
Some(ANDROID_SHARED_TUN_DEV_NAME)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn android_no_tun_config_keeps_empty_device_name() {
|
||||
let mut config = NetworkConfig {
|
||||
no_tun: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
normalize_android_network_config(&mut config);
|
||||
|
||||
assert!(config.dev_name.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stored_gui_config_defaults_missing_source_to_legacy() {
|
||||
let stored: StoredGuiConfig = serde_json::from_value(serde_json::json!({
|
||||
|
||||
@@ -100,11 +100,23 @@ async function waitVpnStatus(target_status: boolean, timeout_sec: number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function detachTunFd(instanceIds: string[], instanceSources: TunFdInstanceSources[]) {
|
||||
try {
|
||||
await setTunFd(0, instanceIds, instanceSources)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('detach tun fd failed', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function doStopVpn(force = false) {
|
||||
const wasRunning = curVpnStatus.running
|
||||
if (!force && !wasRunning) {
|
||||
return
|
||||
}
|
||||
const instanceIds = [...curVpnStatus.instanceIds]
|
||||
const instanceSources = [...curVpnStatus.instanceSources]
|
||||
await detachTunFd(instanceIds, instanceSources)
|
||||
console.log('stop vpn')
|
||||
const stop_ret = await stop_vpn()
|
||||
console.log('stop vpn', JSON.stringify((stop_ret)))
|
||||
@@ -194,6 +206,9 @@ async function onVpnServiceStart(payload: any) {
|
||||
|
||||
async function onVpnServiceStop(payload: any) {
|
||||
console.log('vpn service stop', JSON.stringify(payload))
|
||||
const instanceIds = [...curVpnStatus.instanceIds]
|
||||
const instanceSources = [...curVpnStatus.instanceSources]
|
||||
await detachTunFd(instanceIds, instanceSources)
|
||||
curVpnStatus.running = false
|
||||
resetVpnConfigStatus()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use pnet_packet::{
|
||||
icmp::{self, MutableIcmpPacket},
|
||||
ip::IpNextHeaderProtocols,
|
||||
ipv4::{self, MutableIpv4Packet},
|
||||
ipv6::MutableIpv6Packet,
|
||||
tcp::{self, MutableTcpPacket},
|
||||
udp::{self, MutableUdpPacket},
|
||||
};
|
||||
@@ -48,6 +49,7 @@ const TCP_HEADER_MIN_LEN: usize = 20;
|
||||
const UDP_HEADER_LEN: usize = 8;
|
||||
const ICMP_ECHO_HEADER_LEN: usize = 8;
|
||||
const ICMP_PROTOCOL: u8 = 1;
|
||||
const ICMPV6_PROTOCOL: u8 = 58;
|
||||
const TCP_PROTOCOL: u8 = 6;
|
||||
const UDP_PROTOCOL: u8 = 17;
|
||||
const DISPATCHER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
@@ -372,6 +374,13 @@ impl SharedVirtualNicFlowAddr {
|
||||
Self::V6(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ipv6(self) -> Option<std::net::Ipv6Addr> {
|
||||
match self {
|
||||
Self::V4(_) => None,
|
||||
Self::V6(addr) => Some(std::net::Ipv6Addr::from(addr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SharedVirtualNicFlowKey {
|
||||
@@ -416,12 +425,16 @@ impl SharedVirtualNicFlowKey {
|
||||
return None;
|
||||
}
|
||||
|
||||
let protocol = payload[6];
|
||||
let transport = ipv6_transport_info(payload)?;
|
||||
Some(Self {
|
||||
src: SharedVirtualNicFlowAddr::V6(read_ipv6_addr(payload, 8)),
|
||||
dst: SharedVirtualNicFlowAddr::V6(read_ipv6_addr(payload, 24)),
|
||||
protocol,
|
||||
ports: transport_ports(protocol, &payload[IPV6_HEADER_LEN..]),
|
||||
protocol: transport.protocol,
|
||||
ports: if transport.fragment_offset == 0 {
|
||||
transport_ports(transport.protocol, &payload[transport.offset..])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -431,7 +444,7 @@ impl SharedVirtualNicFlowKey {
|
||||
dst: self.src,
|
||||
protocol: self.protocol,
|
||||
ports: self.ports.map(|ports| {
|
||||
if self.protocol == ICMP_PROTOCOL {
|
||||
if matches!(self.protocol, ICMP_PROTOCOL | ICMPV6_PROTOCOL) {
|
||||
ports
|
||||
} else {
|
||||
ports.reversed()
|
||||
@@ -1231,21 +1244,27 @@ impl SharedVirtualNicDispatcherState {
|
||||
member_id: SharedVirtualNicMemberId,
|
||||
mut packet: ZCPacket,
|
||||
) -> Result<(), ZCPacket> {
|
||||
let mut nat_entry = None;
|
||||
if let Some(key) = SharedVirtualNicFlowKey::from_packet(&packet) {
|
||||
if let Some(translated_src) = self
|
||||
.source_table
|
||||
.source_for_member_destination(member_id, key.dst)
|
||||
{
|
||||
if key.src != translated_src
|
||||
&& rewrite_packet_source(&mut packet, key.src, translated_src)
|
||||
{
|
||||
nat_entry = Some((packet.clone(), key.src, translated_src));
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(key) = SharedVirtualNicFlowKey::from_packet(&packet) else {
|
||||
return Err(packet);
|
||||
};
|
||||
let Some(translated_src) = self
|
||||
.source_table
|
||||
.source_for_member_destination(member_id, key.dst)
|
||||
else {
|
||||
return Err(packet);
|
||||
};
|
||||
let original_packet = packet.clone();
|
||||
let nat_entry = if key.src == translated_src {
|
||||
None
|
||||
} else if rewrite_packet_source(&mut packet, key.src, translated_src) {
|
||||
Some((packet.clone(), key.src, translated_src))
|
||||
} else {
|
||||
return Err(packet);
|
||||
};
|
||||
|
||||
self.send_packet_to_member(member_id, packet).await?;
|
||||
if self.send_packet_to_member(member_id, packet).await.is_err() {
|
||||
return Err(original_packet);
|
||||
}
|
||||
if let Some((translated_packet, original_src, translated_src)) = nat_entry {
|
||||
self.nat_table
|
||||
.remember(&translated_packet, original_src, translated_src);
|
||||
@@ -1304,7 +1323,7 @@ fn rewrite_packet_source(
|
||||
expected_source: SharedVirtualNicFlowAddr,
|
||||
new_source: SharedVirtualNicFlowAddr,
|
||||
) -> bool {
|
||||
rewrite_ipv4_addr(packet, expected_source, new_source, RewriteIpv4Addr::Source)
|
||||
rewrite_ip_addr(packet, expected_source, new_source, RewriteIpAddr::Source)
|
||||
}
|
||||
|
||||
fn rewrite_packet_destination(
|
||||
@@ -1312,25 +1331,42 @@ fn rewrite_packet_destination(
|
||||
expected_destination: SharedVirtualNicFlowAddr,
|
||||
new_destination: SharedVirtualNicFlowAddr,
|
||||
) -> bool {
|
||||
rewrite_ipv4_addr(
|
||||
rewrite_ip_addr(
|
||||
packet,
|
||||
expected_destination,
|
||||
new_destination,
|
||||
RewriteIpv4Addr::Destination,
|
||||
RewriteIpAddr::Destination,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum RewriteIpv4Addr {
|
||||
enum RewriteIpAddr {
|
||||
Source,
|
||||
Destination,
|
||||
}
|
||||
|
||||
fn rewrite_ip_addr(
|
||||
packet: &mut ZCPacket,
|
||||
expected_addr: SharedVirtualNicFlowAddr,
|
||||
new_addr: SharedVirtualNicFlowAddr,
|
||||
rewrite: RewriteIpAddr,
|
||||
) -> bool {
|
||||
match (expected_addr, new_addr) {
|
||||
(SharedVirtualNicFlowAddr::V4(_), SharedVirtualNicFlowAddr::V4(_)) => {
|
||||
rewrite_ipv4_addr(packet, expected_addr, new_addr, rewrite)
|
||||
}
|
||||
(SharedVirtualNicFlowAddr::V6(_), SharedVirtualNicFlowAddr::V6(_)) => {
|
||||
rewrite_ipv6_addr(packet, expected_addr, new_addr, rewrite)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_ipv4_addr(
|
||||
packet: &mut ZCPacket,
|
||||
expected_addr: SharedVirtualNicFlowAddr,
|
||||
new_addr: SharedVirtualNicFlowAddr,
|
||||
rewrite: RewriteIpv4Addr,
|
||||
rewrite: RewriteIpAddr,
|
||||
) -> bool {
|
||||
let Some(expected_addr) = expected_addr.as_ipv4() else {
|
||||
return false;
|
||||
@@ -1355,10 +1391,10 @@ fn rewrite_ipv4_addr(
|
||||
|| (ipv4_packet.get_flags() & ipv4::Ipv4Flags::MoreFragments) != 0;
|
||||
|
||||
match rewrite {
|
||||
RewriteIpv4Addr::Source if ipv4_packet.get_source() == expected_addr => {
|
||||
RewriteIpAddr::Source if ipv4_packet.get_source() == expected_addr => {
|
||||
ipv4_packet.set_source(new_addr);
|
||||
}
|
||||
RewriteIpv4Addr::Destination if ipv4_packet.get_destination() == expected_addr => {
|
||||
RewriteIpAddr::Destination if ipv4_packet.get_destination() == expected_addr => {
|
||||
ipv4_packet.set_destination(new_addr);
|
||||
}
|
||||
_ => return false,
|
||||
@@ -1380,6 +1416,154 @@ fn rewrite_ipv4_addr(
|
||||
true
|
||||
}
|
||||
|
||||
fn rewrite_ipv6_addr(
|
||||
packet: &mut ZCPacket,
|
||||
expected_addr: SharedVirtualNicFlowAddr,
|
||||
new_addr: SharedVirtualNicFlowAddr,
|
||||
rewrite: RewriteIpAddr,
|
||||
) -> bool {
|
||||
let Some(expected_addr) = expected_addr.as_ipv6() else {
|
||||
return false;
|
||||
};
|
||||
let Some(new_addr) = new_addr.as_ipv6() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let payload = packet.mut_payload();
|
||||
let Some(transport) = ipv6_transport_info(payload) else {
|
||||
return false;
|
||||
};
|
||||
if matches!(transport.protocol, 50 | 51)
|
||||
|| (matches!(rewrite, RewriteIpAddr::Destination) && !transport.destination_rewrite_safe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let Some(mut ipv6_packet) = MutableIpv6Packet::new(payload) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let old_source = ipv6_packet.get_source();
|
||||
let old_destination = ipv6_packet.get_destination();
|
||||
match rewrite {
|
||||
RewriteIpAddr::Source if old_source == expected_addr => {
|
||||
ipv6_packet.set_source(new_addr);
|
||||
}
|
||||
RewriteIpAddr::Destination if old_destination == expected_addr => {
|
||||
ipv6_packet.set_destination(new_addr);
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
if transport.fragment_offset == 0 {
|
||||
let source = ipv6_packet.get_source();
|
||||
let destination = ipv6_packet.get_destination();
|
||||
adjust_ipv6_transport_checksum(
|
||||
ipv6_packet.packet_mut(),
|
||||
transport,
|
||||
old_source,
|
||||
source,
|
||||
old_destination,
|
||||
destination,
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Ipv6TransportInfo {
|
||||
protocol: u8,
|
||||
offset: usize,
|
||||
fragment_offset: u16,
|
||||
destination_rewrite_safe: bool,
|
||||
}
|
||||
|
||||
fn ipv6_transport_info(payload: &[u8]) -> Option<Ipv6TransportInfo> {
|
||||
if payload.len() < IPV6_HEADER_LEN || payload[0] >> 4 != 6 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut protocol = payload[6];
|
||||
let mut offset = IPV6_HEADER_LEN;
|
||||
let mut fragment_offset = 0;
|
||||
let mut destination_rewrite_safe = true;
|
||||
loop {
|
||||
match protocol {
|
||||
// Hop-by-hop, routing, and destination options headers.
|
||||
0 | 43 | 60 => {
|
||||
if protocol == 43 {
|
||||
destination_rewrite_safe = false;
|
||||
}
|
||||
let header = payload.get(offset..offset + 2)?;
|
||||
protocol = header[0];
|
||||
let header_len = (usize::from(header[1]) + 1) * 8;
|
||||
offset = offset.checked_add(header_len)?;
|
||||
if offset > payload.len() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Fragment header.
|
||||
44 => {
|
||||
let header = payload.get(offset..offset + 8)?;
|
||||
protocol = header[0];
|
||||
fragment_offset = u16::from_be_bytes([header[2], header[3]]) >> 3;
|
||||
offset += 8;
|
||||
if fragment_offset != 0 {
|
||||
return Some(Ipv6TransportInfo {
|
||||
protocol,
|
||||
offset,
|
||||
fragment_offset,
|
||||
destination_rewrite_safe: destination_rewrite_safe
|
||||
&& matches!(protocol, TCP_PROTOCOL | UDP_PROTOCOL | ICMPV6_PROTOCOL),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Some(Ipv6TransportInfo {
|
||||
protocol,
|
||||
offset,
|
||||
fragment_offset,
|
||||
destination_rewrite_safe,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_ipv6_transport_checksum(
|
||||
payload: &mut [u8],
|
||||
transport: Ipv6TransportInfo,
|
||||
old_source: std::net::Ipv6Addr,
|
||||
source: std::net::Ipv6Addr,
|
||||
old_destination: std::net::Ipv6Addr,
|
||||
destination: std::net::Ipv6Addr,
|
||||
) {
|
||||
let checksum_offset = match transport.protocol {
|
||||
TCP_PROTOCOL => 16,
|
||||
UDP_PROTOCOL => 6,
|
||||
ICMPV6_PROTOCOL => 2,
|
||||
_ => return,
|
||||
};
|
||||
let Some(checksum_bytes) =
|
||||
payload.get_mut(transport.offset + checksum_offset..transport.offset + checksum_offset + 2)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let checksum = u16::from_be_bytes([checksum_bytes[0], checksum_bytes[1]]);
|
||||
let checksum = adjust_ipv6_pseudo_header_checksum(
|
||||
checksum,
|
||||
old_source,
|
||||
source,
|
||||
old_destination,
|
||||
destination,
|
||||
);
|
||||
let checksum = if transport.protocol == UDP_PROTOCOL && checksum == 0 {
|
||||
0xffff
|
||||
} else {
|
||||
checksum
|
||||
};
|
||||
checksum_bytes.copy_from_slice(&checksum.to_be_bytes());
|
||||
}
|
||||
|
||||
fn update_ipv4_transport_checksum(ipv4_packet: &mut MutableIpv4Packet<'_>, header_len: usize) {
|
||||
let source = ipv4_packet.get_source();
|
||||
let destination = ipv4_packet.get_destination();
|
||||
@@ -1483,6 +1667,29 @@ fn adjust_ipv4_pseudo_header_checksum(
|
||||
checksum
|
||||
}
|
||||
|
||||
fn adjust_ipv6_pseudo_header_checksum(
|
||||
mut checksum: u16,
|
||||
old_source: std::net::Ipv6Addr,
|
||||
source: std::net::Ipv6Addr,
|
||||
old_destination: std::net::Ipv6Addr,
|
||||
destination: std::net::Ipv6Addr,
|
||||
) -> u16 {
|
||||
for (old, new) in old_source
|
||||
.segments()
|
||||
.into_iter()
|
||||
.zip(source.segments())
|
||||
.chain(
|
||||
old_destination
|
||||
.segments()
|
||||
.into_iter()
|
||||
.zip(destination.segments()),
|
||||
)
|
||||
{
|
||||
checksum = adjust_checksum_word(checksum, old, new);
|
||||
}
|
||||
checksum
|
||||
}
|
||||
|
||||
fn ipv4_checksum_words(addr: std::net::Ipv4Addr) -> impl Iterator<Item = u16> {
|
||||
let octets = addr.octets();
|
||||
[
|
||||
@@ -1753,7 +1960,7 @@ fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTrans
|
||||
let min_len = match protocol {
|
||||
TCP_PROTOCOL => TCP_HEADER_MIN_LEN,
|
||||
UDP_PROTOCOL => UDP_HEADER_LEN,
|
||||
ICMP_PROTOCOL => ICMP_ECHO_HEADER_LEN,
|
||||
ICMP_PROTOCOL | ICMPV6_PROTOCOL => ICMP_ECHO_HEADER_LEN,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
@@ -1763,6 +1970,7 @@ fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTrans
|
||||
|
||||
match protocol {
|
||||
ICMP_PROTOCOL => icmp_echo_flow(payload),
|
||||
ICMPV6_PROTOCOL => icmpv6_echo_flow(payload),
|
||||
_ => Some(SharedVirtualNicTransportPorts {
|
||||
src: u16::from_be_bytes([payload[0], payload[1]]),
|
||||
dst: u16::from_be_bytes([payload[2], payload[3]]),
|
||||
@@ -1770,6 +1978,16 @@ fn transport_ports(protocol: u8, payload: &[u8]) -> Option<SharedVirtualNicTrans
|
||||
}
|
||||
}
|
||||
|
||||
fn icmpv6_echo_flow(payload: &[u8]) -> Option<SharedVirtualNicTransportPorts> {
|
||||
match payload[0] {
|
||||
128 | 129 => Some(SharedVirtualNicTransportPorts {
|
||||
src: u16::from_be_bytes([payload[4], payload[5]]),
|
||||
dst: u16::from_be_bytes([payload[6], payload[7]]),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn icmp_echo_flow(payload: &[u8]) -> Option<SharedVirtualNicTransportPorts> {
|
||||
match payload[0] {
|
||||
ty if ty == icmp::IcmpTypes::EchoRequest.0 || ty == icmp::IcmpTypes::EchoReply.0 => {
|
||||
@@ -1834,6 +2052,84 @@ mod tests {
|
||||
ZCPacket::new_with_payload(&payload)
|
||||
}
|
||||
|
||||
fn ipv6_udp_packet_with_ports(
|
||||
src: Ipv6Addr,
|
||||
dst: Ipv6Addr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
) -> ZCPacket {
|
||||
let mut payload = vec![0; IPV6_HEADER_LEN + UDP_HEADER_LEN];
|
||||
{
|
||||
let mut ipv6_packet = pnet_packet::ipv6::MutableIpv6Packet::new(&mut payload).unwrap();
|
||||
ipv6_packet.set_version(6);
|
||||
ipv6_packet.set_payload_length(UDP_HEADER_LEN as u16);
|
||||
ipv6_packet.set_next_header(IpNextHeaderProtocols::Udp);
|
||||
ipv6_packet.set_hop_limit(64);
|
||||
ipv6_packet.set_source(src);
|
||||
ipv6_packet.set_destination(dst);
|
||||
}
|
||||
{
|
||||
let mut udp_packet = MutableUdpPacket::new(&mut payload[IPV6_HEADER_LEN..]).unwrap();
|
||||
udp_packet.set_source(src_port);
|
||||
udp_packet.set_destination(dst_port);
|
||||
udp_packet.set_length(UDP_HEADER_LEN as u16);
|
||||
let checksum = udp::ipv6_checksum(&udp_packet.to_immutable(), &src, &dst);
|
||||
udp_packet.set_checksum(checksum);
|
||||
}
|
||||
ZCPacket::new_with_payload(&payload)
|
||||
}
|
||||
|
||||
fn ipv6_tcp_packet_with_ports(
|
||||
src: Ipv6Addr,
|
||||
dst: Ipv6Addr,
|
||||
src_port: u16,
|
||||
dst_port: u16,
|
||||
) -> ZCPacket {
|
||||
let mut payload = vec![0; IPV6_HEADER_LEN + TCP_HEADER_MIN_LEN];
|
||||
{
|
||||
let mut ipv6_packet = pnet_packet::ipv6::MutableIpv6Packet::new(&mut payload).unwrap();
|
||||
ipv6_packet.set_version(6);
|
||||
ipv6_packet.set_payload_length(TCP_HEADER_MIN_LEN as u16);
|
||||
ipv6_packet.set_next_header(IpNextHeaderProtocols::Tcp);
|
||||
ipv6_packet.set_hop_limit(64);
|
||||
ipv6_packet.set_source(src);
|
||||
ipv6_packet.set_destination(dst);
|
||||
}
|
||||
{
|
||||
let mut tcp_packet = MutableTcpPacket::new(&mut payload[IPV6_HEADER_LEN..]).unwrap();
|
||||
tcp_packet.set_source(src_port);
|
||||
tcp_packet.set_destination(dst_port);
|
||||
tcp_packet.set_data_offset(5);
|
||||
let checksum = tcp::ipv6_checksum(&tcp_packet.to_immutable(), &src, &dst);
|
||||
tcp_packet.set_checksum(checksum);
|
||||
}
|
||||
ZCPacket::new_with_payload(&payload)
|
||||
}
|
||||
|
||||
fn ipv6_icmp_echo_packet(src: Ipv6Addr, dst: Ipv6Addr) -> ZCPacket {
|
||||
let mut payload = vec![0; IPV6_HEADER_LEN + ICMP_ECHO_HEADER_LEN];
|
||||
{
|
||||
let mut ipv6_packet = pnet_packet::ipv6::MutableIpv6Packet::new(&mut payload).unwrap();
|
||||
ipv6_packet.set_version(6);
|
||||
ipv6_packet.set_payload_length(ICMP_ECHO_HEADER_LEN as u16);
|
||||
ipv6_packet.set_next_header(IpNextHeaderProtocols::Icmpv6);
|
||||
ipv6_packet.set_hop_limit(64);
|
||||
ipv6_packet.set_source(src);
|
||||
ipv6_packet.set_destination(dst);
|
||||
}
|
||||
{
|
||||
let mut icmp_packet =
|
||||
pnet_packet::icmpv6::MutableIcmpv6Packet::new(&mut payload[IPV6_HEADER_LEN..])
|
||||
.unwrap();
|
||||
icmp_packet.set_icmpv6_type(pnet_packet::icmpv6::Icmpv6Types::EchoRequest);
|
||||
icmp_packet.packet_mut()[4..6].copy_from_slice(&1234_u16.to_be_bytes());
|
||||
icmp_packet.packet_mut()[6..8].copy_from_slice(&1_u16.to_be_bytes());
|
||||
let checksum = pnet_packet::icmpv6::checksum(&icmp_packet.to_immutable(), &src, &dst);
|
||||
icmp_packet.set_checksum(checksum);
|
||||
}
|
||||
ZCPacket::new_with_payload(&payload)
|
||||
}
|
||||
|
||||
fn ipv4_udp_packet(src: Ipv4Addr, dst: Ipv4Addr) -> ZCPacket {
|
||||
ipv4_udp_packet_with_ports(src, dst, 1234, 5678)
|
||||
}
|
||||
@@ -1987,6 +2283,13 @@ mod tests {
|
||||
SharedVirtualNicMemberSources::from_claims(&member_claims(&[], ipv6, &[], &[]))
|
||||
}
|
||||
|
||||
fn member_sources_with_ipv6_routes(
|
||||
ipv6: &[&str],
|
||||
ipv6_routes: &[&str],
|
||||
) -> SharedVirtualNicMemberSources {
|
||||
SharedVirtualNicMemberSources::from_claims(&member_claims(&[], ipv6, &[], ipv6_routes))
|
||||
}
|
||||
|
||||
fn member_claims(
|
||||
ipv4: &[&str],
|
||||
ipv6: &[&str],
|
||||
@@ -2268,6 +2571,98 @@ mod tests {
|
||||
assert_eq!(reply_ipv4.get_destination(), source_owner_ip);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dispatcher_translates_wrong_local_ipv6_source_to_destination_member() {
|
||||
let source_owner = uuid::Uuid::from_u128(1);
|
||||
let destination_owner = uuid::Uuid::from_u128(2);
|
||||
let source_owner_ip = "2001:db8:1::1".parse::<Ipv6Addr>().unwrap();
|
||||
let destination_owner_ip = "2001:db8:2::1".parse::<Ipv6Addr>().unwrap();
|
||||
let remote_ip = "2001:db8:99::2".parse::<Ipv6Addr>().unwrap();
|
||||
let (source_sender, mut source_receiver) = mpsc::channel(1);
|
||||
let (destination_sender, mut destination_receiver) = mpsc::channel(1);
|
||||
let mut state = SharedVirtualNicDispatcherState::default();
|
||||
|
||||
state.register(source_owner, member_entry(source_sender));
|
||||
state.register(destination_owner, member_entry(destination_sender));
|
||||
state.source_table.update_member_sources(
|
||||
source_owner,
|
||||
member_sources_with_ipv6(&["2001:db8:1::1/64"]),
|
||||
);
|
||||
state.source_table.update_member_sources(
|
||||
destination_owner,
|
||||
member_sources_with_ipv6_routes(&["2001:db8:2::1/64"], &["2001:db8:99::/64"]),
|
||||
);
|
||||
|
||||
state
|
||||
.forward_tun_packet_to_member(ipv6_udp_packet_with_ports(
|
||||
source_owner_ip,
|
||||
remote_ip,
|
||||
1234,
|
||||
5678,
|
||||
))
|
||||
.await;
|
||||
|
||||
assert!(source_receiver.try_recv().is_err());
|
||||
let translated = destination_receiver.try_recv().unwrap();
|
||||
let translated_ipv6 = pnet_packet::ipv6::Ipv6Packet::new(translated.payload()).unwrap();
|
||||
assert_eq!(translated_ipv6.get_source(), destination_owner_ip);
|
||||
assert_eq!(translated_ipv6.get_destination(), remote_ip);
|
||||
let translated_udp =
|
||||
pnet_packet::udp::UdpPacket::new(&translated.payload()[IPV6_HEADER_LEN..]).unwrap();
|
||||
assert_eq!(
|
||||
translated_udp.get_checksum(),
|
||||
udp::ipv6_checksum(&translated_udp, &destination_owner_ip, &remote_ip,)
|
||||
);
|
||||
|
||||
let reply = state.prepare_member_packet_to_tun(
|
||||
destination_owner,
|
||||
ipv6_udp_packet_with_ports(remote_ip, destination_owner_ip, 5678, 1234),
|
||||
);
|
||||
let reply_ipv6 = pnet_packet::ipv6::Ipv6Packet::new(reply.payload()).unwrap();
|
||||
assert_eq!(reply_ipv6.get_source(), remote_ip);
|
||||
assert_eq!(reply_ipv6.get_destination(), source_owner_ip);
|
||||
let reply_udp =
|
||||
pnet_packet::udp::UdpPacket::new(&reply.payload()[IPV6_HEADER_LEN..]).unwrap();
|
||||
assert_eq!(
|
||||
reply_udp.get_checksum(),
|
||||
udp::ipv6_checksum(&reply_udp, &remote_ip, &source_owner_ip)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrite_ipv6_source_updates_tcp_and_icmpv6_checksums() {
|
||||
let source = "2001:db8:1::1".parse::<Ipv6Addr>().unwrap();
|
||||
let translated_source = "2001:db8:2::1".parse::<Ipv6Addr>().unwrap();
|
||||
let destination = "2001:db8:99::2".parse::<Ipv6Addr>().unwrap();
|
||||
|
||||
let mut tcp_packet = ipv6_tcp_packet_with_ports(source, destination, 1234, 5678);
|
||||
assert!(rewrite_packet_source(
|
||||
&mut tcp_packet,
|
||||
SharedVirtualNicFlowAddr::from(source),
|
||||
SharedVirtualNicFlowAddr::from(translated_source),
|
||||
));
|
||||
let tcp =
|
||||
pnet_packet::tcp::TcpPacket::new(&tcp_packet.payload()[IPV6_HEADER_LEN..]).unwrap();
|
||||
assert_eq!(
|
||||
tcp.get_checksum(),
|
||||
tcp::ipv6_checksum(&tcp, &translated_source, &destination)
|
||||
);
|
||||
|
||||
let mut icmp_packet = ipv6_icmp_echo_packet(source, destination);
|
||||
assert!(rewrite_packet_source(
|
||||
&mut icmp_packet,
|
||||
SharedVirtualNicFlowAddr::from(source),
|
||||
SharedVirtualNicFlowAddr::from(translated_source),
|
||||
));
|
||||
let icmp =
|
||||
pnet_packet::icmpv6::Icmpv6Packet::new(&icmp_packet.payload()[IPV6_HEADER_LEN..])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
icmp.get_checksum(),
|
||||
pnet_packet::icmpv6::checksum(&icmp, &translated_source, &destination)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dispatcher_unregister_clears_nat_translation() {
|
||||
let source_owner = uuid::Uuid::from_u128(1);
|
||||
|
||||
Generated
+253
-16
@@ -5,6 +5,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
happy-dom: 16.8.1
|
||||
minimatch: 10.2.4
|
||||
|
||||
importers:
|
||||
@@ -58,7 +59,7 @@ importers:
|
||||
devDependencies:
|
||||
'@antfu/eslint-config':
|
||||
specifier: ^3.7.3
|
||||
version: 3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))
|
||||
version: 3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))
|
||||
'@intlify/unplugin-vue-i18n':
|
||||
specifier: ^5.2.0
|
||||
version: 5.3.1(@vue/compiler-dom@3.5.21)(eslint@9.35.0(jiti@2.5.1))(rollup@4.50.1)(typescript@5.6.3)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
|
||||
@@ -1009,8 +1010,8 @@ packages:
|
||||
resolution: {integrity: sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@11.4.8':
|
||||
resolution: {integrity: sha512-XbRgrv+XEuvDr7UCY55oibVrh+o4u+A0VB6nSL0F5Z8LcZxE/8j573LYG6bCrOigIcHdGpSNI7Rh5UpC5/B/eg==}
|
||||
'@intlify/shared@11.4.6':
|
||||
resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==}
|
||||
engines: {node: '>= 22'}
|
||||
|
||||
'@intlify/shared@12.0.0-alpha.4':
|
||||
@@ -1412,6 +1413,9 @@ packages:
|
||||
'@rushstack/ts-command-line@5.0.2':
|
||||
resolution: {integrity: sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@stylistic/eslint-plugin@2.13.0':
|
||||
resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -1522,9 +1526,15 @@ packages:
|
||||
'@types/argparse@1.0.38':
|
||||
resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/default-gateway@7.2.2':
|
||||
resolution: {integrity: sha512-35C93fYQlnLKLASkMPoxRvok4fENwB3By9clRLd2I/08n/XRl0pCdf7EB17K5oMMwZu8NBYA8i66jH5r/LYBKA==}
|
||||
|
||||
@@ -1753,6 +1763,9 @@ packages:
|
||||
'@vitest/expect@2.1.9':
|
||||
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
||||
|
||||
'@vitest/expect@4.1.9':
|
||||
resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
|
||||
|
||||
'@vitest/mocker@2.1.9':
|
||||
resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
|
||||
peerDependencies:
|
||||
@@ -1764,21 +1777,47 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@4.1.9':
|
||||
resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
msw:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@2.1.9':
|
||||
resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
|
||||
|
||||
'@vitest/runner@2.1.9':
|
||||
resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
|
||||
|
||||
'@vitest/runner@4.1.9':
|
||||
resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
|
||||
|
||||
'@vitest/snapshot@2.1.9':
|
||||
resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
|
||||
|
||||
'@vitest/snapshot@4.1.9':
|
||||
resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
|
||||
|
||||
'@vitest/spy@2.1.9':
|
||||
resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
|
||||
|
||||
'@vitest/spy@4.1.9':
|
||||
resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
|
||||
|
||||
'@vitest/utils@2.1.9':
|
||||
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
|
||||
|
||||
'@vitest/utils@4.1.9':
|
||||
resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
|
||||
|
||||
'@volar/language-core@2.4.15':
|
||||
resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==}
|
||||
|
||||
@@ -2284,6 +2323,10 @@ packages:
|
||||
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
chai@6.2.2:
|
||||
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2492,6 +2535,9 @@ packages:
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
es-module-lexer@2.1.0:
|
||||
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2793,6 +2839,15 @@ packages:
|
||||
fault@2.0.1:
|
||||
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -2901,7 +2956,6 @@ packages:
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
globals@13.24.0:
|
||||
@@ -3478,6 +3532,10 @@ packages:
|
||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
obug@2.1.3:
|
||||
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3583,6 +3641,10 @@ packages:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
picomatch@4.0.4:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pify@2.3.0:
|
||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3875,6 +3937,9 @@ packages:
|
||||
std-env@3.10.0:
|
||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||
|
||||
std-env@4.1.0:
|
||||
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
|
||||
|
||||
string-argv@0.3.2:
|
||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
@@ -3973,6 +4038,14 @@ packages:
|
||||
tinyexec@1.0.1:
|
||||
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
|
||||
|
||||
tinyexec@1.2.4:
|
||||
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@1.1.1:
|
||||
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -3981,6 +4054,10 @@ packages:
|
||||
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tinyspy@3.0.2:
|
||||
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -4169,7 +4246,6 @@ packages:
|
||||
|
||||
unplugin-vue-router@0.10.9:
|
||||
resolution: {integrity: sha512-DXmC0GMcROOnCmN56GRvi1bkkG1BnVs4xJqNvucBUeZkmB245URvtxOfbo3H6q4SOUQQbLPYWd6InzvjRh363A==}
|
||||
deprecated: 'Merged into vuejs/router. Migrate: https://router.vuejs.org/guide/migration/v4-to-v5.html'
|
||||
peerDependencies:
|
||||
vue-router: ^4.4.0
|
||||
peerDependenciesMeta:
|
||||
@@ -4201,7 +4277,6 @@ packages:
|
||||
|
||||
uuid@10.0.0:
|
||||
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
uuid@11.1.0:
|
||||
@@ -4305,7 +4380,7 @@ packages:
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@vitest/browser': 2.1.9
|
||||
'@vitest/ui': 2.1.9
|
||||
happy-dom: '*'
|
||||
happy-dom: 16.8.1
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
@@ -4321,6 +4396,47 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.9:
|
||||
resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.9
|
||||
'@vitest/browser-preview': 4.1.9
|
||||
'@vitest/browser-webdriverio': 4.1.9
|
||||
'@vitest/coverage-istanbul': 4.1.9
|
||||
'@vitest/coverage-v8': 4.1.9
|
||||
'@vitest/ui': 4.1.9
|
||||
happy-dom: 16.8.1
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
'@edge-runtime/vm':
|
||||
optional: true
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
'@types/node':
|
||||
optional: true
|
||||
'@vitest/browser-playwright':
|
||||
optional: true
|
||||
'@vitest/browser-preview':
|
||||
optional: true
|
||||
'@vitest/browser-webdriverio':
|
||||
optional: true
|
||||
'@vitest/coverage-istanbul':
|
||||
optional: true
|
||||
'@vitest/coverage-v8':
|
||||
optional: true
|
||||
'@vitest/ui':
|
||||
optional: true
|
||||
happy-dom:
|
||||
optional: true
|
||||
jsdom:
|
||||
optional: true
|
||||
|
||||
vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||
|
||||
@@ -4467,7 +4583,7 @@ snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@antfu/eslint-config@3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))':
|
||||
'@antfu/eslint-config@3.16.0(@typescript-eslint/utils@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.21)(eslint-plugin-format@0.1.3(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@clack/prompts': 0.9.1
|
||||
@@ -4476,7 +4592,7 @@ snapshots:
|
||||
'@stylistic/eslint-plugin': 2.13.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
||||
'@typescript-eslint/eslint-plugin': 8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3))(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
||||
'@typescript-eslint/parser': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
||||
'@vitest/eslint-plugin': 1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))
|
||||
'@vitest/eslint-plugin': 1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))
|
||||
eslint: 9.35.0(jiti@2.5.1)
|
||||
eslint-config-flat-gitignore: 1.0.1(eslint@9.35.0(jiti@2.5.1))
|
||||
eslint-flat-config-utils: 1.1.0
|
||||
@@ -5047,7 +5163,7 @@ snapshots:
|
||||
|
||||
'@intlify/shared@10.0.8': {}
|
||||
|
||||
'@intlify/shared@11.4.8': {}
|
||||
'@intlify/shared@11.4.6': {}
|
||||
|
||||
'@intlify/shared@12.0.0-alpha.4': {}
|
||||
|
||||
@@ -5057,8 +5173,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.5.1))
|
||||
'@intlify/bundle-utils': 9.0.0(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))
|
||||
'@intlify/shared': 11.4.8
|
||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.8)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
|
||||
'@intlify/shared': 11.4.6
|
||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.50.1)
|
||||
'@typescript-eslint/scope-manager': 8.42.0
|
||||
'@typescript-eslint/typescript-estree': 8.42.0(typescript@5.6.3)
|
||||
@@ -5080,11 +5196,11 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.8)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))':
|
||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.6.3)))(vue@3.5.21(typescript@5.6.3))':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
optionalDependencies:
|
||||
'@intlify/shared': 11.4.8
|
||||
'@intlify/shared': 11.4.6
|
||||
'@vue/compiler-dom': 3.5.21
|
||||
vue: 3.5.21(typescript@5.6.3)
|
||||
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.6.3))
|
||||
@@ -5420,6 +5536,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
optional: true
|
||||
|
||||
'@stylistic/eslint-plugin@2.13.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
||||
@@ -5510,10 +5629,19 @@ snapshots:
|
||||
|
||||
'@types/argparse@1.0.38': {}
|
||||
|
||||
'@types/chai@5.2.3':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
assertion-error: 2.0.1
|
||||
optional: true
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
optional: true
|
||||
|
||||
'@types/default-gateway@7.2.2': {}
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
@@ -5711,14 +5839,14 @@ snapshots:
|
||||
vite: 5.4.21(@types/node@22.18.1)
|
||||
vue: 3.5.21(typescript@5.6.3)
|
||||
|
||||
'@vitest/eslint-plugin@1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@2.1.9(@types/node@22.18.1)(happy-dom@16.8.1))':
|
||||
'@vitest/eslint-plugin@1.3.9(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)(vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)))':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.42.0
|
||||
'@typescript-eslint/utils': 8.42.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.6.3)
|
||||
eslint: 9.35.0(jiti@2.5.1)
|
||||
optionalDependencies:
|
||||
typescript: 5.6.3
|
||||
vitest: 2.1.9(@types/node@22.18.1)(happy-dom@16.8.1)
|
||||
vitest: 4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -5729,6 +5857,16 @@ snapshots:
|
||||
chai: 5.3.3
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/expect@4.1.9':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
optional: true
|
||||
|
||||
'@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.18.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.9
|
||||
@@ -5737,31 +5875,69 @@ snapshots:
|
||||
optionalDependencies:
|
||||
vite: 5.4.21(@types/node@22.18.1)
|
||||
|
||||
'@vitest/mocker@4.1.9(vite@5.4.21(@types/node@22.18.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.9
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 5.4.21(@types/node@22.18.1)
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@2.1.9':
|
||||
dependencies:
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
optional: true
|
||||
|
||||
'@vitest/runner@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/utils': 2.1.9
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/runner@4.1.9':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.9
|
||||
pathe: 2.0.3
|
||||
optional: true
|
||||
|
||||
'@vitest/snapshot@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.1.9
|
||||
magic-string: 0.30.21
|
||||
pathe: 1.1.2
|
||||
|
||||
'@vitest/snapshot@4.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
optional: true
|
||||
|
||||
'@vitest/spy@2.1.9':
|
||||
dependencies:
|
||||
tinyspy: 3.0.2
|
||||
|
||||
'@vitest/spy@4.1.9':
|
||||
optional: true
|
||||
|
||||
'@vitest/utils@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 2.1.9
|
||||
loupe: 3.2.1
|
||||
tinyrainbow: 1.2.0
|
||||
|
||||
'@vitest/utils@4.1.9':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
optional: true
|
||||
|
||||
'@volar/language-core@2.4.15':
|
||||
dependencies:
|
||||
'@volar/source-map': 2.4.15
|
||||
@@ -6436,6 +6612,9 @@ snapshots:
|
||||
loupe: 3.2.1
|
||||
pathval: 2.0.1
|
||||
|
||||
chai@6.2.2:
|
||||
optional: true
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@@ -6611,6 +6790,9 @@ snapshots:
|
||||
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
es-module-lexer@2.1.0:
|
||||
optional: true
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -7059,6 +7241,11 @@ snapshots:
|
||||
dependencies:
|
||||
format: 0.2.2
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
optional: true
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
@@ -7850,6 +8037,9 @@ snapshots:
|
||||
|
||||
object-hash@3.0.0: {}
|
||||
|
||||
obug@2.1.3:
|
||||
optional: true
|
||||
|
||||
onetime@6.0.0:
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
@@ -7954,6 +8144,9 @@ snapshots:
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
picomatch@4.0.4:
|
||||
optional: true
|
||||
|
||||
pify@2.3.0: {}
|
||||
|
||||
pinia@2.3.1(typescript@5.6.3)(vue@3.5.21(typescript@5.6.3)):
|
||||
@@ -8237,6 +8430,9 @@ snapshots:
|
||||
|
||||
std-env@3.10.0: {}
|
||||
|
||||
std-env@4.1.0:
|
||||
optional: true
|
||||
|
||||
string-argv@0.3.2: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
@@ -8353,10 +8549,22 @@ snapshots:
|
||||
|
||||
tinyexec@1.0.1: {}
|
||||
|
||||
tinyexec@1.2.4:
|
||||
optional: true
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
optional: true
|
||||
|
||||
tinypool@1.1.1: {}
|
||||
|
||||
tinyrainbow@1.2.0: {}
|
||||
|
||||
tinyrainbow@3.1.0:
|
||||
optional: true
|
||||
|
||||
tinyspy@3.0.2: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
@@ -8810,6 +9018,35 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@4.1.9(@types/node@22.18.1)(happy-dom@16.8.1)(vite@5.4.21(@types/node@22.18.1)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.9
|
||||
'@vitest/mocker': 4.1.9(vite@5.4.21(@types/node@22.18.1))
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/runner': 4.1.9
|
||||
'@vitest/snapshot': 4.1.9
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
es-module-lexer: 2.1.0
|
||||
expect-type: 1.3.0
|
||||
magic-string: 0.30.21
|
||||
obug: 2.1.3
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.4
|
||||
std-env: 4.1.0
|
||||
tinybench: 2.9.0
|
||||
tinyexec: 1.2.4
|
||||
tinyglobby: 0.2.17
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 5.4.21(@types/node@22.18.1)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 22.18.1
|
||||
happy-dom: 16.8.1
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
optional: true
|
||||
|
||||
vscode-uri@3.1.0: {}
|
||||
|
||||
vue-chartjs@5.3.2(chart.js@4.5.0)(vue@3.5.21(typescript@5.6.3)):
|
||||
|
||||
Reference in New Issue
Block a user