├── .github
└── workflows
│ └── swift.yml
├── .gitignore
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── WiredSwfitTests
│ ├── Info.plist
│ └── WiredSwfitTests.swift
├── WiredSwift
│ ├── Additions
│ │ ├── Data.swift
│ │ └── String.swift
│ ├── Core
│ │ ├── Const.swift
│ │ ├── Logger.swift
│ │ └── WiredError.swift
│ ├── Crypto
│ │ ├── P7Cipher.swift
│ │ ├── RSA.swift
│ │ └── SwCrypt.swift
│ ├── Data
│ │ ├── ServerInfo.swift
│ │ └── UserInfo.swift
│ ├── Info.plist
│ ├── Network
│ │ ├── BlockConnection.swift
│ │ ├── Connection.swift
│ │ ├── P7Socket.swift
│ │ └── Url.swift
│ ├── P7
│ │ ├── P7Message.swift
│ │ ├── P7Spec.swift
│ │ ├── P7SpecError.swift
│ │ ├── P7SpecField.swift
│ │ ├── P7SpecItem.swift
│ │ ├── P7SpecMessage.swift
│ │ └── P7SpecType.swift
│ ├── WiredSwift.h
│ ├── de.lproj
│ │ └── InfoPlist.strings
│ ├── fr.lproj
│ │ └── InfoPlist.strings
│ └── wired.xml
├── WiredSwift_iOS
│ ├── Info.plist
│ └── WiredSwift_iOS.h
└── WiredSwift_iOSTests
│ ├── Info.plist
│ └── WiredSwift_iOSTests.swift
├── Tests
├── LinuxMain.swift
└── WiredSwiftTests
│ ├── WiredSwiftTests.swift
│ └── XCTestManifests.swift
└── WiredSwift.xcodeproj
├── AEXML_Info.plist
├── CryptoSwift_Info.plist
├── CryptorRSA_Info.plist
├── Gzip_Info.plist
├── SocketSwift_Info.plist
├── WiredSwiftTests_Info.plist
├── WiredSwift_Info.plist
├── project.pbxproj
├── project.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── system_zlib_Info.plist
└── xcshareddata
└── xcschemes
└── WiredSwift-Package.xcscheme
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: macOS-latest
9 |
10 | steps:
11 | - uses: actions/checkout@master
12 | - name: Force Xcode 11
13 | run: sudo xcode-select -switch /Applications/Xcode_11.4.app
14 | - name: Build
15 | run: swift build -v
16 | - name: Run tests
17 | run: swift test -v
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Xcode
4 | #
5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
6 |
7 | ## User settings
8 | xcuserdata/
9 |
10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
11 | *.xcscmblueprint
12 | *.xccheckout
13 |
14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
15 | build/
16 | DerivedData/
17 | *.moved-aside
18 | *.pbxuser
19 | !default.pbxuser
20 | *.mode1v3
21 | !default.mode1v3
22 | *.mode2v3
23 | !default.mode2v3
24 | *.perspectivev3
25 | !default.perspectivev3
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 |
30 | ## App packaging
31 | *.ipa
32 | *.dSYM.zip
33 | *.dSYM
34 |
35 | ## Playgrounds
36 | timeline.xctimeline
37 | playground.xcworkspace
38 |
39 | # Swift Package Manager
40 | #
41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
42 | # Packages/
43 | # Package.pins
44 | # Package.resolved
45 | # *.xcodeproj
46 | #
47 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
48 | # hence it is not needed unless you have added a package configuration file to your project
49 | # .swiftpm
50 |
51 | .build/
52 |
53 | # CocoaPods
54 | #
55 | # We recommend against adding the Pods directory to your .gitignore. However
56 | # you should judge for yourself, the pros and cons are mentioned at:
57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
58 | #
59 | # Pods/
60 | #
61 | # Add this line if you want to avoid checking in source code from the Xcode workspace
62 | # *.xcworkspace
63 |
64 | # Carthage
65 | #
66 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
67 | # Carthage/Checkouts
68 |
69 | Carthage/Build/
70 |
71 | # Accio dependency management
72 | Dependencies/
73 | .accio/
74 |
75 | # fastlane
76 | #
77 | # It is recommended to not store the screenshots in the git repo.
78 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
79 | # For more information about the recommended setup visit:
80 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
81 |
82 | fastlane/report.xml
83 | fastlane/Preview.html
84 | fastlane/screenshots/**/*.png
85 | fastlane/test_output
86 |
87 | # Code Injection
88 | #
89 | # After new code Injection tools there's a generated folder /iOSInjectionProject
90 | # https://github.com/johnno1962/injectionforxcode
91 |
92 | iOSInjectionProject/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD License
2 |
3 | This code is distributed under BSD license, and it is free for personal or commercial use.
4 |
5 | - Copyright (c) 2003-2009 Axel Andersson, All rights reserved.
6 | - Copyright (c) 2011-2020 Rafaël Warnault, All rights reserved.
7 |
8 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9 |
10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "AEXML",
6 | "repositoryURL": "https://github.com/tadija/AEXML.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "8623e73b193386909566a9ca20203e33a09af142",
10 | "version": "4.5.0"
11 | }
12 | },
13 | {
14 | "package": "CryptorRSA",
15 | "repositoryURL": "https://github.com/IBM-Swift/BlueRSA",
16 | "state": {
17 | "branch": null,
18 | "revision": "8ea901f2582296837d88f882b0fa5a0601759598",
19 | "version": "1.0.35"
20 | }
21 | },
22 | {
23 | "package": "CryptoSwift",
24 | "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "39f08ac5269361a50c08ce1e2f41989bfc4b1ec8",
28 | "version": "1.3.1"
29 | }
30 | },
31 | {
32 | "package": "CZlib",
33 | "repositoryURL": "https://github.com/IBM-Swift/CZlib.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "a781e6ef055fb8f0d608fe30a28e6a0f62ae498b",
37 | "version": "0.1.2"
38 | }
39 | },
40 | {
41 | "package": "Gzip",
42 | "repositoryURL": "https://github.com/1024jp/GzipSwift",
43 | "state": {
44 | "branch": null,
45 | "revision": "ba0b6cb51cc6202f896e469b87d2889a46b10d1b",
46 | "version": "5.1.1"
47 | }
48 | },
49 | {
50 | "package": "SocketSwift",
51 | "repositoryURL": "https://github.com/BiAtoms/Socket.swift.git",
52 | "state": {
53 | "branch": null,
54 | "revision": "36911c13adfe12859ed43dcba878052b1897fcd3",
55 | "version": "2.4.0"
56 | }
57 | }
58 | ]
59 | },
60 | "version": 1
61 | }
62 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | var dependencies: [Package.Dependency] = []
7 | var targetDependencies: [Target.Dependency] = []
8 |
9 |
10 |
11 | #if os(Linux)
12 | dependencies.append(.package(url: "https://github.com/IBM-Swift/OpenSSL.git", from: "2.2.0"))
13 | targetDependencies.append(.byName(name: "OpenSSL"))
14 | #endif
15 |
16 |
17 |
18 | dependencies.append(.package(name: "AEXML", url: "https://github.com/tadija/AEXML.git", from: "4.5.0"))
19 | dependencies.append(.package(name: "CryptoSwift", url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.3.0"))
20 | dependencies.append(.package(name: "CryptorRSA", url: "https://github.com/IBM-Swift/BlueRSA", from: "1.0.35"))
21 | dependencies.append(.package(name: "CZlib", url: "https://github.com/IBM-Swift/CZlib.git", from: "0.1.2"))
22 | dependencies.append(.package(name: "Gzip", url: "https://github.com/1024jp/GzipSwift", from: "5.1.1"))
23 | dependencies.append(.package(name: "SocketSwift", url: "https://github.com/BiAtoms/Socket.swift.git", from: "2.4.0"))
24 |
25 |
26 |
27 | targetDependencies.append(.byName(name: "AEXML"))
28 | targetDependencies.append(.byName(name: "CryptoSwift"))
29 | targetDependencies.append(.byName(name: "CryptorRSA"))
30 | targetDependencies.append(.byName(name: "CZlib"))
31 | targetDependencies.append(.byName(name: "Gzip"))
32 | targetDependencies.append(.byName(name: "SocketSwift"))
33 |
34 |
35 |
36 |
37 | let package = Package(
38 | name: "WiredSwift",
39 | platforms: [.iOS(.v12), .macOS(.v10_13)],
40 | products: [
41 | .library(
42 | name: "WiredSwift",
43 | targets: ["WiredSwift"]),
44 | ],
45 | dependencies: dependencies,
46 | targets: [
47 | .target(
48 | name: "WiredSwift",
49 | dependencies: targetDependencies),
50 | .testTarget(
51 | name: "WiredSwiftTests",
52 | dependencies: ["WiredSwift"]),
53 | ]
54 | )
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WiredSwift
2 |
3 | WiredSwift is an implementation of the Wired 2.0 protocol written in Swift.
4 |
5 | [](https://github.com/nark/WiredSwift/actions)
6 |
7 |
8 | ## Requirements
9 |
10 | * macOS 10.14
11 | * iOS 12
12 | * Xcode
13 |
14 | ## Dependencies
15 |
16 | Dependencies are managed by Swift Package Manager, have a look to the [Package.swif](https://github.com/nark/WiredSwift/blob/master/Package.swift) file for details.
17 |
18 | ## Getting Started
19 |
20 | ### Adding to your project
21 |
22 | Latest release version:
23 |
24 | .package(name: "WiredSwift", url: "https://github.com/nark/WiredSwift", from: "1.0.6")
25 |
26 | Latest upstream version:
27 |
28 | .package(name: "WiredSwift", url: "https://github.com/nark/WiredSwift", .branch("master"))
29 |
30 | ### Connection
31 |
32 | Minimal connection to a Wired 2.0 server:
33 |
34 | let specUrl = URL(string: "https://wired.read-write.fr/spec.xml")!
35 | let spec = P7Spec(withUrl: specUrl)
36 |
37 | // the Wired URL to connect to
38 | let url = Url(withString: "wired://localhost:4871")
39 |
40 | // init connection
41 | let connection = Connection(withSpec: spec, delegate: self)
42 | connection.nick = "Me"
43 | connection.status = "Testing WiredSwift"
44 |
45 | // perform connect
46 | if connection.connect(withUrl: url) {
47 | // connected
48 | } else {
49 | // not connected
50 | print(connection.socket.errors)
51 | }
52 |
53 | ### Join to the public chat
54 |
55 | Once connected, in order to login into the public chat and list connected users, you have to explicitely trigger the `wired.chat.join_chat` transaction. The `Connection` class can help you by calling the following method:
56 |
57 | connection.joinChat(chatID: 1)
58 |
59 | Where `1` is always the ID of the public chat regarding the Wired 2.0 protocol.
60 |
61 | ### Receive messages
62 |
63 | While using interactive mode, you have to comply with the `ConnectionDelegate` protocol to receive messages (`P7Message`) or any other events from the initiated connection. For example, the `connectionDidSendMessage(connection: Connection, message: P7Message)` method will distribute received messages which you can handle as the following:
64 |
65 | extension Controller: ConnectionDelegate {
66 | func connectionDidReceiveMessage(connection: Connection, message: P7Message) {
67 | if message.name == "wired.chat.user_list" {
68 | print(message.xml())
69 | }
70 | }
71 |
72 | func connectionDidReceiveError(connection: Connection, message: P7Message) {
73 | if let specError = spec.error(forMessage: message), let errorMessage = specError.name {
74 | print(errorMessage)
75 | }
76 | }
77 | }
78 |
79 | ### Send messages
80 |
81 | The following example illustrate how to send a message:
82 |
83 | let message = P7Message(withName: "wired.chat.say", spec: spec)
84 | message!.addParameter(field: "wired.chat.id", value: 1) // public chat ID
85 | message!.addParameter(field: "wired.chat.say", value: "Hello, world!")
86 |
87 | self.connection.send(message: message!)
88 |
89 | To learn more about the Wired 2.0 specification you can visit this documentation: [http://wired.read-write.fr/spec.html](http://wired.read-write.fr/spec.html) and read the orginal code of the `libwired` C library: [https://github.com/nark/libwired](https://github.com/nark/libwired)
90 |
91 | ### Interactive socket
92 |
93 | The `Connection` class provides two ways of handling messages:
94 |
95 | * connection instance is set as `interactive` so it will automatically manage a listening loop in a separated thread and dispatch receive message through `ConnectionDelegate` protocol to registered delegates in the main thread. This is the default behavior.
96 | * connection instance is NOT `interactive`, and in that case you have to handle every message read/write transactions by yourself using `Connection.readMessage()` and `Connection.sendMessage()` methods. This for example is used for transfers separated connections that have different behaviors.
97 |
98 | Set the `interactive` attribute to `false` before calling `Connection.connect()` if you want to use uninteractive mode.
99 |
100 | ### Client Info Delegate
101 |
102 | The `Connection` class provides the `ClientInfoDelegate` to return custom values for `wired.info.application.name`, `wired.info.application.version` and `wired.info.application.build` of the `wired.client_info` message.
103 |
104 | extension Controller: ClientInfoDelegate {
105 | func clientInfoApplicationName(for connection: Connection) -> String? {
106 | return "My Swift Client"
107 | }
108 |
109 | func clientInfoApplicationVersion(for connection: Connection) -> String? {
110 | return "1.0"
111 | }
112 |
113 | func clientInfoApplicationBuild(for connection: Connection) -> String? {
114 | return "99"
115 | }
116 | }
117 |
118 | ### BlockConnection
119 |
120 | WiredSwift provides a `BlockConnection` class which instead of using delegate, uses Swift completion closure, combined with Wired 2.0 transaction (`wired.transaction`). Each message sent using this class is flagged with an incremental transaction number in the `wired.transaction` field. Each response to this relative message sent by the server will also provide the same transaction number in the `wired.transaction` field. This way, the `BlockConnection` class can internally match responses with completion closure as callbacks.
121 |
122 | let connection = BlockConnection(withSpec: spec, delegate: self)
123 |
124 | if connection.connect(withUrl: serverURL) {
125 | let message = P7Message(withName: "wired.board.get_boards", spec: spec)
126 |
127 | connection.send(message: message, progressBlock: { (response) in
128 | if response.name == "wired.board.board_list", let board = response.string(forField: "wired.board.board") {
129 | print(board)
130 | }
131 | }) { (response) in
132 | print("Board loading done.")
133 | }
134 | }
135 |
136 | ### Logger
137 |
138 | You can configure the `Logger` class of WiredSwift as follow:
139 |
140 | // set the max vele severity
141 | Logger.setMaxLevel(.ERROR)
142 |
143 | // completely remove STDOUT console output
144 | Logger.removeDestination(.Stdout)
145 |
146 | ## Development
147 |
148 | ### Build
149 |
150 | swift build -v
151 |
152 | ### Run Tests
153 |
154 | swift test -v
155 |
156 | ### Xcode project
157 |
158 | You can (re)genrerate Xcode project by using:
159 |
160 | swift package generate-xcodeproj
161 |
162 | ## Contribute
163 |
164 | You are welcome to contribute using issues and pull-requests if you want to.
165 |
166 | Focus is on:
167 |
168 | * socket IO stability: the quality of in/out data interpretation and management through the Wired socket
169 | * mutli-threading stability: the ability to interact smoothly between connections and UIs
170 | * low-level unit tests: provides a strong implementation to enforce the integrity of the specification
171 | * specification compliance: any not yet implemented features that require kilometers of code…
172 | * limit regression from the original implementation
173 |
174 | Check the GitHub « Projects » page to get a sneap peek on the project insights and progress: https://github.com/nark/WiredSwift/projects
175 |
176 | ## License
177 |
178 | This code is distributed under BSD license, and it is free for personal or commercial use.
179 |
180 | - Copyright (c) 2003-2009 Axel Andersson, All rights reserved.
181 | - Copyright (c) 2011-2020 Rafaël Warnault, All rights reserved.
182 |
183 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
184 |
185 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
186 |
187 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
188 |
--------------------------------------------------------------------------------
/Sources/WiredSwfitTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/WiredSwfitTests/WiredSwfitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WiredSwfitTests.swift
3 | // WiredSwfitTests
4 | //
5 | // Created by Rafael Warnault on 28/03/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class WiredSwfitTests: XCTestCase {
12 | let specPath = Bundle(for: WiredSwfitTests.self).path(forResource: "wired", ofType: "xml")
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testUrl() {
23 | print("specPath : \(specPath)")
24 | let spec = P7Spec(withPath: specPath)
25 | let url = Url(withString: "wired://guest:password@localhost:4871")
26 |
27 | XCTAssert(url.scheme == "wired")
28 | XCTAssert(url.login == "guest")
29 | XCTAssert(url.password == "password")
30 | XCTAssert(url.hostname == "localhost")
31 | XCTAssert(url.port == 4871)
32 | }
33 |
34 |
35 | func testConnect() {
36 | Logger.setMaxLevel(.VERBOSE)
37 | print("specPath : \(specPath)")
38 | let spec = P7Spec(withPath: specPath)
39 | let url = Url(withString: "wired://localhost")
40 | let connection = Connection(withSpec: spec)
41 |
42 | XCTAssert(connection.connect(withUrl: url) == true)
43 | }
44 |
45 | func testPerformanceExample() {
46 | // This is an example of a performance test case.
47 | measure {
48 | // Put the code you want to measure the time of here.
49 | testUrl()
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Additions/Data.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Sata.swift
4 | // Wired
5 | //
6 | // Created by Rafael Warnault on 19/08/2019.
7 | // Copyright © 2019 Read-Write. All rights reserved.
8 | //
9 |
10 | import Foundation
11 |
12 |
13 | extension Data {
14 | public func toHex() -> String {
15 | return self.reduce("") { $0 + String(format: "%02x", $1) }
16 | }
17 |
18 | public mutating func append(byte data: Int8, count:Int = 1) {
19 | var data = data
20 | self.append(UnsafeBufferPointer(start: &data, count: count))
21 | }
22 |
23 |
24 | public mutating func append(uint8 data: UInt8, bigEndian: Bool = true) {
25 | var data = bigEndian ? data.bigEndian : data.littleEndian
26 | self.append(UnsafeBufferPointer(start: &data, count: 1))
27 | }
28 |
29 |
30 | public mutating func append(uint16 data: UInt16, bigEndian: Bool = true) {
31 | var data = bigEndian ? data.bigEndian : data.littleEndian
32 | self.append(UnsafeBufferPointer(start: &data, count: 1))
33 | }
34 |
35 |
36 | public mutating func append(uint32 data: UInt32, bigEndian: Bool = true) {
37 | var data = bigEndian ? data.bigEndian : data.littleEndian
38 | self.append(UnsafeBufferPointer(start: &data, count: 1))
39 | }
40 |
41 |
42 | public mutating func append(uint64 data: UInt64, bigEndian: Bool = true) {
43 | var data = bigEndian ? data.bigEndian : data.littleEndian
44 | self.append(UnsafeBufferPointer(start: &data, count: 1))
45 | }
46 |
47 |
48 | public mutating func append(double data: Double, bigEndian: Bool = true) {
49 | let d = bigEndian ? data.bitPattern.bigEndian : data.bitPattern.littleEndian
50 | self.append(Swift.withUnsafeBytes(of: d) { Data($0) })
51 | }
52 |
53 |
54 | var uint8: UInt8 {
55 | get {
56 | var number: UInt8 = 0
57 | self.copyBytes(to:&number, count: MemoryLayout.size)
58 | return number
59 | }
60 | }
61 |
62 | public var uint16: UInt16? {
63 | get {
64 | self.withUnsafeBytes( { (ptr : UnsafeRawBufferPointer) in
65 | let pointer = ptr.baseAddress!.assumingMemoryBound(to: UInt16.self).pointee
66 | return CFSwapInt16HostToBig(pointer)
67 | })
68 | }
69 | }
70 |
71 | public var uint32: UInt32? {
72 | get {
73 | self.withUnsafeBytes( { (ptr : UnsafeRawBufferPointer) in
74 | let pointer = ptr.baseAddress!.assumingMemoryBound(to: UInt32.self).pointee
75 | return CFSwapInt32HostToBig(pointer)
76 | })
77 | }
78 | }
79 |
80 | public var uint64: UInt64? {
81 | get {
82 | self.withUnsafeBytes( { (ptr : UnsafeRawBufferPointer) in
83 | let pointer = ptr.baseAddress!.assumingMemoryBound(to: UInt64.self).pointee
84 | return CFSwapInt64HostToBig(pointer)
85 | })
86 | }
87 | }
88 |
89 | public var double:Double? {
90 | get {
91 | self.withUnsafeBytes( { (ptr : UnsafeRawBufferPointer) in
92 | let pointer = ptr.baseAddress!.assumingMemoryBound(to: UInt64.self).pointee
93 | return CFConvertDoubleSwappedToHost(CFSwappedFloat64(v: pointer))
94 | })
95 | }
96 | }
97 |
98 | public var uuid: NSUUID? {
99 | get {
100 | var bytes = [UInt8](repeating: 0, count: self.count)
101 | self.copyBytes(to:&bytes, count: self.count * MemoryLayout.size)
102 | return NSUUID(uuidBytes: bytes)
103 | }
104 | }
105 |
106 | public var stringASCII: String? {
107 | get {
108 | return NSString(data: self, encoding: String.Encoding.ascii.rawValue) as String?
109 | }
110 | }
111 |
112 | public var stringUTF8: String? {
113 | get {
114 | return NSString(data: self, encoding: String.Encoding.utf8.rawValue) as String?
115 | }
116 | }
117 |
118 | struct HexEncodingOptions: OptionSet {
119 | let rawValue: Int
120 | static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
121 | }
122 |
123 | func hexEncodedString(options: HexEncodingOptions = []) -> String {
124 | let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
125 | return map { String(format: format, $0) }.joined()
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Additions/String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String.swift
3 | // Wired
4 | //
5 | // Created by Rafael Warnault on 19/08/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension String {
13 | public var nullTerminated: Data? {
14 | if var data = self.data(using: String.Encoding.utf8) {
15 | data.append(0)
16 | return data
17 | }
18 | return nil
19 | }
20 |
21 | public func deletingPrefix(_ prefix: String) -> String {
22 | guard self.hasPrefix(prefix) else { return self }
23 | return String(self.dropFirst(prefix.count))
24 | }
25 |
26 |
27 | public var isBlank: Bool {
28 | return allSatisfy({ $0.isWhitespace })
29 | }
30 | }
31 |
32 | extension Optional where Wrapped == String {
33 | public var isBlank: Bool {
34 | return self?.isBlank ?? true
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Core/Const.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Const.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 18/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 |
13 | public struct Wired {
14 | public static let wiredPort = 4871
15 | public static let wiredScheme = "wired"
16 | public static let defaultUserIcon = """
17 | iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr
18 | 0AAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJ
19 | qcGAAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAA
20 | Dx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0
21 | YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICA
22 | gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3Ln
23 | czLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjI
24 | j4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJv
25 | dXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR
26 | 0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogIC
27 | AgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmO
28 | k9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlw
29 | dGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4
30 | KTMInWQAACCdJREFUWAntVmuMVGcZfs53bnO/7czO7D
31 | Iss7DQcsdKu1RssYXEpmpsU4mxRrGYoOkvo8SY6B9Nj
32 | YkYIjb+MobQP8ZgIcQixVagFVqX+wLCXplZdhZ2534/
33 | M+fqe8ZlWQRajP9M35lvzvV73+d93tsAn8j/AQPc/+L
34 | Df7tZ6H+tf0Mi3v1sxB9Y63P5uyQmyKql1qpKLZUrlA
35 | auXh5/d3D34MjDgnpoABtee+yrfUsT3+uJxfrj3THO5
36 | /TBKcqQBRkGVChWHeVGGempmdrI6OThvw2c/mXut7nz
37 | HwfkYwGEd4S7VqzsfX1x36KXemJhRHx++F0+uCQPvKI
38 | fHOOgoUkAKlDMOnRTg95kSA6V60F14a92btl5FAIaBK
39 | REq8BxXHU+qI8EEH810te1tPNAT9+C1TFfECu64ujwh
40 | yBKDgi8BInJcHBuOJkXXtYBN/PBw4J07YEDTtQtBbl8
41 | PtMbWnwEDCkyPEnrOq3LBCRHR8L2AIm9EotE+4Jverv
42 | dqw2rBVE2INFyygJRH0QnvwgRfiH8LAIHc4OH2NZUVP
43 | IYyg3hytQgLiYv4Gp6pLM4XRYGfnx2N0L0GuCjFbcsq
44 | 2Wz8SAAXMdi768DC71r/E4/Ni3ahL5AAi6HhF7HCnRL
45 | SyByjjno2WoG51NncGLoGC5PXsSt0hRaegOCg6HZtMC
46 | 5+JdD3/ccLu6r/4E2Fcl4no5+WvcH4H5V+Lzcyb7mhA
47 | vfWrMDW7qfh4N3gqPPbbFg4lzyLA6dOUCeXkSmehMaa
48 | yIY8qB7cQDMEYQvKqNQVtFUDK7hrP6i+GL9rzgI2/ic
49 | 3I8BxoXwA40zuCXe5fhs+Fk4edfcBvvkyuQl7PnLbpw
50 | aeR88aVjQ1YlEPAKByI2GgyioFVSaDRRrKgwT4HkKUI
51 | QtkqP4ehPW66QiTitt67oHgOc70qO6pD9VKihw9XjhE
52 | O5QbW+oKhX8fP/PcHL8OHoSXYguDKK7OwxeYpBdAqaL
53 | BRSrdbQUHaZBjBEAXTOgNBpwheVtekU/I0C4QfGv2fq
54 | Y/XOXhK2ndcbkbKmEq1NDSOVTdz2+nLqC8ZkxxBIhRB
55 | /xQQhyVGMKuWIhXcihWFegtkyoDROGagEWQ7VZpTJVI
56 | PrZmu37vqGR8anbSu2svEuELfx2XeQe1xoaqtRYavUK
57 | 3FRqqqri7OgZ/ObPe3C9PIrgAi8krwSOZ1BUHRpnotU
58 | 0wKhKbL+qtxQwMt5qtjBBTlgeE5bLZBeGLx0wBjB+2+
59 | g9IYDLinIdDFyZIVPL4OC1P5HhAYiKA3k9h0KuiFhvB
60 | xati6OcrVC2mwj5Awg7Q8hTM0qOp8F0HjIno1KrIJ2f
61 | RMsu4wAD3wE4/WJ3i1rXbbkXgAKe1S2bOTCKYb5VgCo
62 | pCPoC8Ig+9ES74A45QOTC56euSJlXVqtITt1CbrKEBV
63 | YCL/S/iJAnhJPJ9/DGe3sp/gpM6oN25VrGbMOYRXAPA
64 | EFjVatMtUsvcNRbGLXalqeJnDlDxxo6wiFYlHAaeRXo
65 | DGADvxHHJ45hcHgYETOGnV/5EdYvWQ+KMzYtfQZhvhM
66 | /PfgTGEXymmMwqjaUO3IXgMagFf/C3z9jDEwPgFeIAo
67 | P20LvWDCWzx0KVr6KSqkAIM7ipFcsBAR+UTiI/WsPMs
68 | IIdGzZhbe/atnHbhEN04rl1z2Pv8d9hZGbYnhumWWTU
69 | iknxrMwBoO7kV2cQGB0b+6PoZttsjk0qoTYIwkJJDEs
70 | kOBoHqwkoNzW0iipK6Qo06vCPBdfim0++AomAzRcmMz
71 | CB9tHX0ripRk67a1TPAaBNISmK0fSxbDKwmU9KbtZrk
72 | iGTp3yYrRUmcZBWAc20hgr54ZRFPBndiG3PbMcLq16C
73 | T7a76x1RtRbeOX8UE9kJiEGe4m8exRFU7rwx24jIe5t
74 | p218Vlyhp+/n9Upz7IaMhanIW3dTRUqm2s4BoObHMWI
75 | 6nlm/GFz/9JTyeeAIu2d3WaRFryew4RjNDtEPDufGze
76 | OPUXrTEJgSZ1yiUe+cbt8/bDFDCEAbLBpDYunVr7e3D
77 | bx9Sw8rL/k4p7uCc6HDE0EtteV20H+vj/Xi0e2U7y+0
78 | wNfQ6xorXcG76HziWPIL3L51ATa+C1yWU63UYpFbsJO
79 | 8zeEvcm7gCjNnO0s5/i33RFgIgaJq2cnxkfF0hX1h4r
80 | fjPzVan8rml4eVY1mEbDNtJTAZryDTTmGjSyC2fxcXM
81 | aQIwhEIlD54yn7dEKFULjaqBVsuCQSE08rgpHAp+W5j
82 | mLmTqGeKxzXbb7lwOEAs63RnctWtXheOEDSGXX64WLT
83 | bIf/j0dek0GuUSMmoaN5UUchqNW1ZGxM2j6LTgp27oC
84 | /jp35BF45eIFDWoZLhVN2DOcBXxhGePfMOVMWXq0fOM
85 | 2wjmGLAvZsVOuTgNjKU8pD7vc+aX+74rbImtlgROIwp
86 | Mymr6OGnCyWRYo1KxY6/R2GvQACpRvRcrGkoVHY1RZN
87 | lbnt+LQ67jmq+ZzGazKdJ9pw3SxT2zgO7Z8WlEohHT6
88 | XKI3KinWL7M1TXe7HTEeLfDI0ASCR4B0Cl3Lbu5EPUG
89 | zYKmpqNa01FMGWbthDAoHPDv42/JH+re1jgZv/Gfxun
90 | 6vgzY99sSDAb9oigmWENcRvSuEj+lPuHbqK30PcJFHV
91 | EmSV76i8JzMDULSt5AKWWUq4MYa51xnGIT4geCzA3Xr
92 | NpEuUzxm5d4s+rbh/uFYP7z9nkikXDUarUgl5NiRE/c
93 | 6NB6WJfZJQRNHydZzGxyDT3PT5tpMSXUpBRtuuVJcMV
94 | UKkWd5KPloQA8QAXt3UpJYct+O7nmSqt965Ofh2TgX4
95 | k6d3UEBImrAAAAAElFTkSuQmCC
96 | """
97 | public static let transfersFileExtension = "WiredTransfer"
98 | }
99 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Core/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger.swift
3 | // Wired
4 | //
5 | // Created by Paul Repain on 15/05/2019.
6 | // Copyright © 2019 Read-Write.fr. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol LoggerDelegate: class {
12 | func loggerDidOutput(logger: Logger, output: String)
13 | }
14 |
15 | public extension LoggerDelegate {
16 | func loggerDidOutput(logger: Logger, output: String) {
17 |
18 | }
19 | }
20 |
21 | /**
22 | This class is for printing log, either in the console or in a file.
23 | Log can have different type of severity, and different type of output as
24 | stated before.
25 |
26 | */
27 | public class Logger {
28 |
29 |
30 | /**
31 | Enumeration for severity level
32 | */
33 | public enum LogLevel : Int {
34 | case FATAL = 0
35 | case ERROR = 1
36 | case WARNING = 2
37 | case INFO = 3
38 | case NOTICE = 4
39 | case DEBUG = 5
40 | case VERBOSE = 6
41 |
42 | var description: String {
43 | switch self {
44 | case .FATAL:
45 | return "FATAL"
46 | case .NOTICE:
47 | return "NOTICE"
48 | case .INFO:
49 | return "INFO"
50 | case .VERBOSE:
51 | return "VERBOSE"
52 | case .DEBUG:
53 | return "DEBUG"
54 | case .WARNING:
55 | return "WARNING"
56 | case .ERROR:
57 | return "ERROR"
58 | }
59 | }
60 | }
61 |
62 | /**
63 | Enumeration for type of output
64 | - Stdout: console
65 | - File: file
66 | */
67 | public enum Output {
68 | case Stdout
69 | case File
70 | }
71 |
72 | public enum TimeLimit: Int {
73 | case Minute = 0
74 | case Hour = 1
75 | case Day = 2
76 | case Month = 3
77 | }
78 |
79 |
80 |
81 | /**/
82 |
83 | public var targetName:String {
84 | get {
85 | if let bundleName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
86 | return bundleName
87 | }
88 | return "DcmSwift"
89 | }
90 | }
91 |
92 | private static var shared = Logger()
93 | public static var delegate:LoggerDelegate? = nil
94 |
95 | private var maxLevel: Int = 6
96 | public lazy var fileName:String = targetName + ".log"
97 | lazy var filePath:URL? = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent(fileName)
98 | public var outputs:[Output] = [.Stdout]
99 | public var sizeLimit:UInt64 = 1_000_000
100 | public var timeLimit:TimeLimit = .Minute
101 | public var startDate:Date = Date()
102 |
103 |
104 | /**/
105 |
106 |
107 | public static func notice(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
108 | if LogLevel.NOTICE.rawValue <= shared.maxLevel {
109 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.NOTICE)
110 | }
111 | }
112 |
113 | public static func info(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
114 | if LogLevel.INFO.rawValue <= shared.maxLevel {
115 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.INFO)
116 | }
117 | }
118 |
119 | public static func verbose(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
120 | if LogLevel.NOTICE.rawValue <= shared.maxLevel {
121 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.NOTICE)
122 | }
123 | }
124 |
125 | public static func debug(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
126 | if LogLevel.DEBUG.rawValue <= shared.maxLevel {
127 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.DEBUG)
128 | }
129 | }
130 |
131 | public static func warning(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
132 | if LogLevel.WARNING.rawValue <= shared.maxLevel {
133 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.WARNING)
134 | }
135 | }
136 |
137 | public static func error(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
138 | if LogLevel.ERROR.rawValue <= shared.maxLevel {
139 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.ERROR)
140 | }
141 | }
142 |
143 | public static func error(_ error:WiredError, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
144 | if LogLevel.ERROR.rawValue <= shared.maxLevel {
145 | shared.output(string: error.message, error.title, file, function, line: line, severity: LogLevel.ERROR)
146 | }
147 | }
148 |
149 | public static func fatal(_ string:String, _ tag:String? = nil, _ file: String = #file, _ function: String = #function, line: Int = #line) {
150 | if LogLevel.FATAL.rawValue <= shared.maxLevel {
151 | shared.output(string: string, tag, file, function, line: line, severity: LogLevel.FATAL)
152 | }
153 | }
154 |
155 |
156 |
157 | /**
158 | Format the output
159 | Adds a newline for writting in file
160 | - parameter string: the message to be sent
161 | - parameter tag: the tag to be printed; name of the target by default
162 | - parameter file: file where the log was called
163 | - parameter function: same
164 | - parameter line: same
165 | - parameter severity: level of severity of the log (see enum)
166 |
167 | */
168 | public func output(string:String, _ tag:String?, _ file: String = #file, _ function: String = #function, line: Int = #line, severity:LogLevel) {
169 | let date = Date()
170 | let df = DateFormatter()
171 | // formatting date
172 | df.dateFormat = "dd-MM-yyyy HH:mm:ss"
173 | // if tag is nil, tag is name of target
174 | let tagName:String = tag ?? self.targetName
175 |
176 | /* DATE SEVERITY -> [TAG] MESSAGE */
177 | let outputString:String = "\(df.string(from: date)) \(severity.description) -> [\(tagName)]\t \(string)"
178 |
179 | if let d = Logger.delegate {
180 | d.loggerDidOutput(logger: self, output: outputString)
181 | }
182 |
183 | // managing different type of output (console or file)
184 | for output in outputs {
185 | switch output {
186 | case .Stdout:
187 | consoleLog(message: outputString)
188 | case .File:
189 | if fileLog(message: outputString + "\n") {}
190 | }
191 |
192 | }
193 | }
194 |
195 | /**
196 | Prints to the console
197 | - parameter message: the log to be printed in the console
198 |
199 | */
200 | public func consoleLog(message:String) {
201 | print(message)
202 | }
203 |
204 | /**
205 | Write in file. Creates a file if the file doesn't exist. Append at
206 | the end of the file.
207 | - parameter message: the log to be written in the file
208 | - returns: true if filepath is correct
209 |
210 | */
211 | public func fileLog(message: String) -> Bool {
212 | if let fileURL = filePath {
213 |
214 | if getFileSize() > self.sizeLimit {
215 | do {
216 | try FileManager.default.removeItem(at: fileURL)
217 | }
218 | catch {}
219 | }
220 | Logger.eraseFileByTime()
221 |
222 | var isDirectory = ObjCBool(true)
223 | // if file doesn't exist we create it
224 | if !FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) {
225 | FileManager.default.createFile(atPath: fileURL.path, contents: Data(), attributes: nil)
226 | }
227 |
228 | do {
229 | if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
230 | fileHandle.seekToEndOfFile()
231 | let data:Data = message.data(using: String.Encoding.utf8, allowLossyConversion: false)!
232 | fileHandle.write(data)
233 | } else {
234 | try message.write(to: fileURL, atomically: false, encoding: .utf8)
235 | }
236 | }
237 | catch {/* error handling here */}
238 |
239 | return true
240 | }
241 | return false
242 | }
243 |
244 |
245 | /**
246 | Set the destination for output : file (with name of file), console.
247 | Default log file is dicom.log
248 | - parameter destinations: all the destinations where the logs are outputted
249 | - parameter filePath: path of the logfile
250 |
251 | */
252 | public static func setDestinations(_ destinations: [Output], filePath: String? = nil) {
253 | shared.outputs = destinations
254 | if let fileName:String = filePath {
255 | shared.fileName = fileName
256 | }
257 | }
258 |
259 | /**
260 | Set the file path where the logs are printed
261 | By default, the path is ~/Documents/\(targetName).log
262 | - parameter withPath: path of the file, the filename is appended at the end
263 | if there is none
264 | - returns: false is path is nil
265 |
266 | */
267 | public static func setFileDestination(_ withPath: String?) -> Bool {
268 | guard var path = withPath else {
269 | return false
270 | }
271 |
272 | if !path.contains(self.shared.fileName) {
273 | path += "/" + self.shared.fileName
274 | }
275 | shared.filePath = URL(fileURLWithPath: path)
276 |
277 | return true
278 | }
279 |
280 |
281 | /**
282 | Set the level of logs printed
283 | - parameter at: the log level to be set
284 |
285 | */
286 | public static func setMaxLevel(_ at: LogLevel) {
287 | if 0 <= at.rawValue && at.rawValue <= 5 {
288 | shared.maxLevel = at.rawValue
289 | }
290 | }
291 |
292 | public static func setLimitLogSize(_ at: UInt64) {
293 | shared.sizeLimit = at
294 | }
295 |
296 | public static func addDestination(_ dest: Output) {
297 | shared.outputs.append(dest)
298 | }
299 |
300 | public static func removeDestination(_ dest: Output) {
301 | shared.outputs = shared.outputs.filter{$0 != dest}
302 | }
303 |
304 | public static func setTimeLimit(_ at: TimeLimit) {
305 | shared.timeLimit = at
306 | shared.startDate = Date()/* the date is reset */
307 | UserDefaults.standard.set(shared.startDate, forKey: "startDate")
308 | }
309 |
310 | /**
311 | Erase the log file
312 |
313 | */
314 | public static func eraseFileByTime() {
315 | let range = -Int(shared.startDate.timeIntervalSinceNow)
316 | let t:Int
317 |
318 | switch shared.timeLimit {
319 | case .Minute:
320 | t = range / 60
321 | case .Hour:
322 | t = range / 3600
323 | case .Day:
324 | t = range / 86400
325 | case .Month:
326 | t = range / 100000
327 | }
328 |
329 | if t >= 1 {
330 | if let path = shared.filePath {
331 | Logger.removeLogFile(path)
332 | shared.startDate = Date()
333 | }
334 | }
335 | }
336 |
337 | /**
338 | Delete the log file
339 | - parameter at: the URL where the log file is
340 |
341 | */
342 | public static func removeLogFile(_ at: URL) {
343 | do {
344 | try FileManager.default.removeItem(at: at)
345 | }
346 | catch {}
347 | }
348 |
349 |
350 |
351 | /**/
352 |
353 |
354 | private func getFileSize() -> UInt64 {
355 | var fileSize : UInt64 = 0
356 |
357 | do {
358 | if let path = filePath?.path {
359 | let attr = try FileManager.default.attributesOfItem(atPath: path)
360 | fileSize = attr[FileAttributeKey.size] as! UInt64
361 |
362 | //if you convert to NSDictionary, you can get file size old way as well.
363 | let dict = attr as NSDictionary
364 | fileSize = dict.fileSize()
365 | }
366 | } catch {
367 | print("Error: \(error)")
368 | }
369 |
370 | return fileSize
371 | }
372 |
373 | public static func getSizeLimit() -> UInt64 {
374 | return shared.sizeLimit
375 | }
376 |
377 |
378 | public static func getFileDestination() -> String? {
379 | return shared.filePath?.path
380 | }
381 |
382 | public static func getTimeLimit() -> Int {
383 | return shared.timeLimit.rawValue
384 | }
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 | /**
393 | Set the logger according to the settings in UserDefaults
394 |
395 | */
396 | public static func setPreferences() {
397 | /* set the destinations output */
398 | var destinations:[Logger.Output] = []
399 | if UserDefaults.standard.bool(forKey: "Print LogsInLogFile") {
400 | destinations.append(Logger.Output.Stdout)
401 | }
402 | if UserDefaults.standard.bool(forKey: "logInConsole") {
403 | destinations.append(Logger.Output.File)
404 | }
405 | Logger.setDestinations(destinations)
406 | if Logger.setFileDestination(UserDefaults.standard.string(forKey: "logFilePath")) {
407 | // success
408 | }
409 |
410 | /* set the maximum level of log output */
411 | Logger.setMaxLevel(Logger.LogLevel(rawValue: UserDefaults.standard.integer(forKey: "LogLevel"))!)
412 |
413 | let i = UInt64(UserDefaults.standard.integer(forKey: "clearLogPeriods"))
414 | Logger.setLimitLogSize(i)
415 |
416 | if let tl = TimeLimit.init(rawValue: UserDefaults.standard.integer(forKey: "timeLimitLogger")) {
417 | Logger.shared.timeLimit = tl
418 | }
419 | if let date2 = UserDefaults.standard.object(forKey: "startDate") as? Date {
420 | Logger.shared.startDate = date2
421 | }
422 | }
423 | }
424 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Core/WiredError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WiredError.swift
3 | // Wired
4 | //
5 | // Created by Rafael Warnault on 17/03/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class WiredError: NSObject {
12 | public var specError:SpecError?
13 |
14 | private var errorTitle:String!
15 | private var errorMessage:String!
16 |
17 |
18 | public init(withSPecError specError: SpecError) {
19 | self.specError = specError
20 | }
21 |
22 |
23 | public init(withTitle title:String, message: String) {
24 | self.errorTitle = title
25 | self.errorMessage = message
26 | }
27 |
28 |
29 | public override var description: String {
30 | if let se = self.specError {
31 | return se.name
32 | }
33 | else {
34 | return "\(String(describing: self.errorTitle)): \(String(describing: self.errorMessage))"
35 | }
36 | }
37 |
38 |
39 | public var title:String {
40 | get {
41 | if let se = self.specError {
42 | return se.name
43 | }
44 | else {
45 | return self.errorTitle
46 | }
47 | }
48 | }
49 |
50 |
51 | public var message:String {
52 | get {
53 | if let se = self.specError {
54 | return se.name
55 | }
56 | else {
57 | return self.errorMessage
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Crypto/P7Cipher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // P7Cipher.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 25/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CryptoSwift
11 |
12 |
13 |
14 | public class P7Cipher {
15 | public var cipher: P7Socket.CipherType = .NONE
16 | public var cipherKey:String!
17 | public var cipherIV:[UInt8]!
18 |
19 | private let aesBlockSize = 16
20 |
21 |
22 |
23 | public init(cipher: P7Socket.CipherType) {
24 | self.cipher = cipher
25 | self.cipherKey = self.randomString(length: self.keySize())
26 |
27 | var ivBytes = [UInt8](repeating: 0, count: aesBlockSize)
28 | guard 0 == SecRandomCopyBytes(kSecRandomDefault, ivBytes.count, &ivBytes) else {
29 | Logger.fatal("IV creation failed")
30 | return
31 | }
32 |
33 | self.cipherIV = ivBytes
34 | }
35 |
36 |
37 | public func encrypt(data: Data) -> Data? {
38 | do {
39 | let aes = try AES(key: Array(self.cipherKey.data(using: .utf8)!), blockMode: CBC(iv: self.cipherIV!), padding: .pkcs7)
40 | let dataArray = try aes.encrypt(Array(data))
41 | return Data(dataArray)
42 | } catch {
43 | Logger.fatal("Encryption error: \(error)")
44 |
45 | return nil
46 | }
47 | }
48 |
49 |
50 | public func decrypt(data: Data) -> Data? {
51 | do {
52 | let aes = try AES(key: Array(self.cipherKey.data(using: .utf8)!), blockMode: CBC(iv: self.cipherIV!), padding: .pkcs7)
53 | let dataArray = Array(data)
54 | let decryptedData = try aes.decrypt(dataArray)
55 |
56 | return Data(decryptedData)
57 | } catch {
58 | Logger.fatal("Decryption error: \(error) \(data.toHex())")
59 |
60 | return nil
61 | }
62 | }
63 |
64 |
65 |
66 | private func randomString(length: Int) -> String {
67 | let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
68 | return String((0.. Int {
73 | var keySizeAES = 0
74 |
75 | if self.cipher == .RSA_AES_128 {
76 | keySizeAES = 16
77 | }
78 | else if self.cipher == .RSA_AES_192 {
79 | keySizeAES = 24
80 | }
81 | else if self.cipher == .RSA_AES_256 {
82 | keySizeAES = 32
83 | }
84 |
85 | return keySizeAES
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Crypto/RSA.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RSA.swift
3 | // WiredSwift
4 | //
5 | // Created by Rafael Warnault on 19/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
12 | import Security
13 | #elseif os(Linux)
14 | import OpenSSL
15 | #endif
16 |
17 | open class RSA {
18 | var publicKey:Data!
19 |
20 |
21 | public init?(publicKey: Data) {
22 | do {
23 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
24 | let k = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey)
25 | self.publicKey = try SwKeyConvert.PublicKey.pemToPKCS1DER(k)
26 | #elseif os(Linux)
27 |
28 | #endif
29 | } catch {
30 | Logger.error("RSA Public Key init failed")
31 | }
32 | }
33 |
34 |
35 | func encrypt(data: Data) -> Data? {
36 | do {
37 | var encryptedData:Data? = nil
38 |
39 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
40 | encryptedData = try CC.RSA.encrypt(data, derKey: self.publicKey, tag: Data(), padding: .oaep, digest: .sha1)
41 | #elseif os(Linux)
42 |
43 | #endif
44 |
45 | return encryptedData
46 | } catch {
47 | Logger.error("RSA Public encrypt failed")
48 | }
49 |
50 | return nil
51 | }
52 |
53 |
54 | func decrypt(data: Data) -> Data? {
55 | return nil
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Data/ServerInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ServerInfo.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 20/08/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public class ServerInfo {
13 | public var applicationName: String!
14 | public var applicationVersion: String!
15 | public var applicationBuild: String!
16 |
17 | public var osName: String!
18 | public var osVersion: String!
19 | public var arch: String!
20 |
21 | public var supportRSRC: Bool!
22 | public var serverName: String!
23 | public var serverDescription: String!
24 | public var serverBanner: Data!
25 |
26 | public var startTime:Date!
27 | public var filesCount:UInt64!
28 | public var filesSize:UInt64!
29 |
30 | private var message: P7Message!
31 |
32 | public init(message: P7Message) {
33 | self.message = message
34 |
35 | if let v = message.string(forField: "wired.info.application.name") {
36 | self.applicationName = v
37 | }
38 |
39 | if let v = message.string(forField: "wired.info.application.version") {
40 | self.applicationVersion = v
41 | }
42 |
43 | if let v = message.string(forField: "wired.info.application.build") {
44 | self.applicationBuild = v
45 | }
46 |
47 | if let v = message.string(forField: "wired.info.os.name") {
48 | self.osName = v
49 | }
50 |
51 | if let v = message.string(forField: "wired.info.os.version") {
52 | self.osVersion = v
53 | }
54 |
55 | if let v = message.string(forField: "wired.info.arch") {
56 | self.arch = v
57 | }
58 |
59 | if let v = message.bool(forField: "wired.info.supports_rsrc") {
60 | self.supportRSRC = v
61 | }
62 |
63 | if let v = message.string(forField: "wired.info.name") {
64 | self.serverName = v
65 | }
66 |
67 | if let v = message.string(forField: "wired.info.description") {
68 | self.serverDescription = v
69 | }
70 |
71 | if let v = message.data(forField: "wired.info.banner") {
72 | self.serverBanner = v
73 | }
74 |
75 | if let v = message.date(forField: "wired.info.start_time") {
76 | self.startTime = v
77 | }
78 |
79 | if let v = message.uint64(forField: "wired.info.files.count") {
80 | self.filesCount = v
81 | }
82 |
83 | if let v = message.uint64(forField: "wired.info.files.size") {
84 | self.filesSize = v
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Data/UserInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserInfo.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 20/08/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public class UserInfo {
13 | public var userID: UInt32!
14 | public var idle: Bool!
15 |
16 | public var nick: String!
17 | public var status: String!
18 | public var icon: Data!
19 | public var color: UInt32!
20 |
21 | private var message: P7Message!
22 |
23 | public init(message: P7Message) {
24 | self.message = message
25 |
26 | self.update(withMessage: message)
27 | }
28 |
29 | public func update(withMessage message: P7Message) {
30 | if let v = message.uint32(forField: "wired.user.id") {
31 | self.userID = v
32 | }
33 |
34 | if let v = message.bool(forField: "wired.user.idle") {
35 | self.idle = v
36 | }
37 |
38 | if let v = message.string(forField: "wired.user.nick") {
39 | self.nick = v
40 | }
41 |
42 | if let v = message.string(forField: "wired.user.status") {
43 | self.status = v
44 | }
45 |
46 | if let v = message.data(forField: "wired.user.icon") {
47 | self.icon = v
48 | }
49 |
50 | if let v = message.enumeration(forField: "wired.account.color") {
51 | self.color = v
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2019 Read-Write. All rights reserved.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Network/BlockConnection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlockConnection.swift
3 | // Wired iOS
4 | //
5 | // Created by Rafael Warnault on 29/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class BlockConnection: Connection {
12 | private let queue = DispatchQueue(label: "fr.read-write.WiredSwift.BlockConnection", attributes: .concurrent)
13 |
14 | var transactionCounter:UInt32 = 0
15 | var progressBlocks:[UInt32:(P7Message) -> Void] = [:]
16 | var completionBlocks:[UInt32:(P7Message?) -> Void] = [:]
17 |
18 | public func send(message: P7Message, completionBlock: @escaping (P7Message?) -> Void) {
19 | self.transactionCounter += 1
20 |
21 | if self.socket.connected {
22 | message.addParameter(field: "wired.transaction", value: self.transactionCounter)
23 |
24 | if !self.socket.write(message) {
25 | completionBlock(nil)
26 | }
27 |
28 | queue.async(flags: .barrier) {
29 | self.completionBlocks[self.transactionCounter] = completionBlock
30 | }
31 | } else {
32 | completionBlock(nil)
33 | }
34 | }
35 |
36 |
37 | public func send(message: P7Message, progressBlock: @escaping (P7Message) -> Void, completionBlock: @escaping (P7Message?) -> Void) {
38 | self.transactionCounter += 1
39 |
40 | if self.socket.connected {
41 | message.addParameter(field: "wired.transaction", value: self.transactionCounter)
42 |
43 | if !self.socket.write(message) {
44 | completionBlock(nil)
45 | }
46 |
47 | queue.async(flags: .barrier) {
48 | self.progressBlocks[self.transactionCounter] = progressBlock
49 | self.completionBlocks[self.transactionCounter] = completionBlock
50 | }
51 | } else {
52 | completionBlock(nil)
53 | }
54 | }
55 |
56 | override func handleMessage(_ message: P7Message) {
57 | // lets delegate handled messages transparently
58 | super.handleMessage(message)
59 |
60 | guard let transaction = message.uint32(forField: "wired.transaction") else {
61 | return
62 | }
63 |
64 | switch message.name {
65 | case "wired.send_ping":
66 | super.pingReply()
67 |
68 | case "wired.error":
69 | queue.sync {
70 | if let completionBlock = completionBlocks[transaction] {
71 | DispatchQueue.main.async {
72 | completionBlock(message)
73 | }
74 |
75 | completionBlocks.removeValue(forKey: transaction)
76 | progressBlocks.removeValue(forKey: transaction)
77 | }
78 | }
79 |
80 | default:
81 | if message.name == "wired.okay" || message.name.hasSuffix(".done") {
82 | queue.sync {
83 | if let completionBlock = completionBlocks[transaction] {
84 | DispatchQueue.main.async {
85 | completionBlock(message)
86 | }
87 |
88 | completionBlocks.removeValue(forKey: transaction)
89 | progressBlocks.removeValue(forKey: transaction)
90 | }
91 | }
92 | } else {
93 | queue.sync {
94 | if progressBlocks.count > 0 {
95 | if let progressBlock = progressBlocks[transaction] {
96 | DispatchQueue.main.async {
97 | progressBlock(message)
98 | }
99 | }
100 | } else {
101 | if let completionBlock = completionBlocks[transaction] {
102 | DispatchQueue.main.async {
103 | completionBlock(message)
104 | }
105 | }
106 | }
107 | }
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Network/Connection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Connection.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 18/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Dispatch
11 |
12 |
13 | extension Notification.Name {
14 | public static let linkConnectionWillDisconnect = Notification.Name("linkConnectionWillDisconnect")
15 | public static let linkConnectionDidClose = Notification.Name("linkConnectionDidClose")
16 | public static let linkConnectionDidReconnect = Notification.Name("linkConnectionDidReconnect")
17 | public static let linkConnectionDidFailReconnect = Notification.Name("linkConnectionDidFailReconnect")
18 | }
19 |
20 |
21 |
22 |
23 | public protocol ConnectionDelegate: class {
24 | func connectionDidConnect(connection: Connection)
25 | func connectionDidFailToConnect(connection: Connection, error: Error)
26 | func connectionDisconnected(connection: Connection, error: Error?)
27 |
28 | func connectionDidSendMessage(connection: Connection, message: P7Message)
29 | func connectionDidReceiveMessage(connection: Connection, message: P7Message)
30 | func connectionDidReceiveError(connection: Connection, message: P7Message)
31 | }
32 |
33 | public protocol ClientInfoDelegate: class {
34 | func clientInfoApplicationName(for connection: Connection) -> String?
35 | func clientInfoApplicationVersion(for connection: Connection) -> String?
36 | func clientInfoApplicationBuild(for connection: Connection) -> String?
37 | }
38 |
39 | public extension ConnectionDelegate {
40 | // optional delegate methods
41 | func connectionDidConnect(connection: Connection) { }
42 | func connectionDidFailToConnect(connection: Connection, error: Error) { }
43 | func connectionDisconnected(connection: Connection, error: Error?) { }
44 | func connectionDidSendMessage(connection: Connection, message: P7Message) { }
45 | }
46 |
47 | public extension ClientInfoDelegate {
48 | // optional delegate methods
49 | func clientInfoApplicationName(for connection: Connection) -> String? { return nil }
50 | func clientInfoApplicationVersion(for connection: Connection) -> String? { return nil }
51 | func clientInfoApplicationBuild(for connection: Connection) -> String? { return nil }
52 | }
53 |
54 |
55 |
56 |
57 | open class Connection: NSObject {
58 | public var spec: P7Spec
59 | public var url: Url!
60 | public var socket: P7Socket!
61 | public var delegates: [ConnectionDelegate] = []
62 | public var clientInfoDelegate:ClientInfoDelegate?
63 | public var interactive: Bool = true
64 |
65 | public var userID: UInt32!
66 | public var userInfo: UserInfo?
67 |
68 | public var nick: String = "Swift Wired"
69 | public var status: String = ""
70 | public var icon: String = Wired.defaultUserIcon
71 |
72 | public var serverInfo: ServerInfo!
73 |
74 | private var lastPingDate:Date!
75 | private var pingCheckTimer:Timer!
76 |
77 | private var listener:DispatchWorkItem!
78 |
79 | public var URI:String {
80 | get {
81 | return "\(self.url.login)@\(self.url.hostname):\(self.url.port)"
82 | }
83 | }
84 |
85 | public init(withSpec spec: P7Spec, delegate: ConnectionDelegate? = nil) {
86 | self.spec = spec
87 |
88 | if let d = delegate {
89 | self.delegates.append(d)
90 | }
91 | }
92 |
93 |
94 | public func addDelegate(_ delegate:ConnectionDelegate) {
95 | self.delegates.append(delegate)
96 | }
97 |
98 | public func removeDelegate(_ delegate:ConnectionDelegate) {
99 | if let index = delegates.firstIndex(where: { $0 === delegate }) {
100 | delegates.remove(at: index)
101 | }
102 | }
103 |
104 |
105 | public func connect(withUrl url: Url, cipher:P7Socket.CipherType = .RSA_AES_256, compression:P7Socket.Compression = .NONE, checksum:P7Socket.Checksum = .SHA1) -> Bool {
106 | self.url = url
107 |
108 | self.socket = P7Socket(hostname: self.url.hostname, port: self.url.port, spec: self.spec)
109 |
110 | self.socket.username = url.login
111 | self.socket.password = url.password
112 |
113 | self.socket.cipherType = cipher
114 | self.socket.compression = compression // TODO: Gzip deflate still not implemented
115 | self.socket.checksum = checksum
116 |
117 | if !self.socket.connect() {
118 | return false
119 | }
120 |
121 | for d in self.delegates {
122 | DispatchQueue.main.async {
123 | d.connectionDidConnect(connection: self)
124 | }
125 | }
126 |
127 | if !self.clientInfo() {
128 | return false
129 | }
130 |
131 | if !self.setUser() {
132 | return false
133 | }
134 |
135 | if !self.login() {
136 | return false
137 | }
138 |
139 | if self.interactive == true {
140 | self.listen()
141 | }
142 |
143 | self.pingCheckTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: { (timer) in
144 | if let lpd = self.lastPingDate {
145 | let interval = Date().timeIntervalSince(lpd)
146 | if interval > 65 {
147 | Logger.error("Lost ping, server is probably down, disconnecting...")
148 |
149 | if self.isConnected() {
150 | self.disconnect()
151 | }
152 | }
153 | }
154 | })
155 |
156 | return true
157 |
158 | }
159 |
160 |
161 | public func reconnect() -> Bool {
162 | self.pingCheckTimer.invalidate()
163 | self.pingCheckTimer = nil
164 |
165 | let cipher = self.socket.cipherType
166 | let compression = self.socket.compression
167 | let checksum = self.socket.checksum
168 |
169 | self.socket.disconnect()
170 |
171 | self.socket = P7Socket(hostname: self.url.hostname, port: self.url.port, spec: self.spec)
172 |
173 | self.socket.username = self.url.login
174 | self.socket.password = self.url.password
175 |
176 | self.socket.cipherType = cipher
177 | self.socket.compression = compression // TODO: Gzip deflate still not implemented
178 | self.socket.checksum = checksum
179 |
180 | if !self.socket.connect() {
181 | return false
182 | }
183 |
184 | if !self.clientInfo() {
185 | return false
186 | }
187 |
188 | if !self.setUser() {
189 | return false
190 | }
191 |
192 | if !self.login() {
193 | return false
194 | }
195 |
196 | if self.interactive == true {
197 | self.listen()
198 | }
199 |
200 | self.pingCheckTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: { (timer) in
201 | if let lpd = self.lastPingDate {
202 | let interval = Date().timeIntervalSince(lpd)
203 | if interval > 65 {
204 | Logger.error("Lost ping, server is probably down, disconnecting...")
205 |
206 | if self.isConnected() {
207 | self.disconnect()
208 | }
209 | }
210 | }
211 | })
212 |
213 | return true
214 | }
215 |
216 |
217 | public func disconnect() {
218 | NotificationCenter.default.post(name: .linkConnectionWillDisconnect, object: self)
219 |
220 | self.stopListening()
221 | self.socket.disconnect()
222 |
223 | DispatchQueue.main.async {
224 |
225 | NotificationCenter.default.post(name: .linkConnectionDidClose, object: self)
226 |
227 | for d in self.delegates {
228 | d.connectionDisconnected(connection: self, error: nil)
229 | }
230 | }
231 | }
232 |
233 |
234 | public func isConnected() -> Bool {
235 | return self.socket.connected
236 | }
237 |
238 |
239 | public func send(message:P7Message) -> Bool {
240 | if self.socket.connected {
241 | let r = self.socket.write(message)
242 |
243 | DispatchQueue.main.async {
244 | for d in self.delegates {
245 | d.connectionDidSendMessage(connection: self, message: message)
246 | }
247 | }
248 |
249 | return r
250 | }
251 | return false
252 | }
253 |
254 |
255 | public func readMessage() -> P7Message? {
256 | if self.socket.connected {
257 | return self.socket.readMessage()
258 | }
259 | return nil
260 | }
261 |
262 |
263 |
264 |
265 | public func joinChat(chatID: Int) -> Bool {
266 | let message = P7Message(withName: "wired.chat.join_chat", spec: self.spec)
267 |
268 | message.addParameter(field: "wired.chat.id", value: UInt32(chatID))
269 |
270 | if !self.send(message: message) {
271 | return false
272 | }
273 |
274 | return true
275 | }
276 |
277 |
278 | private func listen() {
279 | self.stopListening()
280 |
281 | // we use a worker to ensure previous thread was terminated
282 | listener = DispatchWorkItem {
283 | while (self.interactive == true && self.socket.connected == true) {
284 | if let message = self.socket.readMessage() {
285 | if self.interactive == true {
286 | self.handleMessage(message)
287 | }
288 | } else {
289 | if self.isConnected() {
290 | print("self.socket error : \(self.socket.errors)")
291 | self.disconnect()
292 | }
293 | }
294 | }
295 | }
296 |
297 | DispatchQueue.global().async(execute: listener)
298 | }
299 |
300 |
301 | public func stopListening() {
302 | if let l = listener {
303 | l.cancel()
304 | listener = nil
305 | }
306 | }
307 |
308 |
309 |
310 | internal func handleMessage(_ message:P7Message) {
311 | switch message.name {
312 | case "wired.send_ping":
313 | self.pingReply()
314 |
315 | case "wired.error":
316 | for d in self.delegates {
317 | DispatchQueue.main.async {
318 | d.connectionDidReceiveError(connection: self, message: message)
319 | }
320 | }
321 |
322 | default:
323 | for d in self.delegates {
324 | DispatchQueue.main.async {
325 | d.connectionDidReceiveMessage(connection: self, message: message)
326 | }
327 | }
328 | }
329 | }
330 |
331 |
332 |
333 | internal func pingReply() {
334 | _ = self.send(message: P7Message(withName: "wired.ping", spec: self.spec))
335 |
336 | self.lastPingDate = Date()
337 | }
338 |
339 |
340 |
341 | private func setNick() -> Bool {
342 | let message = P7Message(withName: "wired.user.set_nick", spec: self.spec)
343 |
344 | message.addParameter(field: "wired.user.nick", value: self.nick)
345 |
346 | if !self.send(message: message) {
347 | return false
348 | }
349 |
350 | if self.socket.readMessage() == nil {
351 | return false
352 | }
353 |
354 | return true
355 | }
356 |
357 |
358 | private func setStatus() -> Bool {
359 | let message = P7Message(withName: "wired.user.set_status", spec: self.spec)
360 |
361 | message.addParameter(field: "wired.user.status", value: self.status)
362 |
363 | if !self.send(message: message) {
364 | return false
365 | }
366 |
367 | if self.socket.readMessage() == nil {
368 | return false
369 | }
370 |
371 | return true
372 | }
373 |
374 |
375 | private func setIcon() -> Bool {
376 | let message = P7Message(withName: "wired.user.set_icon", spec: self.spec)
377 |
378 | message.addParameter(field: "wired.user.icon", value: Data(base64Encoded: self.icon, options: .ignoreUnknownCharacters))
379 |
380 | if !self.send(message: message) {
381 | return false
382 | }
383 |
384 | if self.socket.readMessage() == nil {
385 | return false
386 | }
387 |
388 | return true
389 | }
390 |
391 |
392 |
393 | private func setUser() -> Bool {
394 | if !self.setNick() {
395 | return false
396 | }
397 |
398 | if !self.setStatus() {
399 | return false
400 | }
401 |
402 | if !self.setIcon() {
403 | return false
404 | }
405 |
406 | return true
407 | }
408 |
409 |
410 | private func login() -> Bool {
411 | let message = P7Message(withName: "wired.send_login", spec: self.spec)
412 |
413 | message.addParameter(field: "wired.user.login", value: self.url!.login)
414 |
415 | var password = "".sha1()
416 |
417 | if self.url?.password != nil && self.url?.password != "" {
418 | password = self.url!.password.sha1()
419 | }
420 |
421 | message.addParameter(field: "wired.user.password", value: password)
422 |
423 | _ = self.send(message: message)
424 |
425 | guard let response = self.socket.readMessage() else {
426 | return false
427 | }
428 |
429 | if let uid = response.uint32(forField: "wired.user.id") {
430 | self.userID = uid
431 | }
432 |
433 | // read account priviledges
434 | _ = self.socket.readMessage()
435 |
436 |
437 | return true
438 | }
439 |
440 |
441 | private func clientInfo() -> Bool {
442 | let message = P7Message(withName: "wired.client_info", spec: self.spec)
443 | message.addParameter(field: "wired.info.application.name", value: "Wired Swift")
444 |
445 | if let value = self.clientInfoDelegate?.clientInfoApplicationName(for: self) {
446 | message.addParameter(field: "wired.info.application.name", value: value)
447 | }
448 |
449 | message.addParameter(field: "wired.info.application.version", value: "1.0")
450 | if let value = self.clientInfoDelegate?.clientInfoApplicationVersion(for: self) {
451 | message.addParameter(field: "wired.info.application.version", value: value)
452 | }
453 |
454 | message.addParameter(field: "wired.info.application.build", value: "1")
455 | if let value = self.clientInfoDelegate?.clientInfoApplicationBuild(for: self) {
456 | message.addParameter(field: "wired.info.application.build", value: value)
457 | }
458 |
459 |
460 | #if os(iOS)
461 | message.addParameter(field: "wired.info.os.name", value: "iOS")
462 | #elseif os(macOS)
463 | message.addParameter(field: "wired.info.os.name", value: "macOS")
464 | #else
465 | message.addParameter(field: "wired.info.os.name", value: "Linux")
466 | #endif
467 |
468 | message.addParameter(field: "wired.info.os.version", value: ProcessInfo.processInfo.operatingSystemVersionString)
469 |
470 | #if os(iOS)
471 | message.addParameter(field: "wired.info.arch", value: "armv7")
472 | #elseif os(macOS)
473 | message.addParameter(field: "wired.info.arch", value: "x86_64")
474 | #else
475 | message.addParameter(field: "wired.info.arch", value: "x86_64")
476 | #endif
477 |
478 | message.addParameter(field: "wired.info.supports_rsrc", value: false)
479 |
480 | _ = self.send(message: message)
481 |
482 | guard let response = self.socket.readMessage() else {
483 | print("no response ?")
484 | return false
485 | }
486 |
487 | self.serverInfo = ServerInfo(message: response)
488 |
489 | return true
490 | }
491 |
492 |
493 | // MARK: -
494 |
495 |
496 | }
497 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Network/P7Socket.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Socket.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 18/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SocketSwift
11 | import CryptoSwift
12 | import CZlib
13 |
14 | var md5DigestLength = 16
15 | var sha1DigestLength = 20
16 |
17 | public class P7Socket: NSObject {
18 | public enum Serialization:Int {
19 | case XML = 0
20 | case BINARY = 1
21 | }
22 |
23 | public enum Compression:UInt32 {
24 | case NONE = 999
25 | case DEFLATE = 0
26 | }
27 |
28 | public enum Checksum:UInt32 {
29 | case NONE = 999
30 | case SHA1 = 0
31 | // case SHA256 = 1
32 | }
33 |
34 | public enum CipherType:UInt32 {
35 | case NONE = 999
36 | case RSA_AES_128 = 0
37 | case RSA_AES_192 = 1
38 | case RSA_AES_256 = 2
39 | case RSA_BF_128 = 3
40 | case RSA_3DES_192 = 4
41 |
42 | public static func pretty(_ type:CipherType) -> String {
43 | switch type {
44 | case .NONE:
45 | return "None"
46 | case .RSA_AES_128:
47 | return "AES/128 bits"
48 | case .RSA_AES_192:
49 | return "AES/192 bits"
50 | case .RSA_AES_256:
51 | return "AES/256 bits"
52 | case .RSA_BF_128:
53 | return "BF/128 bits"
54 | default:
55 | return "None"
56 | }
57 | }
58 | }
59 |
60 |
61 | public var hostname: String!
62 | public var port: Int!
63 | public var spec: P7Spec!
64 | public var username: String = "guest"
65 | public var password: String!
66 | public var serialization: Serialization = .BINARY
67 | public var compression: Compression = .NONE
68 | public var cipherType: CipherType = .RSA_AES_256
69 | public var checksum: Checksum = .NONE
70 | public var sslCipher: P7Cipher!
71 | public var timeout: Int = 10
72 | public var errors: [WiredError] = []
73 |
74 | public var compressionEnabled: Bool = false
75 | public var compressionConfigured: Bool = false
76 |
77 | public var encryptionEnabled: Bool = false
78 | public var checksumEnabled: Bool = false
79 | public var checksumLength: Int = sha1DigestLength
80 |
81 | public var localCompatibilityCheck: Bool = false
82 | public var remoteCompatibilityCheck: Bool = false
83 |
84 | public var remoteVersion: String!
85 | public var remoteName: String!
86 |
87 | public var connected: Bool = false
88 |
89 | private var socket: Socket!
90 | private var rsa:RSA!
91 |
92 | private var deflateStream:z_stream = zlib.z_stream()
93 | private var inflateStream:z_stream = zlib.z_stream()
94 |
95 | public init(hostname: String, port: Int, spec: P7Spec) {
96 | self.hostname = hostname
97 | self.port = port
98 | self.spec = spec
99 | }
100 |
101 |
102 | public func connect(withHandshake handshake: Bool = true) -> Bool {
103 | do {
104 | self.socket = try Socket(.inet, type: .stream, protocol: .tcp)
105 |
106 | try socket.set(option: .receiveTimeout, TimeValue(seconds: 10, milliseconds: 0, microseconds: 0))
107 | try socket.set(option: .sendTimeout, TimeValue(seconds: 10, milliseconds: 0, microseconds: 0))
108 | try socket.set(option: .receiveBufferSize, 327680)
109 | try socket.set(option: .sendBufferSize, 327680)
110 |
111 | var addr:SocketAddress!
112 |
113 | do {
114 | addr = try socket.addresses(for: self.hostname, port: Port(self.port)).first!
115 | } catch let e {
116 | if let socketError = e as? Socket.Error {
117 | self.errors.append(WiredError(withTitle: "Socket Error", message: socketError.description))
118 | Logger.error(socketError.description)
119 | } else {
120 | self.errors.append(WiredError(withTitle: "Socket Error", message: e.localizedDescription))
121 | Logger.error(e.localizedDescription)
122 | }
123 | return false
124 | }
125 |
126 |
127 | try self.socket.connect(address: addr)
128 |
129 | self.connected = true
130 |
131 | if handshake {
132 | if !self.connectHandshake() {
133 | Logger.error("Handshake failed")
134 | return false
135 | }
136 |
137 | if self.compression != .NONE {
138 | self.configureCompression()
139 | }
140 |
141 | if self.checksum != .NONE {
142 | self.configureChecksum()
143 | }
144 |
145 | if self.cipherType != .NONE {
146 | if !self.connectKeyExchange() {
147 | Logger.error("Key Exchange failed")
148 | return false
149 | }
150 | }
151 |
152 | if self.remoteCompatibilityCheck {
153 | if !self.sendCompatibilityCheck() {
154 | Logger.error("Remote Compatibility Check failed")
155 | return false
156 | }
157 | }
158 |
159 | if self.localCompatibilityCheck {
160 | if !self.receiveCompatibilityCheck() {
161 | Logger.error("Local Compatibility Check failed")
162 | return false
163 | }
164 | }
165 | }
166 | } catch let error {
167 | if let socketError = error as? Socket.Error {
168 | self.errors.append(WiredError(withTitle: "Socket Error", message: socketError.description))
169 | Logger.error(socketError.description)
170 | } else {
171 | self.errors.append(WiredError(withTitle: "Socket Error", message: error.localizedDescription))
172 | Logger.error(error.localizedDescription)
173 | }
174 | return false
175 | }
176 |
177 | return true
178 | }
179 |
180 |
181 |
182 | public func disconnect() {
183 | self.socket.close()
184 |
185 | self.connected = false
186 |
187 | self.compressionEnabled = false
188 | self.compressionConfigured = false
189 |
190 | self.encryptionEnabled = false
191 | self.checksumEnabled = false
192 | self.checksumLength = sha1DigestLength
193 |
194 | self.localCompatibilityCheck = false
195 | self.remoteCompatibilityCheck = false
196 |
197 | self.rsa = nil
198 | }
199 |
200 |
201 |
202 | public func write(_ message: P7Message) -> Bool {
203 | do {
204 | if self.serialization == .XML {
205 | let xml = message.xml()
206 |
207 | if let xmlData = xml.data(using: .utf8) {
208 | try self.socket.write(xmlData.bytes)
209 | }
210 | }
211 | else if self.serialization == .BINARY {
212 | var lengthData = Data()
213 | var messageData = message.bin()
214 | let originalData = messageData
215 |
216 | lengthData.append(uint32: UInt32(messageData.count))
217 |
218 | Logger.info("WRITE [\(self.hash)]: \(message.name!)")
219 | //Logger.debug("\n\(message.xml())\n")
220 |
221 | // deflate
222 | if self.compressionEnabled {
223 | guard let deflatedMessageData = self.deflate(messageData) else {
224 | Logger.error("Cannot deflate data")
225 | return false
226 |
227 | }
228 | messageData = deflatedMessageData
229 | }
230 |
231 | // encryption
232 | if self.encryptionEnabled {
233 | guard let encryptedMessageData = self.sslCipher.encrypt(data: messageData) else {
234 | Logger.error("Cannot encrypt data")
235 | return false
236 | }
237 |
238 | messageData = encryptedMessageData
239 |
240 | lengthData = Data()
241 | lengthData.append(uint32: UInt32(messageData.count))
242 | }
243 |
244 | _ = self.write(lengthData.bytes, maxLength: lengthData.count)
245 | _ = self.write(messageData.bytes, maxLength: messageData.bytes.count)
246 |
247 | // checksum
248 | if self.checksumEnabled {
249 | let checksum = originalData.sha1()
250 |
251 | _ = self.write(checksum.bytes, maxLength: checksum.count)
252 | }
253 | }
254 |
255 | } catch let error {
256 | if let socketError = error as? Socket.Error {
257 | Logger.error(socketError.description)
258 | } else {
259 | Logger.error(error.localizedDescription)
260 | }
261 | }
262 |
263 | return true
264 | }
265 |
266 |
267 |
268 |
269 | public func readMessage() -> P7Message? {
270 | var messageData = Data()
271 | var error:WiredError? = nil
272 |
273 | var lengthBuffer = [Byte](repeating: 0, count: 4)
274 | let bytesRead = self.read(&lengthBuffer, maxLength: 4)
275 |
276 | if bytesRead > 0 {
277 | if self.serialization == .XML {
278 | if let xml = String(bytes: messageData, encoding: .utf8) {
279 | let message = P7Message(withXML: xml, spec: self.spec)
280 |
281 | return message
282 | }
283 | }
284 | else if self.serialization == .BINARY {
285 | if bytesRead >= 4 {
286 | guard let messageLength = Data(lengthBuffer).uint32 else {
287 | error = WiredError(withTitle: "Read Error", message: "Cannot read message length")
288 |
289 | Logger.error(error!)
290 | self.errors.append(error!)
291 |
292 | return nil
293 | }
294 |
295 | do {
296 | messageData = try self.readData(size: Int(messageLength))
297 | } catch let e {
298 | error = WiredError(withTitle: "Read Error", message: "")
299 |
300 | if let socketError = e as? Socket.Error {
301 | error = WiredError(withTitle: "Read Error", message: socketError.description)
302 | } else {
303 | error = WiredError(withTitle: "Read Error", message: e.localizedDescription)
304 | }
305 |
306 | Logger.error(error!)
307 | self.errors.append(error!)
308 |
309 | return nil
310 | }
311 |
312 | // data to message object
313 | if messageData.count > 0 {
314 | // decryption
315 | if self.encryptionEnabled {
316 | guard let decryptedMessageData = self.sslCipher.decrypt(data: messageData) else {
317 | error = WiredError(withTitle: "Read Error", message: "Cannot decrypt data")
318 |
319 | Logger.error(error!)
320 | self.errors.append(error!)
321 |
322 | return nil
323 | }
324 | messageData = decryptedMessageData
325 | }
326 |
327 | // inflate
328 | if self.compressionEnabled {
329 | guard let inflatedMessageData = self.inflate(messageData) else {
330 | error = WiredError(withTitle: "Read Error", message: "Cannot inflate data")
331 |
332 | Logger.error(error!)
333 | self.errors.append(error!)
334 |
335 | return nil
336 |
337 | }
338 | messageData = inflatedMessageData
339 | }
340 |
341 | // checksum
342 | if self.checksumEnabled {
343 | do {
344 | let remoteChecksum = try self.readData(size: self.checksumLength)
345 |
346 | if remoteChecksum.count == 0 { return nil }
347 | if !messageData.sha1().elementsEqual(remoteChecksum) {
348 | Logger.fatal("Checksum failed")
349 | return nil
350 | }
351 | } catch let e {
352 | Logger.error("Checksum error: \(e)")
353 | }
354 | }
355 |
356 | // init response message
357 | let message = P7Message(withData: messageData, spec: self.spec)
358 |
359 | Logger.info("READ [\(self.hash)]: \(message.name!)")
360 | //Logger.debug("\n\(message.xml())\n")
361 |
362 | return message
363 | }
364 | }
365 | else {
366 | Logger.error("Nothing read, abort")
367 | }
368 | }
369 | }
370 |
371 | return nil
372 | }
373 |
374 |
375 |
376 | private func write(_ buffer: Array, maxLength len: Int, timeout:TimeInterval = 1.0) -> Int {
377 | while let available = try? socket.wait(for: .write, timeout: timeout), self.connected == true {
378 | guard available else { continue } // timeout happend, try again
379 |
380 | let n = try? socket.write(buffer, size: len)
381 |
382 | return n ?? 0
383 | }
384 |
385 | return 0
386 | }
387 |
388 |
389 |
390 | private func read(_ buffer: UnsafeMutablePointer, maxLength len: Int, timeout:TimeInterval = 1.0) -> Int {
391 | while let available = try? socket.wait(for: .read, timeout: timeout), self.connected == true {
392 | guard available else { continue } // timeout happend, try again
393 |
394 | let n = try? socket.read(buffer, size: len)
395 |
396 | return n ?? 0
397 | }
398 |
399 | return 0
400 | }
401 |
402 |
403 | // I have pretty much had to rewrite my own read() function here
404 | private func readData(size: Int, timeout:TimeInterval = 1.0) throws -> Data {
405 | while let available = try? socket.wait(for: .read, timeout: timeout), self.connected == true {
406 | guard available else { continue } // timeout happend, try again
407 |
408 | var data = Data()
409 | var readBytes = 0
410 | var nLength = size
411 |
412 | while readBytes < size && nLength > 0 && self.connected == true {
413 | var messageBuffer = [Byte](repeating: 0, count: nLength)
414 |
415 | readBytes += try ing { recv(socket.fileDescriptor, &messageBuffer, nLength, MSG_WAITALL) }
416 | nLength = size - readBytes
417 |
418 | //print("readBytes : \(readBytes)")
419 | //print("nLength : \(nLength)")
420 |
421 | let subdata = Data(bytes: messageBuffer, count: readBytes)
422 | if subdata.count > nLength {
423 | _ = subdata.dropLast(nLength)
424 | }
425 |
426 | //print("subdata : \(subdata.toHex())")
427 |
428 | data.append(subdata)
429 | }
430 |
431 | return data
432 | }
433 |
434 | return Data()
435 | }
436 |
437 |
438 |
439 | public func readOOB(timeout:TimeInterval = 1.0) -> Data? {
440 | var messageData = Data()
441 | var lengthBuffer = [Byte](repeating: 0, count: 4)
442 | let bytesRead = self.read(&lengthBuffer, maxLength: 4, timeout: timeout)
443 |
444 | if bytesRead >= 4 {
445 | guard let messageLength = Data(lengthBuffer).uint32 else {
446 | Logger.error("Cannot read message length")
447 | return nil
448 | }
449 |
450 | messageData = try! self.readData(size: Int(messageLength))
451 |
452 | // data to message object
453 | if messageData.count > 0 {
454 | // decryption
455 | if self.encryptionEnabled {
456 | guard let decryptedMessageData = self.sslCipher.decrypt(data: messageData) else {
457 | Logger.error("Cannot decrypt data")
458 | return messageData
459 | }
460 | messageData = decryptedMessageData
461 | }
462 |
463 | // inflate
464 | if self.compressionEnabled {
465 | guard let inflatedMessageData = self.inflate(messageData) else {
466 | Logger.error("Cannot inflate data")
467 | return nil
468 |
469 | }
470 | messageData = inflatedMessageData
471 | }
472 |
473 | // checksum
474 | if self.checksumEnabled {
475 | do {
476 | let remoteChecksum = try self.readData(size: self.checksumLength)
477 |
478 | if remoteChecksum.count == 0 { return nil }
479 | if !messageData.sha1().elementsEqual(remoteChecksum) {
480 | Logger.fatal("Checksum failed")
481 | return nil
482 | }
483 | } catch let e {
484 | Logger.error("Checksum error: \(e)")
485 | }
486 | }
487 |
488 | return messageData
489 | }
490 | }
491 | else {
492 | Logger.error("Nothing read, abort")
493 | }
494 |
495 | return messageData
496 | }
497 |
498 |
499 |
500 | public func writeOOB(data:Data, timeout:TimeInterval = 1.0) -> Bool {
501 | do {
502 | if self.serialization == .BINARY {
503 | var messageData = data
504 | let originalData = messageData
505 | var lengthData = Data()
506 |
507 | lengthData.append(uint32: UInt32(messageData.count))
508 |
509 | // deflate
510 | if self.compressionEnabled {
511 | guard let deflatedMessageData = self.deflate(messageData) else {
512 | Logger.error("Cannot deflate data")
513 | return false
514 |
515 | }
516 | messageData = deflatedMessageData
517 | }
518 |
519 | // encryption
520 | if self.encryptionEnabled {
521 | guard let encryptedMessageData = self.sslCipher.encrypt(data: messageData) else {
522 | Logger.error("Cannot encrypt data")
523 | return false
524 | }
525 |
526 | messageData = encryptedMessageData
527 |
528 | lengthData = Data()
529 | lengthData.append(uint32: UInt32(messageData.count))
530 | }
531 |
532 | _ = try self.socket.write(lengthData.bytes, size: lengthData.count)
533 | _ = try self.socket.write(messageData.bytes, size: messageData.count)
534 |
535 | // checksum
536 | if self.checksumEnabled {
537 | let checksum = originalData.sha1()
538 |
539 | _ = self.write(checksum.bytes, maxLength: checksum.count)
540 | }
541 | }
542 | } catch let error {
543 | if let socketError = error as? Socket.Error {
544 | Logger.error(socketError.description)
545 | } else {
546 | Logger.error(error.localizedDescription)
547 | }
548 | return false
549 | }
550 |
551 | return true
552 | }
553 |
554 |
555 | private func connectHandshake() -> Bool {
556 | var message = P7Message(withName: "p7.handshake.client_handshake", spec: self.spec)
557 | message.addParameter(field: "p7.handshake.version", value: "1.0")
558 | message.addParameter(field: "p7.handshake.protocol.name", value: "Wired")
559 | message.addParameter(field: "p7.handshake.protocol.version", value: "2.0b55")
560 |
561 | // handshake settings
562 | if self.serialization == .BINARY {
563 | if self.cipherType != .NONE {
564 | message.addParameter(field: "p7.handshake.encryption", value: self.cipherType.rawValue)
565 | }
566 |
567 | if self.compression != .NONE {
568 | message.addParameter(field: "p7.handshake.compression", value: self.compression.rawValue)
569 | }
570 |
571 | if self.checksum != .NONE {
572 | message.addParameter(field: "p7.handshake.checksum", value: self.checksum.rawValue)
573 | }
574 | }
575 |
576 | _ = self.write(message)
577 |
578 | guard let response = self.readMessage() else {
579 | Logger.error("Handshake Failed: Should Receive Message: p7.handshake.server_handshake")
580 | return false
581 | }
582 |
583 | if response.name != "p7.handshake.server_handshake" {
584 | Logger.error("Handshake Failed: Unexpected message \(response.name!) instead of p7.handshake.server_handshake")
585 | }
586 |
587 | guard let p7Version = response.string(forField: "p7.handshake.version") else {
588 | Logger.error("Handshake Failed: Built-in protocol version field is missing (p7.handshake.version)")
589 | return false
590 | }
591 |
592 | if p7Version != spec.builtinProtocolVersion {
593 | Logger.error("Handshake Failed: Local version is \(p7Version) but remote version is \(spec.builtinProtocolVersion!)")
594 | return false
595 | }
596 |
597 | self.remoteName = response.string(forField: "p7.handshake.protocol.name")
598 | self.remoteVersion = response.string(forField: "p7.handshake.protocol.version")
599 |
600 | self.localCompatibilityCheck = !spec.isCompatibleWithProtocol(withName: self.remoteName, version: self.remoteVersion)
601 |
602 | if self.serialization == .BINARY {
603 | if let comp = response.enumeration(forField: "p7.handshake.compression") {
604 | self.compression = P7Socket.Compression(rawValue: comp)!
605 | }
606 | if let cip = response.enumeration(forField: "p7.handshake.encryption") {
607 | self.cipherType = P7Socket.CipherType(rawValue: cip)!
608 | }
609 | if let chs = response.enumeration(forField: "p7.handshake.checksum") {
610 | self.checksum = P7Socket.Checksum(rawValue: chs)!
611 | }
612 | }
613 |
614 | if let bool = response.bool(forField: "p7.handshake.compatibility_check") {
615 | self.remoteCompatibilityCheck = bool
616 | }
617 |
618 | message = P7Message(withName: "p7.handshake.acknowledge", spec: self.spec)
619 |
620 | if self.localCompatibilityCheck {
621 | message.addParameter(field: "p7.handshake.compatibility_check", value: true)
622 | }
623 |
624 | _ = self.write(message)
625 |
626 | return true
627 | }
628 |
629 |
630 | private func connectKeyExchange() -> Bool {
631 | guard let response = self.readMessage() else {
632 | Logger.error("Handshake Failed: cannot read server key")
633 | return false
634 | }
635 |
636 | if response.name != "p7.encryption.server_key" {
637 | Logger.error("Message should be 'p7.encryption.server_key', not '\(response.name!)'")
638 | }
639 |
640 | guard let publicRSAKeyData = response.data(forField: "p7.encryption.public_key") else {
641 | Logger.error("Message has no 'p7.encryption.public_key' field")
642 | return false
643 | }
644 |
645 | self.rsa = RSA(publicKey: publicRSAKeyData)
646 |
647 | if self.rsa == nil {
648 | Logger.error("Public key cannot be created")
649 | return false
650 | }
651 |
652 | self.sslCipher = P7Cipher(cipher: self.cipherType)
653 |
654 | if self.sslCipher == nil {
655 | Logger.error("Cipher cannot be created")
656 | return false
657 | }
658 |
659 | if self.password == nil || self.password == "" {
660 | self.password = "".sha1()
661 | } else {
662 | self.password = self.password.sha1()
663 | }
664 |
665 | let passwordData = self.password.data(using: .utf8)!
666 |
667 | var clientPassword1 = passwordData
668 | clientPassword1.append(publicRSAKeyData)
669 | clientPassword1 = clientPassword1.sha1().toHexString().data(using: .utf8)!
670 |
671 | var clientPassword2 = publicRSAKeyData
672 | clientPassword2.append(passwordData)
673 | clientPassword2 = clientPassword2.sha1().toHexString().data(using: .utf8)!
674 |
675 | let message = P7Message(withName: "p7.encryption.client_key", spec: self.spec)
676 |
677 | guard let encryptedCipherKey = self.encryptData(self.sslCipher!.cipherKey.data(using: .utf8)!) else {
678 | return false
679 | }
680 |
681 | guard let encryptedCipherIV = self.encryptData(Data(self.sslCipher!.cipherIV)) else {
682 | return false
683 | }
684 |
685 | guard let d = self.username.data(using: .utf8), let encryptedUsername = self.encryptData(d) else {
686 | return false
687 | }
688 |
689 | guard let encryptedClientPassword1 = self.encryptData(clientPassword1) else {
690 | return false
691 | }
692 |
693 | message.addParameter(field: "p7.encryption.cipher.key", value: encryptedCipherKey)
694 | message.addParameter(field: "p7.encryption.cipher.iv", value: encryptedCipherIV)
695 | message.addParameter(field: "p7.encryption.username", value: encryptedUsername)
696 | message.addParameter(field: "p7.encryption.client_password", value: encryptedClientPassword1)
697 |
698 | _ = self.write(message)
699 |
700 | guard let response2 = self.readMessage() else {
701 | Logger.error("Cannot read p7.encryption.client_key message")
702 | return false
703 | }
704 |
705 | if response2.name == "p7.encryption.authentication_error" {
706 | Logger.error("Authentification failed for '\(self.username)'")
707 | return false
708 | }
709 |
710 | if response2.name != "p7.encryption.acknowledge" {
711 | Logger.error("Message should be 'p7.encryption.acknowledge', not '\(response2.name!)'")
712 | return false
713 | }
714 |
715 | guard let encryptedServerPasswordData = response2.data(forField: "p7.encryption.server_password") else {
716 | Logger.error("Message has no 'p7.encryption.server_password' field")
717 | return false
718 | }
719 |
720 | if let serverPasswordData = self.sslCipher.decrypt(data: encryptedServerPasswordData) {
721 | //print("serverPasswordData : \(serverPasswordData.toHex())")
722 | //print("clientPassword2 : \(clientPassword2.toHex())")
723 |
724 | // TODO: write our own passwords comparison method, this is uggly
725 | if serverPasswordData.toHexString() != clientPassword2.toHexString() {
726 | Logger.error("Password mismatch during key exchange")
727 | return false
728 | }
729 | }
730 |
731 | self.encryptionEnabled = true
732 |
733 | return true
734 | }
735 |
736 |
737 | private func checkPassword(password1: String, password2: String) -> Bool {
738 | return true
739 | }
740 |
741 |
742 |
743 | private func sendCompatibilityCheck() -> Bool {
744 | let message = P7Message(withName: "p7.compatibility_check.specification", spec: self.spec)
745 |
746 | message.addParameter(field: "p7.compatibility_check.specification", value: self.spec.xml!)
747 |
748 | _ = self.write(message)
749 |
750 | guard let response = self.readMessage() else {
751 | return false
752 | }
753 |
754 | if response.name != "p7.compatibility_check.status" {
755 | Logger.error("Message should be 'p7.compatibility_check.status', not '\(response.name!)'")
756 | }
757 |
758 | guard let status = response.bool(forField: "p7.compatibility_check.status") else {
759 | Logger.error("Message has no 'p7.compatibility_check.status' field")
760 | return false
761 | }
762 |
763 | if status == false {
764 | Logger.error("Remote protocol '\(self.remoteName!) \(self.remoteVersion!)' is not compatible with local protocol '\(self.spec.protocolName!) \(self.spec.protocolVersion!)'")
765 | }
766 |
767 | return status
768 | }
769 |
770 |
771 | private func receiveCompatibilityCheck() -> Bool {
772 | return false
773 | }
774 |
775 |
776 |
777 |
778 |
779 | private func encryptData(_ data: Data) -> Data? {
780 | return self.rsa.encrypt(data: data)
781 | }
782 |
783 |
784 | private func configureCompression() {
785 | if self.compression == .DEFLATE {
786 | var err = zlib.deflateInit_(&self.deflateStream, Z_DEFAULT_COMPRESSION, ZLIB_VERSION, Int32(MemoryLayout.size))
787 |
788 | self.deflateStream.data_type = Z_UNKNOWN
789 |
790 | if err != Z_OK {
791 | Logger.error("Cannot init Zlib")
792 |
793 | return
794 | }
795 |
796 | err = zlib.inflateInit_(&self.inflateStream, ZLIB_VERSION, Int32(MemoryLayout.size))
797 |
798 | if err != Z_OK {
799 | Logger.error("Cannot init Zlib")
800 |
801 | return
802 | }
803 |
804 | self.compressionEnabled = true
805 |
806 | } else {
807 | self.compressionEnabled = false
808 | }
809 | }
810 |
811 |
812 | private func configureChecksum() {
813 | if self.checksum == .SHA1 {
814 | self.checksumLength = sha1DigestLength
815 | }
816 |
817 | self.checksumEnabled = true
818 | }
819 |
820 |
821 | // MARK: -
822 | private func inflate(_ data: Data) -> Data? {
823 | // var outData = Data()
824 | // var inData = data
825 | //
826 | //
827 | // print("inflate")
828 | //
829 | // for var multiple in stride(from: 0, to: 16, by: 2) {
830 | // print("multiple: \(multiple)")
831 | //
832 | // let compression_buffer_length = inData.count * (1 << multiple)
833 | //
834 | // print("compression_buffer_length: \(compression_buffer_length)")
835 | //
836 | // var subData = Data(capacity: compression_buffer_length)
837 | //
838 | // inData.withUnsafeBytes { (inputPointer: UnsafeRawBufferPointer) in
839 | // self.inflateStream.next_in = UnsafeMutablePointer(mutating: inputPointer.bindMemory(to: Bytef.self).baseAddress!).advanced(by: Int(self.inflateStream.total_in))
840 | // self.inflateStream.avail_in = uint(inData.count)
841 | // }
842 | //
843 | // subData.withUnsafeMutableBytes { (outputPointer: UnsafeMutableRawBufferPointer) in
844 | // self.inflateStream.next_out = outputPointer.bindMemory(to: Bytef.self).baseAddress!.advanced(by: Int(self.inflateStream.total_out))
845 | // self.inflateStream.avail_out = uInt(outData.count)
846 | // }
847 | //
848 | // let err = zlib.inflate(&self.inflateStream, Z_FINISH)
849 | // let enderr = zlib.inflateReset(&self.inflateStream)
850 | //
851 | // outData.append(Data(bytes: self.inflateStream.next_out, count: Int(self.inflateStream.avail_out)))
852 | //
853 | // print("outData: \(outData.toHex())")
854 | //
855 | // if err == Z_STREAM_END && enderr != Z_BUF_ERROR {
856 | // break
857 | // }
858 | // }
859 | //
860 | // return outData
861 | return data
862 | }
863 |
864 | private func deflate(_ data: Data) -> Data? {
865 |
866 | // var inData = data
867 | // let length = (inData.count * 2) + 16
868 | // var outData = Data(capacity: length)
869 | //
870 | //
871 | // var stream = zlib.z_stream()
872 | //
873 | // stream.data_type = Z_UNKNOWN
874 | //
875 | // inData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in
876 | // stream.next_in = bytes
877 | // }
878 | // stream.avail_in = UInt32(inData.count)
879 | //
880 | // outData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) in
881 | // stream.next_out = bytes
882 | // }
883 | // stream.avail_out = UInt32(outData.count)
884 | //
885 | // let err = zlib.deflate(&stream, Z_FINISH)
886 | // let enderr = zlib.deflateReset(&stream)
887 | //
888 | // print("deflate err : \(err)")
889 | // print("deflate err : \(err)")
890 | //
891 | // if (err != Z_STREAM_END) {
892 | // if (err == Z_OK) {
893 | // print("Deflate Z_BUF_ERROR")
894 | // }
895 | //
896 | // return nil;
897 | // }
898 | //
899 | // if (enderr != Z_OK) {
900 | // print("Deflate not Z_OK")
901 | // return nil;
902 | // }
903 | //
904 | // print("outData : \(outData.toHex())")
905 | //
906 | // return outData
907 | return data
908 | }
909 |
910 | }
911 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/Network/Url.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Url.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 18/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Url: NSObject {
12 | public var base: String = ""
13 | public var scheme: String = Wired.wiredScheme
14 | public var login: String = "guest"
15 | public var password: String = ""
16 | public var hostname: String = ""
17 | public var port: Int = Wired.wiredPort
18 |
19 |
20 | public init(withString baseString: String) {
21 | super.init()
22 |
23 | self.base = baseString
24 |
25 | self.decompose()
26 | }
27 |
28 |
29 | public func urlString() -> String {
30 | return "wired://\(self.hostname):\(self.port)"
31 | }
32 |
33 |
34 | private func decompose() {
35 | if let u = URL(string: self.base) {
36 | self.hostname = u.host!
37 | self.port = u.port ?? Wired.wiredPort
38 | self.login = u.user ?? "guest"
39 | self.password = u.password ?? ""
40 | self.scheme = u.scheme!
41 | } else {
42 | Logger.error("ERROR: Invalid URL")
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/P7/P7Message.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Message.swift
3 | // Wired 3
4 | //
5 | // Created by Rafael Warnault on 18/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import AEXML
11 |
12 |
13 | /**
14 | This class handles messages of the Wired protocol. A message can
15 | be created empty for specification name, loaded from XML string or
16 | serialized Data object. The class provides a set of tools to manipulate
17 | messages and their data in those various formats.
18 |
19 | @author Rafaël Warnault (mailto:dev@read-write.fr)
20 | */
21 | public class P7Message: NSObject {
22 | public var id: String!
23 | public var name: String!
24 | public var spec: P7Spec!
25 | public var specMessage: SpecMessage!
26 | public var size:Int = 0
27 |
28 | private var parameters: [String:Any] = [String:Any](minimumCapacity: 50)
29 |
30 | public var numberOfParameters:Int {
31 | get {
32 | return self.parameters.count
33 | }
34 | }
35 | public var parameterKeys:[String] {
36 | get {
37 | return Array(self.parameters.keys)
38 | }
39 | }
40 |
41 |
42 | public init(withName name: String, spec: P7Spec) {
43 | if let specMessage = spec.messagesByName[name] {
44 | self.specMessage = specMessage
45 | self.id = specMessage.attributes["id"] as? String
46 | self.name = name
47 | self.spec = spec
48 | }
49 | }
50 |
51 |
52 | public init(withXML xml: String, spec: P7Spec) {
53 | super.init()
54 |
55 | self.spec = spec
56 |
57 | self.loadXMLMessage(xml)
58 | }
59 |
60 |
61 | public init(withData data: Data, spec: P7Spec) {
62 | super.init()
63 |
64 | self.spec = spec
65 | self.size = data.count
66 |
67 | self.loadBinaryMessage(data)
68 | }
69 |
70 |
71 | public func addParameter(field: String, value: Any?) {
72 | self.parameters[field] = value
73 | }
74 |
75 |
76 | public func lazy(field: String) -> String? {
77 | let value = self.parameters[field]
78 |
79 | if spec.fieldsByName[field]?.type == .string ||
80 | spec.fieldsByName[field]?.type == .uuid {
81 | if let string = value as? String {
82 | return string
83 | }
84 | }
85 | else if spec.fieldsByName[field]?.type == .int32 ||
86 | spec.fieldsByName[field]?.type == .uint32 {
87 | if let val = value as? UInt32 {
88 | return String(val)
89 | }
90 | }
91 | else if spec.fieldsByName[field]?.type == .int64 ||
92 | spec.fieldsByName[field]?.type == .uint64 {
93 | if let val = value as? UInt64 {
94 | return String(val)
95 | }
96 | }
97 | else if spec.fieldsByName[field]?.type == .data {
98 | if let val = value as? Data {
99 | return val.toHex()
100 | }
101 | }
102 | else if spec.fieldsByName[field]?.type == .oobdata {
103 | if let val = value as? Data {
104 | return val.toHex()
105 | }
106 | }
107 | // TODO: complete all types
108 | return nil
109 | }
110 |
111 |
112 | public func string(forField field: String) -> String? {
113 | if let str = self.parameters[field] as? String {
114 | return str.trimmingCharacters(in: .controlCharacters)
115 | }
116 | return nil
117 | }
118 |
119 | public func uuid(forField field: String) -> String? {
120 | if let str = self.parameters[field] as? String {
121 | return str
122 | }
123 | return nil
124 | }
125 |
126 |
127 | public func data(forField field: String) -> Data? {
128 | if let data = self.parameters[field] as? Data {
129 | return data
130 | }
131 | return nil
132 | }
133 |
134 |
135 | public func date(forField field: String) -> Date? {
136 | if let value = self.parameters[field] as? Double {
137 | return Date(timeIntervalSince1970: value)
138 | }
139 | return nil
140 | }
141 |
142 |
143 | public func bool(forField field: String) -> Bool? {
144 | if let value = self.parameters[field] as? UInt8 {
145 | return value == 1 ? true : false
146 | }
147 | return nil
148 | }
149 |
150 |
151 | public func uint32(forField field: String) -> UInt32? {
152 | if let value = self.parameters[field] as? UInt32 {
153 | return value
154 | }
155 | return nil
156 | }
157 |
158 | public func uint64(forField field: String) -> UInt64? {
159 | if let value = self.parameters[field] as? Data {
160 | return value.uint64
161 | }
162 | return nil
163 | }
164 |
165 | public func enumeration(forField field: String) -> UInt32? {
166 | if let value = self.parameters[field] as? UInt32 {
167 | return value
168 | }
169 | return nil
170 | }
171 |
172 |
173 | public func xml() -> String {
174 | let message = AEXMLDocument()
175 | let root = message.addChild(name: "p7:message", attributes: ["name": self.name] )
176 |
177 | for (field, value) in self.parameters {
178 | let p = root.addChild(name: "p7:field", attributes: ["name": field])
179 |
180 | if spec.fieldsByName[field]?.type == .string ||
181 | spec.fieldsByName[field]?.type == .uuid {
182 | if let string = value as? String {
183 | p.value = string
184 | }
185 | }
186 | else if spec.fieldsByName[field]?.type == .int32 ||
187 | spec.fieldsByName[field]?.type == .uint32 {
188 | if let val = value as? UInt32 {
189 | p.value = String(val)
190 | }
191 | }
192 | else if spec.fieldsByName[field]?.type == .int64 ||
193 | spec.fieldsByName[field]?.type == .uint64 {
194 | if let val = value as? UInt64 {
195 | p.value = String(val)
196 | }
197 | }
198 | else if spec.fieldsByName[field]?.type == .data ||
199 | spec.fieldsByName[field]?.type == .oobdata {
200 | if let val = value as? Data {
201 | p.value = val.toHex()
202 | }
203 | }
204 | else if spec.fieldsByName[field]?.type == .date {
205 | if let val = value as? Double {
206 | let dateFormatter = ISO8601DateFormatter()
207 | p.value = dateFormatter.string(from: Date(timeIntervalSince1970: val))
208 | }
209 | }
210 | // TODO: complete all types
211 | }
212 |
213 | return "\(message.xml)"
214 | // return ""
215 | }
216 |
217 |
218 |
219 | public func bin() -> Data {
220 | var data = Data()
221 |
222 | // append message ID
223 | if let messageID = UInt32(self.id) {
224 | data.append(uint32: messageID, bigEndian: true)
225 |
226 | // append fields
227 | for (field, value) in self.parameters {
228 | if let specField = spec.fieldsByName[field] {
229 |
230 | if let fieldIDStr = specField.id {
231 | // append field ID
232 | if let fieldID = UInt32(fieldIDStr) {
233 | data.append(uint32: fieldID, bigEndian: true)
234 |
235 | // append value
236 | if specField.type == .bool { // boolean (1)
237 | let v = (value as? Bool) == true ? UInt8(1) : UInt8(0)
238 | data.append(uint8: v, bigEndian: true)
239 |
240 | } else if specField.type == .enum32 { // enum (4)
241 | data.append(uint32: value as! UInt32, bigEndian: true)
242 |
243 | } else if specField.type == .int32 { // int32 (4)
244 | data.append(uint32: value as! UInt32, bigEndian: true)
245 |
246 | } else if specField.type == .uint32 { // uint32 (4)
247 | data.append(uint32: value as! UInt32, bigEndian: true)
248 |
249 | } else if specField.type == .int64 { // int64 (4)
250 | data.append(uint64: value as! UInt64, bigEndian: true)
251 |
252 | } else if specField.type == .uint64 { // uint64 (4)
253 | data.append(uint64: value as! UInt64, bigEndian: true)
254 |
255 | } else if specField.type == .double { // double (4)
256 | data.append(double: value as! Double, bigEndian: true)
257 |
258 | } else if specField.type == .string { // string (x)
259 | if let str = value as? String {
260 | if let d = str.nullTerminated {
261 | let l = UInt32(d.count)
262 | data.append(uint32: l, bigEndian: true)
263 | data.append(d)
264 | }
265 | }
266 | } else if specField.type == .uuid { // uuid (16)
267 | if let str = value as? String {
268 | var buffer:Array = Array()
269 | if let uuid = NSUUID(uuidString: str) {
270 | uuid.getBytes(&buffer)
271 | data.append(Data(bytes: &buffer, count: 16))
272 | }
273 | }
274 | } else if specField.type == .date { // date (8)
275 |
276 | } else if specField.type == .data { // data (x)
277 | if let d = value as? Data {
278 | let l = UInt32(d.count)
279 | data.append(uint32: l, bigEndian: true)
280 | data.append(d)
281 | }
282 | } else if specField.type == .oobdata { // oobdata (8)
283 | if let d = value as? Data {
284 | data.append(d)
285 | }
286 | } else if specField.type == .list { // list (x)
287 |
288 | }
289 | }
290 | }
291 | }
292 | }
293 | }
294 |
295 | self.size = data.count
296 |
297 | return data
298 | }
299 |
300 |
301 |
302 |
303 | private func loadXMLMessage(_ xml:String) {
304 | print("TODO: reimplement with AEXML")
305 | // do {
306 | // let xmlDoc = try XMLDocument(xmlString: xml)
307 | // let names = try xmlDoc.nodes(forXPath: "//p7:message/@name")
308 | //
309 | // if let name = names.first?.stringValue {
310 | // self.name = name
311 | //
312 | // if let specMessage = spec.messagesByName[name] {
313 | // self.specMessage = specMessage
314 | // self.id = specMessage.id
315 | //
316 | // let nodes = try xmlDoc.nodes(forXPath: "//p7:message/p7:field")
317 | //
318 | // for node in nodes {
319 | // if let element = node as? XMLElement {
320 | // if let attribute = element.attribute(forName: "name"), let attrName = attribute.stringValue {
321 | // self.addParameter(field: attrName, value: element.objectValue)
322 | // }
323 | // }
324 | // }
325 | // } else {
326 | // Logger.error("ERROR: Unknow message")
327 | // }
328 | // } else {
329 | // Logger.error("ERROR: Missing message name")
330 | // }
331 | //
332 | // } catch {
333 | // Logger.error("ERROR: Cannot parse XML message")
334 | // }
335 | }
336 |
337 |
338 |
339 | private func loadBinaryMessage(_ data: Data) {
340 | var offset = 0
341 |
342 | let messageIDData = data.subdata(in: 0..<4)
343 |
344 | offset += 4
345 |
346 | if let v = messageIDData.uint32 {
347 | self.id = String(v)
348 | } else {
349 | Logger.error("ERROR : Cannot read message ID")
350 | return
351 | }
352 |
353 | if let specMessage = spec.messagesByID[Int(messageIDData.uint32!)] {
354 | self.name = specMessage.name!
355 | self.specMessage = specMessage
356 |
357 | var fieldIDData:Data!
358 | var fieldID:Int!
359 |
360 | while offset < data.count {
361 | fieldIDData = data.subdata(in: offset.. 0 {
387 | // read value
388 | let fieldData = data.subdata(in: offset..
82 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | """
210 |
211 |
212 | /**
213 | Init the built-in spec
214 | */
215 |
216 | private override init() {
217 | super.init()
218 |
219 | let data = p7xml.data(using: .utf8)
220 | self.parser = XMLParser(data: data!)
221 | self.parser.delegate = self
222 |
223 | self.parser.parse()
224 |
225 | do {
226 | let builtinDoc = try AEXMLDocument(xml: p7xml)
227 | self.builtinProtocolVersion = builtinDoc.root.attributes["version"]
228 |
229 | } catch {
230 | Logger.error("ERROR: Cannot parse built-in spec, fatal")
231 | }
232 | }
233 |
234 |
235 |
236 | /**
237 | Init a new specification object for a given XML
238 | specification file.
239 |
240 | - Parameters:
241 | - path: The path of your XML specification file
242 |
243 | - Returns: An instance of P7Spec
244 | */
245 | public convenience init(withPath path: String? = nil) {
246 | self.init()
247 |
248 | if let p = path {
249 | self.loadFile(path: p)
250 | } else {
251 | if let p = Bundle(identifier: "fr.read-write.WiredSwift")!.path(forResource: "wired", ofType: "xml") {
252 | self.loadFile(path: p)
253 | }
254 | }
255 | }
256 |
257 | /**
258 | Init a new specification object for a given XML
259 | specification file.
260 |
261 | - Parameters:
262 | - url: The URL of your XML specification file
263 |
264 | - Returns: An instance of P7Spec
265 | */
266 | public convenience init?(withUrl url:URL) {
267 | self.init()
268 |
269 | if !self.loadFile(at: url) {
270 | return nil
271 | }
272 | }
273 |
274 |
275 |
276 | /**
277 | Check whether the given specification name and version
278 | are compatible with the current protocol.
279 |
280 | - Parameters:
281 | - name: The name of your spec (here, Wired)
282 | - version: The version of the given spec
283 |
284 | - Returns: A boolean set to `true` if compatible
285 | */
286 | public func isCompatibleWithProtocol(withName name:String, version: String) -> Bool {
287 | // TODO: check compatibility
288 | return true
289 | }
290 |
291 |
292 | /**
293 | Returns an error for a given P7 Message
294 |
295 | - Parameters:
296 | - message: The error message
297 |
298 | - Returns: An instance of SpecError
299 | */
300 | public func error(forMessage message: P7Message) -> SpecError?{
301 | if let errorID = message.enumeration(forField: "wired.error") {
302 | return errorsByID[Int(errorID)]
303 | }
304 | return nil
305 | }
306 |
307 |
308 |
309 | /**
310 | XMLParser parser method
311 | */
312 | public func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
313 | if elementName == "p7:field" {
314 | self.loadField(attributeDict)
315 | }
316 | else if elementName == "p7:message" {
317 | self.loadMessage(attributeDict)
318 | }
319 | else if elementName == "p7:parameter" {
320 | self.loadParam(attributeDict)
321 | }
322 | else if elementName == "p7:enum" {
323 | if let name = attributeDict["name"] {
324 | if name.starts(with: "wired.error.") {
325 | self.loadError(attributeDict)
326 | }
327 | }
328 | }
329 | }
330 |
331 |
332 |
333 | private func loadError(_ attributes: [String : String]) {
334 | guard let name = attributes["name"] else {
335 | return
336 | }
337 |
338 | guard let value = attributes["value"], let asInt = Int(value) else {
339 | return
340 | }
341 |
342 | let e = SpecError(name: name, spec: self, attributes: attributes)
343 | errors.append(e)
344 | errorsByID[asInt] = e
345 | }
346 |
347 |
348 | private func loadFile(path: String) {
349 | let url = URL(fileURLWithPath: path)
350 |
351 | self.loadFile(at: url)
352 | }
353 |
354 |
355 | @discardableResult
356 | private func loadFile(at url:URL) -> Bool {
357 | do {
358 | self.xml = try String(contentsOf: url, encoding: .utf8)
359 |
360 | self.parser = XMLParser(contentsOf: url)!
361 |
362 | self.parser.delegate = self
363 | self.parser.parse()
364 |
365 | } catch let e {
366 | Logger.error("Cannot load spec at URL: \(e.localizedDescription)")
367 | return false
368 | }
369 | return true
370 | }
371 |
372 |
373 |
374 | private func loadField(_ attributes: [String : String]) {
375 | guard let name = attributes["name"] else {
376 | return
377 | }
378 |
379 | guard let strID = attributes["id"], let fieldID = Int(strID) else {
380 | return
381 | }
382 |
383 | let field = SpecField(name: name, spec: self, attributes: attributes)
384 | self.fields.append(field)
385 | self.fieldsByName[name] = field
386 | self.fieldsByID[fieldID] = field
387 | }
388 |
389 |
390 | private func loadMessage(_ attributes: [String : String]) {
391 | guard let name = attributes["name"] else {
392 | return
393 | }
394 |
395 | guard let strID = attributes["id"], let messageID = Int(strID) else {
396 | return
397 | }
398 |
399 | let message = SpecMessage(name: name, spec: self, attributes: attributes)
400 | self.messages.append(message)
401 |
402 | self.messagesByName[name] = message
403 | self.messagesByID[messageID] = message
404 |
405 | self.currentMessage = message
406 | }
407 |
408 |
409 | private func loadParam(_ attributes: [String : String]) {
410 | guard let fieldName = attributes["field"] else {
411 | return
412 | }
413 |
414 | if let cm = self.currentMessage, let field = self.fieldsByName[fieldName] {
415 | cm.parameters.append(field)
416 | }
417 | }
418 |
419 |
420 | private func loadTransaction() {
421 |
422 | }
423 | }
424 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/P7/P7SpecError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // WiredSwift
4 | //
5 | // Created by Rafael Warnault on 19/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class P7SpecError: P7SpecItem {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/P7/P7SpecField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // WiredSwift
4 | //
5 | // Created by Rafael Warnault on 19/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class P7SpecField: P7SpecItem {
12 | public var type: P7SpecType!
13 | public var required: Bool = false
14 |
15 | public override init(name: String, spec: P7Spec, attributes: [String : Any]) {
16 | super.init(name: name, spec: spec, attributes: attributes)
17 |
18 | if let typeName = attributes["type"] as? String {
19 | self.type = P7SpecType.specType(forString: typeName)
20 | }
21 | }
22 |
23 | public func hasExplicitLength() -> Bool {
24 | return type == .string || type == .data || type == .list
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/P7/P7SpecItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // WiredSwift
4 | //
5 | // Created by Rafael Warnault on 19/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class P7SpecItem : NSObject {
12 | public var spec: P7Spec!
13 | public var name: String!
14 | public var id: String!
15 | public var version: String?
16 | public var attributes: [String : Any] = [:]
17 |
18 | public init(name: String, spec: P7Spec, attributes: [String : Any]) {
19 | self.spec = spec
20 | self.name = name
21 | self.id = attributes["id"] as? String
22 | self.version = attributes["version"] as? String
23 | self.attributes = attributes
24 | }
25 |
26 | public override var description: String {
27 | return "[\(self.id!)] \(self.name!)"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/P7/P7SpecMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // WiredSwift
4 | //
5 | // Created by Rafael Warnault on 19/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class P7SpecMessage: P7SpecItem {
12 | public var parameters : [P7SpecField] = []
13 |
14 | public override init(name: String, spec: P7Spec, attributes: [String : Any]) {
15 | super.init(name: name, spec: spec, attributes: attributes)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/P7/P7SpecType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // WiredSwift
4 | //
5 | // Created by Rafael Warnault on 19/04/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | public enum P7SpecType : UInt32 {
13 | case bool = 1
14 | case enum32 = 2
15 | case int32 = 3
16 | case uint32 = 4
17 | case int64 = 5
18 | case uint64 = 6
19 | case double = 7
20 | case string = 8
21 | case uuid = 9
22 | case date = 10
23 | case data = 11
24 | case oobdata = 12
25 | case list = 13
26 |
27 |
28 | public static func specType(forString: String) -> P7SpecType {
29 | switch forString {
30 | case "bool":
31 | return .bool
32 | case "enum32":
33 | return .enum32
34 | case "int32":
35 | return .int32
36 | case "uint32":
37 | return .uint32
38 | case "int64":
39 | return .int64
40 | case "uint64":
41 | return .uint64
42 | case "double":
43 | return .double
44 | case "string":
45 | return .string
46 | case "uuid":
47 | return .uuid
48 | case "date":
49 | return .date
50 | case "data":
51 | return .data
52 | case "oobdata":
53 | return .oobdata
54 | case "list":
55 | return .list
56 | default:
57 | return .uint32
58 | }
59 | }
60 |
61 | public static func size(forType: P7SpecType) -> Int {
62 | switch forType {
63 | case bool: return 1
64 | case enum32: return 4
65 | case int32: return 4
66 | case uint32: return 4
67 | case int64: return 8
68 | case uint64: return 8
69 | case double: return 8
70 | case uuid: return 16
71 | case date: return 8
72 | case oobdata: return 8
73 |
74 | default:
75 | return 0
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/WiredSwift.h:
--------------------------------------------------------------------------------
1 | //
2 | // Wired.h
3 | // Wired
4 | //
5 | // Created by Rafael Warnault on 17/07/2019.
6 | // Copyright © 2019 Read-Write. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Wired.
12 | FOUNDATION_EXPORT double WiredVersionNumber;
13 |
14 | //! Project version string for Wired.
15 | FOUNDATION_EXPORT const unsigned char WiredVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/de.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Bundle name */
2 | "CFBundleName" = "WiredSwift";
3 |
4 | /* Copyright (human-readable) */
5 | "NSHumanReadableCopyright" = "Copyright © 2019 Read-Write. Alle Rechte vorbehalten.";
6 |
7 |
--------------------------------------------------------------------------------
/Sources/WiredSwift/fr.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Bundle name */
2 | "CFBundleName" = "WiredSwift";
3 |
4 | /* Copyright (human-readable) */
5 | "NSHumanReadableCopyright" = "Copyright © 2019 Read-Write. Alle Rechte vorbehalten.";
6 |
7 |
--------------------------------------------------------------------------------
/Sources/WiredSwift_iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/WiredSwift_iOS/WiredSwift_iOS.h:
--------------------------------------------------------------------------------
1 | //
2 | // WiredSwift_iOS.h
3 | // WiredSwift_iOS
4 | //
5 | // Created by Rafael Warnault on 31/03/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for WiredSwift_iOS.
12 | FOUNDATION_EXPORT double WiredSwift_iOSVersionNumber;
13 |
14 | //! Project version string for WiredSwift_iOS.
15 | FOUNDATION_EXPORT const unsigned char WiredSwift_iOSVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/WiredSwift_iOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/WiredSwift_iOSTests/WiredSwift_iOSTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WiredSwift_iOSTests.swift
3 | // WiredSwift_iOSTests
4 | //
5 | // Created by Rafael Warnault on 31/03/2020.
6 | // Copyright © 2020 Read-Write. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import WiredSwift_iOS
11 |
12 | class WiredSwift_iOSTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import WiredSwiftTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += WiredSwiftTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/WiredSwiftTests/WiredSwiftTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import WiredSwift
3 |
4 | final class WiredSwiftTests: XCTestCase {
5 | let specURL = URL(string: "https://wired.read-write.fr/wired.xml")!
6 | let serverURL = Url(withString: "wired://wired.read-write.fr")
7 | //let serverURL = Url(withString: "wired://localhost")
8 |
9 | func testUrl() {
10 | let url = Url(withString: "wired://guest:password@localhost:4871")
11 |
12 | XCTAssert(url.scheme == "wired")
13 | XCTAssert(url.login == "guest")
14 | XCTAssert(url.password == "password")
15 | XCTAssert(url.hostname == "localhost")
16 | XCTAssert(url.port == 4871)
17 | }
18 |
19 |
20 | func testConnect() {
21 | Logger.setMaxLevel(.VERBOSE)
22 |
23 | guard let spec = P7Spec(withUrl: specURL) else {
24 | XCTFail()
25 | return
26 | }
27 |
28 | let connection = Connection(withSpec: spec, delegate: self)
29 | connection.clientInfoDelegate = self
30 |
31 | XCTAssert(connection.connect(withUrl: serverURL) == true)
32 | }
33 |
34 |
35 |
36 | func testReconnect() {
37 | Logger.setMaxLevel(.VERBOSE)
38 |
39 | guard let spec = P7Spec(withUrl: specURL) else {
40 | XCTFail()
41 | return
42 | }
43 |
44 | let connection = Connection(withSpec: spec, delegate: self)
45 | connection.clientInfoDelegate = self
46 |
47 | if connection.connect(withUrl: serverURL) {
48 | sleep(1)
49 |
50 | connection.disconnect()
51 |
52 | sleep(1)
53 |
54 | _ = connection.reconnect()
55 |
56 | XCTAssert(connection.joinChat(chatID: 1) == true)
57 | }
58 | }
59 |
60 |
61 | func testBlockConnect() {
62 | Logger.setMaxLevel(.VERBOSE)
63 |
64 | guard let spec = P7Spec(withUrl: specURL) else {
65 | XCTFail()
66 | return
67 | }
68 |
69 | let connection = BlockConnection(withSpec: spec, delegate: self)
70 |
71 | if connection.connect(withUrl: serverURL) {
72 | var totalBoards = 0
73 | var loadedBoardThreads = 0
74 | let message = P7Message(withName: "wired.board.get_boards", spec: spec)
75 |
76 | connection.send(message: message, progressBlock: { (response) in
77 | Logger.info("progressBlock: \(response.name!)")
78 |
79 | if response.name == "wired.board.board_list", let board = response.string(forField: "wired.board.board") {
80 | totalBoards += 1
81 |
82 | let message2 = P7Message(withName: "wired.board.get_threads", spec: spec)
83 | message2.addParameter(field: "wired.board.board", value: board)
84 |
85 | connection.send(message: message2, progressBlock: { (response2) in
86 | if let subject = response2.string(forField: "wired.board.subject") {
87 | Logger.info("\(board) > \(subject)")
88 | }
89 | }) { (response2) in
90 | if response2?.name == "wired.board.thread_list.done" {
91 | loadedBoardThreads += 1
92 |
93 | Logger.info("totalBoards: \(totalBoards)")
94 | Logger.info("loadedBoardThreads: \(loadedBoardThreads)")
95 |
96 | if loadedBoardThreads == totalBoards {
97 | Logger.info("LOAD FINISHED")
98 | }
99 | }
100 | }
101 | }
102 | }) { (response) in
103 | if let r = response {
104 | //Logger.info("completionBlock: \(r.name!)")
105 | }
106 | }
107 | }
108 |
109 | // run this test at least 1 minute
110 | let calendar = Calendar.current
111 | let date = calendar.date(byAdding: .minute, value: 1, to: Date())
112 | RunLoop.main.run(until: date!)
113 | }
114 |
115 |
116 | func testUploadFile() {
117 | guard let spec = P7Spec(withUrl: specURL) else {
118 | XCTFail()
119 | return
120 | }
121 |
122 | let connection = Connection(withSpec: spec, delegate: self)
123 | connection.clientInfoDelegate = self
124 | connection.interactive = false
125 |
126 | // create a secondary connection
127 | if (connection.connect(withUrl: serverURL) == false) {
128 |
129 | }
130 | }
131 |
132 |
133 | static var allTests = [
134 | ("testUrl", testUrl),
135 | ("testConnect", testConnect),
136 | ("testUploadFile", testUploadFile),
137 | ]
138 | }
139 |
140 |
141 |
142 | extension WiredSwiftTests: ConnectionDelegate {
143 | func connectionDidReceiveMessage(connection: Connection, message: P7Message) {
144 |
145 | }
146 |
147 | func connectionDidReceiveError(connection: Connection, message: P7Message) {
148 |
149 | }
150 | }
151 |
152 |
153 | extension WiredSwiftTests: ClientInfoDelegate {
154 | func clientInfoApplicationName(for connection: Connection) -> String? {
155 | return "WiredSwiftTests"
156 | }
157 |
158 | func clientInfoApplicationVersion(for connection: Connection) -> String? {
159 | return "1.0"
160 | }
161 |
162 | func clientInfoApplicationBuild(for connection: Connection) -> String? {
163 | return "1"
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/Tests/WiredSwiftTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(WiredSwiftTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/AEXML_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/CryptoSwift_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/CryptorRSA_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/Gzip_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/SocketSwift_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/WiredSwiftTests_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | BNDL
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/WiredSwift_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/system_zlib_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/WiredSwift.xcodeproj/xcshareddata/xcschemes/WiredSwift-Package.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
63 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------