Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Cosmo/BinaryKit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.0.0
Choose a base ref
...
head repository: Cosmo/BinaryKit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Aug 15, 2016

  1. Update README.md

    Cosmo authored Aug 15, 2016
    Copy the full SHA
    bb5acb1 View commit details

Commits on Sep 11, 2016

  1. Update BinaryKit to Swift 3

    Cosmo committed Sep 11, 2016
    Copy the full SHA
    e68fdcb View commit details
  2. Make readingOffset accessible

    Cosmo committed Sep 11, 2016
    Copy the full SHA
    fe96d69 View commit details
  3. Fix README

    Cosmo committed Sep 11, 2016
    Copy the full SHA
    73fd492 View commit details
  4. Add tests

    Cosmo committed Sep 11, 2016
    Copy the full SHA
    88a6d27 View commit details
  5. Merge pull request #1 from Cosmo/swift3

    Swift3
    Cosmo authored Sep 11, 2016
    Copy the full SHA
    15a1d04 View commit details

Commits on Sep 23, 2019

  1. Copy the full SHA
    5d2154f View commit details
  2. Copy the full SHA
    98750e6 View commit details
  3. Update .gitignore

    Cosmo committed Sep 23, 2019
    Copy the full SHA
    574479e View commit details
  4. Update README.md

    Cosmo committed Sep 23, 2019
    Copy the full SHA
    2143bd0 View commit details
  5. Merge pull request #6 from Cosmo/development

    New API and Swift 5 support
    Cosmo authored Sep 23, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    72c0ab8 View commit details

Commits on Sep 26, 2019

  1. Copy the full SHA
    62e1fbb View commit details
  2. Replace "magic number"

    Cosmo committed Sep 26, 2019
    Copy the full SHA
    3ed44fd View commit details
  3. Explain the properties

    Cosmo committed Sep 26, 2019
    Copy the full SHA
    60e3761 View commit details
  4. Copy the full SHA
    c7d53b8 View commit details
  5. Update comments text

    Cosmo committed Sep 26, 2019
    Copy the full SHA
    26a5386 View commit details
  6. Copy the full SHA
    af47563 View commit details
  7. Add readNibble()

    Cosmo committed Sep 26, 2019
    Copy the full SHA
    049a422 View commit details
  8. Copy the full SHA
    39b5ada View commit details
  9. Copy the full SHA
    49e08b6 View commit details
  10. Update .gitignore

    Cosmo committed Sep 26, 2019
    Copy the full SHA
    ec65cb8 View commit details

Commits on Sep 27, 2019

  1. Improve code documentation

    Cosmo committed Sep 27, 2019
    Copy the full SHA
    bfb1b0b View commit details
  2. Move Error enum into own file

    Cosmo committed Sep 27, 2019
    Copy the full SHA
    01ca237 View commit details

Commits on Sep 28, 2019

  1. Update README.md

    Cosmo authored Sep 28, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4d5fbe8 View commit details
  2. Merge pull request #7 from Cosmo/development

    Development
    Cosmo authored Sep 28, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    aa12d37 View commit details

Commits on Sep 29, 2019

  1. Copy the full SHA
    cf63868 View commit details
  2. Update README.md

    Cosmo committed Sep 29, 2019
    Copy the full SHA
    8aa57d3 View commit details
  3. Copy the full SHA
    057fdae View commit details
  4. Merge pull request #8 from Cosmo/development

    Development
    Cosmo authored Sep 29, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7e40443 View commit details

Commits on Oct 1, 2019

  1. Copy the full SHA
    fa20878 View commit details
  2. Set access control for new methods, remove "quantitiy" label on bits …

    …and bytes and remove bool label on writeBool
    Cosmo committed Oct 1, 2019
    Copy the full SHA
    3090624 View commit details
  3. Update README.md

    Cosmo committed Oct 1, 2019
    Copy the full SHA
    41fecd4 View commit details
  4. Merge pull request #9 from Cosmo/development

    Development
    Cosmo authored Oct 1, 2019
    Copy the full SHA
    4dfe66a View commit details

Commits on Oct 3, 2019

  1. Copy the full SHA
    59173d5 View commit details
  2. Publish getters of cursors

    Cosmo committed Oct 3, 2019
    Copy the full SHA
    1dbaaf6 View commit details
  3. Make BinaryError public

    Cosmo committed Oct 3, 2019
    Copy the full SHA
    062849d View commit details

