├── .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 | [![Actions Status](https://github.com/nark/WiredSwift/workflows/Swift/badge.svg)](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 | --------------------------------------------------------------------------------