init
This commit is contained in:
87
Sources/Nats/Extensions/ByteBuffer+Writer.swift
Normal file
87
Sources/Nats/Extensions/ByteBuffer+Writer.swift
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import Foundation
|
||||
import NIO
|
||||
|
||||
extension ByteBuffer {
|
||||
mutating func writeClientOp(_ op: ClientOp) {
|
||||
switch op {
|
||||
case .publish((let subject, let reply, let payload, let headers)):
|
||||
if let payload = payload {
|
||||
self.reserveCapacity(
|
||||
minimumWritableBytes: payload.count + subject.utf8.count
|
||||
+ NatsOperation.publish.rawValue.count + 12)
|
||||
if headers != nil {
|
||||
self.writeBytes(NatsOperation.hpublish.rawBytes)
|
||||
} else {
|
||||
self.writeBytes(NatsOperation.publish.rawBytes)
|
||||
}
|
||||
self.writeString(" ")
|
||||
self.writeString(subject)
|
||||
self.writeString(" ")
|
||||
if let reply = reply {
|
||||
self.writeString("\(reply) ")
|
||||
}
|
||||
if let headers = headers {
|
||||
let headers = headers.toBytes()
|
||||
let totalLen = headers.count + payload.count
|
||||
let headersLen = headers.count
|
||||
self.writeString("\(headersLen) \(totalLen)\r\n")
|
||||
self.writeData(headers)
|
||||
} else {
|
||||
self.writeString("\(payload.count)\r\n")
|
||||
}
|
||||
self.writeData(payload)
|
||||
self.writeString("\r\n")
|
||||
} else {
|
||||
self.reserveCapacity(
|
||||
minimumWritableBytes: subject.utf8.count + NatsOperation.publish.rawValue.count
|
||||
+ 12)
|
||||
self.writeBytes(NatsOperation.publish.rawBytes)
|
||||
self.writeString(" ")
|
||||
self.writeString(subject)
|
||||
if let reply = reply {
|
||||
self.writeString("\(reply) ")
|
||||
}
|
||||
self.writeString("\r\n")
|
||||
}
|
||||
|
||||
case .subscribe((let sid, let subject, let queue)):
|
||||
if let queue {
|
||||
self.writeString(
|
||||
"\(NatsOperation.subscribe.rawValue) \(subject) \(queue) \(sid)\r\n")
|
||||
} else {
|
||||
self.writeString("\(NatsOperation.subscribe.rawValue) \(subject) \(sid)\r\n")
|
||||
}
|
||||
|
||||
case .unsubscribe((let sid, let max)):
|
||||
if let max {
|
||||
self.writeString("\(NatsOperation.unsubscribe.rawValue) \(sid) \(max)\r\n")
|
||||
} else {
|
||||
self.writeString("\(NatsOperation.unsubscribe.rawValue) \(sid)\r\n")
|
||||
}
|
||||
case .connect(let info):
|
||||
// This encode can't actually fail
|
||||
let json = try! JSONEncoder().encode(info)
|
||||
self.reserveCapacity(minimumWritableBytes: json.count + 5)
|
||||
self.writeString("\(NatsOperation.connect.rawValue) ")
|
||||
self.writeData(json)
|
||||
self.writeString("\r\n")
|
||||
case .ping:
|
||||
self.writeString("\(NatsOperation.ping.rawValue)\r\n")
|
||||
case .pong:
|
||||
self.writeString("\(NatsOperation.pong.rawValue)\r\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Sources/Nats/Extensions/Data+Base64.swift
Normal file
24
Sources/Nats/Extensions/Data+Base64.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
/// Swift does not provide a way to encode data to base64 without padding in URL safe way.
|
||||
func base64EncodedURLSafeNotPadded() -> String {
|
||||
return self.base64EncodedString()
|
||||
.replacingOccurrences(of: "+", with: "-")
|
||||
.replacingOccurrences(of: "/", with: "_")
|
||||
.trimmingCharacters(in: CharacterSet(charactersIn: "="))
|
||||
}
|
||||
}
|
||||
184
Sources/Nats/Extensions/Data+Parser.swift
Normal file
184
Sources/Nats/Extensions/Data+Parser.swift
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
private static let cr = UInt8(ascii: "\r")
|
||||
private static let lf = UInt8(ascii: "\n")
|
||||
private static let crlf = Data([cr, lf])
|
||||
private static var currentNum = 0
|
||||
private static var errored = false
|
||||
internal static let versionLinePrefix = "NATS/1.0"
|
||||
|
||||
func removePrefix(_ prefix: Data) -> Data {
|
||||
guard self.starts(with: prefix) else { return self }
|
||||
return self.dropFirst(prefix.count)
|
||||
}
|
||||
|
||||
func split(
|
||||
separator: Data, maxSplits: Int = .max, omittingEmptySubsequences: Bool = true
|
||||
)
|
||||
-> [Data]
|
||||
{
|
||||
var chunks: [Data] = []
|
||||
var start = startIndex
|
||||
var end = startIndex
|
||||
var splitsCount = 0
|
||||
|
||||
while end < count {
|
||||
if splitsCount >= maxSplits {
|
||||
break
|
||||
}
|
||||
if self[start..<end].elementsEqual(separator) {
|
||||
if !omittingEmptySubsequences || start != end {
|
||||
chunks.append(self[start..<end])
|
||||
}
|
||||
start = index(end, offsetBy: separator.count)
|
||||
end = start
|
||||
splitsCount += 1
|
||||
continue
|
||||
}
|
||||
end = index(after: end)
|
||||
}
|
||||
|
||||
if start <= endIndex {
|
||||
if !omittingEmptySubsequences || start != endIndex {
|
||||
chunks.append(self[start..<endIndex])
|
||||
}
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
func getMessageType() -> NatsOperation? {
|
||||
guard self.count > 2 else { return nil }
|
||||
for operation in NatsOperation.allOperations() {
|
||||
if self.starts(with: operation.rawBytes) {
|
||||
return operation
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func starts(with bytes: [UInt8]) -> Bool {
|
||||
guard self.count >= bytes.count else { return false }
|
||||
return self.prefix(bytes.count).elementsEqual(bytes)
|
||||
}
|
||||
|
||||
internal mutating func prepend(_ other: Data) {
|
||||
self = other + self
|
||||
}
|
||||
|
||||
internal func parseOutMessages() throws -> (ops: [ServerOp], remainder: Data?) {
|
||||
var serverOps = [ServerOp]()
|
||||
var startIndex = self.startIndex
|
||||
var remainder: Data?
|
||||
|
||||
while startIndex < self.endIndex {
|
||||
var nextLineStartIndex: Int
|
||||
var lineData: Data
|
||||
if let range = self[startIndex...].range(of: Data.crlf) {
|
||||
let lineEndIndex = range.lowerBound
|
||||
nextLineStartIndex =
|
||||
self.index(range.upperBound, offsetBy: 0, limitedBy: self.endIndex)
|
||||
?? self.endIndex
|
||||
lineData = self[startIndex..<lineEndIndex]
|
||||
} else {
|
||||
remainder = self[startIndex..<self.endIndex]
|
||||
break
|
||||
}
|
||||
if lineData.count == 0 {
|
||||
startIndex = nextLineStartIndex
|
||||
continue
|
||||
}
|
||||
|
||||
let serverOp = try ServerOp.parse(from: lineData)
|
||||
|
||||
// if it's a message, get the full payload and add to returned data
|
||||
if case .message(var msg) = serverOp {
|
||||
if msg.length == 0 {
|
||||
serverOps.append(serverOp)
|
||||
} else {
|
||||
var payload = Data()
|
||||
let payloadEndIndex = nextLineStartIndex + msg.length
|
||||
let payloadStartIndex = nextLineStartIndex
|
||||
// include crlf in the expected payload leangth
|
||||
if payloadEndIndex + Data.crlf.count > endIndex {
|
||||
remainder = self[startIndex..<self.endIndex]
|
||||
break
|
||||
}
|
||||
payload.append(self[payloadStartIndex..<payloadEndIndex])
|
||||
msg.payload = payload
|
||||
startIndex =
|
||||
self.index(
|
||||
payloadEndIndex, offsetBy: Data.crlf.count, limitedBy: self.endIndex)
|
||||
?? self.endIndex
|
||||
serverOps.append(.message(msg))
|
||||
continue
|
||||
}
|
||||
//TODO(jrm): Add HMSG handling here too.
|
||||
} else if case .hMessage(var msg) = serverOp {
|
||||
if msg.length == 0 {
|
||||
serverOps.append(serverOp)
|
||||
} else {
|
||||
let headersStartIndex = nextLineStartIndex
|
||||
let headersEndIndex = nextLineStartIndex + msg.headersLength
|
||||
let payloadStartIndex = headersEndIndex
|
||||
let payloadEndIndex = nextLineStartIndex + msg.length
|
||||
|
||||
var payload: Data?
|
||||
if msg.length > msg.headersLength {
|
||||
payload = Data()
|
||||
}
|
||||
var headers = NatsHeaderMap()
|
||||
|
||||
// if the whole msg length (including training crlf) is longer
|
||||
// than the remaining chunk, break and return the remainder
|
||||
if payloadEndIndex + Data.crlf.count > endIndex {
|
||||
remainder = self[startIndex..<self.endIndex]
|
||||
break
|
||||
}
|
||||
|
||||
let headersData = self[headersStartIndex..<headersEndIndex]
|
||||
if let headersString = String(data: headersData, encoding: .utf8) {
|
||||
headers = try NatsHeaderMap(from: headersString)
|
||||
}
|
||||
msg.status = headers.status
|
||||
msg.description = headers.description
|
||||
msg.headers = headers
|
||||
|
||||
if var payload = payload {
|
||||
payload.append(self[payloadStartIndex..<payloadEndIndex])
|
||||
msg.payload = payload
|
||||
}
|
||||
|
||||
startIndex =
|
||||
self.index(
|
||||
payloadEndIndex, offsetBy: Data.crlf.count, limitedBy: self.endIndex)
|
||||
?? self.endIndex
|
||||
serverOps.append(.hMessage(msg))
|
||||
continue
|
||||
}
|
||||
|
||||
} else {
|
||||
// otherwise, just add this server op to the result
|
||||
serverOps.append(serverOp)
|
||||
}
|
||||
startIndex = nextLineStartIndex
|
||||
|
||||
}
|
||||
|
||||
return (serverOps, remainder)
|
||||
}
|
||||
}
|
||||
24
Sources/Nats/Extensions/Data+String.swift
Normal file
24
Sources/Nats/Extensions/Data+String.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import Foundation
|
||||
import NIOPosix
|
||||
|
||||
extension Data {
|
||||
func toString() -> String? {
|
||||
if let str = String(data: self, encoding: .utf8) {
|
||||
return str
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
38
Sources/Nats/Extensions/String+Utilities.swift
Normal file
38
Sources/Nats/Extensions/String+Utilities.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
private static let charactersToTrim: CharacterSet = .whitespacesAndNewlines.union(
|
||||
CharacterSet(charactersIn: "'"))
|
||||
|
||||
static func hash() -> String {
|
||||
let uuid = String.uuid()
|
||||
return uuid[0...7]
|
||||
}
|
||||
|
||||
func trimWhitespacesAndApostrophes() -> String {
|
||||
return self.trimmingCharacters(in: String.charactersToTrim)
|
||||
}
|
||||
|
||||
static func uuid() -> String {
|
||||
return UUID().uuidString.trimmingCharacters(in: .punctuationCharacters)
|
||||
}
|
||||
|
||||
subscript(bounds: CountableClosedRange<Int>) -> String {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return String(self[start...end])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user