mirror of
https://gitee.com/farsunset/cim.git
synced 2025-06-17 07:35:25 +08:00
弃用Cocoapods, 支持SPM。SwiftUI Hello world!
This commit is contained in:
parent
1faf67a977
commit
4e0520b2b5
40
cim-client-sdk/cim-swift-sdk/.gitignore
vendored
40
cim-client-sdk/cim-swift-sdk/.gitignore
vendored
@ -1,37 +1,7 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
*.xccheckout
|
||||
profile
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
|
||||
# Bundler
|
||||
.bundle
|
||||
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
|
||||
#
|
||||
# Note: if you ignore the Pods directory, make sure to uncomment
|
||||
# `pod install` in .travis.yml
|
||||
#
|
||||
# Pods/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
|
@ -1,14 +0,0 @@
|
||||
# references:
|
||||
# * https://www.objc.io/issues/6-build-tools/travis-ci/
|
||||
# * https://github.com/supermarin/xcpretty#usage
|
||||
|
||||
osx_image: xcode7.3
|
||||
language: objective-c
|
||||
# cache: cocoapods
|
||||
# podfile: Example/Podfile
|
||||
# before_install:
|
||||
# - gem install cocoapods # Since Travis is not always on latest version
|
||||
# - pod install --project-directory=Example
|
||||
script:
|
||||
- set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/cimsdk.xcworkspace -scheme cimsdk-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty
|
||||
- pod lib lint
|
7
cim-client-sdk/cim-swift-sdk/CIMClient/.gitignore
vendored
Normal file
7
cim-client-sdk/cim-swift-sdk/CIMClient/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
39
cim-client-sdk/cim-swift-sdk/CIMClient/Package.swift
Normal file
39
cim-client-sdk/cim-swift-sdk/CIMClient/Package.swift
Normal file
@ -0,0 +1,39 @@
|
||||
// swift-tools-version:5.5
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "CIMClient",
|
||||
platforms: [
|
||||
.iOS(.v11),
|
||||
.macOS(.v11),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "CIMClient",
|
||||
targets: ["CIMClient"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
.package(url: "https://github.com/daltoniam/Starscream.git", .upToNextMajor(from: "4.0.0")),
|
||||
.package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.6.0")),
|
||||
.package(url: "https://github.com/stephencelis/SQLite.swift.git", .upToNextMajor(from: "0.13.0")),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "CIMClient",
|
||||
dependencies: [
|
||||
.product(name: "Starscream", package: "Starscream"),
|
||||
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
|
||||
.product(name: "SQLite", package: "SQLite.swift"),
|
||||
]),
|
||||
.testTarget(
|
||||
name: "CIMClientTests",
|
||||
dependencies: ["CIMClient"]),
|
||||
]
|
||||
)
|
3
cim-client-sdk/cim-swift-sdk/CIMClient/README.md
Normal file
3
cim-client-sdk/cim-swift-sdk/CIMClient/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# CIMClient
|
||||
|
||||
A description of this package.
|
@ -9,10 +9,10 @@ import Foundation
|
||||
|
||||
class WebMessageDecoder {
|
||||
|
||||
let messageFactory = MessageFactory()
|
||||
|
||||
func decoder(_ data: Data) -> Transportable? {
|
||||
let msg = messageFactory.transportData(data)
|
||||
let msg = MessageBuilder()
|
||||
.set(transportData: data)
|
||||
.build()
|
||||
return msg
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import Foundation
|
||||
class WebMessageEncoder {
|
||||
|
||||
func encoder(_ msg: Transportable) -> Data {
|
||||
var data = Data(bytes: msg.type.bytes)
|
||||
var data = Data(msg.type.bytes)
|
||||
data.append(contentsOf: msg.body)
|
||||
return data
|
||||
}
|
@ -10,7 +10,7 @@ import Foundation
|
||||
/// 消息类型
|
||||
public enum MessageType {
|
||||
|
||||
enum Byte: UInt8 {
|
||||
public enum Key: UInt8 {
|
||||
case pong = 0
|
||||
case ping = 1
|
||||
case message = 2
|
||||
@ -26,11 +26,11 @@ public enum MessageType {
|
||||
|
||||
var bytes: [UInt8] {
|
||||
switch self {
|
||||
case .pong: return [Byte.pong.rawValue]
|
||||
case .ping: return [Byte.ping.rawValue]
|
||||
case .message: return [Byte.message.rawValue]
|
||||
case .sentBody: return [Byte.sentBody.rawValue]
|
||||
case .replyBody: return [Byte.replyBody.rawValue]
|
||||
case .pong: return [Key.pong.rawValue]
|
||||
case .ping: return [Key.ping.rawValue]
|
||||
case .message: return [Key.message.rawValue]
|
||||
case .sentBody: return [Key.sentBody.rawValue]
|
||||
case .replyBody: return [Key.replyBody.rawValue]
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,6 @@ import Foundation
|
||||
|
||||
open class CIMClient {
|
||||
|
||||
var url: String
|
||||
|
||||
var wsClient: WebsocketClient
|
||||
|
||||
var account: Account? {
|
||||
@ -19,10 +17,14 @@ open class CIMClient {
|
||||
}
|
||||
}
|
||||
|
||||
public lazy var msgSender: MessageSender = { [unowned self] in
|
||||
return MessageSenderImp(client: self)
|
||||
}()
|
||||
|
||||
public init(url: String) {
|
||||
self.url = url
|
||||
self.wsClient = WebsocketClient(url: URL(string: url)!)
|
||||
self.wsClient.appendMessageInterceptor(MessageLogInterceptor())
|
||||
self.wsClient.appendMessageInterceptor(MessageDatabaseInterceptor())
|
||||
self.wsClient.appendMessageInterceptor(AutoReConnectInterceptor(client: self))
|
||||
self.wsClient.appendMessageInterceptor(HeartInterceptor(client: self))
|
||||
}
|
||||
@ -35,7 +37,7 @@ open class CIMClient {
|
||||
wsClient.disconnect()
|
||||
}
|
||||
|
||||
public func sendMessage(_ message: Transportable) {
|
||||
func sendMessage(_ message: Transportable) {
|
||||
wsClient.sendMessage(message)
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
//
|
||||
// MessageSender.swift
|
||||
// cimsdk
|
||||
//
|
||||
// Created by FeiYu on 2021/10/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol MessageSender {
|
||||
|
||||
func sendTextMessage(_ text: String, receiver: String)
|
||||
}
|
||||
|
||||
class MessageSenderImp: MessageSender {
|
||||
|
||||
weak var client: CIMClient?
|
||||
|
||||
init(client: CIMClient) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
func sendTextMessage(_ text: String, receiver: String) {
|
||||
if let client = client {
|
||||
if let account = client.account {
|
||||
let msg = MessageBuilder()
|
||||
.set(type: .message)
|
||||
.set(action: "2")
|
||||
.set(sender: String(account.id))
|
||||
.set(receiver: receiver)
|
||||
.set(title: "title")
|
||||
.set(content: text)
|
||||
.set(format: "")
|
||||
.set(extra: "")
|
||||
.build()
|
||||
if let msg = msg {
|
||||
client.sendMessage(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
//
|
||||
// CIMDBManager.swift
|
||||
// cimsdk
|
||||
//
|
||||
// Created by FeiYu on 2021/10/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
class CIMDBManager {
|
||||
|
||||
let path: String
|
||||
|
||||
lazy var db: Connection? = {
|
||||
do {
|
||||
let db = try Connection(path)
|
||||
return db
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
lazy var messageDB: MessageDB = { [unowned self] in
|
||||
let messageDB = MessageDB(db: self)
|
||||
return messageDB
|
||||
}()
|
||||
|
||||
init(path: String) {
|
||||
self.path = path
|
||||
}
|
||||
|
||||
func execute(query: String) throws {
|
||||
print("DB query: \(query)")
|
||||
if let db = db {
|
||||
do {
|
||||
try db.execute(query)
|
||||
} catch {
|
||||
print("DB 查询语句执行失败!")
|
||||
}
|
||||
} else {
|
||||
print("DB 连接失败!")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
//
|
||||
// MessageDB.swift
|
||||
// cimsdk
|
||||
//
|
||||
// Created by FeiYu on 2021/10/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class MessageDB {
|
||||
|
||||
weak var db: CIMDBManager?
|
||||
|
||||
init(db: CIMDBManager) {
|
||||
self.db = db
|
||||
do {
|
||||
try createTable()
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func createTable() throws {
|
||||
|
||||
let createSQL = """
|
||||
CREATE TABLE IF NOT EXISTS "messages" (
|
||||
"id" INT64 NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"sender" TEXT NOT NULL,
|
||||
"receiver" TEXT NOT NULL,
|
||||
"format" TEXT NOT NULL,
|
||||
"extra" TEXT NOT NULL,
|
||||
"timestamp" INT64 NOT NULL,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
"""
|
||||
try db?.execute(query: createSQL)
|
||||
}
|
||||
|
||||
func insert(_ message: Message) throws {
|
||||
|
||||
let insertSQL = """
|
||||
REPLACE INTO "messages" ("id","action","title","content","sender","receiver","format","extra","timestamp") VALUES (
|
||||
\(message.id),
|
||||
"\(message.action)",
|
||||
"\(message.title)",
|
||||
"\(message.content)",
|
||||
"\(message.sender)",
|
||||
"\(message.receiver)",
|
||||
"\(message.format)",
|
||||
"\(message.extra)",
|
||||
\(message.timestamp));
|
||||
"""
|
||||
try db?.execute(query: insertSQL)
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
//
|
||||
// AutoReConnectInterceptor.swift
|
||||
// cimsdk
|
||||
//
|
||||
// Created by FeiYu on 2021/10/1.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AutoReConnectInterceptor: MessageInterceptor {
|
||||
|
||||
var uniqueID: String = UUID().uuidString
|
||||
|
||||
weak var client: CIMClient?
|
||||
|
||||
init(client: CIMClient) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
func connect(_ event: ConnectEvent) {
|
||||
switch event {
|
||||
case .connected(_):
|
||||
if let account = self.client?.account {
|
||||
let msg = MessageBuilder()
|
||||
.set(type: .sentBody)
|
||||
.set(key: "client_bind")
|
||||
.set(data: [
|
||||
"uid": String(account.id),
|
||||
"channel": "ios",
|
||||
"deviceId": UUID().uuidString, //UIDevice.current.identifierForVendor?.uuidString ?? "",
|
||||
"token": account.token ?? ""
|
||||
])
|
||||
.build()
|
||||
if let client = self.client,
|
||||
let msg = msg {
|
||||
client.sendMessage(msg)
|
||||
}
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
//
|
||||
// MessageDBInterceptor.swift
|
||||
// cimsdk
|
||||
//
|
||||
// Created by FeiYu on 2021/10/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class MessageDatabaseInterceptor: MessageInterceptor {
|
||||
|
||||
var uniqueID: String = UUID().uuidString
|
||||
|
||||
let dbManager: CIMDBManager? = {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
let databasePath = documentsURL.appendingPathComponent("db.sqlite3").path
|
||||
print("directory path:", documentsURL.path)
|
||||
print("database path:", databasePath)
|
||||
if !fileManager.fileExists(atPath: databasePath) {
|
||||
fileManager.createFile(atPath: databasePath, contents: nil, attributes: nil)
|
||||
}
|
||||
let dbManager = CIMDBManager(path: databasePath)
|
||||
return dbManager
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
/// MARK: - MessageListener
|
||||
func receiveMessage(_ message: Transportable) {
|
||||
do {
|
||||
try insertMsg(message)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - MessageSendListener
|
||||
func sendMessage(_ message: Transportable) {
|
||||
do {
|
||||
try insertMsg(message)
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func insertMsg(_ message: Transportable) throws {
|
||||
switch message.type {
|
||||
case .message(let msg):
|
||||
try dbManager?.messageDB.insert(msg)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,9 +8,9 @@
|
||||
import Foundation
|
||||
|
||||
public struct Account: Codable {
|
||||
let id: Int64
|
||||
let name: String
|
||||
let token: String?
|
||||
public let id: Int64
|
||||
public let name: String
|
||||
public let token: String?
|
||||
|
||||
public init(id: Int64, name: String, token: String?) {
|
||||
self.id = id
|
@ -7,17 +7,17 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Message: Codable {
|
||||
public struct Message: Identifiable, Equatable, Codable {
|
||||
|
||||
let id: Int64
|
||||
let action: String
|
||||
let title: String
|
||||
let content: String
|
||||
let sender: String
|
||||
let receiver: String
|
||||
let format: String
|
||||
let extra: String
|
||||
let timestamp: Int64
|
||||
public let id: Int64
|
||||
public let action: String
|
||||
public let title: String
|
||||
public let content: String
|
||||
public let sender: String
|
||||
public let receiver: String
|
||||
public let format: String
|
||||
public let extra: String
|
||||
public let timestamp: Int64
|
||||
|
||||
public init(action: String, sender: String, receiver: String, title: String?, content: String?, format: String?, extra: String?) {
|
||||
self.id = Date().currentTimestamp()
|
@ -0,0 +1,131 @@
|
||||
//
|
||||
// MessageFactory.swift
|
||||
// cimsdk
|
||||
//
|
||||
// Created by FeiYu on 2021/9/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class MessageBuilder {
|
||||
/// Type
|
||||
private var type: MessageType.Key?
|
||||
/// Message
|
||||
private var action: String = String()
|
||||
private var title: String = String()
|
||||
private var content: String = String()
|
||||
private var sender: String = String()
|
||||
private var receiver: String = String()
|
||||
private var format: String = String()
|
||||
private var extra: String = String()
|
||||
/// Reply & Sent
|
||||
private var key: String = String()
|
||||
private var data: [String:String]? = [:]
|
||||
/// Reply
|
||||
private var code: String = String()
|
||||
private var message: String? = String()
|
||||
|
||||
/// Transportable Data
|
||||
private var transportData: Data = Data()
|
||||
|
||||
|
||||
public func set(type: MessageType.Key) -> MessageBuilder {
|
||||
self.type = type
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(action: String) -> MessageBuilder {
|
||||
self.action = action
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(title: String) -> MessageBuilder {
|
||||
self.title = title
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(content: String) -> MessageBuilder {
|
||||
self.content = content
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(sender: String) -> MessageBuilder {
|
||||
self.sender = sender
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(receiver: String) -> MessageBuilder {
|
||||
self.receiver = receiver
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(format: String) -> MessageBuilder {
|
||||
self.format = format
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(extra: String) -> MessageBuilder {
|
||||
self.extra = extra
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(key: String) -> MessageBuilder {
|
||||
self.key = key
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(data: [String: String]) -> MessageBuilder {
|
||||
self.data = data
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(code: String) -> MessageBuilder {
|
||||
self.code = code
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(message: String) -> MessageBuilder {
|
||||
self.message = message
|
||||
return self
|
||||
}
|
||||
|
||||
public func set(transportData: Data) -> MessageBuilder {
|
||||
self.transportData = transportData
|
||||
return self
|
||||
}
|
||||
|
||||
public func build() -> Transportable? {
|
||||
switch type {
|
||||
case .pong: return Pong()
|
||||
case .ping: return Ping()
|
||||
case .message: return Message(
|
||||
action: action,
|
||||
sender: sender,
|
||||
receiver: receiver,
|
||||
title: title,
|
||||
content: content,
|
||||
format: format,
|
||||
extra: extra)
|
||||
case .sentBody: return SentBody(key: key, data: data ?? [:])
|
||||
case .replyBody: return ReplyBody(key: key, code: code, message: message, data: data)
|
||||
case .none:
|
||||
let data = transportData
|
||||
if data.count > 3 {
|
||||
let bytes: [UInt8] = data.map{$0}
|
||||
let type = MessageType.Key(rawValue: bytes[0])
|
||||
let body: [UInt8] = bytes[1..<bytes.count].map{$0}
|
||||
switch type {
|
||||
case .ping: return Ping()
|
||||
case .pong: return Pong()
|
||||
case .message: return Message(bytes: body)
|
||||
case .sentBody: return SentBody(bytes: body)
|
||||
case .replyBody: return ReplyBody(bytes: body)
|
||||
default: return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import XCTest
|
||||
@testable import CIMClient
|
||||
|
||||
final class CIMClientTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
// XCTAssertEqual(CIMClient().text, "Hello, World!")
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
21
cim-client-sdk/cim-swift-sdk/Demo/Shared/Assets.xcassets/avatar.imageset/Contents.json
vendored
Normal file
21
cim-client-sdk/cim-swift-sdk/Demo/Shared/Assets.xcassets/avatar.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "avatar.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
cim-client-sdk/cim-swift-sdk/Demo/Shared/Assets.xcassets/avatar.imageset/avatar.jpg
vendored
Normal file
BIN
cim-client-sdk/cim-swift-sdk/Demo/Shared/Assets.xcassets/avatar.imageset/avatar.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
18
cim-client-sdk/cim-swift-sdk/Demo/Shared/CIMAppApp.swift
Normal file
18
cim-client-sdk/cim-swift-sdk/Demo/Shared/CIMAppApp.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// CIMAppApp.swift
|
||||
// Shared
|
||||
//
|
||||
// Created by FeiYu on 2021/10/10.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct CIMAppApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
.windowStyle(HiddenTitleBarWindowStyle())
|
||||
}
|
||||
}
|
22
cim-client-sdk/cim-swift-sdk/Demo/Shared/ContentView.swift
Normal file
22
cim-client-sdk/cim-swift-sdk/Demo/Shared/ContentView.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Shared
|
||||
//
|
||||
// Created by FeiYu on 2021/10/10.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
Button("sent msg") {
|
||||
imClient.msgSender.sendTextMessage("hello", receiver: "123456")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
//
|
||||
// IMClient.swift
|
||||
// cimsdk_Example
|
||||
// CIMApp (iOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/1.
|
||||
// Copyright © 2021 CocoaPods. All rights reserved.
|
||||
// Created by FeiYu on 2021/10/10.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import cimsdk
|
||||
import CIMClient
|
||||
|
||||
let account = Account(id: 111111, name: "FeiYu", token: "qwqewewrettryry")
|
||||
let imClient = CIMClient(url: "ws://192.168.2.100:34567")
|
20
cim-client-sdk/cim-swift-sdk/Demo/macOS/ContentView.swift
Normal file
20
cim-client-sdk/cim-swift-sdk/Demo/macOS/ContentView.swift
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/10.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
Home()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
40
cim-client-sdk/cim-swift-sdk/Demo/macOS/Extension.swift
Normal file
40
cim-client-sdk/cim-swift-sdk/Demo/macOS/Extension.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Extension.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/16.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension NSTextField {
|
||||
|
||||
open override var focusRingType: NSFocusRingType {
|
||||
get{.none}
|
||||
set{}
|
||||
}
|
||||
}
|
||||
|
||||
extension Int64 {
|
||||
|
||||
//MARK: -时间戳转时间
|
||||
func timeStampToTime() -> String {
|
||||
let currentTime = Date().timeIntervalSince1970
|
||||
let timeSta: TimeInterval = TimeInterval(self / 1000)
|
||||
let reduceTime : TimeInterval = currentTime - timeSta
|
||||
let hours = Int(reduceTime / 3600)
|
||||
let date = NSDate(timeIntervalSince1970: timeSta)
|
||||
let dfmatter = DateFormatter()
|
||||
if hours < 24 {
|
||||
dfmatter.dateFormat="HH:mm"
|
||||
return dfmatter.string(from: date as Date)
|
||||
}
|
||||
let days = Int(reduceTime / 3600 / 24)
|
||||
if days < 365 {
|
||||
dfmatter.dateFormat="MM月dd日 HH:mm"
|
||||
return dfmatter.string(from: date as Date)
|
||||
}
|
||||
dfmatter.dateFormat="yyyy年MM月dd日 HH:mm:ss"
|
||||
return dfmatter.string(from: date as Date)
|
||||
}
|
||||
}
|
21
cim-client-sdk/cim-swift-sdk/Demo/macOS/Model/Session.swift
Normal file
21
cim-client-sdk/cim-swift-sdk/Demo/macOS/Model/Session.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Session.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CIMClient
|
||||
|
||||
struct Session: Identifiable {
|
||||
var id: String
|
||||
var lastMsg: String
|
||||
var lastMsgTime: String
|
||||
var pendingMsgs: String
|
||||
var userName: String
|
||||
var userImage: String
|
||||
var allMsgs: [Message]
|
||||
}
|
||||
|
||||
var localSessions : [Session] = []
|
14
cim-client-sdk/cim-swift-sdk/Demo/macOS/Model/User.swift
Normal file
14
cim-client-sdk/cim-swift-sdk/Demo/macOS/Model/User.swift
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// User.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/16.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct User: Identifiable {
|
||||
var id: String = ""
|
||||
var name: String = "name"
|
||||
var avatar: String = "avatar"
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
//
|
||||
// AllChatsView.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AllChatsView: View {
|
||||
|
||||
@EnvironmentObject var homeData: HomeViewModel
|
||||
var body: some View {
|
||||
// Side Tab View....
|
||||
|
||||
VStack {
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: {}, label: {
|
||||
Image(systemName: "plus")
|
||||
.font(.title2)
|
||||
.foregroundColor(.white)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
HStack{
|
||||
Image(systemName: "magnifyingglass")
|
||||
.foregroundColor(.gray)
|
||||
TextField("Search", text: $homeData.search)
|
||||
.textFieldStyle(PlainTextFieldStyle())
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal)
|
||||
.background(Color.primary.opacity(0.15))
|
||||
.cornerRadius(10)
|
||||
.padding()
|
||||
|
||||
|
||||
List(selection: $homeData.selectedSession){
|
||||
|
||||
ForEach(homeData.sessions) { session in
|
||||
|
||||
NavigationLink(destination: DetailView(session: session)) {
|
||||
RecentCardView(session: session)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 280)
|
||||
.listStyle(SidebarListStyle())
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct AllChatsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Home()
|
||||
}
|
||||
}
|
22
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/BlurView.swift
Normal file
22
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/BlurView.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// BlurView.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct BlurView: NSViewRepresentable {
|
||||
|
||||
|
||||
func makeNSView(context: Context) -> NSVisualEffectView {
|
||||
let view = NSVisualEffectView()
|
||||
view.blendingMode = .behindWindow
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
|
||||
|
||||
}
|
||||
}
|
299
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/DetailView.swift
Normal file
299
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/DetailView.swift
Normal file
@ -0,0 +1,299 @@
|
||||
//
|
||||
// DetailView.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/16.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CIMClient
|
||||
|
||||
struct DetailView: View {
|
||||
@EnvironmentObject var homeData: HomeViewModel
|
||||
var session: Session
|
||||
var body: some View {
|
||||
HStack{
|
||||
VStack{
|
||||
HStack{
|
||||
Text(session.userName)
|
||||
.font(.title2)
|
||||
Spacer()
|
||||
Button(action: {}, label: {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.font(.title2)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
Button(action: {withAnimation{homeData.isExpanded.toggle()}}, label: {
|
||||
Image(systemName: "sidebar.right")
|
||||
.font(.title2)
|
||||
.foregroundColor(homeData.isExpanded ? .blue : .primary)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.padding()
|
||||
|
||||
MessageView(session: session)
|
||||
|
||||
HStack(spacing:15){
|
||||
Button(action: {
|
||||
print(homeData.message)
|
||||
}, label: {
|
||||
Image(systemName: "paperplane")
|
||||
.font(.title2)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
TextField("Enter Message", text: $homeData.message, onCommit: {
|
||||
homeData.sendMessage(session: session)
|
||||
})
|
||||
.textFieldStyle(PlainTextFieldStyle())
|
||||
.padding(.vertical,8)
|
||||
.padding(.horizontal)
|
||||
.clipShape(Capsule())
|
||||
.background(Capsule().strokeBorder(Color.white))
|
||||
|
||||
Button(action: { }, label: {
|
||||
Image(systemName: "face.smiling.fill")
|
||||
.font(.title2)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
Button(action: {}, label: {
|
||||
Image(systemName: "mic")
|
||||
.font(.title2)
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.padding([.horizontal,.bottom])
|
||||
}
|
||||
|
||||
ExpandedView(session: session)
|
||||
.background(BlurView())
|
||||
.frame(width: homeData.isExpanded ? nil : 0)
|
||||
.opacity(homeData.isExpanded ? 1 : 0)
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: .all)
|
||||
.onAppear(perform: {
|
||||
homeData.clearUnReadMessage(session: session)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct DetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Home()
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageView: View {
|
||||
|
||||
@EnvironmentObject var homeData: HomeViewModel
|
||||
var session: Session
|
||||
var body: some View {
|
||||
|
||||
GeometryReader {render in
|
||||
ScrollView{
|
||||
|
||||
ScrollViewReader{proxy in
|
||||
VStack{
|
||||
ForEach(session.allMsgs){message in
|
||||
|
||||
MessageCardView(message: message, session: session, width: render.frame(in: .global).width)
|
||||
.padding(.bottom,20)
|
||||
.tag(message.id)
|
||||
}
|
||||
}
|
||||
.padding(.leading,10)
|
||||
.onAppear(perform: {
|
||||
let lastID = session.allMsgs.last!.id
|
||||
proxy.scrollTo(lastID, anchor: .bottom)
|
||||
})
|
||||
.onChange(of: session.allMsgs, perform: { value in
|
||||
withAnimation{
|
||||
let lastID = value.last!.id
|
||||
proxy.scrollTo(lastID, anchor: .bottom)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct MessageCardView: View {
|
||||
|
||||
@EnvironmentObject var homeData: HomeViewModel
|
||||
|
||||
var message: Message
|
||||
var session: Session
|
||||
var width: CGFloat
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack(spacing: 10) {
|
||||
let myMessage = (message.sender == "\(homeData.account.id)")
|
||||
if myMessage {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(message.content)
|
||||
.foregroundColor(.white)
|
||||
.padding(10)
|
||||
.background(Color.blue)
|
||||
.clipShape(MessageBubble(myMessage: myMessage))
|
||||
.frame(minWidth: 0, maxWidth: width / 2, alignment: .trailing)
|
||||
|
||||
Image(session.userImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 35, height: 35)
|
||||
.clipShape(Circle())
|
||||
.offset(y: 20)
|
||||
|
||||
} else {
|
||||
|
||||
Image(session.userImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 35, height: 35)
|
||||
.clipShape(Circle())
|
||||
.offset(y: 20)
|
||||
|
||||
Text(message.content)
|
||||
.foregroundColor(.white)
|
||||
.padding(10)
|
||||
.background(Color.primary.opacity(0.2))
|
||||
.clipShape(MessageBubble(myMessage: myMessage))
|
||||
.frame(minWidth: 0, maxWidth: width / 2, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct MessageBubble: Shape {
|
||||
|
||||
var myMessage: Bool
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
return Path{ path in
|
||||
let pt1 = CGPoint(x: 0, y: 0)
|
||||
let pt2 = CGPoint(x: rect.width, y: 0)
|
||||
let pt3 = CGPoint(x: rect.width, y: rect.height)
|
||||
let pt4 = CGPoint(x: 0, y: rect.height)
|
||||
|
||||
if myMessage {
|
||||
path.move(to: pt3)
|
||||
path.addArc(tangent1End: pt3, tangent2End: pt4, radius: 15)
|
||||
path.addArc(tangent1End: pt4, tangent2End: pt1, radius: 15)
|
||||
path.addArc(tangent1End: pt1, tangent2End: pt2, radius: 15)
|
||||
path.addArc(tangent1End: pt2, tangent2End: pt3, radius: 15)
|
||||
} else {
|
||||
path.move(to: pt4)
|
||||
path.addArc(tangent1End: pt4, tangent2End: pt1, radius: 15)
|
||||
path.addArc(tangent1End: pt1, tangent2End: pt2, radius: 15)
|
||||
path.addArc(tangent1End: pt2, tangent2End: pt3, radius: 15)
|
||||
path.addArc(tangent1End: pt3, tangent2End: pt4, radius: 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ExpandedView: View {
|
||||
@EnvironmentObject var homeData: HomeViewModel
|
||||
var session: Session
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
Divider()
|
||||
|
||||
VStack(spacing: 25) {
|
||||
|
||||
Image(session.userImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 90, height: 90)
|
||||
.clipShape(Circle())
|
||||
.padding(.top,35)
|
||||
|
||||
Text(session.userName)
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
|
||||
HStack{
|
||||
|
||||
Button(action: {}, label: {
|
||||
VStack{
|
||||
Image(systemName: "bell.slash")
|
||||
.font(.title2)
|
||||
Text("Mute")
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {}, label: {
|
||||
VStack{
|
||||
Image(systemName: "hand.raised.fill")
|
||||
.font(.title2)
|
||||
Text("Block")
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {}, label: {
|
||||
VStack{
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.font(.title2)
|
||||
Text("Report")
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Picker(selection: $homeData.pickedTab, label: Text("Picker"), content: {
|
||||
Text("Media").tag("Media")
|
||||
Text("Links").tag("Links")
|
||||
Text("Audio").tag("Audio")
|
||||
Text("Files").tag("Files")
|
||||
})
|
||||
.labelsHidden()
|
||||
.padding(.vertical)
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
|
||||
ScrollView{
|
||||
if homeData.pickedTab == "Media" {
|
||||
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 10), count: 3), spacing: 10, content: {
|
||||
|
||||
ForEach(1...8, id: \.self) {
|
||||
index in
|
||||
Image("avatar")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 80, height: 80)
|
||||
.cornerRadius(3)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Text("No \(homeData.pickedTab)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.frame(maxWidth: 300)
|
||||
}
|
||||
}
|
||||
}
|
58
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/Home.swift
Normal file
58
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/Home.swift
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Home.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
var screen = NSScreen.main!.visibleFrame
|
||||
|
||||
struct Home: View {
|
||||
|
||||
@StateObject var homeData = HomeViewModel()
|
||||
|
||||
var body: some View {
|
||||
|
||||
HStack(spacing:0) {
|
||||
VStack {
|
||||
|
||||
TabButton(image: "message", title: "All Chats", selectedTab: $homeData.selectedTab)
|
||||
TabButton(image: "person", title: "Personal", selectedTab: $homeData.selectedTab)
|
||||
TabButton(image: "bubble.middle.bottom", title: "Bots", selectedTab: $homeData.selectedTab)
|
||||
TabButton(image: "slider.horizontal.3", title: "Edit", selectedTab: $homeData.selectedTab)
|
||||
Spacer()
|
||||
TabButton(image: "gear", title: "Settings", selectedTab: $homeData.selectedTab)
|
||||
}
|
||||
.padding()
|
||||
.padding(.top, 35)
|
||||
.background(BlurView())
|
||||
|
||||
// Tab Content
|
||||
ZStack {
|
||||
|
||||
switch homeData.selectedTab {
|
||||
case "All Chats": NavigationView{
|
||||
AllChatsView()
|
||||
}
|
||||
case "Personal": Text("Personal")
|
||||
case "Bots": Text("Bots")
|
||||
case "Edit": Text("Edit")
|
||||
case "Settings": Text("Settings")
|
||||
default: Text("")
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
.ignoresSafeArea(.all, edges: .all)
|
||||
.frame(minWidth: screen.width / 1.2, maxWidth: .infinity, minHeight: screen.height - 60, maxHeight: .infinity)
|
||||
.environmentObject(homeData)
|
||||
}
|
||||
}
|
||||
|
||||
struct Home_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Home()
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
//
|
||||
// RecentCardView.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RecentCardView: View {
|
||||
var session: Session
|
||||
var body: some View {
|
||||
HStack {
|
||||
|
||||
Image(session.userImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(spacing: 4) {
|
||||
HStack{
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(session.userName)
|
||||
.fontWeight(.bold)
|
||||
Text(session.lastMsg)
|
||||
.font(.caption)
|
||||
}
|
||||
Spacer(minLength: 10)
|
||||
VStack{
|
||||
Text(session.lastMsgTime)
|
||||
.font(.caption)
|
||||
Text(session.pendingMsgs)
|
||||
.font(.caption2)
|
||||
.padding(5)
|
||||
.foregroundColor(.white)
|
||||
.background(Color.blue)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RecentCardView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Home()
|
||||
}
|
||||
}
|
38
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/TabButton.swift
Normal file
38
cim-client-sdk/cim-swift-sdk/Demo/macOS/View/TabButton.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// TabButton.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct TabButton: View {
|
||||
|
||||
var image: String
|
||||
var title: String
|
||||
@Binding var selectedTab: String
|
||||
|
||||
var body: some View {
|
||||
Button(action: {withAnimation{selectedTab = title}}) {
|
||||
|
||||
VStack(spacing: 7) {
|
||||
Image(systemName: image)
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(selectedTab == title ? .white : .gray)
|
||||
|
||||
Text(title)
|
||||
.fontWeight(.semibold)
|
||||
.font(.system(size: 11))
|
||||
.foregroundColor(selectedTab == title ? .white : .gray)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.frame(width: 70)
|
||||
.contentShape(Rectangle())
|
||||
.background(Color.primary.opacity(selectedTab == title ? 0.15 : 0))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,119 @@
|
||||
//
|
||||
// HomeViewModel.swift
|
||||
// CIMApp (macOS)
|
||||
//
|
||||
// Created by FeiYu on 2021/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CIMClient
|
||||
|
||||
class HomeViewModel: ObservableObject, MessageInterceptor {
|
||||
|
||||
@Published var selectedTab = "All Chats"
|
||||
|
||||
@Published var sessions : [Session] = localSessions
|
||||
|
||||
@Published var selectedSession : String? = localSessions.first?.id
|
||||
|
||||
@Published var search = ""
|
||||
|
||||
@Published var message = ""
|
||||
|
||||
@Published var isExpanded = false
|
||||
|
||||
@Published var pickedTab = "Media"
|
||||
|
||||
let uniqueID: String = UUID().uuidString
|
||||
let account = Account(id: 123456, name: "FeiYu", token: "qwqewewrettryry")
|
||||
let imClient = CIMClient(url: "ws://192.168.2.100:34567")
|
||||
|
||||
func sendMessage(session: Session) {
|
||||
|
||||
if message != "" {
|
||||
imClient.msgSender.sendTextMessage(message, receiver: session.id)
|
||||
message = ""
|
||||
}
|
||||
}
|
||||
|
||||
func clearUnReadMessage(session: Session) {
|
||||
let index = sessions.firstIndex { (s) -> Bool in
|
||||
return s.id == session.id
|
||||
} ?? -1
|
||||
if index != -1 {
|
||||
var session = sessions[index]
|
||||
session.pendingMsgs = "0"
|
||||
sessions[index] = session
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
imClient.appendMessageInterceptor(self)
|
||||
imClient.connect(account)
|
||||
}
|
||||
|
||||
/// MARK: - MessageInterceptor
|
||||
func connect(_ event: ConnectEvent) {
|
||||
|
||||
}
|
||||
|
||||
func sendMessage(_ message: Transportable) {
|
||||
switch message.type {
|
||||
case .message(let msg):
|
||||
let index = sessions.firstIndex { (session) -> Bool in
|
||||
return session.id == msg.receiver
|
||||
} ?? -1
|
||||
|
||||
if index != -1 {
|
||||
var session = sessions[index]
|
||||
session.allMsgs.append(msg)
|
||||
session.lastMsg = msg.content
|
||||
session.lastMsgTime = msg.timestamp.timeStampToTime()
|
||||
session.pendingMsgs = "0"
|
||||
sessions[index] = session
|
||||
} else {
|
||||
let session = Session(id: msg.receiver, lastMsg: msg.content, lastMsgTime: msg.timestamp.timeStampToTime(), pendingMsgs: "0", userName: "uid:\(msg.receiver)", userImage: "avatar", allMsgs: [msg])
|
||||
sessions.append(session)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func receiveMessage(_ message: Transportable) {
|
||||
switch message.type {
|
||||
case .message(let msg):
|
||||
let index = sessions.firstIndex { (session) -> Bool in
|
||||
return session.id == msg.sender
|
||||
} ?? -1
|
||||
|
||||
if index != -1 {
|
||||
var session = sessions[index]
|
||||
session.lastMsg = msg.content
|
||||
session.lastMsgTime = msg.timestamp.timeStampToTime()
|
||||
session.allMsgs.append(msg)
|
||||
if var pendingMsgs = Int(session.pendingMsgs) {
|
||||
pendingMsgs += 1
|
||||
session.pendingMsgs = "\(pendingMsgs)"
|
||||
}
|
||||
if let selectedSession = selectedSession {
|
||||
if session.id == selectedSession {
|
||||
session.pendingMsgs = "0"
|
||||
}
|
||||
}
|
||||
sessions[index] = session
|
||||
} else {
|
||||
let session = Session(id: msg.sender, lastMsg: msg.content, lastMsgTime: msg.timestamp.timeStampToTime(), pendingMsgs: "1", userName: "uid:\(msg.sender)", userImage: "avatar", allMsgs: [msg])
|
||||
sessions.append(session)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func receiveMessageWithError(_ error: Error) {
|
||||
|
||||
}
|
||||
}
|
14
cim-client-sdk/cim-swift-sdk/Demo/macOS/macOS.entitlements
Normal file
14
cim-client-sdk/cim-swift-sdk/Demo/macOS/macOS.entitlements
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@ -1,13 +0,0 @@
|
||||
use_frameworks!
|
||||
|
||||
platform :ios, '9.0'
|
||||
|
||||
target 'cimsdk_Example' do
|
||||
pod 'cimsdk', :path => '../'
|
||||
|
||||
target 'cimsdk_Tests' do
|
||||
inherit! :search_paths
|
||||
|
||||
|
||||
end
|
||||
end
|
@ -1,27 +0,0 @@
|
||||
PODS:
|
||||
- cimsdk (0.1.0):
|
||||
- Starscream (~> 4.0.0)
|
||||
- SwiftProtobuf (~> 1.0)
|
||||
- Starscream (4.0.4)
|
||||
- SwiftProtobuf (1.17.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- cimsdk (from `../`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Starscream
|
||||
- SwiftProtobuf
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
cimsdk:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
cimsdk: b47490e772cd2be7616f3c5a426ac2ac8e671a6a
|
||||
Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9
|
||||
SwiftProtobuf: 9c85136c6ba74b0a1b84279dbf0f6db8efb714e0
|
||||
|
||||
PODFILE CHECKSUM: 9553001a2a8e262563768c3daaccec410de16fe8
|
||||
|
||||
COCOAPODS: 1.10.1
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "cimsdk",
|
||||
"version": "0.1.0",
|
||||
"summary": "A short description of cimsdk.",
|
||||
"description": "TODO: Add long description of the pod here.",
|
||||
"homepage": "https://github.com/飞鱼/cimsdk",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"file": "LICENSE"
|
||||
},
|
||||
"authors": {
|
||||
"飞鱼": "870027381@qq.com"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/飞鱼/cimsdk.git",
|
||||
"tag": "0.1.0"
|
||||
},
|
||||
"platforms": {
|
||||
"ios": "9.0"
|
||||
},
|
||||
"source_files": "cimsdk/Classes/**/*",
|
||||
"dependencies": {
|
||||
"Starscream": [
|
||||
"~> 4.0.0"
|
||||
],
|
||||
"SwiftProtobuf": [
|
||||
"~> 1.0"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
PODS:
|
||||
- cimsdk (0.1.0):
|
||||
- Starscream (~> 4.0.0)
|
||||
- SwiftProtobuf (~> 1.0)
|
||||
- Starscream (4.0.4)
|
||||
- SwiftProtobuf (1.17.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- cimsdk (from `../`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Starscream
|
||||
- SwiftProtobuf
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
cimsdk:
|
||||
:path: "../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
cimsdk: b47490e772cd2be7616f3c5a426ac2ac8e671a6a
|
||||
Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9
|
||||
SwiftProtobuf: 9c85136c6ba74b0a1b84279dbf0f6db8efb714e0
|
||||
|
||||
PODFILE CHECKSUM: 9553001a2a8e262563768c3daaccec410de16fe8
|
||||
|
||||
COCOAPODS: 1.10.1
|
File diff suppressed because it is too large
Load Diff
@ -1,176 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2014-2016 Dalton Cherry.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
@ -1,308 +0,0 @@
|
||||

|
||||
|
||||
Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6455)) library in Swift.
|
||||
|
||||
## Features
|
||||
|
||||
- Conforms to all of the base [Autobahn test suite](https://crossbar.io/autobahn/).
|
||||
- Nonblocking. Everything happens in the background, thanks to GCD.
|
||||
- TLS/WSS support.
|
||||
- Compression Extensions support ([RFC 7692](https://tools.ietf.org/html/rfc7692))
|
||||
|
||||
### Import the framework
|
||||
|
||||
First thing is to import the framework. See the Installation instructions on how to add the framework to your project.
|
||||
|
||||
```swift
|
||||
import Starscream
|
||||
```
|
||||
|
||||
### Connect to the WebSocket Server
|
||||
|
||||
Once imported, you can open a connection to your WebSocket server. Note that `socket` is probably best as a property, so it doesn't get deallocated right after being setup.
|
||||
|
||||
```swift
|
||||
var request = URLRequest(url: URL(string: "http://localhost:8080")!)
|
||||
request.timeoutInterval = 5
|
||||
socket = WebSocket(request: request)
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
```
|
||||
|
||||
After you are connected, there is either a delegate or closure you can use for process WebSocket events.
|
||||
|
||||
### Receiving data from a WebSocket
|
||||
|
||||
`didReceive` receives all the WebSocket events in a single easy to handle enum.
|
||||
|
||||
```swift
|
||||
func didReceive(event: WebSocketEvent, client: WebSocket) {
|
||||
switch event {
|
||||
case .connected(let headers):
|
||||
isConnected = true
|
||||
print("websocket is connected: \(headers)")
|
||||
case .disconnected(let reason, let code):
|
||||
isConnected = false
|
||||
print("websocket is disconnected: \(reason) with code: \(code)")
|
||||
case .text(let string):
|
||||
print("Received text: \(string)")
|
||||
case .binary(let data):
|
||||
print("Received data: \(data.count)")
|
||||
case .ping(_):
|
||||
break
|
||||
case .pong(_):
|
||||
break
|
||||
case .viabilityChanged(_):
|
||||
break
|
||||
case .reconnectSuggested(_):
|
||||
break
|
||||
case .cancelled:
|
||||
isConnected = false
|
||||
case .error(let error):
|
||||
isConnected = false
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The closure of this would be:
|
||||
|
||||
```swift
|
||||
socket.onEvent = { event in
|
||||
switch event {
|
||||
// handle events just like above...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Writing to a WebSocket
|
||||
|
||||
### write a binary frame
|
||||
|
||||
The writeData method gives you a simple way to send `Data` (binary) data to the server.
|
||||
|
||||
```swift
|
||||
socket.write(data: data) //write some Data over the socket!
|
||||
```
|
||||
|
||||
### write a string frame
|
||||
|
||||
The writeString method is the same as writeData, but sends text/string.
|
||||
|
||||
```swift
|
||||
socket.write(string: "Hi Server!") //example on how to write text over the socket!
|
||||
```
|
||||
|
||||
### write a ping frame
|
||||
|
||||
The writePing method is the same as write, but sends a ping control frame.
|
||||
|
||||
```swift
|
||||
socket.write(ping: Data()) //example on how to write a ping control frame over the socket!
|
||||
```
|
||||
|
||||
### write a pong frame
|
||||
|
||||
|
||||
the writePong method is the same as writePing, but sends a pong control frame.
|
||||
|
||||
```swift
|
||||
socket.write(pong: Data()) //example on how to write a pong control frame over the socket!
|
||||
```
|
||||
|
||||
Starscream will automatically respond to incoming `ping` control frames so you do not need to manually send `pong`s.
|
||||
|
||||
However if for some reason you need to control this process you can turn off the automatic `ping` response by disabling `respondToPingWithPong`.
|
||||
|
||||
```swift
|
||||
socket.respondToPingWithPong = false //Do not automaticaly respond to incoming pings with pongs.
|
||||
```
|
||||
|
||||
In most cases you will not need to do this.
|
||||
|
||||
### disconnect
|
||||
|
||||
The disconnect method does what you would expect and closes the socket.
|
||||
|
||||
```swift
|
||||
socket.disconnect()
|
||||
```
|
||||
|
||||
The disconnect method can also send a custom close code if desired.
|
||||
|
||||
```swift
|
||||
socket.disconnect(closeCode: CloseCode.normal.rawValue)
|
||||
```
|
||||
|
||||
### Custom Headers, Protocols and Timeout
|
||||
|
||||
You can override the default websocket headers, add your own custom ones and set a timeout:
|
||||
|
||||
```swift
|
||||
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
|
||||
request.timeoutInterval = 5 // Sets the timeout for the connection
|
||||
request.setValue("someother protocols", forHTTPHeaderField: "Sec-WebSocket-Protocol")
|
||||
request.setValue("14", forHTTPHeaderField: "Sec-WebSocket-Version")
|
||||
request.setValue("chat,superchat", forHTTPHeaderField: "Sec-WebSocket-Protocol")
|
||||
request.setValue("Everything is Awesome!", forHTTPHeaderField: "My-Awesome-Header")
|
||||
let socket = WebSocket(request: request)
|
||||
```
|
||||
|
||||
### SSL Pinning
|
||||
|
||||
SSL Pinning is also supported in Starscream.
|
||||
|
||||
|
||||
Allow Self-signed certificates:
|
||||
|
||||
```swift
|
||||
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
|
||||
let pinner = FoundationSecurity(allowSelfSigned: true) // don't validate SSL certificates
|
||||
let socket = WebSocket(request: request, certPinner: pinner)
|
||||
```
|
||||
|
||||
TODO: Update docs on how to load certificates and public keys into an app bundle, use the builtin pinner and TrustKit.
|
||||
|
||||
### Compression Extensions
|
||||
|
||||
Compression Extensions ([RFC 7692](https://tools.ietf.org/html/rfc7692)) is supported in Starscream. Compression is enabled by default, however compression will only be used if it is supported by the server as well. You may enable or disable compression via the `.enableCompression` property:
|
||||
|
||||
```swift
|
||||
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
|
||||
let compression = WSCompression()
|
||||
let socket = WebSocket(request: request, compressionHandler: compression)
|
||||
```
|
||||
|
||||
Compression should be disabled if your application is transmitting already-compressed, random, or other uncompressable data.
|
||||
|
||||
### Custom Queue
|
||||
|
||||
A custom queue can be specified when delegate methods are called. By default `DispatchQueue.main` is used, thus making all delegate methods calls run on the main thread. It is important to note that all WebSocket processing is done on a background thread, only the delegate method calls are changed when modifying the queue. The actual processing is always on a background thread and will not pause your app.
|
||||
|
||||
```swift
|
||||
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
//create a custom queue
|
||||
socket.callbackQueue = DispatchQueue(label: "com.vluxe.starscream.myapp")
|
||||
```
|
||||
|
||||
## Example Project
|
||||
|
||||
Check out the SimpleTest project in the examples directory to see how to setup a simple connection to a WebSocket server.
|
||||
|
||||
## Requirements
|
||||
|
||||
Starscream works with iOS 8/10.10 or above for CocoaPods/framework support. To use Starscream with a project targeting iOS 7, you must include all Swift files directly in your project.
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods
|
||||
|
||||
Check out [Get Started](http://cocoapods.org/) tab on [cocoapods.org](http://cocoapods.org/).
|
||||
|
||||
To use Starscream in your project add the following 'Podfile' to your project
|
||||
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios, '9.0'
|
||||
use_frameworks!
|
||||
|
||||
pod 'Starscream', '~> 4.0.0'
|
||||
|
||||
Then run:
|
||||
|
||||
pod install
|
||||
|
||||
### Carthage
|
||||
|
||||
Check out the [Carthage](https://github.com/Carthage/Carthage) docs on how to add a install. The `Starscream` framework is already setup with shared schemes.
|
||||
|
||||
[Carthage Install](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)
|
||||
|
||||
You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
|
||||
|
||||
```bash
|
||||
$ brew update
|
||||
$ brew install carthage
|
||||
```
|
||||
|
||||
To integrate Starscream into your Xcode project using Carthage, specify it in your `Cartfile`:
|
||||
|
||||
```
|
||||
github "daltoniam/Starscream" >= 4.0.0
|
||||
```
|
||||
|
||||
### Accio
|
||||
|
||||
Check out the [Accio](https://github.com/JamitLabs/Accio) docs on how to add a install.
|
||||
|
||||
Add the following to your Package.swift:
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/daltoniam/Starscream.git", .upToNextMajor(from: "4.0.0")),
|
||||
```
|
||||
|
||||
Next, add `Starscream` to your App targets dependencies like so:
|
||||
|
||||
```swift
|
||||
.target(
|
||||
name: "App",
|
||||
dependencies: [
|
||||
"Starscream",
|
||||
]
|
||||
),
|
||||
```
|
||||
|
||||
Then run `accio update`.
|
||||
|
||||
### Rogue
|
||||
|
||||
First see the [installation docs](https://github.com/acmacalister/Rogue) for how to install Rogue.
|
||||
|
||||
To install Starscream run the command below in the directory you created the rogue file.
|
||||
|
||||
```
|
||||
rogue add https://github.com/daltoniam/Starscream
|
||||
```
|
||||
|
||||
Next open the `libs` folder and add the `Starscream.xcodeproj` to your Xcode project. Once that is complete, in your "Build Phases" add the `Starscream.framework` to your "Link Binary with Libraries" phase. Make sure to add the `libs` folder to your `.gitignore` file.
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.
|
||||
|
||||
Once you have your Swift package set up, adding Starscream as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/daltoniam/Starscream.git", majorVersion: 4)
|
||||
]
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
Simply grab the framework (either via git submodule or another package manager).
|
||||
|
||||
Add the `Starscream.xcodeproj` to your Xcode project. Once that is complete, in your "Build Phases" add the `Starscream.framework` to your "Link Binary with Libraries" phase.
|
||||
|
||||
### Add Copy Frameworks Phase
|
||||
|
||||
If you are running this in an OSX app or on a physical iOS device you will need to make sure you add the `Starscream.framework` to be included in your app bundle. To do this, in Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. In the tab bar at the top of that window, open the "Build Phases" panel. Expand the "Link Binary with Libraries" group, and add `Starscream.framework`. Click on the + button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add `Starscream.framework` respectively.
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] Proxy support
|
||||
|
||||
## License
|
||||
|
||||
Starscream is licensed under the Apache v2 License.
|
||||
|
||||
## Contact
|
||||
|
||||
### Dalton Cherry
|
||||
* https://github.com/daltoniam
|
||||
* http://twitter.com/daltoniam
|
||||
* http://daltoniam.com
|
||||
|
||||
### Austin Cherry ###
|
||||
* https://github.com/acmacalister
|
||||
* http://twitter.com/acmacalister
|
||||
* http://austincherry.me
|
@ -1,29 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Compression.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 2/4/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public protocol CompressionHandler {
|
||||
func load(headers: [String: String])
|
||||
func decompress(data: Data, isFinal: Bool) -> Data?
|
||||
func compress(data: Data) -> Data?
|
||||
}
|
@ -1,247 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WSCompression.swift
|
||||
//
|
||||
// Created by Joseph Ross on 7/16/14.
|
||||
// Copyright © 2017 Joseph Ross & Vluxe. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Compression implementation is implemented in conformance with RFC 7692 Compression Extensions
|
||||
// for WebSocket: https://tools.ietf.org/html/rfc7692
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import zlib
|
||||
|
||||
public class WSCompression: CompressionHandler {
|
||||
let headerWSExtensionName = "Sec-WebSocket-Extensions"
|
||||
var decompressor: Decompressor?
|
||||
var compressor: Compressor?
|
||||
var decompressorTakeOver = false
|
||||
var compressorTakeOver = false
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func load(headers: [String: String]) {
|
||||
guard let extensionHeader = headers[headerWSExtensionName] else { return }
|
||||
decompressorTakeOver = false
|
||||
compressorTakeOver = false
|
||||
|
||||
let parts = extensionHeader.components(separatedBy: ";")
|
||||
for p in parts {
|
||||
let part = p.trimmingCharacters(in: .whitespaces)
|
||||
if part.hasPrefix("server_max_window_bits=") {
|
||||
let valString = part.components(separatedBy: "=")[1]
|
||||
if let val = Int(valString.trimmingCharacters(in: .whitespaces)) {
|
||||
decompressor = Decompressor(windowBits: val)
|
||||
}
|
||||
} else if part.hasPrefix("client_max_window_bits=") {
|
||||
let valString = part.components(separatedBy: "=")[1]
|
||||
if let val = Int(valString.trimmingCharacters(in: .whitespaces)) {
|
||||
compressor = Compressor(windowBits: val)
|
||||
}
|
||||
} else if part == "client_no_context_takeover" {
|
||||
compressorTakeOver = true
|
||||
} else if part == "server_no_context_takeover" {
|
||||
decompressorTakeOver = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
guard let decompressor = decompressor else { return nil }
|
||||
do {
|
||||
let decompressedData = try decompressor.decompress(data, finish: isFinal)
|
||||
if decompressorTakeOver {
|
||||
try decompressor.reset()
|
||||
}
|
||||
return decompressedData
|
||||
} catch {
|
||||
//do nothing with the error for now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func compress(data: Data) -> Data? {
|
||||
guard let compressor = compressor else { return nil }
|
||||
do {
|
||||
let compressedData = try compressor.compress(data)
|
||||
if compressorTakeOver {
|
||||
try compressor.reset()
|
||||
}
|
||||
return compressedData
|
||||
} catch {
|
||||
//do nothing with the error for now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Decompressor {
|
||||
private var strm = z_stream()
|
||||
private var buffer = [UInt8](repeating: 0, count: 0x2000)
|
||||
private var inflateInitialized = false
|
||||
private let windowBits: Int
|
||||
|
||||
init?(windowBits: Int) {
|
||||
self.windowBits = windowBits
|
||||
guard initInflate() else { return nil }
|
||||
}
|
||||
|
||||
private func initInflate() -> Bool {
|
||||
if Z_OK == inflateInit2_(&strm, -CInt(windowBits),
|
||||
ZLIB_VERSION, CInt(MemoryLayout<z_stream>.size))
|
||||
{
|
||||
inflateInitialized = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
teardownInflate()
|
||||
guard initInflate() else { throw WSError(type: .compressionError, message: "Error for decompressor on reset", code: 0) }
|
||||
}
|
||||
|
||||
func decompress(_ data: Data, finish: Bool) throws -> Data {
|
||||
return try data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
return try decompress(bytes: bytes, count: data.count, finish: finish)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress(bytes: UnsafePointer<UInt8>, count: Int, finish: Bool) throws -> Data {
|
||||
var decompressed = Data()
|
||||
try decompress(bytes: bytes, count: count, out: &decompressed)
|
||||
|
||||
if finish {
|
||||
let tail:[UInt8] = [0x00, 0x00, 0xFF, 0xFF]
|
||||
try decompress(bytes: tail, count: tail.count, out: &decompressed)
|
||||
}
|
||||
|
||||
return decompressed
|
||||
}
|
||||
|
||||
private func decompress(bytes: UnsafePointer<UInt8>, count: Int, out: inout Data) throws {
|
||||
var res: CInt = 0
|
||||
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: bytes)
|
||||
strm.avail_in = CUnsignedInt(count)
|
||||
|
||||
repeat {
|
||||
buffer.withUnsafeMutableBytes { (bufferPtr) in
|
||||
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
|
||||
strm.avail_out = CUnsignedInt(bufferPtr.count)
|
||||
|
||||
res = inflate(&strm, 0)
|
||||
}
|
||||
|
||||
let byteCount = buffer.count - Int(strm.avail_out)
|
||||
out.append(buffer, count: byteCount)
|
||||
} while res == Z_OK && strm.avail_out == 0
|
||||
|
||||
guard (res == Z_OK && strm.avail_out > 0)
|
||||
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
|
||||
else {
|
||||
throw WSError(type: .compressionError, message: "Error on decompressing", code: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func teardownInflate() {
|
||||
if inflateInitialized, Z_OK == inflateEnd(&strm) {
|
||||
inflateInitialized = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
teardownInflate()
|
||||
}
|
||||
}
|
||||
|
||||
class Compressor {
|
||||
private var strm = z_stream()
|
||||
private var buffer = [UInt8](repeating: 0, count: 0x2000)
|
||||
private var deflateInitialized = false
|
||||
private let windowBits: Int
|
||||
|
||||
init?(windowBits: Int) {
|
||||
self.windowBits = windowBits
|
||||
guard initDeflate() else { return nil }
|
||||
}
|
||||
|
||||
private func initDeflate() -> Bool {
|
||||
if Z_OK == deflateInit2_(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
|
||||
-CInt(windowBits), 8, Z_DEFAULT_STRATEGY,
|
||||
ZLIB_VERSION, CInt(MemoryLayout<z_stream>.size))
|
||||
{
|
||||
deflateInitialized = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
teardownDeflate()
|
||||
guard initDeflate() else { throw WSError(type: .compressionError, message: "Error for compressor on reset", code: 0) }
|
||||
}
|
||||
|
||||
func compress(_ data: Data) throws -> Data {
|
||||
var compressed = Data()
|
||||
var res: CInt = 0
|
||||
data.withUnsafeBytes { (ptr:UnsafePointer<UInt8>) -> Void in
|
||||
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: ptr)
|
||||
strm.avail_in = CUnsignedInt(data.count)
|
||||
|
||||
repeat {
|
||||
buffer.withUnsafeMutableBytes { (bufferPtr) in
|
||||
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
|
||||
strm.avail_out = CUnsignedInt(bufferPtr.count)
|
||||
|
||||
res = deflate(&strm, Z_SYNC_FLUSH)
|
||||
}
|
||||
|
||||
let byteCount = buffer.count - Int(strm.avail_out)
|
||||
compressed.append(buffer, count: byteCount)
|
||||
}
|
||||
while res == Z_OK && strm.avail_out == 0
|
||||
|
||||
}
|
||||
|
||||
guard res == Z_OK && strm.avail_out > 0
|
||||
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
|
||||
else {
|
||||
throw WSError(type: .compressionError, message: "Error on compressing", code: 0)
|
||||
}
|
||||
|
||||
compressed.removeLast(4)
|
||||
return compressed
|
||||
}
|
||||
|
||||
private func teardownDeflate() {
|
||||
if deflateInitialized, Z_OK == deflateEnd(&strm) {
|
||||
deflateInitialized = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
teardownDeflate()
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Data+Extensions.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 3/27/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// Fix for deprecation warnings
|
||||
//
|
||||
// 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
|
||||
|
||||
internal extension Data {
|
||||
struct ByteError: Swift.Error {}
|
||||
|
||||
#if swift(>=5.0)
|
||||
func withUnsafeBytes<ResultType, ContentType>(_ completion: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
|
||||
return try withUnsafeBytes {
|
||||
if let baseAddress = $0.baseAddress, $0.count > 0 {
|
||||
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
|
||||
} else {
|
||||
throw ByteError()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.0)
|
||||
mutating func withUnsafeMutableBytes<ResultType, ContentType>(_ completion: (UnsafeMutablePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
|
||||
return try withUnsafeMutableBytes {
|
||||
if let baseAddress = $0.baseAddress, $0.count > 0 {
|
||||
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
|
||||
} else {
|
||||
throw ByteError()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// Engine.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 6/15/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol EngineDelegate: class {
|
||||
func didReceive(event: WebSocketEvent)
|
||||
}
|
||||
|
||||
public protocol Engine {
|
||||
func register(delegate: EngineDelegate)
|
||||
func start(request: URLRequest)
|
||||
func stop(closeCode: UInt16)
|
||||
func forceStop()
|
||||
func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?)
|
||||
func write(string: String, completion: (() -> ())?)
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
//
|
||||
// NativeEngine.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 6/15/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public class NativeEngine: NSObject, Engine, URLSessionDataDelegate, URLSessionWebSocketDelegate {
|
||||
private var task: URLSessionWebSocketTask?
|
||||
weak var delegate: EngineDelegate?
|
||||
|
||||
public func register(delegate: EngineDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func start(request: URLRequest) {
|
||||
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
||||
task = session.webSocketTask(with: request)
|
||||
doRead()
|
||||
task?.resume()
|
||||
}
|
||||
|
||||
public func stop(closeCode: UInt16) {
|
||||
let closeCode = URLSessionWebSocketTask.CloseCode(rawValue: Int(closeCode)) ?? .normalClosure
|
||||
task?.cancel(with: closeCode, reason: nil)
|
||||
}
|
||||
|
||||
public func forceStop() {
|
||||
stop(closeCode: UInt16(URLSessionWebSocketTask.CloseCode.abnormalClosure.rawValue))
|
||||
}
|
||||
|
||||
public func write(string: String, completion: (() -> ())?) {
|
||||
task?.send(.string(string), completionHandler: { (error) in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
|
||||
switch opcode {
|
||||
case .binaryFrame:
|
||||
task?.send(.data(data), completionHandler: { (error) in
|
||||
completion?()
|
||||
})
|
||||
case .textFrame:
|
||||
let text = String(data: data, encoding: .utf8)!
|
||||
write(string: text, completion: completion)
|
||||
case .ping:
|
||||
task?.sendPing(pongReceiveHandler: { (error) in
|
||||
completion?()
|
||||
})
|
||||
default:
|
||||
break //unsupported
|
||||
}
|
||||
}
|
||||
|
||||
private func doRead() {
|
||||
task?.receive { [weak self] (result) in
|
||||
switch result {
|
||||
case .success(let message):
|
||||
switch message {
|
||||
case .string(let string):
|
||||
self?.broadcast(event: .text(string))
|
||||
case .data(let data):
|
||||
self?.broadcast(event: .binary(data))
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
break
|
||||
case .failure(let error):
|
||||
self?.broadcast(event: .error(error))
|
||||
}
|
||||
self?.doRead()
|
||||
}
|
||||
}
|
||||
|
||||
private func broadcast(event: WebSocketEvent) {
|
||||
delegate?.didReceive(event: event)
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
|
||||
let p = `protocol` ?? ""
|
||||
broadcast(event: .connected([HTTPWSHeader.protocolName: p]))
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
|
||||
var r = ""
|
||||
if let d = reason {
|
||||
r = String(data: d, encoding: .utf8) ?? ""
|
||||
}
|
||||
broadcast(event: .disconnected(r, UInt16(closeCode.rawValue)))
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
//
|
||||
// WSEngine.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 6/15/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class WSEngine: Engine, TransportEventClient, FramerEventClient,
|
||||
FrameCollectorDelegate, HTTPHandlerDelegate {
|
||||
private let transport: Transport
|
||||
private let framer: Framer
|
||||
private let httpHandler: HTTPHandler
|
||||
private let compressionHandler: CompressionHandler?
|
||||
private let certPinner: CertificatePinning?
|
||||
private let headerChecker: HeaderValidator
|
||||
private var request: URLRequest!
|
||||
|
||||
private let frameHandler = FrameCollector()
|
||||
private var didUpgrade = false
|
||||
private var secKeyValue = ""
|
||||
private let writeQueue = DispatchQueue(label: "com.vluxe.starscream.writequeue")
|
||||
private let mutex = DispatchSemaphore(value: 1)
|
||||
private var canSend = false
|
||||
|
||||
weak var delegate: EngineDelegate?
|
||||
public var respondToPingWithPong: Bool = true
|
||||
|
||||
public init(transport: Transport,
|
||||
certPinner: CertificatePinning? = nil,
|
||||
headerValidator: HeaderValidator = FoundationSecurity(),
|
||||
httpHandler: HTTPHandler = FoundationHTTPHandler(),
|
||||
framer: Framer = WSFramer(),
|
||||
compressionHandler: CompressionHandler? = nil) {
|
||||
self.transport = transport
|
||||
self.framer = framer
|
||||
self.httpHandler = httpHandler
|
||||
self.certPinner = certPinner
|
||||
self.headerChecker = headerValidator
|
||||
self.compressionHandler = compressionHandler
|
||||
framer.updateCompression(supports: compressionHandler != nil)
|
||||
frameHandler.delegate = self
|
||||
}
|
||||
|
||||
public func register(delegate: EngineDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func start(request: URLRequest) {
|
||||
mutex.wait()
|
||||
let isConnected = canSend
|
||||
mutex.signal()
|
||||
if isConnected {
|
||||
return
|
||||
}
|
||||
|
||||
self.request = request
|
||||
transport.register(delegate: self)
|
||||
framer.register(delegate: self)
|
||||
httpHandler.register(delegate: self)
|
||||
frameHandler.delegate = self
|
||||
guard let url = request.url else {
|
||||
return
|
||||
}
|
||||
transport.connect(url: url, timeout: request.timeoutInterval, certificatePinning: certPinner)
|
||||
}
|
||||
|
||||
public func stop(closeCode: UInt16 = CloseCode.normal.rawValue) {
|
||||
let capacity = MemoryLayout<UInt16>.size
|
||||
var pointer = [UInt8](repeating: 0, count: capacity)
|
||||
writeUint16(&pointer, offset: 0, value: closeCode)
|
||||
let payload = Data(bytes: pointer, count: MemoryLayout<UInt16>.size)
|
||||
write(data: payload, opcode: .connectionClose, completion: { [weak self] in
|
||||
self?.reset()
|
||||
self?.forceStop()
|
||||
})
|
||||
}
|
||||
|
||||
public func forceStop() {
|
||||
transport.disconnect()
|
||||
}
|
||||
|
||||
public func write(string: String, completion: (() -> ())?) {
|
||||
let data = string.data(using: .utf8)!
|
||||
write(data: data, opcode: .textFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
|
||||
writeQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.mutex.wait()
|
||||
let canWrite = s.canSend
|
||||
s.mutex.signal()
|
||||
if !canWrite {
|
||||
return
|
||||
}
|
||||
|
||||
var isCompressed = false
|
||||
var sendData = data
|
||||
if let compressedData = s.compressionHandler?.compress(data: data) {
|
||||
sendData = compressedData
|
||||
isCompressed = true
|
||||
}
|
||||
|
||||
let frameData = s.framer.createWriteFrame(opcode: opcode, payload: sendData, isCompressed: isCompressed)
|
||||
s.transport.write(data: frameData, completion: {_ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TransportEventClient
|
||||
|
||||
public func connectionChanged(state: ConnectionState) {
|
||||
switch state {
|
||||
case .connected:
|
||||
secKeyValue = HTTPWSHeader.generateWebSocketKey()
|
||||
let wsReq = HTTPWSHeader.createUpgrade(request: request, supportsCompression: framer.supportsCompression(), secKeyValue: secKeyValue)
|
||||
let data = httpHandler.convert(request: wsReq)
|
||||
transport.write(data: data, completion: {_ in })
|
||||
case .waiting:
|
||||
break
|
||||
case .failed(let error):
|
||||
handleError(error)
|
||||
case .viability(let isViable):
|
||||
broadcast(event: .viabilityChanged(isViable))
|
||||
case .shouldReconnect(let status):
|
||||
broadcast(event: .reconnectSuggested(status))
|
||||
case .receive(let data):
|
||||
if didUpgrade {
|
||||
framer.add(data: data)
|
||||
} else {
|
||||
let offset = httpHandler.parse(data: data)
|
||||
if offset > 0 {
|
||||
let extraData = data.subdata(in: offset..<data.endIndex)
|
||||
framer.add(data: extraData)
|
||||
}
|
||||
}
|
||||
case .cancelled:
|
||||
broadcast(event: .cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - HTTPHandlerDelegate
|
||||
|
||||
public func didReceiveHTTP(event: HTTPEvent) {
|
||||
switch event {
|
||||
case .success(let headers):
|
||||
if let error = headerChecker.validate(headers: headers, key: secKeyValue) {
|
||||
handleError(error)
|
||||
return
|
||||
}
|
||||
mutex.wait()
|
||||
didUpgrade = true
|
||||
canSend = true
|
||||
mutex.signal()
|
||||
compressionHandler?.load(headers: headers)
|
||||
if let url = request.url {
|
||||
HTTPCookie.cookies(withResponseHeaderFields: headers, for: url).forEach {
|
||||
HTTPCookieStorage.shared.setCookie($0)
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(event: .connected(headers))
|
||||
case .failure(let error):
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FramerEventClient
|
||||
|
||||
public func frameProcessed(event: FrameEvent) {
|
||||
switch event {
|
||||
case .frame(let frame):
|
||||
frameHandler.add(frame: frame)
|
||||
case .error(let error):
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FrameCollectorDelegate
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
return compressionHandler?.decompress(data: data, isFinal: isFinal)
|
||||
}
|
||||
|
||||
public func didForm(event: FrameCollector.Event) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
broadcast(event: .text(string))
|
||||
case .binary(let data):
|
||||
broadcast(event: .binary(data))
|
||||
case .pong(let data):
|
||||
broadcast(event: .pong(data))
|
||||
case .ping(let data):
|
||||
broadcast(event: .ping(data))
|
||||
if respondToPingWithPong {
|
||||
write(data: data ?? Data(), opcode: .pong, completion: nil)
|
||||
}
|
||||
case .closed(let reason, let code):
|
||||
broadcast(event: .disconnected(reason, code))
|
||||
stop(closeCode: code)
|
||||
case .error(let error):
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func broadcast(event: WebSocketEvent) {
|
||||
delegate?.didReceive(event: event)
|
||||
}
|
||||
|
||||
//This call can be coming from a lot of different queues/threads.
|
||||
//be aware of that when modifying shared variables
|
||||
private func handleError(_ error: Error?) {
|
||||
if let wsError = error as? WSError {
|
||||
stop(closeCode: wsError.code)
|
||||
} else {
|
||||
stop()
|
||||
}
|
||||
|
||||
delegate?.didReceive(event: .error(error))
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
mutex.wait()
|
||||
canSend = false
|
||||
didUpgrade = false
|
||||
mutex.signal()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationHTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/25/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
#if os(watchOS)
|
||||
public typealias FoundationHTTPHandler = StringHTTPHandler
|
||||
#else
|
||||
public class FoundationHTTPHandler: HTTPHandler {
|
||||
|
||||
var buffer = Data()
|
||||
weak var delegate: HTTPHandlerDelegate?
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func convert(request: URLRequest) -> Data {
|
||||
let msg = CFHTTPMessageCreateRequest(kCFAllocatorDefault, request.httpMethod! as CFString,
|
||||
request.url! as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
if let headers = request.allHTTPHeaderFields {
|
||||
for (aKey, aValue) in headers {
|
||||
CFHTTPMessageSetHeaderFieldValue(msg, aKey as CFString, aValue as CFString)
|
||||
}
|
||||
}
|
||||
if let body = request.httpBody {
|
||||
CFHTTPMessageSetBody(msg, body as CFData)
|
||||
}
|
||||
guard let data = CFHTTPMessageCopySerializedMessage(msg) else {
|
||||
return Data()
|
||||
}
|
||||
return data.takeRetainedValue() as Data
|
||||
}
|
||||
|
||||
public func parse(data: Data) -> Int {
|
||||
let offset = findEndOfHTTP(data: data)
|
||||
if offset > 0 {
|
||||
buffer.append(data.subdata(in: 0..<offset))
|
||||
} else {
|
||||
buffer.append(data)
|
||||
}
|
||||
if parseContent(data: buffer) {
|
||||
buffer = Data()
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
//returns true when the buffer should be cleared
|
||||
func parseContent(data: Data) -> Bool {
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue()
|
||||
if !CFHTTPMessageAppendBytes(response, pointer, data.count) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if !CFHTTPMessageIsHeaderComplete(response) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
|
||||
let code = CFHTTPMessageGetResponseStatusCode(response)
|
||||
if code != HTTPWSHeader.switchProtocolCode {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.notAnUpgrade(code)))
|
||||
return true
|
||||
}
|
||||
|
||||
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
|
||||
let nsHeaders = cfHeaders.takeRetainedValue() as NSDictionary
|
||||
var headers = [String: String]()
|
||||
for (key, value) in nsHeaders {
|
||||
if let key = key as? String, let value = value as? String {
|
||||
headers[key] = value
|
||||
}
|
||||
}
|
||||
delegate?.didReceiveHTTP(event: .success(headers))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
|
||||
public func register(delegate: HTTPHandlerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
private func findEndOfHTTP(data: Data) -> Int {
|
||||
let endBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
var k = 0
|
||||
for i in 0..<data.count {
|
||||
if pointer[i] == endBytes[k] {
|
||||
k += 1
|
||||
if k == 4 {
|
||||
return i + 1
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,99 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationHTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 4/2/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public class FoundationHTTPServerHandler: HTTPServerHandler {
|
||||
var buffer = Data()
|
||||
weak var delegate: HTTPServerDelegate?
|
||||
let getVerb: NSString = "GET"
|
||||
|
||||
public func register(delegate: HTTPServerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func createResponse(headers: [String: String]) -> Data {
|
||||
#if os(watchOS)
|
||||
//TODO: build response header
|
||||
return Data()
|
||||
#else
|
||||
let response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, HTTPWSHeader.switchProtocolCode,
|
||||
nil, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
|
||||
//TODO: add other values to make a proper response here...
|
||||
//TODO: also sec key thing (Sec-WebSocket-Key)
|
||||
for (key, value) in headers {
|
||||
CFHTTPMessageSetHeaderFieldValue(response, key as CFString, value as CFString)
|
||||
}
|
||||
guard let cfData = CFHTTPMessageCopySerializedMessage(response)?.takeRetainedValue() else {
|
||||
return Data()
|
||||
}
|
||||
return cfData as Data
|
||||
#endif
|
||||
}
|
||||
|
||||
public func parse(data: Data) {
|
||||
buffer.append(data)
|
||||
if parseContent(data: buffer) {
|
||||
buffer = Data()
|
||||
}
|
||||
}
|
||||
|
||||
//returns true when the buffer should be cleared
|
||||
func parseContent(data: Data) -> Bool {
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
#if os(watchOS)
|
||||
//TODO: parse data
|
||||
return false
|
||||
#else
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true).takeRetainedValue()
|
||||
if !CFHTTPMessageAppendBytes(response, pointer, data.count) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if !CFHTTPMessageIsHeaderComplete(response) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if let method = CFHTTPMessageCopyRequestMethod(response)?.takeRetainedValue() {
|
||||
if (method as NSString) != getVerb {
|
||||
delegate?.didReceive(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
|
||||
let nsHeaders = cfHeaders.takeRetainedValue() as NSDictionary
|
||||
var headers = [String: String]()
|
||||
for (key, value) in nsHeaders {
|
||||
if let key = key as? String, let value = value as? String {
|
||||
headers[key] = value
|
||||
}
|
||||
}
|
||||
delegate?.didReceive(event: .success(headers))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceive(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FrameCollector.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/24/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public protocol FrameCollectorDelegate: class {
|
||||
func didForm(event: FrameCollector.Event)
|
||||
func decompress(data: Data, isFinal: Bool) -> Data?
|
||||
}
|
||||
|
||||
public class FrameCollector {
|
||||
public enum Event {
|
||||
case text(String)
|
||||
case binary(Data)
|
||||
case pong(Data?)
|
||||
case ping(Data?)
|
||||
case error(Error)
|
||||
case closed(String, UInt16)
|
||||
}
|
||||
weak var delegate: FrameCollectorDelegate?
|
||||
var buffer = Data()
|
||||
var frameCount = 0
|
||||
var isText = false //was the first frame a text frame or a binary frame?
|
||||
var needsDecompression = false
|
||||
|
||||
public func add(frame: Frame) {
|
||||
//check single frame action and out of order frames
|
||||
if frame.opcode == .connectionClose {
|
||||
var code = frame.closeCode
|
||||
var reason = "connection closed by server"
|
||||
if let customCloseReason = String(data: frame.payload, encoding: .utf8) {
|
||||
reason = customCloseReason
|
||||
} else {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
}
|
||||
delegate?.didForm(event: .closed(reason, code))
|
||||
return
|
||||
} else if frame.opcode == .pong {
|
||||
delegate?.didForm(event: .pong(frame.payload))
|
||||
return
|
||||
} else if frame.opcode == .ping {
|
||||
delegate?.didForm(event: .ping(frame.payload))
|
||||
return
|
||||
} else if frame.opcode == .continueFrame && frameCount == 0 {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "first frame can't be a continue frame", code: errCode)))
|
||||
reset()
|
||||
return
|
||||
} else if frameCount > 0 && frame.opcode != .continueFrame {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "second and beyond of fragment message must be a continue frame", code: errCode)))
|
||||
reset()
|
||||
return
|
||||
}
|
||||
if frameCount == 0 {
|
||||
isText = frame.opcode == .textFrame
|
||||
needsDecompression = frame.needsDecompression
|
||||
}
|
||||
|
||||
let payload: Data
|
||||
if needsDecompression {
|
||||
payload = delegate?.decompress(data: frame.payload, isFinal: frame.isFin) ?? frame.payload
|
||||
} else {
|
||||
payload = frame.payload
|
||||
}
|
||||
buffer.append(payload)
|
||||
frameCount += 1
|
||||
|
||||
if frame.isFin {
|
||||
if isText {
|
||||
if let string = String(data: buffer, encoding: .utf8) {
|
||||
delegate?.didForm(event: .text(string))
|
||||
} else {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "not valid UTF-8 data", code: errCode)))
|
||||
}
|
||||
} else {
|
||||
delegate?.didForm(event: .binary(buffer))
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
buffer = Data()
|
||||
frameCount = 0
|
||||
}
|
||||
}
|
@ -1,365 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Framer.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
let FinMask: UInt8 = 0x80
|
||||
let OpCodeMask: UInt8 = 0x0F
|
||||
let RSVMask: UInt8 = 0x70
|
||||
let RSV1Mask: UInt8 = 0x40
|
||||
let MaskMask: UInt8 = 0x80
|
||||
let PayloadLenMask: UInt8 = 0x7F
|
||||
let MaxFrameSize: Int = 32
|
||||
|
||||
// Standard WebSocket close codes
|
||||
public enum CloseCode: UInt16 {
|
||||
case normal = 1000
|
||||
case goingAway = 1001
|
||||
case protocolError = 1002
|
||||
case protocolUnhandledType = 1003
|
||||
// 1004 reserved.
|
||||
case noStatusReceived = 1005
|
||||
//1006 reserved.
|
||||
case encoding = 1007
|
||||
case policyViolated = 1008
|
||||
case messageTooBig = 1009
|
||||
}
|
||||
|
||||
public enum FrameOpCode: UInt8 {
|
||||
case continueFrame = 0x0
|
||||
case textFrame = 0x1
|
||||
case binaryFrame = 0x2
|
||||
// 3-7 are reserved.
|
||||
case connectionClose = 0x8
|
||||
case ping = 0x9
|
||||
case pong = 0xA
|
||||
// B-F reserved.
|
||||
case unknown = 100
|
||||
}
|
||||
|
||||
public struct Frame {
|
||||
let isFin: Bool
|
||||
let needsDecompression: Bool
|
||||
let isMasked: Bool
|
||||
let opcode: FrameOpCode
|
||||
let payloadLength: UInt64
|
||||
let payload: Data
|
||||
let closeCode: UInt16 //only used by connectionClose opcode
|
||||
}
|
||||
|
||||
public enum FrameEvent {
|
||||
case frame(Frame)
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
public protocol FramerEventClient: class {
|
||||
func frameProcessed(event: FrameEvent)
|
||||
}
|
||||
|
||||
public protocol Framer {
|
||||
func add(data: Data)
|
||||
func register(delegate: FramerEventClient)
|
||||
func createWriteFrame(opcode: FrameOpCode, payload: Data, isCompressed: Bool) -> Data
|
||||
func updateCompression(supports: Bool)
|
||||
func supportsCompression() -> Bool
|
||||
}
|
||||
|
||||
public class WSFramer: Framer {
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.wsframer", attributes: [])
|
||||
private weak var delegate: FramerEventClient?
|
||||
private var buffer = Data()
|
||||
public var compressionEnabled = false
|
||||
private let isServer: Bool
|
||||
|
||||
public init(isServer: Bool = false) {
|
||||
self.isServer = isServer
|
||||
}
|
||||
|
||||
public func updateCompression(supports: Bool) {
|
||||
compressionEnabled = supports
|
||||
}
|
||||
|
||||
public func supportsCompression() -> Bool {
|
||||
return compressionEnabled
|
||||
}
|
||||
|
||||
enum ProcessEvent {
|
||||
case needsMoreData
|
||||
case processedFrame(Frame, Int)
|
||||
case failed(Error)
|
||||
}
|
||||
|
||||
public func add(data: Data) {
|
||||
queue.async { [weak self] in
|
||||
self?.buffer.append(data)
|
||||
while(true) {
|
||||
let event = self?.process() ?? .needsMoreData
|
||||
switch event {
|
||||
case .needsMoreData:
|
||||
return
|
||||
case .processedFrame(let frame, let split):
|
||||
guard let s = self else { return }
|
||||
s.delegate?.frameProcessed(event: .frame(frame))
|
||||
if split >= s.buffer.count {
|
||||
s.buffer = Data()
|
||||
return
|
||||
}
|
||||
s.buffer = s.buffer.advanced(by: split)
|
||||
case .failed(let error):
|
||||
self?.delegate?.frameProcessed(event: .error(error))
|
||||
self?.buffer = Data()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func register(delegate: FramerEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
private func process() -> ProcessEvent {
|
||||
if buffer.count < 2 {
|
||||
return .needsMoreData
|
||||
}
|
||||
var pointer = [UInt8]()
|
||||
buffer.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
|
||||
let isFin = (FinMask & pointer[0])
|
||||
let opcodeRawValue = (OpCodeMask & pointer[0])
|
||||
let opcode = FrameOpCode(rawValue: opcodeRawValue) ?? .unknown
|
||||
let isMasked = (MaskMask & pointer[1])
|
||||
let payloadLen = (PayloadLenMask & pointer[1])
|
||||
let RSV1 = (RSVMask & pointer[0])
|
||||
var needsDecompression = false
|
||||
|
||||
if compressionEnabled && opcode != .continueFrame {
|
||||
needsDecompression = (RSV1Mask & pointer[0]) > 0
|
||||
}
|
||||
if !isServer && (isMasked > 0 || RSV1 > 0) && opcode != .pong && !needsDecompression {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
return .failed(WSError(type: .protocolError, message: "masked and rsv data is not currently supported", code: errCode))
|
||||
}
|
||||
let isControlFrame = (opcode == .connectionClose || opcode == .ping)
|
||||
if !isControlFrame && (opcode != .binaryFrame && opcode != .continueFrame &&
|
||||
opcode != .textFrame && opcode != .pong) {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
return .failed(WSError(type: .protocolError, message: "unknown opcode: \(opcodeRawValue)", code: errCode))
|
||||
}
|
||||
if isControlFrame && isFin == 0 {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
return .failed(WSError(type: .protocolError, message: "control frames can't be fragmented", code: errCode))
|
||||
}
|
||||
|
||||
var offset = 2
|
||||
|
||||
if isControlFrame && payloadLen > 125 {
|
||||
return .failed(WSError(type: .protocolError, message: "payload length is longer than allowed for a control frame", code: CloseCode.protocolError.rawValue))
|
||||
}
|
||||
|
||||
var dataLength = UInt64(payloadLen)
|
||||
var closeCode = CloseCode.normal.rawValue
|
||||
if opcode == .connectionClose {
|
||||
if payloadLen == 1 {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
dataLength = 0
|
||||
} else if payloadLen > 1 {
|
||||
if pointer.count < 4 {
|
||||
return .needsMoreData
|
||||
}
|
||||
let size = MemoryLayout<UInt16>.size
|
||||
closeCode = pointer.readUint16(offset: offset)
|
||||
offset += size
|
||||
dataLength -= UInt64(size)
|
||||
if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1013 && closeCode < 3000) {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if payloadLen == 127 {
|
||||
let size = MemoryLayout<UInt64>.size
|
||||
if size + offset > pointer.count {
|
||||
return .needsMoreData
|
||||
}
|
||||
dataLength = pointer.readUint64(offset: offset)
|
||||
offset += size
|
||||
} else if payloadLen == 126 {
|
||||
let size = MemoryLayout<UInt16>.size
|
||||
if size + offset > pointer.count {
|
||||
return .needsMoreData
|
||||
}
|
||||
dataLength = UInt64(pointer.readUint16(offset: offset))
|
||||
offset += size
|
||||
}
|
||||
|
||||
let maskStart = offset
|
||||
if isServer {
|
||||
offset += MemoryLayout<UInt32>.size
|
||||
}
|
||||
|
||||
if dataLength > (pointer.count - offset) {
|
||||
return .needsMoreData
|
||||
}
|
||||
|
||||
//I don't like this cast, but Data's count returns an Int.
|
||||
//Might be a problem with huge payloads. Need to revisit.
|
||||
let readDataLength = Int(dataLength)
|
||||
|
||||
let payload: Data
|
||||
if readDataLength == 0 {
|
||||
payload = Data()
|
||||
} else {
|
||||
if isServer {
|
||||
payload = pointer.unmaskData(maskStart: maskStart, offset: offset, length: readDataLength)
|
||||
} else {
|
||||
let end = offset + readDataLength
|
||||
payload = Data(pointer[offset..<end])
|
||||
}
|
||||
}
|
||||
offset += readDataLength
|
||||
|
||||
let frame = Frame(isFin: isFin > 0, needsDecompression: needsDecompression, isMasked: isMasked > 0, opcode: opcode, payloadLength: dataLength, payload: payload, closeCode: closeCode)
|
||||
return .processedFrame(frame, offset)
|
||||
}
|
||||
|
||||
public func createWriteFrame(opcode: FrameOpCode, payload: Data, isCompressed: Bool) -> Data {
|
||||
let payloadLength = payload.count
|
||||
|
||||
let capacity = payloadLength + MaxFrameSize
|
||||
var pointer = [UInt8](repeating: 0, count: capacity)
|
||||
|
||||
//set the framing info
|
||||
pointer[0] = FinMask | opcode.rawValue
|
||||
if isCompressed {
|
||||
pointer[0] |= RSV1Mask
|
||||
}
|
||||
|
||||
var offset = 2 //skip pass the framing info
|
||||
if payloadLength < 126 {
|
||||
pointer[1] = UInt8(payloadLength)
|
||||
} else if payloadLength <= Int(UInt16.max) {
|
||||
pointer[1] = 126
|
||||
writeUint16(&pointer, offset: offset, value: UInt16(payloadLength))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
} else {
|
||||
pointer[1] = 127
|
||||
writeUint64(&pointer, offset: offset, value: UInt64(payloadLength))
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
}
|
||||
|
||||
//clients are required to mask the payload data, but server don't according to the RFC
|
||||
if !isServer {
|
||||
pointer[1] |= MaskMask
|
||||
|
||||
//write the random mask key in
|
||||
let maskKey: UInt32 = UInt32.random(in: 0...UInt32.max)
|
||||
|
||||
writeUint32(&pointer, offset: offset, value: maskKey)
|
||||
let maskStart = offset
|
||||
offset += MemoryLayout<UInt32>.size
|
||||
|
||||
//now write the payload data in
|
||||
for i in 0..<payloadLength {
|
||||
pointer[offset] = payload[i] ^ pointer[maskStart + (i % MemoryLayout<UInt32>.size)]
|
||||
offset += 1
|
||||
}
|
||||
} else {
|
||||
for i in 0..<payloadLength {
|
||||
pointer[offset] = payload[i]
|
||||
offset += 1
|
||||
}
|
||||
}
|
||||
return Data(pointer[0..<offset])
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - functions for simpler array buffer reading and writing
|
||||
|
||||
public protocol MyWSArrayType {}
|
||||
extension UInt8: MyWSArrayType {}
|
||||
|
||||
public extension Array where Element: MyWSArrayType & UnsignedInteger {
|
||||
|
||||
/**
|
||||
Read a UInt16 from a buffer.
|
||||
- parameter offset: is the offset index to start the read from (e.g. buffer[0], buffer[1], etc).
|
||||
- returns: a UInt16 of the value from the buffer
|
||||
*/
|
||||
func readUint16(offset: Int) -> UInt16 {
|
||||
return (UInt16(self[offset + 0]) << 8) | UInt16(self[offset + 1])
|
||||
}
|
||||
|
||||
/**
|
||||
Read a UInt64 from a buffer.
|
||||
- parameter offset: is the offset index to start the read from (e.g. buffer[0], buffer[1], etc).
|
||||
- returns: a UInt64 of the value from the buffer
|
||||
*/
|
||||
func readUint64(offset: Int) -> UInt64 {
|
||||
var value = UInt64(0)
|
||||
for i in 0...7 {
|
||||
value = (value << 8) | UInt64(self[offset + i])
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func unmaskData(maskStart: Int, offset: Int, length: Int) -> Data {
|
||||
var unmaskedBytes = [UInt8](repeating: 0, count: length)
|
||||
let maskSize = MemoryLayout<UInt32>.size
|
||||
for i in 0..<length {
|
||||
unmaskedBytes[i] = UInt8(self[offset + i] ^ self[maskStart + (i % maskSize)])
|
||||
}
|
||||
return Data(unmaskedBytes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Write a UInt16 to the buffer. It fills the 2 array "slots" of the UInt8 array.
|
||||
- parameter buffer: is the UInt8 array (pointer) to write the value too.
|
||||
- parameter offset: is the offset index to start the write from (e.g. buffer[0], buffer[1], etc).
|
||||
*/
|
||||
public func writeUint16( _ buffer: inout [UInt8], offset: Int, value: UInt16) {
|
||||
buffer[offset + 0] = UInt8(value >> 8)
|
||||
buffer[offset + 1] = UInt8(value & 0xff)
|
||||
}
|
||||
|
||||
/**
|
||||
Write a UInt32 to the buffer. It fills the 4 array "slots" of the UInt8 array.
|
||||
- parameter buffer: is the UInt8 array (pointer) to write the value too.
|
||||
- parameter offset: is the offset index to start the write from (e.g. buffer[0], buffer[1], etc).
|
||||
*/
|
||||
public func writeUint32( _ buffer: inout [UInt8], offset: Int, value: UInt32) {
|
||||
for i in 0...3 {
|
||||
buffer[offset + i] = UInt8((value >> (8*UInt32(3 - i))) & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Write a UInt64 to the buffer. It fills the 8 array "slots" of the UInt8 array.
|
||||
- parameter buffer: is the UInt8 array (pointer) to write the value too.
|
||||
- parameter offset: is the offset index to start the write from (e.g. buffer[0], buffer[1], etc).
|
||||
*/
|
||||
public func writeUint64( _ buffer: inout [UInt8], offset: Int, value: UInt64) {
|
||||
for i in 0...7 {
|
||||
buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff)
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/24/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum HTTPUpgradeError: Error {
|
||||
case notAnUpgrade(Int)
|
||||
case invalidData
|
||||
}
|
||||
|
||||
public struct HTTPWSHeader {
|
||||
static let upgradeName = "Upgrade"
|
||||
static let upgradeValue = "websocket"
|
||||
static let hostName = "Host"
|
||||
static let connectionName = "Connection"
|
||||
static let connectionValue = "Upgrade"
|
||||
static let protocolName = "Sec-WebSocket-Protocol"
|
||||
static let versionName = "Sec-WebSocket-Version"
|
||||
static let versionValue = "13"
|
||||
static let extensionName = "Sec-WebSocket-Extensions"
|
||||
static let keyName = "Sec-WebSocket-Key"
|
||||
static let originName = "Origin"
|
||||
static let acceptName = "Sec-WebSocket-Accept"
|
||||
static let switchProtocolCode = 101
|
||||
static let defaultSSLSchemes = ["wss", "https"]
|
||||
|
||||
/// Creates a new URLRequest based off the source URLRequest.
|
||||
/// - Parameter request: the request to "upgrade" the WebSocket request by adding headers.
|
||||
/// - Parameter supportsCompression: set if the client support text compression.
|
||||
/// - Parameter secKeyName: the security key to use in the WebSocket request. https://tools.ietf.org/html/rfc6455#section-1.3
|
||||
/// - returns: A URLRequest request to be converted to data and sent to the server.
|
||||
public static func createUpgrade(request: URLRequest, supportsCompression: Bool, secKeyValue: String) -> URLRequest {
|
||||
guard let url = request.url, let parts = url.getParts() else {
|
||||
return request
|
||||
}
|
||||
|
||||
var req = request
|
||||
if request.value(forHTTPHeaderField: HTTPWSHeader.originName) == nil {
|
||||
var origin = url.absoluteString
|
||||
if let hostUrl = URL (string: "/", relativeTo: url) {
|
||||
origin = hostUrl.absoluteString
|
||||
origin.remove(at: origin.index(before: origin.endIndex))
|
||||
}
|
||||
req.setValue(origin, forHTTPHeaderField: HTTPWSHeader.originName)
|
||||
}
|
||||
req.setValue(HTTPWSHeader.upgradeValue, forHTTPHeaderField: HTTPWSHeader.upgradeName)
|
||||
req.setValue(HTTPWSHeader.connectionValue, forHTTPHeaderField: HTTPWSHeader.connectionName)
|
||||
req.setValue(HTTPWSHeader.versionValue, forHTTPHeaderField: HTTPWSHeader.versionName)
|
||||
req.setValue(secKeyValue, forHTTPHeaderField: HTTPWSHeader.keyName)
|
||||
|
||||
if let cookies = HTTPCookieStorage.shared.cookies(for: url), !cookies.isEmpty {
|
||||
let headers = HTTPCookie.requestHeaderFields(with: cookies)
|
||||
for (key, val) in headers {
|
||||
req.setValue(val, forHTTPHeaderField: key)
|
||||
}
|
||||
}
|
||||
|
||||
if supportsCompression {
|
||||
let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15"
|
||||
req.setValue(val, forHTTPHeaderField: HTTPWSHeader.extensionName)
|
||||
}
|
||||
let hostValue = req.allHTTPHeaderFields?[HTTPWSHeader.hostName] ?? "\(parts.host):\(parts.port)"
|
||||
req.setValue(hostValue, forHTTPHeaderField: HTTPWSHeader.hostName)
|
||||
return req
|
||||
}
|
||||
|
||||
// generateWebSocketKey 16 random characters between a-z and return them as a base64 string
|
||||
public static func generateWebSocketKey() -> String {
|
||||
return Data((0..<16).map{ _ in UInt8.random(in: 97...122) }).base64EncodedString()
|
||||
}
|
||||
}
|
||||
|
||||
public enum HTTPEvent {
|
||||
case success([String: String])
|
||||
case failure(Error)
|
||||
}
|
||||
|
||||
public protocol HTTPHandlerDelegate: class {
|
||||
func didReceiveHTTP(event: HTTPEvent)
|
||||
}
|
||||
|
||||
public protocol HTTPHandler {
|
||||
func register(delegate: HTTPHandlerDelegate)
|
||||
func convert(request: URLRequest) -> Data
|
||||
func parse(data: Data) -> Int
|
||||
}
|
||||
|
||||
public protocol HTTPServerDelegate: class {
|
||||
func didReceive(event: HTTPEvent)
|
||||
}
|
||||
|
||||
public protocol HTTPServerHandler {
|
||||
func register(delegate: HTTPServerDelegate)
|
||||
func parse(data: Data)
|
||||
func createResponse(headers: [String: String]) -> Data
|
||||
}
|
||||
|
||||
public struct URLParts {
|
||||
let port: Int
|
||||
let host: String
|
||||
let isTLS: Bool
|
||||
}
|
||||
|
||||
public extension URL {
|
||||
/// isTLSScheme returns true if the scheme is https or wss
|
||||
var isTLSScheme: Bool {
|
||||
guard let scheme = self.scheme else {
|
||||
return false
|
||||
}
|
||||
return HTTPWSHeader.defaultSSLSchemes.contains(scheme)
|
||||
}
|
||||
|
||||
/// getParts pulls host and port from the url.
|
||||
func getParts() -> URLParts? {
|
||||
guard let host = self.host else {
|
||||
return nil // no host, this isn't a valid url
|
||||
}
|
||||
let isTLS = isTLSScheme
|
||||
var port = self.port ?? 0
|
||||
if self.port == nil {
|
||||
if isTLS {
|
||||
port = 443
|
||||
} else {
|
||||
port = 80
|
||||
}
|
||||
}
|
||||
return URLParts(port: port, host: host, isTLS: isTLS)
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// StringHTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 8/25/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public class StringHTTPHandler: HTTPHandler {
|
||||
|
||||
var buffer = Data()
|
||||
weak var delegate: HTTPHandlerDelegate?
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func convert(request: URLRequest) -> Data {
|
||||
guard let url = request.url else {
|
||||
return Data()
|
||||
}
|
||||
|
||||
var path = url.absoluteString
|
||||
let offset = (url.scheme?.count ?? 2) + 3
|
||||
path = String(path[path.index(path.startIndex, offsetBy: offset)..<path.endIndex])
|
||||
if let range = path.range(of: "/") {
|
||||
path = String(path[range.lowerBound..<path.endIndex])
|
||||
} else {
|
||||
path = "/"
|
||||
if let query = url.query {
|
||||
path += "?" + query
|
||||
}
|
||||
}
|
||||
|
||||
var httpBody = "\(request.httpMethod ?? "GET") \(path) HTTP/1.1\r\n"
|
||||
if let headers = request.allHTTPHeaderFields {
|
||||
for (key, val) in headers {
|
||||
httpBody += "\(key): \(val)\r\n"
|
||||
}
|
||||
}
|
||||
httpBody += "\r\n"
|
||||
|
||||
guard var data = httpBody.data(using: .utf8) else {
|
||||
return Data()
|
||||
}
|
||||
|
||||
if let body = request.httpBody {
|
||||
data.append(body)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
public func parse(data: Data) -> Int {
|
||||
let offset = findEndOfHTTP(data: data)
|
||||
if offset > 0 {
|
||||
buffer.append(data.subdata(in: 0..<offset))
|
||||
if parseContent(data: buffer) {
|
||||
buffer = Data()
|
||||
}
|
||||
} else {
|
||||
buffer.append(data)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
//returns true when the buffer should be cleared
|
||||
func parseContent(data: Data) -> Bool {
|
||||
guard let str = String(data: data, encoding: .utf8) else {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
let splitArr = str.components(separatedBy: "\r\n")
|
||||
var code = -1
|
||||
var i = 0
|
||||
var headers = [String: String]()
|
||||
for str in splitArr {
|
||||
if i == 0 {
|
||||
let responseSplit = str.components(separatedBy: .whitespaces)
|
||||
guard responseSplit.count > 1 else {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
if let c = Int(responseSplit[1]) {
|
||||
code = c
|
||||
}
|
||||
} else {
|
||||
guard let separatorIndex = str.firstIndex(of: ":") else { break }
|
||||
let key = str.prefix(upTo: separatorIndex).trimmingCharacters(in: .whitespaces)
|
||||
let val = str.suffix(from: str.index(after: separatorIndex)).trimmingCharacters(in: .whitespaces)
|
||||
headers[key.lowercased()] = val
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
if code != HTTPWSHeader.switchProtocolCode {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.notAnUpgrade(code)))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceiveHTTP(event: .success(headers))
|
||||
return true
|
||||
}
|
||||
|
||||
public func register(delegate: HTTPHandlerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
private func findEndOfHTTP(data: Data) -> Int {
|
||||
let endBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
var k = 0
|
||||
for i in 0..<data.count {
|
||||
if pointer[i] == endBytes[k] {
|
||||
k += 1
|
||||
if k == 4 {
|
||||
return i + 1
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
@ -1,101 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationSecurity.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 3/16/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 CommonCrypto
|
||||
|
||||
public enum FoundationSecurityError: Error {
|
||||
case invalidRequest
|
||||
}
|
||||
|
||||
public class FoundationSecurity {
|
||||
var allowSelfSigned = false
|
||||
|
||||
public init(allowSelfSigned: Bool = false) {
|
||||
self.allowSelfSigned = allowSelfSigned
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension FoundationSecurity: CertificatePinning {
|
||||
public func evaluateTrust(trust: SecTrust, domain: String?, completion: ((PinningState) -> ())) {
|
||||
if allowSelfSigned {
|
||||
completion(.success)
|
||||
return
|
||||
}
|
||||
|
||||
if let validateDomain = domain {
|
||||
SecTrustSetPolicies(trust, SecPolicyCreateSSL(true, validateDomain as NSString?))
|
||||
}
|
||||
|
||||
handleSecurityTrust(trust: trust, completion: completion)
|
||||
}
|
||||
|
||||
private func handleSecurityTrust(trust: SecTrust, completion: ((PinningState) -> ())) {
|
||||
if #available(iOS 12.0, OSX 10.14, watchOS 5.0, tvOS 12.0, *) {
|
||||
var error: CFError?
|
||||
if SecTrustEvaluateWithError(trust, &error) {
|
||||
completion(.success)
|
||||
} else {
|
||||
completion(.failed(error))
|
||||
}
|
||||
} else {
|
||||
handleOldSecurityTrust(trust: trust, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleOldSecurityTrust(trust: SecTrust, completion: ((PinningState) -> ())) {
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust, &result)
|
||||
if result == .unspecified || result == .proceed {
|
||||
completion(.success)
|
||||
} else {
|
||||
let e = CFErrorCreate(kCFAllocatorDefault, "FoundationSecurityError" as NSString?, Int(result.rawValue), nil)
|
||||
completion(.failed(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FoundationSecurity: HeaderValidator {
|
||||
public func validate(headers: [String: String], key: String) -> Error? {
|
||||
if let acceptKey = headers[HTTPWSHeader.acceptName] {
|
||||
let sha = "\(key)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64()
|
||||
if sha != acceptKey {
|
||||
return WSError(type: .securityError, message: "accept header doesn't match", code: SecurityErrorCode.acceptFailed.rawValue)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
func sha1Base64() -> String {
|
||||
let data = self.data(using: .utf8)!
|
||||
let pointer = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
|
||||
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
|
||||
CC_SHA1(bytes.baseAddress, CC_LONG(data.count), &digest)
|
||||
return digest
|
||||
}
|
||||
return Data(pointer).base64EncodedString()
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Security.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 3/16/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum SecurityErrorCode: UInt16 {
|
||||
case acceptFailed = 1
|
||||
case pinningFailed = 2
|
||||
}
|
||||
|
||||
public enum PinningState {
|
||||
case success
|
||||
case failed(CFError?)
|
||||
}
|
||||
|
||||
// CertificatePinning protocol provides an interface for Transports to handle Certificate
|
||||
// or Public Key Pinning.
|
||||
public protocol CertificatePinning: class {
|
||||
func evaluateTrust(trust: SecTrust, domain: String?, completion: ((PinningState) -> ()))
|
||||
}
|
||||
|
||||
// validates the "Sec-WebSocket-Accept" header as defined 1.3 of the RFC 6455
|
||||
// https://tools.ietf.org/html/rfc6455#section-1.3
|
||||
public protocol HeaderValidator: class {
|
||||
func validate(headers: [String: String], key: String) -> Error?
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Server.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 4/2/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum ConnectionEvent {
|
||||
case connected([String: String])
|
||||
case disconnected(String, UInt16)
|
||||
case text(String)
|
||||
case binary(Data)
|
||||
case pong(Data?)
|
||||
case ping(Data?)
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
public protocol Connection {
|
||||
func write(data: Data, opcode: FrameOpCode)
|
||||
}
|
||||
|
||||
public protocol ConnectionDelegate: class {
|
||||
func didReceive(event: ServerEvent)
|
||||
}
|
||||
|
||||
public enum ServerEvent {
|
||||
case connected(Connection, [String: String])
|
||||
case disconnected(Connection, String, UInt16)
|
||||
case text(Connection, String)
|
||||
case binary(Connection, Data)
|
||||
case pong(Connection, Data?)
|
||||
case ping(Connection, Data?)
|
||||
}
|
||||
|
||||
public protocol Server {
|
||||
func start(address: String, port: UInt16) -> Error?
|
||||
}
|
||||
|
||||
|
@ -1,196 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WebSocketServer.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 4/5/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if canImport(Network)
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
/// WebSocketServer is a Network.framework implementation of a WebSocket server
|
||||
@available(watchOS, unavailable)
|
||||
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
||||
public class WebSocketServer: Server, ConnectionDelegate {
|
||||
public var onEvent: ((ServerEvent) -> Void)?
|
||||
private var connections = [String: ServerConnection]()
|
||||
private var listener: NWListener?
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.server.networkstream", attributes: [])
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func start(address: String, port: UInt16) -> Error? {
|
||||
//TODO: support TLS cert adding/binding
|
||||
let parameters = NWParameters(tls: nil, tcp: NWProtocolTCP.Options())
|
||||
let p = NWEndpoint.Port(rawValue: port)!
|
||||
parameters.requiredLocalEndpoint = NWEndpoint.hostPort(host: NWEndpoint.Host.name(address, nil), port: p)
|
||||
|
||||
guard let listener = try? NWListener(using: parameters, on: p) else {
|
||||
return WSError(type: .serverError, message: "unable to start the listener at: \(address):\(port)", code: 0)
|
||||
}
|
||||
listener.newConnectionHandler = {[weak self] conn in
|
||||
let transport = TCPTransport(connection: conn)
|
||||
let c = ServerConnection(transport: transport)
|
||||
c.delegate = self
|
||||
self?.connections[c.uuid] = c
|
||||
}
|
||||
// listener.stateUpdateHandler = { state in
|
||||
// switch state {
|
||||
// case .ready:
|
||||
// print("ready to get sockets!")
|
||||
// case .setup:
|
||||
// print("setup to get sockets!")
|
||||
// case .cancelled:
|
||||
// print("server cancelled!")
|
||||
// case .waiting(let error):
|
||||
// print("waiting error: \(error)")
|
||||
// case .failed(let error):
|
||||
// print("server failed: \(error)")
|
||||
// @unknown default:
|
||||
// print("wat?")
|
||||
// }
|
||||
// }
|
||||
self.listener = listener
|
||||
listener.start(queue: queue)
|
||||
return nil
|
||||
}
|
||||
|
||||
public func didReceive(event: ServerEvent) {
|
||||
onEvent?(event)
|
||||
switch event {
|
||||
case .disconnected(let conn, _, _):
|
||||
guard let conn = conn as? ServerConnection else {
|
||||
return
|
||||
}
|
||||
connections.removeValue(forKey: conn.uuid)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
||||
public class ServerConnection: Connection, HTTPServerDelegate, FramerEventClient, FrameCollectorDelegate, TransportEventClient {
|
||||
let transport: TCPTransport
|
||||
private let httpHandler = FoundationHTTPServerHandler()
|
||||
private let framer = WSFramer(isServer: true)
|
||||
private let frameHandler = FrameCollector()
|
||||
private var didUpgrade = false
|
||||
public var onEvent: ((ConnectionEvent) -> Void)?
|
||||
public weak var delegate: ConnectionDelegate?
|
||||
private let id: String
|
||||
var uuid: String {
|
||||
return id
|
||||
}
|
||||
|
||||
init(transport: TCPTransport) {
|
||||
self.id = UUID().uuidString
|
||||
self.transport = transport
|
||||
transport.register(delegate: self)
|
||||
httpHandler.register(delegate: self)
|
||||
framer.register(delegate: self)
|
||||
frameHandler.delegate = self
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode) {
|
||||
let wsData = framer.createWriteFrame(opcode: opcode, payload: data, isCompressed: false)
|
||||
transport.write(data: wsData, completion: {_ in })
|
||||
}
|
||||
|
||||
// MARK: - TransportEventClient
|
||||
|
||||
public func connectionChanged(state: ConnectionState) {
|
||||
switch state {
|
||||
case .connected:
|
||||
break
|
||||
case .waiting:
|
||||
break
|
||||
case .failed(let error):
|
||||
print("server connection error: \(error ?? WSError(type: .protocolError, message: "default error, no extra data", code: 0))") //handleError(error)
|
||||
case .viability(_):
|
||||
break
|
||||
case .shouldReconnect(_):
|
||||
break
|
||||
case .receive(let data):
|
||||
if didUpgrade {
|
||||
framer.add(data: data)
|
||||
} else {
|
||||
httpHandler.parse(data: data)
|
||||
}
|
||||
case .cancelled:
|
||||
print("server connection cancelled!")
|
||||
//broadcast(event: .cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - HTTPServerDelegate
|
||||
|
||||
public func didReceive(event: HTTPEvent) {
|
||||
switch event {
|
||||
case .success(let headers):
|
||||
didUpgrade = true
|
||||
let response = httpHandler.createResponse(headers: [:])
|
||||
transport.write(data: response, completion: {_ in })
|
||||
delegate?.didReceive(event: .connected(self, headers))
|
||||
onEvent?(.connected(headers))
|
||||
case .failure(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - FrameCollectorDelegate
|
||||
|
||||
public func frameProcessed(event: FrameEvent) {
|
||||
switch event {
|
||||
case .frame(let frame):
|
||||
frameHandler.add(frame: frame)
|
||||
case .error(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
public func didForm(event: FrameCollector.Event) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
delegate?.didReceive(event: .text(self, string))
|
||||
onEvent?(.text(string))
|
||||
case .binary(let data):
|
||||
delegate?.didReceive(event: .binary(self, data))
|
||||
onEvent?(.binary(data))
|
||||
case .pong(let data):
|
||||
delegate?.didReceive(event: .pong(self, data))
|
||||
onEvent?(.pong(data))
|
||||
case .ping(let data):
|
||||
delegate?.didReceive(event: .ping(self, data))
|
||||
onEvent?(.ping(data))
|
||||
case .closed(let reason, let code):
|
||||
delegate?.didReceive(event: .disconnected(self, reason, code))
|
||||
onEvent?(.disconnected(reason, code))
|
||||
case .error(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,178 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Websocket.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/16/14.
|
||||
// Copyright (c) 2014-2019 Dalton Cherry.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum ErrorType: Error {
|
||||
case compressionError
|
||||
case securityError
|
||||
case protocolError //There was an error parsing the WebSocket frames
|
||||
case serverError
|
||||
}
|
||||
|
||||
public struct WSError: Error {
|
||||
public let type: ErrorType
|
||||
public let message: String
|
||||
public let code: UInt16
|
||||
|
||||
public init(type: ErrorType, message: String, code: UInt16) {
|
||||
self.type = type
|
||||
self.message = message
|
||||
self.code = code
|
||||
}
|
||||
}
|
||||
|
||||
public protocol WebSocketClient: class {
|
||||
func connect()
|
||||
func disconnect(closeCode: UInt16)
|
||||
func write(string: String, completion: (() -> ())?)
|
||||
func write(stringData: Data, completion: (() -> ())?)
|
||||
func write(data: Data, completion: (() -> ())?)
|
||||
func write(ping: Data, completion: (() -> ())?)
|
||||
func write(pong: Data, completion: (() -> ())?)
|
||||
}
|
||||
|
||||
//implements some of the base behaviors
|
||||
extension WebSocketClient {
|
||||
public func write(string: String) {
|
||||
write(string: string, completion: nil)
|
||||
}
|
||||
|
||||
public func write(data: Data) {
|
||||
write(data: data, completion: nil)
|
||||
}
|
||||
|
||||
public func write(ping: Data) {
|
||||
write(ping: ping, completion: nil)
|
||||
}
|
||||
|
||||
public func write(pong: Data) {
|
||||
write(pong: pong, completion: nil)
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
disconnect(closeCode: CloseCode.normal.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public enum WebSocketEvent {
|
||||
case connected([String: String])
|
||||
case disconnected(String, UInt16)
|
||||
case text(String)
|
||||
case binary(Data)
|
||||
case pong(Data?)
|
||||
case ping(Data?)
|
||||
case error(Error?)
|
||||
case viabilityChanged(Bool)
|
||||
case reconnectSuggested(Bool)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
public protocol WebSocketDelegate: class {
|
||||
func didReceive(event: WebSocketEvent, client: WebSocket)
|
||||
}
|
||||
|
||||
open class WebSocket: WebSocketClient, EngineDelegate {
|
||||
private let engine: Engine
|
||||
public weak var delegate: WebSocketDelegate?
|
||||
public var onEvent: ((WebSocketEvent) -> Void)?
|
||||
|
||||
public var request: URLRequest
|
||||
// Where the callback is executed. It defaults to the main UI thread queue.
|
||||
public var callbackQueue = DispatchQueue.main
|
||||
public var respondToPingWithPong: Bool {
|
||||
set {
|
||||
guard let e = engine as? WSEngine else { return }
|
||||
e.respondToPingWithPong = newValue
|
||||
}
|
||||
get {
|
||||
guard let e = engine as? WSEngine else { return true }
|
||||
return e.respondToPingWithPong
|
||||
}
|
||||
}
|
||||
|
||||
// serial write queue to ensure writes happen in order
|
||||
private let writeQueue = DispatchQueue(label: "com.vluxe.starscream.writequeue")
|
||||
private var canSend = false
|
||||
private let mutex = DispatchSemaphore(value: 1)
|
||||
|
||||
public init(request: URLRequest, engine: Engine) {
|
||||
self.request = request
|
||||
self.engine = engine
|
||||
}
|
||||
|
||||
public convenience init(request: URLRequest, certPinner: CertificatePinning? = FoundationSecurity(), compressionHandler: CompressionHandler? = nil, useCustomEngine: Bool = true) {
|
||||
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *), !useCustomEngine {
|
||||
self.init(request: request, engine: NativeEngine())
|
||||
} else if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) {
|
||||
self.init(request: request, engine: WSEngine(transport: TCPTransport(), certPinner: certPinner, compressionHandler: compressionHandler))
|
||||
} else {
|
||||
self.init(request: request, engine: WSEngine(transport: FoundationTransport(), certPinner: certPinner, compressionHandler: compressionHandler))
|
||||
}
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
engine.register(delegate: self)
|
||||
engine.start(request: request)
|
||||
}
|
||||
|
||||
public func disconnect(closeCode: UInt16 = CloseCode.normal.rawValue) {
|
||||
engine.stop(closeCode: closeCode)
|
||||
}
|
||||
|
||||
public func forceDisconnect() {
|
||||
engine.forceStop()
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: (() -> ())?) {
|
||||
write(data: data, opcode: .binaryFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func write(string: String, completion: (() -> ())?) {
|
||||
engine.write(string: string, completion: completion)
|
||||
}
|
||||
|
||||
public func write(stringData: Data, completion: (() -> ())?) {
|
||||
write(data: stringData, opcode: .textFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func write(ping: Data, completion: (() -> ())?) {
|
||||
write(data: ping, opcode: .ping, completion: completion)
|
||||
}
|
||||
|
||||
public func write(pong: Data, completion: (() -> ())?) {
|
||||
write(data: pong, opcode: .pong, completion: completion)
|
||||
}
|
||||
|
||||
private func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
|
||||
engine.write(data: data, opcode: opcode, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - EngineDelegate
|
||||
public func didReceive(event: WebSocketEvent) {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.delegate?.didReceive(event: event, client: s)
|
||||
s.onEvent?(event)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationTransport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum FoundationTransportError: Error {
|
||||
case invalidRequest
|
||||
case invalidOutputStream
|
||||
case timeout
|
||||
}
|
||||
|
||||
public class FoundationTransport: NSObject, Transport, StreamDelegate {
|
||||
private weak var delegate: TransportEventClient?
|
||||
private let workQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: [])
|
||||
private var inputStream: InputStream?
|
||||
private var outputStream: OutputStream?
|
||||
private var isOpen = false
|
||||
private var onConnect: ((InputStream, OutputStream) -> Void)?
|
||||
private var isTLS = false
|
||||
private var certPinner: CertificatePinning?
|
||||
|
||||
public var usingTLS: Bool {
|
||||
return self.isTLS
|
||||
}
|
||||
|
||||
public init(streamConfiguration: ((InputStream, OutputStream) -> Void)? = nil) {
|
||||
super.init()
|
||||
onConnect = streamConfiguration
|
||||
}
|
||||
|
||||
deinit {
|
||||
inputStream?.delegate = nil
|
||||
outputStream?.delegate = nil
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
|
||||
guard let parts = url.getParts() else {
|
||||
delegate?.connectionChanged(state: .failed(FoundationTransportError.invalidRequest))
|
||||
return
|
||||
}
|
||||
self.certPinner = certificatePinning
|
||||
self.isTLS = parts.isTLS
|
||||
var readStream: Unmanaged<CFReadStream>?
|
||||
var writeStream: Unmanaged<CFWriteStream>?
|
||||
let h = parts.host as NSString
|
||||
CFStreamCreatePairWithSocketToHost(nil, h, UInt32(parts.port), &readStream, &writeStream)
|
||||
inputStream = readStream!.takeRetainedValue()
|
||||
outputStream = writeStream!.takeRetainedValue()
|
||||
guard let inStream = inputStream, let outStream = outputStream else {
|
||||
return
|
||||
}
|
||||
inStream.delegate = self
|
||||
outStream.delegate = self
|
||||
|
||||
if isTLS {
|
||||
let key = CFStreamPropertyKey(rawValue: kCFStreamPropertySocketSecurityLevel)
|
||||
CFReadStreamSetProperty(inStream, key, kCFStreamSocketSecurityLevelNegotiatedSSL)
|
||||
CFWriteStreamSetProperty(outStream, key, kCFStreamSocketSecurityLevelNegotiatedSSL)
|
||||
}
|
||||
|
||||
onConnect?(inStream, outStream)
|
||||
|
||||
isOpen = false
|
||||
CFReadStreamSetDispatchQueue(inStream, workQueue)
|
||||
CFWriteStreamSetDispatchQueue(outStream, workQueue)
|
||||
inStream.open()
|
||||
outStream.open()
|
||||
|
||||
|
||||
workQueue.asyncAfter(deadline: .now() + timeout, execute: { [weak self] in
|
||||
guard let s = self else { return }
|
||||
if !s.isOpen {
|
||||
s.delegate?.connectionChanged(state: .failed(FoundationTransportError.timeout))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
if let stream = inputStream {
|
||||
stream.delegate = nil
|
||||
CFReadStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
if let stream = outputStream {
|
||||
stream.delegate = nil
|
||||
CFWriteStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
isOpen = false
|
||||
outputStream = nil
|
||||
inputStream = nil
|
||||
}
|
||||
|
||||
public func register(delegate: TransportEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> ())) {
|
||||
guard let outStream = outputStream else {
|
||||
completion(FoundationTransportError.invalidOutputStream)
|
||||
return
|
||||
}
|
||||
var total = 0
|
||||
let buffer = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
|
||||
//NOTE: this might need to be dispatched to the work queue instead of being written inline. TBD.
|
||||
while total < data.count {
|
||||
let written = outStream.write(buffer, maxLength: data.count)
|
||||
if written < 0 {
|
||||
completion(FoundationTransportError.invalidOutputStream)
|
||||
return
|
||||
}
|
||||
total += written
|
||||
}
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
private func getSecurityData() -> (SecTrust?, String?) {
|
||||
#if os(watchOS)
|
||||
return (nil, nil)
|
||||
#else
|
||||
guard let outputStream = outputStream else {
|
||||
return (nil, nil)
|
||||
}
|
||||
let trust = outputStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust?
|
||||
var domain = outputStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as! String?
|
||||
|
||||
if domain == nil,
|
||||
let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? {
|
||||
var peerNameLen: Int = 0
|
||||
SSLGetPeerDomainNameLength(sslContextOut, &peerNameLen)
|
||||
var peerName = Data(count: peerNameLen)
|
||||
let _ = peerName.withUnsafeMutableBytes { (peerNamePtr: UnsafeMutablePointer<Int8>) in
|
||||
SSLGetPeerDomainName(sslContextOut, peerNamePtr, &peerNameLen)
|
||||
}
|
||||
if let peerDomain = String(bytes: peerName, encoding: .utf8), peerDomain.count > 0 {
|
||||
domain = peerDomain
|
||||
}
|
||||
}
|
||||
return (trust, domain)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func read() {
|
||||
guard let stream = inputStream else {
|
||||
return
|
||||
}
|
||||
let maxBuffer = 4096
|
||||
let buf = NSMutableData(capacity: maxBuffer)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = stream.read(buffer, maxLength: maxBuffer)
|
||||
if length < 1 {
|
||||
return
|
||||
}
|
||||
let data = Data(bytes: buffer, count: length)
|
||||
delegate?.connectionChanged(state: .receive(data))
|
||||
}
|
||||
|
||||
// MARK: - StreamDelegate
|
||||
|
||||
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
switch eventCode {
|
||||
case .hasBytesAvailable:
|
||||
if aStream == inputStream {
|
||||
read()
|
||||
}
|
||||
case .errorOccurred:
|
||||
delegate?.connectionChanged(state: .failed(aStream.streamError))
|
||||
case .endEncountered:
|
||||
if aStream == inputStream {
|
||||
delegate?.connectionChanged(state: .cancelled)
|
||||
}
|
||||
case .openCompleted:
|
||||
if aStream == inputStream {
|
||||
let (trust, domain) = getSecurityData()
|
||||
if let pinner = certPinner, let trust = trust {
|
||||
pinner.evaluateTrust(trust: trust, domain: domain, completion: { [weak self] (state) in
|
||||
switch state {
|
||||
case .success:
|
||||
self?.isOpen = true
|
||||
self?.delegate?.connectionChanged(state: .connected)
|
||||
case .failed(let error):
|
||||
self?.delegate?.connectionChanged(state: .failed(error))
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
isOpen = true
|
||||
delegate?.connectionChanged(state: .connected)
|
||||
}
|
||||
}
|
||||
case .endEncountered:
|
||||
if aStream == inputStream {
|
||||
delegate?.connectionChanged(state: .cancelled)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HTTPTransport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if canImport(Network)
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public enum TCPTransportError: Error {
|
||||
case invalidRequest
|
||||
}
|
||||
|
||||
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
||||
public class TCPTransport: Transport {
|
||||
private var connection: NWConnection?
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.networkstream", attributes: [])
|
||||
private weak var delegate: TransportEventClient?
|
||||
private var isRunning = false
|
||||
private var isTLS = false
|
||||
|
||||
public var usingTLS: Bool {
|
||||
return self.isTLS
|
||||
}
|
||||
|
||||
public init(connection: NWConnection) {
|
||||
self.connection = connection
|
||||
start()
|
||||
}
|
||||
|
||||
public init() {
|
||||
//normal connection, will use the "connect" method below
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
|
||||
guard let parts = url.getParts() else {
|
||||
delegate?.connectionChanged(state: .failed(TCPTransportError.invalidRequest))
|
||||
return
|
||||
}
|
||||
self.isTLS = parts.isTLS
|
||||
let options = NWProtocolTCP.Options()
|
||||
options.connectionTimeout = Int(timeout.rounded(.up))
|
||||
|
||||
let tlsOptions = isTLS ? NWProtocolTLS.Options() : nil
|
||||
if let tlsOpts = tlsOptions {
|
||||
sec_protocol_options_set_verify_block(tlsOpts.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
|
||||
let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
|
||||
guard let pinner = certificatePinning else {
|
||||
sec_protocol_verify_complete(true)
|
||||
return
|
||||
}
|
||||
pinner.evaluateTrust(trust: trust, domain: parts.host, completion: { (state) in
|
||||
switch state {
|
||||
case .success:
|
||||
sec_protocol_verify_complete(true)
|
||||
case .failed(_):
|
||||
sec_protocol_verify_complete(false)
|
||||
}
|
||||
})
|
||||
}, queue)
|
||||
}
|
||||
let parameters = NWParameters(tls: tlsOptions, tcp: options)
|
||||
let conn = NWConnection(host: NWEndpoint.Host.name(parts.host, nil), port: NWEndpoint.Port(rawValue: UInt16(parts.port))!, using: parameters)
|
||||
connection = conn
|
||||
start()
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
isRunning = false
|
||||
connection?.cancel()
|
||||
}
|
||||
|
||||
public func register(delegate: TransportEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> ())) {
|
||||
connection?.send(content: data, completion: .contentProcessed { (error) in
|
||||
completion(error)
|
||||
})
|
||||
}
|
||||
|
||||
private func start() {
|
||||
guard let conn = connection else {
|
||||
return
|
||||
}
|
||||
conn.stateUpdateHandler = { [weak self] (newState) in
|
||||
switch newState {
|
||||
case .ready:
|
||||
self?.delegate?.connectionChanged(state: .connected)
|
||||
case .waiting:
|
||||
self?.delegate?.connectionChanged(state: .waiting)
|
||||
case .cancelled:
|
||||
self?.delegate?.connectionChanged(state: .cancelled)
|
||||
case .failed(let error):
|
||||
self?.delegate?.connectionChanged(state: .failed(error))
|
||||
case .setup, .preparing:
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
conn.viabilityUpdateHandler = { [weak self] (isViable) in
|
||||
self?.delegate?.connectionChanged(state: .viability(isViable))
|
||||
}
|
||||
|
||||
conn.betterPathUpdateHandler = { [weak self] (isBetter) in
|
||||
self?.delegate?.connectionChanged(state: .shouldReconnect(isBetter))
|
||||
}
|
||||
|
||||
conn.start(queue: queue)
|
||||
isRunning = true
|
||||
readLoop()
|
||||
}
|
||||
|
||||
//readLoop keeps reading from the connection to get the latest content
|
||||
private func readLoop() {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
connection?.receive(minimumIncompleteLength: 2, maximumLength: 4096, completion: {[weak self] (data, context, isComplete, error) in
|
||||
guard let s = self else {return}
|
||||
if let data = data {
|
||||
s.delegate?.connectionChanged(state: .receive(data))
|
||||
}
|
||||
|
||||
// Refer to https://developer.apple.com/documentation/network/implementing_netcat_with_network_framework
|
||||
if let context = context, context.isFinal, isComplete {
|
||||
return
|
||||
}
|
||||
|
||||
if error == nil {
|
||||
s.readLoop()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
#else
|
||||
typealias TCPTransport = FoundationTransport
|
||||
#endif
|
@ -1,55 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Transport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum ConnectionState {
|
||||
case connected
|
||||
case waiting
|
||||
case cancelled
|
||||
case failed(Error?)
|
||||
|
||||
//the viability (connection status) of the connection has updated
|
||||
//e.g. connection is down, connection came back up, etc
|
||||
case viability(Bool)
|
||||
|
||||
//the connection has upgrade to wifi from cellular.
|
||||
//you should consider reconnecting to take advantage of this
|
||||
case shouldReconnect(Bool)
|
||||
|
||||
//the connection receive data
|
||||
case receive(Data)
|
||||
}
|
||||
|
||||
public protocol TransportEventClient: class {
|
||||
func connectionChanged(state: ConnectionState)
|
||||
}
|
||||
|
||||
public protocol Transport: class {
|
||||
func register(delegate: TransportEventClient)
|
||||
func connect(url: URL, timeout: Double, certificatePinning: CertificatePinning?)
|
||||
func disconnect()
|
||||
func write(data: Data, completion: @escaping ((Error?) -> ()))
|
||||
var usingTLS: Bool { get }
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
## Runtime Library Exception to the Apache 2.0 License: ##
|
||||
|
||||
|
||||
As an exception, if you use this Software to compile your source code and
|
||||
portions of this Software are embedded into the binary product as a result,
|
||||
you may redistribute such product without providing attribution as would
|
||||
otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
|
@ -1,303 +0,0 @@
|
||||
<img src="https://swift.org/assets/images/swift.svg" alt="Swift logo" height="70" >
|
||||
|
||||
# Swift Protobuf
|
||||
|
||||
**Welcome to Swift Protobuf!**
|
||||
|
||||
[Apple's Swift programming language](https://swift.org/) is a perfect
|
||||
complement to [Google's Protocol
|
||||
Buffer](https://developers.google.com/protocol-buffers/) ("protobuf") serialization
|
||||
technology.
|
||||
They both emphasize high performance and programmer safety.
|
||||
|
||||
This project provides both the command-line program that adds Swift
|
||||
code generation to Google's `protoc` and the runtime library that is
|
||||
necessary for using the generated code.
|
||||
After using the protoc plugin to generate Swift code from your .proto
|
||||
files, you will need to add this library to your project.
|
||||
|
||||
[](https://github.com/apple/swift-protobuf/actions?query=workflow%3A%22Build+and+Test%22)
|
||||
[](https://github.com/apple/swift-protobuf/actions?query=workflow%3A%22Check+Upstream+Proto+Files%22)
|
||||
[](https://github.com/apple/swift-protobuf/actions?query=workflow%3A%22Run+Conformance+Tests%22)
|
||||
|
||||
# Features of SwiftProtobuf
|
||||
|
||||
SwiftProtobuf offers many advantages over alternative serialization
|
||||
systems:
|
||||
|
||||
* Safety: The protobuf code-generation system avoids the
|
||||
errors that are common with hand-built serialization code.
|
||||
* Correctness: SwiftProtobuf passes both its own extensive
|
||||
test suite and Google's full conformance test for protobuf
|
||||
correctness.
|
||||
* Schema-driven: Defining your data structures in a separate
|
||||
`.proto` schema file clearly documents your communications
|
||||
conventions.
|
||||
* Idiomatic: SwiftProtobuf takes full advantage of the Swift language.
|
||||
In particular, all generated types provide full Swift copy-on-write
|
||||
value semantics.
|
||||
* Efficient binary serialization: The `.serializedData()`
|
||||
method returns a `Data` with a compact binary form of your data.
|
||||
You can deserialize the data using the `init(serializedData:)`
|
||||
initializer.
|
||||
* Standard JSON serialization: The `.jsonUTF8Data()` method returns a JSON
|
||||
form of your data that can be parsed with the `init(jsonUTF8Data:)`
|
||||
initializer.
|
||||
* Hashable, Equatable: The generated struct can be put into a
|
||||
`Set<>` or `Dictionary<>`.
|
||||
* Performant: The binary and JSON serializers have been
|
||||
extensively optimized.
|
||||
* Extensible: You can add your own Swift extensions to any
|
||||
of the generated types.
|
||||
|
||||
Best of all, you can take the same `.proto` file and generate
|
||||
Java, C++, Python, or Objective-C for use on other platforms. The
|
||||
generated code for those languages will use the exact same
|
||||
serialization and deserialization conventions as SwiftProtobuf, making
|
||||
it easy to exchange serialized data in binary or JSON forms, with no
|
||||
additional effort on your part.
|
||||
|
||||
# Documentation
|
||||
|
||||
More information is available in the associated documentation:
|
||||
|
||||
* [Google's protobuf documentation](https://developers.google.com/protocol-buffers/)
|
||||
provides general information about protocol buffers, the protoc compiler,
|
||||
and how to use protocol buffers with C++, Java, and other languages.
|
||||
* [PLUGIN.md](Documentation/PLUGIN.md) documents the `protoc-gen-swift`
|
||||
plugin that adds Swift support to the `protoc` program
|
||||
* [API.md](Documentation/API.md) documents how to use the generated code.
|
||||
This is recommended reading for anyone using SwiftProtobuf in their
|
||||
project.
|
||||
* [cocoadocs.org](http://cocoadocs.org/docsets/SwiftProtobuf/) has the generated
|
||||
API documentation
|
||||
* [INTERNALS.md](Documentation/INTERNALS.md) documents the internal structure
|
||||
of the generated code and the library. This
|
||||
should only be needed by folks interested in working on SwiftProtobuf
|
||||
itself.
|
||||
* [STYLE_GUIDELINES.md](Documentation/STYLE_GUIDELINES.md) documents the style
|
||||
guidelines we have adopted in our codebase if you are interested in
|
||||
contributing
|
||||
|
||||
# Getting Started
|
||||
|
||||
If you've worked with Protocol Buffers before, adding Swift support is very
|
||||
simple: you just need to build the `protoc-gen-swift` program and copy it into
|
||||
your PATH.
|
||||
The `protoc` program will find and use it automatically, allowing you
|
||||
to build Swift sources for your proto files.
|
||||
You will also, of course, need to add the SwiftProtobuf runtime library to
|
||||
your project as explained below.
|
||||
|
||||
## System Requirements
|
||||
|
||||
To use Swift with Protocol buffers, you'll need:
|
||||
|
||||
* A Swift 4.2 or later compiler (Xcode 10.0 or later). Support is included
|
||||
for the Swift Package Manager; or using the included Xcode project. The Swift
|
||||
protobuf project is being developed and tested against the latest release
|
||||
version of Swift available from [Swift.org](https://swift.org)
|
||||
|
||||
* Google's protoc compiler. The Swift protoc plugin is being actively
|
||||
developed and tested against the latest protobuf sources.
|
||||
The SwiftProtobuf tests need a version of protoc which supports the
|
||||
`swift_prefix` option (introduced in protoc 3.2.0).
|
||||
It may work with earlier versions of protoc.
|
||||
You can get recent versions from
|
||||
[Google's github repository](https://github.com/protocolbuffers/protobuf).
|
||||
|
||||
## Building and Installing the Code Generator Plugin
|
||||
|
||||
To translate `.proto` files into Swift, you will need both Google's
|
||||
protoc compiler and the SwiftProtobuf code generator plugin.
|
||||
|
||||
Building the plugin should be simple on any supported Swift platform:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/apple/swift-protobuf.git
|
||||
$ cd swift-protobuf
|
||||
```
|
||||
|
||||
Pick what released version of SwiftProtobuf you are going to use. You can get
|
||||
a list of tags with:
|
||||
|
||||
```
|
||||
$ git tag -l
|
||||
```
|
||||
|
||||
Once you pick the version you will use, set your local state to match, and
|
||||
build the protoc plugin:
|
||||
|
||||
```
|
||||
$ git checkout tags/[tag_name]
|
||||
$ swift build -c release
|
||||
```
|
||||
|
||||
This will create a binary called `protoc-gen-swift` in the `.build/release`
|
||||
directory.
|
||||
|
||||
To install, just copy this one executable into a directory that is
|
||||
part of your `PATH` environment variable.
|
||||
|
||||
NOTE: The Swift runtime support is now included with macOS. If you are
|
||||
using old Xcode versions or are on older system versions, you might need
|
||||
to use also use `--static-swift-stdlib` with `swift build`.
|
||||
|
||||
### Alternatively install via Homebrew
|
||||
|
||||
If you prefer using [Homebrew](https://brew.sh):
|
||||
|
||||
```
|
||||
$ brew install swift-protobuf
|
||||
```
|
||||
|
||||
This will install `protoc` compiler and Swift code generator plugin.
|
||||
|
||||
## Converting .proto files into Swift
|
||||
|
||||
To generate Swift output for your .proto files, you run the `protoc` command as
|
||||
usual, using the `--swift_out=<directory>` option:
|
||||
|
||||
```
|
||||
$ protoc --swift_out=. my.proto
|
||||
```
|
||||
|
||||
The `protoc` program will automatically look for `protoc-gen-swift` in your
|
||||
`PATH` and use it.
|
||||
|
||||
Each `.proto` input file will get translated to a corresponding `.pb.swift`
|
||||
file in the output directory.
|
||||
|
||||
More information about building and using `protoc-gen-swift` can be found
|
||||
in the [detailed Plugin documentation](Documentation/PLUGIN.md).
|
||||
|
||||
## Adding the SwiftProtobuf library to your project...
|
||||
|
||||
To use the generated code, you need to include the `SwiftProtobuf` library
|
||||
module in your project. How you do this will vary depending on how
|
||||
you're building your project. Note that in all cases, we strongly recommend
|
||||
that you use the version of the SwiftProtobuf library that corresponds to
|
||||
the version of `protoc-gen-swift` you used to generate the code.
|
||||
|
||||
### ...using `swift build`
|
||||
|
||||
After copying the `.pb.swift` files into your project, you will need to add the
|
||||
[SwiftProtobuf library](https://github.com/apple/swift-protobuf) to your
|
||||
project to support the generated code.
|
||||
If you are using the Swift Package Manager, add a dependency to your
|
||||
`Package.swift` file and import the `SwiftProtobuf` library into the desired
|
||||
targets. Adjust the `"1.6.0"` here to match the `[tag_name]` you used to build
|
||||
the plugin above:
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(name: "SwiftProtobuf", url: "https://github.com/apple/swift-protobuf.git", from: "1.6.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MyTarget", dependencies: ["SwiftProtobuf"]),
|
||||
]
|
||||
```
|
||||
|
||||
### ...using Xcode
|
||||
|
||||
If you are using Xcode, then you should:
|
||||
|
||||
* Add the `.pb.swift` source files generated from your protos directly to your
|
||||
project
|
||||
* Add the appropriate `SwiftProtobuf_<platform>` target from the Xcode project
|
||||
in this package to your project.
|
||||
|
||||
### ...using CocoaPods
|
||||
|
||||
If you're using CocoaPods, add this to your `Podfile` adjusting the `:tag` to
|
||||
match the `[tag_name]` you used to build the plugin above:
|
||||
|
||||
```ruby
|
||||
pod 'SwiftProtobuf', '~> 1.0'
|
||||
```
|
||||
|
||||
And run `pod install`.
|
||||
|
||||
NOTE: CocoaPods 1.7 or newer is required.
|
||||
|
||||
### ...using Carthage
|
||||
|
||||
If you're using Carthage, add this to your `Cartfile` but adjust the tag to match the `[tag_name]` you used to build the plugin above:
|
||||
|
||||
```ruby
|
||||
github "apple/swift-protobuf" ~> 1.0
|
||||
```
|
||||
|
||||
Run `carthage update` and drag `SwiftProtobuf.framework` into your Xcode.project.
|
||||
|
||||
# Quick Start
|
||||
|
||||
Once you have installed the code generator, used it to
|
||||
generate Swift code from your `.proto` file, and
|
||||
added the SwiftProtobuf library to your project, you can
|
||||
just use the generated types as you would any other Swift
|
||||
struct.
|
||||
|
||||
For example, you might start with the following very simple
|
||||
proto file:
|
||||
```protobuf
|
||||
syntax = "proto3";
|
||||
|
||||
message BookInfo {
|
||||
int64 id = 1;
|
||||
string title = 2;
|
||||
string author = 3;
|
||||
}
|
||||
```
|
||||
|
||||
Then generate Swift code using:
|
||||
```
|
||||
$ protoc --swift_out=. DataModel.proto
|
||||
```
|
||||
|
||||
The generated code will expose a Swift property for
|
||||
each of the proto fields as well as a selection
|
||||
of serialization and deserialization capabilities:
|
||||
```swift
|
||||
// Create a BookInfo object and populate it:
|
||||
var info = BookInfo()
|
||||
info.id = 1734
|
||||
info.title = "Really Interesting Book"
|
||||
info.author = "Jane Smith"
|
||||
|
||||
// As above, but generating a read-only value:
|
||||
let info2 = BookInfo.with {
|
||||
$0.id = 1735
|
||||
$0.title = "Even More Interesting"
|
||||
$0.author = "Jane Q. Smith"
|
||||
}
|
||||
|
||||
// Serialize to binary protobuf format:
|
||||
let binaryData: Data = try info.serializedData()
|
||||
|
||||
// Deserialize a received Data object from `binaryData`
|
||||
let decodedInfo = try BookInfo(serializedData: binaryData)
|
||||
|
||||
// Serialize to JSON format as a Data object
|
||||
let jsonData: Data = try info.jsonUTF8Data()
|
||||
|
||||
// Deserialize from JSON format from `jsonData`
|
||||
let receivedFromJSON = try BookInfo(jsonUTF8Data: jsonData)
|
||||
```
|
||||
|
||||
You can find more information in the detailed
|
||||
[API Documentation](Documentation/API.md).
|
||||
|
||||
## Report any issues
|
||||
|
||||
If you run into problems, please send us a detailed report.
|
||||
At a minimum, please include:
|
||||
|
||||
* The specific operating system and version (for example, "macOS 10.12.1" or
|
||||
"Ubuntu 16.10")
|
||||
* The version of Swift you have installed (from `swift --version`)
|
||||
* The version of the protoc compiler you are working with from
|
||||
`protoc --version`
|
||||
* The specific version of this source code (you can use `git log -1` to get the
|
||||
latest commit ID)
|
||||
* Any local changes you may have
|
@ -1,476 +0,0 @@
|
||||
// Sources/SwiftProtobuf/AnyMessageStorage.swift - Custom stroage for Any WKT
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Hand written storage class for Google_Protobuf_Any to support on demand
|
||||
/// transforms between the formats.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
#if !swift(>=4.2)
|
||||
private let i_2166136261 = Int(bitPattern: 2166136261)
|
||||
private let i_16777619 = Int(16777619)
|
||||
#endif
|
||||
|
||||
fileprivate func serializeAnyJSON(
|
||||
for message: Message,
|
||||
typeURL: String,
|
||||
options: JSONEncodingOptions
|
||||
) throws -> String {
|
||||
var visitor = try JSONEncodingVisitor(type: type(of: message), options: options)
|
||||
visitor.startObject(message: message)
|
||||
visitor.encodeField(name: "@type", stringValue: typeURL)
|
||||
if let m = message as? _CustomJSONCodable {
|
||||
let value = try m.encodedJSONString(options: options)
|
||||
visitor.encodeField(name: "value", jsonText: value)
|
||||
} else {
|
||||
try message.traverse(visitor: &visitor)
|
||||
}
|
||||
visitor.endObject()
|
||||
return visitor.stringResult
|
||||
}
|
||||
|
||||
fileprivate func emitVerboseTextForm(visitor: inout TextFormatEncodingVisitor, message: Message, typeURL: String) {
|
||||
let url: String
|
||||
if typeURL.isEmpty {
|
||||
url = buildTypeURL(forMessage: message, typePrefix: defaultAnyTypeURLPrefix)
|
||||
} else {
|
||||
url = typeURL
|
||||
}
|
||||
visitor.visitAnyVerbose(value: message, typeURL: url)
|
||||
}
|
||||
|
||||
fileprivate func asJSONObject(body: Data) -> Data {
|
||||
let asciiOpenCurlyBracket = UInt8(ascii: "{")
|
||||
let asciiCloseCurlyBracket = UInt8(ascii: "}")
|
||||
var result = Data([asciiOpenCurlyBracket])
|
||||
result.append(body)
|
||||
result.append(asciiCloseCurlyBracket)
|
||||
return result
|
||||
}
|
||||
|
||||
fileprivate func unpack(contentJSON: Data,
|
||||
extensions: ExtensionMap,
|
||||
options: JSONDecodingOptions,
|
||||
as messageType: Message.Type) throws -> Message {
|
||||
guard messageType is _CustomJSONCodable.Type else {
|
||||
let contentJSONAsObject = asJSONObject(body: contentJSON)
|
||||
return try messageType.init(jsonUTF8Data: contentJSONAsObject, extensions: extensions, options: options)
|
||||
}
|
||||
|
||||
var value = String()
|
||||
try contentJSON.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
|
||||
if body.count > 0 {
|
||||
var scanner = JSONScanner(source: body, options: options, extensions: extensions)
|
||||
let key = try scanner.nextQuotedString()
|
||||
if key != "value" {
|
||||
// The only thing within a WKT should be "value".
|
||||
throw AnyUnpackError.malformedWellKnownTypeJSON
|
||||
}
|
||||
try scanner.skipRequiredColon() // Can't fail
|
||||
value = try scanner.skip()
|
||||
if !scanner.complete {
|
||||
// If that wasn't the end, then there was another key,
|
||||
// and WKTs should only have the one.
|
||||
throw AnyUnpackError.malformedWellKnownTypeJSON
|
||||
}
|
||||
}
|
||||
}
|
||||
return try messageType.init(jsonString: value, extensions: extensions, options: options)
|
||||
}
|
||||
|
||||
internal class AnyMessageStorage {
|
||||
// The two properties generated Google_Protobuf_Any will reference.
|
||||
var _typeURL = String()
|
||||
var _value: Data {
|
||||
// Remapped to the internal `state`.
|
||||
get {
|
||||
switch state {
|
||||
case .binary(let value):
|
||||
return value
|
||||
case .message(let message):
|
||||
do {
|
||||
return try message.serializedData(partial: true)
|
||||
} catch {
|
||||
return Data()
|
||||
}
|
||||
case .contentJSON(let contentJSON, let options):
|
||||
guard let messageType = Google_Protobuf_Any.messageType(forTypeURL: _typeURL) else {
|
||||
return Data()
|
||||
}
|
||||
do {
|
||||
let m = try unpack(contentJSON: contentJSON,
|
||||
extensions: SimpleExtensionMap(),
|
||||
options: options,
|
||||
as: messageType)
|
||||
return try m.serializedData(partial: true)
|
||||
} catch {
|
||||
return Data()
|
||||
}
|
||||
}
|
||||
}
|
||||
set {
|
||||
state = .binary(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
enum InternalState {
|
||||
// a serialized binary
|
||||
// Note: Unlike contentJSON below, binary does not bother to capture the
|
||||
// decoding options. This is because the actual binary format is the binary
|
||||
// blob, i.e. - when decoding from binary, the spec doesn't include decoding
|
||||
// the binary blob, it is pass through. Instead there is a public api for
|
||||
// unpacking that takes new options when a developer decides to decode it.
|
||||
case binary(Data)
|
||||
// a message
|
||||
case message(Message)
|
||||
// parsed JSON with the @type removed and the decoding options.
|
||||
case contentJSON(Data, JSONDecodingOptions)
|
||||
}
|
||||
var state: InternalState = .binary(Data())
|
||||
|
||||
static let defaultInstance = AnyMessageStorage()
|
||||
|
||||
private init() {}
|
||||
|
||||
init(copying source: AnyMessageStorage) {
|
||||
_typeURL = source._typeURL
|
||||
state = source.state
|
||||
}
|
||||
|
||||
func isA<M: Message>(_ type: M.Type) -> Bool {
|
||||
if _typeURL.isEmpty {
|
||||
return false
|
||||
}
|
||||
let encodedType = typeName(fromURL: _typeURL)
|
||||
return encodedType == M.protoMessageName
|
||||
}
|
||||
|
||||
// This is only ever called with the expectation that target will be fully
|
||||
// replaced during the unpacking and never as a merge.
|
||||
func unpackTo<M: Message>(
|
||||
target: inout M,
|
||||
extensions: ExtensionMap?,
|
||||
options: BinaryDecodingOptions
|
||||
) throws {
|
||||
guard isA(M.self) else {
|
||||
throw AnyUnpackError.typeMismatch
|
||||
}
|
||||
|
||||
switch state {
|
||||
case .binary(let data):
|
||||
target = try M(serializedData: data, extensions: extensions, partial: true, options: options)
|
||||
|
||||
case .message(let msg):
|
||||
if let message = msg as? M {
|
||||
// Already right type, copy it over.
|
||||
target = message
|
||||
} else {
|
||||
// Different type, serialize and parse.
|
||||
let data = try msg.serializedData(partial: true)
|
||||
target = try M(serializedData: data, extensions: extensions, partial: true)
|
||||
}
|
||||
|
||||
case .contentJSON(let contentJSON, let options):
|
||||
target = try unpack(contentJSON: contentJSON,
|
||||
extensions: extensions ?? SimpleExtensionMap(),
|
||||
options: options,
|
||||
as: M.self) as! M
|
||||
}
|
||||
}
|
||||
|
||||
// Called before the message is traversed to do any error preflights.
|
||||
// Since traverse() will use _value, this is our chance to throw
|
||||
// when _value can't.
|
||||
func preTraverse() throws {
|
||||
switch state {
|
||||
case .binary:
|
||||
// Nothing to be checked.
|
||||
break
|
||||
|
||||
case .message:
|
||||
// When set from a developer provided message, partial support
|
||||
// is done. Any message that comes in from another format isn't
|
||||
// checked, and transcoding the isInitialized requirement is
|
||||
// never inserted.
|
||||
break
|
||||
|
||||
case .contentJSON(let contentJSON, let options):
|
||||
// contentJSON requires we have the type available for decoding
|
||||
guard let messageType = Google_Protobuf_Any.messageType(forTypeURL: _typeURL) else {
|
||||
throw BinaryEncodingError.anyTranscodeFailure
|
||||
}
|
||||
do {
|
||||
// Decodes the full JSON and then discard the result.
|
||||
// The regular traversal will decode this again by querying the
|
||||
// `value` field, but that has no way to fail. As a result,
|
||||
// we need this to accurately handle decode errors.
|
||||
_ = try unpack(contentJSON: contentJSON,
|
||||
extensions: SimpleExtensionMap(),
|
||||
options: options,
|
||||
as: messageType)
|
||||
} catch {
|
||||
throw BinaryEncodingError.anyTranscodeFailure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom handling for Text format.
|
||||
extension AnyMessageStorage {
|
||||
func decodeTextFormat(typeURL url: String, decoder: inout TextFormatDecoder) throws {
|
||||
// Decoding the verbose form requires knowing the type.
|
||||
_typeURL = url
|
||||
guard let messageType = Google_Protobuf_Any.messageType(forTypeURL: url) else {
|
||||
// The type wasn't registered, can't parse it.
|
||||
throw TextFormatDecodingError.malformedText
|
||||
}
|
||||
let terminator = try decoder.scanner.skipObjectStart()
|
||||
var subDecoder = try TextFormatDecoder(messageType: messageType, scanner: decoder.scanner, terminator: terminator)
|
||||
if messageType == Google_Protobuf_Any.self {
|
||||
var any = Google_Protobuf_Any()
|
||||
try any.decodeTextFormat(decoder: &subDecoder)
|
||||
state = .message(any)
|
||||
} else {
|
||||
var m = messageType.init()
|
||||
try m.decodeMessage(decoder: &subDecoder)
|
||||
state = .message(m)
|
||||
}
|
||||
decoder.scanner = subDecoder.scanner
|
||||
if try decoder.nextFieldNumber() != nil {
|
||||
// Verbose any can never have additional keys.
|
||||
throw TextFormatDecodingError.malformedText
|
||||
}
|
||||
}
|
||||
|
||||
// Specialized traverse for writing out a Text form of the Any.
|
||||
// This prefers the more-legible "verbose" format if it can
|
||||
// use it, otherwise will fall back to simpler forms.
|
||||
internal func textTraverse(visitor: inout TextFormatEncodingVisitor) {
|
||||
switch state {
|
||||
case .binary(let valueData):
|
||||
if let messageType = Google_Protobuf_Any.messageType(forTypeURL: _typeURL) {
|
||||
// If we can decode it, we can write the readable verbose form:
|
||||
do {
|
||||
let m = try messageType.init(serializedData: valueData, partial: true)
|
||||
emitVerboseTextForm(visitor: &visitor, message: m, typeURL: _typeURL)
|
||||
return
|
||||
} catch {
|
||||
// Fall through to just print the type and raw binary data
|
||||
}
|
||||
}
|
||||
if !_typeURL.isEmpty {
|
||||
try! visitor.visitSingularStringField(value: _typeURL, fieldNumber: 1)
|
||||
}
|
||||
if !valueData.isEmpty {
|
||||
try! visitor.visitSingularBytesField(value: valueData, fieldNumber: 2)
|
||||
}
|
||||
|
||||
case .message(let msg):
|
||||
emitVerboseTextForm(visitor: &visitor, message: msg, typeURL: _typeURL)
|
||||
|
||||
case .contentJSON(let contentJSON, let options):
|
||||
// If we can decode it, we can write the readable verbose form:
|
||||
if let messageType = Google_Protobuf_Any.messageType(forTypeURL: _typeURL) {
|
||||
do {
|
||||
let m = try unpack(contentJSON: contentJSON,
|
||||
extensions: SimpleExtensionMap(),
|
||||
options: options,
|
||||
as: messageType)
|
||||
emitVerboseTextForm(visitor: &visitor, message: m, typeURL: _typeURL)
|
||||
return
|
||||
} catch {
|
||||
// Fall through to just print the raw JSON data
|
||||
}
|
||||
}
|
||||
if !_typeURL.isEmpty {
|
||||
try! visitor.visitSingularStringField(value: _typeURL, fieldNumber: 1)
|
||||
}
|
||||
// Build a readable form of the JSON:
|
||||
let contentJSONAsObject = asJSONObject(body: contentJSON)
|
||||
visitor.visitAnyJSONDataField(value: contentJSONAsObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The obvious goal for Hashable/Equatable conformance would be for
|
||||
/// hash and equality to behave as if we always decoded the inner
|
||||
/// object and hashed or compared that. Unfortunately, Any typically
|
||||
/// stores serialized contents and we don't always have the ability to
|
||||
/// deserialize it. Since none of our supported serializations are
|
||||
/// fully deterministic, we can't even ensure that equality will
|
||||
/// behave this way when the Any contents are in the same
|
||||
/// serialization.
|
||||
///
|
||||
/// As a result, we can only really perform a "best effort" equality
|
||||
/// test. Of course, regardless of the above, we must guarantee that
|
||||
/// hashValue is compatible with equality.
|
||||
extension AnyMessageStorage {
|
||||
#if swift(>=4.2)
|
||||
// Can't use _valueData for a few reasons:
|
||||
// 1. Since decode is done on demand, two objects could be equal
|
||||
// but created differently (one from JSON, one for Message, etc.),
|
||||
// and the hash values have to be equal even if we don't have data
|
||||
// yet.
|
||||
// 2. map<> serialization order is undefined. At the time of writing
|
||||
// the Swift, Objective-C, and Go runtimes all tend to have random
|
||||
// orders, so the messages could be identical, but in binary form
|
||||
// they could differ.
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
if !_typeURL.isEmpty {
|
||||
hasher.combine(_typeURL)
|
||||
}
|
||||
}
|
||||
#else // swift(>=4.2)
|
||||
var hashValue: Int {
|
||||
var hash: Int = i_2166136261
|
||||
if !_typeURL.isEmpty {
|
||||
hash = (hash &* i_16777619) ^ _typeURL.hashValue
|
||||
}
|
||||
return hash
|
||||
}
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
func isEqualTo(other: AnyMessageStorage) -> Bool {
|
||||
if (_typeURL != other._typeURL) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Since the library does lazy Any decode, equality is a very hard problem.
|
||||
// It things exactly match, that's pretty easy, otherwise, one ends up having
|
||||
// to error on saying they aren't equal.
|
||||
//
|
||||
// The best option would be to have Message forms and compare those, as that
|
||||
// removes issues like map<> serialization order, some other protocol buffer
|
||||
// implementation details/bugs around serialized form order, etc.; but that
|
||||
// would also greatly slow down equality tests.
|
||||
//
|
||||
// Do our best to compare what is present have...
|
||||
|
||||
// If both have messages, check if they are the same.
|
||||
if case .message(let myMsg) = state, case .message(let otherMsg) = other.state, type(of: myMsg) == type(of: otherMsg) {
|
||||
// Since the messages are known to be same type, we can claim both equal and
|
||||
// not equal based on the equality comparison.
|
||||
return myMsg.isEqualTo(message: otherMsg)
|
||||
}
|
||||
|
||||
// If both have serialized data, and they exactly match; the messages are equal.
|
||||
// Because there could be map in the message, the fact that the data isn't the
|
||||
// same doesn't always mean the messages aren't equal. Likewise, the binary could
|
||||
// have been created by a library that doesn't order the fields, or the binary was
|
||||
// created using the appending ability in of the binary format.
|
||||
if case .binary(let myValue) = state, case .binary(let otherValue) = other.state, myValue == otherValue {
|
||||
return true
|
||||
}
|
||||
|
||||
// If both have contentJSON, and they exactly match; the messages are equal.
|
||||
// Because there could be map in the message (or the JSON could just be in a different
|
||||
// order), the fact that the JSON isn't the same doesn't always mean the messages
|
||||
// aren't equal.
|
||||
if case .contentJSON(let myJSON, _) = state,
|
||||
case .contentJSON(let otherJSON, _) = other.state,
|
||||
myJSON == otherJSON {
|
||||
return true
|
||||
}
|
||||
|
||||
// Out of options. To do more compares, the states conversions would have to be
|
||||
// done to do comparisions; and since equality can be used somewhat removed from
|
||||
// a developer (if they put protos in a Set, use them as keys to a Dictionary, etc),
|
||||
// the conversion cost might be to high for those uses. Give up and say they aren't equal.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// _CustomJSONCodable support for Google_Protobuf_Any
|
||||
extension AnyMessageStorage {
|
||||
// Override the traversal-based JSON encoding
|
||||
// This builds an Any JSON representation from one of:
|
||||
// * The message we were initialized with,
|
||||
// * The JSON fields we last deserialized, or
|
||||
// * The protobuf field we were deserialized from.
|
||||
// The last case requires locating the type, deserializing
|
||||
// into an object, then reserializing back to JSON.
|
||||
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
|
||||
switch state {
|
||||
case .binary(let valueData):
|
||||
// Transcode by decoding the binary data to a message object
|
||||
// and then recode back into JSON.
|
||||
guard let messageType = Google_Protobuf_Any.messageType(forTypeURL: _typeURL) else {
|
||||
// If we don't have the type available, we can't decode the
|
||||
// binary value, so we're stuck. (The Google spec does not
|
||||
// provide a way to just package the binary value for someone
|
||||
// else to decode later.)
|
||||
throw JSONEncodingError.anyTranscodeFailure
|
||||
}
|
||||
let m = try messageType.init(serializedData: valueData, partial: true)
|
||||
return try serializeAnyJSON(for: m, typeURL: _typeURL, options: options)
|
||||
|
||||
case .message(let msg):
|
||||
// We should have been initialized with a typeURL, but
|
||||
// ensure it wasn't cleared.
|
||||
let url = !_typeURL.isEmpty ? _typeURL : buildTypeURL(forMessage: msg, typePrefix: defaultAnyTypeURLPrefix)
|
||||
return try serializeAnyJSON(for: msg, typeURL: url, options: options)
|
||||
|
||||
case .contentJSON(let contentJSON, _):
|
||||
var jsonEncoder = JSONEncoder()
|
||||
jsonEncoder.startObject()
|
||||
jsonEncoder.startField(name: "@type")
|
||||
jsonEncoder.putStringValue(value: _typeURL)
|
||||
if !contentJSON.isEmpty {
|
||||
jsonEncoder.append(staticText: ",")
|
||||
// NOTE: This doesn't really take `options` into account since it is
|
||||
// just reflecting out what was taken in originally.
|
||||
jsonEncoder.append(utf8Data: contentJSON)
|
||||
}
|
||||
jsonEncoder.endObject()
|
||||
return jsonEncoder.stringResult
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: If the type is well-known or has already been registered,
|
||||
// we should consider decoding eagerly. Eager decoding would
|
||||
// catch certain errors earlier (good) but would probably be
|
||||
// a performance hit if the Any contents were never accessed (bad).
|
||||
// Of course, we can't always decode eagerly (we don't always have the
|
||||
// message type available), so the deferred logic here is still needed.
|
||||
func decodeJSON(from decoder: inout JSONDecoder) throws {
|
||||
try decoder.scanner.skipRequiredObjectStart()
|
||||
// Reset state
|
||||
_typeURL = String()
|
||||
state = .binary(Data())
|
||||
if decoder.scanner.skipOptionalObjectEnd() {
|
||||
return
|
||||
}
|
||||
|
||||
var jsonEncoder = JSONEncoder()
|
||||
while true {
|
||||
let key = try decoder.scanner.nextQuotedString()
|
||||
try decoder.scanner.skipRequiredColon()
|
||||
if key == "@type" {
|
||||
_typeURL = try decoder.scanner.nextQuotedString()
|
||||
} else {
|
||||
jsonEncoder.startField(name: key)
|
||||
let keyValueJSON = try decoder.scanner.skip()
|
||||
jsonEncoder.append(text: keyValueJSON)
|
||||
}
|
||||
if decoder.scanner.skipOptionalObjectEnd() {
|
||||
// Capture the options, but set the messageDepthLimit to be what
|
||||
// was left right now, as that is the limit when the JSON is finally
|
||||
// parsed.
|
||||
var updatedOptions = decoder.options
|
||||
updatedOptions.messageDepthLimit = decoder.scanner.recursionBudget
|
||||
state = .contentJSON(jsonEncoder.dataResult, updatedOptions)
|
||||
return
|
||||
}
|
||||
try decoder.scanner.skipRequiredComma()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// Sources/SwiftProtobuf/AnyUnpackError.swift - Any Unpacking Errors
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Errors that can be throw when unpacking a Google_Protobuf_Any.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Describes errors that can occur when unpacking an `Google_Protobuf_Any`
|
||||
/// message.
|
||||
///
|
||||
/// `Google_Protobuf_Any` messages can be decoded from protobuf binary, text
|
||||
/// format, or JSON. The contents are not parsed immediately; the raw data is
|
||||
/// held in the `Google_Protobuf_Any` message until you `unpack()` it into a
|
||||
/// message. At this time, any error can occur that might have occurred from a
|
||||
/// regular decoding operation. There are also other errors that can occur due
|
||||
/// to problems with the `Any` value's structure.
|
||||
public enum AnyUnpackError: Error {
|
||||
/// The `type_url` field in the `Google_Protobuf_Any` message did not match
|
||||
/// the message type provided to the `unpack()` method.
|
||||
case typeMismatch
|
||||
|
||||
/// Well-known types being decoded from JSON must have only two fields: the
|
||||
/// `@type` field and a `value` field containing the specialized JSON coding
|
||||
/// of the well-known type.
|
||||
case malformedWellKnownTypeJSON
|
||||
|
||||
/// The `Google_Protobuf_Any` message was malformed in some other way not
|
||||
/// covered by the other error cases.
|
||||
case malformedAnyField
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryDecodingError.swift - Protobuf binary decoding errors
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Protobuf binary format decoding errors
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Describes errors that can occur when decoding a message from binary format.
|
||||
public enum BinaryDecodingError: Error {
|
||||
/// Extraneous data remained after decoding should have been complete.
|
||||
case trailingGarbage
|
||||
|
||||
/// The decoder unexpectedly reached the end of the data before it was
|
||||
/// expected.
|
||||
case truncated
|
||||
|
||||
/// A string field was not encoded as valid UTF-8.
|
||||
case invalidUTF8
|
||||
|
||||
/// The binary data was malformed in some way, such as an invalid wire format
|
||||
/// or field tag.
|
||||
case malformedProtobuf
|
||||
|
||||
/// The definition of the message or one of its nested messages has required
|
||||
/// fields but the binary data did not include values for them. You must pass
|
||||
/// `partial: true` during decoding if you wish to explicitly ignore missing
|
||||
/// required fields.
|
||||
case missingRequiredFields
|
||||
|
||||
/// An internal error happened while decoding. If this is ever encountered,
|
||||
/// please file an issue with SwiftProtobuf with as much details as possible
|
||||
/// for what happened (proto definitions, bytes being decoded (if possible)).
|
||||
case internalExtensionError
|
||||
|
||||
/// Reached the nesting limit for messages within messages while decoding.
|
||||
case messageDepthLimit
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryDecodingOptions.swift - Binary decoding options
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Binary decoding options
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Options for JSONDecoding.
|
||||
public struct BinaryDecodingOptions {
|
||||
/// The maximum nesting of message with messages. The default is 100.
|
||||
///
|
||||
/// To prevent corrupt or malicious messages from causing stack overflows,
|
||||
/// this controls how deep messages can be nested within other messages
|
||||
/// while parsing.
|
||||
public var messageDepthLimit: Int = 100
|
||||
|
||||
/// Discard unknown fields while parsing. The default is false, so parsering
|
||||
/// does not discard unknown fields.
|
||||
///
|
||||
/// The Protobuf binary format allows unknown fields to be still parsed
|
||||
/// so the schema can be expanded without requiring all readers to be updated.
|
||||
/// This works in part by haivng any unknown fields preserved so they can
|
||||
/// be relayed on without loss. For a while the proto3 syntax definition
|
||||
/// called for unknown fields to be dropped, but that lead to problems in
|
||||
/// some case. The default is to follow the spec and keep them, but setting
|
||||
/// this option to `true` allows a developer to strip them during a parse
|
||||
/// in case they have a specific need to drop the unknown fields from the
|
||||
/// object graph being created.
|
||||
public var discardUnknownFields: Bool = false
|
||||
|
||||
public init() {}
|
||||
}
|
@ -1,232 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryDelimited.swift - Delimited support
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Helpers to read/write message with a length prefix.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Helper methods for reading/writing messages with a length prefix.
|
||||
public enum BinaryDelimited {
|
||||
/// Additional errors for delimited message handing.
|
||||
public enum Error: Swift.Error {
|
||||
/// If a read/write to the stream fails, but the stream's `streamError` is nil,
|
||||
/// this error will be throw instead since the stream didn't provide anything
|
||||
/// more specific. A common cause for this can be failing to open the stream
|
||||
/// before trying to read/write to it.
|
||||
case unknownStreamError
|
||||
|
||||
/// While reading/writing to the stream, less than the expected bytes was
|
||||
/// read/written.
|
||||
case truncated
|
||||
}
|
||||
|
||||
/// Serialize a single size-delimited message from the given stream. Delimited
|
||||
/// format allows a single file or stream to contain multiple messages,
|
||||
/// whereas normally writing multiple non-delimited messages to the same
|
||||
/// stream would cause them to be merged. A delimited message is a varint
|
||||
/// encoding the message size followed by a message of exactly that size.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - message: The message to be written.
|
||||
/// - to: The `OutputStream` to write the message to. The stream is
|
||||
/// is assumed to be ready to be written to.
|
||||
/// - partial: If `false` (the default), this method will check
|
||||
/// `Message.isInitialized` before encoding to verify that all required
|
||||
/// fields are present. If any are missing, this method throws
|
||||
/// `BinaryEncodingError.missingRequiredFields`.
|
||||
/// - Throws: `BinaryEncodingError` if encoding fails, throws
|
||||
/// `BinaryDelimited.Error` for some writing errors, or the
|
||||
/// underlying `OutputStream.streamError` for a stream error.
|
||||
public static func serialize(
|
||||
message: Message,
|
||||
to stream: OutputStream,
|
||||
partial: Bool = false
|
||||
) throws {
|
||||
// TODO: Revisit to avoid the extra buffering when encoding is streamed in general.
|
||||
let serialized = try message.serializedData(partial: partial)
|
||||
let totalSize = Varint.encodedSize(of: UInt64(serialized.count)) + serialized.count
|
||||
var data = Data(count: totalSize)
|
||||
data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
|
||||
if let baseAddress = body.baseAddress, body.count > 0 {
|
||||
var encoder = BinaryEncoder(forWritingInto: baseAddress)
|
||||
encoder.putBytesValue(value: serialized)
|
||||
}
|
||||
}
|
||||
|
||||
var written: Int = 0
|
||||
data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
|
||||
if let baseAddress = body.baseAddress, body.count > 0 {
|
||||
// This assumingMemoryBound is technically unsafe, but without SR-11078
|
||||
// (https://bugs.swift.org/browse/SR-11087) we don't have another option.
|
||||
// It should be "safe enough".
|
||||
let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
|
||||
written = stream.write(pointer, maxLength: totalSize)
|
||||
}
|
||||
}
|
||||
|
||||
if written != totalSize {
|
||||
if written == -1 {
|
||||
if let streamError = stream.streamError {
|
||||
throw streamError
|
||||
}
|
||||
throw BinaryDelimited.Error.unknownStreamError
|
||||
}
|
||||
throw BinaryDelimited.Error.truncated
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a single size-delimited message from the given stream. Delimited
|
||||
/// format allows a single file or stream to contain multiple messages,
|
||||
/// whereas normally parsing consumes the entire input. A delimited message
|
||||
/// is a varint encoding the message size followed by a message of exactly
|
||||
/// exactly that size.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - messageType: The type of message to read.
|
||||
/// - from: The `InputStream` to read the data from. The stream is assumed
|
||||
/// to be ready to read from.
|
||||
/// - extensions: An `ExtensionMap` used to look up and decode any
|
||||
/// extensions in this message or messages nested within this message's
|
||||
/// fields.
|
||||
/// - partial: If `false` (the default), this method will check
|
||||
/// `Message.isInitialized` before encoding to verify that all required
|
||||
/// fields are present. If any are missing, this method throws
|
||||
/// `BinaryEncodingError.missingRequiredFields`.
|
||||
/// - options: The BinaryDecodingOptions to use.
|
||||
/// - Returns: The message read.
|
||||
/// - Throws: `BinaryDecodingError` if decoding fails, throws
|
||||
/// `BinaryDelimited.Error` for some reading errors, and the
|
||||
/// underlying InputStream.streamError for a stream error.
|
||||
public static func parse<M: Message>(
|
||||
messageType: M.Type,
|
||||
from stream: InputStream,
|
||||
extensions: ExtensionMap? = nil,
|
||||
partial: Bool = false,
|
||||
options: BinaryDecodingOptions = BinaryDecodingOptions()
|
||||
) throws -> M {
|
||||
var message = M()
|
||||
try merge(into: &message,
|
||||
from: stream,
|
||||
extensions: extensions,
|
||||
partial: partial,
|
||||
options: options)
|
||||
return message
|
||||
}
|
||||
|
||||
/// Updates the message by reading a single size-delimited message from
|
||||
/// the given stream. Delimited format allows a single file or stream to
|
||||
/// contain multiple messages, whereas normally parsing consumes the entire
|
||||
/// input. A delimited message is a varint encoding the message size
|
||||
/// followed by a message of exactly that size.
|
||||
///
|
||||
/// - Note: If this method throws an error, the message may still have been
|
||||
/// partially mutated by the binary data that was decoded before the error
|
||||
/// occurred.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - mergingTo: The message to merge the data into.
|
||||
/// - from: The `InputStream` to read the data from. The stream is assumed
|
||||
/// to be ready to read from.
|
||||
/// - extensions: An `ExtensionMap` used to look up and decode any
|
||||
/// extensions in this message or messages nested within this message's
|
||||
/// fields.
|
||||
/// - partial: If `false` (the default), this method will check
|
||||
/// `Message.isInitialized` before encoding to verify that all required
|
||||
/// fields are present. If any are missing, this method throws
|
||||
/// `BinaryEncodingError.missingRequiredFields`.
|
||||
/// - options: The BinaryDecodingOptions to use.
|
||||
/// - Throws: `BinaryDecodingError` if decoding fails, throws
|
||||
/// `BinaryDelimited.Error` for some reading errors, and the
|
||||
/// underlying InputStream.streamError for a stream error.
|
||||
public static func merge<M: Message>(
|
||||
into message: inout M,
|
||||
from stream: InputStream,
|
||||
extensions: ExtensionMap? = nil,
|
||||
partial: Bool = false,
|
||||
options: BinaryDecodingOptions = BinaryDecodingOptions()
|
||||
) throws {
|
||||
let length = try Int(decodeVarint(stream))
|
||||
if length == 0 {
|
||||
// The message was all defaults, nothing to actually read.
|
||||
return
|
||||
}
|
||||
|
||||
var data = Data(count: length)
|
||||
var bytesRead: Int = 0
|
||||
data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
|
||||
if let baseAddress = body.baseAddress, body.count > 0 {
|
||||
// This assumingMemoryBound is technically unsafe, but without SR-11078
|
||||
// (https://bugs.swift.org/browse/SR-11087) we don't have another option.
|
||||
// It should be "safe enough".
|
||||
let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
|
||||
bytesRead = stream.read(pointer, maxLength: length)
|
||||
}
|
||||
}
|
||||
|
||||
if bytesRead != length {
|
||||
if bytesRead == -1 {
|
||||
if let streamError = stream.streamError {
|
||||
throw streamError
|
||||
}
|
||||
throw BinaryDelimited.Error.unknownStreamError
|
||||
}
|
||||
throw BinaryDelimited.Error.truncated
|
||||
}
|
||||
|
||||
try message.merge(serializedData: data,
|
||||
extensions: extensions,
|
||||
partial: partial,
|
||||
options: options)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should go away when encoding/decoding are more stream based
|
||||
// as that should provide a more direct way to do this. This is basically
|
||||
// a rewrite of BinaryDecoder.decodeVarint().
|
||||
internal func decodeVarint(_ stream: InputStream) throws -> UInt64 {
|
||||
|
||||
// Buffer to reuse within nextByte.
|
||||
let readBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
|
||||
#if swift(>=4.1)
|
||||
defer { readBuffer.deallocate() }
|
||||
#else
|
||||
defer { readBuffer.deallocate(capacity: 1) }
|
||||
#endif
|
||||
|
||||
func nextByte() throws -> UInt8 {
|
||||
let bytesRead = stream.read(readBuffer, maxLength: 1)
|
||||
if bytesRead != 1 {
|
||||
if bytesRead == -1 {
|
||||
if let streamError = stream.streamError {
|
||||
throw streamError
|
||||
}
|
||||
throw BinaryDelimited.Error.unknownStreamError
|
||||
}
|
||||
throw BinaryDelimited.Error.truncated
|
||||
}
|
||||
return readBuffer[0]
|
||||
}
|
||||
|
||||
var value: UInt64 = 0
|
||||
var shift: UInt64 = 0
|
||||
while true {
|
||||
let c = try nextByte()
|
||||
value |= UInt64(c & 0x7f) << shift
|
||||
if c & 0x80 == 0 {
|
||||
return value
|
||||
}
|
||||
shift += 7
|
||||
if shift > 63 {
|
||||
throw BinaryDecodingError.malformedProtobuf
|
||||
}
|
||||
}
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryEncoder.swift - Binary encoding support
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Core support for protobuf binary encoding. Note that this is built
|
||||
/// on the general traversal machinery.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
/*
|
||||
* Encoder for Binary Protocol Buffer format
|
||||
*/
|
||||
internal struct BinaryEncoder {
|
||||
private var pointer: UnsafeMutableRawPointer
|
||||
|
||||
init(forWritingInto pointer: UnsafeMutableRawPointer) {
|
||||
self.pointer = pointer
|
||||
}
|
||||
|
||||
private mutating func append(_ byte: UInt8) {
|
||||
pointer.storeBytes(of: byte, as: UInt8.self)
|
||||
pointer = pointer.advanced(by: 1)
|
||||
}
|
||||
|
||||
private mutating func append(contentsOf data: Data) {
|
||||
data.withUnsafeBytes { dataPointer in
|
||||
if let baseAddress = dataPointer.baseAddress, dataPointer.count > 0 {
|
||||
pointer.copyMemory(from: baseAddress, byteCount: dataPointer.count)
|
||||
pointer = pointer.advanced(by: dataPointer.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private mutating func append(contentsOf bufferPointer: UnsafeRawBufferPointer) -> Int {
|
||||
let count = bufferPointer.count
|
||||
if let baseAddress = bufferPointer.baseAddress, count > 0 {
|
||||
memcpy(pointer, baseAddress, count)
|
||||
}
|
||||
pointer = pointer.advanced(by: count)
|
||||
return count
|
||||
}
|
||||
|
||||
func distance(pointer: UnsafeMutableRawPointer) -> Int {
|
||||
return pointer.distance(to: self.pointer)
|
||||
}
|
||||
|
||||
mutating func appendUnknown(data: Data) {
|
||||
append(contentsOf: data)
|
||||
}
|
||||
|
||||
mutating func startField(fieldNumber: Int, wireFormat: WireFormat) {
|
||||
startField(tag: FieldTag(fieldNumber: fieldNumber, wireFormat: wireFormat))
|
||||
}
|
||||
|
||||
mutating func startField(tag: FieldTag) {
|
||||
putVarInt(value: UInt64(tag.rawValue))
|
||||
}
|
||||
|
||||
mutating func putVarInt(value: UInt64) {
|
||||
var v = value
|
||||
while v > 127 {
|
||||
append(UInt8(v & 0x7f | 0x80))
|
||||
v >>= 7
|
||||
}
|
||||
append(UInt8(v))
|
||||
}
|
||||
|
||||
mutating func putVarInt(value: Int64) {
|
||||
putVarInt(value: UInt64(bitPattern: value))
|
||||
}
|
||||
|
||||
mutating func putVarInt(value: Int) {
|
||||
putVarInt(value: Int64(value))
|
||||
}
|
||||
|
||||
mutating func putZigZagVarInt(value: Int64) {
|
||||
let coded = ZigZag.encoded(value)
|
||||
putVarInt(value: coded)
|
||||
}
|
||||
|
||||
mutating func putBoolValue(value: Bool) {
|
||||
append(value ? 1 : 0)
|
||||
}
|
||||
|
||||
mutating func putFixedUInt64(value: UInt64) {
|
||||
var v = value.littleEndian
|
||||
let n = MemoryLayout<UInt64>.size
|
||||
memcpy(pointer, &v, n)
|
||||
pointer = pointer.advanced(by: n)
|
||||
}
|
||||
|
||||
mutating func putFixedUInt32(value: UInt32) {
|
||||
var v = value.littleEndian
|
||||
let n = MemoryLayout<UInt32>.size
|
||||
memcpy(pointer, &v, n)
|
||||
pointer = pointer.advanced(by: n)
|
||||
}
|
||||
|
||||
mutating func putFloatValue(value: Float) {
|
||||
let n = MemoryLayout<Float>.size
|
||||
var v = value
|
||||
var nativeBytes: UInt32 = 0
|
||||
memcpy(&nativeBytes, &v, n)
|
||||
var littleEndianBytes = nativeBytes.littleEndian
|
||||
memcpy(pointer, &littleEndianBytes, n)
|
||||
pointer = pointer.advanced(by: n)
|
||||
}
|
||||
|
||||
mutating func putDoubleValue(value: Double) {
|
||||
let n = MemoryLayout<Double>.size
|
||||
var v = value
|
||||
var nativeBytes: UInt64 = 0
|
||||
memcpy(&nativeBytes, &v, n)
|
||||
var littleEndianBytes = nativeBytes.littleEndian
|
||||
memcpy(pointer, &littleEndianBytes, n)
|
||||
pointer = pointer.advanced(by: n)
|
||||
}
|
||||
|
||||
// Write a string field, including the leading index/tag value.
|
||||
mutating func putStringValue(value: String) {
|
||||
let utf8 = value.utf8
|
||||
#if swift(>=5.0)
|
||||
// If the String does not support an internal representation in a form
|
||||
// of contiguous storage, body is not called and nil is returned.
|
||||
let isAvailable = utf8.withContiguousStorageIfAvailable { (body: UnsafeBufferPointer<UInt8>) -> Int in
|
||||
putVarInt(value: body.count)
|
||||
return append(contentsOf: UnsafeRawBufferPointer(body))
|
||||
}
|
||||
#else
|
||||
let isAvailable: Int? = nil
|
||||
#endif
|
||||
if isAvailable == nil {
|
||||
let count = utf8.count
|
||||
putVarInt(value: count)
|
||||
for b in utf8 {
|
||||
pointer.storeBytes(of: b, as: UInt8.self)
|
||||
pointer = pointer.advanced(by: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func putBytesValue(value: Data) {
|
||||
putVarInt(value: value.count)
|
||||
append(contentsOf: value)
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryEncodingError.swift - Error constants
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Enum constants that identify the particular error.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Describes errors that can occur when decoding a message from binary format.
|
||||
public enum BinaryEncodingError: Error {
|
||||
/// `Any` fields that were decoded from JSON cannot be re-encoded to binary
|
||||
/// unless the object they hold is a well-known type or a type registered via
|
||||
/// `Google_Protobuf_Any.register()`.
|
||||
case anyTranscodeFailure
|
||||
|
||||
/// The definition of the message or one of its nested messages has required
|
||||
/// fields but the message being encoded did not include values for them. You
|
||||
/// must pass `partial: true` during encoding if you wish to explicitly ignore
|
||||
/// missing required fields.
|
||||
case missingRequiredFields
|
||||
}
|
@ -1,473 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryEncodingSizeVisitor.swift - Binary size calculation support
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Visitor used during binary encoding that precalcuates the size of a
|
||||
/// serialized message.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Visitor that calculates the binary-encoded size of a message so that a
|
||||
/// properly sized `Data` or `UInt8` array can be pre-allocated before
|
||||
/// serialization.
|
||||
internal struct BinaryEncodingSizeVisitor: Visitor {
|
||||
|
||||
/// Accumulates the required size of the message during traversal.
|
||||
var serializedSize: Int = 0
|
||||
|
||||
init() {}
|
||||
|
||||
mutating func visitUnknown(bytes: Data) throws {
|
||||
serializedSize += bytes.count
|
||||
}
|
||||
|
||||
mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed32).encodedSize
|
||||
serializedSize += tagSize + MemoryLayout<Float>.size
|
||||
}
|
||||
|
||||
mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed64).encodedSize
|
||||
serializedSize += tagSize + MemoryLayout<Double>.size
|
||||
}
|
||||
|
||||
mutating func visitSingularInt32Field(value: Int32, fieldNumber: Int) throws {
|
||||
try visitSingularInt64Field(value: Int64(value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize + Varint.encodedSize(of: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularUInt32Field(value: UInt32, fieldNumber: Int) throws {
|
||||
try visitSingularUInt64Field(value: UInt64(value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize + Varint.encodedSize(of: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularSInt32Field(value: Int32, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize + Varint.encodedSize(of: ZigZag.encoded(value))
|
||||
}
|
||||
|
||||
mutating func visitSingularSInt64Field(value: Int64, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize + Varint.encodedSize(of: ZigZag.encoded(value))
|
||||
}
|
||||
|
||||
mutating func visitSingularFixed32Field(value: UInt32, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed32).encodedSize
|
||||
serializedSize += tagSize + MemoryLayout<UInt32>.size
|
||||
}
|
||||
|
||||
mutating func visitSingularFixed64Field(value: UInt64, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed64).encodedSize
|
||||
serializedSize += tagSize + MemoryLayout<UInt64>.size
|
||||
}
|
||||
|
||||
mutating func visitSingularSFixed32Field(value: Int32, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed32).encodedSize
|
||||
serializedSize += tagSize + MemoryLayout<Int32>.size
|
||||
}
|
||||
|
||||
mutating func visitSingularSFixed64Field(value: Int64, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed64).encodedSize
|
||||
serializedSize += tagSize + MemoryLayout<Int64>.size
|
||||
}
|
||||
|
||||
mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize + 1
|
||||
}
|
||||
|
||||
mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let count = value.utf8.count
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(count)) + count
|
||||
}
|
||||
|
||||
mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let count = value.count
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(count)) + count
|
||||
}
|
||||
|
||||
// The default impls for visitRepeated*Field would work, but by implementing
|
||||
// these directly, the calculation for the tag overhead can be optimized and
|
||||
// the fixed width fields can be simple multiplication.
|
||||
|
||||
mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed32).encodedSize
|
||||
serializedSize += tagSize * value.count + MemoryLayout<Float>.size * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed64).encodedSize
|
||||
serializedSize += tagSize * value.count + MemoryLayout<Double>.size * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: ZigZag.encoded($1)) }
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: ZigZag.encoded($1)) }
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed32).encodedSize
|
||||
serializedSize += tagSize * value.count + MemoryLayout<UInt32>.size * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed64).encodedSize
|
||||
serializedSize += tagSize * value.count + MemoryLayout<UInt64>.size * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed32).encodedSize
|
||||
serializedSize += tagSize * value.count + MemoryLayout<Int32>.size * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .fixed64).encodedSize
|
||||
serializedSize += tagSize * value.count + MemoryLayout<Int64>.size * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize * value.count + 1 * value.count
|
||||
}
|
||||
|
||||
mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) {
|
||||
let count = $1.utf8.count
|
||||
return $0 + Varint.encodedSize(of: Int64(count)) + count
|
||||
}
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) {
|
||||
let count = $1.count
|
||||
return $0 + Varint.encodedSize(of: Int64(count)) + count
|
||||
}
|
||||
serializedSize += tagSize * value.count + dataSize
|
||||
}
|
||||
|
||||
// Packed field handling.
|
||||
|
||||
mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count * MemoryLayout<Float>.size
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count * MemoryLayout<Double>.size
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: ZigZag.encoded($1)) }
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: ZigZag.encoded($1)) }
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count * MemoryLayout<UInt32>.size
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count * MemoryLayout<UInt64>.size
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count * MemoryLayout<Int32>.size
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count * MemoryLayout<Int64>.size
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber, wireFormat: .lengthDelimited).encodedSize
|
||||
let dataSize = value.count
|
||||
serializedSize += tagSize + Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitSingularEnumField<E: Enum>(value: E,
|
||||
fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize
|
||||
let dataSize = Varint.encodedSize(of: Int32(truncatingIfNeeded: value.rawValue))
|
||||
serializedSize += dataSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedEnumField<E: Enum>(value: [E],
|
||||
fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .varint).encodedSize
|
||||
serializedSize += value.count * tagSize
|
||||
let dataSize = value.reduce(0) {
|
||||
$0 + Varint.encodedSize(of: Int32(truncatingIfNeeded: $1.rawValue))
|
||||
}
|
||||
serializedSize += dataSize
|
||||
}
|
||||
|
||||
mutating func visitPackedEnumField<E: Enum>(value: [E],
|
||||
fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .varint).encodedSize
|
||||
serializedSize += tagSize
|
||||
let dataSize = value.reduce(0) {
|
||||
$0 + Varint.encodedSize(of: Int32(truncatingIfNeeded: $1.rawValue))
|
||||
}
|
||||
serializedSize += Varint.encodedSize(of: Int64(dataSize)) + dataSize
|
||||
}
|
||||
|
||||
mutating func visitSingularMessageField<M: Message>(value: M,
|
||||
fieldNumber: Int) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .lengthDelimited).encodedSize
|
||||
let messageSize = try value.serializedDataSize()
|
||||
serializedSize +=
|
||||
tagSize + Varint.encodedSize(of: UInt64(messageSize)) + messageSize
|
||||
}
|
||||
|
||||
mutating func visitRepeatedMessageField<M: Message>(value: [M],
|
||||
fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .lengthDelimited).encodedSize
|
||||
serializedSize += value.count * tagSize
|
||||
let dataSize = try value.reduce(0) {
|
||||
let messageSize = try $1.serializedDataSize()
|
||||
return $0 + Varint.encodedSize(of: UInt64(messageSize)) + messageSize
|
||||
}
|
||||
serializedSize += dataSize
|
||||
}
|
||||
|
||||
mutating func visitSingularGroupField<G: Message>(value: G, fieldNumber: Int) throws {
|
||||
// The wire format doesn't matter here because the encoded size of the
|
||||
// integer won't change based on the low three bits.
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .startGroup).encodedSize
|
||||
serializedSize += 2 * tagSize
|
||||
try value.traverse(visitor: &self)
|
||||
}
|
||||
|
||||
mutating func visitRepeatedGroupField<G: Message>(value: [G],
|
||||
fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .startGroup).encodedSize
|
||||
serializedSize += 2 * value.count * tagSize
|
||||
for v in value {
|
||||
try v.traverse(visitor: &self)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitMapField<KeyType, ValueType: MapValueType>(
|
||||
fieldType: _ProtobufMap<KeyType, ValueType>.Type,
|
||||
value: _ProtobufMap<KeyType, ValueType>.BaseType,
|
||||
fieldNumber: Int
|
||||
) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .lengthDelimited).encodedSize
|
||||
for (k,v) in value {
|
||||
var sizer = BinaryEncodingSizeVisitor()
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &sizer)
|
||||
try ValueType.visitSingular(value: v, fieldNumber: 2, with: &sizer)
|
||||
let entrySize = sizer.serializedSize
|
||||
serializedSize += Varint.encodedSize(of: Int64(entrySize)) + entrySize
|
||||
}
|
||||
serializedSize += value.count * tagSize
|
||||
}
|
||||
|
||||
mutating func visitMapField<KeyType, ValueType>(
|
||||
fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type,
|
||||
value: _ProtobufEnumMap<KeyType, ValueType>.BaseType,
|
||||
fieldNumber: Int
|
||||
) throws where ValueType.RawValue == Int {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .lengthDelimited).encodedSize
|
||||
for (k,v) in value {
|
||||
var sizer = BinaryEncodingSizeVisitor()
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &sizer)
|
||||
try sizer.visitSingularEnumField(value: v, fieldNumber: 2)
|
||||
let entrySize = sizer.serializedSize
|
||||
serializedSize += Varint.encodedSize(of: Int64(entrySize)) + entrySize
|
||||
}
|
||||
serializedSize += value.count * tagSize
|
||||
}
|
||||
|
||||
mutating func visitMapField<KeyType, ValueType>(
|
||||
fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type,
|
||||
value: _ProtobufMessageMap<KeyType, ValueType>.BaseType,
|
||||
fieldNumber: Int
|
||||
) throws {
|
||||
let tagSize = FieldTag(fieldNumber: fieldNumber,
|
||||
wireFormat: .lengthDelimited).encodedSize
|
||||
for (k,v) in value {
|
||||
var sizer = BinaryEncodingSizeVisitor()
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &sizer)
|
||||
try sizer.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
let entrySize = sizer.serializedSize
|
||||
serializedSize += Varint.encodedSize(of: Int64(entrySize)) + entrySize
|
||||
}
|
||||
serializedSize += value.count * tagSize
|
||||
}
|
||||
|
||||
mutating func visitExtensionFieldsAsMessageSet(
|
||||
fields: ExtensionFieldValueSet,
|
||||
start: Int,
|
||||
end: Int
|
||||
) throws {
|
||||
var sizer = BinaryEncodingMessageSetSizeVisitor()
|
||||
try fields.traverse(visitor: &sizer, start: start, end: end)
|
||||
serializedSize += sizer.serializedSize
|
||||
}
|
||||
}
|
||||
|
||||
extension BinaryEncodingSizeVisitor {
|
||||
|
||||
// Helper Visitor to compute the sizes when writing out the extensions as MessageSets.
|
||||
internal struct BinaryEncodingMessageSetSizeVisitor: SelectiveVisitor {
|
||||
var serializedSize: Int = 0
|
||||
|
||||
init() {}
|
||||
|
||||
mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) throws {
|
||||
var groupSize = WireFormat.MessageSet.itemTagsEncodedSize
|
||||
|
||||
groupSize += Varint.encodedSize(of: Int32(fieldNumber))
|
||||
|
||||
let messageSize = try value.serializedDataSize()
|
||||
groupSize += Varint.encodedSize(of: UInt64(messageSize)) + messageSize
|
||||
|
||||
serializedSize += groupSize
|
||||
}
|
||||
|
||||
// SelectiveVisitor handles the rest.
|
||||
}
|
||||
|
||||
}
|
@ -1,355 +0,0 @@
|
||||
// Sources/SwiftProtobuf/BinaryEncodingVisitor.swift - Binary encoding support
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Core support for protobuf binary encoding. Note that this is built
|
||||
/// on the general traversal machinery.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Visitor that encodes a message graph in the protobuf binary wire format.
|
||||
internal struct BinaryEncodingVisitor: Visitor {
|
||||
|
||||
var encoder: BinaryEncoder
|
||||
|
||||
/// Creates a new visitor that writes the binary-coded message into the memory
|
||||
/// at the given pointer.
|
||||
///
|
||||
/// - Precondition: `pointer` must point to an allocated block of memory that
|
||||
/// is large enough to hold the entire encoded message. For performance
|
||||
/// reasons, the encoder does not make any attempts to verify this.
|
||||
init(forWritingInto pointer: UnsafeMutableRawPointer) {
|
||||
encoder = BinaryEncoder(forWritingInto: pointer)
|
||||
}
|
||||
|
||||
init(encoder: BinaryEncoder) {
|
||||
self.encoder = encoder
|
||||
}
|
||||
|
||||
mutating func visitUnknown(bytes: Data) throws {
|
||||
encoder.appendUnknown(data: bytes)
|
||||
}
|
||||
|
||||
mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .fixed32)
|
||||
encoder.putFloatValue(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .fixed64)
|
||||
encoder.putDoubleValue(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
|
||||
try visitSingularUInt64Field(value: UInt64(bitPattern: value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .varint)
|
||||
encoder.putVarInt(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularSInt32Field(value: Int32, fieldNumber: Int) throws {
|
||||
try visitSingularSInt64Field(value: Int64(value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularSInt64Field(value: Int64, fieldNumber: Int) throws {
|
||||
try visitSingularUInt64Field(value: ZigZag.encoded(value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularFixed32Field(value: UInt32, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .fixed32)
|
||||
encoder.putFixedUInt32(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularFixed64Field(value: UInt64, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .fixed64)
|
||||
encoder.putFixedUInt64(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularSFixed32Field(value: Int32, fieldNumber: Int) throws {
|
||||
try visitSingularFixed32Field(value: UInt32(bitPattern: value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularSFixed64Field(value: Int64, fieldNumber: Int) throws {
|
||||
try visitSingularFixed64Field(value: UInt64(bitPattern: value), fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
|
||||
try visitSingularUInt64Field(value: value ? 1 : 0, fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putStringValue(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putBytesValue(value: value)
|
||||
}
|
||||
|
||||
mutating func visitSingularEnumField<E: Enum>(value: E,
|
||||
fieldNumber: Int) throws {
|
||||
try visitSingularUInt64Field(value: UInt64(bitPattern: Int64(value.rawValue)),
|
||||
fieldNumber: fieldNumber)
|
||||
}
|
||||
|
||||
mutating func visitSingularMessageField<M: Message>(value: M,
|
||||
fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let length = try value.serializedDataSize()
|
||||
encoder.putVarInt(value: length)
|
||||
try value.traverse(visitor: &self)
|
||||
}
|
||||
|
||||
mutating func visitSingularGroupField<G: Message>(value: G, fieldNumber: Int) throws {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .startGroup)
|
||||
try value.traverse(visitor: &self)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .endGroup)
|
||||
}
|
||||
|
||||
// Repeated fields are handled by the default implementations in Visitor.swift
|
||||
|
||||
|
||||
// Packed Fields
|
||||
|
||||
mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count * MemoryLayout<Float>.size)
|
||||
for v in value {
|
||||
encoder.putFloatValue(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count * MemoryLayout<Double>.size)
|
||||
for v in value {
|
||||
encoder.putDoubleValue(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putVarInt(value: Int64(v))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putVarInt(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) { $0 + Varint.encodedSize(of: ZigZag.encoded($1)) }
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putZigZagVarInt(value: Int64(v))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) { $0 + Varint.encodedSize(of: ZigZag.encoded($1)) }
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putZigZagVarInt(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putVarInt(value: UInt64(v))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) { $0 + Varint.encodedSize(of: $1) }
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putVarInt(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count * MemoryLayout<UInt32>.size)
|
||||
for v in value {
|
||||
encoder.putFixedUInt32(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count * MemoryLayout<UInt64>.size)
|
||||
for v in value {
|
||||
encoder.putFixedUInt64(value: v)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count * MemoryLayout<Int32>.size)
|
||||
for v in value {
|
||||
encoder.putFixedUInt32(value: UInt32(bitPattern: v))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count * MemoryLayout<Int64>.size)
|
||||
for v in value {
|
||||
encoder.putFixedUInt64(value: UInt64(bitPattern: v))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
encoder.putVarInt(value: value.count)
|
||||
for v in value {
|
||||
encoder.putVarInt(value: v ? 1 : 0)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitPackedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
|
||||
assert(!value.isEmpty)
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
let packedSize = value.reduce(0) {
|
||||
$0 + Varint.encodedSize(of: Int32(truncatingIfNeeded: $1.rawValue))
|
||||
}
|
||||
encoder.putVarInt(value: packedSize)
|
||||
for v in value {
|
||||
encoder.putVarInt(value: v.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitMapField<KeyType, ValueType: MapValueType>(
|
||||
fieldType: _ProtobufMap<KeyType, ValueType>.Type,
|
||||
value: _ProtobufMap<KeyType, ValueType>.BaseType,
|
||||
fieldNumber: Int
|
||||
) throws {
|
||||
for (k,v) in value {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
var sizer = BinaryEncodingSizeVisitor()
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &sizer)
|
||||
try ValueType.visitSingular(value: v, fieldNumber: 2, with: &sizer)
|
||||
let entrySize = sizer.serializedSize
|
||||
encoder.putVarInt(value: entrySize)
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &self)
|
||||
try ValueType.visitSingular(value: v, fieldNumber: 2, with: &self)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitMapField<KeyType, ValueType>(
|
||||
fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type,
|
||||
value: _ProtobufEnumMap<KeyType, ValueType>.BaseType,
|
||||
fieldNumber: Int
|
||||
) throws where ValueType.RawValue == Int {
|
||||
for (k,v) in value {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
var sizer = BinaryEncodingSizeVisitor()
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &sizer)
|
||||
try sizer.visitSingularEnumField(value: v, fieldNumber: 2)
|
||||
let entrySize = sizer.serializedSize
|
||||
encoder.putVarInt(value: entrySize)
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &self)
|
||||
try visitSingularEnumField(value: v, fieldNumber: 2)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitMapField<KeyType, ValueType>(
|
||||
fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type,
|
||||
value: _ProtobufMessageMap<KeyType, ValueType>.BaseType,
|
||||
fieldNumber: Int
|
||||
) throws {
|
||||
for (k,v) in value {
|
||||
encoder.startField(fieldNumber: fieldNumber, wireFormat: .lengthDelimited)
|
||||
var sizer = BinaryEncodingSizeVisitor()
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &sizer)
|
||||
try sizer.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
let entrySize = sizer.serializedSize
|
||||
encoder.putVarInt(value: entrySize)
|
||||
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &self)
|
||||
try visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func visitExtensionFieldsAsMessageSet(
|
||||
fields: ExtensionFieldValueSet,
|
||||
start: Int,
|
||||
end: Int
|
||||
) throws {
|
||||
var subVisitor = BinaryEncodingMessageSetVisitor(encoder: encoder)
|
||||
try fields.traverse(visitor: &subVisitor, start: start, end: end)
|
||||
encoder = subVisitor.encoder
|
||||
}
|
||||
}
|
||||
|
||||
extension BinaryEncodingVisitor {
|
||||
|
||||
// Helper Visitor to when writing out the extensions as MessageSets.
|
||||
internal struct BinaryEncodingMessageSetVisitor: SelectiveVisitor {
|
||||
var encoder: BinaryEncoder
|
||||
|
||||
init(encoder: BinaryEncoder) {
|
||||
self.encoder = encoder
|
||||
}
|
||||
|
||||
mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) throws {
|
||||
encoder.putVarInt(value: Int64(WireFormat.MessageSet.Tags.itemStart.rawValue))
|
||||
|
||||
encoder.putVarInt(value: Int64(WireFormat.MessageSet.Tags.typeId.rawValue))
|
||||
encoder.putVarInt(value: fieldNumber)
|
||||
|
||||
encoder.putVarInt(value: Int64(WireFormat.MessageSet.Tags.message.rawValue))
|
||||
|
||||
// Use a normal BinaryEncodingVisitor so any message fields end up in the
|
||||
// normal wire format (instead of MessageSet format).
|
||||
let length = try value.serializedDataSize()
|
||||
encoder.putVarInt(value: length)
|
||||
// Create the sub encoder after writing the length.
|
||||
var subVisitor = BinaryEncodingVisitor(encoder: encoder)
|
||||
try value.traverse(visitor: &subVisitor)
|
||||
encoder = subVisitor.encoder
|
||||
|
||||
encoder.putVarInt(value: Int64(WireFormat.MessageSet.Tags.itemEnd.rawValue))
|
||||
}
|
||||
|
||||
// SelectiveVisitor handles the rest.
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// Sources/SwiftProtobuf/CustomJSONCodable.swift - Custom JSON support for WKTs
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Custom protocol for the WKTs to support their custom JSON encodings.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Allows WKTs to provide their custom JSON encodings.
|
||||
internal protocol _CustomJSONCodable {
|
||||
func encodedJSONString(options: JSONEncodingOptions) throws -> String
|
||||
mutating func decodeJSON(from: inout JSONDecoder) throws
|
||||
|
||||
/// Called when the JSON `null` literal is encountered in a position where
|
||||
/// a message of the conforming type is expected. The message type can then
|
||||
/// handle the `null` value differently, if needed; for example,
|
||||
/// `Google_Protobuf_Value` returns a special instance whose `kind` is set to
|
||||
/// `.nullValue(.nullValue)`.
|
||||
///
|
||||
/// The default behavior is to return `nil`, which indicates that `null`
|
||||
/// should be treated as the absence of a message.
|
||||
static func decodedFromJSONNull() throws -> Self?
|
||||
}
|
||||
|
||||
extension _CustomJSONCodable {
|
||||
internal static func decodedFromJSONNull() -> Self? {
|
||||
// Return nil by default. Concrete types can provide custom logic.
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
// Sources/SwiftProtobuf/Data+Extensions.swift - Extension exposing new Data API
|
||||
//
|
||||
// Copyright (c) 2014 - 2019 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Extension exposing new Data API to Swift versions < 5.0.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
#if !swift(>=5.0)
|
||||
internal extension Data {
|
||||
@usableFromInline
|
||||
func withUnsafeBytes<T>(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T {
|
||||
let c = count
|
||||
return try withUnsafeBytes { (p: UnsafePointer<UInt8>) throws -> T in
|
||||
try body(UnsafeRawBufferPointer(start: p, count: c))
|
||||
}
|
||||
}
|
||||
|
||||
mutating func withUnsafeMutableBytes<T>(_ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T {
|
||||
let c = count
|
||||
return try withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) throws -> T in
|
||||
try body(UnsafeMutableRawBufferPointer(start: p, count: c))
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,150 +0,0 @@
|
||||
// Sources/SwiftProtobuf/Decoder.swift - Basic field setting
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// In this way, the generated code only knows about schema
|
||||
/// information; the decoder logic knows how to decode particular
|
||||
/// wire types based on that information.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This is the abstract protocol used by the generated code
|
||||
/// to deserialize data.
|
||||
///
|
||||
/// The generated code looks roughly like this:
|
||||
///
|
||||
/// ```
|
||||
/// while fieldNumber = try decoder.nextFieldNumber() {
|
||||
/// switch fieldNumber {
|
||||
/// case 1: decoder.decodeRepeatedInt32Field(value: &_field)
|
||||
/// ... etc ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For performance, this is mostly broken out into a separate method
|
||||
/// for singular/repeated fields of every supported type. Note that
|
||||
/// we don't distinguish "packed" here, since all existing decoders
|
||||
/// treat "packed" the same as "repeated" at this level. (That is,
|
||||
/// even when the serializer distinguishes packed and non-packed
|
||||
/// forms, the deserializer always accepts both.)
|
||||
///
|
||||
/// Generics come into play at only a few points: `Enum`s and `Message`s
|
||||
/// use a generic type to locate the correct initializer. Maps and
|
||||
/// extensions use generics to avoid the method explosion of having to
|
||||
/// support a separate method for every map and extension type. Maps
|
||||
/// do distinguish `Enum`-valued and `Message`-valued maps to avoid
|
||||
/// polluting the generated `Enum` and `Message` types with all of the
|
||||
/// necessary generic methods to support this.
|
||||
public protocol Decoder {
|
||||
/// Called by a `oneof` when it already has a value and is being asked to
|
||||
/// accept a new value. Some formats require `oneof` decoding to fail in this
|
||||
/// case.
|
||||
mutating func handleConflictingOneOf() throws
|
||||
|
||||
/// Returns the next field number, or nil when the end of the input is
|
||||
/// reached.
|
||||
///
|
||||
/// For JSON and text format, the decoder translates the field name to a
|
||||
/// number at this point, based on information it obtained from the message
|
||||
/// when it was initialized.
|
||||
mutating func nextFieldNumber() throws -> Int?
|
||||
|
||||
// Primitive field decoders
|
||||
mutating func decodeSingularFloatField(value: inout Float) throws
|
||||
mutating func decodeSingularFloatField(value: inout Float?) throws
|
||||
mutating func decodeRepeatedFloatField(value: inout [Float]) throws
|
||||
mutating func decodeSingularDoubleField(value: inout Double) throws
|
||||
mutating func decodeSingularDoubleField(value: inout Double?) throws
|
||||
mutating func decodeRepeatedDoubleField(value: inout [Double]) throws
|
||||
mutating func decodeSingularInt32Field(value: inout Int32) throws
|
||||
mutating func decodeSingularInt32Field(value: inout Int32?) throws
|
||||
mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws
|
||||
mutating func decodeSingularInt64Field(value: inout Int64) throws
|
||||
mutating func decodeSingularInt64Field(value: inout Int64?) throws
|
||||
mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws
|
||||
mutating func decodeSingularUInt32Field(value: inout UInt32) throws
|
||||
mutating func decodeSingularUInt32Field(value: inout UInt32?) throws
|
||||
mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws
|
||||
mutating func decodeSingularUInt64Field(value: inout UInt64) throws
|
||||
mutating func decodeSingularUInt64Field(value: inout UInt64?) throws
|
||||
mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws
|
||||
mutating func decodeSingularSInt32Field(value: inout Int32) throws
|
||||
mutating func decodeSingularSInt32Field(value: inout Int32?) throws
|
||||
mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws
|
||||
mutating func decodeSingularSInt64Field(value: inout Int64) throws
|
||||
mutating func decodeSingularSInt64Field(value: inout Int64?) throws
|
||||
mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws
|
||||
mutating func decodeSingularFixed32Field(value: inout UInt32) throws
|
||||
mutating func decodeSingularFixed32Field(value: inout UInt32?) throws
|
||||
mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws
|
||||
mutating func decodeSingularFixed64Field(value: inout UInt64) throws
|
||||
mutating func decodeSingularFixed64Field(value: inout UInt64?) throws
|
||||
mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws
|
||||
mutating func decodeSingularSFixed32Field(value: inout Int32) throws
|
||||
mutating func decodeSingularSFixed32Field(value: inout Int32?) throws
|
||||
mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws
|
||||
mutating func decodeSingularSFixed64Field(value: inout Int64) throws
|
||||
mutating func decodeSingularSFixed64Field(value: inout Int64?) throws
|
||||
mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws
|
||||
mutating func decodeSingularBoolField(value: inout Bool) throws
|
||||
mutating func decodeSingularBoolField(value: inout Bool?) throws
|
||||
mutating func decodeRepeatedBoolField(value: inout [Bool]) throws
|
||||
mutating func decodeSingularStringField(value: inout String) throws
|
||||
mutating func decodeSingularStringField(value: inout String?) throws
|
||||
mutating func decodeRepeatedStringField(value: inout [String]) throws
|
||||
mutating func decodeSingularBytesField(value: inout Data) throws
|
||||
mutating func decodeSingularBytesField(value: inout Data?) throws
|
||||
mutating func decodeRepeatedBytesField(value: inout [Data]) throws
|
||||
|
||||
// Decode Enum fields
|
||||
mutating func decodeSingularEnumField<E: Enum>(value: inout E) throws where E.RawValue == Int
|
||||
mutating func decodeSingularEnumField<E: Enum>(value: inout E?) throws where E.RawValue == Int
|
||||
mutating func decodeRepeatedEnumField<E: Enum>(value: inout [E]) throws where E.RawValue == Int
|
||||
|
||||
// Decode Message fields
|
||||
mutating func decodeSingularMessageField<M: Message>(value: inout M?) throws
|
||||
mutating func decodeRepeatedMessageField<M: Message>(value: inout [M]) throws
|
||||
|
||||
// Decode Group fields
|
||||
mutating func decodeSingularGroupField<G: Message>(value: inout G?) throws
|
||||
mutating func decodeRepeatedGroupField<G: Message>(value: inout [G]) throws
|
||||
|
||||
// Decode Map fields.
|
||||
// This is broken into separate methods depending on whether the value
|
||||
// type is primitive (_ProtobufMap), enum (_ProtobufEnumMap), or message
|
||||
// (_ProtobufMessageMap)
|
||||
mutating func decodeMapField<KeyType, ValueType: MapValueType>(fieldType: _ProtobufMap<KeyType, ValueType>.Type, value: inout _ProtobufMap<KeyType, ValueType>.BaseType) throws
|
||||
mutating func decodeMapField<KeyType, ValueType>(fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type, value: inout _ProtobufEnumMap<KeyType, ValueType>.BaseType) throws where ValueType.RawValue == Int
|
||||
mutating func decodeMapField<KeyType, ValueType>(fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type, value: inout _ProtobufMessageMap<KeyType, ValueType>.BaseType) throws
|
||||
|
||||
// Decode extension fields
|
||||
mutating func decodeExtensionField(values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int) throws
|
||||
|
||||
// Run a decode loop decoding the MessageSet format for Extensions.
|
||||
mutating func decodeExtensionFieldsAsMessageSet(values: inout ExtensionFieldValueSet,
|
||||
messageType: Message.Type) throws
|
||||
}
|
||||
|
||||
/// Most Decoders won't care about Extension handing as in MessageSet
|
||||
/// format, so provide a default implementation simply looping on the
|
||||
/// fieldNumbers and feeding through to extension decoding.
|
||||
extension Decoder {
|
||||
public mutating func decodeExtensionFieldsAsMessageSet(
|
||||
values: inout ExtensionFieldValueSet,
|
||||
messageType: Message.Type
|
||||
) throws {
|
||||
while let fieldNumber = try self.nextFieldNumber() {
|
||||
try self.decodeExtensionField(values: &values,
|
||||
messageType: messageType,
|
||||
fieldNumber: fieldNumber)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// Sources/SwiftProtobuf/DoubleParser.swift - Generally useful mathematical functions
|
||||
//
|
||||
// Copyright (c) 2014 - 2019 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Numeric parsing helper for float and double strings
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Support parsing float/double values from UTF-8
|
||||
internal class DoubleParser {
|
||||
// Temporary buffer so we can null-terminate the UTF-8 string
|
||||
// before calling the C standard libray to parse it.
|
||||
// In theory, JSON writers should be able to represent any IEEE Double
|
||||
// in at most 25 bytes, but many writers will emit more digits than
|
||||
// necessary, so we size this generously.
|
||||
private var work =
|
||||
UnsafeMutableBufferPointer<Int8>.allocate(capacity: 128)
|
||||
|
||||
deinit {
|
||||
work.deallocate()
|
||||
}
|
||||
|
||||
func utf8ToDouble(bytes: UnsafeRawBufferPointer,
|
||||
start: UnsafeRawBufferPointer.Index,
|
||||
end: UnsafeRawBufferPointer.Index) -> Double? {
|
||||
return utf8ToDouble(bytes: UnsafeRawBufferPointer(rebasing: bytes[start..<end]))
|
||||
}
|
||||
|
||||
func utf8ToDouble(bytes: UnsafeRawBufferPointer) -> Double? {
|
||||
// Reject unreasonably long or short UTF8 number
|
||||
if work.count <= bytes.count || bytes.count < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
#if swift(>=4.1)
|
||||
UnsafeMutableRawBufferPointer(work).copyMemory(from: bytes)
|
||||
#else
|
||||
UnsafeMutableRawBufferPointer(work).copyBytes(from: bytes)
|
||||
#endif
|
||||
work[bytes.count] = 0
|
||||
|
||||
// Use C library strtod() to parse it
|
||||
var e: UnsafeMutablePointer<Int8>? = work.baseAddress
|
||||
let d = strtod(work.baseAddress!, &e)
|
||||
|
||||
// Fail if strtod() did not consume everything we expected
|
||||
// or if strtod() thought the number was out of range.
|
||||
if e != work.baseAddress! + bytes.count || !d.isFinite {
|
||||
return nil
|
||||
}
|
||||
return d
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
// Sources/SwiftProtobuf/Enum.swift - Enum support
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Generated enums conform to SwiftProtobuf.Enum
|
||||
///
|
||||
/// See ProtobufTypes and JSONTypes for extension
|
||||
/// methods to support binary and JSON coding.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/// Generated enum types conform to this protocol.
|
||||
public protocol Enum: RawRepresentable, Hashable {
|
||||
/// Creates a new instance of the enum initialized to its default value.
|
||||
init()
|
||||
|
||||
/// Creates a new instance of the enum from the given raw integer value.
|
||||
///
|
||||
/// For proto2 enums, this initializer will fail if the raw value does not
|
||||
/// correspond to a valid enum value. For proto3 enums, this initializer never
|
||||
/// fails; unknown values are created as instances of the `UNRECOGNIZED` case.
|
||||
///
|
||||
/// - Parameter rawValue: The raw integer value from which to create the enum
|
||||
/// value.
|
||||
init?(rawValue: Int)
|
||||
|
||||
/// The raw integer value of the enum value.
|
||||
///
|
||||
/// For a recognized enum case, this is the integer value of the case as
|
||||
/// defined in the .proto file. For `UNRECOGNIZED` cases in proto3, this is
|
||||
/// the value that was originally decoded.
|
||||
var rawValue: Int { get }
|
||||
}
|
||||
|
||||
extension Enum {
|
||||
#if swift(>=4.2)
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(rawValue)
|
||||
}
|
||||
#else // swift(>=4.2)
|
||||
public var hashValue: Int {
|
||||
return rawValue
|
||||
}
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
/// Internal convenience property representing the name of the enum value (or
|
||||
/// `nil` if it is an `UNRECOGNIZED` value or doesn't provide names).
|
||||
///
|
||||
/// Since the text format and JSON names are always identical, we don't need
|
||||
/// to distinguish them.
|
||||
internal var name: _NameMap.Name? {
|
||||
guard let nameProviding = Self.self as? _ProtoNameProviding.Type else {
|
||||
return nil
|
||||
}
|
||||
return nameProviding._protobuf_nameMap.names(for: rawValue)?.proto
|
||||
}
|
||||
|
||||
/// Internal convenience initializer that returns the enum value with the
|
||||
/// given name, if it provides names.
|
||||
///
|
||||
/// Since the text format and JSON names are always identical, we don't need
|
||||
/// to distinguish them.
|
||||
///
|
||||
/// - Parameter name: The name of the enum case.
|
||||
internal init?(name: String) {
|
||||
guard let nameProviding = Self.self as? _ProtoNameProviding.Type,
|
||||
let number = nameProviding._protobuf_nameMap.number(forJSONName: name) else {
|
||||
return nil
|
||||
}
|
||||
self.init(rawValue: number)
|
||||
}
|
||||
|
||||
/// Internal convenience initializer that returns the enum value with the
|
||||
/// given name, if it provides names.
|
||||
///
|
||||
/// Since the text format and JSON names are always identical, we don't need
|
||||
/// to distinguish them.
|
||||
///
|
||||
/// - Parameter name: Buffer holding the UTF-8 bytes of the desired name.
|
||||
internal init?(rawUTF8: UnsafeRawBufferPointer) {
|
||||
guard let nameProviding = Self.self as? _ProtoNameProviding.Type,
|
||||
let number = nameProviding._protobuf_nameMap.number(forJSONName: rawUTF8) else {
|
||||
return nil
|
||||
}
|
||||
self.init(rawValue: number)
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
// Sources/SwiftProtobuf/ExtensibleMessage.swift - Extension support
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See LICENSE.txt for license information:
|
||||
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
///
|
||||
/// Additional capabilities needed by messages that allow extensions.
|
||||
///
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Messages that support extensions implement this protocol
|
||||
public protocol ExtensibleMessage: Message {
|
||||
var _protobuf_extensionFieldValues: ExtensionFieldValueSet { get set }
|
||||
}
|
||||
|
||||
extension ExtensibleMessage {
|
||||
public mutating func setExtensionValue<F: ExtensionField>(ext: MessageExtension<F, Self>, value: F.ValueType) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] = F(protobufExtension: ext, value: value)
|
||||
}
|
||||
|
||||
public func getExtensionValue<F: ExtensionField>(ext: MessageExtension<F, Self>) -> F.ValueType? {
|
||||
if let fieldValue = _protobuf_extensionFieldValues[ext.fieldNumber] as? F {
|
||||
return fieldValue.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func hasExtensionValue<F: ExtensionField>(ext: MessageExtension<F, Self>) -> Bool {
|
||||
return _protobuf_extensionFieldValues[ext.fieldNumber] is F
|
||||
}
|
||||
|
||||
public mutating func clearExtensionValue<F: ExtensionField>(ext: MessageExtension<F, Self>) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Additional specializations for the different types of repeated fields so
|
||||
// setting them to an empty array clears them from the map.
|
||||
extension ExtensibleMessage {
|
||||
public mutating func setExtensionValue<T>(ext: MessageExtension<RepeatedExtensionField<T>, Self>, value: [T.BaseType]) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] =
|
||||
value.isEmpty ? nil : RepeatedExtensionField<T>(protobufExtension: ext, value: value)
|
||||
}
|
||||
|
||||
public mutating func setExtensionValue<T>(ext: MessageExtension<PackedExtensionField<T>, Self>, value: [T.BaseType]) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] =
|
||||
value.isEmpty ? nil : PackedExtensionField<T>(protobufExtension: ext, value: value)
|
||||
}
|
||||
|
||||
public mutating func setExtensionValue<E>(ext: MessageExtension<RepeatedEnumExtensionField<E>, Self>, value: [E]) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] =
|
||||
value.isEmpty ? nil : RepeatedEnumExtensionField<E>(protobufExtension: ext, value: value)
|
||||
}
|
||||
|
||||
public mutating func setExtensionValue<E>(ext: MessageExtension<PackedEnumExtensionField<E>, Self>, value: [E]) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] =
|
||||
value.isEmpty ? nil : PackedEnumExtensionField<E>(protobufExtension: ext, value: value)
|
||||
}
|
||||
|
||||
public mutating func setExtensionValue<M>(ext: MessageExtension<RepeatedMessageExtensionField<M>, Self>, value: [M]) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] =
|
||||
value.isEmpty ? nil : RepeatedMessageExtensionField<M>(protobufExtension: ext, value: value)
|
||||
}
|
||||
|
||||
public mutating func setExtensionValue<M>(ext: MessageExtension<RepeatedGroupExtensionField<M>, Self>, value: [M]) {
|
||||
_protobuf_extensionFieldValues[ext.fieldNumber] =
|
||||
value.isEmpty ? nil : RepeatedGroupExtensionField<M>(protobufExtension: ext, value: value)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user