diff --git a/Sources/SwiftScaffolding/Client/ScaffoldingClient.swift b/Sources/SwiftScaffolding/Client/ScaffoldingClient.swift index 79fbb09..b7d47d5 100644 --- a/Sources/SwiftScaffolding/Client/ScaffoldingClient.swift +++ b/Sources/SwiftScaffolding/Client/ScaffoldingClient.swift @@ -113,7 +113,7 @@ public final class ScaffoldingClient { try await heartbeat() try await fetchProtocols() - let serverPort: UInt16 = ByteBuffer(data: try await sendRequest("c:server_port").data).readUInt16() + let serverPort: UInt16 = try await sendRequest("c:server_port").parse(using: { try $0.readUInt16() }) room.serverPort = serverPort Logger.info("Minecraft server is ready: 127.0.0.1:\(serverPort)") } @@ -178,7 +178,7 @@ public final class ScaffoldingClient { try await heartbeat() try await fetchProtocols() - let serverPort: UInt16 = ByteBuffer(data: try await sendRequest("c:server_port").data).readUInt16() + let serverPort: UInt16 = try await sendRequest("c:server_port").parse(using: { try $0.readUInt16() }) let localPort: UInt16 = try ConnectionUtil.getPort(serverPort) try easyTier.addPortForward(bind: "127.0.0.1:\(localPort)", destination: "\(serverNodeIp!):\(serverPort)") @@ -195,14 +195,9 @@ public final class ScaffoldingClient { private func fetchProtocols() async throws { let response: Scaffolding.Response = try await sendRequest("c:protocols") { buf in - buf.writeData(protocols.joined(separator: "\0").data(using: .utf8)!) + buf.writeString(protocols.joined(separator: "\0")) } - guard let rawProtocols: String = response.text else { - Logger.error("Failed to parse c:protocols response") - self.serverProtocols = [] - return - } - self.serverProtocols = rawProtocols.split(separator: "\0").map(String.init) + self.serverProtocols = try response.parse(using: { try $0.readString() }).split(separator: "\0").map(String.init) } private func assertReady() throws { diff --git a/Sources/SwiftScaffolding/Errors.swift b/Sources/SwiftScaffolding/Errors.swift index c949ee5..3d1c6d0 100644 --- a/Sources/SwiftScaffolding/Errors.swift +++ b/Sources/SwiftScaffolding/Errors.swift @@ -14,6 +14,8 @@ public enum ConnectionError: LocalizedError, Equatable { case missingConnection case invalidConnectionState case failedToAllocatePort + case notEnoughBytes + case failedToDecodeString public var errorDescription: String? { switch self { @@ -47,6 +49,18 @@ public enum ConnectionError: LocalizedError, Equatable { bundle: Bundle.module, comment: "端口分配失败" ) + case .notEnoughBytes: + return NSLocalizedString( + "ConnectionError.notEnoughBytes", + bundle: Bundle.module, + comment: "ByteBuffer 读取即将越界" + ) + case .failedToDecodeString: + return NSLocalizedString( + "ConnectionError.failedToDecodeString", + bundle: Bundle.module, + comment: "解析 UTF-8 字符串失败" + ) } } } diff --git a/Sources/SwiftScaffolding/Scaffolding.swift b/Sources/SwiftScaffolding/Scaffolding.swift index bd6ea81..c29dc10 100644 --- a/Sources/SwiftScaffolding/Scaffolding.swift +++ b/Sources/SwiftScaffolding/Scaffolding.swift @@ -39,7 +39,7 @@ public final class Scaffolding { ) async throws -> Response { let buffer: ByteBuffer = ByteBuffer() buffer.writeUInt8(UInt8(type.count)) - buffer.writeData(type.data(using: .utf8)!) + buffer.writeString(type) let bodyBuffer: ByteBuffer = ByteBuffer() try body(bodyBuffer) buffer.writeUInt32(UInt32(bodyBuffer.data.count)) @@ -79,8 +79,8 @@ public final class Scaffolding { Task { do { let headerBuffer: ByteBuffer = .init(data: try await ConnectionUtil.receiveData(from: connection, length: 5, timeout: timeout)) - let status: UInt8 = headerBuffer.readUInt8() - let bodyLength: Int = .init(headerBuffer.readUInt32()) + let status: UInt8 = try headerBuffer.readUInt8() + let bodyLength: Int = .init(try headerBuffer.readUInt32()) if bodyLength == 0 { completion(.success(.init(status: 0, data: .init()))) return @@ -117,7 +117,6 @@ public final class Scaffolding { public final class Response { public let status: UInt8 public let data: Data - public var text: String? { String(data: data, encoding: .utf8) } /// 根据响应状态码与响应体创建响应。 /// - Parameters: @@ -138,6 +137,10 @@ public final class Scaffolding { body(buffer) self.data = buffer.data } + + public func parse(using parser: (ByteBuffer) throws -> T) rethrows -> T { + return try parser(ByteBuffer(data: data)) + } } private init() { diff --git a/Sources/SwiftScaffolding/Server/RequestHandler.swift b/Sources/SwiftScaffolding/Server/RequestHandler.swift index 8881164..bd3c5a8 100644 --- a/Sources/SwiftScaffolding/Server/RequestHandler.swift +++ b/Sources/SwiftScaffolding/Server/RequestHandler.swift @@ -46,7 +46,7 @@ public class RequestHandler { responseBuffer.writeUInt8(255) let message: String = "Unknown request" responseBuffer.writeUInt32(UInt32(message.count)) - responseBuffer.writeData(message.data(using: .utf8)!) + responseBuffer.writeString(message) return } @@ -67,7 +67,7 @@ public class RequestHandler { registerHandler(for: "c:protocols") { sender, requestBody in let protocols: String = Array(self.handlers.keys).joined(separator: "\0") - return .init(status: 0, data: protocols.data(using: .utf8)!) + return .init(status: 0) { $0.writeString(protocols) } } registerHandler(for: "c:server_port") { sender, requestBody in diff --git a/Sources/SwiftScaffolding/Server/ScaffoldingServer.swift b/Sources/SwiftScaffolding/Server/ScaffoldingServer.swift index b1d6746..b14b634 100644 --- a/Sources/SwiftScaffolding/Server/ScaffoldingServer.swift +++ b/Sources/SwiftScaffolding/Server/ScaffoldingServer.swift @@ -185,11 +185,11 @@ public final class ScaffoldingServer { let headerBuffer: ByteBuffer = .init() headerBuffer.writeData(try await ConnectionUtil.receiveData(from: connection, length: 1)) - let typeLength: Int = Int(headerBuffer.readUInt8()) + let typeLength: Int = Int(try headerBuffer.readUInt8()) headerBuffer.writeData(try await ConnectionUtil.receiveData(from: connection, length: typeLength + 4)) - guard let type = String(data: headerBuffer.readData(length: typeLength), encoding: .utf8) else { return } + let type: String = try headerBuffer.readString(typeLength) - let bodyLength: Int = Int(headerBuffer.readUInt32()) + let bodyLength: Int = Int(try headerBuffer.readUInt32()) let bodyData: Data = try await ConnectionUtil.receiveData(from: connection, length: bodyLength) let responseBuffer: ByteBuffer = .init() diff --git a/Sources/SwiftScaffolding/ByteBuffer.swift b/Sources/SwiftScaffolding/Util/ByteBuffer.swift similarity index 62% rename from Sources/SwiftScaffolding/ByteBuffer.swift rename to Sources/SwiftScaffolding/Util/ByteBuffer.swift index ba7c7df..2e66326 100644 --- a/Sources/SwiftScaffolding/ByteBuffer.swift +++ b/Sources/SwiftScaffolding/Util/ByteBuffer.swift @@ -31,36 +31,54 @@ public class ByteBuffer { /// 读取 `n` 个字节的数据 /// - Parameter length: 读取的数据长度 /// - Returns: 长度为 `length` 字节的 `Data` - public func readData(length: Int) -> Data { - if length == 0 { return Data() } - defer { index += length } - return data.subdata(in: index.. Data { + guard let intLength: Int = .init(exactly: length) else { + throw ConnectionError.notEnoughBytes + } + if intLength == 0 { return Data() } + if index + intLength > data.count { + throw ConnectionError.notEnoughBytes + } + defer { index += intLength } + return data.subdata(in: index.. UInt8 { - let data: Data = readData(length: 1) + public func readUInt8() throws -> UInt8 { + let data: Data = try readData(length: 1) let value: UInt8 = data.withUnsafeBytes { $0.load(as: UInt8.self) } return value } /// 读取一个 `UInt16`(大端序) /// - Returns: UInt16 - public func readUInt16() -> UInt16 { - let data: Data = readData(length: 2) + public func readUInt16() throws -> UInt16 { + let data: Data = try readData(length: 2) let value: UInt16 = data.withUnsafeBytes { $0.load(as: UInt16.self).bigEndian } return value } /// 读取一个 `UInt32`(大端序) /// - Returns: UInt32 - public func readUInt32() -> UInt32 { - let data: Data = readData(length: 4) + public func readUInt32() throws -> UInt32 { + let data: Data = try readData(length: 4) let value: UInt32 = data.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian } return value } + /// 读取一个 **UTF-8** 字符串。 + /// + /// - Parameter length: 字符串长度。 + public func readString(_ length: (any BinaryInteger)? = nil) throws -> String { + let length: any BinaryInteger = length ?? data.count + let data: Data = try readData(length: length) + guard let string: String = .init(data: data, encoding: .utf8) else { + throw ConnectionError.failedToDecodeString + } + return string + } + // MARK: - 写入 /// 写入一段 `Data` @@ -92,4 +110,10 @@ public class ByteBuffer { let d: Data = Data(bytes: &v, count: MemoryLayout.size) writeData(d) } + + /// 写入一个 **UTF-8** 字符串。 + public func writeString(_ str: String) { + let data: Data = str.data(using: .utf8)! + writeData(data) + } } diff --git a/Sources/SwiftScaffolding/en.lproj/Localizable.strings b/Sources/SwiftScaffolding/en.lproj/Localizable.strings index 8fa55a3..7ab8f6f 100644 --- a/Sources/SwiftScaffolding/en.lproj/Localizable.strings +++ b/Sources/SwiftScaffolding/en.lproj/Localizable.strings @@ -11,6 +11,8 @@ "ConnectionError.connectionClosed" = "The connection was closed."; "ConnectionError.connectionIsNotReady" = "The connection is not established, or the connection state is invalid."; "ConnectionError.failedToAllocatePort" = "Failed to allocate port."; +"ConnectionError.notEnoughBytes" = "The peer did not send enough data."; +"ConnectionError.failedToDecodeString" = "Failed to decode string."; "RoomError.invalidRoomCode" = "The room code was invalid."; "RoomError.roomClosed" = "The room has been closed, or the network is unstable."; diff --git a/Sources/SwiftScaffolding/zh-Hans.lproj/Localizable.strings b/Sources/SwiftScaffolding/zh-Hans.lproj/Localizable.strings index 7cd1e77..7e963b4 100644 --- a/Sources/SwiftScaffolding/zh-Hans.lproj/Localizable.strings +++ b/Sources/SwiftScaffolding/zh-Hans.lproj/Localizable.strings @@ -11,6 +11,8 @@ "ConnectionError.connectionClosed" = "连接被关闭。"; "ConnectionError.connectionIsNotReady" = "连接未被建立,或者连接状态异常。"; "ConnectionError.failedToAllocatePort" = "端口分配失败。"; +"ConnectionError.notEnoughBytes" = "对端未发送足够长的数据。"; +"ConnectionError.failedToDecodeString" = "解析字符串失败。"; "RoomError.invalidRoomCode" = "无效的房间码。"; "RoomError.roomClosed" = "房间已被关闭,或者网络不稳定。";