Swift SDK

This commit is contained in:
飞鱼 2021-10-01 19:00:04 +08:00
parent 0ce42c6901
commit 77ad75cbba
202 changed files with 33244 additions and 0 deletions

37
cim-client-sdk/cim-swift-sdk/.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
# macOS
.DS_Store
# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
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/

View File

@ -0,0 +1,14 @@
# 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,13 @@
use_frameworks!
platform :ios, '9.0'
target 'cimsdk_Example' do
pod 'cimsdk', :path => '../'
target 'cimsdk_Tests' do
inherit! :search_paths
end
end

View File

@ -0,0 +1,27 @@
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

@ -0,0 +1,30 @@
{
"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

@ -0,0 +1,27 @@
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

@ -0,0 +1,176 @@
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

@ -0,0 +1,308 @@
![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

@ -0,0 +1,29 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,247 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,53 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,22 @@
//
// 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

@ -0,0 +1,96 @@
//
// 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

@ -0,0 +1,234 @@
//
// 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

@ -0,0 +1,123 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,99 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,107 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,365 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,148 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,143 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,101 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,45 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,56 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,196 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,178 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,218 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,159 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,55 @@
//////////////////////////////////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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

@ -0,0 +1,211 @@
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

@ -0,0 +1,303 @@
<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

@ -0,0 +1,476 @@
// 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

@ -0,0 +1,37 @@
// 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

@ -0,0 +1,44 @@
// 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

@ -0,0 +1,39 @@
// 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

@ -0,0 +1,232 @@
// 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

@ -0,0 +1,155 @@
// 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

@ -0,0 +1,27 @@
// 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

@ -0,0 +1,473 @@
// 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

@ -0,0 +1,355 @@
// 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

@ -0,0 +1,36 @@
// 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

@ -0,0 +1,34 @@
// 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

@ -0,0 +1,150 @@
// 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

@ -0,0 +1,61 @@
// 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

@ -0,0 +1,93 @@
// 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

@ -0,0 +1,73 @@
// 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)
}
}

View File

@ -0,0 +1,95 @@
// Sources/SwiftProtobuf/ExtensionFieldValueSet.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
//
// -----------------------------------------------------------------------------
///
/// A collection of extension field values on a particular object.
/// This is only used within messages to manage the values of extension fields;
/// it does not need to be very sophisticated.
///
// -----------------------------------------------------------------------------
public struct ExtensionFieldValueSet: Hashable {
fileprivate var values = [Int : AnyExtensionField]()
public static func ==(lhs: ExtensionFieldValueSet,
rhs: ExtensionFieldValueSet) -> Bool {
guard lhs.values.count == rhs.values.count else {
return false
}
for (index, l) in lhs.values {
if let r = rhs.values[index] {
if type(of: l) != type(of: r) {
return false
}
if !l.isEqual(other: r) {
return false
}
} else {
return false
}
}
return true
}
public init() {}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
// AnyExtensionField is not Hashable, and the Self constraint that would
// add breaks some of the uses of it; so the only choice is to manually
// mix things in. However, one must remember to do things in an order
// independent manner.
var hash = 16777619
for (fieldNumber, v) in values {
var localHasher = hasher
localHasher.combine(fieldNumber)
v.hash(into: &localHasher)
hash = hash &+ localHasher.finalize()
}
hasher.combine(hash)
}
#else // swift(>=4.2)
public var hashValue: Int {
var hash = 16777619
for (fieldNumber, v) in values {
// Note: This calculation cannot depend on the order of the items.
hash = hash &+ fieldNumber &+ v.hashValue
}
return hash
}
#endif // swift(>=4.2)
public func traverse<V: Visitor>(visitor: inout V, start: Int, end: Int) throws {
let validIndexes = values.keys.filter {$0 >= start && $0 < end}
for i in validIndexes.sorted() {
let value = values[i]!
try value.traverse(visitor: &visitor)
}
}
public subscript(index: Int) -> AnyExtensionField? {
get { return values[index] }
set { values[index] = newValue }
}
mutating func modify<ReturnType>(index: Int, _ modifier: (inout AnyExtensionField?) throws -> ReturnType) rethrows -> ReturnType {
// This internal helper exists to invoke the _modify accessor on Dictionary for the given operation, which can avoid CoWs
// during the modification operation.
return try modifier(&values[index])
}
public var isInitialized: Bool {
for (_, v) in values {
if !v.isInitialized {
return false
}
}
return true
}
}

View File

