aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2023-11-29 20:13:23 -0800
committerDustin Mierau <dustin@mierau.me>2023-11-29 20:13:23 -0800
commit30255476781789df318a571622ab732681696139 (patch)
tree7e05705c09c1a455ca6573af686f9eb0629df894
parent2bdcc0c3d751d7c7626d8b0e9f79331b803c668b (diff)
Further work on protocol.
-rw-r--r--Hotline/Network/HotlineClient.swift364
-rw-r--r--Hotline/Network/HotlineProtocol.swift212
-rw-r--r--Hotline/Utility/DataExtensions.swift15
3 files changed, 435 insertions, 156 deletions
diff --git a/Hotline/Network/HotlineClient.swift b/Hotline/Network/HotlineClient.swift
index d3b6c98..5193fe5 100644
--- a/Hotline/Network/HotlineClient.swift
+++ b/Hotline/Network/HotlineClient.swift
@@ -5,6 +5,8 @@ enum HotlineClientStatus: Int {
case disconnected
case connecting
case connected
+ case loggingIn
+ case loggedIn
}
class HotlineClient : ObservableObject {
@@ -16,25 +18,37 @@ class HotlineClient : ObservableObject {
0x00, 0x01, // Version
0x00, 0x02, // Sub-version
])
-
+
@Published var connectionStatus: HotlineClientStatus = .disconnected
+ @Published var agreement: String?
+ @Published var userList: [HotlineUser] = []
+ @Published var chatMessages: [String] = []
+
+ let userName: String = "bolt"
+ let userIconID: UInt32 = 128
var server: HotlineServer?
var connection: NWConnection?
- var bytes = Data()
- var handshakeComplete = false
init() {
}
+ // MARK: -
+
func connect(to server: HotlineServer) {
self.server = server
let serverAddress = NWEndpoint.Host(server.address)
let serverPort = NWEndpoint.Port(rawValue: server.port)!
- self.connection = NWConnection(host: serverAddress, port: serverPort, using: .tcp)
+ let tcpOptions = NWProtocolTCP.Options()
+ tcpOptions.enableKeepalive = true
+ tcpOptions.keepaliveInterval = 30
+ let connectionParameters: NWParameters
+ connectionParameters = NWParameters(tls: nil, tcp: tcpOptions)
+
+ self.connection = NWConnection(host: serverAddress, port: serverPort, using: connectionParameters)
self.connection?.stateUpdateHandler = { [weak self] (newState: NWConnection.State) in
switch newState {
case .ready:
@@ -65,85 +79,28 @@ class HotlineClient : ObservableObject {
}
private func disconnect() {
- guard let c = connection else {
- print("HotlineClient: already disconnected")
- return
- }
-
- c.cancel()
+ self.connection?.cancel()
self.connection = nil
- }
-
- private func sendHandshake() {
- guard let c = connection else {
- print("HotlineClient: invalid connection to send handshake.")
- return
- }
- c.send(content: HotlineClient.handshakePacket, completion: .contentProcessed { [weak self] (error) in
- if let err = error {
- print("HotlineClient: sending magic failed \(err)")
- return
- }
-
- print("HotlineClient: sent handshake packet!")
-
- self?.receiveHandshake()
- })
- }
-
- private func receiveHandshake() {
- guard let c = connection else {
- print("HotlineTracker: invalid connection to receive magic.")
- return
- }
-
- print("HotlineClient: receiving handshake...")
- c.receive(minimumIncompleteLength: 8, maximumLength: 8) { [weak self] (data, context, isComplete, error) in
- guard let self = self, let data = data else {
- return
- }
-
- if data.isEmpty {
- print("HotlineClient: empty handshake response")
- self.disconnect()
- return
- }
-
- let protocolID = data.readUInt32(at: 0)!
- if protocolID != 0x54525450 { // 'TRTP'
- print("HotlineClient: invalid handshake protocol ID \(protocolID)")
- self.disconnect()
- return
- }
-
- let errorCode = data.readUInt32(at: 4)!
- if errorCode != 0 { // 0 == no error
- print("HotlineClient: handshake error", errorCode)
- self.disconnect()
- return
- }
-
- print("HotlineClient: completed handshake")
- self.sendLogin()
+ DispatchQueue.main.async {
+ self.connectionStatus = .disconnected
}
}
- private func sendLogin() {
+ // MARK: -
+
+ private func sendTransaction(_ t: HotlineTransaction, autodisconnect disconnectOnError: Bool = true, callback: (() -> Void)? = nil) {
guard let c = connection else {
- print("HotlineClient: no connection for transaction.")
return
}
- c.send(content: HotlineClient.handshakePacket, completion: .contentProcessed { [weak self] (error) in
- if let err = error {
- print("HotlineClient: sending login failed \(err)")
+ c.send(content: t.encoded(), completion: .contentProcessed { [weak self] (error) in
+ if disconnectOnError, error != nil {
+ self?.disconnect()
return
}
- print("HotlineClient: sent handshake packet!")
-
-
+ callback?()
})
}
@@ -153,67 +110,86 @@ class HotlineClient : ObservableObject {
return
}
- print("HotlineClient: waiting on transaction header...")
- c.receive(minimumIncompleteLength: HotlineTransaction.headerSize, maximumLength: HotlineTransaction.headerSize) { [weak self] (data, context, isComplete, error) in
+ print("HotlineClient: waiting for transaction...")
+ c.receive(minimumIncompleteLength: HotlineTransaction.headerSize, maximumLength: HotlineTransaction.headerSize) { [weak self] (headerData, context, isComplete, error) in
guard let self = self else {
return
}
if let error = error {
- print("HotlineClient: receive error \(error)")
+ print("HotlineClient: transaction error \(error)")
self.disconnect()
return
}
- if let data = data, !data.isEmpty {
- print("HotlineClient: received \(data.count) header bytes")
-
- let transaction = self.parseTransaction(data: data)
- if var t = transaction {
- if t.dataSize > 0 {
- c.receive(minimumIncompleteLength: Int(t.dataSize), maximumLength: Int(t.dataSize)) { [weak self] (data, context, isComplete, error) in
- guard let self = self else {
- return
- }
-
- if let data = data, !data.isEmpty {
- t.parameterCount = data.readUInt16(at: 0)!
-
- if t.parameterCount > 0 {
- t.parameters = []
- var dataCursor = 2
- for _ in 0..<t.parameterCount {
- if
- let fieldID = data.readUInt16(at: dataCursor),
- let fieldSize = data.readUInt16(at: dataCursor + 2),
- let fieldData = data.readData(at: dataCursor + 4, length: Int(fieldSize)) {
- t.parameters?.append(HotlineTransactionParameter(id: fieldID, dataSize: fieldSize, data: fieldData))
-
- dataCursor += 4 + Int(fieldSize)
- }
+ guard let headerData = headerData, !headerData.isEmpty else {
+ self.receiveTransaction()
+ return
+ }
+
+ print("HotlineClient: received \(headerData.count) header bytes")
+
+ if var transaction = self.parseTransaction(data: headerData) {
+ // Receive additional data if the transaction has data attached to it.
+ print("DATA SIZE: \(transaction.dataSize)")
+ if transaction.dataSize > 0 {
+ c.receive(minimumIncompleteLength: Int(transaction.dataSize), maximumLength: Int(transaction.dataSize)) { [weak self] (parameterData, context, isComplete, error) in
+ guard let self = self else {
+ return
+ }
+
+ guard let parameterData = parameterData, !parameterData.isEmpty else {
+ print("HotlineClient: transaction parameter data is empty!")
+ self.disconnect()
+ return
+ }
+
+ let parameterCount = parameterData.readUInt16(at: 0)!
+
+ if parameterCount > 0 {
+ var dataCursor = 2
+ for _ in 0..<parameterCount {
+ if
+ let fieldID = parameterData.readUInt16(at: dataCursor),
+ let fieldSize = parameterData.readUInt16(at: dataCursor + 2),
+ let fieldData = parameterData.readData(at: dataCursor + 4, length: Int(fieldSize)) {
+
+ if let fieldType = HotlineTransactionFieldType(rawValue: fieldID) {
+ transaction.parameters.append(HotlineTransactionParameter(type: fieldType, dataSize: fieldSize, data: fieldData))
+// transaction.parameters[fieldType] = HotlineTransactionParameter(type: fieldType, dataSize: fieldSize, data: fieldData)
+ }
+ else {
+ print("HotlineClient: UNKNOWN PARAM TYPE!", fieldID, fieldSize)
}
+
+ dataCursor += 4 + Int(fieldSize)
}
}
- self.processTransaction(t)
+ // Process the transaction if we have processed more than zero parameters here
+ // as we expect parameters at this point.
+ self.processTransaction(transaction)
}
- }
- else {
- self.processTransaction(t)
+
+ // Continue receiving transactions.
+ self.receiveTransaction()
}
}
-
-// print("HotlineTracker: server count = \(self.serverCount)")
+ else {
+ // In this case we have no further data to receive so we simply
+ // process the transaction and then continue receiving.
+ self.processTransaction(transaction)
+ self.receiveTransaction()
+ }
+ }
+ else {
+ // Here we failed to parse the current transaction.
+ // We should consider disconnecting perhaps.
+ // But for now we'll continue receiving.
+ self.receiveTransaction()
}
-
-// self.receiveListing()
-// }
}
}
-
- private func processTransaction(_ transaction: HotlineTransaction) {
- print("HotlineClient processing transaction \(transaction.type) with \(transaction.parameterCount) parameters")
- }
private func parseTransaction(data: Data) -> HotlineTransaction? {
if
@@ -225,17 +201,161 @@ class HotlineClient : ObservableObject {
let transactionSize = data.readUInt32(at: 12),
let dataSize = data.readUInt32(at: 16) {
- return HotlineTransaction(
- flags: flags,
- isReply: isReply,
- type: HotlineTransactionType(rawValue: type) ?? HotlineTransactionType.unknown,
- id: id,
- errorCode: errorCode,
- totalSize: transactionSize,
- dataSize: dataSize
- )
+ print("HotlineClient: Parsing transaction type \(type) with data \(dataSize)")
+ if let transactionType = HotlineTransactionType(rawValue: type) {
+ return HotlineTransaction(type: transactionType, flags: flags, isReply: isReply, id: id, errorCode: errorCode, totalSize: transactionSize, dataSize: dataSize)
+ }
}
return nil
}
+
+ // MARK: - Messages
+
+ private func sendHandshake() {
+ guard let c = connection else {
+ print("HotlineClient: invalid connection to send handshake.")
+ return
+ }
+
+ c.send(content: HotlineClient.handshakePacket, completion: .contentProcessed { [weak self] (error) in
+ if let err = error {
+ print("HotlineClient: sending handshake failed \(err)")
+ return
+ }
+ print("HotlineClient: receiving handshake...")
+ c.receive(minimumIncompleteLength: 8, maximumLength: 8) { [weak self] (data, context, isComplete, error) in
+ guard let self = self, let data = data else {
+ return
+ }
+
+ if data.isEmpty {
+ print("HotlineClient: empty handshake response")
+ self.disconnect()
+ return
+ }
+
+ let protocolID = data.readUInt32(at: 0)!
+ if protocolID != 0x54525450 { // 'TRTP'
+ print("HotlineClient: invalid handshake protocol ID \(protocolID)")
+ self.disconnect()
+ return
+ }
+
+ let errorCode = data.readUInt32(at: 4)!
+ if errorCode != 0 { // 0 == no error
+ print("HotlineClient: handshake error", errorCode)
+ self.disconnect()
+ return
+ }
+
+ print("HotlineClient: completed handshake")
+ self.sendLogin()
+ self.receiveTransaction()
+ }
+ })
+ }
+
+ func sendLogin(callback: (() -> Void)? = nil) {
+ DispatchQueue.main.async {
+ self.connectionStatus = .loggingIn
+ }
+
+ var t = HotlineTransaction(type: .login)
+ t.setParameterEncodedString(type: .userLogin, val: "")
+ t.setParameterEncodedString(type: .userPassword, val: "")
+ t.setParameterUInt32(type: .userIconID, val: self.userIconID)
+ t.setParameterString(type: .userName, val: self.userName)
+ t.setParameterUInt32(type: .versionNumber, val: 151)
+
+ print("HotlineClient: logging in...")
+ self.sendTransaction(t) { [weak self] in
+ print("HotlineClient: logged in!")
+ DispatchQueue.main.async {
+ self?.connectionStatus = .loggedIn
+ }
+
+ callback?()
+ }
+ }
+
+ func sendAgree(callback: (() -> Void)? = nil) {
+ var t = HotlineTransaction(type: .agreed)
+ t.setParameterString(type: .userName, val: self.userName)
+ t.setParameterUInt32(type: .userIconID, val: self.userIconID)
+ t.setParameterUInt32(type: .options, val: 0)
+
+ print("HotlineClient: agreeing")
+ self.sendTransaction(t, callback: callback)
+ }
+
+ func sendChat(message: String, callback: (() -> Void)? = nil) {
+ var t = HotlineTransaction(type: .sendChat)
+ t.setParameterString(type: .data, val: message)
+
+ print("HotlineClient: sending chat...")
+ self.sendTransaction(t, callback: callback)
+ }
+
+ func sendGetUserList(callback: (() -> Void)? = nil) {
+ let t = HotlineTransaction(type: .getUserNameList)
+ print("HotlineClient: fetching user list...")
+ self.sendTransaction(t, callback: callback)
+ }
+
+ // MARK: - Incoming
+
+ private func processTransaction(_ transaction: HotlineTransaction) {
+ switch(transaction.type) {
+ case .reply:
+ print("HotlineClient: GOT REPLY TRANSACTION? \(transaction)")
+ case .chatMessage:
+ print("HotlineClient: CHAT MESSAGE!")
+ if
+ let chatTextParam = transaction.getParameter(type: .data),
+ let chatText = chatTextParam.getString(),
+ let userNameParam = transaction.getParameter(type: .userName),
+ let userName = userNameParam.getString(),
+ let userIDParam = transaction.getParameter(type: .userID),
+ let userID = userIDParam.getUInt16() {
+ print("HotlineClient: \(userName):\(userID): \(chatText)")
+ DispatchQueue.main.async {
+ self.chatMessages.append(chatText)
+ }
+ }
+ case .getUserNameList:
+ print("HotlineClient: GOT USER NAME LIST!")
+ let userList = transaction.getParameterList(type: .userInfo)
+ for u in userList {
+ let userInfo = u.getUserInfo()
+ print("HotlineClient: user \(userInfo.userName)")
+ }
+ case .notifyOfUserChange:
+ print("HotlineClient: user changed")
+ if let p = transaction.getParameter(type: .userName),
+ let userName = p.getString() {
+ print("HotlineClient: user name \(userName)")
+ }
+ case .disconnectMessage:
+ print("HotlineClient: DISCONNECTED BY SERVER!")
+ self.disconnect()
+ case .showAgreement:
+ if let agreementParam = transaction.getParameter(type: .data) {
+ if let agreementText = agreementParam.getString() {
+ print("AGREEMENT:", agreementText)
+ DispatchQueue.main.async {
+ self.agreement = agreementText
+ }
+ self.sendAgree() {
+// self.sendGetUserList()
+ }
+ }
+ }
+ case .userAccess:
+ print("HotlineClient: user access transaction.")
+ default:
+ print("HotlineClient: UNKNOWN transaction \(transaction.type) with \(transaction.parameters.count) parameters")
+ print(transaction.parameters)
+ }
+ }
}
diff --git a/Hotline/Network/HotlineProtocol.swift b/Hotline/Network/HotlineProtocol.swift
index 787675e..d5fe734 100644
--- a/Hotline/Network/HotlineProtocol.swift
+++ b/Hotline/Network/HotlineProtocol.swift
@@ -17,25 +17,145 @@ struct HotlineServer: Identifiable, Hashable {
}
}
-struct HotlineTransactionParameter {
+struct HotlineUser: Identifiable, Hashable {
let id: UInt16
+ let userName: String
+
+ static func == (lhs: HotlineUser, rhs: HotlineUser) -> Bool {
+ return lhs.id == rhs.id
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(self.id)
+ }
+}
+
+struct HotlineUserInfo {
+ let id: UInt16
+ let iconID: UInt16
+ let flags: UInt16
+ let userName: String
+
+ init(data: Data) {
+ self.id = data.readUInt16(at: 0)!
+ self.iconID = data.readUInt16(at: 2)!
+ self.flags = data.readUInt16(at: 4)!
+
+ let userNameLength = data.readUInt16(at: 6)!
+ self.userName = data.readString(at: 8, length: Int(userNameLength), encoding: .ascii)!
+ }
+}
+
+struct HotlineTransactionParameter {
+ let type: HotlineTransactionFieldType
let dataSize: UInt16
let data: Data
+
+ func getUInt8() -> UInt8? {
+ return data.readUInt8(at: 0)
+ }
+
+ func getUInt16() -> UInt16? {
+ return data.readUInt16(at: 0)
+ }
+
+ func getUInt32() -> UInt32? {
+ return data.readUInt32(at: 0)
+ }
+
+ func getString(encoding: String.Encoding = .ascii) -> String? {
+ return String(data: self.data, encoding: encoding)
+ }
+
+ func getUserInfo() -> HotlineUserInfo {
+ return HotlineUserInfo(data: self.data)
+ }
}
struct HotlineTransaction {
static let headerSize = 20
+ static var sequenceID: UInt32 = 1
+
+ static func nextID() -> UInt32 {
+ HotlineTransaction.sequenceID += 1
+ return HotlineTransaction.sequenceID
+ }
- let flags: UInt8
- let isReply: UInt8
- let type: HotlineTransactionType
- let id: UInt32
- let errorCode: UInt32
- let totalSize: UInt32
- let dataSize: UInt32
+ var flags: UInt8 = 0
+ var isReply: UInt8 = 0
+ var type: HotlineTransactionType
+ var id: UInt32 = HotlineTransaction.nextID()
+ var errorCode: UInt32 = 0
+ var totalSize: UInt32 = UInt32(HotlineTransaction.headerSize)
+ var dataSize: UInt32 = 0
- var parameterCount: UInt16 = 0
- var parameters: [HotlineTransactionParameter]? = nil
+ var parameters: [HotlineTransactionParameter] = []
+
+ init(type: HotlineTransactionType) {
+ self.type = type
+ }
+
+ init(type: HotlineTransactionType, flags: UInt8, isReply: UInt8, id: UInt32, errorCode: UInt32, totalSize: UInt32, dataSize: UInt32) {
+ self.type = type
+ self.flags = flags
+ self.isReply = isReply
+ self.id = id
+ self.errorCode = errorCode
+ self.totalSize = totalSize
+ self.dataSize = dataSize
+ }
+
+ mutating func setParameterUInt8(type: HotlineTransactionFieldType, val: UInt8) {
+ self.parameters.append(HotlineTransactionParameter(type: type, dataSize: UInt16(MemoryLayout<UInt8>.size), data: Data(val)))
+// self.parameters[type] = HotlineTransactionParameter(dataSize: UInt16(MemoryLayout<UInt8>.size), data: Data(val))
+ }
+
+ mutating func setParameterUInt16(type: HotlineTransactionFieldType, val: UInt16) {
+ self.parameters.append(HotlineTransactionParameter(type: type, dataSize: UInt16(MemoryLayout<UInt16>.size), data: Data(val)))
+// self.parameters[type] = HotlineTransactionParameter(dataSize: UInt16(MemoryLayout<UInt16>.size), data: Data(val))
+ }
+
+ mutating func setParameterUInt32(type: HotlineTransactionFieldType, val: UInt32) {
+ self.parameters.append(HotlineTransactionParameter(type: type, dataSize: UInt16(MemoryLayout<UInt32>.size), data: Data(val)))
+// self.parameters[type] = HotlineTransactionParameter(type: type, dataSize: UInt16(MemoryLayout<UInt32>.size), data: Data(val))
+ }
+
+ mutating func setParameterEncodedString(type: HotlineTransactionFieldType, val: String) {
+ let encodedVal = String(val.utf8.map { char in
+ Character(UnicodeScalar(0xFF - char))
+ })
+
+ self.setParameterString(type: type, val: encodedVal)
+ }
+
+ mutating func setParameterString(type: HotlineTransactionFieldType, val: String) {
+ var stringData = Data()
+// stringData.appendUInt16(UInt16(val.count))
+ stringData.append(contentsOf: val.utf8)
+
+ self.parameters.append(HotlineTransactionParameter(type: type, dataSize: UInt16(stringData.count), data: stringData))
+// self.parameters[type] = HotlineTransactionParameter(dataSize: UInt16(stringData.count), data: stringData)
+ }
+
+ func getParameter(type: HotlineTransactionFieldType) -> HotlineTransactionParameter? {
+ return self.parameters.first { p in
+ p.type == type
+ }
+
+// return self.parameters[type]
+ }
+
+ func getParameterList(type: HotlineTransactionFieldType) -> [HotlineTransactionParameter] {
+ return self.parameters.filter { p in
+ p.type == type
+ }
+ }
+
+ func encoded() -> Data {
+ var data = Data()
+ self.encode(to: &data)
+ return data
+ }
func encode(to data: inout Data) {
data.appendUInt8(self.flags)
@@ -43,45 +163,68 @@ struct HotlineTransaction {
data.appendUInt16(self.type.rawValue)
data.appendUInt32(self.id)
data.appendUInt32(self.errorCode)
- data.appendUInt32(self.totalSize)
- data.appendUInt32(self.dataSize)
- if let p = self.parameters, p.count > 0 {
- data.appendUInt16(UInt16(p.count))
- for param in p {
- data.appendUInt16(param.id)
- data.appendUInt16(param.dataSize)
- data.append(param.data)
+ if self.parameters.count > 0 {
+ var parameterData = Data()
+ parameterData.appendUInt16(UInt16(self.parameters.count))
+ for param in self.parameters {
+ parameterData.appendUInt16(param.type.rawValue)
+ parameterData.appendUInt16(param.dataSize)
+ parameterData.append(param.data)
}
+
+ data.appendUInt32(UInt32(parameterData.count))
+ data.appendUInt32(UInt32(parameterData.count))
+ data.append(parameterData)
+ }
+ else {
+ data.appendUInt32(0)
+ data.appendUInt32(0)
}
}
}
+enum HotlineTransactionFieldType: UInt16 {
+ case userName = 102 // String
+ case userLogin = 105 // Encoded string
+ case userPassword = 106 // Encoded string
+ case userIconID = 104 // Integer
+ case userID = 103 // Integer
+ case data = 101 // String
+ case userAccess = 110 // 64-bit integer??
+ case userFlags = 112
+ case options = 113 // 32-bit integer?
+ case versionNumber = 160 // Integer
+ case bannerID = 161
+ case serverName = 162
+ case userInfo = 300
+}
+
enum HotlineTransactionType: UInt16 {
- case unknown = 0
+ case reply = 0
case error = 100
case getMessages = 101
- case newMessage = 102
+ case newMessage = 102 // Server
case oldPostNews = 103
- case serverMessage = 104
+ case serverMessage = 104 // Server
case sendChat = 105
- case chatMessage = 106
+ case chatMessage = 106 // Server
case login = 107
case sendInstantMessage = 108
- case showAgreement = 109
+ case showAgreement = 109 // Server
case disconnectUser = 110
- case disconnectMessage = 111
+ case disconnectMessage = 111 // Server
case inviteToNewChat = 112
- case inviteToChat = 113
+ case inviteToChat = 113 // Server
case rejectChatInvite = 114
case joinChat = 115
case leaveChat = 116
- case notifyChatOfUserChange = 117
- case notifyChatOfUserDelete = 118
- case notifyChatSubject = 119
+ case notifyChatOfUserChange = 117 // Server
+ case notifyChatOfUserDelete = 118 // Server
+ case notifyChatSubject = 119 // Server
case setChatSubject = 120
case agreed = 121
- case serverBanner = 122
+ case serverBanner = 122 // Server
case getFileNameList = 200
case downloadFile = 202
case uploadFile = 203
@@ -92,20 +235,20 @@ enum HotlineTransactionType: UInt16 {
case moveFile = 208
case makeFileAlias = 209
case downloadFolder = 210
- case downloadInfo = 211
+ case downloadInfo = 211 // Server
case downloadBanner = 212
case uploadFolder = 213
case getUserNameList = 300
- case notifyOfUserChange = 301
- case notifyOfUserDelete = 302
+ case notifyOfUserChange = 301 // Server
+ case notifyOfUserDelete = 302 // Server
case getClientInfoText = 303
case setClientUserInfo = 304
case newUser = 350
case deleteUser = 351
case getUser = 352
case setUser = 353
- case userAccess = 354
- case userBroadcast = 355
+ case userAccess = 354 // Server
+ case userBroadcast = 355 // Client & Server
case getNewsCategoryNameList = 370
case getNewsArticleNameList = 371
case deleteNewsItem = 380
@@ -114,5 +257,6 @@ enum HotlineTransactionType: UInt16 {
case getNewsArticleData = 400
case postNewsArticle = 410
case deleteNewsArticle = 411
+ case connectionKeepAlive = 500
}
diff --git a/Hotline/Utility/DataExtensions.swift b/Hotline/Utility/DataExtensions.swift
index 3f7101b..caf26a9 100644
--- a/Hotline/Utility/DataExtensions.swift
+++ b/Hotline/Utility/DataExtensions.swift
@@ -6,6 +6,21 @@ enum Endianness {
}
extension Data {
+ init(_ val: UInt8) {
+ self.init()
+ self.appendUInt8(val)
+ }
+
+ init(_ val: UInt16) {
+ self.init()
+ self.appendUInt16(val)
+ }
+
+ init(_ val: UInt32) {
+ self.init()
+ self.appendUInt32(val)
+ }
+
func readUInt8(at offset: Int) -> UInt8? {
guard offset >= 0, offset + MemoryLayout<UInt8>.size <= self.count else {
return nil