Implements verifier and code gen for swift (#6373)

Updates test cases on linux

Adhere to new protocol naming

Adds fuzzing

Adds documentation

Adds support for string unions

Updated fuzzer generated code
This commit is contained in:
mustiikhalil
2021-05-14 20:59:28 +03:00
committed by GitHub
parent 04b10f5a3a
commit a5175c513a
38 changed files with 2291 additions and 168 deletions

View File

@@ -16,6 +16,9 @@
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 {
@@ -32,7 +35,9 @@ public struct ByteBuffer {
@usableFromInline
init(count: Int, alignment: Int) {
memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment)
memory = UnsafeMutableRawPointer.allocate(
byteCount: count,
alignment: alignment)
capacity = count
unowned = false
}
@@ -69,7 +74,7 @@ public struct ByteBuffer {
/// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
/// - Parameter size: Size of the current object
@usableFromInline
internal func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
func reallocate(_ size: Int, writerSize: Int, alignment: Int) {
let currentWritingIndex = capacity &- writerSize
while capacity <= writerSize &+ size {
capacity = capacity << 1
@@ -78,7 +83,9 @@ public struct ByteBuffer {
/// solution take from Apple-NIO
capacity = capacity.convertToPowerofTwo
let newData = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: alignment)
let newData = UnsafeMutableRawPointer.allocate(
byteCount: capacity,
alignment: alignment)
memset(newData, 0, capacity &- writerSize)
memcpy(
newData.advanced(by: capacity &- writerSize),
@@ -94,9 +101,9 @@ public struct ByteBuffer {
/// The size of the elements written to the buffer + their paddings
private var _writerSize: Int = 0
/// Aliginment of the current memory being written to the buffer
internal var alignment = 1
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
internal var writerIndex: Int { _storage.capacity &- _writerSize }
var writerIndex: Int { _storage.capacity &- _writerSize }
/// Reader is the position of the current Writer Index (capacity - size)
public var reader: Int { writerIndex }
@@ -166,7 +173,7 @@ public struct ByteBuffer {
/// - Parameters:
/// - memory: Current memory of the buffer
/// - count: count of bytes
internal init(memory: UnsafeMutableRawPointer, count: Int) {
init(memory: UnsafeMutableRawPointer, count: Int) {
_storage = Storage(count: count, alignment: alignment)
_storage.copy(from: memory, count: count)
_writerSize = _storage.capacity
@@ -177,7 +184,11 @@ public struct ByteBuffer {
/// - memory: Current memory of the buffer
/// - count: count of bytes
/// - removeBytes: Removes a number of bytes from the current size
internal init(memory: UnsafeMutableRawPointer, count: Int, removing removeBytes: Int) {
init(
memory: UnsafeMutableRawPointer,
count: Int,
removing removeBytes: Int)
{
_storage = Storage(count: count, alignment: alignment)
_storage.copy(from: memory, count: count)
_writerSize = removeBytes
@@ -247,7 +258,7 @@ public struct ByteBuffer {
/// - bytes: Pointer to the view
/// - len: Size of string
@inline(__always)
mutating internal func push(
mutating func push(
bytes: UnsafeBufferPointer<String.UTF8View.Element>,
len: Int) -> Bool
{
@@ -292,20 +303,18 @@ public struct ByteBuffer {
/// pops the written VTable if it's already written into the buffer
/// - Parameter size: size of the `VTable`
@inline(__always)
mutating internal func pop(_ size: Int) {
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
@@ -317,10 +326,7 @@ public struct ByteBuffer {
/// - def: Type of the object
/// - position: the index of the object in the buffer
public func read<T>(def: T.Type, position: Int) -> T {
assert(
position + MemoryLayout<T>.size <= _storage.capacity,
"Reading out of bounds is illegal")
return _storage.memory.advanced(by: position).load(as: T.self)
_storage.memory.advanced(by: position).load(as: T.self)
}
/// Reads a slice from the memory assuming a type of T
@@ -329,14 +335,14 @@ public struct ByteBuffer {
/// - count: count of bytes in memory
@inline(__always)
public func readSlice<T>(
index: Int32,
count: Int32) -> [T]
index: Int,
count: Int) -> [T]
{
let _index = Int(index)
let _count = Int(count)
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)
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)
}
@@ -345,17 +351,16 @@ public struct ByteBuffer {
/// - 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: Int32,
count: Int32,
at index: Int,
count: Int,
type: String.Encoding = .utf8) -> String?
{
let _index = Int(index)
let _count = Int(count)
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)
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)
}
@@ -363,12 +368,22 @@ public struct ByteBuffer {
/// - Parameter removeBytes: the amount of bytes to remove from the current Size
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")
assert(
removeBytes < _storage.capacity,
"Can NOT remove more bytes than the ones allocated")
return ByteBuffer(
memory: _storage.memory,
count: _storage.capacity,
removing: _writerSize &- removeBytes)
}
/// 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
@usableFromInline
mutating func skipPrefix() {
_writerSize = _writerSize &- MemoryLayout<Int32>.size
}
}
extension ByteBuffer: CustomDebugStringConvertible {

View File

@@ -32,14 +32,16 @@ public typealias VOffset = UInt16
/// Maximum size for a buffer
public let FlatBufferMaxSize = UInt32.max << ((MemoryLayout<SOffset>.size * 8 - 1) - 1)
/// Protocol that confirms all the numbers
/// Protocol that All Scalars should conform to
///
/// Scalar is used to confirm all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
/// 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
///
@@ -49,7 +51,7 @@ extension Scalar where Self: FixedWidthInteger {
}
}
extension Double: Scalar {
extension Double: Scalar, Verifiable {
public typealias NumericValue = UInt64
public var convertedEndian: UInt64 {
@@ -57,7 +59,7 @@ extension Double: Scalar {
}
}
extension Float32: Scalar {
extension Float32: Scalar, Verifiable {
public typealias NumericValue = UInt32
public var convertedEndian: UInt32 {
@@ -65,7 +67,7 @@ extension Float32: Scalar {
}
}
extension Bool: Scalar {
extension Bool: Scalar, Verifiable {
public var convertedEndian: UInt8 {
self == true ? 1 : 0
}
@@ -73,39 +75,39 @@ extension Bool: Scalar {
public typealias NumericValue = UInt8
}
extension Int: Scalar {
extension Int: Scalar, Verifiable {
public typealias NumericValue = Int
}
extension Int8: Scalar {
extension Int8: Scalar, Verifiable {
public typealias NumericValue = Int8
}
extension Int16: Scalar {
extension Int16: Scalar, Verifiable {
public typealias NumericValue = Int16
}
extension Int32: Scalar {
extension Int32: Scalar, Verifiable {
public typealias NumericValue = Int32
}
extension Int64: Scalar {
extension Int64: Scalar, Verifiable {
public typealias NumericValue = Int64
}
extension UInt8: Scalar {
extension UInt8: Scalar, Verifiable {
public typealias NumericValue = UInt8
}
extension UInt16: Scalar {
extension UInt16: Scalar, Verifiable {
public typealias NumericValue = UInt16
}
extension UInt32: Scalar {
extension UInt32: Scalar, Verifiable {
public typealias NumericValue = UInt32
}
extension UInt64: Scalar {
extension UInt64: Scalar, Verifiable {
public typealias NumericValue = UInt64
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2021 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`
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
}

View File

@@ -16,6 +16,9 @@
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
@frozen
public struct FlatBufferBuilder {
@@ -74,7 +77,9 @@ public struct FlatBufferBuilder {
/// Returns A sized Buffer from the readable bytes
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))
return ByteBuffer(
memory: _bb.memory.advanced(by: _bb.reader),
count: Int(_bb.size))
}
// MARK: - Init
@@ -112,7 +117,9 @@ public struct FlatBufferBuilder {
for field in fields {
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(field)) != 0
let isOkay = _bb.read(
def: VOffset.self,
position: startTable &+ Int(field)) != 0
assert(isOkay, "Flatbuffers requires the following field")
}
}
@@ -122,9 +129,15 @@ public struct FlatBufferBuilder {
/// - offset: Offset of the table
/// - fileId: Takes the fileId
/// - prefix: if false it wont add the size of the buffer
mutating public func finish(offset: Offset, fileId: String, addPrefix prefix: Bool = false) {
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)
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)
@@ -134,7 +147,10 @@ public struct FlatBufferBuilder {
/// - Parameters:
/// - offset: Offset of the table
/// - prefix: if false it wont add the size of the buffer
mutating public func finish(offset: Offset, addPrefix prefix: Bool = false) {
mutating public func finish(
offset: Offset,
addPrefix prefix: Bool = false)
{
notNested()
let size = MemoryLayout<UOffset>.size
preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment)
@@ -184,7 +200,10 @@ public struct FlatBufferBuilder {
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)
_bb.write(
value: VOffset(vTableOffset &- loaded.offset),
index: _index,
direct: true)
}
_vtableStorage.clear()
@@ -375,7 +394,9 @@ public struct FlatBufferBuilder {
/// - Parameter structs: A vector of structs
/// - Returns: offset of the vector
mutating public func createVector<T: NativeStruct>(ofStructs structs: [T]) -> Offset {
startVector(structs.count * MemoryLayout<T>.size, elementSize: MemoryLayout<T>.alignment)
startVector(
structs.count * MemoryLayout<T>.size,
elementSize: MemoryLayout<T>.alignment)
for i in structs.reversed() {
_ = create(struct: i)
}
@@ -394,7 +415,9 @@ public struct FlatBufferBuilder {
struct s: T, position: VOffset) -> Offset
{
let offset = create(struct: s)
_vtableStorage.add(loc: FieldLoc(offset: _bb.size, position: VOffset(position)))
_vtableStorage.add(loc: FieldLoc(
offset: _bb.size,
position: VOffset(position)))
return offset
}
@@ -529,8 +552,6 @@ extension FlatBufferBuilder: CustomDebugStringConvertible {
var numOfFields: Int = 0
/// Last written Index
var writtenIndex: Int = 0
/// the amount of added elements into the buffer
var addedElements: Int { capacity - (numOfFields &* size) }
/// Creates the memory to store the buffer in
@usableFromInline
@@ -555,7 +576,9 @@ extension FlatBufferBuilder: CustomDebugStringConvertible {
/// and max offset
/// - Parameter loc: Location of encoded element
func add(loc: FieldLoc) {
memory.baseAddress?.advanced(by: writtenIndex).storeBytes(of: loc, as: FieldLoc.self)
memory.baseAddress?.advanced(by: writtenIndex).storeBytes(
of: loc,
as: FieldLoc.self)
writtenIndex = writtenIndex &+ size
numOfFields = numOfFields &+ 1
maxOffset = max(loc.position, maxOffset)
@@ -574,7 +597,9 @@ extension FlatBufferBuilder: CustomDebugStringConvertible {
func ensure(space: Int) {
guard space &+ writtenIndex > capacity else { return }
memory.deallocate()
memory = UnsafeMutableRawBufferPointer.allocate(byteCount: space, alignment: size)
memory = UnsafeMutableRawBufferPointer.allocate(
byteCount: space,
alignment: size)
capacity = space
}

View File

@@ -31,15 +31,26 @@ 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` tries 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
static func pack(_ builder: inout FlatBufferBuilder, obj: inout T?) -> Offset
/// `pack` 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
static func pack(_ builder: inout FlatBufferBuilder, obj: inout T) -> Offset
/// `Unpack` unpacks a flatbuffers object into a `NativeObject`
mutating func unpack() -> T
}
public protocol Enum {
associatedtype T: Scalar
static var byteSize: Int { get }
var value: T { get }
}

View File

@@ -16,7 +16,8 @@
import Foundation
public final class FlatBuffersUtils {
/// FlatBuffersUtils hosts some utility functions that might be useful
public enum FlatBuffersUtils {
/// Gets the size of the prefix
/// - Parameter bb: Flatbuffer object
@@ -24,7 +25,9 @@ public final class FlatBuffersUtils {
bb.read(def: Int32.self, position: bb.reader)
}
/// Removes the prefix by duplicating the Flatbuffer
/// 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)

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2021 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 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
public static func == (lhs: FlatbuffersErrors, rhs: FlatbuffersErrors) -> Bool {
lhs.localizedDescription == rhs.localizedDescription
}
}

View File

@@ -14,6 +14,10 @@
* 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

View File

@@ -16,6 +16,9 @@
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 {
@@ -36,7 +39,10 @@ extension NativeObject {
/// - 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 {
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)

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2021 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
public func getPrefixedSizeCheckedRoot<T: FlatBufferObject & Verifiable>(
byteBuffer: inout ByteBuffer,
options: VerifierOptions = .init()) throws -> T
{
byteBuffer.skipPrefix()
return try getCheckedRoot(byteBuffer: &byteBuffer, 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
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
public func getCheckedRoot<T: FlatBufferObject & Verifiable>(
byteBuffer: inout ByteBuffer,
options: VerifierOptions = .init()) throws -> T
{
var verifier = try Verifier(buffer: &byteBuffer, options: options)
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))
}

View File

@@ -16,6 +16,42 @@
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
@@ -23,10 +59,11 @@ extension String: FlatbuffersInitializable {
/// - bb: ByteBuffer containing the readable string
/// - o: Current position
public init(_ bb: ByteBuffer, o: Int32) {
let count = bb.read(def: Int32.self, position: Int(o))
let v = Int(o)
let count = bb.read(def: Int32.self, position: v)
self = bb.readString(
at: Int32(MemoryLayout<Int32>.size) + o,
count: count) ?? ""
at: MemoryLayout<Int32>.size + v,
count: Int(count)) ?? ""
}
}
@@ -53,7 +90,10 @@ extension String: NativeObject {
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 {
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")
}
}

View File

@@ -16,16 +16,30 @@
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 postion: 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
postion = 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 + postion))
return r

View File

@@ -16,11 +16,22 @@
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 postion: 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")
@@ -29,6 +40,10 @@ public struct Table {
postion = 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 = postion - bb.read(def: Int32.self, position: Int(postion))
return o < bb.read(def: VOffset.self, position: Int(vtable)) ? Int32(bb.read(
@@ -36,7 +51,13 @@ public struct Table {
position: Int(vtable + o))) : 0
}
public func indirect(_ o: Int32) -> Int32 { o + bb.read(def: Int32.self, position: Int(o)) }
/// 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
@@ -45,14 +66,15 @@ public struct Table {
}
/// 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
/// 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 = offset + Int32(MemoryLayout<Int32>.size)
return bb.readString(at: position, count: count)
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.
@@ -81,19 +103,30 @@ public struct Table {
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 + postion
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: vector(at: o), count: vector(count: o))
return bb.readSlice(index: Int(vector(at: o)), count: Int(vector(count: o)))
}
/// Vector count gets the count of Elements within the array
@@ -115,17 +148,36 @@ public struct Table {
return o + bb.read(def: Int32.self, position: Int(o)) + 4
}
static public func indirect(_ o: Int32, _ fbb: ByteBuffer) -> Int32 { o + fbb.read(
def: Int32.self,
position: Int(o)) }
/// 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)))))
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))
@@ -145,6 +197,12 @@ public struct Table {
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))

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2021 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()
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2021 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
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2021 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),
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
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2021 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
/// Capacity of the current buffer
fileprivate var _capacity: Int
/// Current ApparentSize
fileprivate var _apparentSize: UOffset = 0
/// Amount of tables present within a buffer
fileprivate var _tableCount = 0
/// Capacity of the buffer
internal var capacity: Int { _capacity }
/// Current reached depth within the buffer
internal var _depth = 0
/// Current verifiable ByteBuffer
internal var _buffer: ByteBuffer
/// Options for verification
internal let _options: VerifierOptions
/// 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
_capacity = buffer.capacity
_checkAlignment = checkAlignment
_options = options
}
/// Resets the verifier to initial state
public mutating func reset() {
_depth = 0
_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 mutating 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 postion 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 mutating func rangeInBuffer(position: Int, size: Int) throws {
let end = UInt(clamping: (position &+ size).magnitude)
if end > _buffer.capacity {
throw FlatbuffersErrors.outOfBounds(position: end, end: capacity)
}
_apparentSize = _apparentSize &+ UInt32(size)
if _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 mutating 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)
_tableCount += 1
if _tableCount > _options._maxTableCount {
throw FlatbuffersErrors.maximumTables
}
_depth += 1
if _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 mutating 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 mutating 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 mutating func finish() {
_depth -= 1
}
}