-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from orchetect/dev
Added `DataReader`
- Loading branch information
Showing
2 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// | ||
// DataReader.swift | ||
// OTCore • https://github.com/orchetect/OTCore | ||
// | ||
|
||
import Foundation | ||
|
||
/// Utility to facilitate sequential reading of bytes. | ||
public struct DataReader { | ||
|
||
public let base: Data | ||
|
||
public init(_ data: Data) { | ||
|
||
base = data | ||
|
||
} | ||
|
||
/// Current byte index of read position | ||
public internal(set) var readPosition = 0 | ||
|
||
/// Resets read position back to 0 | ||
public mutating func reset() { | ||
|
||
readPosition = 0 | ||
|
||
} | ||
|
||
/// Manually advance by n number of bytes from current read position | ||
public mutating func advanceBy(_ count: Int) { | ||
|
||
readPosition += count | ||
|
||
} | ||
|
||
/// Return the next _n_ number of bytes and increment the read position. | ||
/// | ||
/// If `bytes` parameter is nil, the remainder of the data will be returned. | ||
/// | ||
/// If fewer bytes remain than are requested, `nil` will be returned. | ||
public mutating func read(bytes count: Int? = nil) -> Data? { | ||
|
||
if count == 0 { return Data() } | ||
|
||
if let count = count, | ||
count < 0 { return nil } | ||
|
||
let readPosStartIndex = base.startIndex.advanced(by: readPosition) | ||
|
||
let count = count ?? (base.count - readPosition) | ||
|
||
let endIndex = readPosStartIndex.advanced(by: count - 1) | ||
|
||
guard base.indices.contains(readPosStartIndex), | ||
base.indices.contains(endIndex) else { return nil } | ||
|
||
let returnBytes = base[readPosStartIndex ... endIndex] | ||
|
||
// advance read position | ||
readPosition += count | ||
|
||
return returnBytes | ||
|
||
} | ||
|
||
/// Read n number of bytes from current read position, without advancing read position. | ||
/// If `bytes count` passed is nil, the remainder of the data will be returned. | ||
/// If fewer bytes remain than are requested, `nil` will be returned. | ||
public func nonAdvancingRead(bytes count: Int? = nil) -> Data? { | ||
|
||
if count == 0 { return Data() } | ||
|
||
if let count = count, | ||
count < 0 { return nil } | ||
|
||
let readPosStartIndex = base.startIndex.advanced(by: readPosition) | ||
|
||
let count = count ?? (base.count - readPosition) | ||
|
||
let endIndex = readPosStartIndex.advanced(by: count - 1) | ||
|
||
guard base.indices.contains(readPosStartIndex), | ||
base.indices.contains(endIndex) else { return nil } | ||
|
||
let returnBytes = base[readPosStartIndex ... endIndex] | ||
|
||
return returnBytes | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
// | ||
// DataReader Tests.swift | ||
// OTCore • https://github.com/orchetect/OTCore | ||
// | ||
|
||
#if !os(watchOS) | ||
|
||
import XCTest | ||
@testable import OTCore | ||
|
||
class Abstractions_DataReader_Tests: XCTestCase { | ||
|
||
// MARK: - Data storage starting with index 0 | ||
|
||
func testRead() { | ||
|
||
let data = Data([0x01, 0x02, 0x03, 0x04]) | ||
|
||
// .read - byte by byte | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.readPosition, 0) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x02])) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x03])) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x04])) | ||
XCTAssertEqual(dr.read(bytes: 1), nil) | ||
} | ||
|
||
// .read - nil read - return all remaining bytes | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(), data) | ||
XCTAssertEqual(dr.read(bytes: 1), nil) | ||
} | ||
|
||
// .read - zero count read - return empty data, not nil | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(bytes: 0), Data()) | ||
} | ||
|
||
// .read - read overflow - return nil | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(bytes: 5), nil) | ||
} | ||
|
||
} | ||
|
||
func testNonAdvancingRead() { | ||
|
||
let data = Data([0x01, 0x02, 0x03, 0x04]) | ||
|
||
// .nonAdvancingRead - nil read - return all remaining bytes | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.nonAdvancingRead(), data) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
} | ||
|
||
// .nonAdvancingRead - read byte counts | ||
do { | ||
let dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.nonAdvancingRead(bytes: 1), Data([0x01])) | ||
XCTAssertEqual(dr.nonAdvancingRead(bytes: 2), Data([0x01, 0x02])) | ||
} | ||
|
||
// .nonAdvancingRead - read overflow - return nil | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.nonAdvancingRead(bytes: 5), nil) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
} | ||
|
||
} | ||
|
||
func testAdvanceBy() { | ||
|
||
let data = Data([0x01, 0x02, 0x03, 0x04]) | ||
|
||
// advanceBy | ||
do { | ||
var dr = DataReader(data) | ||
|
||
dr.advanceBy(1) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x02])) | ||
} | ||
|
||
} | ||
|
||
func tesReset() { | ||
|
||
let data = Data([0x01, 0x02, 0x03, 0x04]) | ||
|
||
// reset | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
XCTAssertEqual(dr.read(bytes: 2), Data([0x02, 0x03])) | ||
dr.reset() | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
} | ||
|
||
} | ||
|
||
// MARK: - Data storage starting with index >0 | ||
|
||
func testRead_DataIndicesOffset() { | ||
|
||
let rawData = Data([0x00, 0x00, 0x00, | ||
0x01, 0x02, 0x03, 0x04]) | ||
let data = rawData[3 ... 6] | ||
|
||
// .read - byte by byte | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.readPosition, 0) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x02])) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x03])) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x04])) | ||
XCTAssertEqual(dr.read(bytes: 1), nil) | ||
} | ||
|
||
// .read - nil read - return all remaining bytes | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(), data) | ||
XCTAssertEqual(dr.read(bytes: 1), nil) | ||
} | ||
|
||
// .read - zero count read - return empty data, not nil | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(bytes: 0), Data()) | ||
} | ||
|
||
// .read - read overflow - return nil | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(bytes: 5), nil) | ||
} | ||
|
||
} | ||
|
||
func testNonAdvancingRead_DataIndicesOffset() { | ||
|
||
let rawData = Data([0x00, 0x00, 0x00, | ||
0x01, 0x02, 0x03, 0x04]) | ||
let data = rawData[3 ... 6] | ||
|
||
// .nonAdvancingRead - nil read - return all remaining bytes | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.nonAdvancingRead(), data) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
} | ||
|
||
// .nonAdvancingRead - read byte counts | ||
do { | ||
let dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.nonAdvancingRead(bytes: 1), Data([0x01])) | ||
XCTAssertEqual(dr.nonAdvancingRead(bytes: 2), Data([0x01, 0x02])) | ||
} | ||
|
||
// .nonAdvancingRead - read overflow - return nil | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.nonAdvancingRead(bytes: 5), nil) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
} | ||
|
||
} | ||
|
||
func testAdvanceBy_DataIndicesOffset() { | ||
|
||
let rawData = Data([0x00, 0x00, 0x00, | ||
0x01, 0x02, 0x03, 0x04]) | ||
let data = rawData[3 ... 6] | ||
|
||
// advanceBy | ||
do { | ||
var dr = DataReader(data) | ||
|
||
dr.advanceBy(1) | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x02])) | ||
} | ||
|
||
} | ||
|
||
func tesReset_DataIndicesOffset() { | ||
|
||
let rawData = Data([0x00, 0x00, 0x00, | ||
0x01, 0x02, 0x03, 0x04]) | ||
let data = rawData[3 ... 6] | ||
|
||
|
||
// reset | ||
do { | ||
var dr = DataReader(data) | ||
|
||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
XCTAssertEqual(dr.read(bytes: 2), Data([0x02, 0x03])) | ||
dr.reset() | ||
XCTAssertEqual(dr.read(bytes: 1), Data([0x01])) | ||
} | ||
|
||
} | ||
|
||
} | ||
|
||
#endif |