File: ByteBufferBIO.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (371 lines) | stat: -rw-r--r-- 17,378 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIO
#if compiler(>=5.1)
@_implementationOnly import CNIOBoringSSL
#else
import CNIOBoringSSL
#endif


/// The BoringSSL entry point to write to the `ByteBufferBIO`. This thunk unwraps the user data
/// and then passes the call on to the specific BIO reference.
///
/// This specific type signature is annoying (I'd rather have UnsafeRawPointer, and rather than a separate
/// len I'd like a buffer pointer), but this interface is required because this is passed to an BoringSSL
/// function pointer and so needs to be @convention(c).
internal func boringSSLBIOWriteFunc(bio: UnsafeMutablePointer<BIO>?, buf: UnsafePointer<CChar>?, len: CInt) -> CInt {
    guard let concreteBIO = bio, let concreteBuf = buf else {
        preconditionFailure("Invalid pointers in boringSSLBIOWriteFunc: bio: \(String(describing: bio)) buf: \(String(describing: buf))")
    }

    // This unwrap may fail if the user has dropped the ref to the ByteBufferBIO but still has
    // a ref to the other pointer. Sigh heavily and just fail.
    guard let userPtr = CNIOBoringSSL_BIO_get_data(concreteBIO) else {
        return -1
    }

    // Begin by clearing retry flags. We do this at all BoringSSL entry points.
    CNIOBoringSSL_BIO_clear_retry_flags(concreteBIO)

    // In the event a write of 0 bytes has been asked for, just return early, don't bother with the other work.
    guard len > 0 else {
        return 0
    }

    let swiftBIO: ByteBufferBIO = Unmanaged.fromOpaque(userPtr).takeUnretainedValue()
    let bufferPtr = UnsafeRawBufferPointer(start: concreteBuf, count: Int(len))
    return swiftBIO.sslWrite(buffer: bufferPtr)
}

/// The BoringSSL entry point to read from the `ByteBufferBIO`. This thunk unwraps the user data
/// and then passes the call on to the specific BIO reference.
///
/// This specific type signature is annoying (I'd rather have UnsafeRawPointer, and rather than a separate
/// len I'd like a buffer pointer), but this interface is required because this is passed to an BoringSSL
/// function pointer and so needs to be @convention(c).
internal func boringSSLBIOReadFunc(bio: UnsafeMutablePointer<BIO>?, buf: UnsafeMutablePointer<CChar>?, len: CInt) -> CInt {
    guard let concreteBIO = bio, let concreteBuf = buf else {
        preconditionFailure("Invalid pointers in boringSSLBIOReadFunc: bio: \(String(describing: bio)) buf: \(String(describing: buf))")
    }

    // This unwrap may fail if the user has dropped the ref to the ByteBufferBIO but still has
    // a ref to the other pointer. Sigh heavily and just fail.
    guard let userPtr = CNIOBoringSSL_BIO_get_data(concreteBIO) else {
        return -1
    }

    // Begin by clearing retry flags. We do this at all BoringSSL entry points.
    CNIOBoringSSL_BIO_clear_retry_flags(concreteBIO)

    // In the event a read for 0 bytes has been asked for, just return early, don't bother with the other work.
    guard len > 0 else {
        return 0
    }

    let swiftBIO: ByteBufferBIO = Unmanaged.fromOpaque(userPtr).takeUnretainedValue()
    let bufferPtr = UnsafeMutableRawBufferPointer(start: concreteBuf, count: Int(len))
    return swiftBIO.sslRead(buffer: bufferPtr)
}

/// The BoringSSL entry point for `puts`. This is a silly function, so we're just going to implement it
/// in terms of write.
///
/// This specific type signature is annoying (I'd rather have UnsafeRawPointer, and rather than a separate
/// len I'd like a buffer pointer), but this interface is required because this is passed to an BoringSSL
/// function pointer and so needs to be @convention(c).
internal func boringSSLBIOPutsFunc(bio: UnsafeMutablePointer<BIO>?, buf: UnsafePointer<CChar>?) -> CInt {
    guard let concreteBIO = bio, let concreteBuf = buf else {
        preconditionFailure("Invalid pointers in boringSSLBIOPutsFunc: bio: \(String(describing: bio)) buf: \(String(describing: buf))")
    }
    return boringSSLBIOWriteFunc(bio: concreteBIO, buf: concreteBuf, len: CInt(strlen(concreteBuf)))
}

