BinUtils.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. //
  2. // BinUtils.swift
  3. // BinUtils
  4. //
  5. // Copyright © 2016 Nicolas Seriot. All rights reserved.
  6. //
  7. import Foundation
  8. import CoreFoundation
  9. // MARK: protocol UnpackedType
  10. public protocol Unpackable {}
  11. extension NSString: Unpackable {}
  12. extension Bool: Unpackable {}
  13. extension Int: Unpackable {}
  14. extension Double: Unpackable {}
  15. // MARK: protocol DataConvertible
  16. protocol DataConvertible {}
  17. extension DataConvertible {
  18. init?(data: Data) {
  19. guard data.count == MemoryLayout<Self>.size else { return nil }
  20. self = data.withUnsafeBytes { $0.pointee }
  21. }
  22. init?(bytes: [UInt8]) {
  23. let data = Data(bytes)
  24. self.init(data:data)
  25. }
  26. var data: Data {
  27. var value = self
  28. return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
  29. }
  30. }
  31. extension Bool : DataConvertible { }
  32. extension Int8 : DataConvertible { }
  33. extension Int16 : DataConvertible { }
  34. extension Int32 : DataConvertible { }
  35. extension Int64 : DataConvertible { }
  36. extension UInt8 : DataConvertible { }
  37. extension UInt16 : DataConvertible { }
  38. extension UInt32 : DataConvertible { }
  39. extension UInt64 : DataConvertible { }
  40. extension Float32 : DataConvertible { }
  41. extension Float64 : DataConvertible { }
  42. // MARK: String extension
  43. extension String {
  44. subscript (from:Int, to:Int) -> String {
  45. return NSString(string: self).substring(with: NSMakeRange(from, to-from))
  46. }
  47. }
  48. // MARK: Data extension
  49. extension Data {
  50. var bytes : [UInt8] {
  51. return self.withUnsafeBytes {
  52. [UInt8](UnsafeBufferPointer(start: $0, count: self.count))
  53. }
  54. }
  55. }
  56. // MARK: functions
  57. public func hexlify(_ data:Data) -> String {
  58. // similar to hexlify() in Python's binascii module
  59. // https://docs.python.org/2/library/binascii.html
  60. var s = String()
  61. var byte: UInt8 = 0
  62. for i in 0 ..< data.count {
  63. NSData(data: data).getBytes(&byte, range: NSMakeRange(i, 1))
  64. s = s.appendingFormat("%02x", byte)
  65. }
  66. return s as String
  67. }
  68. public func unhexlify(_ string:String) -> Data? {
  69. // similar to unhexlify() in Python's binascii module
  70. // https://docs.python.org/2/library/binascii.html
  71. let s = string.uppercased().replacingOccurrences(of: " ", with: "")
  72. let nonHexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEF").inverted
  73. if let range = s.rangeOfCharacter(from: nonHexCharacterSet) {
  74. print("-- found non hex character at range \(range)")
  75. return nil
  76. }
  77. var data = Data(capacity: s.count / 2)
  78. for i in stride(from: 0, to:s.count, by:2) {
  79. let byteString = s[i, i+2]
  80. let byte = UInt8(byteString.withCString { strtoul($0, nil, 16) })
  81. data.append([byte] as [UInt8], count: 1)
  82. }
  83. return data
  84. }
  85. func readIntegerType<T:DataConvertible>(_ type:T.Type, bytes:[UInt8], loc:inout Int) -> T {
  86. let size = MemoryLayout<T>.size
  87. let sub = Array(bytes[loc..<(loc+size)])
  88. loc += size
  89. return T(bytes: sub)!
  90. }
  91. func readFloatingPointType<T:DataConvertible>(_ type:T.Type, bytes:[UInt8], loc:inout Int, isBigEndian:Bool) -> T {
  92. let size = MemoryLayout<T>.size
  93. let sub = Array(bytes[loc..<(loc+size)])
  94. loc += size
  95. let sub_ = isBigEndian ? sub.reversed() : sub
  96. return T(bytes: sub_)!
  97. }
  98. func isBigEndianFromMandatoryByteOrderFirstCharacter(_ format:String) -> Bool {
  99. guard let firstChar = format.first else { assertionFailure("empty format"); return false }
  100. let s = NSString(string: String(firstChar))
  101. let c = s.substring(to: 1)
  102. if c == "@" { assertionFailure("native size and alignment is unsupported") }
  103. if c == "=" || c == "<" { return false }
  104. if c == ">" || c == "!" { return true }
  105. assertionFailure("format '\(format)' first character must be among '=<>!'")
  106. return false
  107. }
  108. // akin to struct.calcsize(fmt)
  109. func numberOfBytesInFormat(_ format:String) -> Int {
  110. var numberOfBytes = 0
  111. var n = 0 // repeat counter
  112. var mutableFormat = format
  113. while !mutableFormat.isEmpty {
  114. let c = mutableFormat.remove(at: mutableFormat.startIndex)
  115. if let i = Int(String(c)) , 0...9 ~= i {
  116. if n > 0 { n *= 10 }
  117. n += i
  118. continue
  119. }
  120. if c == "s" {
  121. numberOfBytes += max(n,1)
  122. n = 0
  123. continue
  124. }
  125. for _ in 0..<max(n,1) {
  126. switch(c) {
  127. case "@", "<", "=", ">", "!", " ":
  128. ()
  129. case "c", "b", "B", "x", "?":
  130. numberOfBytes += 1
  131. case "h", "H":
  132. numberOfBytes += 2
  133. case "i", "l", "I", "L", "f":
  134. numberOfBytes += 4
  135. case "q", "Q", "d":
  136. numberOfBytes += 8
  137. case "P":
  138. numberOfBytes += MemoryLayout<Int>.size
  139. default:
  140. assertionFailure("-- unsupported format \(c)")
  141. }
  142. }
  143. n = 0
  144. }
  145. return numberOfBytes
  146. }
  147. func formatDoesMatchDataLength(_ format:String, data:Data) -> Bool {
  148. let sizeAccordingToFormat = numberOfBytesInFormat(format)
  149. let dataLength = data.count
  150. if sizeAccordingToFormat != dataLength {
  151. print("format \"\(format)\" expects \(sizeAccordingToFormat) bytes but data is \(dataLength) bytes")
  152. return false
  153. }
  154. return true
  155. }
  156. /*
  157. pack() and unpack() should behave as Python's struct module https://docs.python.org/2/library/struct.html BUT:
  158. - native size and alignment '@' is not supported
  159. - as a consequence, the byte order specifier character is mandatory and must be among "=<>!"
  160. - native byte order '=' assumes a little-endian system (eg. Intel x86)
  161. - Pascal strings 'p' and native pointers 'P' are not supported
  162. */
  163. public enum BinUtilsError: Error {
  164. case formatDoesMatchDataLength(format:String, dataSize:Int)
  165. case unsupportedFormat(character:Character)
  166. }
  167. public func pack(_ format:String, _ objects:[Any], _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) -> Data {
  168. var objectsQueue = objects
  169. var mutableFormat = format
  170. var mutableData = Data()
  171. var isBigEndian = false
  172. let firstCharacter = mutableFormat.remove(at: mutableFormat.startIndex)
  173. switch(firstCharacter) {
  174. case "<", "=":
  175. isBigEndian = false
  176. case ">", "!":
  177. isBigEndian = true
  178. case "@":
  179. assertionFailure("native size and alignment '@' is unsupported'")
  180. default:
  181. assertionFailure("unsupported format chacracter'")
  182. }
  183. var n = 0 // repeat counter
  184. while !mutableFormat.isEmpty {
  185. let c = mutableFormat.remove(at: mutableFormat.startIndex)
  186. if let i = Int(String(c)) , 0...9 ~= i {
  187. if n > 0 { n *= 10 }
  188. n += i
  189. continue
  190. }
  191. var o : Any = 0
  192. if c == "s" {
  193. o = objectsQueue.remove(at: 0)
  194. guard let stringData = (o as! String).data(using: .utf8) else { assertionFailure(); return Data() }
  195. var bytes = stringData.bytes
  196. let expectedSize = max(1, n)
  197. // pad ...
  198. while bytes.count < expectedSize { bytes.append(0x00) }
  199. // ... or trunk
  200. if bytes.count > expectedSize { bytes = Array(bytes[0..<expectedSize]) }
  201. assert(bytes.count == expectedSize)
  202. if isBigEndian { bytes = bytes.reversed() }
  203. mutableData.append(bytes, count: bytes.count)
  204. n = 0
  205. continue
  206. }
  207. for _ in 0..<max(n,1) {
  208. var bytes : [UInt8] = []
  209. if c != "x" {
  210. o = objectsQueue.removeFirst()
  211. }
  212. switch(c) {
  213. case "?":
  214. bytes = (o as! Bool) ? [0x01] : [0x00]
  215. case "c":
  216. let charAsString = (o as! NSString).substring(to: 1)
  217. guard let data = charAsString.data(using: stringEncoding) else {
  218. assertionFailure("cannot decode character \(charAsString) using encoding \(stringEncoding)")
  219. return Data()
  220. }
  221. bytes = data.bytes
  222. case "b":
  223. bytes = Int8(truncatingIfNeeded:o as! Int).data.bytes
  224. case "h":
  225. bytes = Int16(truncatingIfNeeded:o as! Int).data.bytes
  226. case "i", "l":
  227. bytes = Int32(truncatingIfNeeded:o as! Int).data.bytes
  228. case "q", "Q":
  229. bytes = Int64(o as! Int).data.bytes
  230. case "B":
  231. bytes = UInt8(truncatingIfNeeded:o as! Int).data.bytes
  232. case "H":
  233. bytes = UInt16(truncatingIfNeeded:o as! Int).data.bytes
  234. case "I", "L":
  235. bytes = UInt32(truncatingIfNeeded:o as! Int).data.bytes
  236. case "f":
  237. bytes = Float32(o as! Double).data.bytes
  238. case "d":
  239. bytes = Float64(o as! Double).data.bytes
  240. case "x":
  241. bytes = [0x00]
  242. default:
  243. assertionFailure("Unsupported packing format: \(c)")
  244. }
  245. if isBigEndian { bytes = bytes.reversed() }
  246. let data = Data(bytes)
  247. mutableData.append(data)
  248. }
  249. n = 0
  250. }
  251. return mutableData
  252. }
  253. public func unpack(_ format:String, _ data:Data, _ stringEncoding:String.Encoding=String.Encoding.windowsCP1252) throws -> [Unpackable] {
  254. assert(CFByteOrderGetCurrent() == 1 /* CFByteOrderLittleEndian */, "\(#file) assumes little endian, but host is big endian")
  255. let isBigEndian = isBigEndianFromMandatoryByteOrderFirstCharacter(format)
  256. if formatDoesMatchDataLength(format, data: data) == false {
  257. throw BinUtilsError.formatDoesMatchDataLength(format:format, dataSize:data.count)
  258. }
  259. var a : [Unpackable] = []
  260. var loc = 0
  261. let bytes = data.bytes
  262. var n = 0 // repeat counter
  263. var mutableFormat = format
  264. mutableFormat.remove(at: mutableFormat.startIndex) // consume byte-order specifier
  265. while !mutableFormat.isEmpty {
  266. let c = mutableFormat.remove(at: mutableFormat.startIndex)
  267. if let i = Int(String(c)) , 0...9 ~= i {
  268. if n > 0 { n *= 10 }
  269. n += i
  270. continue
  271. }
  272. if c == "s" {
  273. let length = max(n,1)
  274. let sub = Array(bytes[loc..<loc+length])
  275. guard let s = NSString(bytes: sub, length: length, encoding: stringEncoding.rawValue) else {
  276. assertionFailure("-- not a string: \(sub)")
  277. return []
  278. }
  279. a.append(s)
  280. loc += length
  281. n = 0
  282. continue
  283. }
  284. for _ in 0..<max(n,1) {
  285. var o : Unpackable?
  286. switch(c) {
  287. case "c":
  288. let optionalString = NSString(bytes: [bytes[loc]], length: 1, encoding: String.Encoding.utf8.rawValue)
  289. loc += 1
  290. guard let s = optionalString else { assertionFailure(); return [] }
  291. o = s
  292. case "b":
  293. let r = readIntegerType(Int8.self, bytes:bytes, loc:&loc)
  294. o = Int(r)
  295. case "B":
  296. let r = readIntegerType(UInt8.self, bytes:bytes, loc:&loc)
  297. o = Int(r)
  298. case "?":
  299. let r = readIntegerType(Bool.self, bytes:bytes, loc:&loc)
  300. o = r ? true : false
  301. case "h":
  302. let r = readIntegerType(Int16.self, bytes:bytes, loc:&loc)
  303. o = Int(isBigEndian ? Int16(bigEndian: r) : r)
  304. case "H":
  305. let r = readIntegerType(UInt16.self, bytes:bytes, loc:&loc)
  306. o = Int(isBigEndian ? UInt16(bigEndian: r) : r)
  307. case "i":
  308. fallthrough
  309. case "l":
  310. let r = readIntegerType(Int32.self, bytes:bytes, loc:&loc)
  311. o = Int(isBigEndian ? Int32(bigEndian: r) : r)
  312. case "I":
  313. fallthrough
  314. case "L":
  315. let r = readIntegerType(UInt32.self, bytes:bytes, loc:&loc)
  316. o = Int(isBigEndian ? UInt32(bigEndian: r) : r)
  317. case "q":
  318. let r = readIntegerType(Int64.self, bytes:bytes, loc:&loc)
  319. o = Int(isBigEndian ? Int64(bigEndian: r) : r)
  320. case "Q":
  321. let r = readIntegerType(UInt64.self, bytes:bytes, loc:&loc)
  322. o = Int(isBigEndian ? UInt64(bigEndian: r) : r)
  323. case "f":
  324. let r = readFloatingPointType(Float32.self, bytes:bytes, loc:&loc, isBigEndian:isBigEndian)
  325. o = Double(r)
  326. case "d":
  327. let r = readFloatingPointType(Float64.self, bytes:bytes, loc:&loc, isBigEndian:isBigEndian)
  328. o = Double(r)
  329. case "x":
  330. loc += 1
  331. case " ":
  332. ()
  333. default:
  334. throw BinUtilsError.unsupportedFormat(character:c)
  335. }
  336. if let o = o { a.append(o) }
  337. }
  338. n = 0
  339. }
  340. return a
  341. }