summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadSettings/IPOverrideRepository.swift
blob: 1e52f1dc52093405b7a2bb45c9cf5be1c3e4783c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//
//  IPOverrideRepository.swift
//  MullvadVPN
//
//  Created by Jon Petersson on 2024-01-16.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

@preconcurrency import Combine
import MullvadLogging

public protocol IPOverrideRepositoryProtocol: Sendable {
    var overridesPublisher: AnyPublisher<[IPOverride], Never> { get }
    func add(_ overrides: [IPOverride])
    func fetchAll() -> [IPOverride]
    func deleteAll()
    func parse(data: Data) throws -> [IPOverride]
}

public final class IPOverrideRepository: IPOverrideRepositoryProtocol {
    private let overridesSubject: CurrentValueSubject<[IPOverride], Never> = .init([])
    public var overridesPublisher: AnyPublisher<[IPOverride], Never> {
        overridesSubject.eraseToAnyPublisher()
    }

    nonisolated(unsafe) private let logger = Logger(label: "IPOverrideRepository")
    private let readWriteLock = NSLock()

    public init() {}

    public func add(_ overrides: [IPOverride]) {
        var storedOverrides = fetchAll()

        overrides.forEach { override in
            if let existingOverrideIndex = storedOverrides.firstIndex(where: { $0.hostname == override.hostname }) {
                var existingOverride = storedOverrides[existingOverrideIndex]

                if let ipv4Address = override.ipv4Address {
                    existingOverride.ipv4Address = ipv4Address
                }

                if let ipv6Address = override.ipv6Address {
                    existingOverride.ipv6Address = ipv6Address
                }

                storedOverrides[existingOverrideIndex] = existingOverride
            } else {
                storedOverrides.append(override)
            }
        }

        do {
            try writeIpOverrides(storedOverrides)
        } catch {
            logger.error("Could not add override(s): \(overrides) \nError: \(error)")
        }
    }

    public func fetchAll() -> [IPOverride] {
        return (try? readIpOverrides()) ?? []
    }

    public func deleteAll() {
        do {
            try readWriteLock.withLock {
                try SettingsManager.store.delete(key: .ipOverrides)
                overridesSubject.send([])
            }
        } catch {
            logger.error("Could not delete all overrides. \nError: \(error)")
        }
    }

    public func parse(data: Data) throws -> [IPOverride] {
        let decoder = JSONDecoder()
        let jsonData = try decoder.decode(RelayOverrides.self, from: data)

        return jsonData.overrides
    }

    private func readIpOverrides() throws -> [IPOverride] {
        try readWriteLock.withLock {
            let parser = makeParser()
            let data = try SettingsManager.store.read(key: .ipOverrides)
            return try parser.parseUnversionedPayload(as: [IPOverride].self, from: data)
        }
    }

    private func writeIpOverrides(_ overrides: [IPOverride]) throws {
        let parser = makeParser()
        let data = try parser.produceUnversionedPayload(overrides)

        try readWriteLock.withLock {
            try SettingsManager.store.write(data, for: .ipOverrides)
            overridesSubject.send(overrides)
        }
    }

    private func makeParser() -> SettingsParser {
        SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
    }
}