init
Some checks failed
ci / macos (push) Has been cancelled
ci / ios (push) Has been cancelled
ci / check-linter (push) Has been cancelled

This commit is contained in:
wenzuhuai
2026-01-12 18:29:52 +08:00
commit d7bdb4f378
87 changed files with 12664 additions and 0 deletions

View 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")
}
}
}

View 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: "="))
}
}

View 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)
}
}

View 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
}
}

View 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])
}
}