-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:AcalaNetwork/boka
* 'master' of github.com:AcalaNetwork/boka: update bitstring & test (#67)
- Loading branch information
Showing
6 changed files
with
255 additions
and
5 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
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
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
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
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,134 @@ | ||
import Codec | ||
import Foundation | ||
|
||
public enum ConfigSizeBitStringError: Error { | ||
case missingConfig | ||
case invalidData | ||
case invalidIndex | ||
} | ||
|
||
public struct ConfigSizeBitString<TBitLength: ReadInt>: Equatable, Sendable, Codable { | ||
/// Byte storage for bits. | ||
private var bytes: Data | ||
/// Bit length | ||
public let length: Int | ||
|
||
private var byteLength: Int { | ||
(length + 7) / 8 | ||
} | ||
|
||
public init(config: TBitLength.TConfig, data: Data) throws(ConfigSizeBitStringError) { | ||
length = TBitLength.read(config: config) | ||
bytes = data | ||
|
||
if byteLength != data.count { | ||
throw .invalidData | ||
} | ||
} | ||
|
||
public init(config: TBitLength.TConfig) { | ||
length = TBitLength.read(config: config) | ||
let byteLength = (length + 7) / 8 | ||
bytes = Data(repeating: 0, count: byteLength) | ||
} | ||
|
||
private func at(unchecked index: Int) -> Bool { | ||
let byteIndex = index / 8 | ||
let bitIndex = index % 8 | ||
return (bytes[byteIndex] & (1 << bitIndex)) != 0 | ||
} | ||
|
||
/// Formats the bitstring in binary digits. | ||
public var binaryString: String { | ||
(0 ..< length).map { at(unchecked: $0) ? "1" : "0" }.joined() | ||
} | ||
|
||
public var description: String { binaryString } | ||
|
||
public func at(_ index: Int) throws(ConfigSizeBitStringError) -> Bool { | ||
guard index < length else { | ||
throw .invalidIndex | ||
} | ||
return at(unchecked: index) | ||
} | ||
|
||
public mutating func set(_ index: Int, to value: Bool) throws(ConfigSizeBitStringError) { | ||
guard index < length else { | ||
throw .invalidIndex | ||
} | ||
let byteIndex = index / 8 | ||
let bitIndex = index % 8 | ||
if value { | ||
bytes[byteIndex] |= (1 << bitIndex) | ||
} else { | ||
bytes[byteIndex] &= ~(1 << bitIndex) | ||
} | ||
} | ||
} | ||
|
||
extension ConfigSizeBitString: RandomAccessCollection { | ||
public typealias Element = Bool | ||
|
||
public var startIndex: Int { | ||
0 | ||
} | ||
|
||
public var endIndex: Int { | ||
length | ||
} | ||
|
||
public subscript(position: Int) -> Bool { | ||
get { | ||
try! at(position) | ||
} | ||
set { | ||
try! set(position, to: newValue) | ||
} | ||
} | ||
|
||
public func index(after i: Int) -> Int { | ||
i + 1 | ||
} | ||
|
||
public func index(before i: Int) -> Int { | ||
i - 1 | ||
} | ||
|
||
public func index(_ i: Int, offsetBy distance: Int) -> Int { | ||
i + distance | ||
} | ||
|
||
public func index(_ i: Int, offsetBy distance: Int, limitedBy limit: Int) -> Int? { | ||
i + distance < limit ? i + distance : nil | ||
} | ||
|
||
public func distance(from start: Int, to end: Int) -> Int { | ||
end - start | ||
} | ||
|
||
public func formIndex(after i: inout Int) { | ||
i += 1 | ||
} | ||
|
||
public func formIndex(before i: inout Int) { | ||
i -= 1 | ||
} | ||
} | ||
|
||
extension ConfigSizeBitString: FixedLengthData { | ||
public var data: Data { bytes } | ||
|
||
public static func length(decoder: Decoder) throws -> Int { | ||
guard let config = decoder.getConfig(TBitLength.TConfig.self) else { | ||
throw ConfigSizeBitStringError.missingConfig | ||
} | ||
return (TBitLength.read(config: config) + 7) / 8 | ||
} | ||
|
||
public init(decoder: Decoder, data: Data) throws { | ||
guard let config = decoder.getConfig(TBitLength.TConfig.self) else { | ||
throw ConfigSizeBitStringError.missingConfig | ||
} | ||
try self.init(config: config, data: data) | ||
} | ||
} |
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,116 @@ | ||
import Codec | ||
import Foundation | ||
import Testing | ||
|
||
@testable import Utils | ||
|
||
struct ReadIntValue: ReadInt { | ||
typealias TConfig = Int | ||
|
||
static func read(config: Int) -> Int { | ||
config | ||
} | ||
} | ||
|
||
struct ConfigSizeBitStringTests { | ||
@Test func notEnoughData() throws { | ||
let data = Data([0]) | ||
let length = 9 | ||
#expect(throws: ConfigSizeBitStringError.invalidData) { | ||
try ConfigSizeBitString<ReadIntValue>(config: length, data: data) | ||
} | ||
} | ||
|
||
@Test func tooMuchData() throws { | ||
let data = Data([0, 0]) | ||
let length = 8 | ||
#expect(throws: ConfigSizeBitStringError.invalidData) { | ||
try ConfigSizeBitString<ReadIntValue>(config: length, data: data) | ||
} | ||
} | ||
|
||
@Test func works() throws { | ||
var value = ConfigSizeBitString<ReadIntValue>(config: 7) | ||
#expect(value.binaryString == "0000000") | ||
|
||
value[0] = true | ||
#expect(Array(value) == [true, false, false, false, false, false, false]) | ||
#expect(value.binaryString == "1000000") | ||
|
||
value[1] = true | ||
#expect(Array(value) == [true, true, false, false, false, false, false]) | ||
#expect(value.binaryString == "1100000") | ||
|
||
value[6] = true | ||
#expect(Array(value) == [true, true, false, false, false, false, true]) | ||
#expect(value.binaryString == "1100001") | ||
|
||
value[0] = false | ||
#expect(Array(value) == [false, true, false, false, false, false, true]) | ||
#expect(value.binaryString == "0100001") | ||
} | ||
|
||
@Test func initWorks() throws { | ||
let data = Data([0b1011_0101]) | ||
let length = 7 | ||
let value = try ConfigSizeBitString<ReadIntValue>(config: length, data: data) | ||
#expect(Array(value) == [true, false, true, false, true, true, false]) | ||
#expect(value.binaryString == "1010110") | ||
} | ||
|
||
@Test func largeInitWorks() throws { | ||
let data = Data([0b1011_0101, 0b1100_0101, 0b0010_0110]) | ||
let length = 20 | ||
var value = try ConfigSizeBitString<ReadIntValue>(config: length, data: data) | ||
#expect(Array(value) == [ | ||
true, false, true, false, | ||
true, true, false, true, | ||
true, false, true, false, | ||
false, false, true, true, | ||
false, true, true, false, | ||
]) | ||
#expect(value.binaryString == "10101101101000110110") | ||
|
||
value[19] = true | ||
#expect(value[19] == true) | ||
#expect(value.binaryString == "10101101101000110111") | ||
|
||
#expect(throws: ConfigSizeBitStringError.invalidIndex) { | ||
_ = try value.at(20) | ||
} | ||
|
||
#expect(throws: ConfigSizeBitStringError.invalidIndex) { | ||
try value.set(20, to: true) | ||
} | ||
} | ||
|
||
@Test func codable() throws { | ||
let data = Data([0b1011_0101, 0b1100_0101, 0b0000_0110]) | ||
let length = 20 | ||
let value = try ConfigSizeBitString<ReadIntValue>(config: length, data: data) | ||
|
||
let encoded = try JamEncoder.encode(value) | ||
#expect(encoded == data) | ||
let decoded = try JamDecoder.decode(ConfigSizeBitString<ReadIntValue>.self, from: encoded, withConfig: length) | ||
#expect(decoded == value) | ||
} | ||
|
||
@Test func equatable() throws { | ||
let data = Data([0b1011_0101, 0b1100_0101, 0b0000_0110]) | ||
let length = 20 | ||
var value = try ConfigSizeBitString<ReadIntValue>(config: length, data: data) | ||
|
||
let length2 = 21 | ||
let value2 = try ConfigSizeBitString<ReadIntValue>(config: length2, data: data) | ||
|
||
#expect(value == value) | ||
#expect(value != value2) | ||
|
||
var value3 = value | ||
value3[19] = true | ||
#expect(value3 != value) | ||
|
||
value[19] = true | ||
#expect(value == value3) | ||
} | ||
} |