From 25987cb68c1080867f4591e65ef1e7f16787cc86 Mon Sep 17 00:00:00 2001 From: Steffan Andrews Date: Thu, 4 Nov 2021 23:12:06 -0700 Subject: [PATCH] Added `DataReader` --- Sources/OTCore/Abstractions/DataReader.swift | 91 +++++++ .../Abstractions/DataReader Tests.swift | 228 ++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 Sources/OTCore/Abstractions/DataReader.swift create mode 100644 Tests/OTCoreTests/Abstractions/DataReader Tests.swift diff --git a/Sources/OTCore/Abstractions/DataReader.swift b/Sources/OTCore/Abstractions/DataReader.swift new file mode 100644 index 0000000..f51ee8e --- /dev/null +++ b/Sources/OTCore/Abstractions/DataReader.swift @@ -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 + + } + +} diff --git a/Tests/OTCoreTests/Abstractions/DataReader Tests.swift b/Tests/OTCoreTests/Abstractions/DataReader Tests.swift new file mode 100644 index 0000000..55788be --- /dev/null +++ b/Tests/OTCoreTests/Abstractions/DataReader Tests.swift @@ -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