@ -0,0 +1,708 @@
// Sources/SwiftProtobuf/ExtensionFields.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
//
// -----------------------------------------------------------------------------
///
/// Core protocols implemented by generated extensions.
///
// -----------------------------------------------------------------------------
#if !swift(>=4.2)
private let i_2166136261 = Int(bitPattern: 2166136261)
private let i_16777619 = Int(16777619)
#endif
//
// Type-erased Extension field implementation.
// Note that it has no "self or associated type" references, so can
// be used as a protocol type. (In particular, although it does have
// a hashValue property, it cannot be Hashable.)
//
// This can encode, decode, return a hashValue and test for
// equality with some other extension field; but it's type-sealed
// so you can't actually access the contained value itself.
//
public protocol AnyExtensionField: CustomDebugStringConvertible {
#if swift(>=4.2)
func hash(into hasher: inout Hasher)
#else
var hashValue: Int { get }
#endif
var protobufExtension: AnyMessageExtension { get }
func isEqual(other: AnyExtensionField) -> Bool
/// Merging field decoding
mutating func decodeExtensionField<T: Decoder>(decoder: inout T) throws
/// Fields know their own type, so can dispatch to a visitor
func traverse<V: Visitor>(visitor: inout V) throws
/// Check if the field is initialized.
var isInitialized: Bool { get }
}
extension AnyExtensionField {
// Default implementation for extensions fields. The message types below provide
// custom versions.
public var isInitialized: Bool { return true }
}
///
/// The regular ExtensionField type exposes the value directly.
///
public protocol ExtensionField: AnyExtensionField, Hashable {
associatedtype ValueType
var value: ValueType { get set }
init(protobufExtension: AnyMessageExtension, value: ValueType)
init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws
}
///
/// Singular field
///
public struct OptionalExtensionField<T: FieldType>: ExtensionField {
public typealias BaseType = T.BaseType
public typealias ValueType = BaseType
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: OptionalExtensionField,
rhs: OptionalExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
public var debugDescription: String {
get {
return String(reflecting: value)
}
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get { return value.hashValue }
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! OptionalExtensionField<T>
return self == o
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
var v: ValueType?
try T.decodeSingular(value: &v, from: &decoder)
if let v = v {
value = v
}
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType?
try T.decodeSingular(value: &v, from: &decoder)
if let v = v {
self.init(protobufExtension: protobufExtension, value: v)
} else {
return nil
}
}
public func traverse<V: Visitor>(visitor: inout V) throws {
try T.visitSingular(value: value, fieldNumber: protobufExtension.fieldNumber, with: &visitor)
}
}
///
/// Repeated fields
///
public struct RepeatedExtensionField<T: FieldType>: ExtensionField {
public typealias BaseType = T.BaseType
public typealias ValueType = [BaseType]
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: RepeatedExtensionField,
rhs: RepeatedExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get {
var hash = i_2166136261
for e in value {
hash = (hash &* i_16777619) ^ e.hashValue
}
return hash
}
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! RepeatedExtensionField<T>
return self == o
}
public var debugDescription: String {
return "[" + value.map{String(reflecting: $0)}.joined(separator: ",") + "]"
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
try T.decodeRepeated(value: &value, from: &decoder)
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType = []
try T.decodeRepeated(value: &v, from: &decoder)
self.init(protobufExtension: protobufExtension, value: v)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if value.count > 0 {
try T.visitRepeated(value: value, fieldNumber: protobufExtension.fieldNumber, with: &visitor)
}
}
}
///
/// Packed Repeated fields
///
/// TODO: This is almost (but not quite) identical to RepeatedFields;
/// find a way to collapse the implementations.
///
public struct PackedExtensionField<T: FieldType>: ExtensionField {
public typealias BaseType = T.BaseType
public typealias ValueType = [BaseType]
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: PackedExtensionField,
rhs: PackedExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get {
var hash = i_2166136261
for e in value {
hash = (hash &* i_16777619) ^ e.hashValue
}
return hash
}
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! PackedExtensionField<T>
return self == o
}
public var debugDescription: String {
return "[" + value.map{String(reflecting: $0)}.joined(separator: ",") + "]"
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
try T.decodeRepeated(value: &value, from: &decoder)
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType = []
try T.decodeRepeated(value: &v, from: &decoder)
self.init(protobufExtension: protobufExtension, value: v)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if value.count > 0 {
try T.visitPacked(value: value, fieldNumber: protobufExtension.fieldNumber, with: &visitor)
}
}
}
///
/// Enum extensions
///
public struct OptionalEnumExtensionField<E: Enum>: ExtensionField where E.RawValue == Int {
public typealias BaseType = E
public typealias ValueType = E
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: OptionalEnumExtensionField,
rhs: OptionalEnumExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
public var debugDescription: String {
get {
return String(reflecting: value)
}
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get { return value.hashValue }
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! OptionalEnumExtensionField<E>
return self == o
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
var v: ValueType?
try decoder.decodeSingularEnumField(value: &v)
if let v = v {
value = v
}
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType?
try decoder.decodeSingularEnumField(value: &v)
if let v = v {
self.init(protobufExtension: protobufExtension, value: v)
} else {
return nil
}
}
public func traverse<V: Visitor>(visitor: inout V) throws {
try visitor.visitSingularEnumField(
value: value,
fieldNumber: protobufExtension.fieldNumber)
}
}
///
/// Repeated Enum fields
///
public struct RepeatedEnumExtensionField<E: Enum>: ExtensionField where E.RawValue == Int {
public typealias BaseType = E
public typealias ValueType = [E]
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: RepeatedEnumExtensionField,
rhs: RepeatedEnumExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get {
var hash = i_2166136261
for e in value {
hash = (hash &* i_16777619) ^ e.hashValue
}
return hash
}
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! RepeatedEnumExtensionField<E>
return self == o
}
public var debugDescription: String {
return "[" + value.map{String(reflecting: $0)}.joined(separator: ",") + "]"
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
try decoder.decodeRepeatedEnumField(value: &value)
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType = []
try decoder.decodeRepeatedEnumField(value: &v)
self.init(protobufExtension: protobufExtension, value: v)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if value.count > 0 {
try visitor.visitRepeatedEnumField(
value: value,
fieldNumber: protobufExtension.fieldNumber)
}
}
}
///
/// Packed Repeated Enum fields
///
/// TODO: This is almost (but not quite) identical to RepeatedEnumFields;
/// find a way to collapse the implementations.
///
public struct PackedEnumExtensionField<E: Enum>: ExtensionField where E.RawValue == Int {
public typealias BaseType = E
public typealias ValueType = [E]
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: PackedEnumExtensionField,
rhs: PackedEnumExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get {
var hash = i_2166136261
for e in value {
hash = (hash &* i_16777619) ^ e.hashValue
}
return hash
}
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! PackedEnumExtensionField<E>
return self == o
}
public var debugDescription: String {
return "[" + value.map{String(reflecting: $0)}.joined(separator: ",") + "]"
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
try decoder.decodeRepeatedEnumField(value: &value)
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType = []
try decoder.decodeRepeatedEnumField(value: &v)
self.init(protobufExtension: protobufExtension, value: v)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if value.count > 0 {
try visitor.visitPackedEnumField(
value: value,
fieldNumber: protobufExtension.fieldNumber)
}
}
}
//
// ========== Message ==========
//
public struct OptionalMessageExtensionField<M: Message & Equatable>:
ExtensionField {
public typealias BaseType = M
public typealias ValueType = BaseType
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: OptionalMessageExtensionField,
rhs: OptionalMessageExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
public var debugDescription: String {
get {
return String(reflecting: value)
}
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
value.hash(into: &hasher)
}
#else // swift(>=4.2)
public var hashValue: Int {return value.hashValue}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! OptionalMessageExtensionField<M>
return self == o
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
var v: ValueType? = value
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
self.value = v
}
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType?
try decoder.decodeSingularMessageField(value: &v)
if let v = v {
self.init(protobufExtension: protobufExtension, value: v)
} else {
return nil
}
}
public func traverse<V: Visitor>(visitor: inout V) throws {
try visitor.visitSingularMessageField(
value: value, fieldNumber: protobufExtension.fieldNumber)
}
public var isInitialized: Bool {
return value.isInitialized
}
}
public struct RepeatedMessageExtensionField<M: Message & Equatable>:
ExtensionField {
public typealias BaseType = M
public typealias ValueType = [BaseType]
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: RepeatedMessageExtensionField,
rhs: RepeatedMessageExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
for e in value {
e.hash(into: &hasher)
}
}
#else // swift(>=4.2)
public var hashValue: Int {
get {
var hash = i_2166136261
for e in value {
hash = (hash &* i_16777619) ^ e.hashValue
}
return hash
}
}
#endif // swift(>=4.2)
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! RepeatedMessageExtensionField<M>
return self == o
}
public var debugDescription: String {
return "[" + value.map{String(reflecting: $0)}.joined(separator: ",") + "]"
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
try decoder.decodeRepeatedMessageField(value: &value)
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType = []
try decoder.decodeRepeatedMessageField(value: &v)
self.init(protobufExtension: protobufExtension, value: v)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if value.count > 0 {
try visitor.visitRepeatedMessageField(
value: value, fieldNumber: protobufExtension.fieldNumber)
}
}
public var isInitialized: Bool {
return Internal.areAllInitialized(value)
}
}
//
// ======== Groups within Messages ========
//
// Protoc internally treats groups the same as messages, but
// they serialize very differently, so we have separate serialization
// handling here...
public struct OptionalGroupExtensionField<G: Message & Hashable>:
ExtensionField {
public typealias BaseType = G
public typealias ValueType = BaseType
public var value: G
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: OptionalGroupExtensionField,
rhs: OptionalGroupExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {return value.hashValue}
#endif // swift(>=4.2)
public var debugDescription: String { get {return value.debugDescription} }
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! OptionalGroupExtensionField<G>
return self == o
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
var v: ValueType? = value
try decoder.decodeSingularGroupField(value: &v)
if let v = v {
value = v
}
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType?
try decoder.decodeSingularGroupField(value: &v)
if let v = v {
self.init(protobufExtension: protobufExtension, value: v)
} else {
return nil
}
}
public func traverse<V: Visitor>(visitor: inout V) throws {
try visitor.visitSingularGroupField(
value: value, fieldNumber: protobufExtension.fieldNumber)
}
public var isInitialized: Bool {
return value.isInitialized
}
}
public struct RepeatedGroupExtensionField<G: Message & Hashable>:
ExtensionField {
public typealias BaseType = G
public typealias ValueType = [BaseType]
public var value: ValueType
public var protobufExtension: AnyMessageExtension
public static func ==(lhs: RepeatedGroupExtensionField,
rhs: RepeatedGroupExtensionField) -> Bool {
return lhs.value == rhs.value
}
public init(protobufExtension: AnyMessageExtension, value: ValueType) {
self.protobufExtension = protobufExtension
self.value = value
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
#else // swift(>=4.2)
public var hashValue: Int {
get {
var hash = i_2166136261
for e in value {
hash = (hash &* i_16777619) ^ e.hashValue
}
return hash
}
}
#endif // swift(>=4.2)
public var debugDescription: String {
return "[" + value.map{$0.debugDescription}.joined(separator: ",") + "]"
}
public func isEqual(other: AnyExtensionField) -> Bool {
let o = other as! RepeatedGroupExtensionField<G>
return self == o
}
public mutating func decodeExtensionField<D: Decoder>(decoder: inout D) throws {
try decoder.decodeRepeatedGroupField(value: &value)
}
public init?<D: Decoder>(protobufExtension: AnyMessageExtension, decoder: inout D) throws {
var v: ValueType = []
try decoder.decodeRepeatedGroupField(value: &v)
self.init(protobufExtension: protobufExtension, value: v)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if value.count > 0 {
try visitor.visitRepeatedGroupField(
value: value, fieldNumber: protobufExtension.fieldNumber)
}
}
public var isInitialized: Bool {
return Internal.areAllInitialized(value)
}
}

View File

@ -0,0 +1,38 @@
// Sources/SwiftProtobuf/ExtensionMap.swift - Extension 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
//
// -----------------------------------------------------------------------------
///
/// A set of extensions that can be passed into deserializers
/// to provide details of the particular extensions that should
/// be recognized.
///
// -----------------------------------------------------------------------------
/// A collection of extension objects.
///
/// An `ExtensionMap` is used during decoding to look up
/// extension objects corresponding to the serialized data.
///
/// This is a protocol so that developers can build their own
/// extension handling if they need something more complex than the
/// standard `SimpleExtensionMap` implementation.
public protocol ExtensionMap {
/// Returns the extension object describing an extension or nil
subscript(messageType: Message.Type, fieldNumber: Int) -> AnyMessageExtension? { get }
/// Returns the field number for a message with a specific field name
///
/// The field name here matches the format used by the protobuf
/// Text serialization: it typically looks like
/// `package.message.field_name`, where `package` is the package
/// for the proto file and `message` is the name of the message in
/// which the extension was defined. (This is different from the
/// message that is being extended!)
func fieldNumberForProto(messageType: Message.Type, protoFieldName: String) -> Int?
}

View File

@ -0,0 +1,69 @@
// Sources/SwiftProtobuf/FieldTag.swift - Describes a binary field tag
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Types related to binary encoded tags (field numbers and wire formats).
///
// -----------------------------------------------------------------------------
/// Encapsulates the number and wire format of a field, which together form the
/// "tag".
///
/// This type also validates tags in that it will never allow a tag with an
/// improper field number (such as zero) or wire format (such as 6 or 7) to
/// exist. In other words, a `FieldTag`'s properties never need to be tested
/// for validity because they are guaranteed correct at initialization time.
internal struct FieldTag: RawRepresentable {
typealias RawValue = UInt32
/// The raw numeric value of the tag, which contains both the field number and
/// wire format.
let rawValue: UInt32
/// The field number component of the tag.
var fieldNumber: Int {
return Int(rawValue >> 3)
}
/// The wire format component of the tag.
var wireFormat: WireFormat {
// This force-unwrap is safe because there are only two initialization
// paths: one that takes a WireFormat directly (and is guaranteed valid at
// compile-time), or one that takes a raw value but which only lets valid
// wire formats through.
return WireFormat(rawValue: UInt8(rawValue & 7))!
}
/// A helper property that returns the number of bytes required to
/// varint-encode this tag.
var encodedSize: Int {
return Varint.encodedSize(of: rawValue)
}
/// Creates a new tag from its raw numeric representation.
///
/// Note that if the raw value given here is not a valid tag (for example, it
/// has an invalid wire format), this initializer will fail.
init?(rawValue: UInt32) {
// Verify that the field number and wire format are valid and fail if they
// are not.
guard rawValue & ~0x07 != 0,
let _ = WireFormat(rawValue: UInt8(rawValue % 8)) else {
return nil
}
self.rawValue = rawValue
}
/// Creates a new tag by composing the given field number and wire format.
init(fieldNumber: Int, wireFormat: WireFormat) {
self.rawValue = UInt32(truncatingIfNeeded: fieldNumber) << 3 |
UInt32(wireFormat.rawValue)
}
}

View File

@ -0,0 +1,431 @@
// Sources/SwiftProtobuf/FieldTypes.swift - Proto data types
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Serialization/deserialization support for each proto field type.
///
/// Note that we cannot just extend the standard Int32, etc, types
/// with serialization information since proto language supports
/// distinct types (with different codings) that use the same
/// in-memory representation. For example, proto "sint32" and
/// "sfixed32" both are represented in-memory as Int32.
///
/// These types are used generically and also passed into
/// various coding/decoding functions to provide type-specific
/// information.
///
// -----------------------------------------------------------------------------
import Foundation
// Note: The protobuf- and JSON-specific methods here are defined
// in ProtobufTypeAdditions.swift and JSONTypeAdditions.swift
public protocol FieldType {
// The Swift type used to store data for this field. For example,
// proto "sint32" fields use Swift "Int32" type.
associatedtype BaseType: Hashable
// The default value for this field type before it has been set.
// This is also used, for example, when JSON decodes a "null"
// value for a field.
static var proto3DefaultValue: BaseType { get }
// Generic reflector methods for looking up the correct
// encoding/decoding for extension fields, map keys, and map
// values.
static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws
static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws
static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws
static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws
static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws
}
///
/// Marker protocol for types that can be used as map keys
///
public protocol MapKeyType: FieldType {
/// A comparision function for where order is needed. Can't use `Comparable`
/// because `Bool` doesn't conform, and since it is `public` there is no way
/// to add a conformance internal to SwiftProtobuf.
static func _lessThan(lhs: BaseType, rhs: BaseType) -> Bool
}
// Default impl for anything `Comparable`
extension MapKeyType where BaseType: Comparable {
public static func _lessThan(lhs: BaseType, rhs: BaseType) -> Bool {
return lhs < rhs
}
}
///
/// Marker Protocol for types that can be used as map values.
///
public protocol MapValueType: FieldType {
}
//
// We have a struct for every basic proto field type which provides
// serialization/deserialization support as static methods.
//
///
/// Float traits
///
public struct ProtobufFloat: FieldType, MapValueType {
public typealias BaseType = Float
public static var proto3DefaultValue: Float {return 0.0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularFloatField(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedFloatField(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularFloatField(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedFloatField(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedFloatField(value: value, fieldNumber: fieldNumber)
}
}
///
/// Double
///
public struct ProtobufDouble: FieldType, MapValueType {
public typealias BaseType = Double
public static var proto3DefaultValue: Double {return 0.0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularDoubleField(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedDoubleField(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularDoubleField(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedDoubleField(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedDoubleField(value: value, fieldNumber: fieldNumber)
}
}
///
/// Int32
///
public struct ProtobufInt32: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Int32
public static var proto3DefaultValue: Int32 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularInt32Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedInt32Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularInt32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedInt32Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// Int64
///
public struct ProtobufInt64: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Int64
public static var proto3DefaultValue: Int64 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularInt64Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedInt64Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularInt64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedInt64Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// UInt32
///
public struct ProtobufUInt32: FieldType, MapKeyType, MapValueType {
public typealias BaseType = UInt32
public static var proto3DefaultValue: UInt32 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularUInt32Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedUInt32Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularUInt32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedUInt32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedUInt32Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// UInt64
///
public struct ProtobufUInt64: FieldType, MapKeyType, MapValueType {
public typealias BaseType = UInt64
public static var proto3DefaultValue: UInt64 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularUInt64Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedUInt64Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularUInt64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedUInt64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedUInt64Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// SInt32
///
public struct ProtobufSInt32: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Int32
public static var proto3DefaultValue: Int32 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularSInt32Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedSInt32Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularSInt32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedSInt32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedSInt32Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// SInt64
///
public struct ProtobufSInt64: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Int64
public static var proto3DefaultValue: Int64 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularSInt64Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedSInt64Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularSInt64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedSInt64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedSInt64Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// Fixed32
///
public struct ProtobufFixed32: FieldType, MapKeyType, MapValueType {
public typealias BaseType = UInt32
public static var proto3DefaultValue: UInt32 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularFixed32Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedFixed32Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularFixed32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedFixed32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedFixed32Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// Fixed64
///
public struct ProtobufFixed64: FieldType, MapKeyType, MapValueType {
public typealias BaseType = UInt64
public static var proto3DefaultValue: UInt64 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularFixed64Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedFixed64Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularFixed64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedFixed64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedFixed64Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// SFixed32
///
public struct ProtobufSFixed32: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Int32
public static var proto3DefaultValue: Int32 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularSFixed32Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedSFixed32Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularSFixed32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedSFixed32Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedSFixed32Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// SFixed64
///
public struct ProtobufSFixed64: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Int64
public static var proto3DefaultValue: Int64 {return 0}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularSFixed64Field(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedSFixed64Field(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularSFixed64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedSFixed64Field(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedSFixed64Field(value: value, fieldNumber: fieldNumber)
}
}
///
/// Bool
///
public struct ProtobufBool: FieldType, MapKeyType, MapValueType {
public typealias BaseType = Bool
public static var proto3DefaultValue: Bool {return false}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularBoolField(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedBoolField(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularBoolField(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedBoolField(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitPackedBoolField(value: value, fieldNumber: fieldNumber)
}
/// Custom _lessThan since `Bool` isn't `Comparable`.
public static func _lessThan(lhs: BaseType, rhs: BaseType) -> Bool {
if !lhs {
return rhs
}
return false
}
}
///
/// String
///
public struct ProtobufString: FieldType, MapKeyType, MapValueType {
public typealias BaseType = String
public static var proto3DefaultValue: String {return String()}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularStringField(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedStringField(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularStringField(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedStringField(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
assert(false)
}
}
///
/// Bytes
///
public struct ProtobufBytes: FieldType, MapValueType {
public typealias BaseType = Data
public static var proto3DefaultValue: Data {return Data()}
public static func decodeSingular<D: Decoder>(value: inout BaseType?, from decoder: inout D) throws {
try decoder.decodeSingularBytesField(value: &value)
}
public static func decodeRepeated<D: Decoder>(value: inout [BaseType], from decoder: inout D) throws {
try decoder.decodeRepeatedBytesField(value: &value)
}
public static func visitSingular<V: Visitor>(value: BaseType, fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitSingularBytesField(value: value, fieldNumber: fieldNumber)
}
public static func visitRepeated<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
try visitor.visitRepeatedBytesField(value: value, fieldNumber: fieldNumber)
}
public static func visitPacked<V: Visitor>(value: [BaseType], fieldNumber: Int, with visitor: inout V) throws {
assert(false)
}
}

View File

@ -0,0 +1,174 @@
// Sources/SwiftProtobuf/Google_Protobuf_Any+Extensions.swift - Well-known Any type
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extends the `Google_Protobuf_Any` type with various custom behaviors.
///
// -----------------------------------------------------------------------------
// Explicit import of Foundation is necessary on Linux,
// don't remove unless obsolete on all platforms
import Foundation
public let defaultAnyTypeURLPrefix: String = "type.googleapis.com"
extension Google_Protobuf_Any {
/// Initialize an Any object from the provided message.
///
/// This corresponds to the `pack` operation in the C++ API.
///
/// Unlike the C++ implementation, the message is not immediately
/// serialized; it is merely stored until the Any object itself
/// needs to be serialized. This design avoids unnecessary
/// decoding/recoding when writing JSON format.
///
/// - Parameters:
/// - 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`.
/// - typePrefix: The prefix to be used when building the `type_url`.
/// Defaults to "type.googleapis.com".
/// - Throws: `BinaryEncodingError.missingRequiredFields` if `partial` is
/// false and `message` wasn't fully initialized.
public init(
message: Message,
partial: Bool = false,
typePrefix: String = defaultAnyTypeURLPrefix
) throws {
if !partial && !message.isInitialized {
throw BinaryEncodingError.missingRequiredFields
}
self.init()
typeURL = buildTypeURL(forMessage:message, typePrefix: typePrefix)
_storage.state = .message(message)
}
/// Creates a new `Google_Protobuf_Any` by decoding the given string
/// containing a serialized message in Protocol Buffer text format.
///
/// - Parameters:
/// - textFormatString: The text format string to decode.
/// - extensions: An `ExtensionMap` used to look up and decode any
/// extensions in this message or messages nested within this message's
/// fields.
/// - Throws: an instance of `TextFormatDecodingError` on failure.
public init(
textFormatString: String,
extensions: ExtensionMap? = nil
) throws {
// TODO: Remove this api and default the options instead. This api has to
// exist for anything compiled against an older version of the library.
try self.init(textFormatString: textFormatString,
options: TextFormatDecodingOptions(),
extensions: extensions)
}
/// Creates a new `Google_Protobuf_Any` by decoding the given string
/// containing a serialized message in Protocol Buffer text format.
///
/// - Parameters:
/// - textFormatString: The text format string to decode.
/// - options: The `TextFormatDencodingOptions` to use.
/// - extensions: An `ExtensionMap` used to look up and decode any
/// extensions in this message or messages nested within this message's
/// fields.
/// - Throws: an instance of `TextFormatDecodingError` on failure.
public init(
textFormatString: String,
options: TextFormatDecodingOptions,
extensions: ExtensionMap? = nil
) throws {
self.init()
if !textFormatString.isEmpty {
if let data = textFormatString.data(using: String.Encoding.utf8) {
try data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
if let baseAddress = body.baseAddress, body.count > 0 {
var textDecoder = try TextFormatDecoder(
messageType: Google_Protobuf_Any.self,
utf8Pointer: baseAddress,
count: body.count,
options: options,
extensions: extensions)
try decodeTextFormat(decoder: &textDecoder)
if !textDecoder.complete {
throw TextFormatDecodingError.trailingGarbage
}
}
}
}
}
}
/// Returns true if this `Google_Protobuf_Any` message contains the given
/// message type.
///
/// The check is performed by looking at the passed `Message.Type` and the
/// `typeURL` of this message.
///
/// - Parameter type: The concrete message type.
/// - Returns: True if the receiver contains the given message type.
public func isA<M: Message>(_ type: M.Type) -> Bool {
return _storage.isA(type)
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
_storage.hash(into: &hasher)
}
#else // swift(>=4.2)
public var hashValue: Int {
return _storage.hashValue
}
#endif // swift(>=4.2)
}
extension Google_Protobuf_Any {
internal func textTraverse(visitor: inout TextFormatEncodingVisitor) {
_storage.textTraverse(visitor: &visitor)
try! unknownFields.traverse(visitor: &visitor)
}
}
extension Google_Protobuf_Any {
// Custom text format decoding support for Any objects.
// (Note: This is not a part of any protocol; it's invoked
// directly from TextFormatDecoder whenever it sees an attempt
// to decode an Any object)
internal mutating func decodeTextFormat(
decoder: inout TextFormatDecoder
) throws {
// First, check if this uses the "verbose" Any encoding.
// If it does, and we have the type available, we can
// eagerly decode the contained Message object.
if let url = try decoder.scanner.nextOptionalAnyURL() {
try _uniqueStorage().decodeTextFormat(typeURL: url, decoder: &decoder)
} else {
// This is not using the specialized encoding, so we can use the
// standard path to decode the binary value.
// First, clear the fields so we don't waste time re-serializing
// the previous contents as this instances get replaced with a
// new value (can happen when a field name/number is repeated in
// the TextFormat input).
self.typeURL = ""
self.value = Data()
try decodeMessage(decoder: &decoder)
}
}
}
extension Google_Protobuf_Any: _CustomJSONCodable {
internal func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return try _storage.encodedJSONString(options: options)
}
internal mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
try _uniqueStorage().decodeJSON(from: &decoder)
}
}

View File

@ -0,0 +1,137 @@
// Sources/SwiftProtobuf/Google_Protobuf_Any+Registry.swift - Registry for JSON 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
//
// -----------------------------------------------------------------------------
///
/// Support for registering and looking up Message types. Used
/// in support of Google_Protobuf_Any.
///
// -----------------------------------------------------------------------------
import Foundation
import Dispatch
// TODO: Should these first four be exposed as methods to go with
// the general registry support?
internal func buildTypeURL(forMessage message: Message, typePrefix: String) -> String {
var url = typePrefix
let needsSlash = typePrefix.isEmpty || typePrefix.last != "/"
if needsSlash {
url += "/"
}
return url + typeName(fromMessage: message)
}
internal func typeName(fromMessage message: Message) -> String {
let messageType = type(of: message)
return messageType.protoMessageName
}
internal func typeName(fromURL s: String) -> String {
var typeStart = s.startIndex
var i = typeStart
while i < s.endIndex {
let c = s[i]
i = s.index(after: i)
if c == "/" {
typeStart = i
}
}
return String(s[typeStart..<s.endIndex])
}
fileprivate var knownTypesQueue =
DispatchQueue(label: "org.swift.protobuf.typeRegistry",
attributes: .concurrent)
// All access to this should be done on `knownTypesQueue`.
fileprivate var knownTypes: [String:Message.Type] = [
// Seeded with the Well Known Types.
"google.protobuf.Any": Google_Protobuf_Any.self,
"google.protobuf.BoolValue": Google_Protobuf_BoolValue.self,
"google.protobuf.BytesValue": Google_Protobuf_BytesValue.self,
"google.protobuf.DoubleValue": Google_Protobuf_DoubleValue.self,
"google.protobuf.Duration": Google_Protobuf_Duration.self,
"google.protobuf.Empty": Google_Protobuf_Empty.self,
"google.protobuf.FieldMask": Google_Protobuf_FieldMask.self,
"google.protobuf.FloatValue": Google_Protobuf_FloatValue.self,
"google.protobuf.Int32Value": Google_Protobuf_Int32Value.self,
"google.protobuf.Int64Value": Google_Protobuf_Int64Value.self,
"google.protobuf.ListValue": Google_Protobuf_ListValue.self,
"google.protobuf.StringValue": Google_Protobuf_StringValue.self,
"google.protobuf.Struct": Google_Protobuf_Struct.self,
"google.protobuf.Timestamp": Google_Protobuf_Timestamp.self,
"google.protobuf.UInt32Value": Google_Protobuf_UInt32Value.self,
"google.protobuf.UInt64Value": Google_Protobuf_UInt64Value.self,
"google.protobuf.Value": Google_Protobuf_Value.self,
]
extension Google_Protobuf_Any {
/// Register a message type so that Any objects can use
/// them for decoding contents.
///
/// This is currently only required in two cases:
///
/// * When decoding Protobuf Text format. Currently,
/// Any objects do not defer deserialization from Text
/// format. Depending on how the Any objects are stored
/// in text format, the Any object may need to look up
/// the message type in order to deserialize itself.
///
/// * When re-encoding an Any object into a different
/// format than it was decoded from. For example, if
/// you decode a message containing an Any object from
/// JSON format and then re-encode the message into Protobuf
/// Binary format, the Any object will need to complete the
/// deferred deserialization of the JSON object before it
/// can re-encode.
///
/// Note that well-known types are pre-registered for you and
/// you do not need to register them from your code.
///
/// Also note that this is not needed if you only decode and encode
/// to and from the same format.
///
/// Returns: true if the type was registered, false if something
/// else was already registered for the messageName.
@discardableResult public static func register(messageType: Message.Type) -> Bool {
let messageTypeName = messageType.protoMessageName
var result: Bool = false
knownTypesQueue.sync(flags: .barrier) {
if let alreadyRegistered = knownTypes[messageTypeName] {
// Success/failure when something was already registered is
// based on if they are registering the same class or trying
// to register a different type
result = alreadyRegistered == messageType
} else {
knownTypes[messageTypeName] = messageType
result = true
}
}
return result
}
/// Returns the Message.Type expected for the given type URL.
public static func messageType(forTypeURL url: String) -> Message.Type? {
let messageTypeName = typeName(fromURL: url)
return messageType(forMessageName: messageTypeName)
}
/// Returns the Message.Type expected for the given proto message name.
public static func messageType(forMessageName name: String) -> Message.Type? {
var result: Message.Type?
knownTypesQueue.sync {
result = knownTypes[name]
}
return result
}
}

View File

@ -0,0 +1,226 @@
// Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift - Extensions for Duration type
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extends the generated Duration struct with various custom behaviors:
/// * JSON coding and decoding
/// * Arithmetic operations
///
// -----------------------------------------------------------------------------
import Foundation
private let minDurationSeconds: Int64 = -maxDurationSeconds
private let maxDurationSeconds: Int64 = 315576000000
private func parseDuration(text: String) throws -> (Int64, Int32) {
var digits = [Character]()
var digitCount = 0
var total = 0
var chars = text.makeIterator()
var seconds: Int64?
var nanos: Int32 = 0
while let c = chars.next() {
switch c {
case "-":
// Only accept '-' as very first character
if total > 0 {
throw JSONDecodingError.malformedDuration
}
digits.append(c)
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
digits.append(c)
digitCount += 1
case ".":
if let _ = seconds {
throw JSONDecodingError.malformedDuration
}
let digitString = String(digits)
if let s = Int64(digitString),
s >= minDurationSeconds && s <= maxDurationSeconds {
seconds = s
} else {
throw JSONDecodingError.malformedDuration
}
digits.removeAll()
digitCount = 0
case "s":
if let seconds = seconds {
// Seconds already set, digits holds nanos
while (digitCount < 9) {
digits.append(Character("0"))
digitCount += 1
}
while digitCount > 9 {
digits.removeLast()
digitCount -= 1
}
let digitString = String(digits)
if let rawNanos = Int32(digitString) {
if seconds < 0 {
nanos = -rawNanos
} else {
nanos = rawNanos
}
} else {
throw JSONDecodingError.malformedDuration
}
} else {
// No fraction, we just have an integral number of seconds
let digitString = String(digits)
if let s = Int64(digitString),
s >= minDurationSeconds && s <= maxDurationSeconds {
seconds = s
} else {
throw JSONDecodingError.malformedDuration
}
}
// Fail if there are characters after 's'
if chars.next() != nil {
throw JSONDecodingError.malformedDuration
}
return (seconds!, nanos)
default:
throw JSONDecodingError.malformedDuration
}
total += 1
}
throw JSONDecodingError.malformedDuration
}
private func formatDuration(seconds: Int64, nanos: Int32) -> String? {
let (seconds, nanos) = normalizeForDuration(seconds: seconds, nanos: nanos)
guard seconds >= minDurationSeconds && seconds <= maxDurationSeconds else {
return nil
}
let nanosString = nanosToString(nanos: nanos) // Includes leading '.' if needed
return "\(seconds)\(nanosString)s"
}
extension Google_Protobuf_Duration {
/// Creates a new `Google_Protobuf_Duration` equal to the given number of
/// seconds and nanoseconds.
///
/// - Parameter seconds: The number of seconds.
/// - Parameter nanos: The number of nanoseconds.
public init(seconds: Int64 = 0, nanos: Int32 = 0) {
self.init()
self.seconds = seconds
self.nanos = nanos
}
}
extension Google_Protobuf_Duration: _CustomJSONCodable {
mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
let s = try decoder.scanner.nextQuotedString()
(seconds, nanos) = try parseDuration(text: s)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
if let formatted = formatDuration(seconds: seconds, nanos: nanos) {
return "\"\(formatted)\""
} else {
throw JSONEncodingError.durationRange
}
}
}
extension Google_Protobuf_Duration: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Double
/// Creates a new `Google_Protobuf_Duration` from a floating point literal
/// that is interpreted as a duration in seconds, rounded to the nearest
/// nanosecond.
public init(floatLiteral value: Double) {
let sd = trunc(value)
let nd = round((value - sd) * TimeInterval(nanosPerSecond))
let (s, n) = normalizeForDuration(seconds: Int64(sd), nanos: Int32(nd))
self.init(seconds: s, nanos: n)
}
}
extension Google_Protobuf_Duration {
/// Creates a new `Google_Protobuf_Duration` that is equal to the given
/// `TimeInterval` (measured in seconds), rounded to the nearest nanosecond.
///
/// - Parameter timeInterval: The `TimeInterval`.
public init(timeInterval: TimeInterval) {
let sd = trunc(timeInterval)
let nd = round((timeInterval - sd) * TimeInterval(nanosPerSecond))
let (s, n) = normalizeForDuration(seconds: Int64(sd), nanos: Int32(nd))
self.init(seconds: s, nanos: n)
}
/// The `TimeInterval` (measured in seconds) equal to this duration.
public var timeInterval: TimeInterval {
return TimeInterval(self.seconds) +
TimeInterval(self.nanos) / TimeInterval(nanosPerSecond)
}
}
private func normalizeForDuration(
seconds: Int64,
nanos: Int32
) -> (seconds: Int64, nanos: Int32) {
var s = seconds
var n = nanos
// If the magnitude of n exceeds a second then
// we need to factor it into s instead.
if n >= nanosPerSecond || n <= -nanosPerSecond {
s += Int64(n / nanosPerSecond)
n = n % nanosPerSecond
}
// The Duration spec says that when s != 0, s and
// n must have the same sign.
if s > 0 && n < 0 {
n += nanosPerSecond
s -= 1
} else if s < 0 && n > 0 {
n -= nanosPerSecond
s += 1
}
return (seconds: s, nanos: n)
}
public prefix func - (
operand: Google_Protobuf_Duration
) -> Google_Protobuf_Duration {
let (s, n) = normalizeForDuration(seconds: -operand.seconds,
nanos: -operand.nanos)
return Google_Protobuf_Duration(seconds: s, nanos: n)
}
public func + (
lhs: Google_Protobuf_Duration,
rhs: Google_Protobuf_Duration
) -> Google_Protobuf_Duration {
let (s, n) = normalizeForDuration(seconds: lhs.seconds + rhs.seconds,
nanos: lhs.nanos + rhs.nanos)
return Google_Protobuf_Duration(seconds: s, nanos: n)
}
public func - (
lhs: Google_Protobuf_Duration,
rhs: Google_Protobuf_Duration
) -> Google_Protobuf_Duration {
let (s, n) = normalizeForDuration(seconds: lhs.seconds - rhs.seconds,
nanos: lhs.nanos - rhs.nanos)
return Google_Protobuf_Duration(seconds: s, nanos: n)
}
public func - (
lhs: Google_Protobuf_Timestamp,
rhs: Google_Protobuf_Timestamp
) -> Google_Protobuf_Duration {
let (s, n) = normalizeForDuration(seconds: lhs.seconds - rhs.seconds,
nanos: lhs.nanos - rhs.nanos)
return Google_Protobuf_Duration(seconds: s, nanos: n)
}

View File

@ -0,0 +1,163 @@
// Sources/SwiftProtobuf/Google_Protobuf_FieldMask+Extensions.swift - Fieldmask extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extend the generated FieldMask message with customized JSON coding and
/// convenience methods.
///
// -----------------------------------------------------------------------------
// TODO: We should have utilities to apply a fieldmask to an arbitrary
// message, intersect two fieldmasks, etc.
private func ProtoToJSON(name: String) -> String? {
var jsonPath = String()
var chars = name.makeIterator()
while let c = chars.next() {
switch c {
case "_":
if let toupper = chars.next() {
switch toupper {
case "a"..."z":
jsonPath.append(String(toupper).uppercased())
default:
return nil
}
} else {
return nil
}
case "A"..."Z":
return nil
default:
jsonPath.append(c)
}
}
return jsonPath
}
private func JSONToProto(name: String) -> String? {
var path = String()
for c in name {
switch c {
case "_":
return nil
case "A"..."Z":
path.append(Character("_"))
path.append(String(c).lowercased())
default:
path.append(c)
}
}
return path
}
private func parseJSONFieldNames(names: String) -> [String]? {
// An empty field mask is the empty string (no paths).
guard !names.isEmpty else { return [] }
var fieldNameCount = 0
var fieldName = String()
var split = [String]()
for c in names {
switch c {
case ",":
if fieldNameCount == 0 {
return nil
}
if let pbName = JSONToProto(name: fieldName) {
split.append(pbName)
} else {
return nil
}
fieldName = String()
fieldNameCount = 0
default:
fieldName.append(c)
fieldNameCount += 1
}
}
if fieldNameCount == 0 { // Last field name can't be empty
return nil
}
if let pbName = JSONToProto(name: fieldName) {
split.append(pbName)
} else {
return nil
}
return split
}
extension Google_Protobuf_FieldMask {
/// Creates a new `Google_Protobuf_FieldMask` from the given array of paths.
///
/// The paths should match the names used in the .proto file, which may be
/// different than the corresponding Swift property names.
///
/// - Parameter protoPaths: The paths from which to create the field mask,
/// defined using the .proto names for the fields.
public init(protoPaths: [String]) {
self.init()
paths = protoPaths
}
/// Creates a new `Google_Protobuf_FieldMask` from the given paths.
///
/// The paths should match the names used in the .proto file, which may be
/// different than the corresponding Swift property names.
///
/// - Parameter protoPaths: The paths from which to create the field mask,
/// defined using the .proto names for the fields.
public init(protoPaths: String...) {
self.init(protoPaths: protoPaths)
}
/// Creates a new `Google_Protobuf_FieldMask` from the given paths.
///
/// The paths should match the JSON names of the fields, which may be
/// different than the corresponding Swift property names.
///
/// - Parameter jsonPaths: The paths from which to create the field mask,
/// defined using the JSON names for the fields.
public init?(jsonPaths: String...) {
// TODO: This should fail if any of the conversions from JSON fails
#if swift(>=4.1)
self.init(protoPaths: jsonPaths.compactMap(JSONToProto))
#else
self.init(protoPaths: jsonPaths.flatMap(JSONToProto))
#endif
}
// It would be nice if to have an initializer that accepted Swift property
// names, but translating between swift and protobuf/json property
// names is not entirely deterministic.
}
extension Google_Protobuf_FieldMask: _CustomJSONCodable {
mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
let s = try decoder.scanner.nextQuotedString()
if let names = parseJSONFieldNames(names: s) {
paths = names
} else {
throw JSONDecodingError.malformedFieldMask
}
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
// Note: Proto requires alphanumeric field names, so there
// cannot be a ',' or '"' character to mess up this formatting.
var jsonPaths = [String]()
for p in paths {
if let jsonPath = ProtoToJSON(name: p) {
jsonPaths.append(jsonPath)
} else {
throw JSONEncodingError.fieldMaskConversion
}
}
return "\"" + jsonPaths.joined(separator: ",") + "\""
}
}

View File

@ -0,0 +1,80 @@
// Sources/SwiftProtobuf/Google_Protobuf_ListValue+Extensions.swift - ListValue extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// ListValue is a well-known message type that can be used to parse or encode
/// arbitrary JSON arrays without a predefined schema.
///
// -----------------------------------------------------------------------------
extension Google_Protobuf_ListValue: ExpressibleByArrayLiteral {
// TODO: Give this a direct array interface by proxying the interesting
// bits down to values
public typealias Element = Google_Protobuf_Value
/// Creates a new `Google_Protobuf_ListValue` from an array literal containing
/// `Google_Protobuf_Value` elements.
public init(arrayLiteral elements: Element...) {
self.init(values: elements)
}
}
extension Google_Protobuf_ListValue: _CustomJSONCodable {
internal func encodedJSONString(options: JSONEncodingOptions) throws -> String {
var jsonEncoder = JSONEncoder()
jsonEncoder.append(text: "[")
var separator: StaticString = ""
for v in values {
jsonEncoder.append(staticText: separator)
try v.serializeJSONValue(to: &jsonEncoder, options: options)
separator = ","
}
jsonEncoder.append(text: "]")
return jsonEncoder.stringResult
}
internal mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
if decoder.scanner.skipOptionalNull() {
return
}
try decoder.scanner.skipRequiredArrayStart()
if decoder.scanner.skipOptionalArrayEnd() {
return
}
while true {
var v = Google_Protobuf_Value()
try v.decodeJSON(from: &decoder)
values.append(v)
if decoder.scanner.skipOptionalArrayEnd() {
return
}
try decoder.scanner.skipRequiredComma()
}
}
}
extension Google_Protobuf_ListValue {
/// Creates a new `Google_Protobuf_ListValue` from the given array of
/// `Google_Protobuf_Value` elements.
///
/// - Parameter values: The list of `Google_Protobuf_Value` messages from
/// which to create the `Google_Protobuf_ListValue`.
public init(values: [Google_Protobuf_Value]) {
self.init()
self.values = values
}
/// Accesses the `Google_Protobuf_Value` at the specified position.
///
/// - Parameter index: The position of the element to access.
public subscript(index: Int) -> Google_Protobuf_Value {
get {return values[index]}
set(newValue) {values[index] = newValue}
}
}

View File

@ -0,0 +1,28 @@
// Sources/SwiftProtobuf/Google_Protobuf_NullValue+Extensions.swift - NullValue extensions
//
// Copyright (c) 2014 - 2020 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
//
// -----------------------------------------------------------------------------
///
/// NullValue is a well-known message type that can be used to parse or encode
/// JSON Null values.
///
// -----------------------------------------------------------------------------
extension Google_Protobuf_NullValue: _CustomJSONCodable {
internal func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return "null"
}
internal mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
if decoder.scanner.skipOptionalNull() {
return
}
}
static func decodedFromJSONNull() -> Google_Protobuf_NullValue? {
return .nullValue
}
}

View File

@ -0,0 +1,85 @@
// Sources/SwiftProtobuf/Google_Protobuf_Struct+Extensions.swift - Struct extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Struct is a well-known message type that can be used to parse or encode
/// arbitrary JSON objects without a predefined schema.
///
// -----------------------------------------------------------------------------
extension Google_Protobuf_Struct: ExpressibleByDictionaryLiteral {
public typealias Key = String
public typealias Value = Google_Protobuf_Value
/// Creates a new `Google_Protobuf_Struct` from a dictionary of string keys to
/// values of type `Google_Protobuf_Value`.
public init(dictionaryLiteral: (String, Google_Protobuf_Value)...) {
self.init()
for (k,v) in dictionaryLiteral {
fields[k] = v
}
}
}
extension Google_Protobuf_Struct: _CustomJSONCodable {
internal func encodedJSONString(options: JSONEncodingOptions) throws -> String {
var jsonEncoder = JSONEncoder()
jsonEncoder.startObject()
var mapVisitor = JSONMapEncodingVisitor(encoder: jsonEncoder, options: options)
for (k,v) in fields {
try mapVisitor.visitSingularStringField(value: k, fieldNumber: 1)
try mapVisitor.visitSingularMessageField(value: v, fieldNumber: 2)
}
mapVisitor.encoder.endObject()
return mapVisitor.encoder.stringResult
}
internal mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
try decoder.scanner.skipRequiredObjectStart()
if decoder.scanner.skipOptionalObjectEnd() {
return
}
while true {
let key = try decoder.scanner.nextQuotedString()
try decoder.scanner.skipRequiredColon()
var value = Google_Protobuf_Value()
try value.decodeJSON(from: &decoder)
fields[key] = value
if decoder.scanner.skipOptionalObjectEnd() {
return
}
try decoder.scanner.skipRequiredComma()
}
}
}
extension Google_Protobuf_Struct {
/// Creates a new `Google_Protobuf_Struct` from a dictionary of string keys to
/// values of type `Google_Protobuf_Value`.
///
/// - Parameter fields: The dictionary from field names to
/// `Google_Protobuf_Value` messages that should be used to create the
/// `Struct`.
public init(fields: [String: Google_Protobuf_Value]) {
self.init()
self.fields = fields
}
/// Accesses the `Google_Protobuf_Value` with the given key for reading and
/// writing.
///
/// This key-based subscript returns the `Value` for the given key if the key
/// is found in the `Struct`, or nil if the key is not found. If you assign
/// nil as the `Value` for the given key, the `Struct` removes that key and
/// its associated `Value`.
public subscript(key: String) -> Google_Protobuf_Value? {
get {return fields[key]}
set(newValue) {fields[key] = newValue}
}
}

View File

@ -0,0 +1,329 @@
// Sources/SwiftProtobuf/Google_Protobuf_Timestamp+Extensions.swift - Timestamp extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extend the generated Timestamp message with customized JSON coding,
/// arithmetic operations, and convenience methods.
///
// -----------------------------------------------------------------------------
import Foundation
private let minTimestampSeconds: Int64 = -62135596800 // 0001-01-01T00:00:00Z
private let maxTimestampSeconds: Int64 = 253402300799 // 9999-12-31T23:59:59Z
// TODO: Add convenience methods to interoperate with standard
// date/time classes: an initializer that accepts Unix timestamp as
// Int or Double, an easy way to convert to/from Foundation's
// NSDateTime (on Apple platforms only?), others?
// Parse an RFC3339 timestamp into a pair of seconds-since-1970 and nanos.
private func parseTimestamp(s: String) throws -> (Int64, Int32) {
// Convert to an array of integer character values
let value = s.utf8.map{Int($0)}
if value.count < 20 {
throw JSONDecodingError.malformedTimestamp
}
// Since the format is fixed-layout, we can just decode
// directly as follows.
let zero = Int(48)
let nine = Int(57)
let dash = Int(45)
let colon = Int(58)
let plus = Int(43)
let letterT = Int(84)
let letterZ = Int(90)
let period = Int(46)
func fromAscii2(_ digit0: Int, _ digit1: Int) throws -> Int {
if digit0 < zero || digit0 > nine || digit1 < zero || digit1 > nine {
throw JSONDecodingError.malformedTimestamp
}
return digit0 * 10 + digit1 - 528
}
func fromAscii4(
_ digit0: Int,
_ digit1: Int,
_ digit2: Int,
_ digit3: Int
) throws -> Int {
if (digit0 < zero || digit0 > nine
|| digit1 < zero || digit1 > nine
|| digit2 < zero || digit2 > nine
|| digit3 < zero || digit3 > nine) {
throw JSONDecodingError.malformedTimestamp
}
return digit0 * 1000 + digit1 * 100 + digit2 * 10 + digit3 - 53328
}
// Year: 4 digits followed by '-'
let year = try fromAscii4(value[0], value[1], value[2], value[3])
if value[4] != dash || year < Int(1) || year > Int(9999) {
throw JSONDecodingError.malformedTimestamp
}
// Month: 2 digits followed by '-'
let month = try fromAscii2(value[5], value[6])
if value[7] != dash || month < Int(1) || month > Int(12) {
throw JSONDecodingError.malformedTimestamp
}
// Day: 2 digits followed by 'T'
let mday = try fromAscii2(value[8], value[9])
if value[10] != letterT || mday < Int(1) || mday > Int(31) {
throw JSONDecodingError.malformedTimestamp
}
// Hour: 2 digits followed by ':'
let hour = try fromAscii2(value[11], value[12])
if value[13] != colon || hour > Int(23) {
throw JSONDecodingError.malformedTimestamp
}
// Minute: 2 digits followed by ':'
let minute = try fromAscii2(value[14], value[15])
if value[16] != colon || minute > Int(59) {
throw JSONDecodingError.malformedTimestamp
}
// Second: 2 digits (following char is checked below)
let second = try fromAscii2(value[17], value[18])
if second > Int(61) {
throw JSONDecodingError.malformedTimestamp
}
// timegm() is almost entirely useless. It's nonexistent on
// some platforms, broken on others. Everything else I've tried
// is even worse. Hence the code below.
// (If you have a better way to do this, try it and see if it
// passes the test suite on both Linux and OS X.)
// Day of year
let mdayStart: [Int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
var yday = Int64(mdayStart[month - 1])
let isleap = (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0))
if isleap && (month > 2) {
yday += 1
}
yday += Int64(mday - 1)
// Days since start of epoch (including leap days)
var daysSinceEpoch = yday
daysSinceEpoch += Int64(365 * year) - Int64(719527)
daysSinceEpoch += Int64((year - 1) / 4)
daysSinceEpoch -= Int64((year - 1) / 100)
daysSinceEpoch += Int64((year - 1) / 400)
// Second within day
var daySec = Int64(hour)
daySec *= 60
daySec += Int64(minute)
daySec *= 60
daySec += Int64(second)
// Seconds since start of epoch
let t = daysSinceEpoch * Int64(86400) + daySec
// After seconds, comes various optional bits
var pos = 19
var nanos: Int32 = 0
if value[pos] == period { // "." begins fractional seconds
pos += 1
var digitValue = 100000000
while pos < value.count && value[pos] >= zero && value[pos] <= nine {
nanos += Int32(digitValue * (value[pos] - zero))
digitValue /= 10
pos += 1
}
}
var seconds: Int64 = 0
// "+" or "-" starts Timezone offset
if value[pos] == plus || value[pos] == dash {
if pos + 6 > value.count {
throw JSONDecodingError.malformedTimestamp
}
let hourOffset = try fromAscii2(value[pos + 1], value[pos + 2])
let minuteOffset = try fromAscii2(value[pos + 4], value[pos + 5])
if hourOffset > Int(13) || minuteOffset > Int(59) || value[pos + 3] != colon {
throw JSONDecodingError.malformedTimestamp
}
var adjusted: Int64 = t
if value[pos] == plus {
adjusted -= Int64(hourOffset) * Int64(3600)
adjusted -= Int64(minuteOffset) * Int64(60)
} else {
adjusted += Int64(hourOffset) * Int64(3600)
adjusted += Int64(minuteOffset) * Int64(60)
}
if adjusted < minTimestampSeconds || adjusted > maxTimestampSeconds {
throw JSONDecodingError.malformedTimestamp
}
seconds = adjusted
pos += 6
} else if value[pos] == letterZ { // "Z" indicator for UTC
seconds = t
pos += 1
} else {
throw JSONDecodingError.malformedTimestamp
}
if pos != value.count {
throw JSONDecodingError.malformedTimestamp
}
return (seconds, nanos)
}
private func formatTimestamp(seconds: Int64, nanos: Int32) -> String? {
let (seconds, nanos) = normalizeForTimestamp(seconds: seconds, nanos: nanos)
guard seconds >= minTimestampSeconds && seconds <= maxTimestampSeconds else {
return nil
}
let (hh, mm, ss) = timeOfDayFromSecondsSince1970(seconds: seconds)
let (YY, MM, DD) = gregorianDateFromSecondsSince1970(seconds: seconds)
let dateString = "\(fourDigit(YY))-\(twoDigit(MM))-\(twoDigit(DD))"
let timeString = "\(twoDigit(hh)):\(twoDigit(mm)):\(twoDigit(ss))"
let nanosString = nanosToString(nanos: nanos) // Includes leading '.' if needed
return "\(dateString)T\(timeString)\(nanosString)Z"
}
extension Google_Protobuf_Timestamp {
/// Creates a new `Google_Protobuf_Timestamp` equal to the given number of
/// seconds and nanoseconds.
///
/// - Parameter seconds: The number of seconds.
/// - Parameter nanos: The number of nanoseconds.
public init(seconds: Int64 = 0, nanos: Int32 = 0) {
self.init()
self.seconds = seconds
self.nanos = nanos
}
}
extension Google_Protobuf_Timestamp: _CustomJSONCodable {
mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
let s = try decoder.scanner.nextQuotedString()
(seconds, nanos) = try parseTimestamp(s: s)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
if let formatted = formatTimestamp(seconds: seconds, nanos: nanos) {
return "\"\(formatted)\""
} else {
throw JSONEncodingError.timestampRange
}
}
}
extension Google_Protobuf_Timestamp {
/// Creates a new `Google_Protobuf_Timestamp` initialized relative to 00:00:00
/// UTC on 1 January 1970 by a given number of seconds.
///
/// - Parameter timeIntervalSince1970: The `TimeInterval`, interpreted as
/// seconds relative to 00:00:00 UTC on 1 January 1970.
public init(timeIntervalSince1970: TimeInterval) {
let sd = floor(timeIntervalSince1970)
let nd = round((timeIntervalSince1970 - sd) * TimeInterval(nanosPerSecond))
let (s, n) = normalizeForTimestamp(seconds: Int64(sd), nanos: Int32(nd))
self.init(seconds: s, nanos: n)
}
/// Creates a new `Google_Protobuf_Timestamp` initialized relative to 00:00:00
/// UTC on 1 January 2001 by a given number of seconds.
///
/// - Parameter timeIntervalSinceReferenceDate: The `TimeInterval`,
/// interpreted as seconds relative to 00:00:00 UTC on 1 January 2001.
public init(timeIntervalSinceReferenceDate: TimeInterval) {
let sd = floor(timeIntervalSinceReferenceDate)
let nd = round(
(timeIntervalSinceReferenceDate - sd) * TimeInterval(nanosPerSecond))
// The addition of timeIntervalBetween1970And... is deliberately delayed
// until the input is separated into an integer part and a fraction
// part, so that we don't unnecessarily lose precision.
let (s, n) = normalizeForTimestamp(
seconds: Int64(sd) + Int64(Date.timeIntervalBetween1970AndReferenceDate),
nanos: Int32(nd))
self.init(seconds: s, nanos: n)
}
/// Creates a new `Google_Protobuf_Timestamp` initialized to the same time as
/// the given `Date`.
///
/// - Parameter date: The `Date` with which to initialize the timestamp.
public init(date: Date) {
// Note: Internally, Date uses the "reference date," not the 1970 date.
// We use it when interacting with Dates so that Date doesn't perform
// any double arithmetic on our behalf, which might cost us precision.
self.init(
timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
}
/// The interval between the timestamp and 00:00:00 UTC on 1 January 1970.
public var timeIntervalSince1970: TimeInterval {
return TimeInterval(self.seconds) +
TimeInterval(self.nanos) / TimeInterval(nanosPerSecond)
}
/// The interval between the timestamp and 00:00:00 UTC on 1 January 2001.
public var timeIntervalSinceReferenceDate: TimeInterval {
return TimeInterval(
self.seconds - Int64(Date.timeIntervalBetween1970AndReferenceDate)) +
TimeInterval(self.nanos) / TimeInterval(nanosPerSecond)
}
/// A `Date` initialized to the same time as the timestamp.
public var date: Date {
return Date(
timeIntervalSinceReferenceDate: self.timeIntervalSinceReferenceDate)
}
}
private func normalizeForTimestamp(
seconds: Int64,
nanos: Int32
) -> (seconds: Int64, nanos: Int32) {
// The Timestamp spec says that nanos must be in the range [0, 999999999),
// as in actual modular arithmetic.
let s = seconds + Int64(div(nanos, nanosPerSecond))
let n = mod(nanos, nanosPerSecond)
return (seconds: s, nanos: n)
}
public func + (
lhs: Google_Protobuf_Timestamp,
rhs: Google_Protobuf_Duration
) -> Google_Protobuf_Timestamp {
let (s, n) = normalizeForTimestamp(seconds: lhs.seconds + rhs.seconds,
nanos: lhs.nanos + rhs.nanos)
return Google_Protobuf_Timestamp(seconds: s, nanos: n)
}
public func + (
lhs: Google_Protobuf_Duration,
rhs: Google_Protobuf_Timestamp
) -> Google_Protobuf_Timestamp {
let (s, n) = normalizeForTimestamp(seconds: lhs.seconds + rhs.seconds,
nanos: lhs.nanos + rhs.nanos)
return Google_Protobuf_Timestamp(seconds: s, nanos: n)
}
public func - (
lhs: Google_Protobuf_Timestamp,
rhs: Google_Protobuf_Duration
) -> Google_Protobuf_Timestamp {
let (s, n) = normalizeForTimestamp(seconds: lhs.seconds - rhs.seconds,
nanos: lhs.nanos - rhs.nanos)
return Google_Protobuf_Timestamp(seconds: s, nanos: n)
}

View File

@ -0,0 +1,163 @@
// Sources/SwiftProtobuf/Google_Protobuf_Value+Extensions.swift - Value extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Value is a well-known message type that can be used to parse or encode
/// arbitrary JSON without a predefined schema.
///
// -----------------------------------------------------------------------------
extension Google_Protobuf_Value: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Int64
/// Creates a new `Google_Protobuf_Value` from an integer literal.
public init(integerLiteral value: Int64) {
self.init(kind: .numberValue(Double(value)))
}
}
extension Google_Protobuf_Value: ExpressibleByFloatLiteral {
public typealias FloatLiteralType = Double
/// Creates a new `Google_Protobuf_Value` from a floating point literal.
public init(floatLiteral value: Double) {
self.init(kind: .numberValue(value))
}
}
extension Google_Protobuf_Value: ExpressibleByBooleanLiteral {
public typealias BooleanLiteralType = Bool
/// Creates a new `Google_Protobuf_Value` from a boolean literal.
public init(booleanLiteral value: Bool) {
self.init(kind: .boolValue(value))
}
}
extension Google_Protobuf_Value: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public typealias ExtendedGraphemeClusterLiteralType = String
public typealias UnicodeScalarLiteralType = String
/// Creates a new `Google_Protobuf_Value` from a string literal.
public init(stringLiteral value: String) {
self.init(kind: .stringValue(value))
}
/// Creates a new `Google_Protobuf_Value` from a Unicode scalar literal.
public init(unicodeScalarLiteral value: String) {
self.init(kind: .stringValue(value))
}
/// Creates a new `Google_Protobuf_Value` from a character literal.
public init(extendedGraphemeClusterLiteral value: String) {
self.init(kind: .stringValue(value))
}
}
extension Google_Protobuf_Value: ExpressibleByNilLiteral {
/// Creates a new `Google_Protobuf_Value` from the nil literal.
public init(nilLiteral: ()) {
self.init(kind: .nullValue(.nullValue))
}
}
extension Google_Protobuf_Value: _CustomJSONCodable {
internal func encodedJSONString(options: JSONEncodingOptions) throws -> String {
var jsonEncoder = JSONEncoder()
try serializeJSONValue(to: &jsonEncoder, options: options)
return jsonEncoder.stringResult
}
internal mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
let c = try decoder.scanner.peekOneCharacter()
switch c {
case "n":
if !decoder.scanner.skipOptionalNull() {
throw JSONDecodingError.failure
}
kind = .nullValue(.nullValue)
case "[":
var l = Google_Protobuf_ListValue()
try l.decodeJSON(from: &decoder)
kind = .listValue(l)
case "{":
var s = Google_Protobuf_Struct()
try s.decodeJSON(from: &decoder)
kind = .structValue(s)
case "t", "f":
let b = try decoder.scanner.nextBool()
kind = .boolValue(b)
case "\"":
let s = try decoder.scanner.nextQuotedString()
kind = .stringValue(s)
default:
let d = try decoder.scanner.nextDouble()
kind = .numberValue(d)
}
}
internal static func decodedFromJSONNull() -> Google_Protobuf_Value? {
return Google_Protobuf_Value(kind: .nullValue(.nullValue))
}
}
extension Google_Protobuf_Value {
/// Creates a new `Google_Protobuf_Value` with the given kind.
fileprivate init(kind: OneOf_Kind) {
self.init()
self.kind = kind
}
/// Creates a new `Google_Protobuf_Value` whose `kind` is `numberValue` with
/// the given floating-point value.
public init(numberValue: Double) {
self.init(kind: .numberValue(numberValue))
}
/// Creates a new `Google_Protobuf_Value` whose `kind` is `stringValue` with
/// the given string value.
public init(stringValue: String) {
self.init(kind: .stringValue(stringValue))
}
/// Creates a new `Google_Protobuf_Value` whose `kind` is `boolValue` with the
/// given boolean value.
public init(boolValue: Bool) {
self.init(kind: .boolValue(boolValue))
}
/// Creates a new `Google_Protobuf_Value` whose `kind` is `structValue` with
/// the given `Google_Protobuf_Struct` value.
public init(structValue: Google_Protobuf_Struct) {
self.init(kind: .structValue(structValue))
}
/// Creates a new `Google_Protobuf_Value` whose `kind` is `listValue` with the
/// given `Google_Struct_ListValue` value.
public init(listValue: Google_Protobuf_ListValue) {
self.init(kind: .listValue(listValue))
}
/// Writes out the JSON representation of the value to the given encoder.
internal func serializeJSONValue(
to encoder: inout JSONEncoder,
options: JSONEncodingOptions
) throws {
switch kind {
case .nullValue?: encoder.putNullValue()
case .numberValue(let v)?: encoder.putDoubleValue(value: v)
case .stringValue(let v)?: encoder.putStringValue(value: v)
case .boolValue(let v)?: encoder.putBoolValue(value: v)
case .structValue(let v)?: encoder.append(text: try v.jsonString(options: options))
case .listValue(let v)?: encoder.append(text: try v.jsonString(options: options))
case nil: throw JSONEncodingError.missingValue
}
}
}

View File

@ -0,0 +1,247 @@
// Sources/SwiftProtobuf/Google_Protobuf_Wrappers+Extensions.swift - Well-known wrapper type extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extensions to the well-known types in wrapper.proto that customize the JSON
/// format of those messages and provide convenience initializers from literals.
///
// -----------------------------------------------------------------------------
import Foundation
/// Internal protocol that minimizes the code duplication across the multiple
/// wrapper types extended below.
protocol ProtobufWrapper {
/// The wrapped protobuf type (for example, `ProtobufDouble`).
associatedtype WrappedType: FieldType
/// Exposes the generated property to the extensions here.
var value: WrappedType.BaseType { get set }
/// Exposes the parameterless initializer to the extensions here.
init()
/// Creates a new instance of the wrapper with the given value.
init(_ value: WrappedType.BaseType)
}
extension ProtobufWrapper {
mutating func decodeJSON(from decoder: inout JSONDecoder) throws {
var v: WrappedType.BaseType?
try WrappedType.decodeSingular(value: &v, from: &decoder)
value = v ?? WrappedType.proto3DefaultValue
}
}
extension Google_Protobuf_DoubleValue:
ProtobufWrapper, ExpressibleByFloatLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufDouble
public typealias FloatLiteralType = WrappedType.BaseType
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(floatLiteral: FloatLiteralType) {
self.init(floatLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
if value.isFinite {
// Swift 4.2 and later guarantees that this is accurate
// enough to parse back to the exact value on the other end.
return value.description
} else {
// Protobuf-specific handling of NaN and infinities
var encoder = JSONEncoder()
encoder.putDoubleValue(value: value)
return encoder.stringResult
}
}
}
extension Google_Protobuf_FloatValue:
ProtobufWrapper, ExpressibleByFloatLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufFloat
public typealias FloatLiteralType = Float
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(floatLiteral: FloatLiteralType) {
self.init(floatLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
if value.isFinite {
// Swift 4.2 and later guarantees that this is accurate
// enough to parse back to the exact value on the other end.
return value.description
} else {
// Protobuf-specific handling of NaN and infinities
var encoder = JSONEncoder()
encoder.putFloatValue(value: value)
return encoder.stringResult
}
}
}
extension Google_Protobuf_Int64Value:
ProtobufWrapper, ExpressibleByIntegerLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufInt64
public typealias IntegerLiteralType = WrappedType.BaseType
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(integerLiteral: IntegerLiteralType) {
self.init(integerLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return "\"" + String(value) + "\""
}
}
extension Google_Protobuf_UInt64Value:
ProtobufWrapper, ExpressibleByIntegerLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufUInt64
public typealias IntegerLiteralType = WrappedType.BaseType
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(integerLiteral: IntegerLiteralType) {
self.init(integerLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return "\"" + String(value) + "\""
}
}
extension Google_Protobuf_Int32Value:
ProtobufWrapper, ExpressibleByIntegerLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufInt32
public typealias IntegerLiteralType = WrappedType.BaseType
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(integerLiteral: IntegerLiteralType) {
self.init(integerLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return String(value)
}
}
extension Google_Protobuf_UInt32Value:
ProtobufWrapper, ExpressibleByIntegerLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufUInt32
public typealias IntegerLiteralType = WrappedType.BaseType
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(integerLiteral: IntegerLiteralType) {
self.init(integerLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return String(value)
}
}
extension Google_Protobuf_BoolValue:
ProtobufWrapper, ExpressibleByBooleanLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufBool
public typealias BooleanLiteralType = Bool
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(booleanLiteral: Bool) {
self.init(booleanLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
return value ? "true" : "false"
}
}
extension Google_Protobuf_StringValue:
ProtobufWrapper, ExpressibleByStringLiteral, _CustomJSONCodable {
public typealias WrappedType = ProtobufString
public typealias StringLiteralType = String
public typealias ExtendedGraphemeClusterLiteralType = String
public typealias UnicodeScalarLiteralType = String
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
public init(stringLiteral: String) {
self.init(stringLiteral)
}
public init(extendedGraphemeClusterLiteral: String) {
self.init(extendedGraphemeClusterLiteral)
}
public init(unicodeScalarLiteral: String) {
self.init(unicodeScalarLiteral)
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
var encoder = JSONEncoder()
encoder.putStringValue(value: value)
return encoder.stringResult
}
}
extension Google_Protobuf_BytesValue: ProtobufWrapper, _CustomJSONCodable {
public typealias WrappedType = ProtobufBytes
public init(_ value: WrappedType.BaseType) {
self.init()
self.value = value
}
func encodedJSONString(options: JSONEncodingOptions) throws -> String {
var encoder = JSONEncoder()
encoder.putBytesValue(value: value)
return encoder.stringResult
}
}

View File

@ -0,0 +1,426 @@
// Sources/SwiftProtobuf/HashVisitor.swift - Hashing 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
//
// -----------------------------------------------------------------------------
///
/// Hashing is basically a serialization problem, so we can leverage the
/// generated traversal methods for that.
///
// -----------------------------------------------------------------------------
import Foundation
private let i_2166136261 = Int(bitPattern: 2166136261)
private let i_16777619 = Int(16777619)
/// Computes the hash of a message by visiting its fields recursively.
///
/// Note that because this visits every field, it has the potential to be slow
/// for large or deeply nested messages. Users who need to use such messages as
/// dictionary keys or set members can use a wrapper struct around the message
/// and use a custom Hashable implementation that looks at the subset of the
/// message fields they want to include.
internal struct HashVisitor: Visitor {
#if swift(>=4.2)
internal private(set) var hasher: Hasher
#else // swift(>=4.2)
// Roughly based on FNV hash: http://tools.ietf.org/html/draft-eastlake-fnv-03
private(set) var hashValue = i_2166136261
private mutating func mix(_ hash: Int) {
hashValue = (hashValue ^ hash) &* i_16777619
}
private mutating func mixMap<K, V: Hashable>(map: Dictionary<K,V>) {
var mapHash = 0
for (k, v) in map {
// Note: This calculation cannot depend on the order of the items.
mapHash = mapHash &+ (k.hashValue ^ v.hashValue)
}
mix(mapHash)
}
#endif // swift(>=4.2)
#if swift(>=4.2)
init(_ hasher: Hasher) {
self.hasher = hasher
}
#else
init() {}
#endif
mutating func visitUnknown(bytes: Data) throws {
#if swift(>=4.2)
hasher.combine(bytes)
#else
mix(bytes.hashValue)
#endif
}
mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularEnumField<E: Enum>(value: E,
fieldNumber: Int) {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) {
#if swift(>=4.2)
hasher.combine(fieldNumber)
value.hash(into: &hasher)
#else
mix(fieldNumber)
mix(value.hashValue)
#endif
}
mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedMessageField<M: Message>(value: [M], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
for v in value {
v.hash(into: &hasher)
}
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitRepeatedGroupField<G: Message>(value: [G], fieldNumber: Int) throws {
assert(!value.isEmpty)
#if swift(>=4.2)
hasher.combine(fieldNumber)
for v in value {
v.hash(into: &hasher)
}
#else
mix(fieldNumber)
for v in value {
mix(v.hashValue)
}
#endif
}
mutating func visitMapField<KeyType, ValueType: MapValueType>(
fieldType: _ProtobufMap<KeyType, ValueType>.Type,
value: _ProtobufMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mixMap(map: value)
#endif
}
mutating func visitMapField<KeyType, ValueType>(
fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type,
value: _ProtobufEnumMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws where ValueType.RawValue == Int {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mixMap(map: value)
#endif
}
mutating func visitMapField<KeyType, ValueType>(
fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type,
value: _ProtobufMessageMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws {
#if swift(>=4.2)
hasher.combine(fieldNumber)
hasher.combine(value)
#else
mix(fieldNumber)
mixMap(map: value)
#endif
}
}

View File

@ -0,0 +1,51 @@
// Sources/SwiftProtobuf/Internal.swift - Message 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
//
// -----------------------------------------------------------------------------
///
/// Internal helpers on Messages for the library. These are public
/// just so the generated code can call them, but shouldn't be called
/// by developers directly.
///
// -----------------------------------------------------------------------------
import Foundation
/// Functions that are public only because they are used by generated message
/// implementations. NOT INTENDED TO BE CALLED BY CLIENTS.
public enum Internal {
/// A singleton instance of an empty data that is used by the generated code
/// for default values. This is a performance enhancement to work around the
/// fact that the `Data` type in Swift involves a new heap allocation every
/// time an empty instance is initialized, instead of sharing a common empty
/// backing storage.
public static let emptyData = Data()
/// Helper to loop over a list of Messages to see if they are all
/// initialized (see Message.isInitialized for what that means).
public static func areAllInitialized(_ listOfMessages: [Message]) -> Bool {
for msg in listOfMessages {
if !msg.isInitialized {
return false
}
}
return true
}
/// Helper to loop over dictionary with values that are Messages to see if
/// they are all initialized (see Message.isInitialized for what that means).
public static func areAllInitialized<K>(_ mapToMessages: [K: Message]) -> Bool {
for (_, msg) in mapToMessages {
if !msg.isInitialized {
return false
}
}
return true
}
}

View File

@ -0,0 +1,735 @@
// Sources/SwiftProtobuf/JSONDecoder.swift - JSON format decoding
//
// 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
//
// -----------------------------------------------------------------------------
///
/// JSON format decoding engine.
///
// -----------------------------------------------------------------------------
import Foundation
internal struct JSONDecoder: Decoder {
internal var scanner: JSONScanner
internal var messageType: Message.Type
private var fieldCount = 0
private var isMapKey = false
private var fieldNameMap: _NameMap?
internal var options: JSONDecodingOptions {
return scanner.options
}
mutating func handleConflictingOneOf() throws {
throw JSONDecodingError.conflictingOneOf
}
internal init(source: UnsafeRawBufferPointer, options: JSONDecodingOptions,
messageType: Message.Type, extensions: ExtensionMap?) {
let scanner = JSONScanner(source: source,
options: options,
extensions: extensions)
self.init(scanner: scanner, messageType: messageType)
}
private init(scanner: JSONScanner, messageType: Message.Type) {
self.scanner = scanner
self.messageType = messageType
}
mutating func nextFieldNumber() throws -> Int? {
if scanner.skipOptionalObjectEnd() {
return nil
}
if fieldCount > 0 {
try scanner.skipRequiredComma()
}
let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!,
messageType: messageType)
if let fieldNumber = fieldNumber {
fieldCount += 1
return fieldNumber
}
return nil
}
mutating func decodeSingularFloatField(value: inout Float) throws {
if scanner.skipOptionalNull() {
value = 0
return
}
value = try scanner.nextFloat()
}
mutating func decodeSingularFloatField(value: inout Float?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
value = try scanner.nextFloat()
}
mutating func decodeRepeatedFloatField(value: inout [Float]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextFloat()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularDoubleField(value: inout Double) throws {
if scanner.skipOptionalNull() {
value = 0
return
}
value = try scanner.nextDouble()
}
mutating func decodeSingularDoubleField(value: inout Double?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
value = try scanner.nextDouble()
}
mutating func decodeRepeatedDoubleField(value: inout [Double]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextDouble()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularInt32Field(value: inout Int32) throws {
if scanner.skipOptionalNull() {
value = 0
return
}
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw JSONDecodingError.numberRange
}
value = Int32(truncatingIfNeeded: n)
}
mutating func decodeSingularInt32Field(value: inout Int32?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw JSONDecodingError.numberRange
}
value = Int32(truncatingIfNeeded: n)
}
mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw JSONDecodingError.numberRange
}
value.append(Int32(truncatingIfNeeded: n))
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularInt64Field(value: inout Int64) throws {
if scanner.skipOptionalNull() {
value = 0
return
}
value = try scanner.nextSInt()
}
mutating func decodeSingularInt64Field(value: inout Int64?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
value = try scanner.nextSInt()
}
mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextSInt()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularUInt32Field(value: inout UInt32) throws {
if scanner.skipOptionalNull() {
value = 0
return
}
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw JSONDecodingError.numberRange
}
value = UInt32(truncatingIfNeeded: n)
}
mutating func decodeSingularUInt32Field(value: inout UInt32?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw JSONDecodingError.numberRange
}
value = UInt32(truncatingIfNeeded: n)
}
mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw JSONDecodingError.numberRange
}
value.append(UInt32(truncatingIfNeeded: n))
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularUInt64Field(value: inout UInt64) throws {
if scanner.skipOptionalNull() {
value = 0
return
}
value = try scanner.nextUInt()
}
mutating func decodeSingularUInt64Field(value: inout UInt64?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
value = try scanner.nextUInt()
}
mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextUInt()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularSInt32Field(value: inout Int32) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeSingularSInt32Field(value: inout Int32?) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws {
try decodeRepeatedInt32Field(value: &value)
}
mutating func decodeSingularSInt64Field(value: inout Int64) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeSingularSInt64Field(value: inout Int64?) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws {
try decodeRepeatedInt64Field(value: &value)
}
mutating func decodeSingularFixed32Field(value: inout UInt32) throws {
try decodeSingularUInt32Field(value: &value)
}
mutating func decodeSingularFixed32Field(value: inout UInt32?) throws {
try decodeSingularUInt32Field(value: &value)
}
mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws {
try decodeRepeatedUInt32Field(value: &value)
}
mutating func decodeSingularFixed64Field(value: inout UInt64) throws {
try decodeSingularUInt64Field(value: &value)
}
mutating func decodeSingularFixed64Field(value: inout UInt64?) throws {
try decodeSingularUInt64Field(value: &value)
}
mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws {
try decodeRepeatedUInt64Field(value: &value)
}
mutating func decodeSingularSFixed32Field(value: inout Int32) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeSingularSFixed32Field(value: inout Int32?) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws {
try decodeRepeatedInt32Field(value: &value)
}
mutating func decodeSingularSFixed64Field(value: inout Int64) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeSingularSFixed64Field(value: inout Int64?) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws {
try decodeRepeatedInt64Field(value: &value)
}
mutating func decodeSingularBoolField(value: inout Bool) throws {
if scanner.skipOptionalNull() {
value = false
return
}
if isMapKey {
value = try scanner.nextQuotedBool()
} else {
value = try scanner.nextBool()
}
}
mutating func decodeSingularBoolField(value: inout Bool?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
if isMapKey {
value = try scanner.nextQuotedBool()
} else {
value = try scanner.nextBool()
}
}
mutating func decodeRepeatedBoolField(value: inout [Bool]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextBool()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularStringField(value: inout String) throws {
if scanner.skipOptionalNull() {
value = String()
return
}
value = try scanner.nextQuotedString()
}
mutating func decodeSingularStringField(value: inout String?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
value = try scanner.nextQuotedString()
}
mutating func decodeRepeatedStringField(value: inout [String]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextQuotedString()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularBytesField(value: inout Data) throws {
if scanner.skipOptionalNull() {
value = Data()
return
}
value = try scanner.nextBytesValue()
}
mutating func decodeSingularBytesField(value: inout Data?) throws {
if scanner.skipOptionalNull() {
value = nil
return
}
value = try scanner.nextBytesValue()
}
mutating func decodeRepeatedBytesField(value: inout [Data]) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
let n = try scanner.nextBytesValue()
value.append(n)
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularEnumField<E: Enum>(value: inout E?) throws
where E.RawValue == Int {
if scanner.skipOptionalNull() {
if let customDecodable = E.self as? _CustomJSONCodable.Type {
value = try customDecodable.decodedFromJSONNull() as? E
return
}
value = nil
return
}
value = try scanner.nextEnumValue() as E
}
mutating func decodeSingularEnumField<E: Enum>(value: inout E) throws
where E.RawValue == Int {
if scanner.skipOptionalNull() {
if let customDecodable = E.self as? _CustomJSONCodable.Type {
value = try customDecodable.decodedFromJSONNull() as! E
return
}
value = E()
return
}
value = try scanner.nextEnumValue()
}
mutating func decodeRepeatedEnumField<E: Enum>(value: inout [E]) throws
where E.RawValue == Int {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
let maybeCustomDecodable = E.self as? _CustomJSONCodable.Type
while true {
if scanner.skipOptionalNull() {
if let customDecodable = maybeCustomDecodable {
let e = try customDecodable.decodedFromJSONNull() as! E
value.append(e)
} else {
throw JSONDecodingError.illegalNull
}
} else {
let e: E = try scanner.nextEnumValue()
value.append(e)
}
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
internal mutating func decodeFullObject<M: Message>(message: inout M) throws {
guard let nameProviding = (M.self as? _ProtoNameProviding.Type) else {
throw JSONDecodingError.missingFieldNames
}
fieldNameMap = nameProviding._protobuf_nameMap
if let m = message as? _CustomJSONCodable {
var customCodable = m
try customCodable.decodeJSON(from: &self)
message = customCodable as! M
} else {
try scanner.skipRequiredObjectStart()
if scanner.skipOptionalObjectEnd() {
return
}
try message.decodeMessage(decoder: &self)
}
}
mutating func decodeSingularMessageField<M: Message>(value: inout M?) throws {
if scanner.skipOptionalNull() {
if M.self is _CustomJSONCodable.Type {
value =
try (M.self as! _CustomJSONCodable.Type).decodedFromJSONNull() as? M
return
}
// All other message field types treat 'null' as an unset
value = nil
return
}
if value == nil {
value = M()
}
var subDecoder = JSONDecoder(scanner: scanner, messageType: M.self)
try subDecoder.decodeFullObject(message: &value!)
assert(scanner.recursionBudget == subDecoder.scanner.recursionBudget)
scanner = subDecoder.scanner
}
mutating func decodeRepeatedMessageField<M: Message>(
value: inout [M]
) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredArrayStart()
if scanner.skipOptionalArrayEnd() {
return
}
while true {
if scanner.skipOptionalNull() {
var appended = false
if M.self is _CustomJSONCodable.Type {
if let message = try (M.self as! _CustomJSONCodable.Type)
.decodedFromJSONNull() as? M {
value.append(message)
appended = true
}
}
if !appended {
throw JSONDecodingError.illegalNull
}
} else {
var message = M()
var subDecoder = JSONDecoder(scanner: scanner, messageType: M.self)
try subDecoder.decodeFullObject(message: &message)
value.append(message)
assert(scanner.recursionBudget == subDecoder.scanner.recursionBudget)
scanner = subDecoder.scanner
}
if scanner.skipOptionalArrayEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeSingularGroupField<G: Message>(value: inout G?) throws {
throw JSONDecodingError.schemaMismatch
}
mutating func decodeRepeatedGroupField<G: Message>(value: inout [G]) throws {
throw JSONDecodingError.schemaMismatch
}
mutating func decodeMapField<KeyType, ValueType: MapValueType>(
fieldType: _ProtobufMap<KeyType, ValueType>.Type,
value: inout _ProtobufMap<KeyType, ValueType>.BaseType
) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredObjectStart()
if scanner.skipOptionalObjectEnd() {
return
}
while true {
// Next character must be double quote, because
// map keys must always be quoted strings.
let c = try scanner.peekOneCharacter()
if c != "\"" {
throw JSONDecodingError.unquotedMapKey
}
isMapKey = true
var keyField: KeyType.BaseType?
try KeyType.decodeSingular(value: &keyField, from: &self)
isMapKey = false
try scanner.skipRequiredColon()
var valueField: ValueType.BaseType?
try ValueType.decodeSingular(value: &valueField, from: &self)
if let keyField = keyField, let valueField = valueField {
value[keyField] = valueField
} else {
throw JSONDecodingError.malformedMap
}
if scanner.skipOptionalObjectEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeMapField<KeyType, ValueType>(
fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type,
value: inout _ProtobufEnumMap<KeyType, ValueType>.BaseType
) throws where ValueType.RawValue == Int {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredObjectStart()
if scanner.skipOptionalObjectEnd() {
return
}
while true {
// Next character must be double quote, because
// map keys must always be quoted strings.
let c = try scanner.peekOneCharacter()
if c != "\"" {
throw JSONDecodingError.unquotedMapKey
}
isMapKey = true
var keyField: KeyType.BaseType?
try KeyType.decodeSingular(value: &keyField, from: &self)
isMapKey = false
try scanner.skipRequiredColon()
var valueField: ValueType?
try decodeSingularEnumField(value: &valueField)
if let keyField = keyField, let valueField = valueField {
value[keyField] = valueField
} else {
throw JSONDecodingError.malformedMap
}
if scanner.skipOptionalObjectEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeMapField<KeyType, ValueType>(
fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type,
value: inout _ProtobufMessageMap<KeyType, ValueType>.BaseType
) throws {
if scanner.skipOptionalNull() {
return
}
try scanner.skipRequiredObjectStart()
if scanner.skipOptionalObjectEnd() {
return
}
while true {
// Next character must be double quote, because
// map keys must always be quoted strings.
let c = try scanner.peekOneCharacter()
if c != "\"" {
throw JSONDecodingError.unquotedMapKey
}
isMapKey = true
var keyField: KeyType.BaseType?
try KeyType.decodeSingular(value: &keyField, from: &self)
isMapKey = false
try scanner.skipRequiredColon()
var valueField: ValueType?
try decodeSingularMessageField(value: &valueField)
if let keyField = keyField, let valueField = valueField {
value[keyField] = valueField
} else {
throw JSONDecodingError.malformedMap
}
if scanner.skipOptionalObjectEnd() {
return
}
try scanner.skipRequiredComma()
}
}
mutating func decodeExtensionField(
values: inout ExtensionFieldValueSet,
messageType: Message.Type,
fieldNumber: Int
) throws {
// Force-unwrap: we can only get here if the extension exists.
let ext = scanner.extensions[messageType, fieldNumber]!
try values.modify(index: fieldNumber) { fieldValue in
if fieldValue != nil {
try fieldValue!.decodeExtensionField(decoder: &self)
} else {
fieldValue = try ext._protobuf_newField(decoder: &self)
}
}
}
}

View File

@ -0,0 +1,62 @@
// Sources/SwiftProtobuf/JSONDecodingError.swift - JSON 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
//
// -----------------------------------------------------------------------------
///
/// JSON decoding errors
///
// -----------------------------------------------------------------------------
public enum JSONDecodingError: Error {
/// Something was wrong
case failure
/// A number could not be parsed
case malformedNumber
/// Numeric value was out of range or was not an integer value when expected
case numberRange
/// A map could not be parsed
case malformedMap
/// A bool could not be parsed
case malformedBool
/// We expected a quoted string, or a quoted string has a malformed backslash sequence
case malformedString
/// We encountered malformed UTF8
case invalidUTF8
/// The message does not have fieldName information
case missingFieldNames
/// The data type does not match the schema description
case schemaMismatch
/// A value (text or numeric) for an enum was not found on the enum
case unrecognizedEnumValue
/// A 'null' token appeared in an illegal location.
/// For example, Protobuf JSON does not allow 'null' tokens to appear
/// in lists.
case illegalNull
/// A map key was not quoted
case unquotedMapKey
/// JSON RFC 7519 does not allow numbers to have extra leading zeros
case leadingZero
/// We hit the end of the JSON string and expected something more...
case truncated
/// A JSON Duration could not be parsed
case malformedDuration
/// A JSON Timestamp could not be parsed
case malformedTimestamp
/// A FieldMask could not be parsed
case malformedFieldMask
/// Extraneous data remained after decoding should have been complete
case trailingGarbage
/// More than one value was specified for the same oneof field
case conflictingOneOf
/// Reached the nesting limit for messages within messages while decoding.
case messageDepthLimit
/// Encountered an unknown field with the given name. When parsing JSON, you
/// can instead instruct the library to ignore this via
/// JSONDecodingOptions.ignoreUnknownFields.
case unknownField(String)
}

View File

@ -0,0 +1,29 @@
// Sources/SwiftProtobuf/JSONDecodingOptions.swift - JSON 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
//
// -----------------------------------------------------------------------------
///
/// JSON decoding options
///
// -----------------------------------------------------------------------------
/// Options for JSONDecoding.
public struct JSONDecodingOptions {
/// 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
/// If unknown fields in the JSON should be ignored. If they aren't
/// ignored, an error will be raised if one is encountered.
public var ignoreUnknownFields: Bool = false
public init() {}
}

View File

@ -0,0 +1,386 @@
// Sources/SwiftProtobuf/JSONEncoder.swift - JSON Encoding support
//
// 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
//
// -----------------------------------------------------------------------------
///
/// JSON serialization engine.
///
// -----------------------------------------------------------------------------
import Foundation
private let asciiZero = UInt8(ascii: "0")
private let asciiOne = UInt8(ascii: "1")
private let asciiTwo = UInt8(ascii: "2")
private let asciiThree = UInt8(ascii: "3")
private let asciiFour = UInt8(ascii: "4")
private let asciiFive = UInt8(ascii: "5")
private let asciiSix = UInt8(ascii: "6")
private let asciiSeven = UInt8(ascii: "7")
private let asciiEight = UInt8(ascii: "8")
private let asciiNine = UInt8(ascii: "9")
private let asciiMinus = UInt8(ascii: "-")
private let asciiPlus = UInt8(ascii: "+")
private let asciiEquals = UInt8(ascii: "=")
private let asciiColon = UInt8(ascii: ":")
private let asciiComma = UInt8(ascii: ",")
private let asciiDoubleQuote = UInt8(ascii: "\"")
private let asciiBackslash = UInt8(ascii: "\\")
private let asciiForwardSlash = UInt8(ascii: "/")
private let asciiOpenSquareBracket = UInt8(ascii: "[")
private let asciiCloseSquareBracket = UInt8(ascii: "]")
private let asciiOpenCurlyBracket = UInt8(ascii: "{")
private let asciiCloseCurlyBracket = UInt8(ascii: "}")
private let asciiUpperA = UInt8(ascii: "A")
private let asciiUpperB = UInt8(ascii: "B")
private let asciiUpperC = UInt8(ascii: "C")
private let asciiUpperD = UInt8(ascii: "D")
private let asciiUpperE = UInt8(ascii: "E")
private let asciiUpperF = UInt8(ascii: "F")
private let asciiUpperZ = UInt8(ascii: "Z")
private let asciiLowerA = UInt8(ascii: "a")
private let asciiLowerZ = UInt8(ascii: "z")
private let base64Digits: [UInt8] = {
var digits = [UInt8]()
digits.append(contentsOf: asciiUpperA...asciiUpperZ)
digits.append(contentsOf: asciiLowerA...asciiLowerZ)
digits.append(contentsOf: asciiZero...asciiNine)
digits.append(asciiPlus)
digits.append(asciiForwardSlash)
return digits
}()
private let hexDigits: [UInt8] = {
var digits = [UInt8]()
digits.append(contentsOf: asciiZero...asciiNine)
digits.append(contentsOf: asciiUpperA...asciiUpperF)
return digits
}()
internal struct JSONEncoder {
private var data = [UInt8]()
private var separator: UInt8?
internal init() {}
internal var dataResult: Data { return Data(data) }
internal var stringResult: String {
get {
return String(bytes: data, encoding: String.Encoding.utf8)!
}
}
/// Append a `StaticString` to the JSON text. Because
/// `StaticString` is already UTF8 internally, this is faster
/// than appending a regular `String`.
internal mutating func append(staticText: StaticString) {
let buff = UnsafeBufferPointer(start: staticText.utf8Start, count: staticText.utf8CodeUnitCount)
data.append(contentsOf: buff)
}
/// Append a `_NameMap.Name` to the JSON text surrounded by quotes.
/// As with StaticString above, a `_NameMap.Name` provides pre-converted
/// UTF8 bytes, so this is much faster than appending a regular
/// `String`.
internal mutating func appendQuoted(name: _NameMap.Name) {
data.append(asciiDoubleQuote)
data.append(contentsOf: name.utf8Buffer)
data.append(asciiDoubleQuote)
}
/// Append a `String` to the JSON text.
internal mutating func append(text: String) {
data.append(contentsOf: text.utf8)
}
/// Append a raw utf8 in a `Data` to the JSON text.
internal mutating func append(utf8Data: Data) {
data.append(contentsOf: utf8Data)
}
/// Begin a new field whose name is given as a `_NameMap.Name`
internal mutating func startField(name: _NameMap.Name) {
if let s = separator {
data.append(s)
}
appendQuoted(name: name)
data.append(asciiColon)
separator = asciiComma
}
/// Begin a new field whose name is given as a `String`.
internal mutating func startField(name: String) {
if let s = separator {
data.append(s)
}
data.append(asciiDoubleQuote)
// Can avoid overhead of putStringValue, since
// the JSON field names are always clean ASCII.
data.append(contentsOf: name.utf8)
append(staticText: "\":")
separator = asciiComma
}
/// Begin a new extension field
internal mutating func startExtensionField(name: String) {
if let s = separator {
data.append(s)
}
append(staticText: "\"[")
data.append(contentsOf: name.utf8)
append(staticText: "]\":")
separator = asciiComma
}
/// Append an open square bracket `[` to the JSON.
internal mutating func startArray() {
data.append(asciiOpenSquareBracket)
separator = nil
}
/// Append a close square bracket `]` to the JSON.
internal mutating func endArray() {
data.append(asciiCloseSquareBracket)
separator = asciiComma
}
/// Append a comma `,` to the JSON.
internal mutating func comma() {
data.append(asciiComma)
}
/// Append an open curly brace `{` to the JSON.
/// Assumes this object is part of an array of objects.
internal mutating func startArrayObject() {
if let s = separator {
data.append(s)
}
data.append(asciiOpenCurlyBracket)
separator = nil
}
/// Append an open curly brace `{` to the JSON.
internal mutating func startObject() {
data.append(asciiOpenCurlyBracket)
separator = nil
}
/// Append a close curly brace `}` to the JSON.
internal mutating func endObject() {
data.append(asciiCloseCurlyBracket)
separator = asciiComma
}
/// Write a JSON `null` token to the output.
internal mutating func putNullValue() {
append(staticText: "null")
}
/// Append a float value to the output.
/// This handles Nan and infinite values by
/// writing well-known string values.
internal mutating func putFloatValue(value: Float) {
if value.isNaN {
append(staticText: "\"NaN\"")
} else if !value.isFinite {
if value < 0 {
append(staticText: "\"-Infinity\"")
} else {
append(staticText: "\"Infinity\"")
}
} else {
data.append(contentsOf: value.debugDescription.utf8)
}
}
/// Append a double value to the output.
/// This handles Nan and infinite values by
/// writing well-known string values.
internal mutating func putDoubleValue(value: Double) {
if value.isNaN {
append(staticText: "\"NaN\"")
} else if !value.isFinite {
if value < 0 {
append(staticText: "\"-Infinity\"")
} else {
append(staticText: "\"Infinity\"")
}
} else {
data.append(contentsOf: value.debugDescription.utf8)
}
}
/// Append a UInt64 to the output (without quoting).
private mutating func appendUInt(value: UInt64) {
if value >= 10 {
appendUInt(value: value / 10)
}
data.append(asciiZero + UInt8(value % 10))
}
/// Append an Int64 to the output (without quoting).
private mutating func appendInt(value: Int64) {
if value < 0 {
data.append(asciiMinus)
// This is the twos-complement negation of value,
// computed in a way that won't overflow a 64-bit
// signed integer.
appendUInt(value: 1 + ~UInt64(bitPattern: value))
} else {
appendUInt(value: UInt64(bitPattern: value))
}
}
/// Write an Enum as an int.
internal mutating func putEnumInt(value: Int) {
appendInt(value: Int64(value))
}
/// Write an `Int64` using protobuf JSON quoting conventions.
internal mutating func putInt64(value: Int64) {
data.append(asciiDoubleQuote)
appendInt(value: value)
data.append(asciiDoubleQuote)
}
/// Write an `Int32` with quoting suitable for
/// using the value as a map key.
internal mutating func putQuotedInt32(value: Int32) {
data.append(asciiDoubleQuote)
appendInt(value: Int64(value))
data.append(asciiDoubleQuote)
}
/// Write an `Int32` in the default format.
internal mutating func putInt32(value: Int32) {
appendInt(value: Int64(value))
}
/// Write a `UInt64` using protobuf JSON quoting conventions.
internal mutating func putUInt64(value: UInt64) {
data.append(asciiDoubleQuote)
appendUInt(value: value)
data.append(asciiDoubleQuote)
}
/// Write a `UInt32` with quoting suitable for
/// using the value as a map key.
internal mutating func putQuotedUInt32(value: UInt32) {
data.append(asciiDoubleQuote)
appendUInt(value: UInt64(value))
data.append(asciiDoubleQuote)
}
/// Write a `UInt32` in the default format.
internal mutating func putUInt32(value: UInt32) {
appendUInt(value: UInt64(value))
}
/// Write a `Bool` with quoting suitable for
/// using the value as a map key.
internal mutating func putQuotedBoolValue(value: Bool) {
data.append(asciiDoubleQuote)
putBoolValue(value: value)
data.append(asciiDoubleQuote)
}
/// Write a `Bool` in the default format.
internal mutating func putBoolValue(value: Bool) {
if value {
append(staticText: "true")
} else {
append(staticText: "false")
}
}
/// Append a string value escaping special characters as needed.
internal mutating func putStringValue(value: String) {
data.append(asciiDoubleQuote)
for c in value.unicodeScalars {
switch c.value {
// Special two-byte escapes
case 8: append(staticText: "\\b")
case 9: append(staticText: "\\t")
case 10: append(staticText: "\\n")
case 12: append(staticText: "\\f")
case 13: append(staticText: "\\r")
case 34: append(staticText: "\\\"")
case 92: append(staticText: "\\\\")
case 0...31, 127...159: // Hex form for C0 control chars
append(staticText: "\\u00")
data.append(hexDigits[Int(c.value / 16)])
data.append(hexDigits[Int(c.value & 15)])
case 23...126:
data.append(UInt8(truncatingIfNeeded: c.value))
case 0x80...0x7ff:
data.append(0xc0 + UInt8(truncatingIfNeeded: c.value >> 6))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
case 0x800...0xffff:
data.append(0xe0 + UInt8(truncatingIfNeeded: c.value >> 12))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
default:
data.append(0xf0 + UInt8(truncatingIfNeeded: c.value >> 18))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 12) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
}
}
data.append(asciiDoubleQuote)
}
/// Append a bytes value using protobuf JSON Base-64 encoding.
internal mutating func putBytesValue(value: Data) {
data.append(asciiDoubleQuote)
if value.count > 0 {
value.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
if let p = body.baseAddress, body.count > 0 {
var t: Int = 0
var bytesInGroup: Int = 0
for i in 0..<body.count {
if bytesInGroup == 3 {
data.append(base64Digits[(t >> 18) & 63])
data.append(base64Digits[(t >> 12) & 63])
data.append(base64Digits[(t >> 6) & 63])
data.append(base64Digits[t & 63])
t = 0
bytesInGroup = 0
}
t = (t << 8) + Int(p[i])
bytesInGroup += 1
}
switch bytesInGroup {
case 3:
data.append(base64Digits[(t >> 18) & 63])
data.append(base64Digits[(t >> 12) & 63])
data.append(base64Digits[(t >> 6) & 63])
data.append(base64Digits[t & 63])
case 2:
t <<= 8
data.append(base64Digits[(t >> 18) & 63])
data.append(base64Digits[(t >> 12) & 63])
data.append(base64Digits[(t >> 6) & 63])
data.append(asciiEquals)
case 1:
t <<= 16
data.append(base64Digits[(t >> 18) & 63])
data.append(base64Digits[(t >> 12) & 63])
data.append(asciiEquals)
data.append(asciiEquals)
default:
break
}
}
}
}
data.append(asciiDoubleQuote)
}
}

View File

@ -0,0 +1,35 @@
// Sources/SwiftProtobuf/JSONEncodingError.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.
///
// -----------------------------------------------------------------------------
public enum JSONEncodingError: Error {
/// Any fields that were decoded from binary format cannot be
/// re-encoded into JSON unless the object they hold is a
/// well-known type or a type registered with via
/// Google_Protobuf_Any.register()
case anyTranscodeFailure
/// Timestamp values can only be JSON encoded if they hold a value
/// between 0001-01-01Z00:00:00 and 9999-12-31Z23:59:59.
case timestampRange
/// Duration values can only be JSON encoded if they hold a value
/// less than +/- 100 years.
case durationRange
/// Field masks get edited when converting between JSON and protobuf
case fieldMaskConversion
/// Field names were not compiled into the binary
case missingFieldNames
/// Instances of `Google_Protobuf_Value` can only be encoded if they have a
/// valid `kind` (that is, they represent a null value, number, boolean,
/// string, struct, or list).
case missingValue
}

View File

@ -0,0 +1,26 @@
// Sources/SwiftProtobuf/JSONEncodingOptions.swift - JSON encoding options
//
// Copyright (c) 2014 - 2018 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
//
// -----------------------------------------------------------------------------
///
/// JSON encoding options
///
// -----------------------------------------------------------------------------
/// Options for JSONEncoding.
public struct JSONEncodingOptions {
/// Always print enums as ints. By default they are printed as strings.
public var alwaysPrintEnumsAsInts: Bool = false
/// Whether to preserve proto field names.
/// By default they are converted to JSON(lowerCamelCase) names.
public var preserveProtoFieldNames: Bool = false
public init() {}
}

View File

@ -0,0 +1,404 @@
// Sources/SwiftProtobuf/JSONEncodingVisitor.swift - JSON encoding visitor
//
// 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 that writes a message in JSON format.
///
// -----------------------------------------------------------------------------
import Foundation
/// Visitor that serializes a message into JSON format.
internal struct JSONEncodingVisitor: Visitor {
private var encoder = JSONEncoder()
private var nameMap: _NameMap
private var extensions: ExtensionFieldValueSet?
private let options: JSONEncodingOptions
/// The JSON text produced by the visitor, as raw UTF8 bytes.
var dataResult: Data {
return encoder.dataResult
}
/// The JSON text produced by the visitor, as a String.
internal var stringResult: String {
return encoder.stringResult
}
/// Creates a new visitor for serializing a message of the given type to JSON
/// format.
init(type: Message.Type, options: JSONEncodingOptions) throws {
if let nameProviding = type as? _ProtoNameProviding.Type {
self.nameMap = nameProviding._protobuf_nameMap
} else {
throw JSONEncodingError.missingFieldNames
}
self.options = options
}
mutating func startArray() {
encoder.startArray()
}
mutating func endArray() {
encoder.endArray()
}
mutating func startObject(message: Message) {
self.extensions = (message as? ExtensibleMessage)?._protobuf_extensionFieldValues
encoder.startObject()
}
mutating func startArrayObject(message: Message) {
self.extensions = (message as? ExtensibleMessage)?._protobuf_extensionFieldValues
encoder.startArrayObject()
}
mutating func endObject() {
encoder.endObject()
}
mutating func encodeField(name: String, stringValue value: String) {
encoder.startField(name: name)
encoder.putStringValue(value: value)
}
mutating func encodeField(name: String, jsonText text: String) {
encoder.startField(name: name)
encoder.append(text: text)
}
mutating func visitUnknown(bytes: Data) throws {
// JSON encoding has no provision for carrying proto2 unknown fields.
}
mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putFloatValue(value: value)
}
mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putDoubleValue(value: value)
}
mutating func visitSingularInt32Field(value: Int32, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putInt32(value: value)
}
mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putInt64(value: value)
}
mutating func visitSingularUInt32Field(value: UInt32, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putUInt32(value: value)
}
mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putUInt64(value: value)
}
mutating func visitSingularFixed32Field(value: UInt32, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putUInt32(value: value)
}
mutating func visitSingularSFixed32Field(value: Int32, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putInt32(value: value)
}
mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putBoolValue(value: value)
}
mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putStringValue(value: value)
}
mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.putBytesValue(value: value)
}
private mutating func _visitRepeated<T>(
value: [T],
fieldNumber: Int,
encode: (inout JSONEncoder, T) throws -> ()
) throws {
assert(!value.isEmpty)
try startField(for: fieldNumber)
var comma = false
encoder.startArray()
for v in value {
if comma {
encoder.comma()
}
comma = true
try encode(&encoder, v)
}
encoder.endArray()
}
mutating func visitSingularEnumField<E: Enum>(value: E, fieldNumber: Int) throws {
try startField(for: fieldNumber)
if let e = value as? _CustomJSONCodable {
let json = try e.encodedJSONString(options: options)
encoder.append(text: json)
} else if !options.alwaysPrintEnumsAsInts, let n = value.name {
encoder.appendQuoted(name: n)
} else {
encoder.putEnumInt(value: value.rawValue)
}
}
mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) throws {
try startField(for: fieldNumber)
if let m = value as? _CustomJSONCodable {
let json = try m.encodedJSONString(options: options)
encoder.append(text: json)
} else if let newNameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap {
// Preserve outer object's name and extension maps; restore them before returning
let oldNameMap = self.nameMap
let oldExtensions = self.extensions
defer {
self.nameMap = oldNameMap
self.extensions = oldExtensions
}
// Install inner object's name and extension maps
self.nameMap = newNameMap
startObject(message: value)
try value.traverse(visitor: &self)
endObject()
} else {
throw JSONEncodingError.missingFieldNames
}
}
mutating func visitSingularGroupField<G: Message>(value: G, fieldNumber: Int) throws {
// Google does not serialize groups into JSON
}
mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: Float) in
encoder.putFloatValue(value: v)
}
}
mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: Double) in
encoder.putDoubleValue(value: v)
}
}
mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: Int32) in
encoder.putInt32(value: v)
}
}
mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: Int64) in
encoder.putInt64(value: v)
}
}
mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: UInt32) in
encoder.putUInt32(value: v)
}
}
mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: UInt64) in
encoder.putUInt64(value: v)
}
}
mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws {
try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws {
try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
try visitRepeatedUInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
try visitRepeatedUInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: Bool) in
encoder.putBoolValue(value: v)
}
}
mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: String) in
encoder.putStringValue(value: v)
}
}
mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws {
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: Data) in
encoder.putBytesValue(value: v)
}
}
mutating func visitRepeatedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
if let _ = E.self as? _CustomJSONCodable.Type {
let options = self.options
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: E) throws in
let e = v as! _CustomJSONCodable
let json = try e.encodedJSONString(options: options)
encoder.append(text: json)
}
} else {
let alwaysPrintEnumsAsInts = options.alwaysPrintEnumsAsInts
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
(encoder: inout JSONEncoder, v: E) throws in
if !alwaysPrintEnumsAsInts, let n = v.name {
encoder.appendQuoted(name: n)
} else {
encoder.putEnumInt(value: v.rawValue)
}
}
}
}
mutating func visitRepeatedMessageField<M: Message>(value: [M], fieldNumber: Int) throws {
assert(!value.isEmpty)
try startField(for: fieldNumber)
var comma = false
encoder.startArray()
if let _ = M.self as? _CustomJSONCodable.Type {
for v in value {
if comma {
encoder.comma()
}
comma = true
let json = try v.jsonString(options: options)
encoder.append(text: json)
}
} else if let newNameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap {
// Preserve name and extension maps for outer object
let oldNameMap = self.nameMap
let oldExtensions = self.extensions
// Restore outer object's name and extension maps before returning
defer {
self.nameMap = oldNameMap
self.extensions = oldExtensions
}
self.nameMap = newNameMap
for v in value {
startArrayObject(message: v)
try v.traverse(visitor: &self)
encoder.endObject()
}
} else {
throw JSONEncodingError.missingFieldNames
}
encoder.endArray()
}
mutating func visitRepeatedGroupField<G: Message>(value: [G], fieldNumber: Int) throws {
assert(!value.isEmpty)
// Google does not serialize groups into JSON
}
// Packed fields are handled the same as non-packed fields, so JSON just
// relies on the default implementations in Visitor.swift
mutating func visitMapField<KeyType, ValueType: MapValueType>(fieldType: _ProtobufMap<KeyType, ValueType>.Type, value: _ProtobufMap<KeyType, ValueType>.BaseType, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.append(text: "{")
var mapVisitor = JSONMapEncodingVisitor(encoder: encoder, options: options)
for (k,v) in value {
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &mapVisitor)
try ValueType.visitSingular(value: v, fieldNumber: 2, with: &mapVisitor)
}
encoder = mapVisitor.encoder
encoder.append(text: "}")
}
mutating func visitMapField<KeyType, ValueType>(fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type, value: _ProtobufEnumMap<KeyType, ValueType>.BaseType, fieldNumber: Int) throws where ValueType.RawValue == Int {
try startField(for: fieldNumber)
encoder.append(text: "{")
var mapVisitor = JSONMapEncodingVisitor(encoder: encoder, options: options)
for (k, v) in value {
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &mapVisitor)
try mapVisitor.visitSingularEnumField(value: v, fieldNumber: 2)
}
encoder = mapVisitor.encoder
encoder.append(text: "}")
}
mutating func visitMapField<KeyType, ValueType>(fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type, value: _ProtobufMessageMap<KeyType, ValueType>.BaseType, fieldNumber: Int) throws {
try startField(for: fieldNumber)
encoder.append(text: "{")
var mapVisitor = JSONMapEncodingVisitor(encoder: encoder, options: options)
for (k,v) in value {
try KeyType.visitSingular(value: k, fieldNumber: 1, with: &mapVisitor)
try mapVisitor.visitSingularMessageField(value: v, fieldNumber: 2)
}
encoder = mapVisitor.encoder
encoder.append(text: "}")
}
/// Helper function that throws an error if the field number could not be
/// resolved.
private mutating func startField(for number: Int) throws {
let name: _NameMap.Name?
if options.preserveProtoFieldNames {
name = nameMap.names(for: number)?.proto
} else {
name = nameMap.names(for: number)?.json
}
if let name = name {
encoder.startField(name: name)
} else if let name = extensions?[number]?.protobufExtension.fieldName {
encoder.startExtensionField(name: name)
} else {
throw JSONEncodingError.missingFieldNames
}
}
}

