mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-06 05:27:24 +00:00
* Adds new API to reduce memory copying within swift Adds new storage container _InternalByteBuffer which will be holding the data that will be created within the swift lib, however reading data will be redirected to ByteBuffer, which should be able to handle all types of data that swift provide without the need to copy the data itself. This is due to holding a reference to the data. Replaces assumingMemoryBinding with bindMemory which is safer Adds function that provides access to a UnsafeBufferPointer for scalars and NativeStructs within swift Updates docs Suppress compilation warnings by replacing var with let Using overflow operators within swift to improve performance Adds tests for GRPC message creation from a retained _InternalByteBuffer
242 lines
7.9 KiB
Swift
242 lines
7.9 KiB
Swift
/*
|
|
* 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
|
|
try _buffer.withUnsafeBytes { pointer in
|
|
let ptr = pointer.baseAddress!.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
|
|
}
|
|
}
|
|
}
|