Commits on Oct 4, 2019

  1. Copy the full SHA
    70e13b9 View commit details
  2. Copy the full SHA
    f2a2acd View commit details

Commits on Oct 5, 2019

  1. Copy the full SHA
    701376a View commit details
  2. Revert "holdCursor", fix a bug where getting more than 8 bits were cu…

    …t off, use getBits within readBits
    Cosmo committed Oct 5, 2019
    Copy the full SHA
    ddd7ea1 View commit details

Commits on Oct 8, 2019

  1. Copy the full SHA
    6fc2d7e View commit details
  2. Add travis config

    Cosmo committed Oct 8, 2019
    Copy the full SHA
    e40421c View commit details
  3. Update .travis.yml

    Cosmo committed Oct 8, 2019
    Copy the full SHA
    766852a View commit details
  4. Merge pull request #10 from Cosmo/development

    Integrate Travis-CI + Refactoring
    Cosmo authored Oct 8, 2019
    Copy the full SHA
    c3c68ec View commit details

Commits on Jun 3, 2020

  1. Copy the full SHA
    c79aee2 View commit details
  2. Copy the full SHA
    ea97712 View commit details

Commits on Jun 17, 2020

  1. Merge pull request #17 from aserdobintsev/fix-15

    Fix for mixed reading
    Cosmo authored Jun 17, 2020
    Copy the full SHA
    9af23cd View commit details

Commits on Oct 13, 2020

  1. Add LinuxVerificationTests

    wacumov committed Oct 13, 2020
    Copy the full SHA
    fdc1e56 View commit details
  2. Run all test cases on Linux

    wacumov committed Oct 13, 2020
    Copy the full SHA
    fd11dda View commit details
  3. Use GitHub Actions for CI

    wacumov committed Oct 13, 2020
    Copy the full SHA
    ef67d49 View commit details
24 changes: 24 additions & 0 deletions .github/workflows/UnitTests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: UnitTests

on:
push:
branches:
- master

jobs:
test_on_macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: swift build
- name: Test
run: swift test
test_on_linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: swift build
- name: Test
run: swift test
82 changes: 82 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,84 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
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/

# 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:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
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/
.DS_Store
.swiftpm
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
os:
- osx
language: swift
osx_image: xcode11
install: swift package update
script:
- swift build
- swift test
Binary file removed BinaryKitLogo.png
Binary file not shown.
27 changes: 25 additions & 2 deletions Package.swift
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "BinaryKit"
)
name: "BinaryKit",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "BinaryKit",
targets: ["BinaryKit"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "BinaryKit",
dependencies: []),
.testTarget(
name: "BinaryKitTests",
dependencies: ["BinaryKit"]),
]
)
125 changes: 70 additions & 55 deletions README.md
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,83 +1,98 @@
<img src="https://raw.githubusercontent.com/Cosmo/BinaryKit/master/BinaryKitLogo.png" alt=" text" width="100" />

# BinaryKit
Access bits and bytes directly in Swift.

## Usage
BinaryKit helps you to break down binary data into bits and bytes, easily access specific parts and write data to binary.

Initialize from `NSData`
```swift
let data = NSData(...)
let binary = Binary(data: data)
```
## Access Bytes

or `[UInt8]` bytes array
```swift
let binary = Binary(bytes: [0xDE, 0xAD]) // 1101 1110 1010 1101
```
By using any `read*` method (`readByte()`, `readBytes(quantity:)`, `readBit()`, …), BinaryKit will increment an internal cursor (or reading offset) to the end of the requested bit or byte, so the next `read*` method can continue from there.

```swift
// Read first 4 bits, bit by bit
var binary = Binary(bytes: [0xDE, 0xAD])
print(binary)
Any `get*` method (`getByte(index:)`, `getBytes(range:)`, `getBit(index:)`, …) will give access to binary data at any given location — without incrementing the internal cursor.

let bit0 = binary.next(bits: 1)
print(bit0) // 1
Here are the methods you can call:

let bit1 = binary.next(bits: 1)
print(bit1) // 1
```swift
var binary = Binary(bytes: [0xDE, 0xAD, 0xBE, 0xEF, ])

let bit2 = binary.next(bits: 1)
print(bit2) // 0
// Reads exactly 1 byte and
// increments the cursor by 1 byte
try binary.readByte()

