diff options
| author | Mojgan <mojgan.jelodar@mullvad.net> | 2026-04-23 16:47:40 +0200 |
|---|---|---|
| committer | Mojgan <mojgan.jelodar@mullvad.net> | 2026-04-23 16:47:40 +0200 |
| commit | 7944e9b1982feb3deba8871ab49e05d65886a235 (patch) | |
| tree | 7b9c2482cf13631a4442b9f1d5a97f77b7eb78eb /ios/SpeedConnection | |
| parent | 8ccdcafd4ce312f75ffabafc4ae93f8ef5bad736 (diff) | |
| download | mullvadvpn-speed-connetcion-hackday.tar.xz mullvadvpn-speed-connetcion-hackday.zip | |
Speed connection testspeed-connetcion-hackday
Diffstat (limited to 'ios/SpeedConnection')
| -rw-r--r-- | ios/SpeedConnection/MockNetworkSpeedMonitor.swift | 41 | ||||
| -rw-r--r-- | ios/SpeedConnection/NetworkSpeedMonitor.swift | 119 | ||||
| -rw-r--r-- | ios/SpeedConnection/SpeedConnectionView.swift | 66 | ||||
| -rw-r--r-- | ios/SpeedConnection/SpeedConnectionViewModel.swift | 41 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficData.swift | 26 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficPackage.swift | 22 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficSpeed.swift | 34 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficStatus.swift | 17 | ||||
| -rw-r--r-- | ios/SpeedConnection/TrafficSummery.swift | 58 | ||||
| -rw-r--r-- | ios/SpeedConnection/Untitled.swift | 8 |
10 files changed, 432 insertions, 0 deletions
diff --git a/ios/SpeedConnection/MockNetworkSpeedMonitor.swift b/ios/SpeedConnection/MockNetworkSpeedMonitor.swift new file mode 100644 index 0000000000..941dec19fe --- /dev/null +++ b/ios/SpeedConnection/MockNetworkSpeedMonitor.swift @@ -0,0 +1,41 @@ +// +// MockNetworkSpeedMonitor.swift +// MullvadVPN +// +// Created by Mojgan on 2026-04-23. +// Copyright © 2026 Mullvad VPN AB. All rights reserved. +// +import Foundation + +final class MockNetworkSpeedMonitor: NetworkSpeedMonitorProtocol, @unchecked Sendable { + + var onUpdateTrafficSummery: (@Sendable (TrafficSummery) -> Void)? + + private var timer: Timer? + + func start(timeInterval: TimeInterval) { + timer?.invalidate() + + timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { [weak self] _ in + guard let self else { return } + + let sent = UInt64.random(in: 10_000...100_000) + let received = UInt64.random(in: 10_000...100_000) + + let new = TrafficPackage(wifi: TrafficData(sent: sent, received: received)) + + self.onUpdateTrafficSummery?( + TrafficSummery.make(self.previousTraffic, new: new, interval: timeInterval) + ) + + self.previousTraffic = new + } + } + + func stop() { + timer?.invalidate() + timer = nil + } + + private var previousTraffic = TrafficPackage() +} diff --git a/ios/SpeedConnection/NetworkSpeedMonitor.swift b/ios/SpeedConnection/NetworkSpeedMonitor.swift new file mode 100644 index 0000000000..8986bae361 --- /dev/null +++ b/ios/SpeedConnection/NetworkSpeedMonitor.swift @@ -0,0 +1,119 @@ +// +// NetworkSpeedMonitor.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation +import SystemConfiguration + +protocol NetworkSpeedMonitorProtocol { + var onUpdateTrafficSummery: (@Sendable (TrafficSummery) -> Void)? { get set } + func start(timeInterval: TimeInterval) + func stop() +} + +final class NetworkSpeedMonitor : NetworkSpeedMonitorProtocol { + private var timer: DispatchSourceTimer? = nil + private var previousTrafficPackage = TrafficPackage() + private var lock = NSLock() + private let timerQueue = DispatchQueue(label: "NetworkSpeedMonitorTimerQueue") + + var onUpdateTrafficSummery: (@Sendable (TrafficSummery) -> Void)? + + func start(timeInterval: TimeInterval = 1.0) { + timer?.cancel() + timer = DispatchSource.makeTimerSource(queue: timerQueue) + timer?.setEventHandler { [weak self] in + guard let self = self else { return } + self.measureSpeed() + } + timer?.schedule(wallDeadline: .now(), repeating: timeInterval) + timer?.resume() + } + + func stop() { + timer?.cancel() + timer = nil + } + + private func measureSpeed() { + lock.lock() + defer { + lock.unlock() + } + let newTrafficPacket = getTrafficPackage() + onUpdateTrafficSummery?(TrafficSummery.make(previousTrafficPackage, new: newTrafficPacket, interval: 1)) + previousTrafficPackage = newTrafficPacket + } + + private func getTrafficPackage() -> TrafficPackage { + var result = TrafficPackage() + var address: UnsafeMutablePointer<ifaddrs>? = nil + + guard getifaddrs(&address) == 0, let first = address else { + return result + } + + defer { + freeifaddrs(first) + } + + var pointer: UnsafeMutablePointer<ifaddrs>? = first + + while let current = pointer?.pointee { + + defer { + pointer = current.ifa_next + } + + let interfaceName = String(cString: current.ifa_name) + let interface = mapInterface(interfaceName) + + guard + let dataPtr = current.ifa_data + else { + continue + } + + let data = dataPtr.assumingMemoryBound(to: if_data.self).pointee + + let sent = UInt64(data.ifi_obytes) + let received = UInt64(data.ifi_ibytes) + + switch interface { + case .cellular: + result.cellular.sent += sent + result.cellular.received += received + + case .wifi: + result.wifi.sent += sent + result.wifi.received += received + + case .vpn: + result.vpn.sent += sent + result.vpn.received += received + + default: + break + } + } + + return result + } + + func mapInterface(_ name: String) -> InterfaceType { + if name.hasPrefix("en") { + return .wifi + } else if name.hasPrefix("pdp_ip") { + return .cellular + } else if name.hasPrefix("utun") || name.hasPrefix("ppp") { + return .vpn + } else if name == "lo0" { + return .loopback + } else { + return .other + } + } +} diff --git a/ios/SpeedConnection/SpeedConnectionView.swift b/ios/SpeedConnection/SpeedConnectionView.swift new file mode 100644 index 0000000000..0071f9cbc1 --- /dev/null +++ b/ios/SpeedConnection/SpeedConnectionView.swift @@ -0,0 +1,66 @@ +// +// SpeedConnectionView.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct SpeedConnectionView<ViewModel: SpeedConnectionViewModelProtocol>: View { + @State private var uploadValue: Double = 0.0 + @State private var downloadValue: Double = 0.0 + + var viewModel: ViewModel + + init(viewModel: ViewModel) { + self.viewModel = viewModel + } + + var body: some View { + VStack { + Text("Download: \(uploadValue / 1024.0) KB") + .font(.mullvadTiny) + Text("Upload: \(downloadValue / 1024.0) KB") + .font(.mullvadTiny) + } + .padding(8.0) + .onAppear { + viewModel.startMonitoring() + } + .onDisappear { + viewModel.stopMonitoring() + } + } +} + +#Preview { + SpeedConnectionView(viewModel: MockSpeedConnectionViewModel()) +} + +final class MockSpeedConnectionViewModel: SpeedConnectionViewModelProtocol,@unchecked Sendable { + + var uploadValue: Double = 0.0 + var downloadValue: Double = 0.0 + + private var timer: Timer? + + func startMonitoring() { + timer?.invalidate() + + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + let upload = Double.random(in: 10_000...100_000) + let download = Double.random(in: 10_000...100_000) + Task { @MainActor in + self.uploadValue = upload + self.downloadValue = download + } + } + } + + func stopMonitoring() { + timer?.invalidate() + timer = nil + } +} diff --git a/ios/SpeedConnection/SpeedConnectionViewModel.swift b/ios/SpeedConnection/SpeedConnectionViewModel.swift new file mode 100644 index 0000000000..a06e857cc4 --- /dev/null +++ b/ios/SpeedConnection/SpeedConnectionViewModel.swift @@ -0,0 +1,41 @@ +// +// SpeedConnectionViewModel.swift +// MullvadVPN +// +// Created by Mojgan on 2026-04-23. +// Copyright © 2026 Mullvad VPN AB. All rights reserved. +// + +import Combine + +protocol SpeedConnectionViewModelProtocol: ObservableObject { + var uploadValue: Double { get } + var downloadValue: Double { get } + + func startMonitoring() + func stopMonitoring() +} + + +class SpeedConnectionViewModel: SpeedConnectionViewModelProtocol,@unchecked Sendable { + @Published var uploadValue: Double = 0.0 + @Published var downloadValue: Double = 0.0 + var networkSpeedMonitor : NetworkSpeedMonitorProtocol + + init(networkSpeedMonitor : NetworkSpeedMonitorProtocol) { + self.networkSpeedMonitor = networkSpeedMonitor + self.networkSpeedMonitor.onUpdateTrafficSummery = { [weak self] trafficSummery in + Task { @MainActor in + self?.uploadValue = trafficSummery.speed.sent + self?.downloadValue = trafficSummery.speed.received + } + } + } + func startMonitoring() { + self.networkSpeedMonitor.start(timeInterval: 1.0) + } + + func stopMonitoring() { + self.networkSpeedMonitor.stop() + } +} diff --git a/ios/SpeedConnection/TrafficData.swift b/ios/SpeedConnection/TrafficData.swift new file mode 100644 index 0000000000..ce855a833a --- /dev/null +++ b/ios/SpeedConnection/TrafficData.swift @@ -0,0 +1,26 @@ +// +// TrafficData.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + + +struct TrafficData { + var sent: UInt64 = 0// in bytes + var received: UInt64 = 0 // in bytes +} + +extension TrafficData { + static var zero: TrafficData { + TrafficData() + } +} + +func +(lhs: TrafficData, rhs: TrafficData) -> TrafficData { + var result = lhs + result.received += rhs.received + result.sent += rhs.sent + return result +} diff --git a/ios/SpeedConnection/TrafficPackage.swift b/ios/SpeedConnection/TrafficPackage.swift new file mode 100644 index 0000000000..62ac1d2d62 --- /dev/null +++ b/ios/SpeedConnection/TrafficPackage.swift @@ -0,0 +1,22 @@ +// +// TrafficPackage.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation + +enum InterfaceType { + case wifi + case cellular + case vpn + case loopback + case other +} + +struct TrafficPackage { + var wifi = TrafficData() + var cellular = TrafficData() + var vpn = TrafficData() +} diff --git a/ios/SpeedConnection/TrafficSpeed.swift b/ios/SpeedConnection/TrafficSpeed.swift new file mode 100644 index 0000000000..1ba81f9ed0 --- /dev/null +++ b/ios/SpeedConnection/TrafficSpeed.swift @@ -0,0 +1,34 @@ +// +// TrafficSpeed.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +struct TrafficSpeed { + var received: Double + var sent: Double + + private init(received: Double, sent: Double) { + self.received = received + self.sent = sent + } + + public init(old: TrafficData, new: TrafficData, interval: Double) { + self.received = Double((new.received - old.received)) / interval + self.sent = Double((new.sent - old.sent)) / interval + } +} +extension TrafficSpeed { + static var zero: TrafficSpeed { + .init(received: 0, sent: 0) + } +} + +func +(lhs: TrafficSpeed, rhs: TrafficSpeed) -> TrafficSpeed { + var result = lhs + result.received += rhs.received + result.sent += rhs.sent + return result +} diff --git a/ios/SpeedConnection/TrafficStatus.swift b/ios/SpeedConnection/TrafficStatus.swift new file mode 100644 index 0000000000..ebd3776989 --- /dev/null +++ b/ios/SpeedConnection/TrafficStatus.swift @@ -0,0 +1,17 @@ +// +// TrafficStatus.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + +struct TrafficStatus { + var speed: TrafficSpeed + var data: TrafficData + + init(speed: TrafficSpeed = .zero, data: TrafficData = .zero) { + self.speed = speed + self.data = data + } +} diff --git a/ios/SpeedConnection/TrafficSummery.swift b/ios/SpeedConnection/TrafficSummery.swift new file mode 100644 index 0000000000..7ef20ab29b --- /dev/null +++ b/ios/SpeedConnection/TrafficSummery.swift @@ -0,0 +1,58 @@ +// +// TrafficSummery.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// +import Foundation + +struct TrafficSummery { + let wifi: TrafficStatus + let cellular: TrafficStatus + let vpn: TrafficStatus? + + init( + wifi: TrafficStatus = TrafficStatus(), + cellular: TrafficStatus = TrafficStatus(), + vpn: TrafficStatus? = nil + ) { + self.wifi = wifi + self.cellular = cellular + self.vpn = vpn + } + + var speed: TrafficSpeed { + wifi.speed + cellular.speed + (vpn?.speed ?? .zero) + } + + private var data: TrafficData { + if let vpn = vpn { + return vpn.data + } + return wifi.data + cellular.data + } + + static func make(_ old: TrafficPackage, new: TrafficPackage, interval: TimeInterval) -> Self { + TrafficSummery( + wifi: TrafficStatus( + speed: TrafficSpeed( + old: old.wifi, + new: new.wifi, + interval: interval), + data: new.wifi), + cellular: TrafficStatus( + speed: TrafficSpeed( + old: old.cellular, + new: new.cellular, + interval: interval), + data: new.cellular), + vpn: TrafficStatus( + speed: TrafficSpeed( + old: old.vpn, + new: new.vpn, + interval: interval), + data: new.vpn)) + } + +} diff --git a/ios/SpeedConnection/Untitled.swift b/ios/SpeedConnection/Untitled.swift new file mode 100644 index 0000000000..23d31d97a8 --- /dev/null +++ b/ios/SpeedConnection/Untitled.swift @@ -0,0 +1,8 @@ +// +// Untitled.swift +// MullvadVPN +// +// Created by Mojgan on 2025-10-31. +// Copyright © 2025 Mullvad VPN AB. All rights reserved. +// + |