View File

@ -0,0 +1,174 @@
// Sources/SwiftProtobuf/JSONMapEncodingVisitor.swift - JSON map encoding visitor
//
// 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 that writes out the key/value pairs for a JSON map.
///
// -----------------------------------------------------------------------------
import Foundation
/// Visitor that serializes a message into JSON map format.
///
/// This expects to alternately visit the keys and values for a JSON
/// map. It only accepts singular values. Keys should be identified
/// as `fieldNumber:1`, values should be identified as `fieldNumber:2`
///
internal struct JSONMapEncodingVisitor: SelectiveVisitor {
private var separator: StaticString?
internal var encoder: JSONEncoder
private let options: JSONEncodingOptions
init(encoder: JSONEncoder, options: JSONEncodingOptions) {
self.encoder = encoder
self.options = options
}
private mutating func startKey() {
if let s = separator {
encoder.append(staticText: s)
} else {
separator = ","
}
}
private mutating func startValue() {
encoder.append(staticText: ":")
}
mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws {
// Doubles/Floats can never be map keys, only values
assert(fieldNumber == 2)
startValue()
encoder.putFloatValue(value: value)
}
mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
// Doubles/Floats can never be map keys, only values
assert(fieldNumber == 2)
startValue()
encoder.putDoubleValue(value: value)
}
mutating func visitSingularInt32Field(value: Int32, fieldNumber: Int) throws {
if fieldNumber == 1 {
startKey()
encoder.putQuotedInt32(value: value)
} else {
startValue()
encoder.putInt32(value: value)
}
}
mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
if fieldNumber == 1 {
startKey()
} else {
startValue()
}
// Int64 fields are always quoted anyway
encoder.putInt64(value: value)
}
mutating func visitSingularUInt32Field(value: UInt32, fieldNumber: Int) throws {
if fieldNumber == 1 {
startKey()
encoder.putQuotedUInt32(value: value)
} else {
startValue()
encoder.putUInt32(value: value)
}
}
mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
if fieldNumber == 1 {
startKey()
} else {
startValue()
}
encoder.putUInt64(value: value)
}
mutating func visitSingularSInt32Field(value: Int32, fieldNumber: Int) throws {
try visitSingularInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitSingularSInt64Field(value: Int64, fieldNumber: Int) throws {
try visitSingularInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitSingularFixed32Field(value: UInt32, fieldNumber: Int) throws {
try visitSingularUInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitSingularFixed64Field(value: UInt64, fieldNumber: Int) throws {
try visitSingularUInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitSingularSFixed32Field(value: Int32, fieldNumber: Int) throws {
try visitSingularInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitSingularSFixed64Field(value: Int64, fieldNumber: Int) throws {
try visitSingularInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
if fieldNumber == 1 {
startKey()
encoder.putQuotedBoolValue(value: value)
} else {
startValue()
encoder.putBoolValue(value: value)
}
}
mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
if fieldNumber == 1 {
startKey()
} else {
startValue()
}
encoder.putStringValue(value: value)
}
mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
// Bytes can only be map values, never keys
assert(fieldNumber == 2)
startValue()
encoder.putBytesValue(value: value)
}
mutating func visitSingularEnumField<E: Enum>(value: E, fieldNumber: Int) throws {
// Enums can only be map values, never keys
assert(fieldNumber == 2)
startValue()
if !options.alwaysPrintEnumsAsInts, let n = value.name {
encoder.putStringValue(value: String(describing: n))
} else {
encoder.putEnumInt(value: value.rawValue)
}
}
mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) throws {
// Messages can only be map values, never keys
assert(fieldNumber == 2)
startValue()
let json = try value.jsonString(options: options)
encoder.append(text: json)
}
// SelectiveVisitor will block:
// - single Groups
// - everything repeated
// - everything packed
// - all maps
// - unknown fields
// - extensions
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
// Sources/SwiftProtobuf/MathUtils.swift - Generally useful mathematical functions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Generally useful mathematical and arithmetic functions.
///
// -----------------------------------------------------------------------------
import Foundation
/// Remainder in standard modular arithmetic (modulo). This coincides with (%)
/// when a > 0.
///
/// - Parameters:
/// - a: The dividend. Can be positive, 0 or negative.
/// - b: The divisor. This must be positive, and is an error if 0 or negative.
/// - Returns: The unique value r such that 0 <= r < b and b * q + r = a for some q.
internal func mod<T : SignedInteger>(_ a: T, _ b: T) -> T {
assert(b > 0)
let r = a % b
return r >= 0 ? r : r + b
}
/// Quotient in standard modular arithmetic (Euclidean division). This coincides
/// with (/) when a > 0.
///
/// - Parameters:
/// - a: The dividend. Can be positive, 0 or negative.
/// - b: The divisor. This must be positive, and is an error if 0 or negative.
/// - Returns: The unique value q such that for some 0 <= r < b, b * q + r = a.
internal func div<T : SignedInteger>(_ a: T, _ b: T) -> T {
assert(b > 0)
return a >= 0 ? a / b : (a + 1) / b - 1
}

View File

@ -0,0 +1,45 @@
// Sources/SwiftProtobuf/Message+AnyAdditions.swift - Any-related Message extensions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extends the `Message` type with `Google_Protobuf_Any`-specific behavior.
///
// -----------------------------------------------------------------------------
extension Message {
/// Initialize this message from the provided `google.protobuf.Any`
/// well-known type.
///
/// This corresponds to the `unpack` method in the Google C++ API.
///
/// If the Any object was decoded from Protobuf Binary or JSON
/// format, then the enclosed field data was stored and is not
/// fully decoded until you unpack the Any object into a message.
/// As such, this method will typically need to perform a full
/// deserialization of the enclosed data and can fail for any
/// reason that deserialization can fail.
///
/// See `Google_Protobuf_Any.unpackTo()` for more discussion.
///
/// - Parameter unpackingAny: the message to decode.
/// - Parameter extensions: An `ExtensionMap` used to look up and decode any
/// extensions in this message or messages nested within this message's
/// fields.
/// - Parameter options: The BinaryDecodingOptions to use.
/// - Throws: an instance of `AnyUnpackError`, `JSONDecodingError`, or
/// `BinaryDecodingError` on failure.
public init(
unpackingAny: Google_Protobuf_Any,
extensions: ExtensionMap? = nil,
options: BinaryDecodingOptions = BinaryDecodingOptions()
) throws {
self.init()
try unpackingAny._storage.unpackTo(target: &self, extensions: extensions, options: options)
}
}

View File

@ -0,0 +1,204 @@
// Sources/SwiftProtobuf/Message+BinaryAdditions.swift - Per-type binary coding
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extensions to `Message` to provide binary coding and decoding.
///
// -----------------------------------------------------------------------------
import Foundation
/// Binary encoding and decoding methods for messages.
extension Message {
/// Returns a `Data` value containing the Protocol Buffer binary format
/// serialization of the message.
///
/// - Parameters:
/// - 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`.
/// - Returns: A `Data` value containing the binary serialization of the
/// message.
/// - Throws: `BinaryEncodingError` if encoding fails.
public func serializedData(partial: Bool = false) throws -> Data {
if !partial && !isInitialized {
throw BinaryEncodingError.missingRequiredFields
}
let requiredSize = try serializedDataSize()
var data = Data(count: requiredSize)
try data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
if let baseAddress = body.baseAddress, body.count > 0 {
var visitor = BinaryEncodingVisitor(forWritingInto: baseAddress)
try traverse(visitor: &visitor)
// Currently not exposing this from the api because it really would be
// an internal error in the library and should never happen.
assert(requiredSize == visitor.encoder.distance(pointer: baseAddress))
}
}
return data
}
/// Returns the size in bytes required to encode the message in binary format.
/// This is used by `serializedData()` to precalculate the size of the buffer
/// so that encoding can proceed without bounds checks or reallocation.
internal func serializedDataSize() throws -> Int {
// Note: since this api is internal, it doesn't currently worry about
// needing a partial argument to handle proto2 syntax required fields.
// If this become public, it will need that added.
var visitor = BinaryEncodingSizeVisitor()
try traverse(visitor: &visitor)
return visitor.serializedSize
}
/// Creates a new message by decoding the given `Data` value containing a
/// serialized message in Protocol Buffer binary format.
///
/// - Parameters:
/// - serializedData: The binary-encoded message data to decode.
/// - 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.
@inlinable
public init(
serializedData data: Data,
extensions: ExtensionMap? = nil,
partial: Bool = false,
options: BinaryDecodingOptions = BinaryDecodingOptions()
) throws {
self.init()
#if swift(>=5.0)
try merge(contiguousBytes: data, extensions: extensions, partial: partial, options: options)
#else
try merge(serializedData: data, extensions: extensions, partial: partial, options: options)
#endif
}
#if swift(>=5.0)
/// Creates a new message by decoding the given `ContiguousBytes` value
/// containing a serialized message in Protocol Buffer binary format.
///
/// - Parameters:
/// - contiguousBytes: The binary-encoded message data to decode.
/// - 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.
@inlinable
public init<Bytes: ContiguousBytes>(
contiguousBytes bytes: Bytes,
extensions: ExtensionMap? = nil,
partial: Bool = false,
options: BinaryDecodingOptions = BinaryDecodingOptions()
) throws {
self.init()
try merge(contiguousBytes: bytes, extensions: extensions, partial: partial, options: options)
}
#endif // #if swift(>=5.0)
/// Updates the message by decoding the given `Data` value containing a
/// serialized message in Protocol Buffer binary format into the receiver.
///
/// - 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:
/// - serializedData: The binary-encoded message data to decode.
/// - 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.
@inlinable
public mutating func merge(
serializedData data: Data,
extensions: ExtensionMap? = nil,
partial: Bool = false,
options: BinaryDecodingOptions = BinaryDecodingOptions()
) throws {
#if swift(>=5.0)
try merge(contiguousBytes: data, extensions: extensions, partial: partial, options: options)
#else
try data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
try _merge(rawBuffer: body, extensions: extensions, partial: partial, options: options)
}
#endif // swift(>=5.0)
}
#if swift(>=5.0)
/// Updates the message by decoding the given `ContiguousBytes` value
/// containing a serialized message in Protocol Buffer binary format into the
/// receiver.
///
/// - 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:
/// - contiguousBytes: The binary-encoded message data to decode.
/// - 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.
@inlinable
public mutating func merge<Bytes: ContiguousBytes>(
contiguousBytes bytes: Bytes,
extensions: ExtensionMap? = nil,
partial: Bool = false,
options: BinaryDecodingOptions = BinaryDecodingOptions()
) throws {
try bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
try _merge(rawBuffer: body, extensions: extensions, partial: partial, options: options)
}
}
#endif // swift(>=5.0)
// Helper for `merge()`s to keep the Decoder internal to SwiftProtobuf while
// allowing the generic over ContiguousBytes to get better codegen from the
// compiler by being `@inlinable`.
@usableFromInline
internal mutating func _merge(
rawBuffer body: UnsafeRawBufferPointer,
extensions: ExtensionMap?,
partial: Bool,
options: BinaryDecodingOptions
) throws {
if let baseAddress = body.baseAddress, body.count > 0 {
var decoder = BinaryDecoder(forReadingFrom: baseAddress,
count: body.count,
options: options,
extensions: extensions)
try decoder.decodeFullMessage(message: &self)
}
if !partial && !isInitialized {
throw BinaryDecodingError.missingRequiredFields
}
}
}