let bit3 = binary.next(bits: 1)
print(bit3) // 1
```
// Reads the next 4 bytes and
// increments the cursor by 4 bytes
try binary.readBytes(4)

```swift
// Read next 4 bits, 2 x 2 bits
let bits4And5 = binary.next(bits: 2)
print(bits4And5) // 3
// Reads the next 1 bit and
// increments the cursor by 1 bit
try binary.readBit()

let bits6And7 = binary.next(bits: 2)
print(bits6And7) // 2
// Reads the next 4 bits and
// increments the cursor by 4 bits
try binary.readBits(4)
```

```swift
// Set reading offset (cursor) back to starting position
binary.readingOffset = 0
```
### Example

```swift
// Read first byte
let nextTwoBytes = binary.next(bytes: 2)
print(nextTwoBytes) // [222, 173]
var binary = Binary(bytes: [0b1_1_0_1_1_1_0_0])
// | | | | | | | |
// | | | | | | | try binary.readBit() // 0
// | | | | | | try binary.readBit() // 0
// | | | | | try binary.readBit() // 1
// | | | | try binary.readBit() // 1
// | | | try binary.readBit() // 1
// | | try binary.readBit() // 0
// | try binary.readBit() // 1
// try binary.readBit() // 1
```

```swift
// Read bit by position
let bit5 = binary.bit(5)
print(bit5) // 1
```
This shows how easy it is, to break down an [IPv4 header](https://en.wikipedia.org/wiki/IPv4#Header).

```swift
// Read byte by position
let byte1 = binary.byte(1)
print(byte1) // 173
var binary = Binary(bytes: [0x1B, 0x44, ])
let version = try binary.readBits(4)
let internetHeaderLength = try binary.readBits(4)
let differentiatedServicesCodePoint = try binary.readBits(6)
let explicitCongestionNotification = try binary.readBits(2)
let totalLength = try binary.readBytes(2)
let identification = try binary.readBytes(2)
let flags = try binary.readBits(4)
let fragmentOffset = try binary.readBits(12)
let timeToLive = try binary.readByte()
let protocolNumber = try binary.readByte()
let headerChecksum = try binary.readBytes(2)
let sourceIpAddress = try binary.readBytes(4)
let destinationIpAddress = try binary.readBytes(4)
...
```

```swift
// Read first 16 bits as Integer
let first16Bits = binary.bits(0, 16) // 57005
print(first16Bits)
```
## Store Bytes

Use the `write*` methods to store different types to binary.

```swift
// Read first two bytes as Integer
let firstTwoBytes = binary.bytes(0, 2) as Int
print(firstTwoBytes) // 57005
var binary = Binary()
binary.writeInt32(1_350_849_546)
binary.writeString("Hello World!")
binary.writeBytes([0xFF, 0xCC, 0x00, 0x01])
binary.writeBool(true)
```

## Contact

* Devran "Cosmo" Uenal
* Twitter: [@maccosmo](http://twitter.com/maccosmo)
* LinkedIn: [devranuenal](https://www.linkedin.com/in/devranuenal)

## Other Projects

* [Clippy](https://github.com/Cosmo/Clippy) — Clippy from Microsoft Office is back and runs on macOS! Written in Swift.
* [GrammaticalNumber](https://github.com/Cosmo/GrammaticalNumber) — Turns singular words to the plural and vice-versa in Swift.
* [HackMan](https://github.com/Cosmo/HackMan) — Stop writing boilerplate code yourself. Let hackman do it for you via the command line.
* [ISO8859](https://github.com/Cosmo/ISO8859) — Convert ISO8859 1-16 Encoded Text to String in Swift. Supports iOS, tvOS, watchOS and macOS.
* [SpriteMap](https://github.com/Cosmo/SpriteMap) — SpriteMap helps you to extract sprites out of a sprite map. Written in Swift.
* [StringCase](https://github.com/Cosmo/StringCase) — Converts String to lowerCamelCase, UpperCamelCase and snake_case. Tested and written in Swift.
* [TinyConsole](https://github.com/Cosmo/TinyConsole) — TinyConsole is a micro-console that can help you log and display information inside an iOS application, where having a connection to a development computer is not possible.

## License

BinaryKit is released under the [MIT License](http://www.opensource.org/licenses/MIT).
78 changes: 0 additions & 78 deletions Sources/BinaryKit.swift

This file was deleted.

338 changes: 338 additions & 0 deletions Sources/BinaryKit/Binary.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
import Foundation

public struct Binary {
/// Returns the bit position of the reading cursor.
/// All methods starting with `read` will increment this value.
public private(set) var readBitCursor: Int

/// Returns the bit position of the writing cursor.
/// All methods starting with `write` will increment this value.
public private(set) var writeBitCursor: Int

/// Returns the stored bytes.
public private(set) var bytesStore: [UInt8]

/// Constant with number of bits in a byte
private let byteSize = UInt8.bitWidth

/// Returns the stored number of bytes.
public var count: Int {
return bytesStore.count
}

/// Creates a new `Binary`.
public init(bytes: [UInt8]) {
self.readBitCursor = 0
self.writeBitCursor = 0
self.bytesStore = bytes
}

/// Creates an empty `Binary`.
public init() {
self.init(bytes: [])
}

/// Creates a new `Binary` with a string of hexadecimal values converted to bytes.
public init?(hexString: String) {
let charsPerByte = 2
let hexBase = 16
let bytes = hexString.chunked(by: charsPerByte).compactMap{ UInt8($0, radix: hexBase) }
guard hexString.count / charsPerByte == bytes.count else {
return nil
}
self.init(bytes: bytes)
}



// MARK: - Cursor

/// Returns an `Int` with the value of `readBitCursor` incremented by `bits`.
private func incrementedReadCursorBy(bits: Int) -> Int {
return readBitCursor + bits
}

/// Returns an `Int` with the value of `readBitCursor` incremented by `bytes`.
private func incrementedReadCursorBy(bytes: Int) -> Int {
return readBitCursor + (bytes * byteSize)
}

/// Increments the `readBitCursor`-value by the given `bits`.
private mutating func incrementReadCursorBy(bits: Int) {
readBitCursor = incrementedReadCursorBy(bits: bits)
}

/// Increments the `readBitCursor`-value by the given `bytes`.
private mutating func incrementReadCursorBy(bytes: Int) {
readBitCursor = incrementedReadCursorBy(bytes: bytes)
}

/// Sets the reading cursor back to its initial value.
public mutating func resetReadCursor() {
self.readBitCursor = 0
}



// MARK: - Get

/// All `get` methods give access to binary data at any given
/// location — without incrementing the internal cursor.

/// Returns an `UInt8` with the value of 0 or 1 of the given position.
public func getBit(index: Int) throws -> UInt8 {
// Check if the request is within bounds
let storeRange = 0..<bytesStore.count
let readByteCursor = index / byteSize
guard storeRange.contains(readByteCursor) else {
throw BinaryError.outOfBounds
}

// Get bit
let byteLastBitIndex = 7
let bitindex = byteLastBitIndex - (index % byteSize)
return (bytesStore[readByteCursor] >> bitindex) & 1
}

/// Returns the `Int`-value of the given range.
public func getBits(range: Range<Int>) throws -> Int {
// Check if the request is within bounds
let storeRange = 0...(bytesStore.count * byteSize)
guard storeRange.contains(range.endIndex) else {
throw BinaryError.outOfBounds
}

// Get bits
return try range.reversed().enumerated().reduce(0) {
let bit = try getBit(index: $1.element)
return $0 + Int(bit) << $1.offset
}
}

/// Returns the `UInt8`-value of the given `index`.
public func getByte(index: Int) throws -> UInt8 {
// Check if the request is within bounds
let storeRange = 0..<bytesStore.count
guard storeRange.contains(index) else {
throw BinaryError.outOfBounds
}

// Get byte
return bytesStore[index]
}

/// Returns an `[UInt8]` of the given `range`.
public func getBytes(range: Range<Int>) throws -> [UInt8] {
// Check if the request is within bounds
let storeRange = 0...bytesStore.count
guard storeRange.contains(range.endIndex) else {
throw BinaryError.outOfBounds
}

// Get bytes
return Array(bytesStore[range])
}



// 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: readBitCursor)
incrementReadCursorBy(bits: 1)
return result
}

/// Returns the `Int`-value of the next n-bits (`quantity`)
/// and increments the reading cursor by n-bits.
public mutating func readBits(_ quantity: Int) throws -> Int {
let range = (readBitCursor..<(readBitCursor + quantity))
let result = try getBits(range: range)
incrementReadCursorBy(bits: quantity)
return result
}

public mutating func readBits(_ quantity: UInt8) throws -> Int {
return try readBits(Int(quantity))
}

/// Returns the `UInt8`-value of the next byte and
/// increments the reading cursor by 1 byte.
public mutating func readByte() throws -> UInt8 {
return UInt8(try readBits(byteSize))
}

/// Returns a `[UInt8]` of the next n-bytes (`quantity`) and
/// increments the reading cursor by n-bytes.
public mutating func readBytes(_ quantity: Int) throws -> [UInt8] {
// Check if the request is within bounds
let range = (readBitCursor..<(readBitCursor + quantity * byteSize))
let storeRange = 0...(bytesStore.count * byteSize)
guard storeRange.contains(range.endIndex) else {
throw BinaryError.outOfBounds
}
return try (0..<quantity).map{ _ in try readByte() }
}

public mutating func readBytes(_ quantity: UInt8) throws -> [UInt8] {
return try readBytes(Int(quantity))
}

/// Returns a `String` of the next n-bytes (`quantity`) and
/// increments the reading cursor by n-bytes.
public mutating func readString(quantityOfBytes quantity: Int, encoding: String.Encoding = .ascii) throws -> String {
guard let result = String(bytes: try self.readBytes(quantity), encoding: encoding) else {
throw BinaryError.notString
}
return result
}

/// 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 {
let NibbleBitWidth = 4
return UInt8(try readBits(NibbleBitWidth))
}

// MARK: Read — Signed Integer

/// Returns an `Int8` and increments the reading cursor by 1 byte.
public mutating func readInt8() throws -> Int8 {
return Int8(bitPattern: try readByte())
}

/// Returns an `Int16` and increments the reading cursor by 2 bytes.
public mutating func readInt16() throws -> Int16 {
let bytes = try readBytes(MemoryLayout<Int16>.size)
return Int16(bitPattern: UInt16(UInt(bytes: bytes)))
}

/// Returns an `Int32` and increments the reading cursor by 4 bytes.
public mutating func readInt32() throws -> Int32 {
let bytes = try readBytes(MemoryLayout<Int32>.size)
return Int32(bitPattern: UInt32(UInt(bytes: bytes)))
}

/// Returns an `Int64` and increments the reading cursor by 8 bytes.
public mutating func readInt64() throws -> Int64 {
let bytes = try readBytes(MemoryLayout<Int64>.size)
return Int64(bitPattern: UInt64(UInt(bytes: bytes)))
}

// MARK: Read - Unsigned Integer

/// Returns an `UInt8` and increments the reading cursor by 1 byte.
public mutating func readUInt8() throws -> UInt8 {
return try readByte()
}

/// Returns an `UInt16` and increments the reading cursor by 2 bytes.
public mutating func readUInt16() throws -> UInt16 {
let bytes = try readBytes(MemoryLayout<UInt16>.size)
return UInt16(UInt(bytes: bytes))
}

/// Returns an `UInt32` and increments the reading cursor by 4 bytes.
public mutating func readUInt32() throws -> UInt32 {
let bytes = try readBytes(MemoryLayout<UInt32>.size)
return UInt32(UInt(bytes: bytes))
}

/// Returns an `UInt64` and increments the reading cursor by 8 bytes.
public mutating func readUInt64() throws -> UInt64 {
let bytes = try readBytes(MemoryLayout<UInt64>.size)
return UInt64(UInt(bytes: bytes))
}



// MARK: - Find

/// Returns indices of given `[UInt8]`.
public 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`.
public func indices(of string: String) -> [Int] {
let sequence = [UInt8](string.utf8)
return indices(of: sequence)
}



