diff --git a/ListenerApp.xcodeproj/project.pbxproj b/ListenerApp.xcodeproj/project.pbxproj index d8b694d..eb03e28 100644 --- a/ListenerApp.xcodeproj/project.pbxproj +++ b/ListenerApp.xcodeproj/project.pbxproj @@ -21,7 +21,9 @@ 9D51565526A36B410075EBC7 /* UDPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51564F26A36B410075EBC7 /* UDPClient.swift */; }; 9D51565626A36B410075EBC7 /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51565026A36B410075EBC7 /* Socket.swift */; }; 9D51565726A36B410075EBC7 /* TCPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51565126A36B410075EBC7 /* TCPClient.swift */; }; - 9D51565F26A36BB90075EBC7 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9D51565E26A36BB90075EBC7 /* LICENSE */; }; + 9D51566526A36F6D0075EBC7 /* BinUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D51566426A36F6C0075EBC7 /* BinUtils.swift */; }; + 9D51567326A36FEC0075EBC7 /* BinUtils.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9D51567226A36FEC0075EBC7 /* BinUtils.LICENSE */; }; + 9D51567E26A370380075EBC7 /* SwiftSocket.LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 9D51567D26A370380075EBC7 /* SwiftSocket.LICENSE */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -65,7 +67,9 @@ 9D51564F26A36B410075EBC7 /* UDPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UDPClient.swift; sourceTree = ""; }; 9D51565026A36B410075EBC7 /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = ""; }; 9D51565126A36B410075EBC7 /* TCPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TCPClient.swift; sourceTree = ""; }; - 9D51565E26A36BB90075EBC7 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 9D51566426A36F6C0075EBC7 /* BinUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BinUtils.swift; sourceTree = ""; }; + 9D51567226A36FEC0075EBC7 /* BinUtils.LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = BinUtils.LICENSE; sourceTree = ""; }; + 9D51567D26A370380075EBC7 /* SwiftSocket.LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SwiftSocket.LICENSE; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -122,6 +126,7 @@ children = ( 9D5155F226A1EF7B0075EBC7 /* ListenerAppApp.swift */, 9D5155F426A1EF7B0075EBC7 /* ContentView.swift */, + 9D51566326A36F530075EBC7 /* BinUtils */, 9D51563626A36AD60075EBC7 /* SwiftSocket */, 9D5155F626A1EF7C0075EBC7 /* Assets.xcassets */, 9D5155FB26A1EF7C0075EBC7 /* Info.plist */, @@ -167,7 +172,7 @@ 9D51563626A36AD60075EBC7 /* SwiftSocket */ = { isa = PBXGroup; children = ( - 9D51565E26A36BB90075EBC7 /* LICENSE */, + 9D51567D26A370380075EBC7 /* SwiftSocket.LICENSE */, 9D51564C26A36B410075EBC7 /* Result.swift */, 9D51565026A36B410075EBC7 /* Socket.swift */, 9D51564B26A36B410075EBC7 /* SwiftSocket.h */, @@ -180,6 +185,15 @@ path = SwiftSocket; sourceTree = ""; }; + 9D51566326A36F530075EBC7 /* BinUtils */ = { + isa = PBXGroup; + children = ( + 9D51567226A36FEC0075EBC7 /* BinUtils.LICENSE */, + 9D51566426A36F6C0075EBC7 /* BinUtils.swift */, + ); + path = BinUtils; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -287,8 +301,9 @@ files = ( 9D51562226A1F0DF0075EBC7 /* LICENSE in Resources */, 9D5155FA26A1EF7C0075EBC7 /* Preview Assets.xcassets in Resources */, - 9D51565F26A36BB90075EBC7 /* LICENSE in Resources */, + 9D51567326A36FEC0075EBC7 /* BinUtils.LICENSE in Resources */, 9D5155F726A1EF7C0075EBC7 /* Assets.xcassets in Resources */, + 9D51567E26A370380075EBC7 /* SwiftSocket.LICENSE in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -340,6 +355,7 @@ 9D51565226A36B410075EBC7 /* Result.swift in Sources */, 9D5155F526A1EF7B0075EBC7 /* ContentView.swift in Sources */, 9D5155F326A1EF7B0075EBC7 /* ListenerAppApp.swift in Sources */, + 9D51566526A36F6D0075EBC7 /* BinUtils.swift in Sources */, 9D51565426A36B410075EBC7 /* ytcpsocket.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ListenerApp/BinUtils/BinUtils.LICENSE b/ListenerApp/BinUtils/BinUtils.LICENSE new file mode 100644 index 0000000..fe3e5a8 --- /dev/null +++ b/ListenerApp/BinUtils/BinUtils.LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Nicolas Seriot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ListenerApp/BinUtils/BinUtils.swift b/ListenerApp/BinUtils/BinUtils.swift new file mode 100644 index 0000000..4da8c6f --- /dev/null +++ b/ListenerApp/BinUtils/BinUtils.swift @@ -0,0 +1,450 @@ +// +// BinUtils.swift +// BinUtils +// +// Created by Nicolas Seriot on 12/03/16. +// Copyright © 2016 Nicolas Seriot. All rights reserved. +// + +import Foundation +import CoreFoundation + +// MARK: protocol UnpackedType + +public protocol Unpackable {} + +extension NSString: Unpackable {} +extension Bool: Unpackable {} +extension Int: Unpackable {} +extension Double: Unpackable {} + +// MARK: protocol DataConvertible + +protocol DataConvertible {} + +extension DataConvertible { + + init?(data: Data) { + guard data.count == MemoryLayout.size else { return nil } + self = data.withUnsafeBytes { $0.load(as: Self.self) } + } + + init?(bytes: [UInt8]) { + let data = Data(bytes) + self.init(data:data) + } + + var data: Data { + var value = self + return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) + } +} + +extension Bool : DataConvertible { } + +extension Int8 : DataConvertible { } +extension Int16 : DataConvertible { } +extension Int32 : DataConvertible { } +extension Int64 : DataConvertible { } + +extension UInt8 : DataConvertible { } +extension UInt16 : DataConvertible { } +extension UInt32 : DataConvertible { } +extension UInt64 : DataConvertible { } + +extension Float32 : DataConvertible { } +extension Float64 : DataConvertible { } + +// MARK: String extension + +extension String { + subscript (from:Int, to:Int) -> String { + return NSString(string: self).substring(with: NSMakeRange(from, to-from)) + } +} + +// MARK: Data extension + +extension Data { + var bytes : [UInt8] { + return [UInt8](self) + } +} + +// MARK: functions + +public func hexlify(_ data:Data) -> String { + + // similar to hexlify() in Python's binascii module + // https://docs.python.org/2/library/binascii.html + + var s = String() + var byte: UInt8 = 0 + + for i in 0 ..< data.count { + NSData(data: data).getBytes(&byte, range: NSMakeRange(i, 1)) + s = s.appendingFormat("%02x", byte) + } + + return s as String +} + +public func unhexlify(_ string:String) -> Data? { + + // similar to unhexlify() in Python's binascii module + // https://docs.python.org/2/library/binascii.html + + let s = string.uppercased().replacingOccurrences(of: " ", with: "") + + let nonHexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEF").inverted + if let range = s.rangeOfCharacter(from: nonHexCharacterSet) { + print("-- found non hex character at range \(range)") + return nil + } + + var data = Data(capacity: s.count / 2) + + for i in stride(from: 0, to:s.count, by:2) { + let byteString = s[i, i+2] + let byte = UInt8(byteString.withCString { strtoul($0, nil, 16) }) + data.append([byte] as [UInt8], count: 1) + } + + return data +} + +func readIntegerType(_ type:T.Type, bytes:[UInt8], loc:inout Int) -> T { + let size = MemoryLayout.size + let sub = Array(bytes[loc..<(loc+size)]) + loc += size + return T(bytes: sub)! +} + +func readFloatingPointType(_ type:T.Type, bytes:[UInt8], loc:inout Int, isBigEndian:Bool) -> T { + let size = MemoryLayout.size + let sub = Array(bytes[loc..<(loc+size)]) + loc += size + let sub_ = isBigEndian ? sub.reversed() : sub + return T(bytes: sub_)! +} + +func isBigEndianFromMandatoryByteOrderFirstCharacter(_ format:String) -> Bool { + + guard let firstChar = format.first else { assertionFailure("empty format"); return false } + + let s = NSString(string: String(firstChar)) + let c = s.substring(to: 1) + + if c == "@" { assertionFailure("native size and alignment is unsupported") } + + if c == "=" || c == "<" { return false } + if c == ">" || c == "!" { return true } + + assertionFailure("format '\(format)' first character must be among '=<>!'") + + return false +} + +// akin to struct.calcsize(fmt) +func numberOfBytesInFormat(_ format:String) -> Int { + + var numberOfBytes = 0 + + var n = 0 // repeat counter + + var mutableFormat = format + + while !mutableFormat.isEmpty { + + let c = mutableFormat.remove(at: mutableFormat.startIndex) + + if let i = Int(String(c)) , 0...9 ~= i { + if n > 0 { n *= 10 } + n += i + continue + } + + if c == "s" { + numberOfBytes += max(n,1) + n = 0 + continue + } + + let repeatCount = max(n,1) + + switch(c) { + + case "@", "<", "=", ">", "!", " ": + () + case "c", "b", "B", "x", "?": + numberOfBytes += 1 * repeatCount + case "h", "H": + numberOfBytes += 2 * repeatCount + case "i", "l", "I", "L", "f": + numberOfBytes += 4 * repeatCount + case "q", "Q", "d": + numberOfBytes += 8 * repeatCount + case "P": + numberOfBytes += MemoryLayout.size * repeatCount + default: + assertionFailure("-- unsupported format \(c)") + } + + n = 0 + } + + return numberOfBytes +} + +func formatDoesMatchDataLength(_ format:String, data:Data) -> Bool { + let sizeAccordingToFormat = numberOfBytesInFormat(format) + let dataLength = data.count + if sizeAccordingToFormat != dataLength { + print("format \"\(format)\" expects \(sizeAccordingToFormat) bytes but data is \(dataLength) bytes") + return false + } + + return true +} + +/* + pack() and unpack() should behave as Python's struct module https://docs.python.org/2/library/struct.html BUT: + - native size and alignment '@' is not supported + - as a consequence, the byte order specifier character is mandatory and must be among "=<>!" + - native byte order '=' assumes a little-endian system (eg. Intel x86) + - Pascal strings 'p' and native pointers 'P' are not supported + */ + +public enum BinUtilsError: Error { + case formatDoesMatchDataLength(format:String, dataSize:Int) + case unsupportedFormat(character:Character) +} + +public func pack(_ format:String, _ objects:[Any], _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) -> Data { + + var objectsQueue = objects + + var mutableFormat = format + + var mutableData = Data() + + var isBigEndian = false + + let firstCharacter = mutableFormat.remove(at: mutableFormat.startIndex) + + switch(firstCharacter) { + case "<", "=": + isBigEndian = false + case ">", "!": + isBigEndian = true + case "@": + assertionFailure("native size and alignment '@' is unsupported'") + default: + assertionFailure("unsupported format chacracter'") + } + + var n = 0 // repeat counter + + while !mutableFormat.isEmpty { + + let c = mutableFormat.remove(at: mutableFormat.startIndex) + + if let i = Int(String(c)) , 0...9 ~= i { + if n > 0 { n *= 10 } + n += i + continue + } + + var o : Any = 0 + + if c == "s" { + o = objectsQueue.remove(at: 0) + + guard let stringData = (o as! String).data(using: .utf8) else { assertionFailure(); return Data() } + var bytes = stringData.bytes + + let expectedSize = max(1, n) + + // pad ... + while bytes.count < expectedSize { bytes.append(0x00) } + + // ... or trunk + if bytes.count > expectedSize { bytes = Array(bytes[0.. [Unpackable] { + + assert(CFByteOrderGetCurrent() == 1 /* CFByteOrderLittleEndian */, "\(#file) assumes little endian, but host is big endian") + + let isBigEndian = isBigEndianFromMandatoryByteOrderFirstCharacter(format) + + if formatDoesMatchDataLength(format, data: data) == false { + throw BinUtilsError.formatDoesMatchDataLength(format:format, dataSize:data.count) + } + + var a : [Unpackable] = [] + + var loc = 0 + + let bytes = data.bytes + + var n = 0 // repeat counter + + var mutableFormat = format + + mutableFormat.remove(at: mutableFormat.startIndex) // consume byte-order specifier + + while !mutableFormat.isEmpty { + + let c = mutableFormat.remove(at: mutableFormat.startIndex) + + if let i = Int(String(c)) , 0...9 ~= i { + if n > 0 { n *= 10 } + n += i + continue + } + + if c == "s" { + let length = max(n,1) + let sub = Array(bytes[loc..CFBundleShortVersionString 1.0 CFBundleVersion - 42 + 46 LSRequiresIPhoneOS NSSpeechRecognitionUsageDescription diff --git a/ListenerApp/SwiftSocket/LICENSE b/ListenerApp/SwiftSocket/SwiftSocket.LICENSE similarity index 100% rename from ListenerApp/SwiftSocket/LICENSE rename to ListenerApp/SwiftSocket/SwiftSocket.LICENSE