弃用Cocoapods, 支持SPM。SwiftUI Hello world!

This commit is contained in:
飞鱼 2021-10-20 20:43:08 +08:00
parent 1faf67a977
commit 4e0520b2b5
233 changed files with 1512 additions and 32074 deletions

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

View 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"]),
]
)

View File

@ -0,0 +1,3 @@
# CIMClient
A description of this package.

View File

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

View File

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

View File

@ -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]
}
}
}

View File

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

View File

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

View File

@ -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 连接失败!")
}
}
}

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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()

View File

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

View File

@ -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!")
}
}

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

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

View File

@ -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")

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

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

View 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] = []

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

View File

@ -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()
}
}

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

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

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

View File

@ -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()
}
}

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

View File

@ -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) {
}
}

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

View File

@ -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

View File

@ -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

View File

@ -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"
]
}
}

View File

@ -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

View File

@ -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.

View File

@ -1,308 +0,0 @@
![starscream](https://raw.githubusercontent.com/daltoniam/starscream/assets/starscream.jpg)
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

View File

@ -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?
}

View File

@ -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()
}
}

View File

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

View File

@ -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: (() -> ())?)
}

View File

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

View File

@ -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()
}
}

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
}
}

View File

@ -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?
}

View File

@ -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?
}

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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.

View File

@ -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.
[![Build and Test](https://github.com/apple/swift-protobuf/workflows/Build%20and%20Test/badge.svg)](https://github.com/apple/swift-protobuf/actions?query=workflow%3A%22Build+and+Test%22)
[![Check Upstream Protos](https://github.com/apple/swift-protobuf/workflows/Check%20Upstream%20Proto%20Files/badge.svg)](https://github.com/apple/swift-protobuf/actions?query=workflow%3A%22Check+Upstream+Proto+Files%22)
[![Run Conformance Tests](https://github.com/apple/swift-protobuf/workflows/Run%20Conformance%20Tests/badge.svg)](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

View File

@ -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()
}
}
}

View File

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

View File

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

View File

@ -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() {}
}

View File

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

View File

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

View File

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

View File

@ -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.
}
}

View File

@ -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.
}
}

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

@ -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