1*890232f2SAndroid Build Coastguard Worker /* 2*890232f2SAndroid Build Coastguard Worker * Copyright 2021 Google Inc. All rights reserved. 3*890232f2SAndroid Build Coastguard Worker * 4*890232f2SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*890232f2SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*890232f2SAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*890232f2SAndroid Build Coastguard Worker * 8*890232f2SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*890232f2SAndroid Build Coastguard Worker * 10*890232f2SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*890232f2SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*890232f2SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*890232f2SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*890232f2SAndroid Build Coastguard Worker * limitations under the License. 15*890232f2SAndroid Build Coastguard Worker */ 16*890232f2SAndroid Build Coastguard Worker 17*890232f2SAndroid Build Coastguard Worker #if !os(WASI) 18*890232f2SAndroid Build Coastguard Worker import Foundation 19*890232f2SAndroid Build Coastguard Worker #else 20*890232f2SAndroid Build Coastguard Worker import SwiftOverlayShims 21*890232f2SAndroid Build Coastguard Worker #endif 22*890232f2SAndroid Build Coastguard Worker 23*890232f2SAndroid Build Coastguard Worker /// Verifier that check if the buffer passed into it is a valid, 24*890232f2SAndroid Build Coastguard Worker /// safe, aligned Flatbuffers object since swift read from `unsafeMemory` 25*890232f2SAndroid Build Coastguard Worker public struct Verifier { 26*890232f2SAndroid Build Coastguard Worker 27*890232f2SAndroid Build Coastguard Worker /// Flag to check for alignment if true 28*890232f2SAndroid Build Coastguard Worker fileprivate let _checkAlignment: Bool 29*890232f2SAndroid Build Coastguard Worker /// Capacity of the current buffer 30*890232f2SAndroid Build Coastguard Worker fileprivate var _capacity: Int 31*890232f2SAndroid Build Coastguard Worker /// Current ApparentSize 32*890232f2SAndroid Build Coastguard Worker fileprivate var _apparentSize: UOffset = 0 33*890232f2SAndroid Build Coastguard Worker /// Amount of tables present within a buffer 34*890232f2SAndroid Build Coastguard Worker fileprivate var _tableCount = 0 35*890232f2SAndroid Build Coastguard Worker 36*890232f2SAndroid Build Coastguard Worker /// Capacity of the buffer 37*890232f2SAndroid Build Coastguard Worker internal var capacity: Int { _capacity } 38*890232f2SAndroid Build Coastguard Worker /// Current reached depth within the buffer 39*890232f2SAndroid Build Coastguard Worker internal var _depth = 0 40*890232f2SAndroid Build Coastguard Worker /// Current verifiable ByteBuffer 41*890232f2SAndroid Build Coastguard Worker internal var _buffer: ByteBuffer 42*890232f2SAndroid Build Coastguard Worker /// Options for verification 43*890232f2SAndroid Build Coastguard Worker internal let _options: VerifierOptions 44*890232f2SAndroid Build Coastguard Worker 45*890232f2SAndroid Build Coastguard Worker /// Initializer for the verifier 46*890232f2SAndroid Build Coastguard Worker /// - Parameters: 47*890232f2SAndroid Build Coastguard Worker /// - buffer: Bytebuffer that is required to be verified 48*890232f2SAndroid Build Coastguard Worker /// - options: `VerifierOptions` that set the rule for some of the verification done 49*890232f2SAndroid Build Coastguard Worker /// - checkAlignment: If alignment check is required to be preformed 50*890232f2SAndroid Build Coastguard Worker /// - Throws: `exceedsMaxSizeAllowed` if capacity of the buffer is more than 2GiB 51*890232f2SAndroid Build Coastguard Worker public init( 52*890232f2SAndroid Build Coastguard Worker buffer: inout ByteBuffer, 53*890232f2SAndroid Build Coastguard Worker options: VerifierOptions = .init(), 54*890232f2SAndroid Build Coastguard Worker checkAlignment: Bool = true) throws 55*890232f2SAndroid Build Coastguard Worker { 56*890232f2SAndroid Build Coastguard Worker guard buffer.capacity < FlatBufferMaxSize else { 57*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.exceedsMaxSizeAllowed 58*890232f2SAndroid Build Coastguard Worker } 59*890232f2SAndroid Build Coastguard Worker 60*890232f2SAndroid Build Coastguard Worker _buffer = buffer 61*890232f2SAndroid Build Coastguard Worker _capacity = buffer.capacity 62*890232f2SAndroid Build Coastguard Worker _checkAlignment = checkAlignment 63*890232f2SAndroid Build Coastguard Worker _options = options 64*890232f2SAndroid Build Coastguard Worker } 65*890232f2SAndroid Build Coastguard Worker 66*890232f2SAndroid Build Coastguard Worker /// Resets the verifier to initial state resetnull67*890232f2SAndroid Build Coastguard Worker public mutating func reset() { 68*890232f2SAndroid Build Coastguard Worker _depth = 0 69*890232f2SAndroid Build Coastguard Worker _tableCount = 0 70*890232f2SAndroid Build Coastguard Worker } 71*890232f2SAndroid Build Coastguard Worker 72*890232f2SAndroid Build Coastguard Worker /// Checks if the value of type `T` is aligned properly in the buffer 73*890232f2SAndroid Build Coastguard Worker /// - Parameters: 74*890232f2SAndroid Build Coastguard Worker /// - position: Current position 75*890232f2SAndroid Build Coastguard Worker /// - type: Type of value to check 76*890232f2SAndroid Build Coastguard Worker /// - Throws: `missAlignedPointer` if the pointer is not aligned properly isAligned<T>null77*890232f2SAndroid Build Coastguard Worker public mutating func isAligned<T>(position: Int, type: T.Type) throws { 78*890232f2SAndroid Build Coastguard Worker 79*890232f2SAndroid Build Coastguard Worker /// If check alignment is false this mutating function doesnt continue 80*890232f2SAndroid Build Coastguard Worker if !_checkAlignment { return } 81*890232f2SAndroid Build Coastguard Worker 82*890232f2SAndroid Build Coastguard Worker /// advance pointer to position X 83*890232f2SAndroid Build Coastguard Worker let ptr = _buffer._storage.memory.advanced(by: position) 84*890232f2SAndroid Build Coastguard Worker /// Check if the pointer is aligned 85*890232f2SAndroid Build Coastguard Worker if Int(bitPattern: ptr) & (MemoryLayout<T>.alignment &- 1) == 0 { 86*890232f2SAndroid Build Coastguard Worker return 87*890232f2SAndroid Build Coastguard Worker } 88*890232f2SAndroid Build Coastguard Worker 89*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.missAlignedPointer( 90*890232f2SAndroid Build Coastguard Worker position: position, 91*890232f2SAndroid Build Coastguard Worker type: String(describing: T.self)) 92*890232f2SAndroid Build Coastguard Worker } 93*890232f2SAndroid Build Coastguard Worker 94*890232f2SAndroid Build Coastguard Worker /// Checks if the value of Size "X" is within the range of the buffer 95*890232f2SAndroid Build Coastguard Worker /// - Parameters: 96*890232f2SAndroid Build Coastguard Worker /// - position: Current postion to be read 97*890232f2SAndroid Build Coastguard Worker /// - size: `Byte` Size of readable object within the buffer 98*890232f2SAndroid Build Coastguard Worker /// - Throws: `outOfBounds` if the value is out of the bounds of the buffer 99*890232f2SAndroid Build Coastguard Worker /// and `apparentSizeTooLarge` if the apparent size is bigger than the one specified 100*890232f2SAndroid Build Coastguard Worker /// in `VerifierOptions` rangeInBuffernull101*890232f2SAndroid Build Coastguard Worker public mutating func rangeInBuffer(position: Int, size: Int) throws { 102*890232f2SAndroid Build Coastguard Worker let end = UInt(clamping: (position &+ size).magnitude) 103*890232f2SAndroid Build Coastguard Worker if end > _buffer.capacity { 104*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.outOfBounds(position: end, end: capacity) 105*890232f2SAndroid Build Coastguard Worker } 106*890232f2SAndroid Build Coastguard Worker _apparentSize = _apparentSize &+ UInt32(size) 107*890232f2SAndroid Build Coastguard Worker if _apparentSize > _options._maxApparentSize { 108*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.apparentSizeTooLarge 109*890232f2SAndroid Build Coastguard Worker } 110*890232f2SAndroid Build Coastguard Worker } 111*890232f2SAndroid Build Coastguard Worker 112*890232f2SAndroid Build Coastguard Worker /// Validates if a value of type `T` is aligned and within the bounds of 113*890232f2SAndroid Build Coastguard Worker /// the buffer 114*890232f2SAndroid Build Coastguard Worker /// - Parameters: 115*890232f2SAndroid Build Coastguard Worker /// - position: Current readable position 116*890232f2SAndroid Build Coastguard Worker /// - type: Type of value to check 117*890232f2SAndroid Build Coastguard Worker /// - Throws: FlatbuffersErrors inBuffer<T>null118*890232f2SAndroid Build Coastguard Worker public mutating func inBuffer<T>(position: Int, of type: T.Type) throws { 119*890232f2SAndroid Build Coastguard Worker try isAligned(position: position, type: type) 120*890232f2SAndroid Build Coastguard Worker try rangeInBuffer(position: position, size: MemoryLayout<T>.size) 121*890232f2SAndroid Build Coastguard Worker } 122*890232f2SAndroid Build Coastguard Worker 123*890232f2SAndroid Build Coastguard Worker /// Visits a table at the current position and validates if the table meets 124*890232f2SAndroid Build Coastguard Worker /// the rules specified in the `VerifierOptions` 125*890232f2SAndroid Build Coastguard Worker /// - Parameter position: Current position to be read 126*890232f2SAndroid Build Coastguard Worker /// - Throws: FlatbuffersErrors 127*890232f2SAndroid Build Coastguard Worker /// - Returns: A `TableVerifier` at the current readable table visitTablenull128*890232f2SAndroid Build Coastguard Worker public mutating func visitTable(at position: Int) throws -> TableVerifier { 129*890232f2SAndroid Build Coastguard Worker let vtablePosition = try derefOffset(position: position) 130*890232f2SAndroid Build Coastguard Worker let vtableLength: VOffset = try getValue(at: vtablePosition) 131*890232f2SAndroid Build Coastguard Worker 132*890232f2SAndroid Build Coastguard Worker let length = Int(vtableLength) 133*890232f2SAndroid Build Coastguard Worker try isAligned( 134*890232f2SAndroid Build Coastguard Worker position: Int(clamping: (vtablePosition + length).magnitude), 135*890232f2SAndroid Build Coastguard Worker type: VOffset.self) 136*890232f2SAndroid Build Coastguard Worker try rangeInBuffer(position: vtablePosition, size: length) 137*890232f2SAndroid Build Coastguard Worker 138*890232f2SAndroid Build Coastguard Worker _tableCount += 1 139*890232f2SAndroid Build Coastguard Worker 140*890232f2SAndroid Build Coastguard Worker if _tableCount > _options._maxTableCount { 141*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.maximumTables 142*890232f2SAndroid Build Coastguard Worker } 143*890232f2SAndroid Build Coastguard Worker 144*890232f2SAndroid Build Coastguard Worker _depth += 1 145*890232f2SAndroid Build Coastguard Worker 146*890232f2SAndroid Build Coastguard Worker if _depth > _options._maxDepth { 147*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.maximumDepth 148*890232f2SAndroid Build Coastguard Worker } 149*890232f2SAndroid Build Coastguard Worker 150*890232f2SAndroid Build Coastguard Worker return TableVerifier( 151*890232f2SAndroid Build Coastguard Worker position: position, 152*890232f2SAndroid Build Coastguard Worker vtable: vtablePosition, 153*890232f2SAndroid Build Coastguard Worker vtableLength: length, 154*890232f2SAndroid Build Coastguard Worker verifier: &self) 155*890232f2SAndroid Build Coastguard Worker } 156*890232f2SAndroid Build Coastguard Worker 157*890232f2SAndroid Build Coastguard Worker /// Validates if a value of type `T` is within the buffer and returns it 158*890232f2SAndroid Build Coastguard Worker /// - Parameter position: Current position to be read 159*890232f2SAndroid Build Coastguard Worker /// - Throws: `inBuffer` errors 160*890232f2SAndroid Build Coastguard Worker /// - Returns: a value of type `T` usually a `VTable` or a table offset getValue<T>null161*890232f2SAndroid Build Coastguard Worker internal mutating func getValue<T>(at position: Int) throws -> T { 162*890232f2SAndroid Build Coastguard Worker try inBuffer(position: position, of: T.self) 163*890232f2SAndroid Build Coastguard Worker return _buffer.read(def: T.self, position: position) 164*890232f2SAndroid Build Coastguard Worker } 165*890232f2SAndroid Build Coastguard Worker 166*890232f2SAndroid Build Coastguard Worker /// derefrences an offset within a vtable to get the position of the field 167*890232f2SAndroid Build Coastguard Worker /// in the bytebuffer 168*890232f2SAndroid Build Coastguard Worker /// - Parameter position: Current readable position 169*890232f2SAndroid Build Coastguard Worker /// - Throws: `inBuffer` errors & `signedOffsetOutOfBounds` 170*890232f2SAndroid Build Coastguard Worker /// - Returns: Current readable position for a field 171*890232f2SAndroid Build Coastguard Worker @inline(__always) derefOffsetnull172*890232f2SAndroid Build Coastguard Worker internal mutating func derefOffset(position: Int) throws -> Int { 173*890232f2SAndroid Build Coastguard Worker try inBuffer(position: position, of: Int32.self) 174*890232f2SAndroid Build Coastguard Worker 175*890232f2SAndroid Build Coastguard Worker let offset = _buffer.read(def: Int32.self, position: position) 176*890232f2SAndroid Build Coastguard Worker // switching to int32 since swift's default Int is int64 177*890232f2SAndroid Build Coastguard Worker // this should be safe since we already checked if its within 178*890232f2SAndroid Build Coastguard Worker // the buffer 179*890232f2SAndroid Build Coastguard Worker let _int32Position = UInt32(position) 180*890232f2SAndroid Build Coastguard Worker 181*890232f2SAndroid Build Coastguard Worker let reportedOverflow: (partialValue: UInt32, overflow: Bool) 182*890232f2SAndroid Build Coastguard Worker if offset > 0 { 183*890232f2SAndroid Build Coastguard Worker reportedOverflow = _int32Position 184*890232f2SAndroid Build Coastguard Worker .subtractingReportingOverflow(offset.magnitude) 185*890232f2SAndroid Build Coastguard Worker } else { 186*890232f2SAndroid Build Coastguard Worker reportedOverflow = _int32Position 187*890232f2SAndroid Build Coastguard Worker .addingReportingOverflow(offset.magnitude) 188*890232f2SAndroid Build Coastguard Worker } 189*890232f2SAndroid Build Coastguard Worker 190*890232f2SAndroid Build Coastguard Worker /// since `subtractingReportingOverflow` & `addingReportingOverflow` returns true, 191*890232f2SAndroid Build Coastguard Worker /// if there is overflow we return failure 192*890232f2SAndroid Build Coastguard Worker if reportedOverflow.overflow || reportedOverflow.partialValue > _buffer 193*890232f2SAndroid Build Coastguard Worker .capacity 194*890232f2SAndroid Build Coastguard Worker { 195*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.signedOffsetOutOfBounds( 196*890232f2SAndroid Build Coastguard Worker offset: Int(offset), 197*890232f2SAndroid Build Coastguard Worker position: position) 198*890232f2SAndroid Build Coastguard Worker } 199*890232f2SAndroid Build Coastguard Worker 200*890232f2SAndroid Build Coastguard Worker return Int(reportedOverflow.partialValue) 201*890232f2SAndroid Build Coastguard Worker } 202*890232f2SAndroid Build Coastguard Worker 203*890232f2SAndroid Build Coastguard Worker /// finishes the current iteration of verification on an object finishnull204*890232f2SAndroid Build Coastguard Worker internal mutating func finish() { 205*890232f2SAndroid Build Coastguard Worker _depth -= 1 206*890232f2SAndroid Build Coastguard Worker } 207*890232f2SAndroid Build Coastguard Worker verifynull208*890232f2SAndroid Build Coastguard Worker mutating func verify(id: String) throws { 209*890232f2SAndroid Build Coastguard Worker let size = MemoryLayout<Int32>.size 210*890232f2SAndroid Build Coastguard Worker let str = _buffer.readString(at: size, count: size) 211*890232f2SAndroid Build Coastguard Worker if id == str { 212*890232f2SAndroid Build Coastguard Worker return 213*890232f2SAndroid Build Coastguard Worker } 214*890232f2SAndroid Build Coastguard Worker throw FlatbuffersErrors.bufferIdDidntMatchPassedId 215*890232f2SAndroid Build Coastguard Worker } 216*890232f2SAndroid Build Coastguard Worker 217*890232f2SAndroid Build Coastguard Worker } 218