View File

@ -0,0 +1,150 @@
// Sources/SwiftProtobuf/Message+JSONAdditions.swift - JSON format primitive types
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extensions to `Message` to support JSON encoding/decoding.
///
// -----------------------------------------------------------------------------
import Foundation
/// JSON encoding and decoding methods for messages.
extension Message {
/// Returns a string containing the JSON serialization of the message.
///
/// Unlike binary encoding, presence of required fields is not enforced when
/// serializing to JSON.
///
/// - Returns: A string containing the JSON serialization of the message.
/// - Parameters:
/// - options: The JSONEncodingOptions to use.
/// - Throws: `JSONEncodingError` if encoding fails.
public func jsonString(
options: JSONEncodingOptions = JSONEncodingOptions()
) throws -> String {
if let m = self as? _CustomJSONCodable {
return try m.encodedJSONString(options: options)
}
let data = try jsonUTF8Data(options: options)
return String(data: data, encoding: String.Encoding.utf8)!
}
/// Returns a Data containing the UTF-8 JSON serialization of the message.
///
/// Unlike binary encoding, presence of required fields is not enforced when
/// serializing to JSON.
///
/// - Returns: A Data containing the JSON serialization of the message.
/// - Parameters:
/// - options: The JSONEncodingOptions to use.
/// - Throws: `JSONEncodingError` if encoding fails.
public func jsonUTF8Data(
options: JSONEncodingOptions = JSONEncodingOptions()
) throws -> Data {
if let m = self as? _CustomJSONCodable {
let string = try m.encodedJSONString(options: options)
let data = string.data(using: String.Encoding.utf8)! // Cannot fail!
return data
}
var visitor = try JSONEncodingVisitor(type: Self.self, options: options)
visitor.startObject(message: self)
try traverse(visitor: &visitor)
visitor.endObject()
return visitor.dataResult
}
/// Creates a new message by decoding the given string containing a
/// serialized message in JSON format.
///
/// - Parameter jsonString: The JSON-formatted string to decode.
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public init(
jsonString: String,
options: JSONDecodingOptions = JSONDecodingOptions()
) throws {
try self.init(jsonString: jsonString, extensions: nil, options: options)
}
/// Creates a new message by decoding the given string containing a
/// serialized message in JSON format.
///
/// - Parameter jsonString: The JSON-formatted string to decode.
/// - Parameter extensions: An ExtensionMap for looking up extensions by name
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public init(
jsonString: String,
extensions: ExtensionMap? = nil,
options: JSONDecodingOptions = JSONDecodingOptions()
) throws {
if jsonString.isEmpty {
throw JSONDecodingError.truncated
}
if let data = jsonString.data(using: String.Encoding.utf8) {
try self.init(jsonUTF8Data: data, extensions: extensions, options: options)
} else {
throw JSONDecodingError.truncated
}
}
/// Creates a new message by decoding the given `Data` containing a
/// serialized message in JSON format, interpreting the data as UTF-8 encoded
/// text.
///
/// - Parameter jsonUTF8Data: The JSON-formatted data to decode, represented
/// as UTF-8 encoded text.
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public init(
jsonUTF8Data: Data,
options: JSONDecodingOptions = JSONDecodingOptions()
) throws {
try self.init(jsonUTF8Data: jsonUTF8Data, extensions: nil, options: options)
}
/// Creates a new message by decoding the given `Data` containing a
/// serialized message in JSON format, interpreting the data as UTF-8 encoded
/// text.
///
/// - Parameter jsonUTF8Data: The JSON-formatted data to decode, represented
/// as UTF-8 encoded text.
/// - Parameter extensions: The extension map to use with this decode
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public init(
jsonUTF8Data: Data,
extensions: ExtensionMap? = nil,
options: JSONDecodingOptions = JSONDecodingOptions()
) throws {
self.init()
try jsonUTF8Data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
// Empty input is valid for binary, but not for JSON.
guard body.count > 0 else {
throw JSONDecodingError.truncated
}
var decoder = JSONDecoder(source: body, options: options,
messageType: Self.self, extensions: extensions)
if decoder.scanner.skipOptionalNull() {
if let customCodable = Self.self as? _CustomJSONCodable.Type,
let message = try customCodable.decodedFromJSONNull() {
self = message as! Self
} else {
throw JSONDecodingError.illegalNull
}
} else {
try decoder.decodeFullObject(message: &self)
}
if !decoder.scanner.complete {
throw JSONDecodingError.trailingGarbage
}
}
}
}