// MARK: - Write

/// Writes a byte (`UInt8`) to `Binary`.
public mutating func writeByte(_ byte: UInt8) {
bytesStore.append(byte)
}

/// Writes bytes (`[UInt8]`) to `Binary`.
public mutating func writeBytes(_ bytes: [UInt8]) {
bytesStore.append(contentsOf: bytes)
}

/// Writes a bit (`UInt8`) to `Binary`.
public mutating func writeBit(bit: UInt8) {
let byte: UInt8 = bit << Int(7 - (writeBitCursor % byteSize))
let index = writeBitCursor / byteSize

if bytesStore.count == index {
bytesStore.append(byte)
} else {
let oldByte = bytesStore[index]
let newByte = oldByte ^ byte
bytesStore[index] = newByte
}

writeBitCursor = writeBitCursor + 1
}

/// Writes a `Bool` as a bit to `Binary`.
public mutating func writeBool(_ bool: Bool) {
writeBit(bit: bool ? 1 : 0)
}

/// Writes a `String` to `Binary`.
public mutating func writeString(_ string: String) {
let bytes = [UInt8](string.utf8)
writeBytes(bytes)
}

/// Writes an `FixedWidthInteger` (`Int`, `UInt8`, `Int8`, `UInt16`, `Int16`, …) to `Binary`.
public mutating func writeInt<T: FixedWidthInteger>(_ int: T) {
bytesStore.append(contentsOf: int.bytes)
}
}


