mirror of
https://github.com/status-im/nim-websock.git
synced 2025-01-12 10:35:03 +00:00
7b2ed397d6
`nim-websock` suffered from a number of issues that are being addressed: 1. Long messages > `frameSize` (default 1 MB) were split into fragments of `frameSize` each. However, when a concurrent message is sent, it may be interleaved among the fragments of an already-sending message. This is only allowed for control packets without a mux extension. 2. When the WebSocket session is closed, a msg may have been partially received. This partial frame was reported as a full message, without indication that the receiving was canceled. This behaviour is fixed by raising a `WSClosedError` instead of reporting the partial msg. 3. When an individual `send` operation was canceled, it would actually stop sending the remainder of a potentially partially sent messages. This would corrupt the stream for concurrent and followup operations. Cancellation is now inhibited for the message currently sending. It is still possible to cancel messages that are not yet scheduled. 4. Messages could get reordered when using asynchronous encoders. This is addressed by delaying followup messages until the current message is fully encoded and transmitted (except for control packets). Co-authored-by: Tanguy <tanguy@status.im>
1164 lines
30 KiB
Nim
1164 lines
30 KiB
Nim
## nim-websock
|
|
## Copyright (c) 2021-2022 Status Research & Development GmbH
|
|
## Licensed under either of
|
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
## at your option.
|
|
## This file may not be copied, modified, or distributed except according to
|
|
## those terms.
|
|
|
|
import std/[
|
|
random,
|
|
sequtils,
|
|
strutils]
|
|
import pkg/[
|
|
httputils,
|
|
chronos/unittest2/asynctests,
|
|
chronicles,
|
|
stew/byteutils]
|
|
|
|
import ../websock/websock
|
|
|
|
import ./helpers
|
|
|
|
let address = initTAddress("127.0.0.1:8888")
|
|
|
|
suite "Test handshake":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "Should not select incorrect protocol":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let
|
|
server = WSServer.new(protos = ["proto"])
|
|
ws = await server.handleRequest(request)
|
|
check ws.proto == ""
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
protocols = @["wrongproto"])
|
|
|
|
check session.proto == ""
|
|
await session.stream.closeWait()
|
|
|
|
asyncTest "Test for incorrect version":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["ws"])
|
|
|
|
expect WSVersionError:
|
|
discard await server.handleRequest(request)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
expect WSFailedUpgradeError:
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
version = 14)
|
|
|
|
asyncTest "Test for client headers":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
check request.headers.getString("Connection").toUpperAscii() ==
|
|
"Upgrade".toUpperAscii()
|
|
check request.headers.getString("Upgrade").toUpperAscii() ==
|
|
"websocket".toUpperAscii()
|
|
check request.headers.getString("Cache-Control").toUpperAscii() ==
|
|
"no-cache".toUpperAscii()
|
|
check request.headers.getString("Sec-WebSocket-Version") == $WSDefaultVersion
|
|
check request.headers.contains("Sec-WebSocket-Key")
|
|
|
|
await request.sendError(Http500)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
expect WSFailedUpgradeError:
|
|
discard await connectClient()
|
|
|
|
asyncTest "Test for incorrect scheme":
|
|
let uri = "wx://127.0.0.1:8888/ws"
|
|
expect WSWrongUriSchemeError:
|
|
discard await WebSocket.connect(
|
|
parseUri(uri),
|
|
protocols = @["proto"])
|
|
|
|
suite "Test transmission":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "Server - asyncTest reading simple frame":
|
|
let testString = "Hello!"
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
let servRes = await ws.recvMsg()
|
|
|
|
check string.fromBytes(servRes) == testString
|
|
await ws.waitForClose()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.send(testString)
|
|
await session.close()
|
|
|
|
asyncTest "Send text message message with payload of length 65535":
|
|
let testString = rndStr(65535)
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
let servRes = await ws.recvMsg()
|
|
check string.fromBytes(servRes) == testString
|
|
await ws.waitForClose()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.send(testString)
|
|
await session.close()
|
|
|
|
asyncTest "Client - asyncTest reading simple frame":
|
|
let testString = "Hello!"
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
await ws.send(testString)
|
|
await ws.close()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
var clientRes = await session.recvMsg()
|
|
check string.fromBytes(clientRes) == testString
|
|
await waitForClose(session)
|
|
|
|
suite "Test ping-pong":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "Server - asyncTest ping-pong control messages":
|
|
var ping, pong = false
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onPong = proc(data: openArray[byte]) =
|
|
pong = true
|
|
)
|
|
let ws = await server.handleRequest(request)
|
|
|
|
await ws.ping()
|
|
await ws.close()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onPing = proc(data: openArray[byte]) =
|
|
ping = true
|
|
)
|
|
|
|
await waitForClose(session)
|
|
check:
|
|
ping
|
|
pong
|
|
|
|
asyncTest "Client - asyncTest ping-pong control messages":
|
|
var ping, pong = false
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onPing = proc(data: openArray[byte]) =
|
|
ping = true
|
|
)
|
|
|
|
let ws = await server.handleRequest(request)
|
|
await waitForClose(ws)
|
|
check:
|
|
ping
|
|
pong
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onPong = proc(data: openArray[byte]) =
|
|
pong = true
|
|
)
|
|
|
|
await session.ping()
|
|
await session.close()
|
|
|
|
asyncTest "Send ping with small text payload":
|
|
let testData = toBytes("Hello, world!")
|
|
var ping, pong = false
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onPing = proc(data: openArray[byte]) =
|
|
ping = data == testData)
|
|
|
|
let ws = await server.handleRequest(request)
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onPong = proc(data: openArray[byte]) =
|
|
pong = true
|
|
)
|
|
|
|
await session.ping(testData)
|
|
await session.close()
|
|
check:
|
|
ping
|
|
pong
|
|
|
|
asyncTest "Test ping payload message length":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
expect WSPayloadTooLarge:
|
|
discard await ws.recvMsg()
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let str = rndStr(126)
|
|
let session = await connectClient()
|
|
await session.ping(str.toBytes())
|
|
await session.close()
|
|
|
|
suite "Test framing":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "should split message into frames":
|
|
let testString = "1234567890"
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
let frame1 = await ws.readFrame(@[])
|
|
check not isNil(frame1)
|
|
var data1 = newSeq[byte](frame1.remainder().int)
|
|
let read1 = await ws.stream.reader.readOnce(addr data1[0], data1.len)
|
|
check read1 == 5
|
|
|
|
let frame2 = await ws.readFrame(@[])
|
|
check not isNil(frame2)
|
|
var data2 = newSeq[byte](frame2.remainder().int)
|
|
let read2 = await ws.stream.reader.readOnce(addr data2[0], data2.len)
|
|
check read2 == 5
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
frameSize = 5)
|
|
|
|
await session.send(testString)
|
|
await session.close()
|
|
|
|
asyncTest "should fail to read past max message size":
|
|
let testString = "1234567890"
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
await ws.send(testString)
|
|
await ws.close()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
|
|
expect WSMaxMessageSizeError:
|
|
discard await session.recvMsg(5)
|
|
await waitForClose(session)
|
|
|
|
asyncTest "should serialize long messages":
|
|
const numMessages = 10
|
|
let testData = newSeqWith(10 * 1024 * 1024, byte.rand())
|
|
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
for i in 0 ..< numMessages:
|
|
try:
|
|
let message = await ws.recvMsg()
|
|
let matchesExpectedMessage = (message == testData)
|
|
check matchesExpectedMessage
|
|
except CatchableError:
|
|
fail()
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
frameSize = 1 * 1024 * 1024)
|
|
|
|
var futs: seq[Future[void]]
|
|
for i in 0 ..< numMessages:
|
|
futs.add session.send(testData, Opcode.Binary)
|
|
await allFutures(futs)
|
|
await session.close()
|
|
|
|
asyncTest "should handle cancellations":
|
|
const numMessages = 10
|
|
let expectedNumMessages = numMessages - 1
|
|
let testData = newSeqWith(10 * 1024 * 1024, byte.rand())
|
|
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
for i in 0 ..< expectedNumMessages:
|
|
try:
|
|
let message = await ws.recvMsg()
|
|
let matchesExpectedMessage = (message == testData)
|
|
check matchesExpectedMessage
|
|
except CatchableError:
|
|
fail()
|
|
|
|
expect WSClosedError:
|
|
discard await ws.recvMsg() # try to receive canceled message
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
frameSize = 1 * 1024 * 1024)
|
|
|
|
var futs: seq[Future[void]]
|
|
for i in 0 ..< numMessages:
|
|
futs.add session.send(testData, Opcode.Binary)
|
|
futs[0].cancel() # expected to complete as it already started sending
|
|
futs[^2].cancel() # expected to be canceled as it has not started yet
|
|
await allFutures(futs)
|
|
await session.close()
|
|
|
|
asyncTest "should prioritize control packets":
|
|
const numMessages = 10
|
|
let testData = newSeqWith(10 * 1024 * 1024, byte.rand())
|
|
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
expect WSClosedError:
|
|
discard await ws.recvMsg()
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
frameSize = 1 * 1024 * 1024)
|
|
|
|
let messageFut = session.send(testData, Opcode.Binary)
|
|
|
|
# interleave ping packets
|
|
var futs: seq[Future[void]]
|
|
for i in 0 ..< numMessages:
|
|
futs.add session.send(opcode = Opcode.Ping)
|
|
await allFutures(futs)
|
|
check not messageFut.finished
|
|
|
|
# interleave close packet
|
|
await session.close()
|
|
check messageFut.finished
|
|
await messageFut
|
|
|
|
suite "Test Closing":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "Server closing":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
await ws.close()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await waitForClose(session)
|
|
check session.readyState == ReadyState.Closed
|
|
|
|
asyncTest "Server closing with status":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
proc closeServer(status: StatusCodes, reason: string): CloseResult{.gcsafe,
|
|
raises: [Defect].} =
|
|
try:
|
|
check status == StatusTooLarge
|
|
check reason == "Message too big!"
|
|
except Exception as exc:
|
|
raise newException(Defect, exc.msg)
|
|
|
|
return (StatusFulfilled, "")
|
|
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onClose = closeServer
|
|
)
|
|
|
|
let ws = await server.handleRequest(request)
|
|
await ws.close()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
proc clientClose(status: StatusCodes, reason: string): CloseResult {.gcsafe,
|
|
raises: [Defect].} =
|
|
try:
|
|
check status == StatusFulfilled
|
|
return (StatusTooLarge, "Message too big!")
|
|
except Exception as exc:
|
|
raise newException(Defect, exc.msg)
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onClose = clientClose)
|
|
|
|
await waitForClose(session)
|
|
check session.readyState == ReadyState.Closed
|
|
|
|
asyncTest "Client closing":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.close()
|
|
|
|
asyncTest "Client closing with status":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
proc closeServer(status: StatusCodes, reason: string): CloseResult{.gcsafe,
|
|
raises: [Defect].} =
|
|
try:
|
|
check status == StatusFulfilled
|
|
return (StatusTooLarge, "Message too big!")
|
|
except Exception as exc:
|
|
raise newException(Defect, exc.msg)
|
|
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onClose = closeServer
|
|
)
|
|
|
|
let ws = await server.handleRequest(request)
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
proc clientClose(status: StatusCodes, reason: string): CloseResult {.gcsafe,
|
|
raises: [Defect].} =
|
|
try:
|
|
check status == StatusTooLarge
|
|
check reason == "Message too big!"
|
|
return (StatusFulfilled, "")
|
|
except Exception as exc:
|
|
raise newException(Defect, exc.msg)
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onClose = clientClose)
|
|
|
|
await session.close()
|
|
check session.readyState == ReadyState.Closed
|
|
|
|
asyncTest "Mutual closing":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
await ws.close()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.close()
|
|
await waitForClose(session)
|
|
check session.readyState == ReadyState.Closed
|
|
|
|
asyncTest "Server closing with valid close code 3999":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
await ws.close(code = StatusCodes(StatusLibsCodes.high))
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
proc closeClient(status: StatusCodes, reason: string): CloseResult
|
|
{.gcsafe, raises: [Defect].} =
|
|
try:
|
|
check status == StatusCodes(StatusLibsCodes.high)
|
|
return (StatusCodes(StatusLibsCodes.high), "Reserved StatusCodes")
|
|
except Exception as exc:
|
|
raise newException(Defect, exc.msg)
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onClose = closeClient)
|
|
|
|
await waitForClose(session)
|
|
|
|
asyncTest "Client closing with valid close code 3999":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
proc closeServer(status: StatusCodes, reason: string): CloseResult{.gcsafe,
|
|
raises: [Defect].} =
|
|
try:
|
|
check status == StatusCodes(3999)
|
|
return (StatusCodes(3999), "Reserved StatusCodes")
|
|
except Exception as exc:
|
|
raise newException(Defect, exc.msg)
|
|
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onClose = closeServer
|
|
)
|
|
let ws = await server.handleRequest(request)
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.close(code = StatusCodes(3999))
|
|
|
|
asyncTest "Server closing with Payload of length 2":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
# Close with payload of length 2
|
|
await ws.close(reason = "HH")
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await waitForClose(session)
|
|
|
|
asyncTest "Client closing with Payload of length 2":
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
|
|
# Close with payload of length 2
|
|
await session.close(reason = "HH")
|
|
|
|
suite "Test Payload":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "Test payload of length 0":
|
|
let emptyStr = ""
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
let servRes = await ws.recvMsg()
|
|
|
|
check:
|
|
servRes.len == 0
|
|
string.fromBytes(servRes) == emptyStr
|
|
|
|
await ws.send(emptyStr)
|
|
await ws.waitForClose()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.send(emptyStr)
|
|
let clientRes = await session.recvMsg()
|
|
|
|
check:
|
|
clientRes.len == 0
|
|
string.fromBytes(clientRes) == emptyStr
|
|
|
|
await session.close()
|
|
|
|
asyncTest "Test multiple payloads of length 0":
|
|
let emptyStr = ""
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
for _ in 0..<3:
|
|
let servRes = await ws.recvMsg()
|
|
|
|
check:
|
|
servRes.len == 0
|
|
string.fromBytes(servRes) == emptyStr
|
|
|
|
for i in 0..3:
|
|
await ws.send(emptyStr)
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
|
|
for i in 0..3:
|
|
await session.send(emptyStr)
|
|
|
|
for _ in 0..<3:
|
|
let clientRes = await session.recvMsg()
|
|
|
|
check:
|
|
clientRes.len == 0
|
|
string.fromBytes(clientRes) == emptyStr
|
|
|
|
await session.close()
|
|
|
|
asyncTest "Send two fragments":
|
|
var ping, pong = false
|
|
let testString = "1234567890"
|
|
let msg = toBytes(testString)
|
|
let maxFrameSize = 5
|
|
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(protos = ["proto"])
|
|
|
|
let ws = await server.handleRequest(request)
|
|
let respData = await ws.recvMsg()
|
|
|
|
check:
|
|
string.fromBytes(respData) == testString
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
frameSize = maxFrameSize)
|
|
|
|
let maskKey = genMaskKey(newRng())
|
|
await session.stream.writer.write(
|
|
(await Frame(
|
|
fin: false,
|
|
rsv1: false,
|
|
rsv2: false,
|
|
rsv3: false,
|
|
opcode: Opcode.Text,
|
|
mask: true,
|
|
data: msg[0..4],
|
|
maskKey: maskKey)
|
|
.encode()))
|
|
|
|
await session.stream.writer.write(
|
|
(await Frame(
|
|
fin: true,
|
|
rsv1: false,
|
|
rsv2: false,
|
|
rsv3: false,
|
|
opcode: Opcode.Cont,
|
|
mask: true,
|
|
data: msg[5..9],
|
|
maskKey: maskKey)
|
|
.encode()))
|
|
|
|
await session.close()
|
|
|
|
asyncTest "Send two fragments with a ping with payload in-between":
|
|
var ping, pong = false
|
|
let testString = "1234567890"
|
|
let msg = toBytes(testString)
|
|
let maxFrameSize = 5
|
|
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onPing = proc(data: openArray[byte]) =
|
|
ping = true
|
|
)
|
|
|
|
let ws = await server.handleRequest(request)
|
|
let respData = await ws.recvMsg()
|
|
check:
|
|
string.fromBytes(respData) == testString
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
frameSize = maxFrameSize,
|
|
onPong = proc(data: openArray[byte]) =
|
|
pong = true
|
|
)
|
|
|
|
let maskKey = genMaskKey(newRng())
|
|
await session.stream.writer.write(
|
|
(await Frame(
|
|
fin: false,
|
|
rsv1: false,
|
|
rsv2: false,
|
|
rsv3: false,
|
|
opcode: Opcode.Text,
|
|
mask: true,
|
|
data: msg[0..4],
|
|
maskKey: maskKey)
|
|
.encode()))
|
|
|
|
await session.ping()
|
|
|
|
await session.stream.writer.write(
|
|
(await Frame(
|
|
fin: true,
|
|
rsv1: false,
|
|
rsv2: false,
|
|
rsv3: false,
|
|
opcode: Opcode.Cont,
|
|
mask: true,
|
|
data: msg[5..9],
|
|
maskKey: maskKey)
|
|
.encode()))
|
|
|
|
await session.close()
|
|
check:
|
|
ping
|
|
pong
|
|
|
|
asyncTest "Send text message with multiple frames":
|
|
const FrameSize = 3000
|
|
let testData = rndStr(FrameSize * 3 + 100)
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"], frameSize = FrameSize)
|
|
let ws = await server.handleRequest(request)
|
|
let res = await ws.recvMsg()
|
|
|
|
check ws.binary == false
|
|
await ws.send(res, Opcode.Text)
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let ws = await connectClient(
|
|
address = address,
|
|
frameSize = FrameSize
|
|
)
|
|
|
|
await ws.send(testData)
|
|
let echoed = await ws.recvMsg()
|
|
await ws.close()
|
|
|
|
check:
|
|
string.fromBytes(echoed) == testData
|
|
ws.binary == false
|
|
|
|
suite "Test Binary message with Payload":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "Test binary message with single empty payload message":
|
|
let emptyData = newSeq[byte](0)
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
let servRes = await ws.recvMsg()
|
|
|
|
check:
|
|
servRes == emptyData
|
|
ws.binary == true
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
await session.send(emptyData, Opcode.Binary)
|
|
await session.close()
|
|
|
|
asyncTest "Test binary message with multiple empty payload":
|
|
let emptyData = newSeq[byte](0)
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
|
|
let servRes = await ws.recvMsg()
|
|
|
|
check:
|
|
servRes == emptyData
|
|
ws.binary == true
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient()
|
|
|
|
for i in 0..3:
|
|
await session.send(emptyData, Opcode.Binary)
|
|
await session.close()
|
|
|
|
asyncTest "Send binary data with small text payload":
|
|
let testData = rndBin(10)
|
|
trace "testData", testData = testData
|
|
var ping, pong = false
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onPing = proc(data: openArray[byte]) =
|
|
ping = true
|
|
)
|
|
let ws = await server.handleRequest(request)
|
|
|
|
let res = await ws.recvMsg()
|
|
check:
|
|
res == testData
|
|
ws.binary == true
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onPong = proc(data: openArray[byte]) =
|
|
pong = true
|
|
)
|
|
|
|
await session.send(testData, Opcode.Binary)
|
|
await session.close()
|
|
|
|
asyncTest "Send binary message message with payload of length 125":
|
|
let testData = rndBin(125)
|
|
var ping, pong = false
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(
|
|
protos = ["proto"],
|
|
onPing = proc(data: openArray[byte]) =
|
|
ping = true
|
|
)
|
|
let ws = await server.handleRequest(request)
|
|
|
|
let res = await ws.recvMsg()
|
|
check:
|
|
res == testData
|
|
ws.binary == true
|
|
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = initTAddress("127.0.0.1:8888"),
|
|
onPong = proc(data: openArray[byte]) =
|
|
pong = true
|
|
)
|
|
|
|
await session.send(testData, Opcode.Binary)
|
|
await session.close()
|
|
|
|
asyncTest "Send binary message with multiple frames":
|
|
const FrameSize = 3000
|
|
let testData = rndBin(FrameSize * 3)
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let server = WSServer.new(protos = ["proto"])
|
|
let ws = await server.handleRequest(request)
|
|
let res = await ws.recvMsg()
|
|
|
|
check:
|
|
ws.binary == true
|
|
res == testData
|
|
|
|
await ws.send(res, Opcode.Binary)
|
|
await waitForClose(ws)
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let ws = await connectClient(
|
|
address = address,
|
|
frameSize = FrameSize
|
|
)
|
|
|
|
await ws.send(testData, Opcode.Binary)
|
|
let echoed = await ws.recvMsg()
|
|
|
|
check:
|
|
echoed == testData
|
|
|
|
await ws.close()
|
|
|
|
check:
|
|
echoed == testData
|
|
ws.binary == true
|
|
|
|
suite "Partial frames":
|
|
setup:
|
|
var
|
|
server: HttpServer
|
|
|
|
proc lowLevelRecv(
|
|
senderFrameSize, receiverFrameSize, readChunkSize: int) {.async.} =
|
|
|
|
const
|
|
howMuchWood = "How much wood could a wood chuck chuck ..."
|
|
|
|
proc handle(request: HttpRequest) {.async.} =
|
|
check request.uri.path == WSPath
|
|
|
|
let
|
|
server = WSServer.new(frameSize = receiverFrameSize)
|
|
ws = await server.handleRequest(request)
|
|
|
|
var
|
|
res = newSeq[byte](howMuchWood.len)
|
|
pos = 0
|
|
|
|
while ws.readyState != ReadyState.Closed:
|
|
let read = await ws.recv(addr res[pos], min(res.len - pos, readChunkSize))
|
|
pos += read
|
|
|
|
if pos >= res.len:
|
|
break
|
|
|
|
res.setLen(pos)
|
|
check res.len == howMuchWood.toBytes().len
|
|
check res == howMuchWood.toBytes()
|
|
await ws.waitForClose()
|
|
|
|
server = createServer(
|
|
address = address,
|
|
handler = handle,
|
|
flags = {ReuseAddr})
|
|
|
|
let session = await connectClient(
|
|
address = address,
|
|
frameSize = senderFrameSize)
|
|
|
|
await session.send(howMuchWood)
|
|
await session.close()
|
|
|
|
teardown:
|
|
if server != nil:
|
|
server.stop()
|
|
waitFor server.closeWait()
|
|
|
|
asyncTest "read in chunks less than sender frameSize":
|
|
await lowLevelRecv(7, 7, 5)
|
|
|
|
asyncTest "read in chunks greater than sender frameSize":
|
|
await lowLevelRecv(3, 7, 5)
|
|
|
|
asyncTest "sender frameSize greater than receiver":
|
|
await lowLevelRecv(7, 5, 5)
|
|
|
|
asyncTest "receiver frameSize greater than sender":
|
|
await lowLevelRecv(7, 10, 5)
|