diff --git a/Sources/MacroCore/Buffer/Buffer.swift b/Sources/MacroCore/Buffer/Buffer.swift index ab0debd..264e5b6 100644 --- a/Sources/MacroCore/Buffer/Buffer.swift +++ b/Sources/MacroCore/Buffer/Buffer.swift @@ -3,7 +3,7 @@ // Macro // // Created by Helge Heß. -// Copyright © 2020-2021 ZeeZide GmbH. All rights reserved. +// Copyright © 2020-2024 ZeeZide GmbH. All rights reserved. // import struct NIO.ByteBuffer @@ -16,40 +16,44 @@ import struct NIO.ByteBufferAllocator * Node Buffer API: https://nodejs.org/api/buffer.html * * Creating buffers: - * - * Buffer.from("Hello") // UTF-8 data - * Buffer() // empty - * Buffer([ 42, 13, 10 ]) + * ```swift + * Buffer.from("Hello") // UTF-8 data + * Buffer() // empty + * Buffer([ 42, 13, 10 ]) + * ``` * * Reading: + * ```swift + * buffer.count + * buffer.isEmpty + * let byte = buffer[10] * - * buffer.count - * buffer.isEmpty - * let byte = buffer[10] - * - * let slice = buffer.consumeFirst(10) - * let slice = buffer.slice(5) - * let slice = buffer.slice(5, 7) + * let slice = buffer.consumeFirst(10) + * let slice = buffer.slice(5) + * let slice = buffer.slice(5, 7) * - * let idx = buffer.indexOf(42) // -1 on not found - * let idx = buffer.indexOf([ 13, 10 ]) + * let idx = buffer.indexOf(42) // -1 on not found + * let idx = buffer.indexOf([ 13, 10 ]) + * ``` * * Writing: - * - * buffer.append(otherBuffer) - * buffer.append([ 42, 10 ]) + * ```swift + * buffer.append(otherBuffer) + * buffer.append([ 42, 10 ]) + * ``` * * Converting to strings: - * - * try buffer.toString() - * try buffer.toString("hex") - * try buffer.toString("base64") - * buffer.hexEncodedString() - * + * ```swift + * try buffer.toString() + * try buffer.toString("hex") + * try buffer.toString("base64") + * buffer.hexEncodedString() + * ``` */ -public struct Buffer: Codable, Hashable { +public struct Buffer: Codable, Hashable, Sendable { - public typealias Index = Int + public typealias Index = Int + public typealias Element = UInt8 public var byteBuffer : ByteBuffer @@ -82,7 +86,7 @@ public struct Buffer: Codable, Hashable { @inlinable public mutating func append(contentsOf sequence: S) - where S : Sequence, S.Element == UInt8 + where S : Sequence, S.Element == UInt8 { byteBuffer.writeBytes(sequence) } @@ -139,7 +143,7 @@ public struct Buffer: Codable, Hashable { let end = endIndex ?? count let startOffset = startIndex >= 0 ? startIndex : (count + startIndex) let endOffset = end >= 0 ? end : (count + end) - let length = max(0, endOffset - startOffset) + let length = Swift.max(0, endOffset - startOffset) let startIndex = byteBuffer.readerIndex + startOffset assert(length >= 0, "invalid index parameters to `slice`") if length < 1 { return Buffer(MacroCore.shared.emptyByteBuffer) } @@ -277,6 +281,54 @@ public extension Buffer { } } +public extension Buffer { + + @inlinable + static func concat(_ buffers: S...) -> Buffer + where S: Sequence, S.Element == UInt8 + { + return concat(buffers) + } + + @inlinable + static func concat(_ buffers: S...) -> Buffer + where S: Collection, S.Element == UInt8 + { + return concat(buffers) + } + + @inlinable + static func concat(_ buffers: S) -> Buffer + where S: Sequence, S.Element: Sequence, S.Element.Element == UInt8 + { + var buffer = Buffer() + for sub in buffers { + buffer.append(contentsOf: sub) + } + return buffer + } + @inlinable + static func concat(_ buffers: S) -> Buffer + where S: Collection, S.Element: Collection, S.Element.Element == UInt8 + { + let size = buffers.reduce(0) { $0 + $1.count } + var buffer = Buffer(capacity: size) + for sub in buffers { buffer.append(contentsOf: sub)} + return buffer + } +} + +extension Buffer: Collection { + + @inlinable + public func index(after i: Int) -> Int { return i + 1 } + + @inlinable + public var startIndex : Int { return 0 } + @inlinable + public var endIndex : Int { return count } +} + extension Buffer: CustomStringConvertible { @inlinable diff --git a/Sources/MacroCore/Buffer/BufferData.swift b/Sources/MacroCore/Buffer/BufferData.swift index e4fb0eb..bc66d39 100644 --- a/Sources/MacroCore/Buffer/BufferData.swift +++ b/Sources/MacroCore/Buffer/BufferData.swift @@ -3,12 +3,13 @@ // Macro // // Created by Helge Heß. -// Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. +// Copyright © 2020-2024 ZeeZide GmbH. All rights reserved. // #if canImport(Foundation) -import struct Foundation.Data +import struct Foundation.Data +import protocol Foundation.ContiguousBytes import NIOCore import NIOFoundationCompat @@ -17,16 +18,32 @@ public extension Buffer { /** * Initialize the Buffer with the contents of the given `Data`. Copies the * bytes. + * + * - Parameters: + * - data: The bytes to copy into the buffer. */ - @inlinable init(_ data: Data) { + @inlinable + init(_ data: Data) { self.init(capacity: data.count) byteBuffer.writeBytes(data) } - @inlinable var data : Data { + @inlinable + var data : Data { return byteBuffer.getData(at : byteBuffer.readerIndex, length : byteBuffer.readableBytes) ?? Data() } } +extension Buffer: ContiguousBytes { + + @inlinable + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) + rethrows -> R + { + return try byteBuffer.readableBytesView.withUnsafeBytes(body) + } + +} + #endif // canImport(Foundation) diff --git a/Sources/MacroCore/Buffer/BufferHexEncoding.swift b/Sources/MacroCore/Buffer/BufferHexEncoding.swift index de7f6ab..981696c 100644 --- a/Sources/MacroCore/Buffer/BufferHexEncoding.swift +++ b/Sources/MacroCore/Buffer/BufferHexEncoding.swift @@ -3,7 +3,7 @@ // Macro // // Created by Helge Heß. -// Copyright © 2020-2023 ZeeZide GmbH. All rights reserved. +// Copyright © 2020-2024 ZeeZide GmbH. All rights reserved. // import NIOCore @@ -19,19 +19,21 @@ public extension Buffer { * Returns the data in the buffer as a hex encoded string. * * Example: - * - * let buffer = Buffer("Hello".utf8) - * let string = buffer.hexEncodedString() - * // "48656c6c6f" + * ```swift + * let buffer = Buffer("Hello".utf8) + * let string = buffer.hexEncodedString() + * // "48656c6c6f" + * ``` * * Each byte is represented by two hex digits, e.g. `6c` in the example. * * `hex` is also recognized as a string encoding, this works as well: + * ```swift + * let buffer = Buffer("Hello".utf8) + * let string = try buffer.toString("hex") + * // "48656c6c6f" + * ``` * - * let buffer = Buffer("Hello".utf8) - * let string = try buffer.toString("hex") - * // "48656c6c6f" - * * - Parameters: * - uppercase: If true, the a-f hexdigits are generated in * uppercase (ABCDEF). Defaults to false. @@ -67,18 +69,18 @@ public extension Buffer { * Appends the bytes represented by a hex encoded string to the Buffer. * * Example: - * - * let buffer = Buffer() - * buffer.writeHexString("48656c6c6f") - * let string = try buffer.toString() - * // "Hello" - * + * ```swift + * let buffer = Buffer() + * buffer.writeHexString("48656c6c6f") + * let string = try buffer.toString() + * // "Hello" + * ``` * `hex` is also recognized as a string encoding, this works as well: - * - * let buffer = try Buffer.from("48656c6c6f", "hex") - * let string = try buffer.toString() - * // "Hello" - * + * ```swift + * let buffer = try Buffer.from("48656c6c6f", "hex") + * let string = try buffer.toString() + * // "Hello" + * ``` * - Parameters: * - hexString: A hex encoded string, no spaces etc allowed between the * bytes. diff --git a/Sources/MacroCore/Buffer/BufferStrings.swift b/Sources/MacroCore/Buffer/BufferStrings.swift index 7d2c634..3706703 100644 --- a/Sources/MacroCore/Buffer/BufferStrings.swift +++ b/Sources/MacroCore/Buffer/BufferStrings.swift @@ -3,7 +3,7 @@ // Macro // // Created by Helge Heß. -// Copyright © 2020-2021 ZeeZide GmbH. All rights reserved. +// Copyright © 2020-2024 ZeeZide GmbH. All rights reserved. // #if canImport(Foundation) @@ -125,11 +125,12 @@ public extension Buffer { * `CharsetConversionError`. * * Example: + * ```swift + * let buffer = try Buffer.from("48656c6c6f", "hex") + * let string = try buffer.toString() + * // "Hello" + * ``` * - * let buffer = try Buffer.from("48656c6c6f", "hex") - * let string = try buffer.toString() - * // "Hello" - * * - Parameters: * - string: The string to convert to a Buffer. * - encoding: The requested encoding, e.g. 'utf8' or 'hex'. @@ -150,34 +151,56 @@ public extension Buffer { return buffer case "base64": - guard let data = Data(base64Encoded: String(string)) else { - throw DataDecodingError.failedToDecodeBase64 + let s = String(string) + if let data = Data(base64Encoded: s) { return Buffer(data) } + + // https://stackoverflow.com/questions/4080988/why-does-base64-encoding + switch s.count % 4 { + case 0: // should have decoded above + throw DataDecodingError.failedToDecodeBase64 + case 1: // invalid base64 + throw DataDecodingError.failedToDecodeBase64 + case 2: + if let data = Data(base64Encoded: s + "==") { return Buffer(data) } + case 3: + if let data = Data(base64Encoded: s + "=") { return Buffer(data) } + default: + assertionFailure("Invalid % operation.") } - return Buffer(data) + throw DataDecodingError.failedToDecodeBase64 + + case "base64url": + return try Self.from(String(string.map { + switch $0 { + case "-" : return "+" + case "_" : return "/" + default : return $0 + } + }), "base64") default: return try from(string, .encodingWithName(encoding)) } } - + /** * Returns true if the string argument represents a valid encoding, i.e. * if it can be used in the `Buffer.toString` method. * * Example: - * - * Buffer.isEncoding("utf8") // true - * Buffer.isEncoding("hex") // true - * Buffer.isEncoding("base64") // true - * Buffer.isEncoding("alwaysright") // false - * + * ```swift + * Buffer.isEncoding("utf8") // true + * Buffer.isEncoding("hex") // true + * Buffer.isEncoding("base64") // true + * Buffer.isEncoding("alwaysright") // false + * ``` * - Parameter encoding: The name of an encoding, e.g. 'utf8' or 'hex' * - Returns: true if the name is a known encoding, false otherwise. */ @inlinable static func isEncoding(_ encoding: String) -> Bool { switch encoding { - case "hex", "base64": return true + case "hex", "base64", "base64url": return true default: return String.Encoding.isEncoding(encoding) } } @@ -208,11 +231,12 @@ public extension Buffer { * `CharsetConversionError`. * * Example: + * ```swift + * let buffer = Buffer("Hello".utf8) + * let string = try buffer.toString("hex") + * // "48656c6c6f" + * ``` * - * let buffer = Buffer("Hello".utf8) - * let string = try buffer.toString("hex") - * // "48656c6c6f" - * * - Parameter encoding: The requested encoding, e.g. 'hex' or 'base64' * - Returns: A string representing the Buffer in the given encoding. * - Throws: CharsetConversionError if the data could not be converted to @@ -221,8 +245,19 @@ public extension Buffer { @inlinable func toString(_ encoding: String) throws -> String { switch encoding { - case "hex" : return hexEncodedString() - case "base64" : return data.base64EncodedString() + case "hex" : return hexEncodedString() + case "base64" : return data.base64EncodedString() + case "base64url" : + var b64 = data.base64EncodedString() + if let eqIdx = b64.firstIndex(of: "=") { b64 = String(b64[..