import Foundation struct HistoryItem { let outgoingDate: Date let outgoingMessage: Message let incomingDate: Date? let incomingMessage: Message? let response: ClientState? init(sent message: Message, date: Date) { self.outgoingDate = date self.outgoingMessage = message self.incomingDate = nil self.incomingMessage = nil self.response = nil } func didReceive(response: ClientState, date: Date?, message: Message?) -> HistoryItem { .init(sent: self, response: response, date: date, message: message) } func invalidated() -> HistoryItem { didReceive(response: .responseRejected(.invalidAuthentication), date: incomingDate, message: incomingMessage) } func notAuthenticated() -> HistoryItem { didReceive(response: .responseRejected(.missingKey), date: incomingDate, message: incomingMessage) } private init(sent: HistoryItem, response: ClientState, date: Date?, message: Message?) { self.outgoingDate = sent.outgoingDate self.outgoingMessage = sent.outgoingMessage self.incomingDate = date self.incomingMessage = message self.response = response } // MARK: Statistics var roundTripTime: TimeInterval? { incomingDate?.timeIntervalSince(outgoingDate) } var deviceTime: Date? { guard let timestamp = incomingMessage?.content.time else { return nil } return Date(timestamp: timestamp) } var requestLatency: TimeInterval? { deviceTime?.timeIntervalSince(outgoingDate) } var responseLatency: TimeInterval? { guard let deviceTime = deviceTime else { return nil } return incomingDate?.timeIntervalSince(deviceTime) } var clockOffset: Int? { guard let interval = roundTripTime, let deviceTime = deviceTime else { return nil } let estimatedArrival = outgoingDate.advanced(by: interval / 2) return Int(deviceTime.timeIntervalSince(estimatedArrival)) } // MARK: Coding static func testEncoding() { } var encoded: Data { var result = outgoingDate.encoded + outgoingMessage.encoded if let date = incomingDate { result += Data([1]) + date.encoded } else { result += Data([0]) } if let message = incomingMessage { result += Data([1]) + message.encoded } else { result += Data([0]) } result += response?.encoded ?? Data([0]) return result } init?(decodeFrom data: Data, index: inout Int) { guard let outgoingDate = Date(decodeFrom: data, index: &index) else { return nil } self.outgoingDate = outgoingDate guard let outgoingMessage = Message(decodeFrom: data, index: &index) else { return nil } self.outgoingMessage = outgoingMessage if data[index] > 0 { index += 1 guard let incomingDate = Date(decodeFrom: data, index: &index) else { return nil } self.incomingDate = incomingDate } else { self.incomingDate = nil index += 1 } if data[index] > 0 { index += 1 guard let incomingMessage = Message(decodeFrom: data, index: &index) else { return nil } self.incomingMessage = incomingMessage } else { self.incomingMessage = nil index += 1 } guard index < data.count else { return nil } self.response = ClientState(code: data[index]) index += 1 } } private extension Date { static var encodedSize: Int { MemoryLayout.size } var encoded: Data { .init(from: timeIntervalSince1970) } init?(decodeFrom data: Data, index: inout Int) { guard index + Date.encodedSize <= data.count else { return nil } self.init(timeIntervalSince1970: data.advanced(by: index).convert(into: .zero)) index += Date.encodedSize } } extension HistoryItem: Identifiable { var id: UInt32 { outgoingDate.timestamp } } extension HistoryItem: Comparable { static func < (lhs: HistoryItem, rhs: HistoryItem) -> Bool { lhs.outgoingDate < rhs.outgoingDate } }