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