extension Binary {
mutating func readSignedBits(_ quantity: UInt8) throws -> Int {
let multiplicationFactor = (try readBit() == 1) ? -1 : 1
let value = try readBits(quantity - 1)
return value * multiplicationFactor
}
}
11 changes: 11 additions & 0 deletions Sources/BinaryKit/BinaryError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// File.swift
//
//
// Created by Devran on 27.09.19.
//

public enum BinaryError: Error {
case outOfBounds
case notString
}
14 changes: 14 additions & 0 deletions Sources/BinaryKit/FixedWidthIntegerExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// FixedWidthIntegerExtensions.swift
//
//
// Created by Devran on 01.10.19.
//

extension FixedWidthInteger {
var bytes: [UInt8] {
return (0..<(bitWidth / UInt8.bitWidth)).map {
UInt8(truncatingIfNeeded: self >> ($0 * UInt8.bitWidth))
}.reversed()
}
}
18 changes: 18 additions & 0 deletions Sources/BinaryKit/StringExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// StringExtensions.swift
//
//
// Created by Devran on 19.09.19.
//

extension String {
internal func chunked(by groupCount: Int) -> [String] {
let startIndex = self.startIndex
return (0..<(self.count / groupCount)).map { (index: Int) -> String in
let offset = index * groupCount
let subStringStartIndex = self.index(startIndex, offsetBy: offset)
let subStringEndIndex = self.index(startIndex, offsetBy: offset + groupCount)
return String(self[subStringStartIndex..<subStringEndIndex])
}
}
}
14 changes: 14 additions & 0 deletions Sources/BinaryKit/UIntExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// UIntExtensions.swift
//
//
// Created by Devran on 01.10.19.
//

