Skip to content

Commit

Permalink
Fixed size Numeric array encode/decode implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaRU committed Feb 2, 2024
1 parent 3461e93 commit 391d8b5
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 72 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ The following table shows the basic IDL types supported by CDRCodable and how th
| String | std::string | string |

### 2. Arrays
Static size arrays is not supported by CDRCodable directly and needed custom coding.
Fixed size arrays is not supported by CDRCodable directly and needed custom coding.

### 3. Sequences
CDRCodable supports sequences, which map between Swift Array and C++ std::vector container. The following table represents how the map between Swift, C++11 and IDL and is handled.
Expand Down
72 changes: 42 additions & 30 deletions Sources/CDRCodable/Decoder/CDRDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final public class CDRDecoder {
/// - Throws: `DecodingError.dataCorrupted(_:)` if the data is not valid
public func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
let dataStore = DataStore(data: data)
switch T.self {
switch type {
case is [Double].Type: return try dataStore.readArray(Double.self) as! T
case is [Float].Type: return try dataStore.readArray(Float.self) as! T
case is [Int].Type: return try dataStore.readArray(Int.self) as! T
Expand All @@ -45,12 +45,14 @@ final public class CDRDecoder {

final class DataStore {
let data: Data
var index: Data.Index
let beginIndex: Data.Index
var cursor: Data.Index
var codingPath: [CodingKey] = []

init(data: Data) {
self.data = data
self.index = self.data.startIndex
self.beginIndex = self.data.startIndex
self.cursor = self.data.startIndex
}
}

Expand Down Expand Up @@ -104,15 +106,15 @@ protocol _CDRDecodingContainer {
extension DataStore {
@inline(__always)
func align(to aligment: Int) {
let offset = index % aligment
let offset = (cursor - beginIndex) % aligment
if offset != 0 {
index = index.advanced(by: aligment - offset)
cursor = cursor.advanced(by: aligment - offset)
}
}

@inline(__always)
func checkDataEnd(_ length: Int) throws {
let nextIndex = index.advanced(by: length)
let nextIndex = cursor.advanced(by: length)
guard nextIndex <= data.endIndex else {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Unexpected end of data")
throw DecodingError.dataCorrupted(context)
Expand All @@ -128,40 +130,23 @@ extension DataStore {

@inline(__always)
func read<T>(_ type: T.Type) throws -> T where T : Numeric {
let aligment = MemoryLayout<T>.alignment
let offset = index % aligment
if offset != 0 {
index = index.advanced(by: aligment - offset)
}
align(to: MemoryLayout<T>.alignment)
let stride = MemoryLayout<T>.stride
try checkDataEnd(stride)
defer {
index = index.advanced(by: stride)
}
return data.withUnsafeBytes{ $0.load(fromByteOffset: index, as: T.self) }
}

@inline(__always)
func readArray<T>(_ type: T.Type) throws -> [T] where T: Numeric {
let size = MemoryLayout<T>.size
let count = try readCheckBlockCount(of: size)
defer {
index = index.advanced(by: count * size)
}
return Array<T>.init(unsafeUninitializedCapacity: count) {
data.copyBytes(to: $0, from: index...)
$1 = count
cursor = cursor.advanced(by: stride)
}
return data.withUnsafeBytes{ $0.load(fromByteOffset: cursor - beginIndex, as: T.self) }
}

@inline(__always)
func readString() throws -> String {
let length = try readCheckBlockCount(of: 1)

defer {
index = index.advanced(by: length)
cursor = cursor.advanced(by: length)
}
guard let string = String(data: data[index..<index.advanced(by: length - 1)], encoding: .utf8) else {
guard let string = String(data: data[cursor..<cursor.advanced(by: length - 1)], encoding: .utf8) else {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Couldn't decode string with UTF-8 encoding")
throw DecodingError.dataCorrupted(context)
}
Expand All @@ -172,8 +157,35 @@ extension DataStore {
func readData() throws -> Data {
let length = try readCheckBlockCount(of: 1)
defer {
index = index.advanced(by: length)
cursor = cursor.advanced(by: length)
}
return data.subdata(in: cursor..<cursor.advanced(by: length))
}

@inline(__always)
func readArray<T>(_ type: T.Type) throws -> [T] where T: Numeric {
let size = MemoryLayout<T>.size
let count = try readCheckBlockCount(of: size)
defer {
cursor = cursor.advanced(by: count * size)
}
return Array<T>.init(unsafeUninitializedCapacity: count) {
let _: Int = data.copyBytes(to: $0, from: cursor...)
$1 = count
}
}

@inline(__always)
func readFixedArray<T>(_ type: T.Type, count: Int) throws -> [T] where T: Numeric {
align(to: MemoryLayout<T>.alignment)
let stride = MemoryLayout<T>.stride
try checkDataEnd(count * stride)
defer {
cursor = cursor.advanced(by: count * stride)
}
return Array<T>.init(unsafeUninitializedCapacity: count) {
let _: Int = data.copyBytes(to: $0, from: cursor...)
$1 = count
}
return data.subdata(in: index..<index.advanced(by: length))
}
}
29 changes: 28 additions & 1 deletion Sources/CDRCodable/Decoder/KeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,34 @@ extension _CDRDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
switch T.self {
if let intValue = key.intValue, intValue > 0x10000 {
let fixedCount = intValue >> 16
switch type {
case is [Double].Type: return try dataStore.readFixedArray(Double.self, count: fixedCount) as! T
case is [Float].Type: return try dataStore.readFixedArray(Float.self, count: fixedCount) as! T
case is [Int].Type: return try dataStore.readFixedArray(Int.self, count: fixedCount) as! T
case is [Int8].Type: return try dataStore.readFixedArray(Int8.self, count: fixedCount) as! T
case is [Int16].Type: return try dataStore.readFixedArray(Int16.self, count: fixedCount) as! T
case is [Int32].Type: return try dataStore.readFixedArray(Int32.self, count: fixedCount) as! T
case is [Int64].Type: return try dataStore.readFixedArray(Int64.self, count: fixedCount) as! T
case is [UInt].Type: return try dataStore.readFixedArray(UInt.self, count: fixedCount) as! T
case is [UInt8].Type: return try dataStore.readFixedArray(UInt8.self, count: fixedCount) as! T
case is [UInt16].Type: return try dataStore.readFixedArray(UInt16.self, count: fixedCount) as! T
case is [UInt32].Type: return try dataStore.readFixedArray(UInt32.self, count: fixedCount) as! T
case is [UInt64].Type: return try dataStore.readFixedArray(UInt64.self, count: fixedCount) as! T
case is [String].Type:
var stringArray: [String] = []
stringArray.reserveCapacity(fixedCount)
for _ in 0..<fixedCount {
stringArray.append(try dataStore.readString())
}
default:
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Non Array<Numberic> type as fixed array")
throw DecodingError.typeMismatch(T.self, context)
}
}

switch type {
case is [Double].Type: return try dataStore.readArray(Double.self) as! T
case is [Float].Type: return try dataStore.readArray(Float.self) as! T
case is [Int].Type: return try dataStore.readArray(Int.self) as! T
Expand Down
41 changes: 24 additions & 17 deletions Sources/CDRCodable/Encoder/CDREncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ final public class CDREncoder {
let dataStore = _CDREncoder.DataStore(capacity: capacity)

switch value {
case let value as [Int]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Int>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int8]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Int8>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int16]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Int16>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int32]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Int32>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int64]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Int64>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<UInt>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt8]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<UInt8>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt16]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<UInt16>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt32]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<UInt32>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt64]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<UInt64>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Float]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Float>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Double]: try dataStore.encodeNumericArray(count: value.count, size: MemoryLayout<Double>.size, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int8]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int16]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int32]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [Int64]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt8]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt16]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt32]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [UInt64]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [Float]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as [Double]: try dataStore.encodeNumericArray(count: value.count, pointer: value.withUnsafeBytes{ $0 })
case let value as Data:
try dataStore.write(count: value.count)
dataStore.write(data: value)
Expand Down Expand Up @@ -112,6 +112,13 @@ extension _CDREncoder: Encoder {
}

