import Foundation struct DomainState: Codable { /// The date of the last successful update let date: Date /// The last registered addresses let addresses: Set /** Number of successive errors signaled by the server. Needed to prevent excessive requests and being flagged as abuse. */ let serverErrorCount: Int /** Number of errors on the client side. Errors here will not force the algorithm to back off. */ let clientErrorCount: Int /** The time to wait before the next update attempt */ let currentWaitPeriod: TimeInterval let lastResult: DNSChangeResponse init(date: Date, addresses: Set, serverErrorCount: Int, clientErrorCount: Int, currentWaitPeriod: TimeInterval, lastResult: DNSChangeResponse) { self.date = date self.addresses = addresses self.serverErrorCount = serverErrorCount self.clientErrorCount = clientErrorCount self.currentWaitPeriod = currentWaitPeriod self.lastResult = lastResult } init(initial date: Date) { self.date = date self.addresses = [] self.serverErrorCount = 0 self.clientErrorCount = 0 self.currentWaitPeriod = 0 self.lastResult = .success } var nextUpdate: Date { date.addingTimeInterval(currentWaitPeriod) } private static let formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .short return formatter }() func logEntry(domain: String) -> String { "\(domain);\(DomainState.formatter.string(from: date));\(addressList);\(serverErrorCount);\(clientErrorCount);\(currentWaitPeriod);\(lastResult.rawValue)" } var addressList: String { addresses.sortedList } func needsUpdate(at now: Date, to addresses: Set) -> Bool { guard now >= nextUpdate else { return false } return !self.addresses.subtracting(addresses).isEmpty } func updated(_ now: Date, from result: DNSChangeResponse, expecting addresses: Set) -> DomainState { let addresses = result.isSuccessStatus ? addresses : self.addresses let serverError = result.isServerError ? serverErrorCount + 1 : 0 let clientError = result.isClientError ? clientErrorCount + 1 : 0 let waitTime = result.delayTime(previous: currentWaitPeriod) return .init( date: now, addresses: addresses, serverErrorCount: serverError, clientErrorCount: clientError, currentWaitPeriod: waitTime, lastResult: result) } } extension DomainState: Equatable { static func == (lhs: DomainState, rhs: DomainState) -> Bool { lhs.date == rhs.date && lhs.addresses == rhs.addresses && lhs.clientErrorCount == rhs.clientErrorCount && lhs.serverErrorCount == rhs.serverErrorCount && lhs.currentWaitPeriod == rhs.currentWaitPeriod && lhs.lastResult == rhs.lastResult } }