/// The BoringSSL entry point for `gets`. This is a *really* silly function and we can't implement it nicely
/// in terms of read, so we just refuse to support it.
///
/// This specific type signature is annoying (I'd rather have UnsafeRawPointer, and rather than a separate
/// len I'd like a buffer pointer), but this interface is required because this is passed to an BoringSSL
/// function pointer and so needs to be @convention(c).
internal func boringSSLBIOGetsFunc(bio: UnsafeMutablePointer<BIO>?, buf: UnsafeMutablePointer<CChar>?, len: CInt) -> CInt {
    return -2
}

/// The BoringSSL entry point for `BIO_ctrl`. We don't support most of these.
internal func boringSSLBIOCtrlFunc(bio: UnsafeMutablePointer<BIO>?, cmd: CInt, larg: CLong, parg: UnsafeMutableRawPointer?) -> CLong {
    switch cmd {
    case BIO_CTRL_GET_CLOSE:
        return CLong(CNIOBoringSSL_BIO_get_shutdown(bio))
    case BIO_CTRL_SET_CLOSE:
        CNIOBoringSSL_BIO_set_shutdown(bio, CInt(larg))
        return 1
    case BIO_CTRL_FLUSH:
        return 1
    default:
        return 0
    }
}

internal func boringSSLBIOCreateFunc(bio: UnsafeMutablePointer<BIO>?) -> CInt {
    return 1
}

internal func boringSSLBIODestroyFunc(bio: UnsafeMutablePointer<BIO>?) -> CInt {
    return 1
}


/// An BoringSSL BIO object that wraps `ByteBuffer` objects.
///
/// BoringSSL extensively uses an abstraction called `BIO` to manage its input and output
/// channels. For NIO we want a BIO that operates entirely in-memory, and it's tempting
/// to assume that BoringSSL's `BIO_s_mem` is the best choice for that. However, ultimately
/// `BIO_s_mem` is a flat memory buffer that we end up using as a staging between one
/// `ByteBuffer` of plaintext and one of ciphertext. We'd like to cut out that middleman.
///
/// For this reason, we want to create an object that implements the `BIO` abstraction
/// but which use `ByteBuffer`s to do so. This allows us to avoid unnecessary memory copies,
/// which can be a really large win.
final class ByteBufferBIO {
    /// The unsafe pointer to the BoringSSL BIO_METHOD.
    ///
    /// This is used to give BoringSSL pointers to the methods that need to be invoked when
    /// using a ByteBufferBIO. There will only ever be one value of this in a NIO program,
    /// and it will always be non-NULL. Failure to initialize this structure is fatal to
    /// the program.
    private static let boringSSLBIOMethod: UnsafeMutablePointer<BIO_METHOD> = {
        guard boringSSLIsInitialized else {
            preconditionFailure("Failed to initialize BoringSSL")
        }

        let bioType = CNIOBoringSSL_BIO_get_new_index() | BIO_TYPE_SOURCE_SINK
        guard let method = CNIOBoringSSL_BIO_meth_new(bioType, "ByteBuffer BIO") else {
            preconditionFailure("Unable to allocate new BIO_METHOD")
        }

        CNIOBoringSSL_BIO_meth_set_write(method, boringSSLBIOWriteFunc)
        CNIOBoringSSL_BIO_meth_set_read(method, boringSSLBIOReadFunc)
        CNIOBoringSSL_BIO_meth_set_puts(method, boringSSLBIOPutsFunc)
        CNIOBoringSSL_BIO_meth_set_gets(method, boringSSLBIOGetsFunc)
        CNIOBoringSSL_BIO_meth_set_ctrl(method, boringSSLBIOCtrlFunc)
        CNIOBoringSSL_BIO_meth_set_create(method, boringSSLBIOCreateFunc)
        CNIOBoringSSL_BIO_meth_set_destroy(method, boringSSLBIODestroyFunc)

        return method
    }()

    /// Pointer to the backing BoringSSL BIO object.
    ///
    /// Generally speaking BoringSSL wants to own the object initialization logic for a BIO.
    /// This doesn't work for us, because we'd like to ensure that the `ByteBufferBIO` is
    /// correctly initialized with all the state it needs. One of those bits of state is
    /// a `ByteBuffer`, which BoringSSL cannot give us, so we need to build our `ByteBufferBIO`
    /// *first* and then use that to drive `BIO` initialization.
    ///
    /// Because of this split initialization dance, we elect to initialize this data structure,
    /// and have it own building an BoringSSL `BIO` structure.
    private let bioPtr: UnsafeMutablePointer<BIO>