extension _CDREncoder.DataStore {
func align(_ alignment: Int) {
let offset = self.data.count % alignment
if offset != 0 {
self.data.append(contentsOf: Array(repeating: UInt8(0), count: alignment - offset))
}
}

@inline(__always)
func write(data: Data) {
self.data.append(data)
Expand All @@ -124,10 +131,10 @@ extension _CDREncoder.DataStore {

@inline(__always)
func write<T>(value: T) where T: Numeric {
let aligment = MemoryLayout<T>.alignment
let offset = self.data.count % aligment
let alignment = MemoryLayout<T>.alignment
let offset = self.data.count % alignment
if offset != 0 {
self.data.append(contentsOf: Array(repeating: UInt8(0), count: aligment - offset))
self.data.append(contentsOf: Array(repeating: UInt8(0), count: alignment - offset))
}
self.data.append(contentsOf: value.bytes)
}
Expand All @@ -141,13 +148,13 @@ extension _CDREncoder.DataStore {
}

@inline(__always)
func encodeNumericArray(count: Int, size: Int, pointer: UnsafeRawBufferPointer) throws {
func encodeNumericArray(count: Int, pointer: UnsafeRawBufferPointer) throws {
guard let uint32 = UInt32(exactly: count) else {
let context = EncodingError.Context(codingPath: [], debugDescription: "Cannot encode data of length \(count).")
throw EncodingError.invalidValue(count, context)
}
write(value: uint32)
data.append(pointer.baseAddress!.assumingMemoryBound(to: UInt8.self), count: count * size)
data.append(pointer.baseAddress!.assumingMemoryBound(to: UInt8.self), count: pointer.count)
}
}

Expand Down
Loading

0 comments on commit 391d8b5

Please sign in to comment.