Skip to content

Commit

Permalink
Support VPN improvements (#771)
Browse files Browse the repository at this point in the history
  • Loading branch information
quanganhdo authored Apr 19, 2024
1 parent 4ce0496 commit b8f0e5d
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public enum ExtensionMessage: RawRepresentable {
case simulateTunnelFatalError
case simulateTunnelMemoryOveruse
case simulateConnectionInterruption
case getConnectionThroughput
case getDataVolume
}

// This is actually an improved way to send messages.
Expand All @@ -68,7 +68,7 @@ public enum ExtensionMessage: RawRepresentable {
case simulateTunnelFatalError
case simulateTunnelMemoryOveruse
case simulateConnectionInterruption
case getConnectionThroughput
case getDataVolume

// swiftlint:disable:next cyclomatic_complexity function_body_length
public init?(rawValue data: Data) {
Expand Down Expand Up @@ -136,8 +136,8 @@ public enum ExtensionMessage: RawRepresentable {
case .simulateConnectionInterruption:
self = .simulateConnectionInterruption

case .getConnectionThroughput:
self = .getConnectionThroughput
case .getDataVolume:
self = .getDataVolume

case .none:
assertionFailure("Invalid data")
Expand Down Expand Up @@ -165,7 +165,7 @@ public enum ExtensionMessage: RawRepresentable {
case .simulateTunnelFatalError: return .simulateTunnelFatalError
case .simulateTunnelMemoryOveruse: return .simulateTunnelMemoryOveruse
case .simulateConnectionInterruption: return .simulateConnectionInterruption
case .getConnectionThroughput: return .getConnectionThroughput
case .getDataVolume: return .getDataVolume
}
}

Expand Down Expand Up @@ -215,7 +215,7 @@ public enum ExtensionMessage: RawRepresentable {
.simulateTunnelFatalError,
.simulateTunnelMemoryOveruse,
.simulateConnectionInterruption,
.getConnectionThroughput: break
.getDataVolume: break

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,7 @@ extension NetworkProtectionServerInfo {
extension NetworkProtectionServerInfo.ServerAttributes {

public var serverLocation: String {
let stateOrCountry = isUSServerLocation ? state : country
return "\(city), \(stateOrCountry.localizedUppercase)"
let fullCountryName = Locale.current.localizedString(forRegionCode: country)
return "\(city), \(fullCountryName ?? country.capitalized)"
}

private var isUSServerLocation: Bool {
return country.localizedUppercase == "US"
}

}
6 changes: 3 additions & 3 deletions Sources/NetworkProtection/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,8 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
simulateTunnelMemoryOveruse(completionHandler: completionHandler)
case .simulateConnectionInterruption:
simulateConnectionInterruption(completionHandler: completionHandler)
case .getConnectionThroughput:
getConnectionThroughput(completionHandler: completionHandler)
case .getDataVolume:
getDataVolume(completionHandler: completionHandler)
}
}

Expand Down Expand Up @@ -1150,7 +1150,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
}
}

private func getConnectionThroughput(completionHandler: ((Data?) -> Void)? = nil) {
private func getDataVolume(completionHandler: ((Data?) -> Void)? = nil) {
Task { @MainActor in
guard let (received, sent) = try? await adapter.getBytesTransmitted() else {
completionHandler?(nil)
Expand Down
34 changes: 34 additions & 0 deletions Sources/NetworkProtection/Settings/VPNLocationFormatting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// VPNLocationFormatting.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import SwiftUI

public protocol VPNLocationFormatting {
func emoji(for country: String?,
preferredLocation: VPNSettings.SelectedLocation) -> String?

func string(from location: String?,
preferredLocation: VPNSettings.SelectedLocation) -> String

@available(macOS 12, iOS 15, *)
func string(from location: String?,
preferredLocation: VPNSettings.SelectedLocation,
locationTextColor: Color,
preferredLocationTextColor: Color) -> AttributedString
}
29 changes: 29 additions & 0 deletions Sources/NetworkProtection/Status/DataVolume.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// DataVolume.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

public struct DataVolume: Codable, Equatable {
public let bytesSent: Int64
public let bytesReceived: Int64

public init(bytesSent: Int64 = 0, bytesReceived: Int64 = 0) {
self.bytesSent = bytesSent
self.bytesReceived = bytesReceived
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// DataVolumeObserver.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Combine
import Foundation
import NetworkExtension

public protocol DataVolumeObserver {
var publisher: AnyPublisher<DataVolume, Never> { get }
var recentValue: DataVolume { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// DataVolumeObserverThroughSession.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Combine
import Foundation
import NetworkExtension
import NotificationCenter
import Common

public class DataVolumeObserverThroughSession: DataVolumeObserver {
public lazy var publisher = subject.eraseToAnyPublisher()
public var recentValue: DataVolume {
subject.value
}

private let subject = CurrentValueSubject<DataVolume, Never>(.init())

private let tunnelSessionProvider: TunnelSessionProvider

// MARK: - Notifications

private let platformNotificationCenter: NotificationCenter
private let platformDidWakeNotification: Notification.Name
private var cancellables = Set<AnyCancellable>()

// MARK: - Timer

private static let interval: TimeInterval = .seconds(1)

// MARK: - Logging

private let log: OSLog

// MARK: - Initialization

public init(tunnelSessionProvider: TunnelSessionProvider,
platformNotificationCenter: NotificationCenter,
platformDidWakeNotification: Notification.Name,
log: OSLog = .networkProtection) {

self.platformNotificationCenter = platformNotificationCenter
self.platformDidWakeNotification = platformDidWakeNotification
self.tunnelSessionProvider = tunnelSessionProvider
self.log = log

start()
}

public func start() {
updateDataVolume()

Timer.publish(every: Self.interval, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.updateDataVolume()
}.store(in: &cancellables)

platformNotificationCenter.publisher(for: platformDidWakeNotification).sink { [weak self] notification in
self?.handleDidWake(notification)
}.store(in: &cancellables)
}

// MARK: - Handling Notifications

private func handleDidWake(_ notification: Notification) {
updateDataVolume()
}

// MARK: - Obtaining the data volume

private func updateDataVolume(session: NETunnelProviderSession) async {
guard let data: ExtensionMessageString = try? await session.sendProviderMessage(.getDataVolume) else {
return
}

let bytes = data.value.components(separatedBy: ",")
guard let receivedString = bytes.first, let sentString = bytes.last,
let received = Int64(receivedString), let sent = Int64(sentString) else {
return
}

subject.send(DataVolume(bytesSent: sent, bytesReceived: received))
}

private func updateDataVolume() {
Task {
guard let session = await tunnelSessionProvider.activeSession() else {
return
}

await updateDataVolume(session: session)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public protocol NetworkProtectionStatusReporter {
var connectionErrorObserver: ConnectionErrorObserver { get }
var connectivityIssuesObserver: ConnectivityIssueObserver { get }
var controllerErrorMessageObserver: ControllerErrorMesssageObserver { get }
var dataVolumeObserver: DataVolumeObserver { get }

func forceRefresh()
}
Expand Down Expand Up @@ -68,6 +69,7 @@ public final class DefaultNetworkProtectionStatusReporter: NetworkProtectionStat
public let connectionErrorObserver: ConnectionErrorObserver
public let connectivityIssuesObserver: ConnectivityIssueObserver
public let controllerErrorMessageObserver: ControllerErrorMesssageObserver
public let dataVolumeObserver: DataVolumeObserver

// MARK: - Init & deinit

Expand All @@ -76,13 +78,15 @@ public final class DefaultNetworkProtectionStatusReporter: NetworkProtectionStat
connectionErrorObserver: ConnectionErrorObserver,
connectivityIssuesObserver: ConnectivityIssueObserver,
controllerErrorMessageObserver: ControllerErrorMesssageObserver,
dataVolumeObserver: DataVolumeObserver,
distributedNotificationCenter: DistributedNotificationCenter = .default()) {

self.statusObserver = statusObserver
self.serverInfoObserver = serverInfoObserver
self.connectionErrorObserver = connectionErrorObserver
self.connectivityIssuesObserver = connectivityIssuesObserver
self.controllerErrorMessageObserver = controllerErrorMessageObserver
self.dataVolumeObserver = dataVolumeObserver
self.distributedNotificationCenter = distributedNotificationCenter

start()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// MockDataVolumeObserver.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Combine
import Foundation
import NetworkProtection

public final class MockDataVolumeObserver: DataVolumeObserver {
public init() {}
public let subject = CurrentValueSubject<DataVolume, Never>(.init())
lazy public var publisher = subject.eraseToAnyPublisher()
public var recentValue: DataVolume {
subject.value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class MockNetworkProtectionStatusReporter: NetworkProtectionStatusR
public let connectionErrorObserver: ConnectionErrorObserver
public let connectivityIssuesObserver: ConnectivityIssueObserver
public let controllerErrorMessageObserver: ControllerErrorMesssageObserver
public let dataVolumeObserver: DataVolumeObserver

// MARK: - Init & deinit

Expand All @@ -42,12 +43,14 @@ public final class MockNetworkProtectionStatusReporter: NetworkProtectionStatusR
connectionErrorObserver: ConnectionErrorObserver = MockConnectionErrorObserver(),
connectivityIssuesObserver: ConnectivityIssueObserver = MockConnectivityIssueObserver(),
controllerErrorMessageObserver: ControllerErrorMesssageObserver = MockControllerErrorMesssageObserver(),
dataVolumeObserver: DataVolumeObserver = MockDataVolumeObserver(),
distributedNotificationCenter: DistributedNotificationCenter = .default()) {

self.statusObserver = statusObserver
self.serverInfoObserver = serverInfoObserver
self.connectionErrorObserver = connectionErrorObserver
self.connectivityIssuesObserver = connectivityIssuesObserver
self.dataVolumeObserver = dataVolumeObserver
self.controllerErrorMessageObserver = controllerErrorMessageObserver
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class NetworkProtectionServerInfoTests: XCTestCase {
port: 42,
attributes: .init(city: "Amsterdam", country: "nl", state: "na"))

XCTAssertEqual(serverInfo.serverLocation, "Amsterdam, NL")
XCTAssertEqual(serverInfo.serverLocation, "Amsterdam, Netherlands")
}

func testWhenGettingServerLocation_AndAttributesExist_isUS_ThenServerLocationIsCityState() {
Expand All @@ -43,7 +43,7 @@ final class NetworkProtectionServerInfoTests: XCTestCase {
port: 42,
attributes: .init(city: "New York", country: "us", state: "ny"))

XCTAssertEqual(serverInfo.serverLocation, "New York, NY")
XCTAssertEqual(serverInfo.serverLocation, "New York, United States")
}

}

0 comments on commit b8f0e5d

Please sign in to comment.