View File

@ -0,0 +1,146 @@
// Sources/SwiftProtobuf/Array+JSONAdditions.swift - JSON format primitive types
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extensions to `Array` to support JSON encoding/decoding.
///
// -----------------------------------------------------------------------------
import Foundation
/// JSON encoding and decoding methods for arrays of messages.
extension Message {
/// Returns a string containing the JSON serialization of the messages.
///
/// Unlike binary encoding, presence of required fields is not enforced when
/// serializing to JSON.
///
/// - Returns: A string containing the JSON serialization of the messages.
/// - Parameters:
/// - collection: The list of messages to encode.
/// - options: The JSONEncodingOptions to use.
/// - Throws: `JSONEncodingError` if encoding fails.
public static func jsonString<C: Collection>(
from collection: C,
options: JSONEncodingOptions = JSONEncodingOptions()
) throws -> String where C.Iterator.Element == Self {
let data = try jsonUTF8Data(from: collection, options: options)
return String(data: data, encoding: String.Encoding.utf8)!
}
/// Returns a Data containing the UTF-8 JSON serialization of the messages.
///
/// Unlike binary encoding, presence of required fields is not enforced when
/// serializing to JSON.
///
/// - Returns: A Data containing the JSON serialization of the messages.
/// - Parameters:
/// - collection: The list of messages to encode.
/// - options: The JSONEncodingOptions to use.
/// - Throws: `JSONEncodingError` if encoding fails.
public static func jsonUTF8Data<C: Collection>(
from collection: C,
options: JSONEncodingOptions = JSONEncodingOptions()
) throws -> Data where C.Iterator.Element == Self {
var visitor = try JSONEncodingVisitor(type: Self.self, options: options)
visitor.startArray()
for message in collection {
visitor.startArrayObject(message: message)
try message.traverse(visitor: &visitor)
visitor.endObject()
}
visitor.endArray()
return visitor.dataResult
}
/// Creates a new array of messages by decoding the given string containing a
/// serialized array of messages in JSON format.
///
/// - Parameter jsonString: The JSON-formatted string to decode.
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public static func array(
fromJSONString jsonString: String,
options: JSONDecodingOptions = JSONDecodingOptions()
) throws -> [Self] {
return try self.array(fromJSONString: jsonString,
extensions: SimpleExtensionMap(),
options: options)
}
/// Creates a new array of messages by decoding the given string containing a
/// serialized array of messages in JSON format.
///
/// - Parameter jsonString: The JSON-formatted string to decode.
/// - Parameter extensions: The extension map to use with this decode
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public static func array(
fromJSONString jsonString: String,
extensions: ExtensionMap = SimpleExtensionMap(),
options: JSONDecodingOptions = JSONDecodingOptions()
) throws -> [Self] {
if jsonString.isEmpty {
throw JSONDecodingError.truncated
}
if let data = jsonString.data(using: String.Encoding.utf8) {
return try array(fromJSONUTF8Data: data, extensions: extensions, options: options)
} else {
throw JSONDecodingError.truncated
}
}
/// Creates a new array of messages by decoding the given `Data` containing a
/// serialized array of messages in JSON format, interpreting the data as
/// UTF-8 encoded text.
///
/// - Parameter jsonUTF8Data: The JSON-formatted data to decode, represented
/// as UTF-8 encoded text.
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public static func array(
fromJSONUTF8Data jsonUTF8Data: Data,
options: JSONDecodingOptions = JSONDecodingOptions()
) throws -> [Self] {
return try self.array(fromJSONUTF8Data: jsonUTF8Data,
extensions: SimpleExtensionMap(),
options: options)
}
/// Creates a new array of messages by decoding the given `Data` containing a
/// serialized array of messages in JSON format, interpreting the data as
/// UTF-8 encoded text.
///
/// - Parameter jsonUTF8Data: The JSON-formatted data to decode, represented
/// as UTF-8 encoded text.
/// - Parameter extensions: The extension map to use with this decode
/// - Parameter options: The JSONDecodingOptions to use.
/// - Throws: `JSONDecodingError` if decoding fails.
public static func array(
fromJSONUTF8Data jsonUTF8Data: Data,
extensions: ExtensionMap = SimpleExtensionMap(),
options: JSONDecodingOptions = JSONDecodingOptions()
) throws -> [Self] {
return try jsonUTF8Data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
var array = [Self]()
if body.count > 0 {
var decoder = JSONDecoder(source: body, options: options,
messageType: Self.self, extensions: extensions)
try decoder.decodeRepeatedMessageField(value: &array)
if !decoder.scanner.complete {
throw JSONDecodingError.trailingGarbage
}
}
return array
}
}
}

View File

@ -0,0 +1,111 @@
// Sources/SwiftProtobuf/Message+TextFormatAdditions.swift - Text format primitive types
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Extensions to `Message` to support text format encoding/decoding.
///
// -----------------------------------------------------------------------------
import Foundation
/// Text format encoding and decoding methods for messages.
extension Message {
/// Returns a string containing the Protocol Buffer text format serialization
/// of the message.
///
/// Unlike binary encoding, presence of required fields is not enforced when
/// serializing to text format.
///
/// - Returns: A string containing the text format serialization of the
/// message.
public func textFormatString() -> String {
// This is implemented as a separate zero-argument function
// to preserve binary compatibility.
return textFormatString(options: TextFormatEncodingOptions())
}
/// Returns a string containing the Protocol Buffer text format serialization
/// of the message.
///
/// Unlike binary encoding, presence of required fields is not enforced when
/// serializing to JSON.
///
/// - Returns: A string containing the text format serialization of the message.
/// - Parameters:
/// - options: The TextFormatEncodingOptions to use.
public func textFormatString(
options: TextFormatEncodingOptions
) -> String {
var visitor = TextFormatEncodingVisitor(message: self, options: options)
if let any = self as? Google_Protobuf_Any {
any._storage.textTraverse(visitor: &visitor)
} else {
// Although the general traversal/encoding infrastructure supports
// throwing errors (needed for JSON/Binary WKTs support, binary format
// missing required fields); TextEncoding never actually does throw.
try! traverse(visitor: &visitor)
}
return visitor.result
}
/// Creates a new message by decoding the given string containing a
/// serialized message in Protocol Buffer text format.
///
/// - Parameters:
/// - textFormatString: The text format string to decode.
/// - extensions: An `ExtensionMap` used to look up and decode any
/// extensions in this message or messages nested within this message's
/// fields.
/// - Throws: an instance of `TextFormatDecodingError` on failure.
public init(
textFormatString: String,
extensions: ExtensionMap? = nil
) throws {
// TODO: Remove this api and default the options instead. This api has to
// exist for anything compiled against an older version of the library.
try self.init(textFormatString: textFormatString,
options: TextFormatDecodingOptions(),
extensions: extensions)
}
/// Creates a new message by decoding the given string containing a
/// serialized message in Protocol Buffer text format.
///
/// - Parameters:
/// - textFormatString: The text format string to decode.
/// - options: The `TextFormatDencodingOptions` to use.
/// - extensions: An `ExtensionMap` used to look up and decode any
/// extensions in this message or messages nested within this message's
/// fields.
/// - Throws: an instance of `TextFormatDecodingError` on failure.
public init(
textFormatString: String,
options: TextFormatDecodingOptions,
extensions: ExtensionMap? = nil
) throws {
self.init()
if !textFormatString.isEmpty {
if let data = textFormatString.data(using: String.Encoding.utf8) {
try data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
if let baseAddress = body.baseAddress, body.count > 0 {
var decoder = try TextFormatDecoder(messageType: Self.self,
utf8Pointer: baseAddress,
count: body.count,
options: options,
extensions: extensions)
try decodeMessage(decoder: &decoder)
if !decoder.complete {
throw TextFormatDecodingError.trailingGarbage
}
}
}
}
}
}
}