extension UInt {
init(bytes: [UInt8]) {
self = bytes.reduce(UInt(0)) { value, byte in
return value << UInt8.bitWidth | UInt(byte)
}
}
}
354 changes: 354 additions & 0 deletions Tests/BinaryKitTests/BinaryKitTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
import XCTest
@testable import BinaryKit

final class BinaryKitTests: XCTestCase {
// MARK: - Bit

func testBit() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111]
var bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 0)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 0)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 0)
XCTAssertEqual(try bin.readBit(), 1)

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 0)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 0)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)

XCTAssertThrowsError(try bin.readBit())

bin.resetReadCursor()

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 0)
}

func testBitIndex() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111]
let bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.getBit(index: 0), 1)
XCTAssertEqual(try bin.getBit(index: 1), 0)
XCTAssertEqual(try bin.getBit(index: 2), 1)
XCTAssertEqual(try bin.getBit(index: 3), 0)
}

func testBits() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111]
var bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.readBits(4), 10)
XCTAssertEqual(try bin.readBits(4), 13)
XCTAssertEqual(try bin.readBits(8), 175)
XCTAssertThrowsError(try bin.readBits(1))
bin.resetReadCursor()
XCTAssertEqual(try bin.readBits(8), 173)
}

func testBitsRange() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111]
let bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.getBits(range: 0..<4), 10)
XCTAssertEqual(try bin.getBits(range: 4..<8), 13)
XCTAssertEqual(try bin.getBits(range: 8..<16), 175)
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.resetReadCursor()
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(1), [173])
XCTAssertEqual(try bin.readBytes(2), [175, 141])
XCTAssertThrowsError(try bin.readBytes(3))
bin.resetReadCursor()
XCTAssertEqual(try bin.readBytes(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: - Mixed Reading

func testMixedReadByte() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111]
var bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readByte(), 91)
XCTAssertEqual(bin.readBitCursor, 9)
}

func testMixedReadBytes() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111, 0b1000_1101]
var bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBytes(2), [UInt8(91),UInt8(95)])
XCTAssertEqual(bin.readBitCursor, 17)
}

