diff --git a/FlyingFox/Sources/WebSocket/WSFrameValidator.swift b/FlyingFox/Sources/WebSocket/WSFrameValidator.swift index 0437351..9df7b19 100644 --- a/FlyingFox/Sources/WebSocket/WSFrameValidator.swift +++ b/FlyingFox/Sources/WebSocket/WSFrameValidator.swift @@ -45,6 +45,16 @@ struct WSFrameValidator: Sendable { @Sendable func validateFrame(_ frame: WSFrame) throws -> WSFrame? { + switch frame.opcode { + case .ping, .pong, .close: + // RFC 6455 §5.5: control frame payload MUST be ≤ 125 bytes. + guard frame.payload.count <= 125 else { + throw Error("Control frame payload exceeds 125 bytes") + } + default: + break + } + if frame.opcode == .continuation { try appendContinuation(frame) guard let last = last, frame.fin else { diff --git a/FlyingFox/Tests/WebSocket/WSFrameValidatorTests.swift b/FlyingFox/Tests/WebSocket/WSFrameValidatorTests.swift index 21d5ba0..dc5dd9c 100644 --- a/FlyingFox/Tests/WebSocket/WSFrameValidatorTests.swift +++ b/FlyingFox/Tests/WebSocket/WSFrameValidatorTests.swift @@ -80,6 +80,31 @@ struct WSFrameValidatorTests { } } + @Test + func controlFrames_throwError_whenPayloadExceeds125Bytes() async { + // RFC 6455 §5.5: control frames MUST have payload length ≤ 125 bytes. + let oversized = Data(repeating: 0x41, count: 126) + await #expect(throws: WSFrameValidator.Error.self) { + try await WSFrameValidator.validate([.make(opcode: .ping, payload: oversized)]).collectAll() + } + await #expect(throws: WSFrameValidator.Error.self) { + try await WSFrameValidator.validate([.make(opcode: .pong, payload: oversized)]).collectAll() + } + await #expect(throws: WSFrameValidator.Error.self) { + try await WSFrameValidator.validate([.make(opcode: .close, payload: oversized)]).collectAll() + } + } + + @Test + func controlFrames_areAccepted_whenPayloadIsAtMost125Bytes() async throws { + let maxPayload = Data(repeating: 0x41, count: 125) + let ping = WSFrame.make(opcode: .ping, payload: maxPayload) + let emptyPing = WSFrame.make(opcode: .ping) + #expect( + try await WSFrameValidator.validate([ping, emptyPing]).collectAll() == [ping, emptyPing] + ) + } + @Test func controlFrames_ThrowError_WhenNotFin() async { await #expect(throws: WSFrameValidator.Error.self) {