View File

@ -0,0 +1,216 @@
// Sources/SwiftProtobuf/Message.swift - Message 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
//
/// The protocol which all generated protobuf messages implement.
/// `Message` is the protocol type you should use whenever
/// you need an argument or variable which holds "some message".
///
/// Generated messages also implement `Hashable`, and thus `Equatable`.
/// However, the protocol conformance is declared on a different protocol.
/// This allows you to use `Message` as a type directly:
///
/// func consume(message: Message) { ... }
///
/// Instead of needing to use it as a type constraint on a generic declaration:
///
/// func consume<M: Message>(message: M) { ... }
///
/// If you need to convince the compiler that your message is `Hashable` so
/// you can insert it into a `Set` or use it as a `Dictionary` key, use
/// a generic declaration with a type constraint:
///
/// func insertIntoSet<M: Message & Hashable>(message: M) {
/// mySet.insert(message)
/// }
///
/// The actual functionality is implemented either in the generated code or in
/// default implementations of the below methods and properties.
public protocol Message: CustomDebugStringConvertible {
/// Creates a new message with all of its fields initialized to their default
/// values.
init()
// Metadata
// Basic facts about this class and the proto message it was generated from
// Used by various encoders and decoders
/// The fully-scoped name of the message from the original .proto file,
/// including any relevant package name.
static var protoMessageName: String { get }
/// True if all required fields (if any) on this message and any nested
/// messages (recursively) have values set; otherwise, false.
var isInitialized: Bool { get }
/// Some formats include enough information to transport fields that were
/// not known at generation time. When encountered, they are stored here.
var unknownFields: UnknownStorage { get set }
//
// General serialization/deserialization machinery
//
/// Decode all of the fields from the given decoder.
///
/// This is a simple loop that repeatedly gets the next field number
/// from `decoder.nextFieldNumber()` and then uses the number returned
/// and the type information from the original .proto file to decide
/// what type of data should be decoded for that field. The corresponding
/// method on the decoder is then called to get the field value.
///
/// This is the core method used by the deserialization machinery. It is
/// `public` to enable users to implement their own encoding formats by
/// conforming to `Decoder`; it should not be called otherwise.
///
/// Note that this is not specific to binary encodng; formats that use
/// textual identifiers translate those to field numbers and also go
/// through this to decode messages.
///
/// - Parameters:
/// - decoder: a `Decoder`; the `Message` will call the method
/// corresponding to the type of this field.
/// - Throws: an error on failure or type mismatch. The type of error
/// thrown depends on which decoder is used.
mutating func decodeMessage<D: Decoder>(decoder: inout D) throws
/// Traverses the fields of the message, calling the appropriate methods
/// of the passed `Visitor` object.
///
/// This is used internally by:
///
/// * Protobuf binary serialization
/// * JSON serialization (with some twists to account for specialty JSON)
/// * Protobuf Text serialization
/// * `Hashable` computation
///
/// Conceptually, serializers create visitor objects that are
/// then passed recursively to every message and field via generated
/// `traverse` methods. The details get a little involved due to
/// the need to allow particular messages to override particular
/// behaviors for specific encodings, but the general idea is quite simple.
func traverse<V: Visitor>(visitor: inout V) throws
// Standard utility properties and methods.
// Most of these are simple wrappers on top of the visitor machinery.
// They are implemented in the protocol, not in the generated structs,
// so can be overridden in user code by defining custom extensions to
// the generated struct.
#if swift(>=4.2)
/// An implementation of hash(into:) to provide conformance with the
/// `Hashable` protocol.
func hash(into hasher: inout Hasher)
#else // swift(>=4.2)
/// The hash value generated from this message's contents, for conformance
/// with the `Hashable` protocol.
var hashValue: Int { get }
#endif // swift(>=4.2)
/// Helper to compare `Message`s when not having a specific type to use
/// normal `Equatable`. `Equatable` is provided with specific generated
/// types.
func isEqualTo(message: Message) -> Bool
}
extension Message {
/// Generated proto2 messages that contain required fields, nested messages
/// that contain required fields, and/or extensions will provide their own
/// implementation of this property that tests that all required fields are
/// set. Users of the generated code SHOULD NOT override this property.
public var isInitialized: Bool {
// The generated code will include a specialization as needed.
return true
}
/// A hash based on the message's full contents.
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
var visitor = HashVisitor(hasher)
try? traverse(visitor: &visitor)
hasher = visitor.hasher
}
#else // swift(>=4.2)
public var hashValue: Int {
var visitor = HashVisitor()
try? traverse(visitor: &visitor)
return visitor.hashValue
}
#endif // swift(>=4.2)
/// A description generated by recursively visiting all fields in the message,
/// including messages.
public var debugDescription: String {
// TODO Ideally there would be something like serializeText() that can
// take a prefix so we could do something like:
// [class name](
// [text format]
// )
let className = String(reflecting: type(of: self))
let header = "\(className):\n"
return header + textFormatString()
}
/// Creates an instance of the message type on which this method is called,
/// executes the given block passing the message in as its sole `inout`
/// argument, and then returns the message.
///
/// This method acts essentially as a "builder" in that the initialization of
/// the message is captured within the block, allowing the returned value to
/// be set in an immutable variable. For example,
///
/// let msg = MyMessage.with { $0.myField = "foo" }
/// msg.myOtherField = 5 // error: msg is immutable
///
/// - Parameter populator: A block or function that populates the new message,
/// which is passed into the block as an `inout` argument.
/// - Returns: The message after execution of the block.
public static func with(
_ populator: (inout Self) throws -> ()
) rethrows -> Self {
var message = Self()
try populator(&message)
return message
}
}
/// Implementation base for all messages; not intended for client use.
///
/// In general, use `SwiftProtobuf.Message` instead when you need a variable or
/// argument that can hold any type of message. Occasionally, you can use
/// `SwiftProtobuf.Message & Equatable` or `SwiftProtobuf.Message & Hashable` as
/// generic constraints if you need to write generic code that can be applied to
/// multiple message types that uses equality tests, puts messages in a `Set`,
/// or uses them as `Dictionary` keys.
public protocol _MessageImplementationBase: Message, Hashable {
// Legacy function; no longer used, but left to maintain source compatibility.
func _protobuf_generated_isEqualTo(other: Self) -> Bool
}
extension _MessageImplementationBase {
public func isEqualTo(message: Message) -> Bool {
guard let other = message as? Self else {
return false
}
return self == other
}
// Legacy default implementation that is used by old generated code, current
// versions of the plugin/generator provide this directly, but this is here
// just to avoid breaking source compatibility.
public static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs._protobuf_generated_isEqualTo(other: rhs)
}
// Legacy function that is generated by old versions of the plugin/generator,
// defaulted to keep things simple without changing the api surface.
public func _protobuf_generated_isEqualTo(other: Self) -> Bool {
return self == other
}
}

View File

@ -0,0 +1,41 @@
// Sources/SwiftProtobuf/MessageExtension.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
//
// -----------------------------------------------------------------------------
///
/// A 'Message Extension' is an immutable class object that describes
/// a particular extension field, including string and number
/// identifiers, serialization details, and the identity of the
/// message that is being extended.
///
// -----------------------------------------------------------------------------
/// Type-erased MessageExtension field implementation.
public protocol AnyMessageExtension {
var fieldNumber: Int { get }
var fieldName: String { get }
var messageType: Message.Type { get }
func _protobuf_newField<D: Decoder>(decoder: inout D) throws -> AnyExtensionField?
}
/// A "Message Extension" relates a particular extension field to
/// a particular message. The generic constraints allow
/// compile-time compatibility checks.
public class MessageExtension<FieldType: ExtensionField, MessageType: Message>: AnyMessageExtension {
public let fieldNumber: Int
public let fieldName: String
public let messageType: Message.Type
public init(_protobuf_fieldNumber: Int, fieldName: String) {
self.fieldNumber = _protobuf_fieldNumber
self.fieldName = fieldName
self.messageType = MessageType.self
}
public func _protobuf_newField<D: Decoder>(decoder: inout D) throws -> AnyExtensionField? {
return try FieldType(protobufExtension: self, decoder: &decoder)
}
}

View File

@ -0,0 +1,310 @@
// Sources/SwiftProtobuf/NameMap.swift - Bidirectional number/name mapping
//
// 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
//
// -----------------------------------------------------------------------------
/// TODO: Right now, only the NameMap and the NameDescription enum
/// (which are directly used by the generated code) are public.
/// This means that code outside the library has no way to actually
/// use this data. We should develop and publicize a suitable API
/// for that purpose. (Which might be the same as the internal API.)
/// This must be exactly the same as the corresponding code in the
/// protoc-gen-swift code generator. Changing it will break
/// compatibility of the library with older generated code.
///
/// It does not necessarily need to match protoc's JSON field naming
/// logic, however.
private func toJsonFieldName(_ s: String) -> String {
var result = String()
var capitalizeNext = false
for c in s {
if c == "_" {
capitalizeNext = true
} else if capitalizeNext {
result.append(String(c).uppercased())
capitalizeNext = false
} else {
result.append(String(c))
}
}
return result
}
/// Allocate static memory buffers to intern UTF-8
/// string data. Track the buffers and release all of those buffers
/// in case we ever get deallocated.
fileprivate class InternPool {
private var interned = [UnsafeRawBufferPointer]()
func intern(utf8: String.UTF8View) -> UnsafeRawBufferPointer {
#if swift(>=4.1)
let mutable = UnsafeMutableRawBufferPointer.allocate(byteCount: utf8.count,
alignment: MemoryLayout<UInt8>.alignment)
#else
let mutable = UnsafeMutableRawBufferPointer.allocate(count: utf8.count)
#endif
mutable.copyBytes(from: utf8)
let immutable = UnsafeRawBufferPointer(mutable)
interned.append(immutable)
return immutable
}
func intern(utf8Ptr: UnsafeBufferPointer<UInt8>) -> UnsafeRawBufferPointer {
#if swift(>=4.1)
let mutable = UnsafeMutableRawBufferPointer.allocate(byteCount: utf8Ptr.count,
alignment: MemoryLayout<UInt8>.alignment)
#else
let mutable = UnsafeMutableRawBufferPointer.allocate(count: utf8.count)
#endif
mutable.copyBytes(from: utf8Ptr)
let immutable = UnsafeRawBufferPointer(mutable)
interned.append(immutable)
return immutable
}
deinit {
for buff in interned {
#if swift(>=4.1)
buff.deallocate()
#else
let p = UnsafeMutableRawPointer(mutating: buff.baseAddress)!
p.deallocate(bytes: buff.count, alignedTo: 1)
#endif
}
}
}
#if !swift(>=4.2)
// Constants for FNV hash http://tools.ietf.org/html/draft-eastlake-fnv-03
private let i_2166136261 = Int(bitPattern: 2166136261)
private let i_16777619 = Int(16777619)
#endif
/// An immutable bidirectional mapping between field/enum-case names
/// and numbers, used to record field names for text-based
/// serialization (JSON and text). These maps are lazily instantiated
/// for each message as needed, so there is no run-time overhead for
/// users who do not use text-based serialization formats.
public struct _NameMap: ExpressibleByDictionaryLiteral {
/// An immutable interned string container. The `utf8Start` pointer
/// is guaranteed valid for the lifetime of the `NameMap` that you
/// fetched it from. Since `NameMap`s are only instantiated as
/// immutable static values, that should be the lifetime of the
/// program.
///
/// Internally, this uses `StaticString` (which refers to a fixed
/// block of UTF-8 data) where possible. In cases where the string
/// has to be computed, it caches the UTF-8 bytes in an
/// unmovable and immutable heap area.
internal struct Name: Hashable, CustomStringConvertible {
// This should not be used outside of this file, as it requires
// coordinating the lifecycle with the lifecycle of the pool
// where the raw UTF8 gets interned.
fileprivate init(staticString: StaticString, pool: InternPool) {
self.nameString = .staticString(staticString)
if staticString.hasPointerRepresentation {
self.utf8Buffer = UnsafeRawBufferPointer(start: staticString.utf8Start,
count: staticString.utf8CodeUnitCount)
} else {
self.utf8Buffer = staticString.withUTF8Buffer { pool.intern(utf8Ptr: $0) }
}
}
// This should not be used outside of this file, as it requires
// coordinating the lifecycle with the lifecycle of the pool
// where the raw UTF8 gets interned.
fileprivate init(string: String, pool: InternPool) {
let utf8 = string.utf8
self.utf8Buffer = pool.intern(utf8: utf8)
self.nameString = .string(string)
}
// This is for building a transient `Name` object sufficient for lookup purposes.
// It MUST NOT be exposed outside of this file.
fileprivate init(transientUtf8Buffer: UnsafeRawBufferPointer) {
self.nameString = .staticString("")
self.utf8Buffer = transientUtf8Buffer
}
private(set) var utf8Buffer: UnsafeRawBufferPointer
private enum NameString {
case string(String)
case staticString(StaticString)
}
private var nameString: NameString
public var description: String {
switch nameString {
case .string(let s): return s
case .staticString(let s): return s.description
}
}
#if swift(>=4.2)
public func hash(into hasher: inout Hasher) {
for byte in utf8Buffer {
hasher.combine(byte)
}
}
#else // swift(>=4.2)
public var hashValue: Int {
var h = i_2166136261
for byte in utf8Buffer {
h = (h ^ Int(byte)) &* i_16777619
}
return h
}
#endif // swift(>=4.2)
public static func ==(lhs: Name, rhs: Name) -> Bool {
if lhs.utf8Buffer.count != rhs.utf8Buffer.count {
return false
}
return lhs.utf8Buffer.elementsEqual(rhs.utf8Buffer)
}
}
/// The JSON and proto names for a particular field, enum case, or extension.
internal struct Names {
private(set) var json: Name?
private(set) var proto: Name
}
/// A description of the names for a particular field or enum case.
/// The different forms here let us minimize the amount of string
/// data that we store in the binary.
///
/// These are only used in the generated code to initialize a NameMap.
public enum NameDescription {
/// The proto (text format) name and the JSON name are the same string.
case same(proto: StaticString)
/// The JSON name can be computed from the proto string
case standard(proto: StaticString)
/// The JSON and text format names are just different.
case unique(proto: StaticString, json: StaticString)
/// Used for enum cases only to represent a value's primary proto name (the
/// first defined case) and its aliases. The JSON and text format names for
/// enums are always the same.
case aliased(proto: StaticString, aliases: [StaticString])
}
private var internPool = InternPool()
/// The mapping from field/enum-case numbers to names.
private var numberToNameMap: [Int: Names] = [:]
/// The mapping from proto/text names to field/enum-case numbers.
private var protoToNumberMap: [Name: Int] = [:]
/// The mapping from JSON names to field/enum-case numbers.
/// Note that this also contains all of the proto/text names,
/// as required by Google's spec for protobuf JSON.
private var jsonToNumberMap: [Name: Int] = [:]
/// Creates a new empty field/enum-case name/number mapping.
public init() {}
/// Build the bidirectional maps between numbers and proto/JSON names.
public init(dictionaryLiteral elements: (Int, NameDescription)...) {
for (number, description) in elements {
switch description {
case .same(proto: let p):
let protoName = Name(staticString: p, pool: internPool)
let names = Names(json: protoName, proto: protoName)
numberToNameMap[number] = names
protoToNumberMap[protoName] = number
jsonToNumberMap[protoName] = number
case .standard(proto: let p):
let protoName = Name(staticString: p, pool: internPool)
let jsonString = toJsonFieldName(protoName.description)
let jsonName = Name(string: jsonString, pool: internPool)
let names = Names(json: jsonName, proto: protoName)
numberToNameMap[number] = names
protoToNumberMap[protoName] = number
jsonToNumberMap[protoName] = number
jsonToNumberMap[jsonName] = number
case .unique(proto: let p, json: let j):
let jsonName = Name(staticString: j, pool: internPool)
let protoName = Name(staticString: p, pool: internPool)
let names = Names(json: jsonName, proto: protoName)
numberToNameMap[number] = names
protoToNumberMap[protoName] = number
jsonToNumberMap[protoName] = number
jsonToNumberMap[jsonName] = number
case .aliased(proto: let p, aliases: let aliases):
let protoName = Name(staticString: p, pool: internPool)
let names = Names(json: protoName, proto: protoName)
numberToNameMap[number] = names
protoToNumberMap[protoName] = number
jsonToNumberMap[protoName] = number
for alias in aliases {
let protoName = Name(staticString: alias, pool: internPool)
protoToNumberMap[protoName] = number
jsonToNumberMap[protoName] = number
}
}
}
}
/// Returns the name bundle for the field/enum-case with the given number, or
/// `nil` if there is no match.
internal func names(for number: Int) -> Names? {
return numberToNameMap[number]
}
/// Returns the field/enum-case number that has the given JSON name,
/// or `nil` if there is no match.
///
/// This is used by the Text format parser to look up field or enum
/// names using a direct reference to the un-decoded UTF8 bytes.
internal func number(forProtoName raw: UnsafeRawBufferPointer) -> Int? {
let n = Name(transientUtf8Buffer: raw)
return protoToNumberMap[n]
}
/// Returns the field/enum-case number that has the given JSON name,
/// or `nil` if there is no match.
///
/// This accepts a regular `String` and is used in JSON parsing
/// only when a field name or enum name was decoded from a string
/// containing backslash escapes.
///
/// JSON parsing must interpret *both* the JSON name of the
/// field/enum-case provided by the descriptor *as well as* its
/// original proto/text name.
internal func number(forJSONName name: String) -> Int? {
let utf8 = Array(name.utf8)
return utf8.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) in
let n = Name(transientUtf8Buffer: buffer)
return jsonToNumberMap[n]
}
}
/// Returns the field/enum-case number that has the given JSON name,
/// or `nil` if there is no match.
///
/// This is used by the JSON parser when a field name or enum name
/// required no special processing. As a result, we can avoid
/// copying the name and look up the number using a direct reference
/// to the un-decoded UTF8 bytes.
internal func number(forJSONName raw: UnsafeRawBufferPointer) -> Int? {
let n = Name(transientUtf8Buffer: raw)
return jsonToNumberMap[n]
}
}

View File

@ -0,0 +1,23 @@
// Sources/SwiftProtobuf/ProtoNameProviding.swift - Support for accessing proto names
//
// 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
//
// -----------------------------------------------------------------------------
/// SwiftProtobuf Internal: Common support looking up field names.
///
/// Messages conform to this protocol to provide the proto/text and JSON field
/// names for their fields. This allows these names to be pulled out into
/// extensions in separate files so that users can omit them in release builds
/// (reducing bloat and minimizing leaks of field names).
public protocol _ProtoNameProviding {
/// The mapping between field numbers and proto/JSON field names defined in
/// the conforming message type.
static var _protobuf_nameMap: _NameMap { get }
}

View File

@ -0,0 +1,43 @@
// Sources/SwiftProtobuf/ProtobufAPIVersionCheck.swift - Version checking
//
// 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
//
// -----------------------------------------------------------------------------
///
/// A scheme that ensures that generated protos cannot be compiled or linked
/// against a version of the runtime with which they are not compatible.
///
/// In many cases, API changes themselves might introduce incompatibilities
/// between generated code and the runtime library, but we also want to protect
/// against cases where breaking behavioral changes (without affecting the API)
/// would cause generated code to be incompatible with a particular version of
/// the runtime.
///
// -----------------------------------------------------------------------------
/// An empty protocol that encodes the version of the runtime library.
///
/// This protocol will be replaced with one containing a different version
/// number any time that breaking changes are made to the Swift Protobuf API.
/// Combined with the protocol below, this lets us verify that generated code is
/// never compiled against a version of the API with which it is incompatible.
///
/// The version associated with a particular build of the compiler is defined as
/// `Version.compatibilityVersion` in `protoc-gen-swift`. That version and this
/// version must match for the generated protos to be compatible, so if you
/// update one, make sure to update it here and in the associated type below.
public protocol ProtobufAPIVersion_2 {}
/// This protocol is expected to be implemented by a `fileprivate` type in each
/// source file emitted by `protoc-gen-swift`. It effectively creates a binding
/// between the version of the generated code and the version of this library,
/// causing a compile-time error (with reasonable diagnostics) if they are
/// incompatible.
public protocol ProtobufAPIVersionCheck {
associatedtype Version: ProtobufAPIVersion_2
}

View File

@ -0,0 +1,39 @@
// Sources/SwiftProtobuf/ProtobufMap.swift - Map<> 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
//
// -----------------------------------------------------------------------------
///
/// Generic type representing proto map<> fields.
///
// -----------------------------------------------------------------------------
import Foundation
/// SwiftProtobuf Internal: Support for Encoding/Decoding.
public struct _ProtobufMap<KeyType: MapKeyType, ValueType: FieldType>
{
public typealias Key = KeyType.BaseType
public typealias Value = ValueType.BaseType
public typealias BaseType = Dictionary<Key, Value>
}
/// SwiftProtobuf Internal: Support for Encoding/Decoding.
public struct _ProtobufMessageMap<KeyType: MapKeyType, ValueType: Message & Hashable>
{
public typealias Key = KeyType.BaseType
public typealias Value = ValueType
public typealias BaseType = Dictionary<Key, Value>
}
/// SwiftProtobuf Internal: Support for Encoding/Decoding.
public struct _ProtobufEnumMap<KeyType: MapKeyType, ValueType: Enum>
{
public typealias Key = KeyType.BaseType
public typealias Value = ValueType
public typealias BaseType = Dictionary<Key, Value>
}

View File