func testReadBytesThrowsBeforeReading() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111, 0b1000_1101]
var bin = Binary(bytes: bytes)

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(bin.readBitCursor, 1)
XCTAssertThrowsError(try bin.readBytes(3))
XCTAssertEqual(bin.readBitCursor, 1)
}

func testReadBitsThrowsBeforeReading() {
let bytes: [UInt8] = [0b1010_1101, 0b1010_1111, 0b1000_1101]
var bin = Binary(bytes: bytes)

XCTAssertEqual(bin.readBitCursor, 0)
XCTAssertThrowsError(try bin.readBits(100))
XCTAssertEqual(bin.readBitCursor, 0)
}

// MARK: - Init

func testHexInit() {
let brokenBinary = Binary(hexString: "xFF00F1")
XCTAssertNil(brokenBinary)

let binary = Binary(hexString: "FF00F1")
XCTAssertNotNil(binary)
guard var bin = binary else { return }

XCTAssertEqual(try bin.readByte(), 255)
XCTAssertEqual(try bin.readByte(), 0)
XCTAssertEqual(try bin.readByte(), 241)
XCTAssertThrowsError(try bin.readByte())
}

// MARK: - Nibble

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(quantityOfBytes: 12), "hello, world")
XCTAssertEqual(try bin.readCharacter(), "!")
XCTAssertThrowsError(try bin.readString(quantityOfBytes: 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: - Write

func testWrite() {
var bin = Binary(bytes: [])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [128])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [192])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [224])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [240])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [248])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [252])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [254])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [255])
bin.writeBit(bit: 1)
XCTAssertEqual(bin.bytesStore, [255, 128])
bin.writeByte(7)
XCTAssertEqual(bin.bytesStore, [255, 128, 7])
bin.writeByte(128)
XCTAssertEqual(bin.bytesStore, [255, 128, 7, 128])
bin.writeString("hello world!")
XCTAssertEqual(bin.bytesStore, [255, 128, 7, 128] + [UInt8]("hello world!".utf8))
bin.writeInt(UInt8(2))
bin.writeInt(UInt32(UInt32.max))



XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)

XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)
XCTAssertEqual(try bin.readBit(), 1)

XCTAssertEqual(try bin.readByte(), 128)

XCTAssertEqual(try bin.readByte(), 7)
XCTAssertEqual(try bin.readByte(), 128)
XCTAssertEqual(try bin.readString(quantityOfBytes: 12), "hello world!")
XCTAssertEqual(try bin.readByte(), 2)

XCTAssertEqual(try bin.readByte(), 255)
XCTAssertEqual(try bin.readByte(), 255)
XCTAssertEqual(try bin.readByte(), 255)
XCTAssertEqual(try bin.readByte(), 255)
}

func testWriteInt() {
var bin = Binary(bytes: [])
bin.writeInt(UInt32(2_370_718_244))
bin.writeInt(UInt64(0x4112444912881220))
bin.writeInt(Int8(Int8.max))
bin.writeInt(Int8(Int8.min))
bin.writeInt(Int16(Int16.max))
bin.writeInt(Int16(Int16.min))
bin.writeInt(Int32(Int32.max))
bin.writeInt(Int32(Int32.min))
bin.writeInt(Int64(Int64.max))
bin.writeInt(Int64(Int64.min))

bin.writeInt(UInt8(UInt8.max))
bin.writeInt(UInt8(UInt8.min))
bin.writeInt(UInt16(UInt16.max))
bin.writeInt(UInt16(UInt16.min))
bin.writeInt(UInt32(UInt32.max))
bin.writeInt(UInt32(UInt32.min))
bin.writeInt(UInt64(UInt64.max))
bin.writeInt(UInt64(UInt64.min))


XCTAssertEqual(try bin.readUInt32(), 2_370_718_244)
XCTAssertEqual(try bin.readUInt64(), 0x4112444912881220)

XCTAssertEqual(try bin.readInt8(), Int8.max)
XCTAssertEqual(try bin.readInt8(), Int8.min)
XCTAssertEqual(try bin.readInt16(), Int16.max)
XCTAssertEqual(try bin.readInt16(), Int16.min)
XCTAssertEqual(try bin.readInt32(), Int32.max)
XCTAssertEqual(try bin.readInt32(), Int32.min)
XCTAssertEqual(try bin.readInt64(), Int64.max)
XCTAssertEqual(try bin.readInt64(), Int64.min)

XCTAssertEqual(try bin.readUInt8(), UInt8.max)
XCTAssertEqual(try bin.readUInt8(), UInt8.min)
XCTAssertEqual(try bin.readUInt16(), UInt16.max)
XCTAssertEqual(try bin.readUInt16(), UInt16.min)
XCTAssertEqual(try bin.readUInt32(), UInt32.max)
XCTAssertEqual(try bin.readUInt32(), UInt32.min)
XCTAssertEqual(try bin.readUInt64(), UInt64.max)
XCTAssertEqual(try bin.readUInt64(), UInt64.min)
}

