summaryrefslogtreecommitdiffhomepage
path: root/ios/SpeedConnection
diff options
context:
space:
mode:
authorMojgan <mojgan.jelodar@mullvad.net>2026-04-23 16:47:40 +0200
committerMojgan <mojgan.jelodar@mullvad.net>2026-04-23 16:47:40 +0200
commit7944e9b1982feb3deba8871ab49e05d65886a235 (patch)
tree7b9c2482cf13631a4442b9f1d5a97f77b7eb78eb /ios/SpeedConnection
parent8ccdcafd4ce312f75ffabafc4ae93f8ef5bad736 (diff)
downloadmullvadvpn-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.swift41
-rw-r--r--ios/SpeedConnection/NetworkSpeedMonitor.swift119
-rw-r--r--ios/SpeedConnection/SpeedConnectionView.swift66
-rw-r--r--ios/SpeedConnection/SpeedConnectionViewModel.swift41
-rw-r--r--ios/SpeedConnection/TrafficData.swift26
-rw-r--r--ios/SpeedConnection/TrafficPackage.swift22
-rw-r--r--ios/SpeedConnection/TrafficSpeed.swift34
-rw-r--r--ios/SpeedConnection/TrafficStatus.swift17
-rw-r--r--ios/SpeedConnection/TrafficSummery.swift58
-rw-r--r--ios/SpeedConnection/Untitled.swift8
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.
+//
+