Skip to content

Commit

Permalink
Merge branch 'master' of github.com:AcalaNetwork/boka
Browse files Browse the repository at this point in the history
* 'master' of github.com:AcalaNetwork/boka:
  update  bitstring & test (#67)
  • Loading branch information
MacOMNI committed Aug 22, 2024
2 parents 98897c7 + 317e274 commit f0423dc
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ public struct ExtrinsicAvailability: Sendable, Equatable, Codable {
// a
public var parentHash: Data32
// f
public var assurance: Data // bit string with length of Constants.TotalNumberOfCores TODO: use a BitString type
public var assurance: ConfigSizeBitString<ProtocolConfig.TotalNumberOfCores>
// v
public var validatorIndex: ValidatorIndex
// s
public var signature: Ed25519Signature

public init(
parentHash: Data32,
assurance: Data,
assurance: ConfigSizeBitString<ProtocolConfig.TotalNumberOfCores>,
validatorIndex: ValidatorIndex,
signature: Ed25519Signature
) {
Expand Down
2 changes: 1 addition & 1 deletion Codec/Sources/Codec/FixedLengthCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import Foundation
public protocol FixedLengthData {
var data: Data { get }

static func length(decoder: Decoder) -> Int
static func length(decoder: Decoder) throws -> Int
init(decoder: Decoder, data: Data) throws
}
2 changes: 1 addition & 1 deletion Codec/Sources/Codec/JamDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ private class DecodeContext: Decoder {

fileprivate func decodeFixedLengthData<T: FixedLengthData>(_ type: T.Type, key: CodingKey?) throws -> T {
try withExtendedLifetime(PushCodingPath(decoder: self, key: key)) {
let length = type.length(decoder: self)
let length = try type.length(decoder: self)
guard data.count >= length else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
Expand Down
2 changes: 1 addition & 1 deletion Codec/Sources/Codec/JamEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ private class EncodeContext: Encoder {
// reserve capacity for the length
// length is variable size but very unlikely to be larger than 4 bytes
data.reserveCapacity(data.count + value.count + (lengthPrefix ? 4 : 0))
let length = UInt32(value.count)
if lengthPrefix {
let length = UInt32(value.count)
data.append(contentsOf: length.encode(method: .variableWidth))
}
data.append(value)
Expand Down
134 changes: 134 additions & 0 deletions Utils/Sources/Utils/ConfigSizeBitString.swift
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)
}
}
116 changes: 116 additions & 0 deletions Utils/Tests/UtilsTests/ConfigSizeBitStringTests.swift
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)
}
}

0 comments on commit f0423dc

Please sign in to comment.