204 lines
7.1 KiB
Nim
204 lines
7.1 KiB
Nim
## nim-websock
|
|
## Copyright (c) 2021 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.
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
import pkg/[chronos,
|
|
chronos/streams/tlsstream,
|
|
chronos/apps/http/httptable,
|
|
httputils,
|
|
stew/results]
|
|
import ./utils
|
|
|
|
const
|
|
SHA1DigestSize* = 20
|
|
WSHeaderSize* = 12
|
|
WSDefaultVersion* = 13
|
|
WSDefaultFrameSize* = 1 shl 20 # 1mb
|
|
WSMaxMessageSize* = 20 shl 20 # 20mb
|
|
WSGuid* = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
|
|
type
|
|
ReadyState* {.pure.} = enum
|
|
Connecting = 0 # The connection is not yet open.
|
|
Open = 1 # The connection is open and ready to communicate.
|
|
Closing = 2 # The connection is in the process of closing.
|
|
Closed = 3 # The connection is closed or couldn't be opened.
|
|
|
|
Opcode* {.pure.} = enum
|
|
## 4 bits. Defines the interpretation of the "Payload data".
|
|
Cont = 0x0 ## Denotes a continuation frame.
|
|
Text = 0x1 ## Denotes a text frame.
|
|
Binary = 0x2 ## Denotes a binary frame.
|
|
# 3-7 are reserved for further non-control frames.
|
|
Close = 0x8 ## Denotes a connection close.
|
|
Ping = 0x9 ## Denotes a ping.
|
|
Pong = 0xa ## Denotes a pong.
|
|
# B-F are reserved for further control frames.
|
|
Reserved = 0xf
|
|
|
|
HeaderFlag* {.pure, size: sizeof(uint8).} = enum
|
|
rsv3
|
|
rsv2
|
|
rsv1
|
|
fin
|
|
|
|
HeaderFlags* = set[HeaderFlag]
|
|
|
|
MaskKey* = array[4, char]
|
|
|
|
Frame* = ref object
|
|
fin*: bool ## Indicates that this is the final fragment in a message.
|
|
rsv1*: bool ## MUST be 0 unless negotiated that defines meanings
|
|
rsv2*: bool ## MUST be 0
|
|
rsv3*: bool ## MUST be 0
|
|
opcode*: Opcode ## Defines the interpretation of the "Payload data".
|
|
mask*: bool ## Defines whether the "Payload data" is masked.
|
|
data*: seq[byte] ## Payload data
|
|
maskKey*: MaskKey ## Masking key
|
|
length*: uint64 ## Message size.
|
|
consumed*: uint64 ## how much has been consumed from the frame
|
|
offset*: int ## offset of buffered payload data
|
|
|
|
StatusCodes* = distinct range[0..4999]
|
|
|
|
ControlCb* = proc(data: openArray[byte] = [])
|
|
{.gcsafe, raises: [Defect].}
|
|
|
|
CloseResult* = tuple
|
|
code: StatusCodes
|
|
reason: string
|
|
|
|
CloseCb* = proc(code: StatusCodes, reason: string):
|
|
CloseResult {.gcsafe, raises: [Defect].}
|
|
|
|
WebSocket* = ref object of RootObj
|
|
extensions*: seq[Ext]
|
|
version*: uint
|
|
key*: string
|
|
readyState*: ReadyState
|
|
masked*: bool # send masked packets
|
|
binary*: bool # is payload binary?
|
|
flags*: set[TLSFlags]
|
|
rng*: Rng
|
|
frameSize*: int # max frame buffer size
|
|
onPing*: ControlCb
|
|
onPong*: ControlCb
|
|
onClose*: CloseCb
|
|
|
|
WSSession* = ref object of WebSocket
|
|
stream*: AsyncStream
|
|
frame*: Frame
|
|
first*: bool
|
|
reading*: bool
|
|
proto*: string
|
|
|
|
Ext* = ref object of RootObj
|
|
name*: string
|
|
session*: WSSession
|
|
|
|
ExtParam* = object
|
|
name* : string
|
|
value*: string
|
|
|
|
ExtFactoryProc* = proc(
|
|
isServer: bool,
|
|
args: seq[ExtParam]): Result[Ext, string]
|
|
{.gcsafe, raises: [Defect].}
|
|
|
|
ExtFactory* = object
|
|
name*: string
|
|
factory*: ExtFactoryProc
|
|
clientOffer*: string
|
|
|
|
# client exec order:
|
|
# 1. append to request header
|
|
# 2. verify response header
|
|
# server exec order:
|
|
# 1. verify request header
|
|
# 2. append to response header
|
|
# ------------------------------
|
|
# Handshake exec order:
|
|
# 1. client append to request header
|
|
# 2. server verify request header
|
|
# 3. server reply with response header
|
|
# 4. client verify response header from server
|
|
Hook* = ref object of RootObj
|
|
append*: proc(ctx: Hook,
|
|
headers: var HttpTable): Result[void, string]
|
|
{.gcsafe, raises: [Defect].}
|
|
verify*: proc(ctx: Hook,
|
|
headers: HttpTable): Future[Result[void, string]]
|
|
{.closure, gcsafe, raises: [Defect].}
|
|
|
|
WebSocketError* = object of CatchableError
|
|
WSMalformedHeaderError* = object of WebSocketError
|
|
WSFailedUpgradeError* = object of WebSocketError
|
|
WSVersionError* = object of WebSocketError
|
|
WSProtoMismatchError* = object of WebSocketError
|
|
WSMaskMismatchError* = object of WebSocketError
|
|
WSHandshakeError* = object of WebSocketError
|
|
WSOpcodeMismatchError* = object of WebSocketError
|
|
WSRsvMismatchError* = object of WebSocketError
|
|
WSWrongUriSchemeError* = object of WebSocketError
|
|
WSMaxMessageSizeError* = object of WebSocketError
|
|
WSClosedError* = object of WebSocketError
|
|
WSSendError* = object of WebSocketError
|
|
WSPayloadTooLarge* = object of WebSocketError
|
|
WSReservedOpcodeError* = object of WebSocketError
|
|
WSFragmentedControlFrameError* = object of WebSocketError
|
|
WSInvalidCloseCodeError* = object of WebSocketError
|
|
WSPayloadLengthError* = object of WebSocketError
|
|
WSInvalidOpcodeError* = object of WebSocketError
|
|
WSInvalidUTF8* = object of WebSocketError
|
|
WSExtError* = object of WebSocketError
|
|
WSHookError* = object of WebSocketError
|
|
|
|
const
|
|
StatusNotUsed* = (StatusCodes(0)..StatusCodes(999))
|
|
StatusFulfilled* = StatusCodes(1000)
|
|
StatusGoingAway* = StatusCodes(1001)
|
|
StatusProtocolError* = StatusCodes(1002)
|
|
StatusCannotAccept* = StatusCodes(1003)
|
|
StatusReserved* = StatusCodes(1004) # 1004 reserved
|
|
StatusNoStatus* = StatusCodes(1005) # use by clients
|
|
StatusClosedAbnormally* = StatusCodes(1006) # use by clients
|
|
StatusInconsistent* = StatusCodes(1007)
|
|
StatusPolicyError* = StatusCodes(1008)
|
|
StatusTooLarge* = StatusCodes(1009)
|
|
StatusNoExtensions* = StatusCodes(1010)
|
|
StatusUnexpectedError* = StatusCodes(1011)
|
|
StatusFailedTls* = StatusCodes(1015) # passed to applications to indicate TLS errors
|
|
StatusReservedProtocol* = StatusCodes(1016)..StatusCodes(2999) # reserved for this protocol
|
|
StatusLibsCodes* = (StatusCodes(3000)..StatusCodes(3999)) # 3000-3999 reserved for libs
|
|
StatusAppsCodes* = (StatusCodes(4000)..StatusCodes(4999)) # 4000-4999 reserved for apps
|
|
|
|
proc `<=`*(a, b: StatusCodes): bool = a.uint16 <= b.uint16
|
|
proc `>=`*(a, b: StatusCodes): bool = a.uint16 >= b.uint16
|
|
proc `<`*(a, b: StatusCodes): bool = a.uint16 < b.uint16
|
|
proc `>`*(a, b: StatusCodes): bool = a.uint16 > b.uint16
|
|
proc `==`*(a, b: StatusCodes): bool = a.uint16 == b.uint16
|
|
|
|
proc high*(a: HSlice[StatusCodes, StatusCodes]): uint16 = a.b.uint16
|
|
proc low*(a: HSlice[StatusCodes, StatusCodes]): uint16 = a.a.uint16
|
|
|
|
proc `$`*(a: StatusCodes): string = $(a.int)
|
|
|
|
proc `name=`*(self: Ext, name: string) =
|
|
raiseAssert "Can't change extensions name!"
|
|
|
|
method decode*(self: Ext, frame: Frame): Future[Frame] {.base, async.} =
|
|
raiseAssert "Not implemented!"
|
|
|
|
method encode*(self: Ext, frame: Frame): Future[Frame] {.base, async.} =
|
|
raiseAssert "Not implemented!"
|
|
|
|
method toHttpOptions*(self: Ext): string {.base.} =
|
|
raiseAssert "Not implemented!"
|