    /// The buffer of bytes received from the network.
    ///
    /// By default, `ByteBufferBIO` expects to pass data directly to BoringSSL whenever it
    /// is received. It is, in essence, a temporary container for a `ByteBuffer` on the
    /// read side. This provides a powerful optimisation, which is that the read buffer
    /// passed to the `NIOSSLHandler` can be re-used immediately upon receipt. Given that
    /// the `NIOSSLHandler` is almost always the first handler in the pipeline, this greatly
    /// improves the allocation profile of busy connections, which can more-easily re-use
    /// the receive buffer.
    private var inboundBuffer: ByteBuffer?

    /// The buffer of bytes to send to the network.
    ///
    /// While on the read side `ByteBufferBIO` expects to hold each bytebuffer only temporarily,
    /// on the write side we attempt to coalesce as many writes as possible. This is because a
    /// buffer can only be re-used if it is flushed to the network, and that can only happen
    /// on flush calls, so we are incentivised to write as many SSL_write calls into one buffer
    /// as possible.
    private var outboundBuffer: ByteBuffer

    /// Whether the outbound buffer should be cleared before writing.
    ///
    /// This is true only if we've flushed the buffer to the network. Rather than track an annoying
    /// boolean for this, we use a quick check on the properties of the buffer itself. This clear
    /// wants to be delayed as long as possible to maximise the possibility that it does not
    /// trigger an allocation.
    private var mustClearOutboundBuffer: Bool {
        return outboundBuffer.readerIndex == outboundBuffer.writerIndex && outboundBuffer.readerIndex > 0
    }

    init(allocator: ByteBufferAllocator) {
        // We allocate enough space for a single TLS record. We may not actually write a record that size, but we want to
        // give ourselves the option. We may also write more data than that: if we do, the ByteBuffer will just handle it.
        self.outboundBuffer = allocator.buffer(capacity: SSL_MAX_RECORD_SIZE)

        guard let bio = CNIOBoringSSL_BIO_new(ByteBufferBIO.boringSSLBIOMethod) else {
            preconditionFailure("Unable to initialize custom BIO")
        }

        // We now need to complete the BIO initialization. The BIO takes an owned reference to self here,
        // which is broken on close().
        self.bioPtr = bio
        CNIOBoringSSL_BIO_set_data(self.bioPtr, Unmanaged.passRetained(self).toOpaque())
        CNIOBoringSSL_BIO_set_init(self.bioPtr, 1)
        CNIOBoringSSL_BIO_set_shutdown(self.bioPtr, 1)
    }

    deinit {
        // In debug mode we assert that we've been closed.
        assert(CNIOBoringSSL_BIO_get_data(self.bioPtr) == nil, "must call close() on ByteBufferBIO before deinit")

        // On deinit we need to drop our reference to the BIO.
        CNIOBoringSSL_BIO_free(self.bioPtr)
    }

    /// Shuts down the BIO, rendering it unable to be used.
    ///
    /// This method is idempotent: it is safe to call more than once.
    internal func close() {
        guard let selfRef = CNIOBoringSSL_BIO_get_data(self.bioPtr) else {
            // Shutdown is safe to call more than once.
            return
        }

        // We consume the original retain of self, and then nil out the ref in the BIO so that this can't happen again.
        Unmanaged<ByteBufferBIO>.fromOpaque(selfRef).release()
        CNIOBoringSSL_BIO_set_data(self.bioPtr, nil)
    }

    /// Obtain an owned pointer to the backing BoringSSL BIO object.
    ///
    /// This pointer is safe to use elsewhere, as it has increased the reference to the backing
    /// `BIO`. This makes it safe to use with BoringSSL functions that require an owned reference
    /// (that is, that consume a reference count).
    ///
    /// Note that the BIO may not remain useful for long periods of time: if the `ByteBufferBIO`
    /// object that owns the BIO goes out of scope, the BIO will have its pointers invalidated
    /// and will no longer be able to send/receive data.
    internal func retainedBIO() -> UnsafeMutablePointer<BIO> {
        CNIOBoringSSL_BIO_up_ref(self.bioPtr)
        return self.bioPtr
    }


