xref: /aosp_15_r20/external/flatbuffers/swift/Sources/FlatBuffers/Verifier.swift (revision 890232f25432b36107d06881e0a25aaa6b473652)
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