func testLongBits() {
var bin = Binary(bytes: [0x47, 0x11, 0xff, 0x1c])
XCTAssertEqual(try bin.readBits(8), 0x47)
XCTAssertEqual(try bin.readBits(3), 0)
XCTAssertEqual(try bin.readBits(13), 0x11ff)
}

func testSignedBits() {
var binary = Binary(bytes: [0xFF, 0x7F, 0x00, 0xFF, 0x77, 0xFF, 0xFF])
XCTAssertEqual(try binary.readSignedBits(8), -127)
XCTAssertEqual(try binary.readSignedBits(8), 127)
XCTAssertEqual(try binary.readSignedBits(8), 0)
XCTAssertEqual(try binary.readSignedBits(4), -7)
XCTAssertEqual(try binary.readSignedBits(4), -7)
XCTAssertEqual(try binary.readSignedBits(4), 7)
XCTAssertEqual(try binary.readSignedBits(4), 7)
XCTAssertEqual(try binary.readSignedBits(16), -32767)
}

// MARK: -

static var allTests: Linux.TestList<BinaryKitTests> = [
("testBitIndex", testBitIndex),
("testBit", testBit),
("testBits", testBits),
("testBitsRange", testBitsRange),
("testByte", testByte),
("testByteIndex", testByteIndex),
("testBytes", testBytes),
("testBytesRange", testBytesRange),
("testMixedReadByte", testMixedReadByte),
("testMixedReadBytes", testMixedReadBytes),
("testReadBytesThrowsBeforeReading", testReadBytesThrowsBeforeReading),
("testReadBitsThrowsBeforeReading", testReadBitsThrowsBeforeReading),
("testHexInit", testHexInit),
("testNibble", testNibble),
("testStringAndCharacter", testStringAndCharacter),
("testBool", testBool),
("testFinders", testFinders),
("testWrite", testWrite),
("testWriteInt", testWriteInt),
("testLongBits", testLongBits),
("testSignedBits", testSignedBits),
]
}
61 changes: 61 additions & 0 deletions Tests/BinaryKitTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import XCTest

public func allTests() -> [Linux.TestCase] {
return [
Linux.makeTestCase(using: BinaryKitTests.allTests),
]
}

#if canImport(ObjectiveC)
internal final class LinuxVerificationTests: XCTestCase {
func testAllTestsRunOnLinux() {
Linux.testAllTestsRunOnLinux(allTests: allTests())
}
}
#endif

public enum Linux {}

public extension Linux {
typealias TestCase = (testCaseClass: XCTestCase.Type, allTests: TestManifest)
typealias TestManifest = [(String, TestRunner)]
typealias TestRunner = (XCTestCase) throws -> Void
typealias TestList<T: XCTestCase> = [(String, Test<T>)]
typealias Test<T: XCTestCase> = (T) -> () throws -> Void
}

extension Linux {
static func makeTestCase<T: XCTestCase>(using list: TestList<T>) -> TestCase {
let manifest: TestManifest = list.map { name, function in
(name, { type in
try function(type as! T)()
})
}

return (T.self, manifest)
}

#if canImport(ObjectiveC)
static func testAllTestsRunOnLinux(allTests: [Linux.TestCase]) {
for testCase in allTests {
let type = testCase.testCaseClass

let testNames: [String] = type.defaultTestSuite.tests.map { test in
let components = test.name.components(separatedBy: .whitespaces)
return components[1].replacingOccurrences(of: "]", with: "")
}

let linuxTestNames = Set(testCase.allTests.map { $0.0 })

for name in testNames {
if !linuxTestNames.contains(name) {
XCTFail("""
\(type).\(name) does not run on Linux.
Please add it to \(type).allTests.
""")
}
}
}
}
#endif
}
7 changes: 7 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import XCTest

import BinaryKitTests

var tests = [XCTestCaseEntry]()
tests += BinaryKitTests.allTests()
XCTMain(tests)