diff --git a/.gitignore b/.gitignore index df439e5..d79485f 100755 --- a/.gitignore +++ b/.gitignore @@ -1,53 +1,5 @@ - -# Created by https://www.gitignore.io/api/macos,swift,xcode,swiftpm,carthage,cocoapods -# Edit at https://www.gitignore.io/?templates=macos,swift,xcode,swiftpm,carthage,cocoapods - -### Carthage ### -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build - -### CocoaPods ### -## CocoaPods GitIgnore Template - -# CocoaPods - Only use to conserve bandwidth / Save time on Pushing -# - Also handy if you have a large number of dependant pods -# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE -Pods/ - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Swift ### # Xcode +# # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated @@ -81,32 +33,37 @@ timeline.xctimeline playground.xcworkspace # Swift Package Manager +# # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved .build/ -# Add this line if you want to avoid checking in Xcode SPM integration. -# .swiftpm/xcode # CocoaPods +# # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# # Pods/ +# # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage +# # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts +Carthage/Build # Accio dependency management Dependencies/ .accio/ # fastlane +# # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: @@ -118,35 +75,10 @@ fastlane/screenshots/**/*.png fastlane/test_output # Code Injection +# # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ - -### SwiftPM ### -Packages -xcuserdata -*.xcodeproj - - -### Xcode ### -# Xcode -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## User settings - -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) - -## Xcode Patch -*.xcodeproj/* -!*.xcodeproj/project.pbxproj -!*.xcodeproj/xcshareddata/ -!*.xcworkspace/contents.xcworkspacedata -/*.gcno - -### Xcode Patch ### -**/xcshareddata/WorkspaceSettings.xcsettings - -# End of https://www.gitignore.io/api/macos,swift,xcode,swiftpm,carthage,cocoapods +.DS_Store +.swiftpm diff --git a/README.md b/README.md index 9ea206e..95584f9 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,6 @@ try binary.readBits(quantitiy: 4) ## Example -This shows how easy it is, to break down an [IPv4 header](https://en.wikipedia.org/wiki/IPv4#Header). - ```swift let binary = Binary(bytes: [0b1_1_0_1_1_1_0_0]) | | | | | | | | @@ -47,6 +45,7 @@ let binary = Binary(bytes: [0b1_1_0_1_1_1_0_0]) try binary.bit() // 1 ``` +This shows how easy it is, to break down an [IPv4 header](https://en.wikipedia.org/wiki/IPv4#Header). ```swift let binary = Binary(bytes: [0x1B, 0x44, …]) diff --git a/Sources/BinaryKit/BinaryKit.swift b/Sources/BinaryKit/Binary.swift similarity index 52% rename from Sources/BinaryKit/BinaryKit.swift rename to Sources/BinaryKit/Binary.swift index f798ad1..c57ef23 100644 --- a/Sources/BinaryKit/BinaryKit.swift +++ b/Sources/BinaryKit/Binary.swift @@ -1,15 +1,17 @@ import Foundation -enum BinError: Error { - case outOfBounds - case notString -} - public struct Binary { + /// Stores a reading cursor in bits. + /// All methods starting with `read` will increment the value of `bitCursor`. private var bitCursor: Int + + /// Stores the binary content. var bytesStore: [UInt8] - private let byteSize = 8 + /// Constant with number of bits in a byte (8) + private let byteSize = UInt8.bitWidth + + /// Initialize a new `Binary`. public init(bytes: [UInt8]) { self.bitCursor = 0 self.bytesStore = bytes @@ -36,17 +38,30 @@ public struct Binary { return bitCursor + (bytes * byteSize) } + /// Increments the `bitCursor`-value by the given `bits`. + private mutating func incrementCursorBy(bits: Int) { + bitCursor = incrementedCursorBy(bits: bits) + } + + /// Increments the `bitCursor`-value by the given `bytes`. + private mutating func incrementCursorBy(bytes: Int) { + bitCursor = incrementedCursorBy(bytes: bytes) + } + /// Sets the reading cursor back to its initial value. public mutating func resetCursor() { self.bitCursor = 0 } - // MARK: - Bit + // MARK: - Get + + /// All `get` methods give access to binary data at any given + /// location — without incrementing the internal cursor. - /// Returns the binary value `0` or `1` of the given position. + /// Returns an `UInt8` with the value of 0 or 1 of the given position. public func getBit(index: Int) throws -> UInt8 { guard (0..<(bytesStore.count)).contains(index / byteSize) else { - throw BinError.outOfBounds + throw BinaryError.outOfBounds } let byteCursor = index / byteSize let bitindex = 7 - (index % byteSize) @@ -56,39 +71,18 @@ public struct Binary { /// Returns the `Int`-value of the given range. public mutating func getBits(range: Range) throws -> Int { guard (0...(bytesStore.count * byteSize)).contains(range.endIndex) else { - throw BinError.outOfBounds + throw BinaryError.outOfBounds } return try range.reversed().enumerated().reduce(0) { $0 + Int(try getBit(index: $1.element) << $1.offset) } } - /// Returns the binary value `0` or `1` of the given position and - /// increments the reading cursor by one bit. - public mutating func readBit() throws -> UInt8 { - defer { bitCursor = incrementedCursorBy(bits: 1) } - return try getBit(index: bitCursor) - } - - /// Returns the `Int`-value of the next n-bits (`quantitiy`) - /// and increments the reading cursor by n-bits. - public mutating func readBits(quantitiy: Int) throws -> Int { - guard (0...(bytesStore.count * byteSize)).contains(bitCursor + quantitiy) else { - throw BinError.outOfBounds - } - defer { bitCursor = incrementedCursorBy(bits: quantitiy) } - return try (bitCursor..<(bitCursor + quantitiy)).reversed().enumerated().reduce(0) { - $0 + Int(try getBit(index: $1.element) << $1.offset) - } - } - - // MARK: - Byte - /// Returns the `UInt8`-value of the given `index`. public func getByte(index: Int) throws -> UInt8 { /// Check if `index` is within bounds of `bytes` guard (0..<(bytesStore.count)).contains(index) else { - throw BinError.outOfBounds + throw BinaryError.outOfBounds } return bytesStore[index] } @@ -96,39 +90,95 @@ public struct Binary { /// Returns an `[UInt8]` of the given `range`. public func getBytes(range: Range) throws -> [UInt8] { guard (0...(bytesStore.count)).contains(range.endIndex) else { - throw BinError.outOfBounds + throw BinaryError.outOfBounds } return Array(bytesStore[range]) } - /// Returns the `UInt8`-value of the next byte and increments the reading cursor. + // MARK: - Read + + /// All `read*` methods return the next requested binary data + /// and increment an internal cursor (or reading offset) to + /// the end of the requested data, so the + /// next `read*`-method can continue from there. + + /// Returns an `UInt8` with the value of 0 or 1 of the given + /// position and increments the reading cursor by one bit. + public mutating func readBit() throws -> UInt8 { + let result = try getBit(index: bitCursor) + incrementCursorBy(bits: 1) + return result + } + + /// Returns the `Int`-value of the next n-bits (`quantitiy`) + /// and increments the reading cursor by n-bits. + public mutating func readBits(quantitiy: Int) throws -> Int { + guard (0...(bytesStore.count * byteSize)).contains(bitCursor + quantitiy) else { + throw BinaryError.outOfBounds + } + let result = try (bitCursor..<(bitCursor + quantitiy)).reversed().enumerated().reduce(0) { + $0 + Int(try getBit(index: $1.element) << $1.offset) + } + incrementCursorBy(bits: quantitiy) + return result + } + + /// Returns the `UInt8`-value of the next byte and + /// increments the reading cursor by 1 byte. public mutating func readByte() throws -> UInt8 { let result = try getByte(index: bitCursor / byteSize) - bitCursor = incrementedCursorBy(bytes: 1) + incrementCursorBy(bytes: 1) return result } - /// Returns an `[UInt8]` of the next n-bytes (`quantitiy`) and + /// Returns a `[UInt8]` of the next n-bytes (`quantitiy`) and /// increments the reading cursor by n-bytes. public mutating func readBytes(quantitiy: Int) throws -> [UInt8] { let byteCursor = bitCursor / byteSize - defer { bitCursor = incrementedCursorBy(bytes: quantitiy) } + incrementCursorBy(bytes: quantitiy) return try getBytes(range: byteCursor..<(byteCursor + quantitiy)) } - // MARK: - String + /// Returns a `String` of the next n-bytes (`quantitiy`) and + /// increments the reading cursor by n-bytes. public mutating func readString(quantitiyOfBytes quantitiy: Int, encoding: String.Encoding = .utf8) throws -> String { guard let result = String(bytes: try self.readBytes(quantitiy: quantitiy), encoding: encoding) else { - throw BinError.notString + throw BinaryError.notString } return result } - public mutating func getCharacter() throws -> Character { + /// Returns the next byte as `Character` and + /// increments the reading cursor by 1 byte. + public mutating func readCharacter() throws -> Character { return Character(UnicodeScalar(try readByte())) } + /// Returns the `Bool`-value of the next bit and + /// increments the reading cursor by 1 bit. public mutating func readBool() throws -> Bool { return try readBit() == 1 } + + /// Returns the `UInt8`-value of the next 4 bit and + /// increments the reading cursor by 4 bits. + public mutating func readNibble() throws -> UInt8 { + return UInt8(try readBits(quantitiy: 4)) + } + + // MARK: - Find + + /// Returns indices of given `[UInt8]`. + func indices(of sequence: [UInt8]) -> [Int] { + let size = sequence.count + return bytesStore.indices.dropLast(size - 1).filter { + bytesStore[$0..<($0 + size)].elementsEqual(sequence) + } + } + + /// Returns indices of given `String`. + func indices(of string: String) -> [Int] { + let sequence = [UInt8](string.utf8) + return indices(of: sequence) + } } diff --git a/Sources/BinaryKit/BinaryError.swift b/Sources/BinaryKit/BinaryError.swift new file mode 100644 index 0000000..e50c867 --- /dev/null +++ b/Sources/BinaryKit/BinaryError.swift @@ -0,0 +1,13 @@ +// +// File.swift +// +// +// Created by Devran on 27.09.19. +// + +import Foundation + +enum BinaryError: Error { + case outOfBounds + case notString +} diff --git a/Tests/BinaryKitTests/BinaryKitTests.swift b/Tests/BinaryKitTests/BinaryKitTests.swift index b7463aa..1360e0c 100644 --- a/Tests/BinaryKitTests/BinaryKitTests.swift +++ b/Tests/BinaryKitTests/BinaryKitTests.swift @@ -2,49 +2,6 @@ import XCTest @testable import BinaryKit final class BinaryKitTests: XCTestCase { - // MARK: - Byte - - func testByte() { - let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] - var bin = Binary(bytes: bytes) - - XCTAssertEqual(try bin.readByte(), 173) - XCTAssertEqual(try bin.readByte(), 175) - XCTAssertThrowsError(try bin.readByte()) - bin.resetCursor() - XCTAssertEqual(try bin.readByte(), 173) - XCTAssertEqual(try bin.readByte(), 175) - } - - func testByteIndex() { - let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] - let bin = Binary(bytes: bytes) - - XCTAssertThrowsError(try bin.getByte(index: -1)) - XCTAssertEqual(try bin.getByte(index: 0), 173) - XCTAssertEqual(try bin.getByte(index: 1), 175) - XCTAssertThrowsError(try bin.getByte(index: 2)) - } - - func testBytes() { - let bytes: [UInt8] = [0b1010_1101, 0b1010_1111, 0b1000_1101] - var bin = Binary(bytes: bytes) - - XCTAssertEqual(try bin.readBytes(quantitiy: 1), [173]) - XCTAssertEqual(try bin.readBytes(quantitiy: 2), [175, 141]) - XCTAssertThrowsError(try bin.readBytes(quantitiy: 3)) - bin.resetCursor() - XCTAssertEqual(try bin.readBytes(quantitiy: 1), [173]) - } - - func testBytesRange() { - let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] - let bin = Binary(bytes: bytes) - - XCTAssertEqual(try bin.getBytes(range: 0..<2), [173, 175]) - XCTAssertThrowsError(try bin.getBytes(range: 2..<3)) - } - // MARK: - Bit func testBit() { @@ -109,6 +66,49 @@ final class BinaryKitTests: XCTestCase { XCTAssertThrowsError(try bin.getBits(range: 16..<17)) } + // MARK: - Byte + + func testByte() { + let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] + var bin = Binary(bytes: bytes) + + XCTAssertEqual(try bin.readByte(), 173) + XCTAssertEqual(try bin.readByte(), 175) + XCTAssertThrowsError(try bin.readByte()) + bin.resetCursor() + XCTAssertEqual(try bin.readByte(), 173) + XCTAssertEqual(try bin.readByte(), 175) + } + + func testByteIndex() { + let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] + let bin = Binary(bytes: bytes) + + XCTAssertThrowsError(try bin.getByte(index: -1)) + XCTAssertEqual(try bin.getByte(index: 0), 173) + XCTAssertEqual(try bin.getByte(index: 1), 175) + XCTAssertThrowsError(try bin.getByte(index: 2)) + } + + func testBytes() { + let bytes: [UInt8] = [0b1010_1101, 0b1010_1111, 0b1000_1101] + var bin = Binary(bytes: bytes) + + XCTAssertEqual(try bin.readBytes(quantitiy: 1), [173]) + XCTAssertEqual(try bin.readBytes(quantitiy: 2), [175, 141]) + XCTAssertThrowsError(try bin.readBytes(quantitiy: 3)) + bin.resetCursor() + XCTAssertEqual(try bin.readBytes(quantitiy: 1), [173]) + } + + func testBytesRange() { + let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] + let bin = Binary(bytes: bytes) + + XCTAssertEqual(try bin.getBytes(range: 0..<2), [173, 175]) + XCTAssertThrowsError(try bin.getBytes(range: 2..<3)) + } + // MARK: - Init func testHexInit() { @@ -125,26 +125,62 @@ final class BinaryKitTests: XCTestCase { XCTAssertThrowsError(try bin.readByte()) } - // MARK: - Other Readers + // MARK: - Nibble - func testOtherReaders() { - let bytes: [UInt8] = [104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] + func testNibble() { + let bytes: [UInt8] = [0b1010_1101, 0b1010_1111] + var bin = Binary(bytes: bytes) + + XCTAssertEqual(try bin.readNibble(), 10) + XCTAssertEqual(try bin.readNibble(), 13) + } + + // MARK: - String and Character + + func testStringAndCharacter() { + let bytes: [UInt8] = [104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 0, 255, 0, 100, 0, 9] var bin = Binary(bytes: bytes) XCTAssertEqual(try bin.readString(quantitiyOfBytes: 12), "hello, world") + XCTAssertEqual(try bin.readCharacter(), "!") + XCTAssertThrowsError(try bin.readString(quantitiyOfBytes: 6, encoding: .nonLossyASCII)) + } + + // MARK: - Bool + + func testBool() { + let bytes: [UInt8] = [0b1101_0101] + var bin = Binary(bytes: bytes) + XCTAssertEqual(try bin.readBool(), true) + XCTAssertEqual(try bin.readBool(), true) + XCTAssertEqual(try bin.readBool(), false) + XCTAssertEqual(try bin.readBool(), true) + XCTAssertEqual(try bin.readBool(), false) + } + + // MARK: - Finders + + func testFinders() { + let bytes: [UInt8] = [104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] + let bin = Binary(bytes: bytes) + XCTAssertEqual(bin.indices(of: [111, 44]), [4]) + XCTAssertEqual(bin.indices(of: "l"), [2, 3, 10]) + XCTAssertEqual(bin.indices(of: "wo"), [7]) } // MARK: - static var allTests = [ - ("testByte", testByte), - ("testByteIndex", testByteIndex), - ("testBytes", testBytes), - ("testBytesRange", testBytesRange), ("testBitIndex", testBitIndex), ("testBit", testBit), ("testBits", testBits), ("testBitsRange", testBitsRange), + ("testByte", testByte), + ("testByteIndex", testByteIndex), + ("testBytes", testBytes), + ("testBytesRange", testBytesRange), ("testHexInit", testHexInit), - ("testOtherReaders", testOtherReaders), + ("testNibble", testNibble), + ("testStringAndCharacter", testStringAndCharacter), + ("testFinders", testFinders), ] }