    /// Called to obtain the outbound ciphertext written by BoringSSL.
    ///
    /// This function obtains a buffer of ciphertext that needs to be written to the network. In a
    /// normal application, this should be obtained on a call to `flush`. If no bytes have been flushed
    /// to the network, then this call will return `nil` rather than an empty byte buffer, to help signal
    /// that the `write` call should be elided.
    ///
    /// - returns: A buffer of ciphertext to send to the network, or `nil` if no buffer is available.
    func outboundCiphertext() -> ByteBuffer? {
        guard self.outboundBuffer.readableBytes > 0 else {
            // No data to send.
            return nil
        }

        /// Once we return from this function, we want to account for the bytes we've handed off.
        defer {
            self.outboundBuffer.moveReaderIndex(to: self.outboundBuffer.writerIndex)
        }

        return self.outboundBuffer
    }

    /// Stores a buffer received from the network for delivery to BoringSSL.
    ///
    /// Whenever a buffer is received from the network, it is passed to the BIO via this function
    /// call. In almost all cases this BIO should be immediately consumed by BoringSSL, but in some cases
    /// it may not be. In those cases, additional calls will cause byte-by-byte copies. This should
    /// be avoided, but usually only happens during asynchronous certificate verification in the
    /// handshake.
    ///
    /// - parameters:
    ///     - buffer: The buffer of ciphertext bytes received from the network.
    func receiveFromNetwork(buffer: ByteBuffer) {
        var buffer = buffer

        if self.inboundBuffer == nil {
            self.inboundBuffer = buffer
        } else {
            self.inboundBuffer!.writeBuffer(&buffer)
        }
    }

    /// Retrieves any inbound data that has not been processed by BoringSSL.
    ///
    /// When unwrapping TLS from a connection, there may be application bytes that follow the terminating
    /// CLOSE_NOTIFY message. Those bytes may be in the buffer passed to this BIO, and so we need to
    /// retrieve them.
    ///
    /// This function extracts those bytes and returns them to the user, and drops the reference to them
    /// in this BIO.
    ///
    /// - returns: The unconsumed `ByteBuffer`, if any.
    func evacuateInboundData() -> ByteBuffer? {
        defer {
            self.inboundBuffer = nil
        }
        return self.inboundBuffer
    }

    /// BoringSSL has requested to read ciphertext bytes from the network.
    ///
    /// This function is invoked whenever BoringSSL is looking to read data.
    ///
    /// - parameters:
    ///     - buffer: The buffer for NIO to copy bytes into.
    /// - returns: The number of bytes we have copied.
    fileprivate func sslRead(buffer: UnsafeMutableRawBufferPointer) -> CInt {
        guard var inboundBuffer = self.inboundBuffer else {
            // We have no bytes to read. Mark this as "needs read retry".
            CNIOBoringSSL_BIO_set_retry_read(self.bioPtr)
            return -1
        }

        let bytesToCopy = min(buffer.count, inboundBuffer.readableBytes)
        _ = inboundBuffer.readWithUnsafeReadableBytes { bytePointer in
            assert(bytePointer.count >= bytesToCopy, "Copying more bytes (\(bytesToCopy)) than fits in readable bytes \((bytePointer.count))")
            assert(buffer.count >= bytesToCopy, "Copying more bytes (\(bytesToCopy) than contained in source buffer (\(buffer.count))")
            buffer.baseAddress!.copyMemory(from: bytePointer.baseAddress!, byteCount: bytesToCopy)
            return bytesToCopy
        }

        // If we have read all the bytes from the inbound buffer, nil it out.
        if inboundBuffer.readableBytes > 0 {
            self.inboundBuffer = inboundBuffer
        } else {
            self.inboundBuffer = nil
        }

        return CInt(bytesToCopy)
    }

    /// BoringSSL has requested to write ciphertext bytes from the network.
    ///
    /// - parameters:
    ///     - buffer: The buffer for NIO to copy bytes from.
    /// - returns: The number of bytes we have copied.
    fileprivate func sslWrite(buffer: UnsafeRawBufferPointer) -> CInt {
        if self.mustClearOutboundBuffer {
            // We just flushed, and this is a new write. Let's clear the buffer now.
            self.outboundBuffer.clear()
            assert(!self.mustClearOutboundBuffer)
        }

        let writtenBytes = self.outboundBuffer.writeBytes(buffer)
        return CInt(writtenBytes)
    }
}