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,341 @@
// 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
internal struct NatsOperation: RawRepresentable, Hashable {
let rawValue: String
static let connect = NatsOperation(rawValue: "CONNECT")
static let subscribe = NatsOperation(rawValue: "SUB")
static let unsubscribe = NatsOperation(rawValue: "UNSUB")
static let publish = NatsOperation(rawValue: "PUB")
static let hpublish = NatsOperation(rawValue: "HPUB")
static let message = NatsOperation(rawValue: "MSG")
static let hmessage = NatsOperation(rawValue: "HMSG")
static let info = NatsOperation(rawValue: "INFO")
static let ok = NatsOperation(rawValue: "+OK")
static let error = NatsOperation(rawValue: "-ERR")
static let ping = NatsOperation(rawValue: "PING")
static let pong = NatsOperation(rawValue: "PONG")
var rawBytes: String.UTF8View {
self.rawValue.utf8
}
static func allOperations() -> [NatsOperation] {
return [
.connect, .subscribe, .unsubscribe, .publish, .message, .hmessage, .info, .ok, .error,
.ping, .pong,
]
}
}
enum ServerOp {
case ok
case info(ServerInfo)
case ping
case pong
case error(NatsError.ServerError)
case message(MessageInbound)
case hMessage(HMessageInbound)
static func parse(from msg: Data) throws -> ServerOp {
guard msg.count > 2 else {
throw NatsError.ProtocolError.parserFailure(
"unable to parse inbound message: \(String(data: msg, encoding: .utf8)!)")
}
guard let msgType = msg.getMessageType() else {
throw NatsError.ProtocolError.invalidOperation(String(data: msg, encoding: .utf8)!)
}
switch msgType {
case .message:
return try message(MessageInbound.parse(data: msg))
case .hmessage:
return try hMessage(HMessageInbound.parse(data: msg))
case .info:
return try info(ServerInfo.parse(data: msg))
case .ok:
return ok
case .error:
if let errMsg = msg.removePrefix(Data(NatsOperation.error.rawBytes)).toString() {
return error(NatsError.ServerError(errMsg))
}
return error(NatsError.ServerError("unexpected error"))
case .ping:
return ping
case .pong:
return pong
default:
throw NatsError.ProtocolError.invalidOperation(
"unknown server op: \(String(data: msg, encoding: .utf8)!)")
}
}
}
internal struct HMessageInbound: Equatable {
private static let newline = UInt8(ascii: "\n")
private static let space = UInt8(ascii: " ")
var subject: String
var sid: UInt64
var reply: String?
var payload: Data?
var headers: NatsHeaderMap
var headersLength: Int
var length: Int
var status: StatusCode?
var description: String?
// Parse the operation syntax: HMSG <subject> <sid> [reply-to]
internal static func parse(data: Data) throws -> HMessageInbound {
let protoComponents =
data
.dropFirst(NatsOperation.hmessage.rawValue.count) // Assuming msg starts with "HMSG "
.split(separator: space)
.filter { !$0.isEmpty }
let parseArgs: ((Data, Data, Data?, Data, Data) throws -> HMessageInbound) = {
subjectData, sidData, replyData, lengthHeaders, lengthData in
let subject = String(decoding: subjectData, as: UTF8.self)
guard let sid = UInt64(String(decoding: sidData, as: UTF8.self)) else {
throw NatsError.ProtocolError.parserFailure(
"unable to parse subscription ID as number")
}
var replySubject: String? = nil
if let replyData = replyData {
replySubject = String(decoding: replyData, as: UTF8.self)
}
let headersLength = Int(String(decoding: lengthHeaders, as: UTF8.self)) ?? 0
let length = Int(String(decoding: lengthData, as: UTF8.self)) ?? 0
return HMessageInbound(
subject: subject, sid: sid, reply: replySubject, payload: nil,
headers: NatsHeaderMap(),
headersLength: headersLength, length: length)
}
var msg: HMessageInbound
switch protoComponents.count {
case 4:
msg = try parseArgs(
protoComponents[0], protoComponents[1], nil, protoComponents[2],
protoComponents[3])
case 5:
msg = try parseArgs(
protoComponents[0], protoComponents[1], protoComponents[2], protoComponents[3],
protoComponents[4])
default:
throw NatsError.ProtocolError.parserFailure("unable to parse inbound message header")
}
return msg
}
}
// TODO(pp): add headers and HMSG parsing
internal struct MessageInbound: Equatable {
private static let newline = UInt8(ascii: "\n")
private static let space = UInt8(ascii: " ")
var subject: String
var sid: UInt64
var reply: String?
var payload: Data?
var length: Int
// Parse the operation syntax: MSG <subject> <sid> [reply-to]
internal static func parse(data: Data) throws -> MessageInbound {
let protoComponents =
data
.dropFirst(NatsOperation.message.rawValue.count) // Assuming msg starts with "MSG "
.split(separator: space)
.filter { !$0.isEmpty }
let parseArgs: ((Data, Data, Data?, Data) throws -> MessageInbound) = {
subjectData, sidData, replyData, lengthData in
let subject = String(decoding: subjectData, as: UTF8.self)
guard let sid = UInt64(String(decoding: sidData, as: UTF8.self)) else {
throw NatsError.ProtocolError.parserFailure(
"unable to parse subscription ID as number")
}
var replySubject: String? = nil
if let replyData = replyData {
replySubject = String(decoding: replyData, as: UTF8.self)
}
let length = Int(String(decoding: lengthData, as: UTF8.self)) ?? 0
return MessageInbound(
subject: subject, sid: sid, reply: replySubject, payload: nil, length: length)
}
var msg: MessageInbound
switch protoComponents.count {
case 3:
msg = try parseArgs(protoComponents[0], protoComponents[1], nil, protoComponents[2])
case 4:
msg = try parseArgs(
protoComponents[0], protoComponents[1], protoComponents[2], protoComponents[3])
default:
throw NatsError.ProtocolError.parserFailure("unable to parse inbound message header")
}
return msg
}
}
/// Struct representing server information in NATS.
struct ServerInfo: Codable, Equatable {
/// The unique identifier of the NATS server.
let serverId: String
/// Generated Server Name.
let serverName: String
/// The host specified in the cluster parameter/options.
let host: String
/// The port number specified in the cluster parameter/options.
let port: UInt16
/// The version of the NATS server.
let version: String
/// If this is set, then the server should try to authenticate upon connect.
let authRequired: Bool?
/// If this is set, then the server must authenticate using TLS.
let tlsRequired: Bool?
/// Maximum payload size that the server will accept.
let maxPayload: UInt
/// The protocol version in use.
let proto: Int8
/// The server-assigned client ID. This may change during reconnection.
let clientId: UInt64?
/// The version of golang the NATS server was built with.
let go: String
/// The nonce used for nkeys.
let nonce: String?
/// A list of server urls that a client can connect to.
let connectUrls: [String]?
/// The client IP as known by the server.
let clientIp: String
/// Whether the server supports headers.
let headers: Bool
/// Whether server goes into lame duck
private let _lameDuckMode: Bool?
var lameDuckMode: Bool {
return _lameDuckMode ?? false
}
private static let prefix = NatsOperation.info.rawValue.data(using: .utf8)!
private enum CodingKeys: String, CodingKey {
case serverId = "server_id"
case serverName = "server_name"
case host
case port
case version
case authRequired = "auth_required"
case tlsRequired = "tls_required"
case maxPayload = "max_payload"
case proto
case clientId = "client_id"
case go
case nonce
case connectUrls = "connect_urls"
case clientIp = "client_ip"
case headers
case _lameDuckMode = "ldm"
}
internal static func parse(data: Data) throws -> ServerInfo {
let info = data.removePrefix(prefix)
return try JSONDecoder().decode(self, from: info)
}
}
enum ClientOp {
case publish((subject: String, reply: String?, payload: Data?, headers: NatsHeaderMap?))
case subscribe((sid: UInt64, subject: String, queue: String?))
case unsubscribe((sid: UInt64, max: UInt64?))
case connect(ConnectInfo)
case ping
case pong
}
/// Info to construct a CONNECT message.
struct ConnectInfo: Encodable {
/// Turns on +OK protocol acknowledgments.
var verbose: Bool
/// Turns on additional strict format checking, e.g. for properly formed
/// subjects.
var pedantic: Bool
/// User's JWT.
var userJwt: String?
/// Public nkey.
var nkey: String
/// Signed nonce, encoded to Base64URL.
var signature: String?
/// Optional client name.
var name: String
/// If set to `true`, the server (version 1.2.0+) will not send originating
/// messages from this connection to its own subscriptions. Clients should
/// set this to `true` only for server supporting this feature, which is
/// when proto in the INFO protocol is set to at least 1.
var echo: Bool
/// The implementation language of the client.
var lang: String
/// The version of the client.
var version: String
/// Sending 0 (or absent) indicates client supports original protocol.
/// Sending 1 indicates that the client supports dynamic reconfiguration
/// of cluster topology changes by asynchronously receiving INFO messages
/// with known servers it can reconnect to.
var natsProtocol: NatsProtocol
/// Indicates whether the client requires an SSL connection.
var tlsRequired: Bool
/// Connection username (if `auth_required` is set)
var user: String
/// Connection password (if auth_required is set)
var pass: String
/// Client authorization token (if auth_required is set)
var authToken: String
/// Whether the client supports the usage of headers.
var headers: Bool
/// Whether the client supports no_responders.
var noResponders: Bool
enum CodingKeys: String, CodingKey {
case verbose
case pedantic
case userJwt = "jwt"
case nkey
case signature = "sig" // Custom key name for JSON
case name
case echo
case lang
case version
case natsProtocol = "protocol"
case tlsRequired = "tls_required"
case user
case pass
case authToken = "auth_token"
case headers
case noResponders = "no_responders"
}
}
enum NatsProtocol: Encodable {
case original
case dynamic
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .original:
try container.encode(0)
case .dynamic:
try container.encode(1)
}
}
}