@ -0,0 +1,268 @@
// Sources/SwiftProtobuf/SelectiveVisitor.swift - Base for custom Visitors
//
// 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
//
// -----------------------------------------------------------------------------
///
/// A base for Visitors that only expect a subset of things to called.
///
// -----------------------------------------------------------------------------
import Foundation
/// A base for Visitors that only expects a subset of things to called.
internal protocol SelectiveVisitor: Visitor {
// Adds nothing.
}
/// Default impls for everything so things using this only have to write the
/// methods they expect. Asserts to catch developer errors, but becomes
/// nothing in release to keep code size small.
///
/// NOTE: This is an impl for *everything*. This means the default impls
/// provided by Visitor to bridge packed->repeated, repeated->singular, etc
/// won't kick in.
extension SelectiveVisitor {
internal mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularInt32Field(value: Int32, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularUInt32Field(value: UInt32, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularSInt32Field(value: Int32, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularSInt64Field(value: Int64, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularFixed32Field(value: UInt32, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularFixed64Field(value: UInt64, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularSFixed32Field(value: Int32, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularSFixed64Field(value: Int64, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularEnumField<E: Enum>(value: E, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitSingularGroupField<G: Message>(value: G, fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedMessageField<M: Message>(value: [M], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitRepeatedGroupField<G: Message>(value: [G], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitPackedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitMapField<KeyType, ValueType: MapValueType>(
fieldType: _ProtobufMap<KeyType, ValueType>.Type,
value: _ProtobufMap<KeyType, ValueType>.BaseType,
fieldNumber: Int) throws {
assert(false)
}
internal mutating func visitMapField<KeyType, ValueType>(
fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type,
value: _ProtobufEnumMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws where ValueType.RawValue == Int {
assert(false)
}
internal mutating func visitMapField<KeyType, ValueType>(
fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type,
value: _ProtobufMessageMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws {
assert(false)
}
internal mutating func visitExtensionFields(fields: ExtensionFieldValueSet, start: Int, end: Int) throws {
assert(false)
}
internal mutating func visitExtensionFieldsAsMessageSet(
fields: ExtensionFieldValueSet,
start: Int,
end: Int
) throws {
assert(false)
}
internal mutating func visitUnknown(bytes: Data) throws {
assert(false)
}
}

View File

@ -0,0 +1,112 @@
// Sources/SwiftProtobuf/SimpleExtensionMap.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
//
// -----------------------------------------------------------------------------
///
/// A default implementation of ExtensionMap.
///
// -----------------------------------------------------------------------------
// Note: The generated code only relies on ExpressibleByArrayLiteral
public struct SimpleExtensionMap: ExtensionMap, ExpressibleByArrayLiteral, CustomDebugStringConvertible {
public typealias Element = AnyMessageExtension
// Since type objects aren't Hashable, we can't do much better than this...
internal var fields = [Int: Array<AnyMessageExtension>]()
public init() {}
public init(arrayLiteral: Element...) {
insert(contentsOf: arrayLiteral)
}
public init(_ others: SimpleExtensionMap...) {
for other in others {
formUnion(other)
}
}
public subscript(messageType: Message.Type, fieldNumber: Int) -> AnyMessageExtension? {
get {
if let l = fields[fieldNumber] {
for e in l {
if messageType == e.messageType {
return e
}
}
}
return nil
}
}
public func fieldNumberForProto(messageType: Message.Type, protoFieldName: String) -> Int? {
// TODO: Make this faster...
for (_, list) in fields {
for e in list {
if e.fieldName == protoFieldName && e.messageType == messageType {
return e.fieldNumber
}
}
}
return nil
}
public mutating func insert(_ newValue: Element) {
let fieldNumber = newValue.fieldNumber
if let l = fields[fieldNumber] {
let messageType = newValue.messageType
var newL = l.filter { return $0.messageType != messageType }
newL.append(newValue)
fields[fieldNumber] = newL
} else {
fields[fieldNumber] = [newValue]
}
}
public mutating func insert(contentsOf: [Element]) {
for e in contentsOf {
insert(e)
}
}
public mutating func formUnion(_ other: SimpleExtensionMap) {
for (fieldNumber, otherList) in other.fields {
if let list = fields[fieldNumber] {
var newList = list.filter {
for o in otherList {
if $0.messageType == o.messageType { return false }
}
return true
}
newList.append(contentsOf: otherList)
fields[fieldNumber] = newList
} else {
fields[fieldNumber] = otherList
}
}
}
public func union(_ other: SimpleExtensionMap) -> SimpleExtensionMap {
var out = self
out.formUnion(other)
return out
}
public var debugDescription: String {
var names = [String]()
for (_, list) in fields {
for e in list {
names.append("\(e.fieldName):(\(e.fieldNumber))")
}
}
let d = names.joined(separator: ",")
return "SimpleExtensionMap(\(d))"
}
}

View File

@ -0,0 +1,109 @@
// Sources/SwiftProtobuf/StringUtils.swift - String utility functions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Utility functions for converting UTF8 bytes into Strings.
/// These functions must:
/// * Accept any valid UTF8, including a zero byte (which is
/// a valid UTF8 encoding of U+0000)
/// * Return nil for any invalid UTF8
/// * Be fast (since they're extensively used by all decoders
/// and even some of the encoders)
///
// -----------------------------------------------------------------------------
import Foundation
/*
Note: Once our minimum support version is at least Swift 5.3, we
should probably recast the following to use
String(unsafeUninitializedCapacity:)
*/
// Note: We're trying to avoid Foundation's String(format:) since that's not
// universally available.
fileprivate func formatZeroPaddedInt(_ value: Int32, digits: Int) -> String {
precondition(value >= 0)
let s = String(value)
if s.count >= digits {
return s
} else {
let pad = String(repeating: "0", count: digits - s.count)
return pad + s
}
}
internal func twoDigit(_ value: Int32) -> String {
return formatZeroPaddedInt(value, digits: 2)
}
internal func threeDigit(_ value: Int32) -> String {
return formatZeroPaddedInt(value, digits: 3)
}
internal func fourDigit(_ value: Int32) -> String {
return formatZeroPaddedInt(value, digits: 4)
}
internal func sixDigit(_ value: Int32) -> String {
return formatZeroPaddedInt(value, digits: 6)
}
internal func nineDigit(_ value: Int32) -> String {
return formatZeroPaddedInt(value, digits: 9)
}
// Wrapper that takes a buffer and start/end offsets
internal func utf8ToString(
bytes: UnsafeRawBufferPointer,
start: UnsafeRawBufferPointer.Index,
end: UnsafeRawBufferPointer.Index
) -> String? {
return utf8ToString(bytes: bytes.baseAddress! + start, count: end - start)
}
// Swift 4 introduced new faster String facilities
// that seem to work consistently across all platforms.
// Notes on performance:
//
// The pre-verification here only takes about 10% of
// the time needed for constructing the string.
// Eliminating it would provide only a very minor
// speed improvement.
//
// On macOS, this is only about 25% faster than
// the Foundation initializer used below for Swift 3.
// On Linux, the Foundation initializer is much
// slower than on macOS, so this is a much bigger
// win there.
internal func utf8ToString(bytes: UnsafeRawPointer, count: Int) -> String? {
if count == 0 {
return String()
}
let codeUnits = UnsafeRawBufferPointer(start: bytes, count: count)
let sourceEncoding = Unicode.UTF8.self
// Verify that the UTF-8 is valid.
var p = sourceEncoding.ForwardParser()
var i = codeUnits.makeIterator()
Loop:
while true {
switch p.parseScalar(from: &i) {
case .valid(_):
break
case .error:
return nil
case .emptyInput:
break Loop
}
}
// This initializer is fast but does not reject broken
// UTF-8 (which is why we validate the UTF-8 above).
return String(decoding: codeUnits, as: sourceEncoding)
}

View File

@ -0,0 +1,726 @@
// Sources/SwiftProtobuf/TextFormatDecoder.swift - Text format decoding
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Test format decoding engine.
///
// -----------------------------------------------------------------------------
import Foundation
///
/// Provides a higher-level interface to the token stream coming
/// from a TextFormatScanner. In particular, this provides
/// single-token pushback and convenience functions for iterating
/// over complex structures.
///
internal struct TextFormatDecoder: Decoder {
internal var scanner: TextFormatScanner
private var fieldCount = 0
private var terminator: UInt8?
private var fieldNameMap: _NameMap?
private var messageType: Message.Type?
internal var complete: Bool {
mutating get {
return scanner.complete
}
}
internal init(
messageType: Message.Type,
utf8Pointer: UnsafeRawPointer,
count: Int,
options: TextFormatDecodingOptions,
extensions: ExtensionMap?
) throws {
scanner = TextFormatScanner(utf8Pointer: utf8Pointer, count: count, options: options, extensions: extensions)
guard let nameProviding = (messageType as? _ProtoNameProviding.Type) else {
throw TextFormatDecodingError.missingFieldNames
}
fieldNameMap = nameProviding._protobuf_nameMap
self.messageType = messageType
}
internal init(messageType: Message.Type, scanner: TextFormatScanner, terminator: UInt8?) throws {
self.scanner = scanner
self.terminator = terminator
guard let nameProviding = (messageType as? _ProtoNameProviding.Type) else {
throw TextFormatDecodingError.missingFieldNames
}
fieldNameMap = nameProviding._protobuf_nameMap
self.messageType = messageType
}
mutating func handleConflictingOneOf() throws {
throw TextFormatDecodingError.conflictingOneOf
}
mutating func nextFieldNumber() throws -> Int? {
if let terminator = terminator {
if scanner.skipOptionalObjectEnd(terminator) {
return nil
}
}
if fieldCount > 0 {
scanner.skipOptionalSeparator()
}
if let key = try scanner.nextOptionalExtensionKey() {
// Extension key; look up in the extension registry
if let fieldNumber = scanner.extensions?.fieldNumberForProto(messageType: messageType!, protoFieldName: key) {
fieldCount += 1
return fieldNumber
} else {
throw TextFormatDecodingError.unknownField
}
} else if let fieldNumber = try scanner.nextFieldNumber(names: fieldNameMap!) {
fieldCount += 1
return fieldNumber
} else if terminator == nil {
return nil
} else {
throw TextFormatDecodingError.truncated
}
}
mutating func decodeSingularFloatField(value: inout Float) throws {
try scanner.skipRequiredColon()
value = try scanner.nextFloat()
}
mutating func decodeSingularFloatField(value: inout Float?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextFloat()
}
mutating func decodeRepeatedFloatField(value: inout [Float]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextFloat()
value.append(n)
}
} else {
let n = try scanner.nextFloat()
value.append(n)
}
}
mutating func decodeSingularDoubleField(value: inout Double) throws {
try scanner.skipRequiredColon()
value = try scanner.nextDouble()
}
mutating func decodeSingularDoubleField(value: inout Double?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextDouble()
}
mutating func decodeRepeatedDoubleField(value: inout [Double]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextDouble()
value.append(n)
}
} else {
let n = try scanner.nextDouble()
value.append(n)
}
}
mutating func decodeSingularInt32Field(value: inout Int32) throws {
try scanner.skipRequiredColon()
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw TextFormatDecodingError.malformedNumber
}
value = Int32(truncatingIfNeeded: n)
}
mutating func decodeSingularInt32Field(value: inout Int32?) throws {
try scanner.skipRequiredColon()
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw TextFormatDecodingError.malformedNumber
}
value = Int32(truncatingIfNeeded: n)
}
mutating func decodeRepeatedInt32Field(value: inout [Int32]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw TextFormatDecodingError.malformedNumber
}
value.append(Int32(truncatingIfNeeded: n))
}
} else {
let n = try scanner.nextSInt()
if n > Int64(Int32.max) || n < Int64(Int32.min) {
throw TextFormatDecodingError.malformedNumber
}
value.append(Int32(truncatingIfNeeded: n))
}
}
mutating func decodeSingularInt64Field(value: inout Int64) throws {
try scanner.skipRequiredColon()
value = try scanner.nextSInt()
}
mutating func decodeSingularInt64Field(value: inout Int64?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextSInt()
}
mutating func decodeRepeatedInt64Field(value: inout [Int64]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextSInt()
value.append(n)
}
} else {
let n = try scanner.nextSInt()
value.append(n)
}
}
mutating func decodeSingularUInt32Field(value: inout UInt32) throws {
try scanner.skipRequiredColon()
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw TextFormatDecodingError.malformedNumber
}
value = UInt32(truncatingIfNeeded: n)
}
mutating func decodeSingularUInt32Field(value: inout UInt32?) throws {
try scanner.skipRequiredColon()
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw TextFormatDecodingError.malformedNumber
}
value = UInt32(truncatingIfNeeded: n)
}
mutating func decodeRepeatedUInt32Field(value: inout [UInt32]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw TextFormatDecodingError.malformedNumber
}
value.append(UInt32(truncatingIfNeeded: n))
}
} else {
let n = try scanner.nextUInt()
if n > UInt64(UInt32.max) {
throw TextFormatDecodingError.malformedNumber
}
value.append(UInt32(truncatingIfNeeded: n))
}
}
mutating func decodeSingularUInt64Field(value: inout UInt64) throws {
try scanner.skipRequiredColon()
value = try scanner.nextUInt()
}
mutating func decodeSingularUInt64Field(value: inout UInt64?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextUInt()
}
mutating func decodeRepeatedUInt64Field(value: inout [UInt64]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextUInt()
value.append(n)
}
} else {
let n = try scanner.nextUInt()
value.append(n)
}
}
mutating func decodeSingularSInt32Field(value: inout Int32) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeSingularSInt32Field(value: inout Int32?) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeRepeatedSInt32Field(value: inout [Int32]) throws {
try decodeRepeatedInt32Field(value: &value)
}
mutating func decodeSingularSInt64Field(value: inout Int64) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeSingularSInt64Field(value: inout Int64?) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeRepeatedSInt64Field(value: inout [Int64]) throws {
try decodeRepeatedInt64Field(value: &value)
}
mutating func decodeSingularFixed32Field(value: inout UInt32) throws {
try decodeSingularUInt32Field(value: &value)
}
mutating func decodeSingularFixed32Field(value: inout UInt32?) throws {
try decodeSingularUInt32Field(value: &value)
}
mutating func decodeRepeatedFixed32Field(value: inout [UInt32]) throws {
try decodeRepeatedUInt32Field(value: &value)
}
mutating func decodeSingularFixed64Field(value: inout UInt64) throws {
try decodeSingularUInt64Field(value: &value)
}
mutating func decodeSingularFixed64Field(value: inout UInt64?) throws {
try decodeSingularUInt64Field(value: &value)
}
mutating func decodeRepeatedFixed64Field(value: inout [UInt64]) throws {
try decodeRepeatedUInt64Field(value: &value)
}
mutating func decodeSingularSFixed32Field(value: inout Int32) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeSingularSFixed32Field(value: inout Int32?) throws {
try decodeSingularInt32Field(value: &value)
}
mutating func decodeRepeatedSFixed32Field(value: inout [Int32]) throws {
try decodeRepeatedInt32Field(value: &value)
}
mutating func decodeSingularSFixed64Field(value: inout Int64) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeSingularSFixed64Field(value: inout Int64?) throws {
try decodeSingularInt64Field(value: &value)
}
mutating func decodeRepeatedSFixed64Field(value: inout [Int64]) throws {
try decodeRepeatedInt64Field(value: &value)
}
mutating func decodeSingularBoolField(value: inout Bool) throws {
try scanner.skipRequiredColon()
value = try scanner.nextBool()
}
mutating func decodeSingularBoolField(value: inout Bool?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextBool()
}
mutating func decodeRepeatedBoolField(value: inout [Bool]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextBool()
value.append(n)
}
} else {
let n = try scanner.nextBool()
value.append(n)
}
}
mutating func decodeSingularStringField(value: inout String) throws {
try scanner.skipRequiredColon()
value = try scanner.nextStringValue()
}
mutating func decodeSingularStringField(value: inout String?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextStringValue()
}
mutating func decodeRepeatedStringField(value: inout [String]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextStringValue()
value.append(n)
}
} else {
let n = try scanner.nextStringValue()
value.append(n)
}
}
mutating func decodeSingularBytesField(value: inout Data) throws {
try scanner.skipRequiredColon()
value = try scanner.nextBytesValue()
}
mutating func decodeSingularBytesField(value: inout Data?) throws {
try scanner.skipRequiredColon()
value = try scanner.nextBytesValue()
}
mutating func decodeRepeatedBytesField(value: inout [Data]) throws {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let n = try scanner.nextBytesValue()
value.append(n)
}
} else {
let n = try scanner.nextBytesValue()
value.append(n)
}
}
private mutating func decodeEnum<E: Enum>() throws -> E where E.RawValue == Int {
if let name = try scanner.nextOptionalEnumName() {
if let b = E(rawUTF8: name) {
return b
} else {
throw TextFormatDecodingError.unrecognizedEnumValue
}
}
let number = try scanner.nextSInt()
if number >= Int64(Int32.min) && number <= Int64(Int32.max) {
let n = Int32(truncatingIfNeeded: number)
if let e = E(rawValue: Int(n)) {
return e
} else {
throw TextFormatDecodingError.unrecognizedEnumValue
}
}
throw TextFormatDecodingError.malformedText
}
mutating func decodeSingularEnumField<E: Enum>(value: inout E?) throws where E.RawValue == Int {
try scanner.skipRequiredColon()
let e: E = try decodeEnum()
value = e
}
mutating func decodeSingularEnumField<E: Enum>(value: inout E) throws where E.RawValue == Int {
try scanner.skipRequiredColon()
let e: E = try decodeEnum()
value = e
}
mutating func decodeRepeatedEnumField<E: Enum>(value: inout [E]) throws where E.RawValue == Int {
try scanner.skipRequiredColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let e: E = try decodeEnum()
value.append(e)
}
} else {
let e: E = try decodeEnum()
value.append(e)
}
}
mutating func decodeSingularMessageField<M: Message>(value: inout M?) throws {
_ = scanner.skipOptionalColon()
if value == nil {
value = M()
}
let terminator = try scanner.skipObjectStart()
var subDecoder = try TextFormatDecoder(messageType: M.self, scanner: scanner, terminator: terminator)
if M.self == Google_Protobuf_Any.self {
var any = value as! Google_Protobuf_Any?
try any!.decodeTextFormat(decoder: &subDecoder)
value = any as! M?
} else {
try value!.decodeMessage(decoder: &subDecoder)
}
assert((scanner.recursionBudget + 1) == subDecoder.scanner.recursionBudget)
scanner = subDecoder.scanner
}
mutating func decodeRepeatedMessageField<M: Message>(value: inout [M]) throws {
_ = scanner.skipOptionalColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
let terminator = try scanner.skipObjectStart()
var subDecoder = try TextFormatDecoder(messageType: M.self, scanner: scanner, terminator: terminator)
if M.self == Google_Protobuf_Any.self {
var message = Google_Protobuf_Any()
try message.decodeTextFormat(decoder: &subDecoder)
value.append(message as! M)
} else {
var message = M()
try message.decodeMessage(decoder: &subDecoder)
value.append(message)
}
assert((scanner.recursionBudget + 1) == subDecoder.scanner.recursionBudget)
scanner = subDecoder.scanner
}
} else {
let terminator = try scanner.skipObjectStart()
var subDecoder = try TextFormatDecoder(messageType: M.self, scanner: scanner, terminator: terminator)
if M.self == Google_Protobuf_Any.self {
var message = Google_Protobuf_Any()
try message.decodeTextFormat(decoder: &subDecoder)
value.append(message as! M)
} else {
var message = M()
try message.decodeMessage(decoder: &subDecoder)
value.append(message)
}
assert((scanner.recursionBudget + 1) == subDecoder.scanner.recursionBudget)
scanner = subDecoder.scanner
}
}
mutating func decodeSingularGroupField<G: Message>(value: inout G?) throws {
try decodeSingularMessageField(value: &value)
}
mutating func decodeRepeatedGroupField<G: Message>(value: inout [G]) throws {
try decodeRepeatedMessageField(value: &value)
}
private mutating func decodeMapEntry<KeyType, ValueType: MapValueType>(mapType: _ProtobufMap<KeyType, ValueType>.Type, value: inout _ProtobufMap<KeyType, ValueType>.BaseType) throws {
var keyField: KeyType.BaseType?
var valueField: ValueType.BaseType?
let terminator = try scanner.skipObjectStart()
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
value[keyField] = valueField
return
} else {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try ValueType.decodeSingular(value: &valueField, from: &self)
default:
throw TextFormatDecodingError.unknownField
}
scanner.skipOptionalSeparator()
} else {
throw TextFormatDecodingError.malformedText
}
}
}
mutating func decodeMapField<KeyType, ValueType: MapValueType>(fieldType: _ProtobufMap<KeyType, ValueType>.Type, value: inout _ProtobufMap<KeyType, ValueType>.BaseType) throws {
_ = scanner.skipOptionalColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
try decodeMapEntry(mapType: fieldType, value: &value)
}
} else {
try decodeMapEntry(mapType: fieldType, value: &value)
}
}
private mutating func decodeMapEntry<KeyType, ValueType>(mapType: _ProtobufEnumMap<KeyType, ValueType>.Type, value: inout _ProtobufEnumMap<KeyType, ValueType>.BaseType) throws where ValueType.RawValue == Int {
var keyField: KeyType.BaseType?
var valueField: ValueType?
let terminator = try scanner.skipObjectStart()
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
value[keyField] = valueField
return
} else {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try decodeSingularEnumField(value: &valueField)
default:
throw TextFormatDecodingError.unknownField
}
scanner.skipOptionalSeparator()
} else {
throw TextFormatDecodingError.malformedText
}
}
}
mutating func decodeMapField<KeyType, ValueType>(fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type, value: inout _ProtobufEnumMap<KeyType, ValueType>.BaseType) throws where ValueType.RawValue == Int {
_ = scanner.skipOptionalColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
try decodeMapEntry(mapType: fieldType, value: &value)
}
} else {
try decodeMapEntry(mapType: fieldType, value: &value)
}
}
private mutating func decodeMapEntry<KeyType, ValueType>(mapType: _ProtobufMessageMap<KeyType, ValueType>.Type, value: inout _ProtobufMessageMap<KeyType, ValueType>.BaseType) throws {
var keyField: KeyType.BaseType?
var valueField: ValueType?
let terminator = try scanner.skipObjectStart()
while true {
if scanner.skipOptionalObjectEnd(terminator) {
if let keyField = keyField, let valueField = valueField {
value[keyField] = valueField
return
} else {
throw TextFormatDecodingError.malformedText
}
}
if let key = try scanner.nextKey() {
switch key {
case "key", "1":
try KeyType.decodeSingular(value: &keyField, from: &self)
case "value", "2":
try decodeSingularMessageField(value: &valueField)
default:
throw TextFormatDecodingError.unknownField
}
scanner.skipOptionalSeparator()
} else {
throw TextFormatDecodingError.malformedText
}
}
}
mutating func decodeMapField<KeyType, ValueType>(fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type, value: inout _ProtobufMessageMap<KeyType, ValueType>.BaseType) throws {
_ = scanner.skipOptionalColon()
if scanner.skipOptionalBeginArray() {
var firstItem = true
while true {
if scanner.skipOptionalEndArray() {
return
}
if firstItem {
firstItem = false
} else {
try scanner.skipRequiredComma()
}
try decodeMapEntry(mapType: fieldType, value: &value)
}
} else {
try decodeMapEntry(mapType: fieldType, value: &value)
}
}
mutating func decodeExtensionField(values: inout ExtensionFieldValueSet, messageType: Message.Type, fieldNumber: Int) throws {
if let ext = scanner.extensions?[messageType, fieldNumber] {
try values.modify(index: fieldNumber) { fieldValue in
if fieldValue != nil {
try fieldValue!.decodeExtensionField(decoder: &self)
} else {
fieldValue = try ext._protobuf_newField(decoder: &self)
}
if fieldValue == nil {
// Really things should never get here, for TextFormat, decoding
// the value should always work or throw an error. This specific
// error result is to allow this to be more detectable.
throw TextFormatDecodingError.internalExtensionError
}
}
}
}
}

View File

@ -0,0 +1,42 @@
// Sources/SwiftProtobuf/TextFormatDecodingError.swift - Protobuf text format 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 text format decoding errors
///
// -----------------------------------------------------------------------------
public enum TextFormatDecodingError: Error {
/// Text data could not be parsed
case malformedText
/// A number could not be parsed
case malformedNumber
/// Extraneous data remained after decoding should have been complete
case trailingGarbage
/// The data stopped before we expected
case truncated
/// A string was not valid UTF8
case invalidUTF8
/// The data being parsed does not match the type specified in the proto file
case schemaMismatch
/// Field names were not compiled into the binary
case missingFieldNames
/// A field identifier (name or number) was not found on the message
case unknownField
/// The enum value was not recognized
case unrecognizedEnumValue
/// Text format rejects conflicting values for the same oneof field
case conflictingOneOf
/// 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

@ -0,0 +1,25 @@
// Sources/SwiftProtobuf/TextFormatDecodingOptions.swift - Text format decoding options
//
// Copyright (c) 2014 - 2021 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
//
// -----------------------------------------------------------------------------
///
/// Text format decoding options
///
// -----------------------------------------------------------------------------
/// Options for TextFormatDecoding.
public struct TextFormatDecodingOptions {
/// 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
public init() {}
}

View File

@ -0,0 +1,296 @@
// Sources/SwiftProtobuf/TextFormatEncoder.swift - Text format encoding support
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Text format serialization engine.
///
// -----------------------------------------------------------------------------
import Foundation
private let asciiSpace = UInt8(ascii: " ")
private let asciiColon = UInt8(ascii: ":")
private let asciiComma = UInt8(ascii: ",")
private let asciiMinus = UInt8(ascii: "-")
private let asciiBackslash = UInt8(ascii: "\\")
private let asciiDoubleQuote = UInt8(ascii: "\"")
private let asciiZero = UInt8(ascii: "0")
private let asciiOpenCurlyBracket = UInt8(ascii: "{")
private let asciiCloseCurlyBracket = UInt8(ascii: "}")
private let asciiOpenSquareBracket = UInt8(ascii: "[")
private let asciiCloseSquareBracket = UInt8(ascii: "]")
private let asciiNewline = UInt8(ascii: "\n")
private let asciiUpperA = UInt8(ascii: "A")
private let tabSize = 2
private let tab = [UInt8](repeating: asciiSpace, count: tabSize)
/// TextFormatEncoder has no public members.
internal struct TextFormatEncoder {
private var data = [UInt8]()
private var indentString: [UInt8] = []
var stringResult: String {
get {
return String(bytes: data, encoding: String.Encoding.utf8)!
}
}
internal mutating func append(staticText: StaticString) {
let buff = UnsafeBufferPointer(start: staticText.utf8Start, count: staticText.utf8CodeUnitCount)
data.append(contentsOf: buff)
}
internal mutating func append(name: _NameMap.Name) {
data.append(contentsOf: name.utf8Buffer)
}
internal mutating func append(bytes: [UInt8]) {
data.append(contentsOf: bytes)
}
private mutating func append(text: String) {
data.append(contentsOf: text.utf8)
}
init() {}
internal mutating func indent() {
data.append(contentsOf: indentString)
}
mutating func emitFieldName(name: UnsafeRawBufferPointer) {
indent()
data.append(contentsOf: name)
}
mutating func emitFieldName(name: StaticString) {
let buff = UnsafeRawBufferPointer(start: name.utf8Start, count: name.utf8CodeUnitCount)
emitFieldName(name: buff)
}
mutating func emitFieldName(name: [UInt8]) {
indent()
data.append(contentsOf: name)
}
mutating func emitExtensionFieldName(name: String) {
indent()
data.append(asciiOpenSquareBracket)
append(text: name)
data.append(asciiCloseSquareBracket)
}
mutating func emitFieldNumber(number: Int) {
indent()
appendUInt(value: UInt64(number))
}
mutating func startRegularField() {
append(staticText: ": ")
}
mutating func endRegularField() {
data.append(asciiNewline)
}
// In Text format, a message-valued field writes the name
// without a trailing colon:
// name_of_field {key: value key2: value2}
mutating func startMessageField() {
append(staticText: " {\n")
indentString.append(contentsOf: tab)
}
mutating func endMessageField() {
indentString.removeLast(tabSize)
indent()
append(staticText: "}\n")
}
mutating func startArray() {
data.append(asciiOpenSquareBracket)
}
mutating func arraySeparator() {
append(staticText: ", ")
}
mutating func endArray() {
data.append(asciiCloseSquareBracket)
}
mutating func putEnumValue<E: Enum>(value: E) {
if let name = value.name {
data.append(contentsOf: name.utf8Buffer)
} else {
appendInt(value: Int64(value.rawValue))
}
}
mutating func putFloatValue(value: Float) {
if value.isNaN {
append(staticText: "nan")
} else if !value.isFinite {
if value < 0 {
append(staticText: "-inf")
} else {
append(staticText: "inf")
}
} else {
data.append(contentsOf: value.debugDescription.utf8)
}
}
mutating func putDoubleValue(value: Double) {
if value.isNaN {
append(staticText: "nan")
} else if !value.isFinite {
if value < 0 {
append(staticText: "-inf")
} else {
append(staticText: "inf")
}
} else {
data.append(contentsOf: value.debugDescription.utf8)
}
}
private mutating func appendUInt(value: UInt64) {
if value >= 1000 {
appendUInt(value: value / 1000)
}
if value >= 100 {
data.append(asciiZero + UInt8((value / 100) % 10))
}
if value >= 10 {
data.append(asciiZero + UInt8((value / 10) % 10))
}
data.append(asciiZero + UInt8(value % 10))
}
private mutating func appendInt(value: Int64) {
if value < 0 {
data.append(asciiMinus)
// This is the twos-complement negation of value,
// computed in a way that won't overflow a 64-bit
// signed integer.
appendUInt(value: 1 + ~UInt64(bitPattern: value))
} else {
appendUInt(value: UInt64(bitPattern: value))
}
}
mutating func putInt64(value: Int64) {
appendInt(value: value)
}
mutating func putUInt64(value: UInt64) {
appendUInt(value: value)
}
mutating func appendUIntHex(value: UInt64, digits: Int) {
if digits == 0 {
append(staticText: "0x")
} else {
appendUIntHex(value: value >> 4, digits: digits - 1)
let d = UInt8(truncatingIfNeeded: value % 16)
data.append(d < 10 ? asciiZero + d : asciiUpperA + d - 10)
}
}
mutating func putUInt64Hex(value: UInt64, digits: Int) {
appendUIntHex(value: value, digits: digits)
}
mutating func putBoolValue(value: Bool) {
append(staticText: value ? "true" : "false")
}
mutating func putStringValue(value: String) {
data.append(asciiDoubleQuote)
for c in value.unicodeScalars {
switch c.value {
// Special two-byte escapes
case 8:
append(staticText: "\\b")
case 9:
append(staticText: "\\t")
case 10:
append(staticText: "\\n")
case 11:
append(staticText: "\\v")
case 12:
append(staticText: "\\f")
case 13:
append(staticText: "\\r")
case 34:
append(staticText: "\\\"")
case 92:
append(staticText: "\\\\")
case 0...31, 127: // Octal form for C0 control chars
data.append(asciiBackslash)
data.append(asciiZero + UInt8(c.value / 64))
data.append(asciiZero + UInt8(c.value / 8 % 8))
data.append(asciiZero + UInt8(c.value % 8))
case 0...127: // ASCII
data.append(UInt8(truncatingIfNeeded: c.value))
case 0x80...0x7ff:
data.append(0xc0 + UInt8(c.value / 64))
data.append(0x80 + UInt8(c.value % 64))
case 0x800...0xffff:
data.append(0xe0 + UInt8(truncatingIfNeeded: c.value >> 12))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
default:
data.append(0xf0 + UInt8(truncatingIfNeeded: c.value >> 18))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 12) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f))
data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f))
}
}
data.append(asciiDoubleQuote)
}
mutating func putBytesValue(value: Data) {
data.append(asciiDoubleQuote)
value.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
if let p = body.baseAddress, body.count > 0 {
for i in 0..<body.count {
let c = p[i]
switch c {
// Special two-byte escapes
case 8:
append(staticText: "\\b")
case 9:
append(staticText: "\\t")
case 10:
append(staticText: "\\n")
case 11:
append(staticText: "\\v")
case 12:
append(staticText: "\\f")
case 13:
append(staticText: "\\r")
case 34:
append(staticText: "\\\"")
case 92:
append(staticText: "\\\\")
case 32...126: // printable ASCII
data.append(c)
default: // Octal form for non-printable chars
data.append(asciiBackslash)
data.append(asciiZero + UInt8(c / 64))
data.append(asciiZero + UInt8(c / 8 % 8))
data.append(asciiZero + UInt8(c % 8))
}
}
}
}
data.append(asciiDoubleQuote)
}
}

