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
|
//===----------------------------------------------------------------------===//
//
// 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
private let maxOneByteSize = 125
private let maxTwoByteSize = Int(UInt16.max)
#if arch(arm) || arch(i386)
// on 32-bit platforms we can't put a whole UInt32 in an Int
private let maxNIOFrameSize = Int(UInt32.max / 2)
#else
// on 64-bit platforms this works just fine
private let maxNIOFrameSize = Int(UInt32.max)
#endif
/// An inbound `ChannelHandler` that serializes structured websocket frames into a byte stream
/// for sending on the network.
///
/// This encoder has limited enforcement of compliance to RFC 6455. In particular, to guarantee
/// that the encoder can handle arbitrary extensions, only normative MUST/MUST NOTs that do not
/// relate to extensions (e.g. the requirement that control frames not have lengths larger than
/// 125 bytes) are enforced by this encoder.
///
/// This encoder does not have any support for encoder extensions. If you wish to support
/// extensions, you should implement a message-to-message encoder that performs the appropriate
/// frame transformation as needed.
public final class WebSocketFrameEncoder: ChannelOutboundHandler {
public typealias OutboundIn = WebSocketFrame
public typealias OutboundOut = ByteBuffer
/// This buffer is used to write frame headers into. We hold a buffer here as it's possible we'll be
/// able to avoid some allocations by re-using it.
private var headerBuffer: ByteBuffer? = nil
/// The maximum size of a websocket frame header. One byte for the frame "first byte", one more for the first
/// length byte and the mask bit, potentially up to 8 more bytes for a 64-bit length field, and potentially 4 bytes
/// for a mask key.
private static let maximumFrameHeaderLength: Int = (2 + 4 + 8)
public init() { }
public func handlerAdded(context: ChannelHandlerContext) {
self.headerBuffer = context.channel.allocator.buffer(capacity: WebSocketFrameEncoder.maximumFrameHeaderLength)
}
public func handlerRemoved(context: ChannelHandlerContext) {
self.headerBuffer = nil
}
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
let data = self.unwrapOutboundIn(data)
// First, we explode the frame structure and apply the mask.
let frameHeader = FrameHeader(frame: data)
var (extensionData, applicationData) = self.mask(key: frameHeader.maskKey, extensionData: data.extensionData, applicationData: data.data)
// Now we attempt to prepend the frame header to the first buffer. If we can't, we'll write to the header buffer. If we have
// an extension data buffer, that's the first buffer, and we'll also write it here.
if var unwrappedExtensionData = extensionData {
extensionData = nil // Again, forcibly nil to drop the reference.
if !unwrappedExtensionData.prependFrameHeaderIfPossible(frameHeader) {
self.writeSeparateHeaderBuffer(frameHeader, context: context)
}
context.write(self.wrapOutboundOut(unwrappedExtensionData), promise: nil)
} else if !applicationData.prependFrameHeaderIfPossible(frameHeader) {
self.writeSeparateHeaderBuffer(frameHeader, context: context)
}
// Ok, now we need to write the application data buffer.
context.write(self.wrapOutboundOut(applicationData), promise: promise)
}
/// Applies the websocket masking operation based on the passed byte buffers.
private func mask(key: WebSocketMaskingKey?, extensionData: ByteBuffer?, applicationData: ByteBuffer) -> (ByteBuffer?, ByteBuffer) {
guard let key = key else {
return (extensionData, applicationData)
}
// We take local "copies" here. This is only an issue if someone else is holding onto the parent buffers.
var extensionData = extensionData
var applicationData = applicationData
extensionData?.webSocketMask(key)
applicationData.webSocketMask(key, indexOffset: (extensionData?.readableBytes ?? 0) % 4)
return (extensionData, applicationData)
}
private func writeSeparateHeaderBuffer(_ frameHeader: FrameHeader, context: ChannelHandlerContext) {
// Grab the header buffer. We nil it out while we're in this call to avoid the risk of CoWing when we
// write to it.
guard var buffer = self.headerBuffer else {
fatalError("Channel handler lifecycle violated: did not allocate header buffer")
}
self.headerBuffer = nil
// We couldn't prepend the frame header, write it to the header buffer.
buffer.clear()
buffer.writeFrameHeader(frameHeader)
// Ok, frame header away! Before we send it we save it back onto ourselves in case we get recursively called.
self.headerBuffer = buffer
context.write(self.wrapOutboundOut(buffer), promise: nil)
}
}
extension ByteBuffer {
fileprivate mutating func prependFrameHeaderIfPossible(_ frameHeader: FrameHeader) -> Bool {
let written: Int? = self.modifyIfUniquelyOwned { buffer in
let startIndex = buffer.readerIndex - frameHeader.requiredBytes
guard startIndex >= 0 else {
return 0
}
let written = buffer.setFrameHeader(frameHeader, at: startIndex)
buffer.moveReaderIndex(to: startIndex)
return written
}
switch written {
case .none, .some(0):
return false
case .some(let x):
assert(x == frameHeader.requiredBytes)
return true
}
}
@discardableResult
fileprivate mutating func writeFrameHeader(_ frameHeader: FrameHeader) -> Int {
let written = self.setFrameHeader(frameHeader, at: self.writerIndex)
self.moveWriterIndex(forwardBy: written)
return written
}
@discardableResult
private mutating func setFrameHeader(_ frameHeader: FrameHeader, at index: Int) -> Int {
var writeIndex = index
// Calculate some information about the mask.
let maskBitMask: UInt8 = frameHeader.maskKey != nil ? 0x80 : 0x00
let frameLength = frameHeader.length
// Time to add the extra bytes. To avoid checking this twice, we also start writing stuff out here.
switch frameLength {
case 0...maxOneByteSize:
writeIndex += self.setInteger(frameHeader.firstByte, at: writeIndex)
writeIndex += self.setInteger(UInt8(frameLength) | maskBitMask, at: writeIndex)
case (maxOneByteSize + 1)...maxTwoByteSize:
writeIndex += self.setInteger(frameHeader.firstByte, at: writeIndex)
writeIndex += self.setInteger(UInt8(126) | maskBitMask, at: writeIndex)
writeIndex += self.setInteger(UInt16(frameLength), at: writeIndex)
case (maxTwoByteSize + 1)...maxNIOFrameSize:
writeIndex += self.setInteger(frameHeader.firstByte, at: writeIndex)
writeIndex += self.setInteger(UInt8(127) | maskBitMask, at: writeIndex)
writeIndex += self.setInteger(UInt64(frameLength), at: writeIndex)
default:
fatalError("NIO cannot serialize frames longer than \(maxNIOFrameSize)")
}
if let maskKey = frameHeader.maskKey {
writeIndex += self.setBytes(maskKey, at: writeIndex)
}
return writeIndex - index
}
}
/// A helper object that holds only a websocket frame header. Used to avoid accidentally CoWing on some paths.
fileprivate struct FrameHeader {
var length: Int
var maskKey: WebSocketMaskingKey?
var firstByte: UInt8 = 0
init(frame: WebSocketFrame) {
self.maskKey = frame.maskKey
self.firstByte = frame.firstByte
self.length = frame.length
}
var requiredBytes: Int {
var size = 2 // First byte and initial length byte
switch self.length {
case 0...maxOneByteSize:
// Only requires the initial length byte
break
case (maxOneByteSize + 1)...maxTwoByteSize:
// Requires an extra UInt16
size += MemoryLayout<UInt16>.size
case (maxTwoByteSize + 1)...maxNIOFrameSize:
size += MemoryLayout<UInt64>.size
default:
fatalError("NIO cannot serialize frames longer than \(maxNIOFrameSize)")
}
if maskKey != nil {
size += 4 // Masking key
}
return size
}
}
|