Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,542 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object
/// it allows users to write and read data directly from memory thus the use of its
/// functions should be used
@frozen
public struct ByteBuffer {
/// Storage is a container that would hold the memory pointer to solve the issue of
/// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
@usableFromInline
final class Storage {
// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
private let unowned: Bool
/// pointer to the start of the buffer object in memory
var memory: UnsafeMutableRawPointer
/// Capacity of UInt8 the buffer can hold
var capacity: Int
@usableFromInline
init(count: Int, alignment: Int) {
memory = UnsafeMutableRawPointer.allocate(
byteCount: count,
alignment: alignment)
capacity = count
unowned = false
}
@usableFromInline
init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) {
self.memory = memory
self.capacity = capacity
self.unowned = unowned
}
deinit {
if !unowned {
memory.deallocate()
}
}
@usableFromInline
func copy(from ptr: UnsafeRawPointer, count: Int) {
assert(
!unowned,
"copy should NOT be called on a buffer that is built by assumingMemoryBound")
memory.copyMemory(from: ptr, byteCount: count)
}
@usableFromInline
func initialize(for size: Int) {
assert(
!unowned,
"initalize should NOT be called on a buffer that is built by assumingMemoryBound")
memset(memory, 0, size)
}
/// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
/// - Parameter size: Size of the current object
@usableFromInline
func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
let currentWritingIndex = capacity &- writerSize
while capacity <= writerSize &+ size {
capacity = capacity << 1
}
/// solution take from Apple-NIO
capacity = capacity.convertToPowerofTwo
let newData = UnsafeMutableRawPointer.allocate(
byteCount: capacity,
alignment: alignment)
memset(newData, 0, capacity &- writerSize)
memcpy(
newData.advanced(by: capacity &- writerSize),
memory.advanced(by: currentWritingIndex),
writerSize)
memory.deallocate()
memory = newData
}
}
@usableFromInline var _storage: Storage
/// The size of the elements written to the buffer + their paddings
private var _writerSize: Int = 0
/// Alignment of the current memory being written to the buffer
var alignment = 1
/// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer
var writerIndex: Int { _storage.capacity &- _writerSize }
/// Reader is the position of the current Writer Index (capacity - size)
public var reader: Int { writerIndex }
/// Current size of the buffer
public var size: UOffset { UOffset(_writerSize) }
/// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
public var memory: UnsafeMutableRawPointer { _storage.memory }
/// Current capacity for the buffer
public var capacity: Int { _storage.capacity }
/// Crash if the trying to read an unaligned buffer instead of allowing users to read them.
public let allowReadingUnalignedBuffers: Bool
/// Constructor that creates a Flatbuffer object from a UInt8
/// - Parameter
/// - bytes: Array of UInt8
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
public init(
bytes: [UInt8],
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
{
var b = bytes
_storage = Storage(count: bytes.count, alignment: alignment)
_writerSize = _storage.capacity
allowReadingUnalignedBuffers = allowUnalignedBuffers
b.withUnsafeMutableBytes { bufferPointer in
_storage.copy(from: bufferPointer.baseAddress!, count: bytes.count)
}
}
#if !os(WASI)
/// Constructor that creates a Flatbuffer from the Swift Data type object
/// - Parameter
/// - data: Swift data Object
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
public init(
data: Data,
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
{
var b = data
_storage = Storage(count: data.count, alignment: alignment)
_writerSize = _storage.capacity
allowReadingUnalignedBuffers = allowUnalignedBuffers
b.withUnsafeMutableBytes { bufferPointer in
_storage.copy(from: bufferPointer.baseAddress!, count: data.count)
}
}
#endif
/// Constructor that creates a Flatbuffer instance with a size
/// - Parameter:
/// - size: Length of the buffer
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
init(initialSize size: Int) {
let size = size.convertToPowerofTwo
_storage = Storage(count: size, alignment: alignment)
_storage.initialize(for: size)
allowReadingUnalignedBuffers = false
}
#if swift(>=5.0) && !os(WASI)
/// Constructor that creates a Flatbuffer object from a ContiguousBytes
/// - Parameters:
/// - contiguousBytes: Binary stripe to use as the buffer
/// - count: amount of readable bytes
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
public init<Bytes: ContiguousBytes>(
contiguousBytes: Bytes,
count: Int,
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
{
_storage = Storage(count: count, alignment: alignment)
_writerSize = _storage.capacity
allowReadingUnalignedBuffers = allowUnalignedBuffers
contiguousBytes.withUnsafeBytes { buf in
_storage.copy(from: buf.baseAddress!, count: buf.count)
}
}
#endif
/// Constructor that creates a Flatbuffer from unsafe memory region without copying
/// - Parameter:
/// - assumingMemoryBound: The unsafe memory region
/// - capacity: The size of the given memory region
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
public init(
assumingMemoryBound memory: UnsafeMutableRawPointer,
capacity: Int,
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
{
_storage = Storage(memory: memory, capacity: capacity, unowned: true)
_writerSize = capacity
allowReadingUnalignedBuffers = allowUnalignedBuffers
}
/// Creates a copy of the buffer that's being built by calling sizedBuffer
/// - Parameters:
/// - memory: Current memory of the buffer
/// - count: count of bytes
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
init(
memory: UnsafeMutableRawPointer,
count: Int,
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
{
_storage = Storage(count: count, alignment: alignment)
_storage.copy(from: memory, count: count)
_writerSize = _storage.capacity
allowReadingUnalignedBuffers = allowUnalignedBuffers
}
/// Creates a copy of the existing flatbuffer, by copying it to a different memory.
/// - Parameters:
/// - memory: Current memory of the buffer
/// - count: count of bytes
/// - removeBytes: Removes a number of bytes from the current size
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
init(
memory: UnsafeMutableRawPointer,
count: Int,
removing removeBytes: Int,
allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false)
{
_storage = Storage(count: count, alignment: alignment)
_storage.copy(from: memory, count: count)
_writerSize = removeBytes
allowReadingUnalignedBuffers = allowUnalignedBuffers
}
/// Fills the buffer with padding by adding to the writersize
/// - Parameter padding: Amount of padding between two to be serialized objects
@inline(__always)
@usableFromInline
mutating func fill(padding: Int) {
assert(padding >= 0, "Fill should be larger than or equal to zero")
ensureSpace(size: padding)
_writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding)
}
/// Adds an array of type Scalar to the buffer memory
/// - Parameter elements: An array of Scalars
@inline(__always)
@usableFromInline
mutating func push<T: Scalar>(elements: [T]) {
elements.withUnsafeBytes { ptr in
ensureSpace(size: ptr.count)
memcpy(
_storage.memory.advanced(by: writerIndex &- ptr.count),
ptr.baseAddress!,
ptr.count)
_writerSize = _writerSize &+ ptr.count
}
}
/// Adds an array of type Scalar to the buffer memory
/// - Parameter elements: An array of Scalars
@inline(__always)
@usableFromInline
mutating func push<T: NativeStruct>(elements: [T]) {
elements.withUnsafeBytes { ptr in
ensureSpace(size: ptr.count)
memcpy(
_storage.memory.advanced(by: writerIndex &- ptr.count),
ptr.baseAddress!,
ptr.count)
_writerSize = _writerSize &+ ptr.count
}
}
/// Adds a `ContiguousBytes` to buffer memory
/// - Parameter value: bytes to copy
#if swift(>=5.0) && !os(WASI)
@inline(__always)
@usableFromInline
mutating func push(bytes: ContiguousBytes) {
bytes.withUnsafeBytes { ptr in
ensureSpace(size: ptr.count)
memcpy(
_storage.memory.advanced(by: writerIndex &- ptr.count),
ptr.baseAddress!,
ptr.count)
_writerSize = _writerSize &+ ptr.count
}
}
#endif
/// Adds an object of type NativeStruct into the buffer
/// - Parameters:
/// - value: Object that will be written to the buffer
/// - size: size to subtract from the WriterIndex
@usableFromInline
@inline(__always)
mutating func push<T: NativeStruct>(struct value: T, size: Int) {
ensureSpace(size: size)
withUnsafePointer(to: value) {
memcpy(
_storage.memory.advanced(by: writerIndex &- size),
$0,
size)
_writerSize = _writerSize &+ size
}
}
/// Adds an object of type Scalar into the buffer
/// - Parameters:
/// - value: Object that will be written to the buffer
/// - len: Offset to subtract from the WriterIndex
@inline(__always)
@usableFromInline
mutating func push<T: Scalar>(value: T, len: Int) {
ensureSpace(size: len)
withUnsafePointer(to: value) {
memcpy(
_storage.memory.advanced(by: writerIndex &- len),
$0,
len)
_writerSize = _writerSize &+ len
}
}
/// Adds a string to the buffer using swift.utf8 object
/// - Parameter str: String that will be added to the buffer
/// - Parameter len: length of the string
@inline(__always)
@usableFromInline
mutating func push(string str: String, len: Int) {
ensureSpace(size: len)
if str.utf8
.withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) !=
nil
{
} else {
let utf8View = str.utf8
for c in utf8View.reversed() {
push(value: c, len: 1)
}
}
}
/// Writes a string to Bytebuffer using UTF8View
/// - Parameters:
/// - bytes: Pointer to the view
/// - len: Size of string
@usableFromInline
@inline(__always)
mutating func push(
bytes: UnsafeBufferPointer<String.UTF8View.Element>,
len: Int) -> Bool
{
memcpy(
_storage.memory.advanced(by: writerIndex &- len),
bytes.baseAddress!,
len)
_writerSize = _writerSize &+ len
return true
}
/// Write stores an object into the buffer directly or indirectly.
///
/// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
/// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
/// - Parameters:
/// - value: Value that needs to be written to the buffer
/// - index: index to write to
/// - direct: Should take into consideration the capacity of the buffer
@inline(__always)
func write<T>(value: T, index: Int, direct: Bool = false) {
var index = index
if !direct {
index = _storage.capacity &- index
}
assert(index < _storage.capacity, "Write index is out of writing bound")
assert(index >= 0, "Writer index should be above zero")
withUnsafePointer(to: value) {
memcpy(
_storage.memory.advanced(by: index),
$0,
MemoryLayout<T>.size)
}
}
/// Makes sure that buffer has enouch space for each of the objects that will be written into it
/// - Parameter size: size of object
@discardableResult
@usableFromInline
@inline(__always)
mutating func ensureSpace(size: Int) -> Int {
if size &+ _writerSize > _storage.capacity {
_storage.reallocate(size, writerSize: _writerSize, alignment: alignment)
}
assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes")
return size
}
/// pops the written VTable if it's already written into the buffer
/// - Parameter size: size of the `VTable`
@usableFromInline
@inline(__always)
mutating func pop(_ size: Int) {
assert(
(_writerSize &- size) > 0,
"New size should NOT be a negative number")
memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size)
_writerSize = size
}
/// Clears the current size of the buffer
@inline(__always)
mutating public func clearSize() {
_writerSize = 0
}
/// Clears the current instance of the buffer, replacing it with new memory
@inline(__always)
mutating public func clear() {
_writerSize = 0
alignment = 1
_storage.initialize(for: _storage.capacity)
}
/// Reads an object from the buffer
/// - Parameters:
/// - def: Type of the object
/// - position: the index of the object in the buffer
@inline(__always)
public func read<T>(def: T.Type, position: Int) -> T {
if allowReadingUnalignedBuffers {
return _storage.memory.advanced(by: position).loadUnaligned(as: T.self)
}
return _storage.memory.advanced(by: position).load(as: T.self)
}
/// Reads a slice from the memory assuming a type of T
/// - Parameters:
/// - index: index of the object to be read from the buffer
/// - count: count of bytes in memory
@inline(__always)
public func readSlice<T>(
index: Int,
count: Int) -> [T]
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
let start = _storage.memory.advanced(by: index)
.assumingMemoryBound(to: T.self)
let array = UnsafeBufferPointer(start: start, count: count)
return Array(array)
}
#if !os(WASI)
/// Reads a string from the buffer and encodes it to a swift string
/// - Parameters:
/// - index: index of the string in the buffer
/// - count: length of the string
/// - type: Encoding of the string
@inline(__always)
public func readString(
at index: Int,
count: Int,
type: String.Encoding = .utf8) -> String?
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
let start = _storage.memory.advanced(by: index)
.assumingMemoryBound(to: UInt8.self)
let bufprt = UnsafeBufferPointer(start: start, count: count)
return String(bytes: Array(bufprt), encoding: type)
}
#else
/// Reads a string from the buffer and encodes it to a swift string
/// - Parameters:
/// - index: index of the string in the buffer
/// - count: length of the string
@inline(__always)
public func readString(
at index: Int,
count: Int) -> String?
{
assert(
index + count <= _storage.capacity,
"Reading out of bounds is illegal")
let start = _storage.memory.advanced(by: index)
.assumingMemoryBound(to: UInt8.self)
let bufprt = UnsafeBufferPointer(start: start, count: count)
return String(cString: bufprt.baseAddress!)
}
#endif
/// Creates a new Flatbuffer object that's duplicated from the current one
/// - Parameter removeBytes: the amount of bytes to remove from the current Size
@inline(__always)
public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
assert(removeBytes > 0, "Can NOT remove negative bytes")
assert(
removeBytes < _storage.capacity,
"Can NOT remove more bytes than the ones allocated")
return ByteBuffer(
memory: _storage.memory,
count: _storage.capacity,
removing: _writerSize &- removeBytes)
}
/// Returns the written bytes into the ``ByteBuffer``
public var underlyingBytes: [UInt8] {
let cp = capacity &- writerIndex
let start = memory.advanced(by: writerIndex)
.bindMemory(to: UInt8.self, capacity: cp)
let ptr = UnsafeBufferPointer<UInt8>(start: start, count: cp)
return Array(ptr)
}
/// SkipPrefix Skips the first 4 bytes in case one of the following
/// functions are called `getPrefixedSizeCheckedRoot` & `getPrefixedSizeRoot`
/// which allows us to skip the first 4 bytes instead of recreating the buffer
@discardableResult
@usableFromInline
@inline(__always)
mutating func skipPrefix() -> Int32 {
_writerSize = _writerSize &- MemoryLayout<Int32>.size
return read(def: Int32.self, position: 0)
}
}
extension ByteBuffer: CustomDebugStringConvertible {
public var debugDescription: String {
"""
buffer located at: \(_storage.memory), with capacity of \(_storage.capacity)
{ writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(
writerIndex) }
"""
}
}
@@ -0,0 +1,114 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// A boolean to see if the system is littleEndian
let isLitteEndian: Bool = {
let number: UInt32 = 0x12345678
return number == number.littleEndian
}()
/// Constant for the file id length
let FileIdLength = 4
/// Type aliases
public typealias Byte = UInt8
public typealias UOffset = UInt32
public typealias SOffset = Int32
public typealias VOffset = UInt16
/// Maximum size for a buffer
public let FlatBufferMaxSize = UInt32
.max << ((MemoryLayout<SOffset>.size * 8 - 1) - 1)
/// Protocol that All Scalars should conform to
///
/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
public protocol Scalar: Equatable {
associatedtype NumericValue
var convertedEndian: NumericValue { get }
}
extension Scalar where Self: Verifiable {}
extension Scalar where Self: FixedWidthInteger {
/// Converts the value from BigEndian to LittleEndian
///
/// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet.
public var convertedEndian: NumericValue {
self as! Self.NumericValue
}
}
extension Double: Scalar, Verifiable {
public typealias NumericValue = UInt64
public var convertedEndian: UInt64 {
bitPattern.littleEndian
}
}
extension Float32: Scalar, Verifiable {
public typealias NumericValue = UInt32
public var convertedEndian: UInt32 {
bitPattern.littleEndian
}
}
extension Bool: Scalar, Verifiable {
public var convertedEndian: UInt8 {
self == true ? 1 : 0
}
public typealias NumericValue = UInt8
}
extension Int: Scalar, Verifiable {
public typealias NumericValue = Int
}
extension Int8: Scalar, Verifiable {
public typealias NumericValue = Int8
}
extension Int16: Scalar, Verifiable {
public typealias NumericValue = Int16
}
extension Int32: Scalar, Verifiable {
public typealias NumericValue = Int32
}
extension Int64: Scalar, Verifiable {
public typealias NumericValue = Int64
}
extension UInt8: Scalar, Verifiable {
public typealias NumericValue = UInt8
}
extension UInt16: Scalar, Verifiable {
public typealias NumericValue = UInt16
}
extension UInt32: Scalar, Verifiable {
public typealias NumericValue = UInt32
}
extension UInt64: Scalar, Verifiable {
public typealias NumericValue = UInt64
}
public func FlatBuffersVersion_24_12_23() {}
@@ -0,0 +1,55 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Enum is a protocol that all flatbuffers enums should conform to
/// Since it allows us to get the actual `ByteSize` and `Value` from
/// a swift enum.
public protocol Enum {
/// associatedtype that the type of the enum should conform to
associatedtype T: Scalar & Verifiable
/// Size of the current associatedtype in the enum
static var byteSize: Int { get }
/// The current value the enum hosts
var value: T { get }
}
extension Enum where Self: Verifiable {
/// Verifies that the current value is which the bounds of the buffer, and if
/// the current `Value` is aligned properly
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Errors coming from `inBuffer` function
public static func verify<T>(
_ verifier: inout Verifier,
at position: Int,
of type: T.Type) throws where T: Verifiable
{
try verifier.inBuffer(position: position, of: type.self)
}
}
/// UnionEnum is a Protocol that allows us to create Union type of enums
/// and their value initializers. Since an `init` was required by
/// the verifier
public protocol UnionEnum: Enum {
init?(value: T) throws
}
@@ -0,0 +1,925 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// ``FlatBufferBuilder`` builds a `FlatBuffer` through manipulating its internal state.
///
/// This is done by creating a ``ByteBuffer`` that hosts the incoming data and
/// has a hardcoded growth limit of `2GiB` which is set by the Flatbuffers standards.
///
/// ```swift
/// var builder = FlatBufferBuilder()
/// ```
/// The builder should be always created as a variable, since it would be passed into the writers
///
@frozen
public struct FlatBufferBuilder {
/// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable
@usableFromInline internal var _vtableStorage = VTableStorage()
/// Flatbuffer data will be written into
@usableFromInline internal var _bb: ByteBuffer
/// Reference Vtables that were already written to the buffer
private var _vtables: [UOffset] = []
/// A check if the buffer is being written into by a different table
private var isNested = false
/// Dictonary that stores a map of all the strings that were written to the buffer
private var stringOffsetMap: [String: Offset] = [:]
/// A check to see if finish(::) was ever called to retreive data object
private var finished = false
/// A check to see if the buffer should serialize Default values
private var serializeDefaults: Bool
/// Current alignment for the buffer
var _minAlignment: Int = 0 {
didSet {
_bb.alignment = _minAlignment
}
}
/// Gives a read access to the buffer's size
public var size: UOffset { _bb.size }
#if !os(WASI)
/// Data representation of the buffer
///
/// Should only be used after ``finish(offset:addPrefix:)`` is called
public var data: Data {
assert(finished, "Data shouldn't be called before finish()")
return Data(
bytes: _bb.memory.advanced(by: _bb.writerIndex),
count: _bb.capacity &- _bb.writerIndex)
}
#endif
/// Returns the underlying bytes in the ``ByteBuffer``
///
/// Note: This should be used with caution.
public var fullSizedByteArray: [UInt8] {
let ptr = UnsafeBufferPointer(
start: _bb.memory.assumingMemoryBound(to: UInt8.self),
count: _bb.capacity)
return Array(ptr)
}
/// Returns the written bytes into the ``ByteBuffer``
///
/// Should only be used after ``finish(offset:addPrefix:)`` is called
public var sizedByteArray: [UInt8] {
assert(finished, "Data shouldn't be called before finish()")
return _bb.underlyingBytes
}
/// Returns the original ``ByteBuffer``
///
/// Returns the current buffer that was just created
/// with the offsets, and data written to it.
public var buffer: ByteBuffer { _bb }
/// Returns a newly created sized ``ByteBuffer``
///
/// returns a new buffer that is sized to the data written
/// to the main buffer
public var sizedBuffer: ByteBuffer {
assert(finished, "Data shouldn't be called before finish()")
return ByteBuffer(
memory: _bb.memory.advanced(by: _bb.reader),
count: Int(_bb.size))
}
// MARK: - Init
/// Initialize the buffer with a size
/// - Parameters:
/// - initialSize: Initial size for the buffer
/// - force: Allows default to be serialized into the buffer
///
/// This initializes a new builder with an initialSize that would initialize
/// a new ``ByteBuffer``. ``FlatBufferBuilder`` by default doesnt serialize defaults
/// however the builder can be force by passing true for `serializeDefaults`
public init(
initialSize: Int32 = 1024,
serializeDefaults force: Bool = false)
{
assert(initialSize > 0, "Size should be greater than zero!")
guard isLitteEndian else {
fatalError(
"Reading/Writing a buffer in big endian machine is not supported on swift")
}
serializeDefaults = force
_bb = ByteBuffer(initialSize: Int(initialSize))
}
/// Clears the builder and the buffer from the written data.
mutating public func clear() {
_minAlignment = 0
isNested = false
stringOffsetMap.removeAll(keepingCapacity: true)
_vtables.removeAll(keepingCapacity: true)
_vtableStorage.clear()
_bb.clear()
}
// MARK: - Create Tables
/// Checks if the required fields were serialized into the buffer
/// - Parameters:
/// - table: offset for the table
/// - fields: Array of all the important fields to be serialized
///
/// *NOTE: Never call this function, this is only supposed to be called
/// by the generated code*
@inline(__always)
mutating public func require(table: Offset, fields: [Int32]) {
for index in stride(from: 0, to: fields.count, by: 1) {
let start = _bb.capacity &- Int(table.o)
let startTable = start &- Int(_bb.read(def: Int32.self, position: start))
let isOkay = _bb.read(
def: VOffset.self,
position: startTable &+ Int(fields[index])) != 0
assert(isOkay, "Flatbuffers requires the following field")
}
}
/// Finished the buffer by adding the file id and then calling finish
/// - Parameters:
/// - offset: Offset of the table
/// - fileId: Takes the fileId
/// - prefix: if false it wont add the size of the buffer
///
/// ``finish(offset:fileId:addPrefix:)`` should be called at the end of creating
/// a table
/// ```swift
/// var root = SomeObject
/// .createObject(&builder,
/// name: nameOffset)
/// builder.finish(
/// offset: root,
/// fileId: "ax1a",
/// addPrefix: true)
/// ```
/// File id would append a file id name at the end of the written bytes before,
/// finishing the buffer.
///
/// Whereas, if `addPrefix` is true, the written bytes would
/// include the size of the current buffer.
mutating public func finish(
offset: Offset,
fileId: String,
addPrefix prefix: Bool = false)
{
let size = MemoryLayout<UOffset>.size
preAlign(
len: size &+ (prefix ? size : 0) &+ FileIdLength,
alignment: _minAlignment)
assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4")
_bb.push(string: fileId, len: 4)
finish(offset: offset, addPrefix: prefix)
}
/// Finished the buffer by adding the file id, offset, and prefix to it.
/// - Parameters:
/// - offset: Offset of the table
/// - prefix: if false it wont add the size of the buffer
///
/// ``finish(offset:addPrefix:)`` should be called at the end of creating
/// a table
/// ```swift
/// var root = SomeObject
/// .createObject(&builder,
/// name: nameOffset)
/// builder.finish(
/// offset: root,
/// addPrefix: true)
/// ```
/// If `addPrefix` is true, the written bytes would
/// include the size of the current buffer.
mutating public func finish(
offset: Offset,
addPrefix prefix: Bool = false)
{
notNested()
let size = MemoryLayout<UOffset>.size
preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment)
push(element: refer(to: offset.o))
if prefix { push(element: _bb.size) }
_vtableStorage.clear()
finished = true
}
/// ``startTable(with:)`` will let the builder know, that a new object is being serialized.
///
/// The function will fatalerror if called while there is another object being serialized.
/// ```swift
/// let start = Monster
/// .startMonster(&fbb)
/// ```
/// - Parameter numOfFields: Number of elements to be written to the buffer
/// - Returns: Offset of the newly started table
@inline(__always)
mutating public func startTable(with numOfFields: Int) -> UOffset {
notNested()
isNested = true
_vtableStorage.start(count: numOfFields)
return _bb.size
}
/// ``endTable(at:)`` will let the ``FlatBufferBuilder`` know that the
/// object that's written to it is completed
///
/// This would be called after all the elements are serialized,
/// it will add the current vtable into the ``ByteBuffer``.
/// The functions will `fatalError` in case the object is called
/// without ``startTable(with:)``, or the object has exceeded the limit of 2GB.
///
/// - Parameter startOffset:Start point of the object written
/// - returns: The root of the table
mutating public func endTable(at startOffset: UOffset) -> UOffset {
assert(isNested, "Calling endtable without calling starttable")
let sizeofVoffset = MemoryLayout<VOffset>.size
let vTableOffset = push(element: SOffset(0))
let tableObjectSize = vTableOffset &- startOffset
assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes")
let _max = Int(_vtableStorage.maxOffset) &+ sizeofVoffset
_bb.fill(padding: _max)
_bb.write(
value: VOffset(tableObjectSize),
index: _bb.writerIndex &+ sizeofVoffset,
direct: true)
_bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true)
var itr = 0
while itr < _vtableStorage.writtenIndex {
let loaded = _vtableStorage.load(at: itr)
itr = itr &+ _vtableStorage.size
guard loaded.offset != 0 else { continue }
let _index = (_bb.writerIndex &+ Int(loaded.position))
_bb.write(
value: VOffset(vTableOffset &- loaded.offset),
index: _index,
direct: true)
}
_vtableStorage.clear()
let vt_use = _bb.size
var isAlreadyAdded: Int?
let vt2 = _bb.memory.advanced(by: _bb.writerIndex)
let len2 = vt2.load(fromByteOffset: 0, as: Int16.self)
for index in stride(from: 0, to: _vtables.count, by: 1) {
let position = _bb.capacity &- Int(_vtables[index])
let vt1 = _bb.memory.advanced(by: position)
let len1 = _bb.read(def: Int16.self, position: position)
if len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2)) { continue }
isAlreadyAdded = Int(_vtables[index])
break
}
if let offset = isAlreadyAdded {
let vTableOff = Int(vTableOffset)
let space = _bb.capacity &- vTableOff
_bb.write(value: Int32(offset &- vTableOff), index: space, direct: true)
_bb.pop(_bb.capacity &- space)
} else {
_bb.write(value: Int32(vt_use &- vTableOffset), index: Int(vTableOffset))
_vtables.append(_bb.size)
}
isNested = false
return vTableOffset
}
// MARK: - Builds Buffer
/// Asserts to see if the object is not nested
@inline(__always)
@usableFromInline
mutating internal func notNested() {
assert(!isNested, "Object serialization must not be nested")
}
/// Changes the minimuim alignment of the buffer
/// - Parameter size: size of the current alignment
@inline(__always)
@usableFromInline
mutating internal func minAlignment(size: Int) {
if size > _minAlignment {
_minAlignment = size
}
}
/// Gets the padding for the current element
/// - Parameters:
/// - bufSize: Current size of the buffer + the offset of the object to be written
/// - elementSize: Element size
@inline(__always)
@usableFromInline
mutating internal func padding(
bufSize: UInt32,
elementSize: UInt32) -> UInt32
{
((~bufSize) &+ 1) & (elementSize - 1)
}
/// Prealigns the buffer before writting a new object into the buffer
/// - Parameters:
/// - len:Length of the object
/// - alignment: Alignment type
@inline(__always)
@usableFromInline
mutating internal func preAlign(len: Int, alignment: Int) {
minAlignment(size: alignment)
_bb.fill(padding: Int(padding(
bufSize: _bb.size &+ UOffset(len),
elementSize: UOffset(alignment))))
}
/// Prealigns the buffer before writting a new object into the buffer
/// - Parameters:
/// - len: Length of the object
/// - type: Type of the object to be written
@inline(__always)
@usableFromInline
mutating internal func preAlign<T: Scalar>(len: Int, type: T.Type) {
preAlign(len: len, alignment: MemoryLayout<T>.size)
}
/// Refers to an object that's written in the buffer
/// - Parameter off: the objects index value
@inline(__always)
@usableFromInline
mutating internal func refer(to off: UOffset) -> UOffset {
let size = MemoryLayout<UOffset>.size
preAlign(len: size, alignment: size)
return _bb.size &- off &+ UInt32(size)
}
/// Tracks the elements written into the buffer
/// - Parameters:
/// - offset: The offset of the element witten
/// - position: The position of the element
@inline(__always)
@usableFromInline
mutating internal func track(offset: UOffset, at position: VOffset) {
_vtableStorage.add(loc: (offset: offset, position: position))
}
// MARK: - Inserting Vectors
/// ``startVector(_:elementSize:)`` creates a new vector within buffer
///
/// The function checks if there is a current object being written, if
/// the check passes it creates a buffer alignment of `length * elementSize`
/// ```swift
/// builder.startVector(
/// int32Values.count, elementSize: 4)
/// ```
///
/// - Parameters:
/// - len: Length of vector to be created
/// - elementSize: Size of object type to be written
@inline(__always)
mutating public func startVector(_ len: Int, elementSize: Int) {
notNested()
isNested = true
preAlign(len: len &* elementSize, type: UOffset.self)
preAlign(len: len &* elementSize, alignment: elementSize)
}
/// ``endVector(len:)`` ends the currently created vector
///
/// Calling ``endVector(len:)`` requires the length, of the current
/// vector. The length would be pushed to indicate the count of numbers
/// within the vector. If ``endVector(len:)`` is called without
/// ``startVector(_:elementSize:)`` it asserts.
///
/// ```swift
/// let vectorOffset = builder.
/// endVector(len: int32Values.count)
/// ```
///
/// - Parameter len: Length of the buffer
/// - Returns: Returns the current ``Offset`` in the ``ByteBuffer``
@inline(__always)
mutating public func endVector(len: Int) -> Offset {
assert(isNested, "Calling endVector without calling startVector")
isNested = false
return Offset(offset: push(element: Int32(len)))
}
/// Creates a vector of type ``Scalar`` into the ``ByteBuffer``
///
/// ``createVector(_:)-4swl0`` writes a vector of type Scalars into
/// ``ByteBuffer``. This is a convenient method instead of calling,
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
/// ```swift
/// let vectorOffset = builder.
/// createVector([1, 2, 3, 4])
/// ```
///
/// The underlying implementation simply calls ``createVector(_:size:)-4lhrv``
///
/// - Parameter elements: elements to be written into the buffer
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector<T: Scalar>(_ elements: [T]) -> Offset {
createVector(elements, size: elements.count)
}
/// Creates a vector of type Scalar in the buffer
///
/// ``createVector(_:)-4swl0`` writes a vector of type Scalars into
/// ``ByteBuffer``. This is a convenient method instead of calling,
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
/// ```swift
/// let vectorOffset = builder.
/// createVector([1, 2, 3, 4], size: 4)
/// ```
///
/// - Parameter elements: Elements to be written into the buffer
/// - Parameter size: Count of elements
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector<T: Scalar>(
_ elements: [T],
size: Int) -> Offset
{
let size = size
startVector(size, elementSize: MemoryLayout<T>.size)
_bb.push(elements: elements)
return endVector(len: size)
}
#if swift(>=5.0) && !os(WASI)
@inline(__always)
/// Creates a vector of bytes in the buffer.
///
/// Allows creating a vector from `Data` without copying to a `[UInt8]`
///
/// - Parameter bytes: bytes to be written into the buffer
/// - Returns: ``Offset`` of the vector
mutating public func createVector(bytes: ContiguousBytes) -> Offset {
let size = bytes.withUnsafeBytes { ptr in ptr.count }
startVector(size, elementSize: MemoryLayout<UInt8>.size)
_bb.push(bytes: bytes)
return endVector(len: size)
}
#endif
/// Creates a vector of type ``Enum`` into the ``ByteBuffer``
///
/// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into
/// ``ByteBuffer``. This is a convenient method instead of calling,
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
/// ```swift
/// let vectorOffset = builder.
/// createVector([.swift, .cpp])
/// ```
///
/// The underlying implementation simply calls ``createVector(_:size:)-7cx6z``
///
/// - Parameter elements: elements to be written into the buffer
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector<T: Enum>(_ elements: [T]) -> Offset {
createVector(elements, size: elements.count)
}
/// Creates a vector of type ``Enum`` into the ``ByteBuffer``
///
/// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into
/// ``ByteBuffer``. This is a convenient method instead of calling,
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
/// ```swift
/// let vectorOffset = builder.
/// createVector([.swift, .cpp])
/// ```
///
/// - Parameter elements: Elements to be written into the buffer
/// - Parameter size: Count of elements
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector<T: Enum>(
_ elements: [T],
size: Int) -> Offset
{
let size = size
startVector(size, elementSize: T.byteSize)
for index in stride(from: elements.count, to: 0, by: -1) {
_bb.push(value: elements[index &- 1].value, len: T.byteSize)
}
return endVector(len: size)
}
/// Creates a vector of already written offsets
///
/// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into
/// ``ByteBuffer``. This is a convenient method instead of calling,
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``.
///
/// The underlying implementation simply calls ``createVector(ofOffsets:len:)``
///
/// ```swift
/// let namesOffsets = builder.
/// createVector(ofOffsets: [name1, name2])
/// ```
/// - Parameter offsets: Array of offsets of type ``Offset``
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector(ofOffsets offsets: [Offset]) -> Offset {
createVector(ofOffsets: offsets, len: offsets.count)
}
/// Creates a vector of already written offsets
///
/// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into
/// ``ByteBuffer``. This is a convenient method instead of calling,
/// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
///
/// ```swift
/// let namesOffsets = builder.
/// createVector(ofOffsets: [name1, name2])
/// ```
///
/// - Parameter offsets: Array of offsets of type ``Offset``
/// - Parameter size: Count of elements
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector(
ofOffsets offsets: [Offset],
len: Int) -> Offset
{
startVector(len, elementSize: MemoryLayout<Offset>.size)
for index in stride(from: offsets.count, to: 0, by: -1) {
push(element: offsets[index &- 1])
}
return endVector(len: len)
}
/// Creates a vector of strings
///
/// ``createVector(ofStrings:)`` creates a vector of `String` into
/// ``ByteBuffer``. This is a convenient method instead of manually
/// creating the string offsets, you simply pass it to this function
/// and it would write the strings into the ``ByteBuffer``.
/// After that it calls ``createVector(ofOffsets:)``
///
/// ```swift
/// let namesOffsets = builder.
/// createVector(ofStrings: ["Name", "surname"])
/// ```
///
/// - Parameter str: Array of string
/// - returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector(ofStrings str: [String]) -> Offset {
var offsets: [Offset] = []
for index in stride(from: 0, to: str.count, by: 1) {
offsets.append(create(string: str[index]))
}
return createVector(ofOffsets: offsets)
}
/// Creates a vector of type ``NativeStruct``.
///
/// Any swift struct in the generated code, should confirm to
/// ``NativeStruct``. Since the generated swift structs are padded
/// to the `FlatBuffers` standards.
///
/// ```swift
/// let offsets = builder.
/// createVector(ofStructs: [NativeStr(num: 1), NativeStr(num: 2)])
/// ```
///
/// - Parameter structs: A vector of ``NativeStruct``
/// - Returns: ``Offset`` of the vector
@inline(__always)
mutating public func createVector<T: NativeStruct>(ofStructs structs: [T])
-> Offset
{
startVector(
structs.count * MemoryLayout<T>.size,
elementSize: MemoryLayout<T>.alignment)
_bb.push(elements: structs)
return endVector(len: structs.count)
}
// MARK: - Inserting Structs
/// Writes a ``NativeStruct`` into the ``ByteBuffer``
///
/// Adds a native struct that's build and padded according
/// to `FlatBuffers` standards. with a predefined position.
///
/// ```swift
/// let offset = builder.create(
/// struct: NativeStr(num: 1),
/// position: 10)
/// ```
///
/// - Parameters:
/// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer``
/// - position: The predefined position of the object
/// - Returns: ``Offset`` of written struct
@inline(__always)
@discardableResult
mutating public func create<T: NativeStruct>(
struct s: T, position: VOffset) -> Offset
{
let offset = create(struct: s)
_vtableStorage.add(
loc: (offset: _bb.size, position: VOffset(position)))
return offset
}
/// Writes a ``NativeStruct`` into the ``ByteBuffer``
///
/// Adds a native struct that's build and padded according
/// to `FlatBuffers` standards, directly into the buffer without
/// a predefined position.
///
/// ```swift
/// let offset = builder.create(
/// struct: NativeStr(num: 1))
/// ```
///
/// - Parameters:
/// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer``
/// - Returns: ``Offset`` of written struct
@inline(__always)
@discardableResult
mutating public func create<T: NativeStruct>(
struct s: T) -> Offset
{
let size = MemoryLayout<T>.size
preAlign(len: size, alignment: MemoryLayout<T>.alignment)
_bb.push(struct: s, size: size)
return Offset(offset: _bb.size)
}
// MARK: - Inserting Strings
/// Insets a string into the buffer of type `UTF8`
///
/// Adds a swift string into ``ByteBuffer`` by encoding it
/// using `UTF8`
///
/// ```swift
/// let nameOffset = builder
/// .create(string: "welcome")
/// ```
///
/// - Parameter str: String to be serialized
/// - returns: ``Offset`` of inserted string
@inline(__always)
mutating public func create(string str: String?) -> Offset {
guard let str = str else { return Offset() }
let len = str.utf8.count
notNested()
preAlign(len: len &+ 1, type: UOffset.self)
_bb.fill(padding: 1)
_bb.push(string: str, len: len)
push(element: UOffset(len))
return Offset(offset: _bb.size)
}
/// Insets a shared string into the buffer of type `UTF8`
///
/// Adds a swift string into ``ByteBuffer`` by encoding it
/// using `UTF8`. The function will check if the string,
/// is already written to the ``ByteBuffer``
///
/// ```swift
/// let nameOffset = builder
/// .createShared(string: "welcome")
///
///
/// let secondOffset = builder
/// .createShared(string: "welcome")
///
/// assert(nameOffset.o == secondOffset.o)
/// ```
///
/// - Parameter str: String to be serialized
/// - returns: ``Offset`` of inserted string
@inline(__always)
mutating public func createShared(string str: String?) -> Offset {
guard let str = str else { return Offset() }
if let offset = stringOffsetMap[str] {
return offset
}
let offset = create(string: str)
stringOffsetMap[str] = offset
return offset
}
// MARK: - Inseting offsets
/// Writes the ``Offset`` of an already written table
///
/// Writes the ``Offset`` of a table if not empty into the
/// ``ByteBuffer``
///
/// - Parameters:
/// - offset: ``Offset`` of another object to be written
/// - position: The predefined position of the object
@inline(__always)
mutating public func add(offset: Offset, at position: VOffset) {
if offset.isEmpty { return }
add(element: refer(to: offset.o), def: 0, at: position)
}
/// Pushes a value of type ``Offset`` into the ``ByteBuffer``
/// - Parameter o: ``Offset``
/// - returns: Current position of the ``Offset``
@inline(__always)
@discardableResult
mutating public func push(element o: Offset) -> UOffset {
push(element: refer(to: o.o))
}
// MARK: - Inserting Scalars to Buffer
/// Writes a ``Scalar`` value into ``ByteBuffer``
///
/// ``add(element:def:at:)`` takes in a default value, and current value
/// and the position within the `VTable`. The default value would not
/// be serialized if the value is the same as the current value or
/// `serializeDefaults` is equal to false.
///
/// If serializing defaults is important ``init(initialSize:serializeDefaults:)``,
/// passing true for `serializeDefaults` would do the job.
///
/// ```swift
/// // Adds 10 to the buffer
/// builder.add(element: Int(10), def: 1, position 12)
/// ```
///
/// *NOTE: Never call this manually*
///
/// - Parameters:
/// - element: Element to insert
/// - def: Default value for that element
/// - position: The predefined position of the element
@inline(__always)
mutating public func add<T: Scalar>(
element: T,
def: T,
at position: VOffset)
{
if element == def && !serializeDefaults { return }
track(offset: push(element: element), at: position)
}
/// Writes a optional ``Scalar`` value into ``ByteBuffer``
///
/// Takes an optional value to be written into the ``ByteBuffer``
///
/// *NOTE: Never call this manually*
///
/// - Parameters:
/// - element: Optional element of type scalar
/// - position: The predefined position of the element
@inline(__always)
mutating public func add<T: Scalar>(element: T?, at position: VOffset) {
guard let element = element else { return }
track(offset: push(element: element), at: position)
}
/// Pushes a values of type ``Scalar`` into the ``ByteBuffer``
///
/// *NOTE: Never call this manually*
///
/// - Parameter element: Element to insert
/// - returns: position of the Element
@inline(__always)
@discardableResult
mutating public func push<T: Scalar>(element: T) -> UOffset {
let size = MemoryLayout<T>.size
preAlign(
len: size,
alignment: size)
_bb.push(value: element, len: size)
return _bb.size
}
}
extension FlatBufferBuilder: CustomDebugStringConvertible {
public var debugDescription: String {
"""
buffer debug:
\(_bb)
builder debug:
{ finished: \(finished), serializeDefaults: \(
serializeDefaults), isNested: \(isNested) }
"""
}
typealias FieldLoc = (offset: UOffset, position: VOffset)
/// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer
@usableFromInline
internal class VTableStorage {
/// Memory check since deallocating each time we want to clear would be expensive
/// and memory leaks would happen if we dont deallocate the first allocated memory.
/// memory is promised to be available before adding `FieldLoc`
private var memoryInUse = false
/// Size of FieldLoc in memory
let size = MemoryLayout<FieldLoc>.stride
/// Memeory buffer
var memory: UnsafeMutableRawBufferPointer!
/// Capacity of the current buffer
var capacity: Int = 0
/// Maximuim offset written to the class
var maxOffset: VOffset = 0
/// number of fields written into the buffer
var numOfFields: Int = 0
/// Last written Index
var writtenIndex: Int = 0
/// Creates the memory to store the buffer in
@usableFromInline
@inline(__always)
init() {
memory = UnsafeMutableRawBufferPointer.allocate(
byteCount: 0,
alignment: 0)
}
@inline(__always)
deinit {
memory.deallocate()
}
/// Builds a buffer with byte count of fieldloc.size * count of field numbers
/// - Parameter count: number of fields to be written
@inline(__always)
func start(count: Int) {
assert(count >= 0, "number of fields should NOT be negative")
let capacity = count &* size
ensure(space: capacity)
}
/// Adds a FieldLoc into the buffer, which would track how many have been written,
/// and max offset
/// - Parameter loc: Location of encoded element
@inline(__always)
func add(loc: FieldLoc) {
memory.baseAddress?.advanced(by: writtenIndex).storeBytes(
of: loc,
as: FieldLoc.self)
writtenIndex = writtenIndex &+ size
numOfFields = numOfFields &+ 1
maxOffset = max(loc.position, maxOffset)
}
/// Clears the data stored related to the encoded buffer
@inline(__always)
func clear() {
maxOffset = 0
numOfFields = 0
writtenIndex = 0
}
/// Ensure that the buffer has enough space instead of recreating the buffer each time.
/// - Parameter space: space required for the new vtable
@inline(__always)
func ensure(space: Int) {
guard space &+ writtenIndex > capacity else { return }
memory.deallocate()
memory = UnsafeMutableRawBufferPointer.allocate(
byteCount: space,
alignment: size)
capacity = space
}
/// Loads an object of type `FieldLoc` from buffer memory
/// - Parameter index: index of element
/// - Returns: a FieldLoc at index
@inline(__always)
func load(at index: Int) -> FieldLoc {
memory.load(fromByteOffset: index, as: FieldLoc.self)
}
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// NativeStruct is a protocol that indicates if the struct is a native `swift` struct
/// since now we will be serializing native structs into the buffer.
public protocol NativeStruct {}
/// FlatbuffersInitializable is a protocol that allows any object to be
/// Initialized from a ByteBuffer
public protocol FlatbuffersInitializable {
/// Any flatbuffers object that confirms to this protocol is going to be
/// initializable through this initializer
init(_ bb: ByteBuffer, o: Int32)
}
/// FlatbufferObject structures all the Flatbuffers objects
public protocol FlatBufferObject: FlatbuffersInitializable {
var __buffer: ByteBuffer! { get }
}
/// ``ObjectAPIPacker`` is a protocol that allows object to pack and unpack from a
/// ``NativeObject`` to a flatbuffers Object and vice versa.
public protocol ObjectAPIPacker {
/// associatedtype to the object that should be unpacked.
associatedtype T
/// ``pack(_:obj:)-3ptws`` tries to pacs the variables of a native Object into the `ByteBuffer` by using
/// a FlatBufferBuilder
/// - Parameters:
/// - builder: FlatBufferBuilder that will host incoming data
/// - obj: Object of associatedtype to the current implementer
///
/// ``pack(_:obj:)-3ptws`` can be called by passing through an already initialized ``FlatBufferBuilder``
/// or it can be called by using the public API that will create a new ``FlatBufferBuilder``
static func pack(_ builder: inout FlatBufferBuilder, obj: inout T?) -> Offset
/// ``pack(_:obj:)-20ipk`` packs the variables of a native Object into the `ByteBuffer` by using
/// the FlatBufferBuilder
/// - Parameters:
/// - builder: FlatBufferBuilder that will host incoming data
/// - obj: Object of associatedtype to the current implementer
///
/// ``pack(_:obj:)-20ipk`` can be called by passing through an already initialized ``FlatBufferBuilder``
/// or it can be called by using the public API that will create a new ``FlatBufferBuilder``
static func pack(_ builder: inout FlatBufferBuilder, obj: inout T) -> Offset
/// ``unpack()`` unpacks a ``FlatBuffers`` object into a Native swift object.
mutating func unpack() -> T
}
@@ -0,0 +1,37 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// FlatBuffersUtils hosts some utility functions that might be useful
public enum FlatBuffersUtils {
/// Gets the size of the prefix
/// - Parameter bb: Flatbuffer object
public static func getSizePrefix(bb: ByteBuffer) -> Int32 {
bb.read(def: Int32.self, position: bb.reader)
}
/// Removes the prefix by duplicating the Flatbuffer this call is expensive since its
/// creates a new buffer use `readPrefixedSizeCheckedRoot` instead
/// unless a completely new buffer is required
/// - Parameter bb: Flatbuffer object
///
///
public static func removeSizePrefix(bb: ByteBuffer) -> ByteBuffer {
bb.duplicate(removing: MemoryLayout<Int32>.size)
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Collection of thrown from the Flatbuffer verifier
public enum FlatbuffersErrors: Error, Equatable {
/// Thrown when trying to verify a buffer that doesnt have the length of an ID
case bufferDoesntContainID
/// Thrown when verifying a file id that doesnt match buffer id
case bufferIdDidntMatchPassedId
/// Prefixed size doesnt match the current (readable) buffer size
case prefixedSizeNotEqualToBufferSize
/// Thrown when buffer is bigger than the allowed 2GiB
case exceedsMaxSizeAllowed
/// Thrown when there is an missaligned pointer at position
/// of type
case missAlignedPointer(position: Int, type: String)
/// Thrown when trying to read a value that goes out of the
/// current buffer bounds
case outOfBounds(position: UInt, end: Int)
/// Thrown when the signed offset is out of the bounds of the
/// current buffer
case signedOffsetOutOfBounds(offset: Int, position: Int)
/// Thrown when a required field doesnt exist within the buffer
case requiredFieldDoesntExist(position: VOffset, name: String)
/// Thrown when a string is missing its NULL Terminator `\0`,
/// this can be disabled in the `VerifierOptions`
case missingNullTerminator(position: Int, str: String?)
/// Thrown when the verifier has reached the maximum tables allowed,
/// this can be disabled in the `VerifierOptions`
case maximumTables
/// Thrown when the verifier has reached the maximum depth allowed,
/// this can be disabled in the `VerifierOptions`
case maximumDepth
/// Thrown when the verifier is presented with an unknown union case
case unknownUnionCase
/// thrown when a value for a union is not found within the buffer
case valueNotFound(key: Int?, keyName: String, field: Int?, fieldName: String)
/// thrown when the size of the keys vector doesnt match fields vector
case unionVectorSize(
keyVectorSize: Int,
fieldVectorSize: Int,
unionKeyName: String,
fieldName: String)
case apparentSizeTooLarge
}
#if !os(WASI)
extension FlatbuffersErrors {
public static func == (
lhs: FlatbuffersErrors,
rhs: FlatbuffersErrors) -> Bool
{
lhs.localizedDescription == rhs.localizedDescription
}
}
#endif
@@ -0,0 +1,47 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
extension Int {
/// Moves the current int into the nearest power of two
///
/// This is used since the UnsafeMutableRawPointer will face issues when writing/reading
/// if the buffer alignment exceeds that actual size of the buffer
var convertToPowerofTwo: Int {
guard self > 0 else { return 1 }
var n = UOffset(self)
#if arch(arm) || arch(i386)
let max = UInt32(Int.max)
#else
let max = UInt32.max
#endif
n -= 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
if n != max {
n += 1
}
return Int(n)
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// FlatBufferGRPCMessage protocol that should allow us to invoke
/// initializers directly from the GRPC generated code
public protocol FlatBufferGRPCMessage {
/// Raw pointer which would be pointing to the beginning of the readable bytes
var rawPointer: UnsafeMutableRawPointer { get }
/// Size of readable bytes in the buffer
var size: Int { get }
init(byteBuffer: ByteBuffer)
}
/// Message is a wrapper around Buffers to to able to send Flatbuffers `Buffers` through the
/// GRPC library
public struct Message<T: FlatBufferObject>: FlatBufferGRPCMessage {
internal var buffer: ByteBuffer
/// Returns the an object of type T that would be read from the buffer
public var object: T {
T.init(
buffer,
o: Int32(buffer.read(def: UOffset.self, position: buffer.reader)) +
Int32(buffer.reader))
}
public var rawPointer: UnsafeMutableRawPointer {
buffer.memory.advanced(by: buffer.reader) }
public var size: Int { Int(buffer.size) }
/// Initializes the message with the type Flatbuffer.Bytebuffer that is transmitted over
/// GRPC
/// - Parameter byteBuffer: Flatbuffer ByteBuffer object
public init(byteBuffer: ByteBuffer) {
buffer = byteBuffer
}
/// Initializes the message by copying the buffer to the message to be sent.
/// from the builder
/// - Parameter builder: FlatbufferBuilder that has the bytes created in
/// - Note: Use `builder.finish(offset)` before passing the builder without prefixing anything to it
public init(builder: inout FlatBufferBuilder) {
buffer = builder.sizedBuffer
builder.clear()
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Mutable is a protocol that allows us to mutate Scalar values within a ``ByteBuffer``
public protocol Mutable {
/// makes Flatbuffer accessed within the Protocol
var bb: ByteBuffer { get }
/// makes position of the ``Table``/``Struct`` accessed within the Protocol
var position: Int32 { get }
}
extension Mutable {
/// Mutates the memory in the buffer, this is only called from the access function of ``Table`` and ``struct``
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
func mutate<T: Scalar>(value: T, o: Int32) -> Bool {
guard o != 0 else { return false }
bb.write(value: value, index: Int(o), direct: true)
return true
}
}
extension Mutable where Self == Table {
/// Mutates a value by calling mutate with respect to the position in a ``Table``
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
guard index != 0 else { return false }
return mutate(value: value, o: index + position)
}
/// Directly mutates the element by calling mutate
///
/// Mutates the Element at index ignoring the current position by calling mutate
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
mutate(value: value, o: index)
}
}
extension Mutable where Self == Struct {
/// Mutates a value by calling mutate with respect to the position in the struct
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
mutate(value: value, o: index + position)
}
/// Directly mutates the element by calling mutate
///
/// Mutates the Element at index ignoring the current position by calling mutate
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
mutate(value: value, o: index)
}
}
extension Struct: Mutable {}
extension Table: Mutable {}
@@ -0,0 +1,53 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// NativeObject is a protocol that all of the `Object-API` generated code should be
/// conforming to since it allows developers the ease of use to pack and unpack their
/// Flatbuffers objects
public protocol NativeObject {}
extension NativeObject {
/// Serialize is a helper function that serializes the data from the Object API to a bytebuffer directly th
/// - Parameter type: Type of the Flatbuffer object
/// - Returns: returns the encoded sized ByteBuffer
public func serialize<T: ObjectAPIPacker>(type: T.Type) -> ByteBuffer
where T.T == Self
{
var builder = FlatBufferBuilder(initialSize: 1024)
return serialize(builder: &builder, type: type.self)
}
/// Serialize is a helper function that serializes the data from the Object API to a bytebuffer directly.
///
/// - Parameters:
/// - builder: A FlatBufferBuilder
/// - type: Type of the Flatbuffer object
/// - Returns: returns the encoded sized ByteBuffer
/// - Note: The `serialize(builder:type)` can be considered as a function that allows you to create smaller builder instead of the default `1024`.
/// It can be considered less expensive in terms of memory allocation
public func serialize<T: ObjectAPIPacker>(
builder: inout FlatBufferBuilder,
type: T.Type) -> ByteBuffer where T.T == Self
{
var s = self
let root = type.pack(&builder, obj: &s)
builder.finish(offset: root)
return builder.sizedBuffer
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Offset object for all the Objects that are written into the buffer
public struct Offset {
/// Offset of the object in the buffer
public var o: UOffset
/// Returns false if the offset is equal to zero
public var isEmpty: Bool { o == 0 }
public init(offset: UOffset) { o = offset }
public init() { o = 0 }
}
@@ -0,0 +1,116 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Takes in a prefixed sized buffer, where the prefixed size would be skipped.
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
/// - Parameters:
/// - byteBuffer: Buffer that needs to be checked and read
/// - options: Verifier options
/// - Throws: FlatbuffersErrors
/// - Returns: Returns a valid, checked Flatbuffers object
///
/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in
/// the ``ByteBuffer`` and verifies the buffer by calling ``getCheckedRoot(byteBuffer:options:)``
public func getPrefixedSizeCheckedRoot<T: FlatBufferObject & Verifiable>(
byteBuffer: inout ByteBuffer,
fileId: String? = nil,
options: VerifierOptions = .init()) throws -> T
{
byteBuffer.skipPrefix()
return try getCheckedRoot(
byteBuffer: &byteBuffer,
fileId: fileId,
options: options)
}
/// Takes in a prefixed sized buffer, where we check if the sized buffer is equal to prefix size.
/// And would verify that the buffer passed is a valid `Flatbuffers` Object.
/// - Parameters:
/// - byteBuffer: Buffer that needs to be checked and read
/// - options: Verifier options
/// - Throws: FlatbuffersErrors
/// - Returns: Returns a valid, checked Flatbuffers object
///
/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in
/// the ``ByteBuffer`` and verifies the buffer by calling ``getCheckedRoot(byteBuffer:options:)``
public func getCheckedPrefixedSizeRoot<T: FlatBufferObject & Verifiable>(
byteBuffer: inout ByteBuffer,
fileId: String? = nil,
options: VerifierOptions = .init()) throws -> T
{
let prefix = byteBuffer.skipPrefix()
if prefix != byteBuffer.size {
throw FlatbuffersErrors.prefixedSizeNotEqualToBufferSize
}
return try getCheckedRoot(
byteBuffer: &byteBuffer,
fileId: fileId,
options: options)
}
/// Takes in a prefixed sized buffer, where the prefixed size would be skipped.
/// Returns a `NON-Checked` flatbuffers object
/// - Parameter byteBuffer: Buffer that contains data
/// - Returns: Returns a Flatbuffers object
///
/// ``getPrefixedSizeCheckedRoot(byteBuffer:options:)`` would skip the first Bytes in
/// the ``ByteBuffer`` and then calls ``getRoot(byteBuffer:)``
public func getPrefixedSizeRoot<T: FlatBufferObject>(
byteBuffer: inout ByteBuffer)
-> T
{
byteBuffer.skipPrefix()
return getRoot(byteBuffer: &byteBuffer)
}
/// Verifies that the buffer passed is a valid `Flatbuffers` Object.
/// - Parameters:
/// - byteBuffer: Buffer that needs to be checked and read
/// - options: Verifier options
/// - Throws: FlatbuffersErrors
/// - Returns: Returns a valid, checked Flatbuffers object
///
/// ``getCheckedRoot(byteBuffer:options:)`` Takes in a ``ByteBuffer`` and verifies
/// that by creating a ``Verifier`` and checkes if all the `Bytes` and correctly aligned
/// and within the ``ByteBuffer`` range.
public func getCheckedRoot<T: FlatBufferObject & Verifiable>(
byteBuffer: inout ByteBuffer,
fileId: String? = nil,
options: VerifierOptions = .init()) throws -> T
{
var verifier = try Verifier(buffer: &byteBuffer, options: options)
if let fileId = fileId {
try verifier.verify(id: fileId)
}
try ForwardOffset<T>.verify(&verifier, at: 0, of: T.self)
return T.init(
byteBuffer,
o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader)) +
Int32(byteBuffer.reader))
}
/// Returns a `NON-Checked` flatbuffers object
/// - Parameter byteBuffer: Buffer that contains data
/// - Returns: Returns a Flatbuffers object
public func getRoot<T: FlatBufferObject>(byteBuffer: inout ByteBuffer) -> T {
T.init(
byteBuffer,
o: Int32(byteBuffer.read(def: UOffset.self, position: byteBuffer.reader)) +
Int32(byteBuffer.reader))
}
@@ -0,0 +1,109 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
extension String: Verifiable {
/// Verifies that the current value is which the bounds of the buffer, and if
/// the current `Value` is aligned properly
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Errors coming from `inBuffer`, `missingNullTerminator` and `outOfBounds`
public static func verify<T>(
_ verifier: inout Verifier,
at position: Int,
of type: T.Type) throws where T: Verifiable
{
let range = try String.verifyRange(&verifier, at: position, of: UInt8.self)
/// Safe &+ since we already check for overflow in verify range
let stringLen = range.start &+ range.count
if stringLen >= verifier.capacity {
throw FlatbuffersErrors.outOfBounds(
position: UInt(clamping: stringLen.magnitude),
end: verifier.capacity)
}
let isNullTerminated = verifier._buffer.read(
def: UInt8.self,
position: stringLen) == 0
if !verifier._options._ignoreMissingNullTerminators && !isNullTerminated {
let str = verifier._buffer.readString(at: range.start, count: range.count)
throw FlatbuffersErrors.missingNullTerminator(
position: position,
str: str)
}
}
}
extension String: FlatbuffersInitializable {
/// Initailizes a string from a Flatbuffers ByteBuffer
/// - Parameters:
/// - bb: ByteBuffer containing the readable string
/// - o: Current position
public init(_ bb: ByteBuffer, o: Int32) {
let v = Int(o)
let count = bb.read(def: Int32.self, position: v)
self = bb.readString(
at: MemoryLayout<Int32>.size + v,
count: Int(count)) ?? ""
}
}
extension String: ObjectAPIPacker {
public static func pack(
_ builder: inout FlatBufferBuilder,
obj: inout String?) -> Offset
{
guard var obj = obj else { return Offset() }
return pack(&builder, obj: &obj)
}
public static func pack(
_ builder: inout FlatBufferBuilder,
obj: inout String) -> Offset
{
builder.create(string: obj)
}
public mutating func unpack() -> String {
self
}
}
extension String: NativeObject {
public func serialize<T: ObjectAPIPacker>(type: T.Type) -> ByteBuffer
where T.T == Self
{
fatalError("serialize should never be called from string directly")
}
public func serialize<T: ObjectAPIPacker>(
builder: inout FlatBufferBuilder,
type: T.Type) -> ByteBuffer where T.T == Self
{
fatalError("serialize should never be called from string directly")
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Struct is a representation of a mutable `Flatbuffers` struct
/// since native structs are value types and cant be mutated
@frozen
public struct Struct {
/// Hosting Bytebuffer
public private(set) var bb: ByteBuffer
/// Current position of the struct
public private(set) var position: Int32
/// Initializer for a mutable flatbuffers struct
/// - Parameters:
/// - bb: Current hosting Bytebuffer
/// - position: Current position for the struct in the ByteBuffer
public init(bb: ByteBuffer, position: Int32 = 0) {
self.bb = bb
self.position = position
}
/// Reads data from the buffer directly at offset O
/// - Parameters:
/// - type: Type of data to be read
/// - o: Current offset of the data
/// - Returns: Data of Type T that conforms to type Scalar
public func readBuffer<T: Scalar>(of type: T.Type, at o: Int32) -> T {
let r = bb.read(def: T.self, position: Int(o + position))
return r
}
}
@@ -0,0 +1,236 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// `Table` is a Flatbuffers object that can read,
/// mutate scalar fields within a valid flatbuffers buffer
@frozen
public struct Table {
/// Hosting Bytebuffer
public private(set) var bb: ByteBuffer
/// Current position of the table within the buffer
public private(set) var position: Int32
/// Initializer for the table interface to allow generated code to read
/// data from memory
/// - Parameters:
/// - bb: ByteBuffer that stores data
/// - position: Current table position
/// - Note: This will `CRASH` if read on a big endian machine
public init(bb: ByteBuffer, position: Int32 = 0) {
guard isLitteEndian else {
fatalError(
"Reading/Writing a buffer in big endian machine is not supported on swift")
}
self.bb = bb
self.position = position
}
/// Gets the offset of the current field within the buffer by reading
/// the vtable
/// - Parameter o: current offset
/// - Returns: offset of field within buffer
public func offset(_ o: Int32) -> Int32 {
let vtable = position - bb.read(def: Int32.self, position: Int(position))
return o < bb
.read(def: VOffset.self, position: Int(vtable)) ? Int32(bb.read(
def: Int16.self,
position: Int(vtable + o))) : 0
}
/// Gets the indirect offset of the current stored object
/// (applicable only for object arrays)
/// - Parameter o: current offset
/// - Returns: offset of field within buffer
public func indirect(_ o: Int32) -> Int32 {
o + bb.read(def: Int32.self, position: Int(o))
}
/// String reads from the buffer with respect to position of the current table.
/// - Parameter offset: Offset of the string
public func string(at offset: Int32) -> String? {
directString(at: offset + position)
}
/// Direct string reads from the buffer disregarding the position of the table.
/// It would be preferable to use string unless the current position of the table
/// is not needed
/// - Parameter offset: Offset of the string
public func directString(at offset: Int32) -> String? {
var offset = offset
offset += bb.read(def: Int32.self, position: Int(offset))
let count = bb.read(def: Int32.self, position: Int(offset))
let position = Int(offset) + MemoryLayout<Int32>.size
return bb.readString(at: position, count: Int(count))
}
/// Reads from the buffer with respect to the position in the table.
/// - Parameters:
/// - type: Type of Element that needs to be read from the buffer
/// - o: Offset of the Element
public func readBuffer<T>(of type: T.Type, at o: Int32) -> T {
directRead(of: T.self, offset: o + position)
}
/// Reads from the buffer disregarding the position of the table.
/// It would be used when reading from an
/// ```
/// let offset = __t.offset(10)
/// //Only used when the we already know what is the
/// // position in the table since __t.vector(at:)
/// // returns the index with respect to the position
/// __t.directRead(of: Byte.self,
/// offset: __t.vector(at: offset) + index * 1)
/// ```
/// - Parameters:
/// - type: Type of Element that needs to be read from the buffer
/// - o: Offset of the Element
public func directRead<T>(of type: T.Type, offset o: Int32) -> T {
let r = bb.read(def: T.self, position: Int(o))
return r
}
/// Returns that current `Union` object at a specific offset
/// by adding offset to the current position of table
/// - Parameter o: offset
/// - Returns: A flatbuffers object
public func union<T: FlatbuffersInitializable>(_ o: Int32) -> T {
let o = o + position
return directUnion(o)
}
/// Returns a direct `Union` object at a specific offset
/// - Parameter o: offset
/// - Returns: A flatbuffers object
public func directUnion<T: FlatbuffersInitializable>(_ o: Int32) -> T {
T.init(bb, o: o + bb.read(def: Int32.self, position: Int(o)))
}
/// Returns a vector of type T at a specific offset
/// This should only be used by `Scalars`
/// - Parameter off: Readable offset
/// - Returns: Returns a vector of type [T]
public func getVector<T>(at off: Int32) -> [T]? {
let o = offset(off)
guard o != 0 else { return nil }
return bb.readSlice(index: Int(vector(at: o)), count: Int(vector(count: o)))
}
/// Vector count gets the count of Elements within the array
/// - Parameter o: start offset of the vector
/// - returns: Count of elements
public func vector(count o: Int32) -> Int32 {
var o = o
o += position
o += bb.read(def: Int32.self, position: Int(o))
return bb.read(def: Int32.self, position: Int(o))
}
/// Vector start index in the buffer
/// - Parameter o:start offset of the vector
/// - returns: the start index of the vector
public func vector(at o: Int32) -> Int32 {
var o = o
o += position
return o + bb.read(def: Int32.self, position: Int(o)) + 4
}
/// Reading an indirect offset of a table.
/// - Parameters:
/// - o: position within the buffer
/// - fbb: ByteBuffer
/// - Returns: table offset
static public func indirect(_ o: Int32, _ fbb: ByteBuffer) -> Int32 {
o + fbb.read(def: Int32.self, position: Int(o))
}
/// Gets a vtable value according to an table Offset and a field offset
/// - Parameters:
/// - o: offset relative to entire buffer
/// - vOffset: Field offset within a vtable
/// - fbb: ByteBuffer
/// - Returns: an position of a field
static public func offset(
_ o: Int32,
vOffset: Int32,
fbb: ByteBuffer) -> Int32
{
let vTable = Int32(fbb.capacity) - o
return vTable + Int32(fbb.read(
def: Int16.self,
position: Int(vTable + vOffset - fbb.read(
def: Int32.self,
position: Int(vTable)))))
}
/// Compares two objects at offset A and offset B within a ByteBuffer
/// - Parameters:
/// - off1: first offset to compare
/// - off2: second offset to compare
/// - fbb: Bytebuffer
/// - Returns: returns the difference between
static public func compare(
_ off1: Int32,
_ off2: Int32,
fbb: ByteBuffer) -> Int32
{
let memorySize = Int32(MemoryLayout<Int32>.size)
let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1))
let _off2 = off2 + fbb.read(def: Int32.self, position: Int(off2))
let len1 = fbb.read(def: Int32.self, position: Int(_off1))
let len2 = fbb.read(def: Int32.self, position: Int(_off2))
let startPos1 = _off1 + memorySize
let startPos2 = _off2 + memorySize
let minValue = min(len1, len2)
for i in 0...minValue {
let b1 = fbb.read(def: Int8.self, position: Int(i + startPos1))
let b2 = fbb.read(def: Int8.self, position: Int(i + startPos2))
if b1 != b2 {
return Int32(b2 - b1)
}
}
return len1 - len2
}
/// Compares two objects at offset A and array of `Bytes` within a ByteBuffer
/// - Parameters:
/// - off1: Offset to compare to
/// - key: bytes array to compare to
/// - fbb: Bytebuffer
/// - Returns: returns the difference between
static public func compare(
_ off1: Int32,
_ key: [Byte],
fbb: ByteBuffer) -> Int32
{
let memorySize = Int32(MemoryLayout<Int32>.size)
let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1))
let len1 = fbb.read(def: Int32.self, position: Int(_off1))
let len2 = Int32(key.count)
let startPos1 = _off1 + memorySize
let minValue = min(len1, len2)
for i in 0..<minValue {
let b = fbb.read(def: Int8.self, position: Int(i + startPos1))
let byte = key[Int(i)]
if b != byte {
return Int32(b - Int8(byte))
}
}
return len1 - len2
}
}
@@ -0,0 +1,203 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// `TableVerifier` verifies a table object is within a provided memory.
/// It checks if all the objects for a specific generated table, are within
/// the bounds of the buffer, aligned.
public struct TableVerifier {
/// position of current table in `ByteBuffer`
fileprivate var _position: Int
/// Current VTable position
fileprivate var _vtable: Int
/// Length of current VTable
fileprivate var _vtableLength: Int
/// `Verifier` object created in the base verifable call.
fileprivate var _verifier: Verifier
/// Creates a `TableVerifier` verifier that allows the Flatbuffer object
/// to verify the buffer before accessing any of the data.
///
/// - Parameters:
/// - position: Current table Position
/// - vtable: Current `VTable` position
/// - vtableLength: Current `VTable` length
/// - verifier: `Verifier` Object that caches the data of the verifiable object
internal init(
position: Int,
vtable: Int,
vtableLength: Int,
verifier: inout Verifier)
{
_position = position
_vtable = vtable
_vtableLength = vtableLength
_verifier = verifier
}
/// Dereference the current object position from the `VTable`
/// - Parameter field: Current VTable refrence to position.
/// - Throws: A `FlatbuffersErrors` incase the voffset is not aligned/outOfBounds/apparentSizeTooLarge
/// - Returns: An optional position for current field
internal mutating func dereference(_ field: VOffset) throws -> Int? {
if field >= _vtableLength {
return nil
}
/// Reading the offset for the field needs to be read.
let offset: VOffset = try _verifier.getValue(
at: Int(clamping: _vtable &+ Int(field)))
if offset > 0 {
return Int(clamping: _position &+ Int(offset))
}
return nil
}
/// Visits all the fields within the table to validate the integrity
/// of the data
/// - Parameters:
/// - field: voffset of the current field to be read
/// - fieldName: fieldname to report data Errors.
/// - required: If the field has to be available in the buffer
/// - type: Type of field to be read
/// - Throws: A `FlatbuffersErrors` where the field is corrupt
public mutating func visit<T>(
field: VOffset,
fieldName: String,
required: Bool,
type: T.Type) throws where T: Verifiable
{
let derefValue = try dereference(field)
if let value = derefValue {
try T.verify(&_verifier, at: value, of: T.self)
return
}
if required {
throw FlatbuffersErrors.requiredFieldDoesntExist(
position: field,
name: fieldName)
}
}
/// Visits all the fields for a union object within the table to
/// validate the integrity of the data
/// - Parameters:
/// - key: Current Key Voffset
/// - field: Current field Voffset
/// - unionKeyName: Union key name
/// - fieldName: Field key name
/// - required: indicates if an object is required to be present
/// - completion: Completion is a handler that WILL be called in the generated
/// - Throws: A `FlatbuffersErrors` where the field is corrupt
public mutating func visit<T>(
unionKey key: VOffset,
unionField field: VOffset,
unionKeyName: String,
fieldName: String,
required: Bool,
completion: @escaping (inout Verifier, T, Int) throws -> Void) throws
where T: UnionEnum
{
let keyPos = try dereference(key)
let valPos = try dereference(field)
if keyPos == nil && valPos == nil {
if required {
throw FlatbuffersErrors.requiredFieldDoesntExist(
position: key,
name: unionKeyName)
}
return
}
if let _key = keyPos,
let _val = valPos
{
/// verifiying that the key is within the buffer
try T.T.verify(&_verifier, at: _key, of: T.T.self)
guard let _enum = try T.init(value: _verifier._buffer.read(
def: T.T.self,
position: _key)) else
{
throw FlatbuffersErrors.unknownUnionCase
}
/// we are assuming that Unions will always be of type Uint8
try completion(
&_verifier,
_enum,
_val)
return
}
throw FlatbuffersErrors.valueNotFound(
key: keyPos,
keyName: unionKeyName,
field: valPos,
fieldName: fieldName)
}
/// Visits and validates all the objects within a union vector
/// - Parameters:
/// - key: Current Key Voffset
/// - field: Current field Voffset
/// - unionKeyName: Union key name
/// - fieldName: Field key name
/// - required: indicates if an object is required to be present
/// - completion: Completion is a handler that WILL be called in the generated
/// - Throws: A `FlatbuffersErrors` where the field is corrupt
public mutating func visitUnionVector<T>(
unionKey key: VOffset,
unionField field: VOffset,
unionKeyName: String,
fieldName: String,
required: Bool,
completion: @escaping (inout Verifier, T, Int) throws -> Void) throws
where T: UnionEnum
{
let keyVectorPosition = try dereference(key)
let offsetVectorPosition = try dereference(field)
if let keyPos = keyVectorPosition,
let valPos = offsetVectorPosition
{
try UnionVector<T>.verify(
&_verifier,
keyPosition: keyPos,
fieldPosition: valPos,
unionKeyName: unionKeyName,
fieldName: fieldName,
completion: completion)
return
}
if required {
throw FlatbuffersErrors.requiredFieldDoesntExist(
position: field,
name: fieldName)
}
}
/// Finishs the current Table verifier, and subtracts the current
/// table from the incremented depth.
public mutating func finish() {
_verifier.finish()
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// `VerifierOptions` is a set of options to verify a flatbuffer
public struct VerifierOptions {
/// Maximum `Apparent` size if the buffer can be expanded into a DAG tree
internal var _maxApparentSize: UOffset
/// Maximum table count allowed in a buffer
internal var _maxTableCount: UOffset
/// Maximum depth allowed in a buffer
internal var _maxDepth: UOffset
/// Ignoring missing null terminals in strings
internal var _ignoreMissingNullTerminators: Bool
/// initializes the set of options for the verifier
/// - Parameters:
/// - maxDepth: Maximum depth allowed in a buffer
/// - maxTableCount: Maximum table count allowed in a buffer
/// - maxApparentSize: Maximum `Apparent` size if the buffer can be expanded into a DAG tree
/// - ignoreMissingNullTerminators: Ignoring missing null terminals in strings *Currently not supported in swift*
public init(
maxDepth: UOffset = 64,
maxTableCount: UOffset = 1000000,
maxApparentSize: UOffset = 1 << 31,
ignoreMissingNullTerminators: Bool = false)
{
_maxDepth = maxDepth
_maxTableCount = maxTableCount
_maxApparentSize = maxApparentSize
_ignoreMissingNullTerminators = ignoreMissingNullTerminators
}
}
@@ -0,0 +1,213 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Verifiable is a protocol all swift flatbuffers object should conform to,
/// since swift is similar to `cpp` and `rust` where the data is read directly
/// from `unsafeMemory` thus the need to verify if the buffer received is a valid one
public protocol Verifiable {
/// Verifies that the current value is which the bounds of the buffer, and if
/// the current `Value` is aligned properly
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Errors coming from `inBuffer` function
static func verify<T>(
_ verifier: inout Verifier,
at position: Int,
of type: T.Type) throws where T: Verifiable
}
extension Verifiable {
/// Verifies if the current range to be read is within the bounds of the buffer,
/// and if the range is properly aligned
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Erros thrown from `isAligned` & `rangeInBuffer`
/// - Returns: a tuple of the start position and the count of objects within the range
@discardableResult
public static func verifyRange<T>(
_ verifier: inout Verifier,
at position: Int, of type: T.Type) throws -> (start: Int, count: Int)
{
let len: UOffset = try verifier.getValue(at: position)
let intLen = Int(len)
let start = Int(clamping: (position &+ MemoryLayout<Int32>.size).magnitude)
try verifier.isAligned(position: start, type: type.self)
try verifier.rangeInBuffer(position: start, size: intLen)
return (start, intLen)
}
}
extension Verifiable where Self: Scalar {
/// Verifies that the current value is which the bounds of the buffer, and if
/// the current `Value` is aligned properly
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Errors coming from `inBuffer` function
public static func verify<T>(
_ verifier: inout Verifier,
at position: Int,
of type: T.Type) throws where T: Verifiable
{
try verifier.inBuffer(position: position, of: type.self)
}
}
// MARK: - ForwardOffset
/// ForwardOffset is a container to wrap around the Generic type to be verified
/// from the flatbuffers object.
public enum ForwardOffset<U>: Verifiable where U: Verifiable {
/// Verifies that the current value is which the bounds of the buffer, and if
/// the current `Value` is aligned properly
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Errors coming from `inBuffer` function
public static func verify<T>(
_ verifier: inout Verifier,
at position: Int,
of type: T.Type) throws where T: Verifiable
{
let offset: UOffset = try verifier.getValue(at: position)
let nextOffset = Int(clamping: (Int(offset) &+ position).magnitude)
try U.verify(&verifier, at: nextOffset, of: U.self)
}
}
// MARK: - Vector
/// Vector is a container to wrap around the Generic type to be verified
/// from the flatbuffers object.
public enum Vector<U, S>: Verifiable where U: Verifiable, S: Verifiable {
/// Verifies that the current value is which the bounds of the buffer, and if
/// the current `Value` is aligned properly
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - position: Current position within the buffer
/// - type: The type of the object to be verified
/// - Throws: Errors coming from `inBuffer` function
public static func verify<T>(
_ verifier: inout Verifier,
at position: Int,
of type: T.Type) throws where T: Verifiable
{
/// checks if the next verification type S is equal to U of type forwardOffset
/// This had to be done since I couldnt find a solution for duplicate call functions
/// A fix will be appreciated
if U.self is ForwardOffset<S>.Type {
let range = try verifyRange(&verifier, at: position, of: UOffset.self)
for index in stride(
from: range.start,
to: Int(
clamping: range
.start &+ (range.count &* MemoryLayout<Int32>.size)),
by: MemoryLayout<UOffset>.size)
{
try U.verify(&verifier, at: index, of: U.self)
}
} else {
try S.verifyRange(&verifier, at: position, of: S.self)
}
}
}
// MARK: - UnionVector
/// UnionVector is a container to wrap around the Generic type to be verified
/// from the flatbuffers object.
public enum UnionVector<S> where S: UnionEnum {
/// Completion handler for the function Verify, that passes the verifier
/// enum type and position of union field
public typealias Completion = (inout Verifier, S, Int) throws -> Void
/// Verifies if the current range to be read is within the bounds of the buffer,
/// and if the range is properly aligned. It also verifies if the union type is a
/// *valid/supported* union type.
/// - Parameters:
/// - verifier: Verifier that hosts the buffer
/// - keyPosition: Current union key position within the buffer
/// - fieldPosition: Current union field position within the buffer
/// - unionKeyName: Name of key to written if error is presented
/// - fieldName: Name of field to written if error is presented
/// - completion: Completion is a handler that WILL be called in the generated
/// code to verify the actual objects
/// - Throws: FlatbuffersErrors
public static func verify(
_ verifier: inout Verifier,
keyPosition: Int,
fieldPosition: Int,
unionKeyName: String,
fieldName: String,
completion: @escaping Completion) throws
{
/// Get offset for union key vectors and offset vectors
let keyOffset: UOffset = try verifier.getValue(at: keyPosition)
let fieldOffset: UOffset = try verifier.getValue(at: fieldPosition)
/// Check if values are within the buffer, returns the start position of vectors, and vector counts
/// Using &+ is safe since we already verified that the value is within the buffer, where the max is
/// going to be 2Gib and swift supports Int64 by default
let keysRange = try S.T.verifyRange(
&verifier,
at: Int(keyOffset) &+ keyPosition,
of: S.T.self)
let offsetsRange = try UOffset.verifyRange(
&verifier,
at: Int(fieldOffset) &+ fieldPosition,
of: UOffset.self)
guard keysRange.count == offsetsRange.count else {
throw FlatbuffersErrors.unionVectorSize(
keyVectorSize: keysRange.count,
fieldVectorSize: offsetsRange.count,
unionKeyName: unionKeyName,
fieldName: fieldName)
}
var count = 0
/// Iterate over the vector of keys and offsets.
while count < keysRange.count {
/// index of readable enum value in array
let keysIndex = MemoryLayout<S.T>.size * count
guard let _enum = try S.init(value: verifier._buffer.read(
def: S.T.self,
position: keysRange.start + keysIndex)) else
{
throw FlatbuffersErrors.unknownUnionCase
}
/// index of readable offset value in array
let fieldIndex = MemoryLayout<UOffset>.size * count
try completion(&verifier, _enum, offsetsRange.start + fieldIndex)
count += 1
}
}
}
@@ -0,0 +1,238 @@
/*
* Copyright 2024 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/// Verifier that check if the buffer passed into it is a valid,
/// safe, aligned Flatbuffers object since swift read from `unsafeMemory`
public struct Verifier {
/// Flag to check for alignment if true
fileprivate let _checkAlignment: Bool
/// Storage for all changing values within the verifier
private let storage: Storage
/// Current verifiable ByteBuffer
internal var _buffer: ByteBuffer
/// Options for verification
internal let _options: VerifierOptions
/// Current stored capacity within the verifier
var capacity: Int {
storage.capacity
}
/// Current depth of verifier
var depth: Int {
storage.depth
}
/// Current table count
var tableCount: Int {
storage.tableCount
}
/// Initializer for the verifier
/// - Parameters:
/// - buffer: Bytebuffer that is required to be verified
/// - options: `VerifierOptions` that set the rule for some of the verification done
/// - checkAlignment: If alignment check is required to be preformed
/// - Throws: `exceedsMaxSizeAllowed` if capacity of the buffer is more than 2GiB
public init(
buffer: inout ByteBuffer,
options: VerifierOptions = .init(),
checkAlignment: Bool = true) throws
{
guard buffer.capacity < FlatBufferMaxSize else {
throw FlatbuffersErrors.exceedsMaxSizeAllowed
}
_buffer = buffer
_checkAlignment = checkAlignment
_options = options
storage = Storage(capacity: buffer.capacity)
}
/// Resets the verifier to initial state
public func reset() {
storage.depth = 0
storage.tableCount = 0
}
/// Checks if the value of type `T` is aligned properly in the buffer
/// - Parameters:
/// - position: Current position
/// - type: Type of value to check
/// - Throws: `missAlignedPointer` if the pointer is not aligned properly
public func isAligned<T>(position: Int, type: T.Type) throws {
/// If check alignment is false this mutating function doesnt continue
if !_checkAlignment { return }
/// advance pointer to position X
let ptr = _buffer._storage.memory.advanced(by: position)
/// Check if the pointer is aligned
if Int(bitPattern: ptr) & (MemoryLayout<T>.alignment &- 1) == 0 {
return
}
throw FlatbuffersErrors.missAlignedPointer(
position: position,
type: String(describing: T.self))
}
/// Checks if the value of Size "X" is within the range of the buffer
/// - Parameters:
/// - position: Current position to be read
/// - size: `Byte` Size of readable object within the buffer
/// - Throws: `outOfBounds` if the value is out of the bounds of the buffer
/// and `apparentSizeTooLarge` if the apparent size is bigger than the one specified
/// in `VerifierOptions`
public func rangeInBuffer(position: Int, size: Int) throws {
let end = UInt(clamping: (position &+ size).magnitude)
if end > _buffer.capacity {
throw FlatbuffersErrors.outOfBounds(position: end, end: storage.capacity)
}
storage.apparentSize = storage.apparentSize &+ UInt32(size)
if storage.apparentSize > _options._maxApparentSize {
throw FlatbuffersErrors.apparentSizeTooLarge
}
}
/// Validates if a value of type `T` is aligned and within the bounds of
/// the buffer
/// - Parameters:
/// - position: Current readable position
/// - type: Type of value to check
/// - Throws: FlatbuffersErrors
public func inBuffer<T>(position: Int, of type: T.Type) throws {
try isAligned(position: position, type: type)
try rangeInBuffer(position: position, size: MemoryLayout<T>.size)
}
/// Visits a table at the current position and validates if the table meets
/// the rules specified in the `VerifierOptions`
/// - Parameter position: Current position to be read
/// - Throws: FlatbuffersErrors
/// - Returns: A `TableVerifier` at the current readable table
public mutating func visitTable(at position: Int) throws -> TableVerifier {
let vtablePosition = try derefOffset(position: position)
let vtableLength: VOffset = try getValue(at: vtablePosition)
let length = Int(vtableLength)
try isAligned(
position: Int(clamping: (vtablePosition + length).magnitude),
type: VOffset.self)
try rangeInBuffer(position: vtablePosition, size: length)
storage.tableCount += 1
if storage.tableCount > _options._maxTableCount {
throw FlatbuffersErrors.maximumTables
}
storage.depth += 1
if storage.depth > _options._maxDepth {
throw FlatbuffersErrors.maximumDepth
}
return TableVerifier(
position: position,
vtable: vtablePosition,
vtableLength: length,
verifier: &self)
}
/// Validates if a value of type `T` is within the buffer and returns it
/// - Parameter position: Current position to be read
/// - Throws: `inBuffer` errors
/// - Returns: a value of type `T` usually a `VTable` or a table offset
internal func getValue<T>(at position: Int) throws -> T {
try inBuffer(position: position, of: T.self)
return _buffer.read(def: T.self, position: position)
}
/// derefrences an offset within a vtable to get the position of the field
/// in the bytebuffer
/// - Parameter position: Current readable position
/// - Throws: `inBuffer` errors & `signedOffsetOutOfBounds`
/// - Returns: Current readable position for a field
@inline(__always)
internal func derefOffset(position: Int) throws -> Int {
try inBuffer(position: position, of: Int32.self)
let offset = _buffer.read(def: Int32.self, position: position)
// switching to int32 since swift's default Int is int64
// this should be safe since we already checked if its within
// the buffer
let _int32Position = UInt32(position)
let reportedOverflow: (partialValue: UInt32, overflow: Bool)
if offset > 0 {
reportedOverflow = _int32Position
.subtractingReportingOverflow(offset.magnitude)
} else {
reportedOverflow = _int32Position
.addingReportingOverflow(offset.magnitude)
}
/// since `subtractingReportingOverflow` & `addingReportingOverflow` returns true,
/// if there is overflow we return failure
if reportedOverflow.overflow || reportedOverflow.partialValue > _buffer
.capacity
{
throw FlatbuffersErrors.signedOffsetOutOfBounds(
offset: Int(offset),
position: position)
}
return Int(reportedOverflow.partialValue)
}
/// finishes the current iteration of verification on an object
internal func finish() {
storage.depth -= 1
}
@inline(__always)
func verify(id: String) throws {
let size = MemoryLayout<Int32>.size
guard storage.capacity >= (size * 2) else {
throw FlatbuffersErrors.bufferDoesntContainID
}
let str = _buffer.readString(at: size, count: size)
if id == str {
return
}
throw FlatbuffersErrors.bufferIdDidntMatchPassedId
}
final private class Storage {
/// Current ApparentSize
fileprivate var apparentSize: UOffset = 0
/// Amount of tables present within a buffer
fileprivate var tableCount = 0
/// Capacity of the current buffer
fileprivate let capacity: Int
/// Current reached depth within the buffer
fileprivate var depth = 0
init(capacity: Int) {
self.capacity = capacity
}
}
}