View File

@ -0,0 +1,22 @@
// Sources/SwiftProtobuf/TextFormatEncodingOptions.swift - Text format encoding options
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Text format encoding options
///
// -----------------------------------------------------------------------------
/// Options for TextFormatEncoding.
public struct TextFormatEncodingOptions {
/// Default: Do print unknown fields using numeric notation
public var printUnknownFields: Bool = true
public init() {}
}

View File

@ -0,0 +1,662 @@
// Sources/SwiftProtobuf/TextFormatEncodingVisitor.swift - Text format 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
//
// -----------------------------------------------------------------------------
///
/// Text format serialization engine.
///
// -----------------------------------------------------------------------------
import Foundation
private let mapNameResolver: [Int:StaticString] = [1: "key", 2: "value"]
/// Visitor that serializes a message into protobuf text format.
internal struct TextFormatEncodingVisitor: Visitor {
private var encoder: TextFormatEncoder
private var nameMap: _NameMap?
private var nameResolver: [Int:StaticString]
private var extensions: ExtensionFieldValueSet?
private let options: TextFormatEncodingOptions
/// The protobuf text produced by the visitor.
var result: String {
return encoder.stringResult
}
/// Creates a new visitor that serializes the given message to protobuf text
/// format.
init(message: Message, options: TextFormatEncodingOptions) {
self.init(message: message, encoder: TextFormatEncoder(), options: options)
}
/// Creates a new visitor that serializes the given message to protobuf text
/// format, using an existing encoder.
private init(message: Message, encoder: TextFormatEncoder, options: TextFormatEncodingOptions) {
let nameMap: _NameMap?
if let nameProviding = message as? _ProtoNameProviding {
nameMap = type(of: nameProviding)._protobuf_nameMap
} else {
nameMap = nil
}
let extensions = (message as? ExtensibleMessage)?._protobuf_extensionFieldValues
self.init(nameMap: nameMap, nameResolver: [:], extensions: extensions, encoder: encoder, options: options)
}
private init(
nameMap: _NameMap?,
nameResolver: [Int:StaticString],
extensions: ExtensionFieldValueSet?,
encoder: TextFormatEncoder,
options: TextFormatEncodingOptions
) {
self.nameMap = nameMap
self.nameResolver = nameResolver
self.extensions = extensions
self.encoder = encoder
self.options = options
}
// TODO: This largely duplicates emitFieldName() below.
// But, it's slower so we don't want to just have emitFieldName() use
// formatFieldName(). Also, we need to measure whether the optimization
// this provides to repeated fields is worth the effort; consider just
// removing this and having repeated fields just re-run emitFieldName()
// for each item.
private func formatFieldName(lookingUp fieldNumber: Int) -> [UInt8] {
var bytes = [UInt8]()
if let protoName = nameMap?.names(for: fieldNumber)?.proto {
bytes.append(contentsOf: protoName.utf8Buffer)
} else if let protoName = nameResolver[fieldNumber] {
let buff = UnsafeBufferPointer(start: protoName.utf8Start, count: protoName.utf8CodeUnitCount)
bytes.append(contentsOf: buff)
} else if let extensionName = extensions?[fieldNumber]?.protobufExtension.fieldName {
bytes.append(UInt8(ascii: "["))
bytes.append(contentsOf: extensionName.utf8)
bytes.append(UInt8(ascii: "]"))
} else {
bytes.append(contentsOf: fieldNumber.description.utf8)
}
return bytes
}
private mutating func emitFieldName(lookingUp fieldNumber: Int) {
if let protoName = nameMap?.names(for: fieldNumber)?.proto {
encoder.emitFieldName(name: protoName.utf8Buffer)
} else if let protoName = nameResolver[fieldNumber] {
encoder.emitFieldName(name: protoName)
} else if let extensionName = extensions?[fieldNumber]?.protobufExtension.fieldName {
encoder.emitExtensionFieldName(name: extensionName)
} else {
encoder.emitFieldNumber(number: fieldNumber)
}
}
mutating func visitUnknown(bytes: Data) throws {
if options.printUnknownFields {
try bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) -> () in
if let baseAddress = body.baseAddress, body.count > 0 {
// All fields will be directly handled, so there is no need for
// the unknown field buffering/collection (when scannings to see
// if something is a message, this would be extremely wasteful).
var binaryOptions = BinaryDecodingOptions()
binaryOptions.discardUnknownFields = true
var decoder = BinaryDecoder(forReadingFrom: baseAddress,
count: body.count,
options: binaryOptions)
try visitUnknown(decoder: &decoder)
}
}
}
}
/// Helper for printing out unknowns.
///
/// The implementation tries to be "helpful" and if a length delimited field
/// appears to be a submessage, it prints it as such. However, that opens the
/// door to someone sending a message with an unknown field that is a stack
/// bomb, i.e. - it causes this code to recurse, exhausing the stack and
/// thus opening up an attack vector. To keep this "help", but avoid the
/// attack, a limit is placed on how many times it will recurse before just
/// treating the length delimted fields as bytes and not trying to decode
/// them.
private mutating func visitUnknown(
decoder: inout BinaryDecoder,
recursionBudget: Int = 10
) throws {
// This stack serves to avoid recursion for groups within groups within
// groups..., this avoid the stack attack that the message detection
// hits. No limit is placed on this because there is no stack risk with
// recursion, and because if a limit was hit, there is no other way to
// encode the group (the message field can just print as length
// delimited, groups don't have an option like that).
var groupFieldNumberStack: [Int] = []
while let tag = try decoder.getTag() {
switch tag.wireFormat {
case .varint:
encoder.emitFieldNumber(number: tag.fieldNumber)
var value: UInt64 = 0
encoder.startRegularField()
try decoder.decodeSingularUInt64Field(value: &value)
encoder.putUInt64(value: value)
encoder.endRegularField()
case .fixed64:
encoder.emitFieldNumber(number: tag.fieldNumber)
var value: UInt64 = 0
encoder.startRegularField()
try decoder.decodeSingularFixed64Field(value: &value)
encoder.putUInt64Hex(value: value, digits: 16)
encoder.endRegularField()
case .lengthDelimited:
encoder.emitFieldNumber(number: tag.fieldNumber)
var bytes = Data()
try decoder.decodeSingularBytesField(value: &bytes)
bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) -> () in
if let baseAddress = body.baseAddress, body.count > 0 {
var encodeAsBytes: Bool
if (recursionBudget > 0) {
do {
// Walk all the fields to test if it looks like a message
var testDecoder = BinaryDecoder(forReadingFrom: baseAddress,
count: body.count,
parent: decoder)
while let _ = try testDecoder.nextFieldNumber() {
}
// No error? Output the message body.
encodeAsBytes = false
var subDecoder = BinaryDecoder(forReadingFrom: baseAddress,
count: bytes.count,
parent: decoder)
encoder.startMessageField()
try visitUnknown(decoder: &subDecoder,
recursionBudget: recursionBudget - 1)
encoder.endMessageField()
} catch {
encodeAsBytes = true
}
} else {
encodeAsBytes = true
}
if (encodeAsBytes) {
encoder.startRegularField()
encoder.putBytesValue(value: bytes)
encoder.endRegularField()
}
}
}
case .startGroup:
encoder.emitFieldNumber(number: tag.fieldNumber)
encoder.startMessageField()
groupFieldNumberStack.append(tag.fieldNumber)
case .endGroup:
let groupFieldNumber = groupFieldNumberStack.popLast()
// Unknown data is scanned and verified by the
// binary parser, so this can never fail.
assert(tag.fieldNumber == groupFieldNumber)
encoder.endMessageField()
case .fixed32:
encoder.emitFieldNumber(number: tag.fieldNumber)
var value: UInt32 = 0
encoder.startRegularField()
try decoder.decodeSingularFixed32Field(value: &value)
encoder.putUInt64Hex(value: UInt64(value), digits: 8)
encoder.endRegularField()
}
}
// Unknown data is scanned and verified by the binary parser, so this can
// never fail.
assert(groupFieldNumberStack.isEmpty)
}
// Visitor.swift defines default versions for other singular field types
// that simply widen and dispatch to one of the following. Since Text format
// does not distinguish e.g., Fixed64 vs. UInt64, this is sufficient.
mutating func visitSingularFloatField(value: Float, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putFloatValue(value: value)
encoder.endRegularField()
}
mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putDoubleValue(value: value)
encoder.endRegularField()
}
mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putInt64(value: value)
encoder.endRegularField()
}
mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putUInt64(value: value)
encoder.endRegularField()
}
mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putBoolValue(value: value)
encoder.endRegularField()
}
mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putStringValue(value: value)
encoder.endRegularField()
}
mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putBytesValue(value: value)
encoder.endRegularField()
}
mutating func visitSingularEnumField<E: Enum>(value: E, fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
encoder.putEnumValue(value: value)
encoder.endRegularField()
}
mutating func visitSingularMessageField<M: Message>(value: M,
fieldNumber: Int) throws {
emitFieldName(lookingUp: fieldNumber)
// Cache old encoder state
let oldNameMap = self.nameMap
let oldNameResolver = self.nameResolver
let oldExtensions = self.extensions
// Update encoding state for new message
self.nameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap
self.nameResolver = [:]
self.extensions = (value as? ExtensibleMessage)?._protobuf_extensionFieldValues
// Restore state before returning
defer {
self.extensions = oldExtensions
self.nameResolver = oldNameResolver
self.nameMap = oldNameMap
}
// Encode submessage
encoder.startMessageField()
if let any = value as? Google_Protobuf_Any {
any.textTraverse(visitor: &self)
} else {
try! value.traverse(visitor: &self)
}
encoder.endMessageField()
}
// Emit the full "verbose" form of an Any. This writes the typeURL
// as a field name in `[...]` followed by the fields of the
// contained message.
internal mutating func visitAnyVerbose(value: Message, typeURL: String) {
encoder.emitExtensionFieldName(name: typeURL)
encoder.startMessageField()
var visitor = TextFormatEncodingVisitor(message: value, encoder: encoder, options: options)
if let any = value as? Google_Protobuf_Any {
any.textTraverse(visitor: &visitor)
} else {
try! value.traverse(visitor: &visitor)
}
encoder = visitor.encoder
encoder.endMessageField()
}
// Write a single special field called "#json". This
// is used for Any objects with undecoded JSON contents.
internal mutating func visitAnyJSONDataField(value: Data) {
encoder.indent()
encoder.append(staticText: "#json: ")
encoder.putBytesValue(value: value)
encoder.append(staticText: "\n")
}
// The default implementations in Visitor.swift provide the correct
// results, but we get significantly better performance by only doing
// the name lookup once for the array, rather than once for each element:
mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putFloatValue(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putDoubleValue(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putInt64(value: Int64(v))
encoder.endRegularField()
}
}
mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putInt64(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putUInt64(value: UInt64(v))
encoder.endRegularField()
}
}
mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putUInt64(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws {
try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws {
try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
try visitRepeatedUInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
try visitRepeatedUInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putBoolValue(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putStringValue(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putBytesValue(value: v)
encoder.endRegularField()
}
}
mutating func visitRepeatedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
assert(!value.isEmpty)
let fieldName = formatFieldName(lookingUp: fieldNumber)
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startRegularField()
encoder.putEnumValue(value: v)
encoder.endRegularField()
}
}
// Messages and groups
mutating func visitRepeatedMessageField<M: Message>(value: [M],
fieldNumber: Int) throws {
assert(!value.isEmpty)
// Look up field name against outer message encoding state
let fieldName = formatFieldName(lookingUp: fieldNumber)
// Cache old encoder state
let oldNameMap = self.nameMap
let oldNameResolver = self.nameResolver
let oldExtensions = self.extensions
// Update encoding state for new message type
self.nameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap
self.nameResolver = [:]
self.extensions = (value as? ExtensibleMessage)?._protobuf_extensionFieldValues
// Iterate and encode each message
for v in value {
encoder.emitFieldName(name: fieldName)
encoder.startMessageField()
if let any = v as? Google_Protobuf_Any {
any.textTraverse(visitor: &self)
} else {
try! v.traverse(visitor: &self)
}
encoder.endMessageField()
}
// Restore state
self.extensions = oldExtensions
self.nameResolver = oldNameResolver
self.nameMap = oldNameMap
}
// Google's C++ implementation of Text format supports two formats
// for repeated numeric fields: "short" format writes the list as a
// single field with values enclosed in `[...]`, "long" format
// writes a separate field name/value for each item. They provide
// an option for callers to select which output version they prefer.
// Since this distinction mirrors the difference in Protobuf Binary
// between "packed" and "non-packed", I've chosen to use the short
// format for packed fields and the long version for repeated
// fields. This provides a clear visual distinction between these
// fields (including proto3's default use of packed) without
// introducing the baggage of a separate option.
private mutating func _visitPacked<T>(
value: [T], fieldNumber: Int,
encode: (T, inout TextFormatEncoder) -> ()
) throws {
assert(!value.isEmpty)
emitFieldName(lookingUp: fieldNumber)
encoder.startRegularField()
var firstItem = true
encoder.startArray()
for v in value {
if !firstItem {
encoder.arraySeparator()
}
encode(v, &encoder)
firstItem = false
}
encoder.endArray()
encoder.endRegularField()
}
mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: Float, encoder: inout TextFormatEncoder) in
encoder.putFloatValue(value: v)
}
}
mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: Double, encoder: inout TextFormatEncoder) in
encoder.putDoubleValue(value: v)
}
}
mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: Int32, encoder: inout TextFormatEncoder) in
encoder.putInt64(value: Int64(v))
}
}
mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: Int64, encoder: inout TextFormatEncoder) in
encoder.putInt64(value: v)
}
}
mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: UInt32, encoder: inout TextFormatEncoder) in
encoder.putUInt64(value: UInt64(v))
}
}
mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: UInt64, encoder: inout TextFormatEncoder) in
encoder.putUInt64(value: v)
}
}
mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws {
try visitPackedInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws {
try visitPackedInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
try visitPackedUInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
try visitPackedUInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
try visitPackedInt32Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
try visitPackedInt64Field(value: value, fieldNumber: fieldNumber)
}
mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: Bool, encoder: inout TextFormatEncoder) in
encoder.putBoolValue(value: v)
}
}
mutating func visitPackedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
try _visitPacked(value: value, fieldNumber: fieldNumber) {
(v: E, encoder: inout TextFormatEncoder) in
encoder.putEnumValue(value: v)
}
}
/// Helper to encapsulate the common structure of iterating over a map
/// and encoding the keys and values.
private mutating func _visitMap<K, V>(
map: Dictionary<K, V>,
fieldNumber: Int,
isOrderedBefore: (K, K) -> Bool,
coder: (inout TextFormatEncodingVisitor, K, V) throws -> ()
) throws {
for (k,v) in map.sorted(by: { isOrderedBefore( $0.0, $1.0) }) {
emitFieldName(lookingUp: fieldNumber)
encoder.startMessageField()
var visitor = TextFormatEncodingVisitor(nameMap: nil, nameResolver: mapNameResolver, extensions: nil, encoder: encoder, options: options)
try coder(&visitor, k, v)
encoder = visitor.encoder
encoder.endMessageField()
}
}
mutating func visitMapField<KeyType, ValueType: MapValueType>(
fieldType: _ProtobufMap<KeyType, ValueType>.Type,
value: _ProtobufMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws {
try _visitMap(map: value, fieldNumber: fieldNumber, isOrderedBefore: KeyType._lessThan) {
(visitor: inout TextFormatEncodingVisitor, key, value) throws -> () in
try KeyType.visitSingular(value: key, fieldNumber: 1, with: &visitor)
try ValueType.visitSingular(value: value, fieldNumber: 2, with: &visitor)
}
}
mutating func visitMapField<KeyType, ValueType>(
fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type,
value: _ProtobufEnumMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws where ValueType.RawValue == Int {
try _visitMap(map: value, fieldNumber: fieldNumber, isOrderedBefore: KeyType._lessThan) {
(visitor: inout TextFormatEncodingVisitor, key, value) throws -> () in
try KeyType.visitSingular(value: key, fieldNumber: 1, with: &visitor)
try visitor.visitSingularEnumField(value: value, fieldNumber: 2)
}
}
mutating func visitMapField<KeyType, ValueType>(
fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type,
value: _ProtobufMessageMap<KeyType, ValueType>.BaseType,
fieldNumber: Int
) throws {
try _visitMap(map: value, fieldNumber: fieldNumber, isOrderedBefore: KeyType._lessThan) {
(visitor: inout TextFormatEncodingVisitor, key, value) throws -> () in
try KeyType.visitSingular(value: key, fieldNumber: 1, with: &visitor)
try visitor.visitSingularMessageField(value: value, fieldNumber: 2)
}
}
}

View File

@ -0,0 +1,65 @@
// Sources/SwiftProtobuf/TimeUtils.swift - Generally useful time/calendar functions
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Generally useful time/calendar functions and constants
///
// -----------------------------------------------------------------------------
let minutesPerDay: Int32 = 1440
let minutesPerHour: Int32 = 60
let secondsPerDay: Int32 = 86400
let secondsPerHour: Int32 = 3600
let secondsPerMinute: Int32 = 60
let nanosPerSecond: Int32 = 1000000000
internal func timeOfDayFromSecondsSince1970(seconds: Int64) -> (hh: Int32, mm: Int32, ss: Int32) {
let secondsSinceMidnight = Int32(mod(seconds, Int64(secondsPerDay)))
let ss = mod(secondsSinceMidnight, secondsPerMinute)
let mm = mod(div(secondsSinceMidnight, secondsPerMinute), minutesPerHour)
let hh = Int32(div(secondsSinceMidnight, secondsPerHour))
return (hh: hh, mm: mm, ss: ss)
}
internal func julianDayNumberFromSecondsSince1970(seconds: Int64) -> Int64 {
// January 1, 1970 is Julian Day Number 2440588.
// See http://aa.usno.navy.mil/faq/docs/JD_Formula.php
return div(seconds + 2440588 * Int64(secondsPerDay), Int64(secondsPerDay))
}
internal func gregorianDateFromSecondsSince1970(seconds: Int64) -> (YY: Int32, MM: Int32, DD: Int32) {
// The following implements Richards' algorithm (see the Wikipedia article
// for "Julian day").
// If you touch this code, please test it exhaustively by playing with
// Test_Timestamp.testJSON_range.
let JJ = julianDayNumberFromSecondsSince1970(seconds: seconds)
let f = JJ + 1401 + div(div(4 * JJ + 274277, 146097) * 3, 4) - 38
let e = 4 * f + 3
let g = Int64(div(mod(e, 1461), 4))
let h = 5 * g + 2
let DD = div(mod(h, 153), 5) + 1
let MM = mod(div(h, 153) + 2, 12) + 1
let YY = div(e, 1461) - 4716 + div(12 + 2 - MM, 12)
return (YY: Int32(YY), MM: Int32(MM), DD: Int32(DD))
}
internal func nanosToString(nanos: Int32) -> String {
if nanos == 0 {
return ""
} else if nanos % 1000000 == 0 {
return ".\(threeDigit(abs(nanos) / 1000000))"
} else if nanos % 1000 == 0 {
return ".\(sixDigit(abs(nanos) / 1000))"
} else {
return ".\(nineDigit(abs(nanos)))"
}
}

View File

@ -0,0 +1,46 @@
// Sources/SwiftProtobuf/UnknownStorage.swift - Handling unknown fields
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Proto2 binary coding requires storing and recoding of unknown fields.
/// This simple support class handles that requirement. A property of this type
/// is compiled into every proto2 message.
///
// -----------------------------------------------------------------------------
import Foundation
/// Contains any unknown fields in a decoded message; that is, fields that were
/// sent on the wire but were not recognized by the generated message
/// implementation or were valid field numbers but with mismatching wire
/// formats (for example, a field encoded as a varint when a fixed32 integer
/// was expected).
public struct UnknownStorage: Equatable {
/// The raw protocol buffer binary-encoded bytes that represent the unknown
/// fields of a decoded message.
public private(set) var data = Data()
#if !swift(>=4.1)
public static func ==(lhs: UnknownStorage, rhs: UnknownStorage) -> Bool {
return lhs.data == rhs.data
}
#endif
public init() {}
internal mutating func append(protobufData: Data) {
data.append(protobufData)
}
public func traverse<V: Visitor>(visitor: inout V) throws {
if !data.isEmpty {
try visitor.visitUnknown(bytes: data)
}
}
}

View File

@ -0,0 +1,37 @@
// Sources/SwiftProtobuf/UnsafeBufferPointer+Shims.swift - Shims for UnsafeBufferPointer
//
// Copyright (c) 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
//
// -----------------------------------------------------------------------------
///
/// Shims for UnsafeBufferPointer
///
// -----------------------------------------------------------------------------
extension UnsafeMutableBufferPointer {
#if !swift(>=4.2)
internal static func allocate(capacity: Int) -> UnsafeMutableBufferPointer<Element> {
let pointer = UnsafeMutablePointer<Element>.allocate(capacity: capacity)
return UnsafeMutableBufferPointer(start: pointer, count: capacity)
}
#endif
#if !swift(>=4.1)
internal func deallocate() {
self.baseAddress?.deallocate(capacity: self.count)
}
#endif
}
extension UnsafeMutableRawBufferPointer {
#if !swift(>=4.1)
internal func copyMemory<C: Collection>(from source: C) where C.Element == UInt8 {
self.copyBytes(from: source)
}
#endif
}

View File

@ -0,0 +1,45 @@
// Sources/SwiftProtobuf/UnsafeRawPointer+Shims.swift - Shims for UnsafeRawPointer and friends
//
// Copyright (c) 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
//
// -----------------------------------------------------------------------------
///
/// Shims for UnsafeRawPointer and friends.
///
// -----------------------------------------------------------------------------
extension UnsafeRawPointer {
/// A shim subscript for UnsafeRawPointer aiming to maintain code consistency.
///
/// We can remove this shim when we rewrite the code to use buffer pointers.
internal subscript(_ offset: Int) -> UInt8 {
get {
return self.load(fromByteOffset: offset, as: UInt8.self)
}
}
}
extension UnsafeMutableRawPointer {
/// A shim subscript for UnsafeMutableRawPointer aiming to maintain code consistency.
///
/// We can remove this shim when we rewrite the code to use buffer pointers.
internal subscript(_ offset: Int) -> UInt8 {
get {
return self.load(fromByteOffset: offset, as: UInt8.self)
}
set {
self.storeBytes(of: newValue, toByteOffset: offset, as: UInt8.self)
}
}
#if !swift(>=4.1)
internal mutating func copyMemory(from source: UnsafeRawPointer, byteCount: Int) {
self.copyBytes(from: source, count: byteCount)
}
#endif
}

View File

@ -0,0 +1,108 @@
// Sources/SwiftProtobuf/Varint.swift - Varint encoding/decoding helpers
//
// 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
//
// -----------------------------------------------------------------------------
///
/// Helper functions to varint-encode and decode integers.
///
// -----------------------------------------------------------------------------
/// Contains helper methods to varint-encode and decode integers.
internal enum Varint {
/// Computes the number of bytes that would be needed to store a 32-bit varint.
///
/// - Parameter value: The number whose varint size should be calculated.
/// - Returns: The size, in bytes, of the 32-bit varint.
static func encodedSize(of value: UInt32) -> Int {
if (value & (~0 << 7)) == 0 {
return 1
}
if (value & (~0 << 14)) == 0 {
return 2
}
if (value & (~0 << 21)) == 0 {
return 3
}
if (value & (~0 << 28)) == 0 {
return 4
}
return 5
}
/// Computes the number of bytes that would be needed to store a signed 32-bit varint, if it were
/// treated as an unsigned integer with the same bit pattern.
///
/// - Parameter value: The number whose varint size should be calculated.
/// - Returns: The size, in bytes, of the 32-bit varint.
static func encodedSize(of value: Int32) -> Int {
if value >= 0 {
return encodedSize(of: UInt32(bitPattern: value))
} else {
// Must sign-extend.
return encodedSize(of: Int64(value))
}
}
/// Computes the number of bytes that would be needed to store a 64-bit varint.
///
/// - Parameter value: The number whose varint size should be calculated.
/// - Returns: The size, in bytes, of the 64-bit varint.
static func encodedSize(of value: Int64) -> Int {
// Handle two common special cases up front.
if (value & (~0 << 7)) == 0 {
return 1
}
if value < 0 {
return 10
}
// Divide and conquer the remaining eight cases.
var value = value
var n = 2
if (value & (~0 << 35)) != 0 {
n += 4
value >>= 28
}
if (value & (~0 << 21)) != 0 {
n += 2
value >>= 14
}
if (value & (~0 << 14)) != 0 {
n += 1
}
return n
}
/// Computes the number of bytes that would be needed to store an unsigned 64-bit varint, if it
/// were treated as a signed integer witht he same bit pattern.
///
/// - Parameter value: The number whose varint size should be calculated.
/// - Returns: The size, in bytes, of the 64-bit varint.
static func encodedSize(of value: UInt64) -> Int {
return encodedSize(of: Int64(bitPattern: value))
}
/// Counts the number of distinct varints in a packed byte buffer.
static func countVarintsInBuffer(start: UnsafeRawPointer, count: Int) -> Int {
// We don't need to decode all the varints to count how many there
// are. Just observe that every varint has exactly one byte with
// value < 128. So we just count those...
var n = 0
var ints = 0
while n < count {
if start.load(fromByteOffset: n, as: UInt8.self) < 128 {
ints += 1
}
n += 1
}
return ints
}
}

Some files were not shown because too many files have changed in this diff Show More