mirror of
https://github.com/defold/extension-websocket.git
synced 2025-06-27 09:47:44 +02:00
Merge pull request #1 from defold/initial
Add first version of websocket extension
This commit is contained in:
commit
2bddcac495
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
/.internal
|
/.internal
|
||||||
/build
|
/build
|
||||||
|
/bundle*
|
||||||
.externalToolBuilders
|
.externalToolBuilders
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
@ -7,4 +8,7 @@ Thumbs.db
|
|||||||
*.pyc
|
*.pyc
|
||||||
.project
|
.project
|
||||||
.cproject
|
.cproject
|
||||||
builtins
|
builtins
|
||||||
|
lws_source
|
||||||
|
lws_build
|
||||||
|
*.profraw
|
Binary file not shown.
Binary file not shown.
57
docs/index.md
Normal file
57
docs/index.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Defold websocket extension API documentation
|
||||||
|
brief: This manual covers how to use websockets with Defold
|
||||||
|
---
|
||||||
|
|
||||||
|
# Defold websocket extension API documentation
|
||||||
|
|
||||||
|
This extension supports both secure (`wss://`) and non secure (`ws://`) websocket connections.
|
||||||
|
All platforms should support this extension.
|
||||||
|
|
||||||
|
|
||||||
|
Here is how you connect to a websocket and listen to events:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local function websocket_callback(self, conn, data)
|
||||||
|
if data.event == websocket.EVENT_DISCONNECTED then
|
||||||
|
print("disconnected " .. conn)
|
||||||
|
self.connection = nil
|
||||||
|
elseif data.event == websocket.EVENT_CONNECTED then
|
||||||
|
print("Connected " .. conn)
|
||||||
|
-- self.connection = conn
|
||||||
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
|
print("Error:", data.error)
|
||||||
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
|
print("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function init(self)
|
||||||
|
self.url = "ws://echo.websocket.org"
|
||||||
|
local params = {}
|
||||||
|
self.connection = websocket.connect(self.url, params, websocket_callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
function finalize(self)
|
||||||
|
if self.connection ~= nil then
|
||||||
|
websocket.disconnect(self.connection)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
To use this library in your Defold project, add the following URL to your `game.project` dependencies:
|
||||||
|
|
||||||
|
https://github.com/defold/extension-websocket/archive/master.zip
|
||||||
|
|
||||||
|
We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-websocket/releases).
|
||||||
|
|
||||||
|
|
||||||
|
## Source code
|
||||||
|
|
||||||
|
The source code is available on [GitHub](https://github.com/defold/extension-websocket)
|
||||||
|
|
||||||
|
## API reference
|
||||||
|
|
||||||
|
https://defold.com/extension-websocket/api/
|
@ -1,30 +0,0 @@
|
|||||||
function init(self)
|
|
||||||
local s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
local reverse_s = myextension.reverse(s)
|
|
||||||
print(reverse_s) --> ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba
|
|
||||||
end
|
|
||||||
|
|
||||||
function final(self)
|
|
||||||
-- Add finalization code here
|
|
||||||
-- Remove this function if not needed
|
|
||||||
end
|
|
||||||
|
|
||||||
function update(self, dt)
|
|
||||||
-- Add update code here
|
|
||||||
-- Remove this function if not needed
|
|
||||||
end
|
|
||||||
|
|
||||||
function on_message(self, message_id, message, sender)
|
|
||||||
-- Add message-handling code here
|
|
||||||
-- Remove this function if not needed
|
|
||||||
end
|
|
||||||
|
|
||||||
function on_input(self, action_id, action)
|
|
||||||
-- Add input-handling code here
|
|
||||||
-- Remove this function if not needed
|
|
||||||
end
|
|
||||||
|
|
||||||
function on_reload(self)
|
|
||||||
-- Add reload-handling code here
|
|
||||||
-- Remove this function if not needed
|
|
||||||
end
|
|
144
examples/assets/button.gui
Normal file
144
examples/assets/button.gui
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
script: ""
|
||||||
|
fonts {
|
||||||
|
name: "example"
|
||||||
|
font: "/examples/assets/fonts/example.font"
|
||||||
|
}
|
||||||
|
textures {
|
||||||
|
name: "ui"
|
||||||
|
texture: "/examples/assets/ui.atlas"
|
||||||
|
}
|
||||||
|
background_color {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 45.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
texture: "ui/green_button08"
|
||||||
|
id: "button"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
layer: "below"
|
||||||
|
inherit_alpha: true
|
||||||
|
slice9 {
|
||||||
|
x: 8.0
|
||||||
|
y: 8.0
|
||||||
|
z: 8.0
|
||||||
|
w: 8.0
|
||||||
|
}
|
||||||
|
clipping_mode: CLIPPING_MODE_NONE
|
||||||
|
clipping_visible: true
|
||||||
|
clipping_inverted: false
|
||||||
|
alpha: 1.0
|
||||||
|
template_node_child: false
|
||||||
|
size_mode: SIZE_MODE_MANUAL
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 40.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: "FOOBAR"
|
||||||
|
font: "example"
|
||||||
|
id: "label"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: false
|
||||||
|
parent: "button"
|
||||||
|
layer: "text"
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
template_node_child: false
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "below"
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "text"
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "above"
|
||||||
|
}
|
||||||
|
material: "/builtins/materials/gui.material"
|
||||||
|
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||||
|
max_nodes: 512
|
3
examples/assets/fonts/example.font
Normal file
3
examples/assets/fonts/example.font
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
font: "/builtins/fonts/vera_mo_bd.ttf"
|
||||||
|
material: "/builtins/fonts/font.material"
|
||||||
|
size: 15
|
BIN
examples/assets/images/green_button08.png
Executable file
BIN
examples/assets/images/green_button08.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 267 B |
6
examples/assets/ui.atlas
Normal file
6
examples/assets/ui.atlas
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
images {
|
||||||
|
image: "/examples/assets/images/green_button08.png"
|
||||||
|
}
|
||||||
|
margin: 0
|
||||||
|
extrude_borders: 2
|
||||||
|
inner_padding: 0
|
6
example/example.collection → examples/websocket.collection
Executable file → Normal file
6
example/example.collection → examples/websocket.collection
Executable file → Normal file
@ -1,10 +1,10 @@
|
|||||||
name: "main"
|
name: "default"
|
||||||
scale_along_z: 0
|
scale_along_z: 0
|
||||||
embedded_instances {
|
embedded_instances {
|
||||||
id: "go"
|
id: "go"
|
||||||
data: "components {\n"
|
data: "components {\n"
|
||||||
" id: \"example\"\n"
|
" id: \"gui\"\n"
|
||||||
" component: \"/example/example.script\"\n"
|
" component: \"/examples/websocket.gui\"\n"
|
||||||
" position {\n"
|
" position {\n"
|
||||||
" x: 0.0\n"
|
" x: 0.0\n"
|
||||||
" y: 0.0\n"
|
" y: 0.0\n"
|
786
examples/websocket.gui
Normal file
786
examples/websocket.gui
Normal file
@ -0,0 +1,786 @@
|
|||||||
|
script: "/examples/websocket.gui_script"
|
||||||
|
fonts {
|
||||||
|
name: "example"
|
||||||
|
font: "/examples/assets/fonts/example.font"
|
||||||
|
}
|
||||||
|
textures {
|
||||||
|
name: "ui"
|
||||||
|
texture: "/examples/assets/ui.atlas"
|
||||||
|
}
|
||||||
|
background_color {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 10.0
|
||||||
|
y: 949.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 620.0
|
||||||
|
y: 600.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: ""
|
||||||
|
font: "example"
|
||||||
|
id: "log"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_NW
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: true
|
||||||
|
layer: ""
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
template_node_child: false
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 214.0
|
||||||
|
y: 314.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 100.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEMPLATE
|
||||||
|
id: "connect_ws"
|
||||||
|
layer: ""
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
template: "/examples/assets/button.gui"
|
||||||
|
template_node_child: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 45.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
texture: "ui/green_button08"
|
||||||
|
id: "connect_ws/button"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
parent: "connect_ws"
|
||||||
|
layer: "below"
|
||||||
|
inherit_alpha: true
|
||||||
|
slice9 {
|
||||||
|
x: 8.0
|
||||||
|
y: 8.0
|
||||||
|
z: 8.0
|
||||||
|
w: 8.0
|
||||||
|
}
|
||||||
|
clipping_mode: CLIPPING_MODE_NONE
|
||||||
|
clipping_visible: true
|
||||||
|
clipping_inverted: false
|
||||||
|
alpha: 1.0
|
||||||
|
template_node_child: true
|
||||||
|
size_mode: SIZE_MODE_MANUAL
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 40.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: "CONNECT ws://"
|
||||||
|
font: "example"
|
||||||
|
id: "connect_ws/label"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: false
|
||||||
|
parent: "connect_ws/button"
|
||||||
|
layer: "text"
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
overridden_fields: 8
|
||||||
|
template_node_child: true
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 320.0
|
||||||
|
y: 184.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 100.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEMPLATE
|
||||||
|
id: "close"
|
||||||
|
layer: ""
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
template: "/examples/assets/button.gui"
|
||||||
|
template_node_child: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 45.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
texture: "ui/green_button08"
|
||||||
|
id: "close/button"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
parent: "close"
|
||||||
|
layer: "below"
|
||||||
|
inherit_alpha: true
|
||||||
|
slice9 {
|
||||||
|
x: 8.0
|
||||||
|
y: 8.0
|
||||||
|
z: 8.0
|
||||||
|
w: 8.0
|
||||||
|
}
|
||||||
|
clipping_mode: CLIPPING_MODE_NONE
|
||||||
|
clipping_visible: true
|
||||||
|
clipping_inverted: false
|
||||||
|
alpha: 1.0
|
||||||
|
template_node_child: true
|
||||||
|
size_mode: SIZE_MODE_MANUAL
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 40.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: "CLOSE"
|
||||||
|
font: "example"
|
||||||
|
id: "close/label"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: false
|
||||||
|
parent: "close/button"
|
||||||
|
layer: "text"
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
overridden_fields: 8
|
||||||
|
template_node_child: true
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 320.0
|
||||||
|
y: 249.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 100.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEMPLATE
|
||||||
|
id: "send"
|
||||||
|
layer: ""
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
template: "/examples/assets/button.gui"
|
||||||
|
template_node_child: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 45.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
texture: "ui/green_button08"
|
||||||
|
id: "send/button"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
parent: "send"
|
||||||
|
layer: "below"
|
||||||
|
inherit_alpha: true
|
||||||
|
slice9 {
|
||||||
|
x: 8.0
|
||||||
|
y: 8.0
|
||||||
|
z: 8.0
|
||||||
|
w: 8.0
|
||||||
|
}
|
||||||
|
clipping_mode: CLIPPING_MODE_NONE
|
||||||
|
clipping_visible: true
|
||||||
|
clipping_inverted: false
|
||||||
|
alpha: 1.0
|
||||||
|
template_node_child: true
|
||||||
|
size_mode: SIZE_MODE_MANUAL
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 40.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: "SEND"
|
||||||
|
font: "example"
|
||||||
|
id: "send/label"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: false
|
||||||
|
parent: "send/button"
|
||||||
|
layer: "text"
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
overridden_fields: 8
|
||||||
|
template_node_child: true
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 429.0
|
||||||
|
y: 314.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 100.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEMPLATE
|
||||||
|
id: "connect_wss"
|
||||||
|
layer: ""
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
template: "/examples/assets/button.gui"
|
||||||
|
template_node_child: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 45.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
texture: "ui/green_button08"
|
||||||
|
id: "connect_wss/button"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
parent: "connect_wss"
|
||||||
|
layer: "below"
|
||||||
|
inherit_alpha: true
|
||||||
|
slice9 {
|
||||||
|
x: 8.0
|
||||||
|
y: 8.0
|
||||||
|
z: 8.0
|
||||||
|
w: 8.0
|
||||||
|
}
|
||||||
|
clipping_mode: CLIPPING_MODE_NONE
|
||||||
|
clipping_visible: true
|
||||||
|
clipping_inverted: false
|
||||||
|
alpha: 1.0
|
||||||
|
template_node_child: true
|
||||||
|
size_mode: SIZE_MODE_MANUAL
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 40.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: "CONNECT wss://"
|
||||||
|
font: "example"
|
||||||
|
id: "connect_wss/label"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: false
|
||||||
|
parent: "connect_wss/button"
|
||||||
|
layer: "text"
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
overridden_fields: 8
|
||||||
|
template_node_child: true
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
x: 320.0
|
||||||
|
y: 314.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
rotation {
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
scale {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 200.0
|
||||||
|
y: 100.0
|
||||||
|
z: 0.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
blend_mode: BLEND_MODE_ALPHA
|
||||||
|
text: "<text>"
|
||||||
|
font: "example"
|
||||||
|
id: "connection_text"
|
||||||
|
xanchor: XANCHOR_NONE
|
||||||
|
yanchor: YANCHOR_NONE
|
||||||
|
pivot: PIVOT_CENTER
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
w: 1.0
|
||||||
|
}
|
||||||
|
adjust_mode: ADJUST_MODE_FIT
|
||||||
|
line_break: false
|
||||||
|
layer: ""
|
||||||
|
inherit_alpha: true
|
||||||
|
alpha: 1.0
|
||||||
|
outline_alpha: 1.0
|
||||||
|
shadow_alpha: 1.0
|
||||||
|
template_node_child: false
|
||||||
|
text_leading: 1.0
|
||||||
|
text_tracking: 0.0
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "below"
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "dynamic"
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "text"
|
||||||
|
}
|
||||||
|
layers {
|
||||||
|
name: "above"
|
||||||
|
}
|
||||||
|
material: "/builtins/materials/gui.material"
|
||||||
|
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||||
|
max_nodes: 512
|
104
examples/websocket.gui_script
Normal file
104
examples/websocket.gui_script
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
local URL="://echo.websocket.org"
|
||||||
|
|
||||||
|
local function click_button(node, action)
|
||||||
|
return gui.is_enabled(node) and action.pressed and gui.pick_node(node, action.x, action.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function update_gui(self)
|
||||||
|
if self.connection then
|
||||||
|
gui.set_enabled(self.connect_ws_node, false)
|
||||||
|
gui.set_enabled(self.connect_wss_node, false)
|
||||||
|
gui.set_enabled(self.send_node, true)
|
||||||
|
gui.set_enabled(self.close_node, true)
|
||||||
|
gui.set_enabled(self.connection_text, true)
|
||||||
|
gui.set_text(self.connection_text, "Connected to " .. self.url)
|
||||||
|
else
|
||||||
|
gui.set_enabled(self.connect_ws_node, true)
|
||||||
|
gui.set_enabled(self.connect_wss_node, true)
|
||||||
|
gui.set_enabled(self.send_node, false)
|
||||||
|
gui.set_enabled(self.close_node, false)
|
||||||
|
gui.set_enabled(self.connection_text, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function log(...)
|
||||||
|
local text = ""
|
||||||
|
local len = select("#", ...)
|
||||||
|
for i=1,len do
|
||||||
|
text = text .. tostring(select(i, ...)) .. (i == len and "" or ", ")
|
||||||
|
end
|
||||||
|
|
||||||
|
print(text)
|
||||||
|
local node = gui.get_node("log")
|
||||||
|
gui.set_text(node, gui.get_text(node) .. "\n" .. text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function init(self)
|
||||||
|
msg.post(".", "acquire_input_focus")
|
||||||
|
msg.post("@render:", "clear_color", { color = vmath.vector4(0.2, 0.4, 0.8, 1.0) })
|
||||||
|
self.connect_ws_node = gui.get_node("connect_ws/button")
|
||||||
|
self.connect_wss_node = gui.get_node("connect_wss/button")
|
||||||
|
self.send_node = gui.get_node("send/button")
|
||||||
|
self.close_node = gui.get_node("close/button")
|
||||||
|
self.connection_text = gui.get_node("connection_text")
|
||||||
|
self.connection = nil
|
||||||
|
update_gui(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function final(self)
|
||||||
|
msg.post(".", "release_input_focus")
|
||||||
|
end
|
||||||
|
|
||||||
|
function update(self, dt)
|
||||||
|
if self.ws then
|
||||||
|
self.ws.step()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function websocket_callback(self, conn, data)
|
||||||
|
if data.event == websocket.EVENT_DISCONNECTED then
|
||||||
|
log("Disconnected: " .. tostring(conn))
|
||||||
|
self.connection = nil
|
||||||
|
update_gui(self)
|
||||||
|
elseif data.event == websocket.EVENT_CONNECTED then
|
||||||
|
update_gui(self)
|
||||||
|
log("Connected: " .. tostring(conn))
|
||||||
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
|
log("Error: '" .. data.error .. "'")
|
||||||
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
|
log("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function connect(self, scheme)
|
||||||
|
local params = {}
|
||||||
|
|
||||||
|
self.url = scheme .. URL
|
||||||
|
log("Connecting to " .. self.url)
|
||||||
|
self.connection = websocket.connect(self.url, params, websocket_callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function disconnect(self)
|
||||||
|
if self.connection ~= nil then
|
||||||
|
websocket.disconnect(self.connection)
|
||||||
|
end
|
||||||
|
self.connection = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function on_input(self, action_id, action)
|
||||||
|
if click_button(self.connect_ws_node, action) then
|
||||||
|
connect(self, "ws")
|
||||||
|
elseif click_button(self.connect_wss_node, action) then
|
||||||
|
connect(self, "wss")
|
||||||
|
elseif click_button(self.close_node, action) then
|
||||||
|
disconnect(self)
|
||||||
|
elseif click_button(gui.get_node("send/button"), action) then
|
||||||
|
local message_to_send = 'sending to server'
|
||||||
|
local ok, was_clean, code, reason = websocket.send(self.connection, message_to_send)
|
||||||
|
log("Sending '" .. message_to_send .. "'", ok, was_clean, code, reason)
|
||||||
|
elseif click_button(gui.get_node("close/button"), action) then
|
||||||
|
log("Closing")
|
||||||
|
self.ws:close()
|
||||||
|
end
|
||||||
|
end
|
11
game.project
11
game.project
@ -1,16 +1,17 @@
|
|||||||
[bootstrap]
|
[bootstrap]
|
||||||
main_collection = /example/example.collectionc
|
main_collection = /examples/websocket.collectionc
|
||||||
|
|
||||||
[script]
|
[script]
|
||||||
shared_state = 1
|
shared_state = 1
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
width = 960
|
width = 640
|
||||||
height = 640
|
height = 960
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
title = myextension
|
title = extension-websocket
|
||||||
|
_dependencies = https://github.com/GameAnalytics/defold-openssl/archive/1.0.0.zip
|
||||||
|
|
||||||
[library]
|
[library]
|
||||||
include_dirs = myextension
|
include_dirs = websocket
|
||||||
|
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# C++ symbol in your extension
|
|
||||||
name: "MyExtension"
|
|
@ -1,112 +0,0 @@
|
|||||||
// myextension.cpp
|
|
||||||
// Extension lib defines
|
|
||||||
#define LIB_NAME "MyExtension"
|
|
||||||
#define MODULE_NAME "myextension"
|
|
||||||
|
|
||||||
// include the Defold SDK
|
|
||||||
#include <dmsdk/sdk.h>
|
|
||||||
|
|
||||||
static int Reverse(lua_State* L)
|
|
||||||
{
|
|
||||||
// The number of expected items to be on the Lua stack
|
|
||||||
// once this struct goes out of scope
|
|
||||||
DM_LUA_STACK_CHECK(L, 1);
|
|
||||||
|
|
||||||
// Check and get parameter string from stack
|
|
||||||
char* str = (char*)luaL_checkstring(L, 1);
|
|
||||||
|
|
||||||
// Reverse the string
|
|
||||||
int len = strlen(str);
|
|
||||||
for(int i = 0; i < len / 2; i++) {
|
|
||||||
const char a = str[i];
|
|
||||||
const char b = str[len - i - 1];
|
|
||||||
str[i] = b;
|
|
||||||
str[len - i - 1] = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the reverse string on the stack
|
|
||||||
lua_pushstring(L, str);
|
|
||||||
|
|
||||||
// Return 1 item
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions exposed to Lua
|
|
||||||
static const luaL_reg Module_methods[] =
|
|
||||||
{
|
|
||||||
{"reverse", Reverse},
|
|
||||||
{0, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void LuaInit(lua_State* L)
|
|
||||||
{
|
|
||||||
int top = lua_gettop(L);
|
|
||||||
|
|
||||||
// Register lua names
|
|
||||||
luaL_register(L, MODULE_NAME, Module_methods);
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
assert(top == lua_gettop(L));
|
|
||||||
}
|
|
||||||
|
|
||||||
dmExtension::Result AppInitializeMyExtension(dmExtension::AppParams* params)
|
|
||||||
{
|
|
||||||
dmLogInfo("AppInitializeMyExtension\n");
|
|
||||||
return dmExtension::RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
dmExtension::Result InitializeMyExtension(dmExtension::Params* params)
|
|
||||||
{
|
|
||||||
// Init Lua
|
|
||||||
LuaInit(params->m_L);
|
|
||||||
dmLogInfo("Registered %s Extension\n", MODULE_NAME);
|
|
||||||
return dmExtension::RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
dmExtension::Result AppFinalizeMyExtension(dmExtension::AppParams* params)
|
|
||||||
{
|
|
||||||
dmLogInfo("AppFinalizeMyExtension\n");
|
|
||||||
return dmExtension::RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
dmExtension::Result FinalizeMyExtension(dmExtension::Params* params)
|
|
||||||
{
|
|
||||||
dmLogInfo("FinalizeMyExtension\n");
|
|
||||||
return dmExtension::RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
dmExtension::Result OnUpdateMyExtension(dmExtension::Params* params)
|
|
||||||
{
|
|
||||||
dmLogInfo("OnUpdateMyExtension\n");
|
|
||||||
return dmExtension::RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnEventMyExtension(dmExtension::Params* params, const dmExtension::Event* event)
|
|
||||||
{
|
|
||||||
switch(event->m_Event)
|
|
||||||
{
|
|
||||||
case dmExtension::EVENT_ID_ACTIVATEAPP:
|
|
||||||
dmLogInfo("OnEventMyExtension - EVENT_ID_ACTIVATEAPP\n");
|
|
||||||
break;
|
|
||||||
case dmExtension::EVENT_ID_DEACTIVATEAPP:
|
|
||||||
dmLogInfo("OnEventMyExtension - EVENT_ID_DEACTIVATEAPP\n");
|
|
||||||
break;
|
|
||||||
case dmExtension::EVENT_ID_ICONIFYAPP:
|
|
||||||
dmLogInfo("OnEventMyExtension - EVENT_ID_ICONIFYAPP\n");
|
|
||||||
break;
|
|
||||||
case dmExtension::EVENT_ID_DEICONIFYAPP:
|
|
||||||
dmLogInfo("OnEventMyExtension - EVENT_ID_DEICONIFYAPP\n");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
dmLogWarning("OnEventMyExtension - Unknown event id\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defold SDK uses a macro for setting up extension entry points:
|
|
||||||
//
|
|
||||||
// DM_DECLARE_EXTENSION(symbol, name, app_init, app_final, init, update, on_event, final)
|
|
||||||
|
|
||||||
// MyExtension is the C++ symbol that holds all relevant extension data.
|
|
||||||
// It must match the name field in the `ext.manifest`
|
|
||||||
DM_DECLARE_EXTENSION(MyExtension, LIB_NAME, AppInitializeMyExtension, AppFinalizeMyExtension, InitializeMyExtension, OnUpdateMyExtension, OnEventMyExtension, FinalizeMyExtension)
|
|
119
websocket/api/api.script_api
Normal file
119
websocket/api/api.script_api
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
- name: websocket
|
||||||
|
type: table
|
||||||
|
desc: Functions and constants for using websockets. Supported on all platforms.
|
||||||
|
members:
|
||||||
|
|
||||||
|
#*****************************************************************************************************
|
||||||
|
|
||||||
|
- name: connect
|
||||||
|
type: function
|
||||||
|
desc: Connects to a remote address
|
||||||
|
parameters:
|
||||||
|
- name: url
|
||||||
|
type: string
|
||||||
|
desc: url of the remote connection
|
||||||
|
|
||||||
|
- name: params
|
||||||
|
type: table
|
||||||
|
desc: optional parameters as properties. The following parameters can be set
|
||||||
|
members:
|
||||||
|
|
||||||
|
- name: callback
|
||||||
|
type: function
|
||||||
|
desc: callback that receives all messages from the connection
|
||||||
|
parameters:
|
||||||
|
- name: self
|
||||||
|
type: object
|
||||||
|
desc: The script instance that was used to register the callback
|
||||||
|
|
||||||
|
- name: connection
|
||||||
|
type: object
|
||||||
|
desc: the connection
|
||||||
|
|
||||||
|
- name: data
|
||||||
|
type: table
|
||||||
|
desc: the event payload
|
||||||
|
members:
|
||||||
|
- name: event
|
||||||
|
type: number
|
||||||
|
desc: The current event. One of the following
|
||||||
|
|
||||||
|
- `websocket.EVENT_CONNECTED`
|
||||||
|
|
||||||
|
- `websocket.EVENT_DISCONNECTED`
|
||||||
|
|
||||||
|
- `websocket.EVENT_ERROR`
|
||||||
|
|
||||||
|
- `websocket.EVENT_MESSAGE`
|
||||||
|
|
||||||
|
- name: message
|
||||||
|
type: string
|
||||||
|
desc: The received data. Only valid if event is `websocket.EVENT_MESSAGE`
|
||||||
|
|
||||||
|
- name: error
|
||||||
|
type: string
|
||||||
|
desc: The error string. Only valid if event is `websocket.EVENT_ERROR`
|
||||||
|
|
||||||
|
|
||||||
|
returns:
|
||||||
|
- name: connection
|
||||||
|
type: object
|
||||||
|
desc: the connection
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- desc: |-
|
||||||
|
```lua
|
||||||
|
local function websocket_callback(self, conn, data)
|
||||||
|
if data.event == websocket.EVENT_DISCONNECTED then
|
||||||
|
print("disconnected " .. conn)
|
||||||
|
self.connection = nil
|
||||||
|
elseif data.event == websocket.EVENT_CONNECTED then
|
||||||
|
print("Connected " .. conn)
|
||||||
|
-- self.connection = conn
|
||||||
|
elseif data.event == websocket.EVENT_ERROR then
|
||||||
|
print("Error:", data.error)
|
||||||
|
elseif data.event == websocket.EVENT_MESSAGE then
|
||||||
|
print("Receiving: '" .. tostring(data.message) .. "'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function init(self)
|
||||||
|
self.url = "ws://echo.websocket.org"
|
||||||
|
local params = {}
|
||||||
|
self.connection = websocket.connect(self.url, params, websocket_callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
function finalize(self)
|
||||||
|
if self.connection ~= nil then
|
||||||
|
websocket.disconnect(self.connection)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
#*****************************************************************************************************
|
||||||
|
|
||||||
|
- name: disconnect
|
||||||
|
type: function
|
||||||
|
desc: Explicitly close a websocket
|
||||||
|
parameters:
|
||||||
|
- name: connection
|
||||||
|
type: object
|
||||||
|
desc: the websocket connection
|
||||||
|
|
||||||
|
#*****************************************************************************************************
|
||||||
|
|
||||||
|
- name: EVENT_CONNECTED
|
||||||
|
type: number
|
||||||
|
desc: The websocket was connected
|
||||||
|
|
||||||
|
- name: EVENT_DISCONNECTED
|
||||||
|
type: number
|
||||||
|
desc: The websocket disconnected
|
||||||
|
|
||||||
|
- name: EVENT_MESSAGE
|
||||||
|
type: number
|
||||||
|
desc: The websocket received data
|
||||||
|
|
||||||
|
- name: EVENT_ERROR
|
||||||
|
type: number
|
||||||
|
desc: The websocket encountered an error
|
8
websocket/ext.manifest
Normal file
8
websocket/ext.manifest
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# C++ symbol in your extension
|
||||||
|
name: "Websocket"
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
common:
|
||||||
|
context:
|
||||||
|
includes: ["upload/websocket/include/wslay"]
|
||||||
|
defines: ["HAVE_CONFIG_H"]
|
10
websocket/include/wslay/config.h
Normal file
10
websocket/include/wslay/config.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* This configuration file is used only by CMake build system. */
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#define HAVE_ARPA_INET_H
|
||||||
|
#define HAVE_NETINET_IN_H
|
||||||
|
/* #undef HAVE_WINSOCK2_H */
|
||||||
|
/* #undef WORDS_BIGENDIAN */
|
||||||
|
|
||||||
|
#endif /* CONFIG_H */
|
772
websocket/include/wslay/wslay.h
Normal file
772
websocket/include/wslay/wslay.h
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_H
|
||||||
|
#define WSLAY_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* wslay/wslayver.h is generated from wslay/wslayver.h.in by
|
||||||
|
* configure. The projects which do not use autotools can set
|
||||||
|
* WSLAY_VERSION macro from outside to avoid to generating wslayver.h
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_VERSION
|
||||||
|
# include <wslay/wslayver.h>
|
||||||
|
#endif /* WSLAY_VERSION */
|
||||||
|
|
||||||
|
enum wslay_error {
|
||||||
|
WSLAY_ERR_WANT_READ = -100,
|
||||||
|
WSLAY_ERR_WANT_WRITE = -101,
|
||||||
|
WSLAY_ERR_PROTO = -200,
|
||||||
|
WSLAY_ERR_INVALID_ARGUMENT = -300,
|
||||||
|
WSLAY_ERR_INVALID_CALLBACK = -301,
|
||||||
|
WSLAY_ERR_NO_MORE_MSG = -302,
|
||||||
|
WSLAY_ERR_CALLBACK_FAILURE = -400,
|
||||||
|
WSLAY_ERR_WOULDBLOCK = -401,
|
||||||
|
WSLAY_ERR_NOMEM = -500
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Status codes defined in RFC6455
|
||||||
|
*/
|
||||||
|
enum wslay_status_code {
|
||||||
|
WSLAY_CODE_NORMAL_CLOSURE = 1000,
|
||||||
|
WSLAY_CODE_GOING_AWAY = 1001,
|
||||||
|
WSLAY_CODE_PROTOCOL_ERROR = 1002,
|
||||||
|
WSLAY_CODE_UNSUPPORTED_DATA = 1003,
|
||||||
|
WSLAY_CODE_NO_STATUS_RCVD = 1005,
|
||||||
|
WSLAY_CODE_ABNORMAL_CLOSURE = 1006,
|
||||||
|
WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA = 1007,
|
||||||
|
WSLAY_CODE_POLICY_VIOLATION = 1008,
|
||||||
|
WSLAY_CODE_MESSAGE_TOO_BIG = 1009,
|
||||||
|
WSLAY_CODE_MANDATORY_EXT = 1010,
|
||||||
|
WSLAY_CODE_INTERNAL_SERVER_ERROR = 1011,
|
||||||
|
WSLAY_CODE_TLS_HANDSHAKE = 1015
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wslay_io_flags {
|
||||||
|
/*
|
||||||
|
* There is more data to send.
|
||||||
|
*/
|
||||||
|
WSLAY_MSG_MORE = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function used by wslay_frame_send() function when it needs
|
||||||
|
* to send data. The implementation of this function must send at most
|
||||||
|
* len bytes of data in data. flags is the bitwise OR of zero or more
|
||||||
|
* of the following flag:
|
||||||
|
*
|
||||||
|
* WSLAY_MSG_MORE
|
||||||
|
* There is more data to send
|
||||||
|
*
|
||||||
|
* It provides some hints to tune performance and behaviour. user_data
|
||||||
|
* is one given in wslay_frame_context_init() function. The
|
||||||
|
* implementation of this function must return the number of bytes
|
||||||
|
* sent. If there is an error, return -1. The return value 0 is also
|
||||||
|
* treated an error by the library.
|
||||||
|
*/
|
||||||
|
typedef ssize_t (*wslay_frame_send_callback)(const uint8_t *data, size_t len,
|
||||||
|
int flags, void *user_data);
|
||||||
|
/*
|
||||||
|
* Callback function used by wslay_frame_recv() function when it needs
|
||||||
|
* more data. The implementation of this function must fill at most
|
||||||
|
* len bytes of data into buf. The memory area of buf is allocated by
|
||||||
|
* library and not be freed by the application code. flags is always 0
|
||||||
|
* in this version. user_data is one given in
|
||||||
|
* wslay_frame_context_init() function. The implementation of this
|
||||||
|
* function must return the number of bytes filled. If there is an
|
||||||
|
* error, return -1. The return value 0 is also treated an error by
|
||||||
|
* the library.
|
||||||
|
*/
|
||||||
|
typedef ssize_t (*wslay_frame_recv_callback)(uint8_t *buf, size_t len,
|
||||||
|
int flags, void *user_data);
|
||||||
|
/*
|
||||||
|
* Callback function used by wslay_frame_send() function when it needs
|
||||||
|
* new mask key. The implementation of this function must write
|
||||||
|
* exactly len bytes of mask key to buf. user_data is one given in
|
||||||
|
* wslay_frame_context_init() function. The implementation of this
|
||||||
|
* function return 0 on success. If there is an error, return -1.
|
||||||
|
*/
|
||||||
|
typedef int (*wslay_frame_genmask_callback)(uint8_t *buf, size_t len,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
struct wslay_frame_callbacks {
|
||||||
|
wslay_frame_send_callback send_callback;
|
||||||
|
wslay_frame_recv_callback recv_callback;
|
||||||
|
wslay_frame_genmask_callback genmask_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The opcode defined in RFC6455.
|
||||||
|
*/
|
||||||
|
enum wslay_opcode {
|
||||||
|
WSLAY_CONTINUATION_FRAME = 0x0u,
|
||||||
|
WSLAY_TEXT_FRAME = 0x1u,
|
||||||
|
WSLAY_BINARY_FRAME = 0x2u,
|
||||||
|
WSLAY_CONNECTION_CLOSE = 0x8u,
|
||||||
|
WSLAY_PING = 0x9u,
|
||||||
|
WSLAY_PONG = 0xau
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Macro that returns 1 if opcode is control frame opcode, otherwise
|
||||||
|
* returns 0.
|
||||||
|
*/
|
||||||
|
#define wslay_is_ctrl_frame(opcode) ((opcode >> 3) & 1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Macros that represent and return reserved bits: RSV1, RSV2, RSV3.
|
||||||
|
* These macros assume that rsv is constructed by ((RSV1 << 2) |
|
||||||
|
* (RSV2 << 1) | RSV3)
|
||||||
|
*/
|
||||||
|
#define WSLAY_RSV_NONE ((uint8_t) 0)
|
||||||
|
#define WSLAY_RSV1_BIT (((uint8_t) 1) << 2)
|
||||||
|
#define WSLAY_RSV2_BIT (((uint8_t) 1) << 1)
|
||||||
|
#define WSLAY_RSV3_BIT (((uint8_t) 1) << 0)
|
||||||
|
|
||||||
|
#define wslay_get_rsv1(rsv) ((rsv >> 2) & 1)
|
||||||
|
#define wslay_get_rsv2(rsv) ((rsv >> 1) & 1)
|
||||||
|
#define wslay_get_rsv3(rsv) (rsv & 1)
|
||||||
|
|
||||||
|
struct wslay_frame_iocb {
|
||||||
|
/* 1 for fragmented final frame, 0 for otherwise */
|
||||||
|
uint8_t fin;
|
||||||
|
/*
|
||||||
|
* reserved 3 bits. rsv = ((RSV1 << 2) | (RSV << 1) | RSV3).
|
||||||
|
* RFC6455 requires 0 unless extensions are negotiated.
|
||||||
|
*/
|
||||||
|
uint8_t rsv;
|
||||||
|
/* 4 bit opcode */
|
||||||
|
uint8_t opcode;
|
||||||
|
/* payload length [0, 2**63-1] */
|
||||||
|
uint64_t payload_length;
|
||||||
|
/* 1 for masked frame, 0 for unmasked */
|
||||||
|
uint8_t mask;
|
||||||
|
/* part of payload data */
|
||||||
|
const uint8_t *data;
|
||||||
|
/* bytes of data defined above */
|
||||||
|
size_t data_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_frame_context;
|
||||||
|
typedef struct wslay_frame_context *wslay_frame_context_ptr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes ctx using given callbacks and user_data. This function
|
||||||
|
* allocates memory for struct wslay_frame_context and stores the
|
||||||
|
* result to *ctx. The callback functions specified in callbacks are
|
||||||
|
* copied to ctx. user_data is stored in ctx and it will be passed to
|
||||||
|
* callback functions. When the user code finished using ctx, it must
|
||||||
|
* call wslay_frame_context_free to deallocate memory.
|
||||||
|
*/
|
||||||
|
int wslay_frame_context_init(wslay_frame_context_ptr *ctx,
|
||||||
|
const struct wslay_frame_callbacks *callbacks,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Deallocates memory pointed by ctx.
|
||||||
|
*/
|
||||||
|
void wslay_frame_context_free(wslay_frame_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send WebSocket frame specified in iocb. ctx must be initialized
|
||||||
|
* using wslay_frame_context_init() function. iocb->fin must be 1 if
|
||||||
|
* this is a fin frame, otherwise 0. iocb->rsv is reserved bits.
|
||||||
|
* iocb->opcode must be the opcode of this frame. iocb->mask must be
|
||||||
|
* 1 if this is masked frame, otherwise 0. iocb->payload_length is
|
||||||
|
* the payload_length of this frame. iocb->data must point to the
|
||||||
|
* payload data to be sent. iocb->data_length must be the length of
|
||||||
|
* the data. This function calls send_callback function if it needs
|
||||||
|
* to send bytes. This function calls gen_mask_callback function if
|
||||||
|
* it needs new mask key. This function returns the number of payload
|
||||||
|
* bytes sent. Please note that it does not include any number of
|
||||||
|
* header bytes. If it cannot send any single bytes of payload, it
|
||||||
|
* returns WSLAY_ERR_WANT_WRITE. If the library detects error in iocb,
|
||||||
|
* this function returns WSLAY_ERR_INVALID_ARGUMENT. If callback
|
||||||
|
* functions report a failure, this function returns
|
||||||
|
* WSLAY_ERR_INVALID_CALLBACK. This function does not always send all
|
||||||
|
* given data in iocb. If there are remaining data to be sent, adjust
|
||||||
|
* data and data_length in iocb accordingly and call this function
|
||||||
|
* again.
|
||||||
|
*/
|
||||||
|
ssize_t wslay_frame_send(wslay_frame_context_ptr ctx,
|
||||||
|
struct wslay_frame_iocb *iocb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Receives WebSocket frame and stores it in iocb. This function
|
||||||
|
* returns the number of payload bytes received. This does not
|
||||||
|
* include header bytes. In this case, iocb will be populated as
|
||||||
|
* follows: iocb->fin is 1 if received frame is fin frame, otherwise
|
||||||
|
* 0. iocb->rsv is reserved bits of received frame. iocb->opcode is
|
||||||
|
* opcode of received frame. iocb->mask is 1 if received frame is
|
||||||
|
* masked, otherwise 0. iocb->payload_length is the payload length of
|
||||||
|
* received frame. iocb->data is pointed to the buffer containing
|
||||||
|
* received payload data. This buffer is allocated by the library and
|
||||||
|
* must be read-only. iocb->data_length is the number of payload
|
||||||
|
* bytes recieved. This function calls recv_callback if it needs to
|
||||||
|
* receive additional bytes. If it cannot receive any single bytes of
|
||||||
|
* payload, it returns WSLAY_ERR_WANT_READ. If the library detects
|
||||||
|
* protocol violation in a received frame, this function returns
|
||||||
|
* WSLAY_ERR_PROTO. If callback functions report a failure, this
|
||||||
|
* function returns WSLAY_ERR_INVALID_CALLBACK. This function does
|
||||||
|
* not always receive whole frame in a single call. If there are
|
||||||
|
* remaining data to be received, call this function again. This
|
||||||
|
* function ensures frame alignment.
|
||||||
|
*/
|
||||||
|
ssize_t wslay_frame_recv(wslay_frame_context_ptr ctx,
|
||||||
|
struct wslay_frame_iocb *iocb);
|
||||||
|
|
||||||
|
struct wslay_event_context;
|
||||||
|
/* Pointer to the event-based API context */
|
||||||
|
typedef struct wslay_event_context *wslay_event_context_ptr;
|
||||||
|
|
||||||
|
struct wslay_event_on_msg_recv_arg {
|
||||||
|
/* reserved bits: rsv = (RSV1 << 2) | (RSV2 << 1) | RSV3 */
|
||||||
|
uint8_t rsv;
|
||||||
|
/* opcode */
|
||||||
|
uint8_t opcode;
|
||||||
|
/* received message */
|
||||||
|
const uint8_t *msg;
|
||||||
|
/* message length */
|
||||||
|
size_t msg_length;
|
||||||
|
/*
|
||||||
|
* Status code iff opcode == WSLAY_CONNECTION_CLOSE. If no status
|
||||||
|
* code is included in the close control frame, it is set to 0.
|
||||||
|
*/
|
||||||
|
uint16_t status_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_recv() when a message is
|
||||||
|
* completely received.
|
||||||
|
*/
|
||||||
|
typedef void (*wslay_event_on_msg_recv_callback)
|
||||||
|
(wslay_event_context_ptr ctx,
|
||||||
|
const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
|
||||||
|
|
||||||
|
struct wslay_event_on_frame_recv_start_arg {
|
||||||
|
/* fin bit; 1 for final frame, or 0. */
|
||||||
|
uint8_t fin;
|
||||||
|
/* reserved bits: rsv = (RSV1 << 2) | (RSV2 << 1) | RSV3 */
|
||||||
|
uint8_t rsv;
|
||||||
|
/* opcode of the frame */
|
||||||
|
uint8_t opcode;
|
||||||
|
/* payload length of ths frame */
|
||||||
|
uint64_t payload_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_recv() when a new frame
|
||||||
|
* starts to be received. This callback function is only invoked once
|
||||||
|
* for each frame.
|
||||||
|
*/
|
||||||
|
typedef void (*wslay_event_on_frame_recv_start_callback)
|
||||||
|
(wslay_event_context_ptr ctx,
|
||||||
|
const struct wslay_event_on_frame_recv_start_arg *arg, void *user_data);
|
||||||
|
|
||||||
|
struct wslay_event_on_frame_recv_chunk_arg {
|
||||||
|
/* chunk of payload data */
|
||||||
|
const uint8_t *data;
|
||||||
|
/* length of data */
|
||||||
|
size_t data_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_recv() when a chunk of
|
||||||
|
* frame payload is received.
|
||||||
|
*/
|
||||||
|
typedef void (*wslay_event_on_frame_recv_chunk_callback)
|
||||||
|
(wslay_event_context_ptr ctx,
|
||||||
|
const struct wslay_event_on_frame_recv_chunk_arg *arg, void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_recv() when a frame is
|
||||||
|
* completely received.
|
||||||
|
*/
|
||||||
|
typedef void (*wslay_event_on_frame_recv_end_callback)
|
||||||
|
(wslay_event_context_ptr ctx, void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_recv() when it wants to
|
||||||
|
* receive more data from peer. The implementation of this callback
|
||||||
|
* function must read data at most len bytes from peer and store them
|
||||||
|
* in buf and return the number of bytes read. flags is always 0 in
|
||||||
|
* this version.
|
||||||
|
*
|
||||||
|
* If there is an error, return -1 and set error code
|
||||||
|
* WSLAY_ERR_CALLBACK_FAILURE using wslay_event_set_error(). Wslay
|
||||||
|
* event-based API on the whole assumes non-blocking I/O. If the cause
|
||||||
|
* of error is EAGAIN or EWOULDBLOCK, set WSLAY_ERR_WOULDBLOCK
|
||||||
|
* instead. This is important because it tells wslay_event_recv() to
|
||||||
|
* stop receiving further data and return.
|
||||||
|
*/
|
||||||
|
typedef ssize_t (*wslay_event_recv_callback)(wslay_event_context_ptr ctx,
|
||||||
|
uint8_t *buf, size_t len,
|
||||||
|
int flags, void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_send() when it wants to
|
||||||
|
* send more data to peer. The implementation of this callback
|
||||||
|
* function must send data at most len bytes to peer and return the
|
||||||
|
* number of bytes sent. flags is the bitwise OR of zero or more of
|
||||||
|
* the following flag:
|
||||||
|
*
|
||||||
|
* WSLAY_MSG_MORE
|
||||||
|
* There is more data to send
|
||||||
|
*
|
||||||
|
* It provides some hints to tune performance and behaviour.
|
||||||
|
*
|
||||||
|
* If there is an error, return -1 and set error code
|
||||||
|
* WSLAY_ERR_CALLBACK_FAILURE using wslay_event_set_error(). Wslay
|
||||||
|
* event-based API on the whole assumes non-blocking I/O. If the cause
|
||||||
|
* of error is EAGAIN or EWOULDBLOCK, set WSLAY_ERR_WOULDBLOCK
|
||||||
|
* instead. This is important because it tells wslay_event_send() to
|
||||||
|
* stop sending data and return.
|
||||||
|
*/
|
||||||
|
typedef ssize_t (*wslay_event_send_callback)(wslay_event_context_ptr ctx,
|
||||||
|
const uint8_t *data, size_t len,
|
||||||
|
int flags, void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function invoked by wslay_event_send() when it wants new
|
||||||
|
* mask key. As described in RFC6455, only the traffic from WebSocket
|
||||||
|
* client is masked, so this callback function is only needed if an
|
||||||
|
* event-based API is initialized for WebSocket client use.
|
||||||
|
*/
|
||||||
|
typedef int (*wslay_event_genmask_callback)(wslay_event_context_ptr ctx,
|
||||||
|
uint8_t *buf, size_t len,
|
||||||
|
void *user_data);
|
||||||
|
|
||||||
|
struct wslay_event_callbacks {
|
||||||
|
wslay_event_recv_callback recv_callback;
|
||||||
|
wslay_event_send_callback send_callback;
|
||||||
|
wslay_event_genmask_callback genmask_callback;
|
||||||
|
wslay_event_on_frame_recv_start_callback on_frame_recv_start_callback;
|
||||||
|
wslay_event_on_frame_recv_chunk_callback on_frame_recv_chunk_callback;
|
||||||
|
wslay_event_on_frame_recv_end_callback on_frame_recv_end_callback;
|
||||||
|
wslay_event_on_msg_recv_callback on_msg_recv_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes ctx as WebSocket Server. user_data is an arbitrary
|
||||||
|
* pointer, which is directly passed to each callback functions as
|
||||||
|
* user_data argument.
|
||||||
|
*
|
||||||
|
* On success, returns 0. On error, returns one of following negative
|
||||||
|
* values:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*/
|
||||||
|
int wslay_event_context_server_init
|
||||||
|
(wslay_event_context_ptr *ctx,
|
||||||
|
const struct wslay_event_callbacks *callbacks, void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes ctx as WebSocket client. user_data is an arbitrary
|
||||||
|
* pointer, which is directly passed to each callback functions as
|
||||||
|
* user_data argument.
|
||||||
|
*
|
||||||
|
* On success, returns 0. On error, returns one of following negative
|
||||||
|
* values:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*/
|
||||||
|
int wslay_event_context_client_init
|
||||||
|
(wslay_event_context_ptr *ctx,
|
||||||
|
const struct wslay_event_callbacks *callbacks, void *user_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Releases allocated resources for ctx.
|
||||||
|
*/
|
||||||
|
void wslay_event_context_free(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets a bit mask of allowed reserved bits.
|
||||||
|
* Currently only permitted values are WSLAY_RSV1_BIT to allow PMCE
|
||||||
|
* extension (see RFC-7692) or WSLAY_RSV_NONE to disable.
|
||||||
|
*
|
||||||
|
* Default: WSLAY_RSV_NONE
|
||||||
|
*/
|
||||||
|
void wslay_event_config_set_allowed_rsv_bits(wslay_event_context_ptr ctx,
|
||||||
|
uint8_t rsv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enables or disables buffering of an entire message for non-control
|
||||||
|
* frames. If val is 0, buffering is enabled. Otherwise, buffering is
|
||||||
|
* disabled. If wslay_event_on_msg_recv_callback is invoked when
|
||||||
|
* buffering is disabled, the msg_length member of struct
|
||||||
|
* wslay_event_on_msg_recv_arg is set to 0.
|
||||||
|
*
|
||||||
|
* The control frames are always buffered regardless of this function call.
|
||||||
|
*
|
||||||
|
* This function must not be used after the first invocation of
|
||||||
|
* wslay_event_recv() function.
|
||||||
|
*/
|
||||||
|
void wslay_event_config_set_no_buffering(wslay_event_context_ptr ctx, int val);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets maximum length of a message that can be received. The length
|
||||||
|
* of message is checked by wslay_event_recv() function. If the length
|
||||||
|
* of a message is larger than this value, reading operation is
|
||||||
|
* disabled (same effect with wslay_event_shutdown_read() call) and
|
||||||
|
* close control frame with WSLAY_CODE_MESSAGE_TOO_BIG is queued. If
|
||||||
|
* buffering for non-control frames is disabled, the library checks
|
||||||
|
* each frame payload length and does not check length of entire
|
||||||
|
* message.
|
||||||
|
*
|
||||||
|
* The default value is (1u << 31)-1.
|
||||||
|
*/
|
||||||
|
void wslay_event_config_set_max_recv_msg_length(wslay_event_context_ptr ctx,
|
||||||
|
uint64_t val);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets callbacks to ctx. The callbacks previouly set by this function
|
||||||
|
* or wslay_event_context_server_init() or
|
||||||
|
* wslay_event_context_client_init() are replaced with callbacks.
|
||||||
|
*/
|
||||||
|
void wslay_event_config_set_callbacks
|
||||||
|
(wslay_event_context_ptr ctx, const struct wslay_event_callbacks *callbacks);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Receives messages from peer. When receiving
|
||||||
|
* messages, it uses wslay_event_recv_callback function. Single call
|
||||||
|
* of this function receives multiple messages until
|
||||||
|
* wslay_event_recv_callback function sets error code
|
||||||
|
* WSLAY_ERR_WOULDBLOCK.
|
||||||
|
*
|
||||||
|
* When close control frame is received, this function automatically
|
||||||
|
* queues close control frame. Also this function calls
|
||||||
|
* wslay_event_set_read_enabled() with second argument 0 to disable
|
||||||
|
* further read from peer.
|
||||||
|
*
|
||||||
|
* When ping control frame is received, this function automatically
|
||||||
|
* queues pong control frame.
|
||||||
|
*
|
||||||
|
* In case of a fatal errror which leads to negative return code, this
|
||||||
|
* function calls wslay_event_set_read_enabled() with second argument
|
||||||
|
* 0 to disable further read from peer.
|
||||||
|
*
|
||||||
|
* wslay_event_recv() returns 0 if it succeeds, or one of the
|
||||||
|
* following negative error codes:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_CALLBACK_FAILURE
|
||||||
|
* User defined callback function is failed.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*
|
||||||
|
* When negative error code is returned, application must not make any
|
||||||
|
* further call of wslay_event_recv() and must close WebSocket
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
int wslay_event_recv(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sends queued messages to peer. When sending a
|
||||||
|
* message, it uses wslay_event_send_callback function. Single call of
|
||||||
|
* wslay_event_send() sends multiple messages until
|
||||||
|
* wslay_event_send_callback sets error code WSLAY_ERR_WOULDBLOCK.
|
||||||
|
*
|
||||||
|
* If ctx is initialized for WebSocket client use, wslay_event_send()
|
||||||
|
* uses wslay_event_genmask_callback to get new mask key.
|
||||||
|
*
|
||||||
|
* When a message queued using wslay_event_queue_fragmented_msg() is
|
||||||
|
* sent, wslay_event_send() invokes
|
||||||
|
* wslay_event_fragmented_msg_callback for that message.
|
||||||
|
*
|
||||||
|
* After close control frame is sent, this function calls
|
||||||
|
* wslay_event_set_write_enabled() with second argument 0 to disable
|
||||||
|
* further transmission to peer.
|
||||||
|
*
|
||||||
|
* If there are any pending messages, wslay_event_want_write() returns
|
||||||
|
* 1, otherwise returns 0.
|
||||||
|
*
|
||||||
|
* In case of a fatal errror which leads to negative return code, this
|
||||||
|
* function calls wslay_event_set_write_enabled() with second argument
|
||||||
|
* 0 to disable further transmission to peer.
|
||||||
|
*
|
||||||
|
* wslay_event_send() returns 0 if it succeeds, or one of the
|
||||||
|
* following negative error codes:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_CALLBACK_FAILURE
|
||||||
|
* User defined callback function is failed.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*
|
||||||
|
* When negative error code is returned, application must not make any
|
||||||
|
* further call of wslay_event_send() and must close WebSocket
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
int wslay_event_send(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
struct wslay_event_msg {
|
||||||
|
uint8_t opcode;
|
||||||
|
const uint8_t *msg;
|
||||||
|
size_t msg_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queues message specified in arg.
|
||||||
|
*
|
||||||
|
* This function supports both control and non-control messages and
|
||||||
|
* the given message is sent without fragmentation. If fragmentation
|
||||||
|
* is needed, use wslay_event_queue_fragmented_msg() function instead.
|
||||||
|
*
|
||||||
|
* This function just queues a message and does not send
|
||||||
|
* it. wslay_event_send() function call sends these queued messages.
|
||||||
|
*
|
||||||
|
* wslay_event_queue_msg() returns 0 if it succeeds, or returns the
|
||||||
|
* following negative error codes:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NO_MORE_MSG
|
||||||
|
* Could not queue given message. The one of possible reason is that
|
||||||
|
* close control frame has been queued/sent and no further queueing
|
||||||
|
* message is not allowed.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_INVALID_ARGUMENT
|
||||||
|
* The given message is invalid.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*/
|
||||||
|
int wslay_event_queue_msg(wslay_event_context_ptr ctx,
|
||||||
|
const struct wslay_event_msg *arg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extended version of wslay_event_queue_msg which allows to set reserved bits.
|
||||||
|
*/
|
||||||
|
int wslay_event_queue_msg_ex(wslay_event_context_ptr ctx,
|
||||||
|
const struct wslay_event_msg *arg, uint8_t rsv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Specify "source" to generate message.
|
||||||
|
*/
|
||||||
|
union wslay_event_msg_source {
|
||||||
|
int fd;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback function called by wslay_event_send() to read message data
|
||||||
|
* from source. The implementation of
|
||||||
|
* wslay_event_fragmented_msg_callback must store at most len bytes of
|
||||||
|
* data to buf and return the number of stored bytes. If all data is
|
||||||
|
* read (i.e., EOF), set *eof to 1. If no data can be generated at the
|
||||||
|
* moment, return 0. If there is an error, return -1 and set error
|
||||||
|
* code WSLAY_ERR_CALLBACK_FAILURE using wslay_event_set_error().
|
||||||
|
*/
|
||||||
|
typedef ssize_t (*wslay_event_fragmented_msg_callback)
|
||||||
|
(wslay_event_context_ptr ctx,
|
||||||
|
uint8_t *buf, size_t len, const union wslay_event_msg_source *source,
|
||||||
|
int *eof, void *user_data);
|
||||||
|
|
||||||
|
struct wslay_event_fragmented_msg {
|
||||||
|
/* opcode */
|
||||||
|
uint8_t opcode;
|
||||||
|
/* "source" to generate message data */
|
||||||
|
union wslay_event_msg_source source;
|
||||||
|
/* Callback function to read message data from source. */
|
||||||
|
wslay_event_fragmented_msg_callback read_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queues a fragmented message specified in arg.
|
||||||
|
*
|
||||||
|
* This function supports non-control messages only. For control frames,
|
||||||
|
* use wslay_event_queue_msg() or wslay_event_queue_close().
|
||||||
|
*
|
||||||
|
* This function just queues a message and does not send
|
||||||
|
* it. wslay_event_send() function call sends these queued messages.
|
||||||
|
*
|
||||||
|
* wslay_event_queue_fragmented_msg() returns 0 if it succeeds, or
|
||||||
|
* returns the following negative error codes:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NO_MORE_MSG
|
||||||
|
* Could not queue given message. The one of possible reason is that
|
||||||
|
* close control frame has been queued/sent and no further queueing
|
||||||
|
* message is not allowed.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_INVALID_ARGUMENT
|
||||||
|
* The given message is invalid.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*/
|
||||||
|
int wslay_event_queue_fragmented_msg
|
||||||
|
(wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extended version of wslay_event_queue_fragmented_msg which allows to set
|
||||||
|
* reserved bits.
|
||||||
|
*/
|
||||||
|
int wslay_event_queue_fragmented_msg_ex(wslay_event_context_ptr ctx,
|
||||||
|
const struct wslay_event_fragmented_msg *arg, uint8_t rsv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Queues close control frame. This function is provided just for
|
||||||
|
* convenience. wslay_event_queue_msg() can queue a close control
|
||||||
|
* frame as well. status_code is the status code of close control
|
||||||
|
* frame. reason is the close reason encoded in UTF-8. reason_length
|
||||||
|
* is the length of reason in bytes. reason_length must be less than
|
||||||
|
* 123 bytes.
|
||||||
|
*
|
||||||
|
* If status_code is 0, reason and reason_length is not used and close
|
||||||
|
* control frame with zero-length payload will be queued.
|
||||||
|
*
|
||||||
|
* This function just queues a message and does not send
|
||||||
|
* it. wslay_event_send() function call sends these queued messages.
|
||||||
|
*
|
||||||
|
* wslay_event_queue_close() returns 0 if it succeeds, or returns the
|
||||||
|
* following negative error codes:
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NO_MORE_MSG
|
||||||
|
* Could not queue given message. The one of possible reason is that
|
||||||
|
* close control frame has been queued/sent and no further queueing
|
||||||
|
* message is not allowed.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_INVALID_ARGUMENT
|
||||||
|
* The given message is invalid.
|
||||||
|
*
|
||||||
|
* WSLAY_ERR_NOMEM
|
||||||
|
* Out of memory.
|
||||||
|
*/
|
||||||
|
int wslay_event_queue_close(wslay_event_context_ptr ctx,
|
||||||
|
uint16_t status_code,
|
||||||
|
const uint8_t *reason, size_t reason_length);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets error code to tell the library there is an error. This
|
||||||
|
* function is typically used in user defined callback functions. See
|
||||||
|
* the description of callback function to know which error code
|
||||||
|
* should be used.
|
||||||
|
*/
|
||||||
|
void wslay_event_set_error(wslay_event_context_ptr ctx, int val);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query whehter the library want to read more data from peer.
|
||||||
|
*
|
||||||
|
* wslay_event_want_read() returns 1 if the library want to read more
|
||||||
|
* data from peer, or returns 0.
|
||||||
|
*/
|
||||||
|
int wslay_event_want_read(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Query whehter the library want to send more data to peer.
|
||||||
|
*
|
||||||
|
* wslay_event_want_write() returns 1 if the library want to send more
|
||||||
|
* data to peer, or returns 0.
|
||||||
|
*/
|
||||||
|
int wslay_event_want_write(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevents the event-based API context from reading any further data
|
||||||
|
* from peer.
|
||||||
|
*
|
||||||
|
* This function may be used with wslay_event_queue_close() if the
|
||||||
|
* application detects error in the data received and wants to fail
|
||||||
|
* WebSocket connection.
|
||||||
|
*/
|
||||||
|
void wslay_event_shutdown_read(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prevents the event-based API context from sending any further data
|
||||||
|
* to peer.
|
||||||
|
*/
|
||||||
|
void wslay_event_shutdown_write(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns 1 if the event-based API context allows read operation, or
|
||||||
|
* return 0.
|
||||||
|
*
|
||||||
|
* After wslay_event_shutdown_read() is called,
|
||||||
|
* wslay_event_get_read_enabled() returns 0.
|
||||||
|
*/
|
||||||
|
int wslay_event_get_read_enabled(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns 1 if the event-based API context allows write operation, or
|
||||||
|
* return 0.
|
||||||
|
*
|
||||||
|
* After wslay_event_shutdown_write() is called,
|
||||||
|
* wslay_event_get_write_enabled() returns 0.
|
||||||
|
*/
|
||||||
|
int wslay_event_get_write_enabled(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns 1 if a close control frame has been received from peer, or
|
||||||
|
* returns 0.
|
||||||
|
*/
|
||||||
|
int wslay_event_get_close_received(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns 1 if a close control frame has been sent to peer, or
|
||||||
|
* returns 0.
|
||||||
|
*/
|
||||||
|
int wslay_event_get_close_sent(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns status code received in close control frame. If no close
|
||||||
|
* control frame has not been received, returns
|
||||||
|
* WSLAY_CODE_ABNORMAL_CLOSURE. If received close control frame has no
|
||||||
|
* status code, returns WSLAY_CODE_NO_STATUS_RCVD.
|
||||||
|
*/
|
||||||
|
uint16_t wslay_event_get_status_code_received(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns status code sent in close control frame. If no close
|
||||||
|
* control frame has not been sent, returns
|
||||||
|
* WSLAY_CODE_ABNORMAL_CLOSURE. If sent close control frame has no
|
||||||
|
* status code, returns WSLAY_CODE_NO_STATUS_RCVD.
|
||||||
|
*/
|
||||||
|
uint16_t wslay_event_get_status_code_sent(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the number of queued messages.
|
||||||
|
*/
|
||||||
|
size_t wslay_event_get_queued_msg_count(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the sum of queued message length. It only counts the
|
||||||
|
* message length queued using wslay_event_queue_msg() or
|
||||||
|
* wslay_event_queue_close().
|
||||||
|
*/
|
||||||
|
size_t wslay_event_get_queued_msg_length(wslay_event_context_ptr ctx);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* WSLAY_H */
|
142
websocket/include/wslay/wslay_event.h
Normal file
142
websocket/include/wslay/wslay_event.h
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_EVENT_H
|
||||||
|
#define WSLAY_EVENT_H
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <wslay/wslay.h>
|
||||||
|
|
||||||
|
struct wslay_stack;
|
||||||
|
struct wslay_queue;
|
||||||
|
|
||||||
|
struct wslay_event_byte_chunk {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t data_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_event_imsg {
|
||||||
|
uint8_t fin;
|
||||||
|
uint8_t rsv;
|
||||||
|
uint8_t opcode;
|
||||||
|
uint32_t utf8state;
|
||||||
|
struct wslay_queue *chunks;
|
||||||
|
size_t msg_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wslay_event_msg_type {
|
||||||
|
WSLAY_NON_FRAGMENTED,
|
||||||
|
WSLAY_FRAGMENTED
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_event_omsg {
|
||||||
|
uint8_t fin;
|
||||||
|
uint8_t opcode;
|
||||||
|
uint8_t rsv;
|
||||||
|
enum wslay_event_msg_type type;
|
||||||
|
|
||||||
|
uint8_t *data;
|
||||||
|
size_t data_length;
|
||||||
|
|
||||||
|
union wslay_event_msg_source source;
|
||||||
|
wslay_event_fragmented_msg_callback read_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_event_frame_user_data {
|
||||||
|
wslay_event_context_ptr ctx;
|
||||||
|
void *user_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wslay_event_close_status {
|
||||||
|
WSLAY_CLOSE_RECEIVED = 1 << 0,
|
||||||
|
WSLAY_CLOSE_QUEUED = 1 << 1,
|
||||||
|
WSLAY_CLOSE_SENT = 1 << 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum wslay_event_config {
|
||||||
|
WSLAY_CONFIG_NO_BUFFERING = 1 << 0
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_event_context {
|
||||||
|
/* config status, bitwise OR of enum wslay_event_config values*/
|
||||||
|
uint32_t config;
|
||||||
|
/* maximum message length that can be received */
|
||||||
|
uint64_t max_recv_msg_length;
|
||||||
|
/* 1 if initialized for server, otherwise 0 */
|
||||||
|
uint8_t server;
|
||||||
|
/* bitwise OR of enum wslay_event_close_status values */
|
||||||
|
uint8_t close_status;
|
||||||
|
/* status code in received close control frame */
|
||||||
|
uint16_t status_code_recv;
|
||||||
|
/* status code in sent close control frame */
|
||||||
|
uint16_t status_code_sent;
|
||||||
|
wslay_frame_context_ptr frame_ctx;
|
||||||
|
/* 1 if reading is enabled, otherwise 0. Upon receiving close
|
||||||
|
control frame this value set to 0. If any errors in read
|
||||||
|
operation will also set this value to 0. */
|
||||||
|
uint8_t read_enabled;
|
||||||
|
/* 1 if writing is enabled, otherwise 0 Upon completing sending
|
||||||
|
close control frame, this value set to 0. If any errors in write
|
||||||
|
opration will also set this value to 0. */
|
||||||
|
uint8_t write_enabled;
|
||||||
|
/* imsg buffer to allow interleaved control frame between
|
||||||
|
non-control frames. */
|
||||||
|
struct wslay_event_imsg imsgs[2];
|
||||||
|
/* Pointer to imsgs to indicate current used buffer. */
|
||||||
|
struct wslay_event_imsg *imsg;
|
||||||
|
/* payload length of frame currently being received. */
|
||||||
|
uint64_t ipayloadlen;
|
||||||
|
/* next byte offset of payload currently being received. */
|
||||||
|
uint64_t ipayloadoff;
|
||||||
|
/* error value set by user callback */
|
||||||
|
int error;
|
||||||
|
/* Pointer to the message currently being sent. NULL if no message
|
||||||
|
is currently sent. */
|
||||||
|
struct wslay_event_omsg *omsg;
|
||||||
|
/* Queue for non-control frames */
|
||||||
|
struct wslay_queue/*<wslay_omsg*>*/ *send_queue;
|
||||||
|
/* Queue for control frames */
|
||||||
|
struct wslay_queue/*<wslay_omsg*>*/ *send_ctrl_queue;
|
||||||
|
/* Size of send_queue + size of send_ctrl_queue */
|
||||||
|
size_t queued_msg_count;
|
||||||
|
/* The sum of message length in send_queue */
|
||||||
|
size_t queued_msg_length;
|
||||||
|
/* Buffer used for fragmented messages */
|
||||||
|
uint8_t obuf[4096];
|
||||||
|
uint8_t *obuflimit;
|
||||||
|
uint8_t *obufmark;
|
||||||
|
/* payload length of frame currently being sent. */
|
||||||
|
uint64_t opayloadlen;
|
||||||
|
/* next byte offset of payload currently being sent. */
|
||||||
|
uint64_t opayloadoff;
|
||||||
|
struct wslay_event_callbacks callbacks;
|
||||||
|
struct wslay_event_frame_user_data frame_user_data;
|
||||||
|
void *user_data;
|
||||||
|
uint8_t allowed_rsv_bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* WSLAY_EVENT_H */
|
76
websocket/include/wslay/wslay_frame.h
Normal file
76
websocket/include/wslay/wslay_frame.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_FRAME_H
|
||||||
|
#define WSLAY_FRAME_H
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <wslay/wslay.h>
|
||||||
|
|
||||||
|
enum wslay_frame_state {
|
||||||
|
PREP_HEADER,
|
||||||
|
SEND_HEADER,
|
||||||
|
SEND_PAYLOAD,
|
||||||
|
RECV_HEADER1,
|
||||||
|
RECV_PAYLOADLEN,
|
||||||
|
RECV_EXT_PAYLOADLEN,
|
||||||
|
RECV_MASKKEY,
|
||||||
|
RECV_PAYLOAD
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_frame_opcode_memo {
|
||||||
|
uint8_t fin;
|
||||||
|
uint8_t opcode;
|
||||||
|
uint8_t rsv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_frame_context {
|
||||||
|
uint8_t ibuf[4096];
|
||||||
|
uint8_t *ibufmark;
|
||||||
|
uint8_t *ibuflimit;
|
||||||
|
struct wslay_frame_opcode_memo iom;
|
||||||
|
uint64_t ipayloadlen;
|
||||||
|
uint64_t ipayloadoff;
|
||||||
|
uint8_t imask;
|
||||||
|
uint8_t imaskkey[4];
|
||||||
|
enum wslay_frame_state istate;
|
||||||
|
size_t ireqread;
|
||||||
|
|
||||||
|
uint8_t oheader[14];
|
||||||
|
uint8_t *oheadermark;
|
||||||
|
uint8_t *oheaderlimit;
|
||||||
|
uint64_t opayloadlen;
|
||||||
|
uint64_t opayloadoff;
|
||||||
|
uint8_t omask;
|
||||||
|
uint8_t omaskkey[4];
|
||||||
|
enum wslay_frame_state ostate;
|
||||||
|
|
||||||
|
struct wslay_frame_callbacks callbacks;
|
||||||
|
void *user_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* WSLAY_FRAME_H */
|
54
websocket/include/wslay/wslay_net.h
Normal file
54
websocket/include/wslay/wslay_net.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_NET_H
|
||||||
|
#define WSLAY_NET_H
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <wslay/wslay.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_ARPA_INET_H
|
||||||
|
# include <arpa/inet.h>
|
||||||
|
#endif /* HAVE_ARPA_INET_H */
|
||||||
|
#ifdef HAVE_NETINET_IN_H
|
||||||
|
# include <netinet/in.h>
|
||||||
|
#endif /* HAVE_NETINET_IN_H */
|
||||||
|
/* For Mingw build */
|
||||||
|
#ifdef HAVE_WINSOCK2_H
|
||||||
|
# include <winsock2.h>
|
||||||
|
#endif /* HAVE_WINSOCK2_H */
|
||||||
|
|
||||||
|
#ifdef WORDS_BIGENDIAN
|
||||||
|
# define ntoh64(x) (x)
|
||||||
|
# define hton64(x) (x)
|
||||||
|
#else /* !WORDS_BIGENDIAN */
|
||||||
|
uint64_t wslay_byteswap64(uint64_t x);
|
||||||
|
# define ntoh64(x) wslay_byteswap64(x)
|
||||||
|
# define hton64(x) wslay_byteswap64(x)
|
||||||
|
#endif /* !WORDS_BIGENDIAN */
|
||||||
|
|
||||||
|
#endif /* WSLAY_NET_H */
|
53
websocket/include/wslay/wslay_queue.h
Normal file
53
websocket/include/wslay/wslay_queue.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_QUEUE_H
|
||||||
|
#define WSLAY_QUEUE_H
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <wslay/wslay.h>
|
||||||
|
|
||||||
|
struct wslay_queue_cell {
|
||||||
|
void *data;
|
||||||
|
struct wslay_queue_cell *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_queue {
|
||||||
|
struct wslay_queue_cell *top;
|
||||||
|
struct wslay_queue_cell *tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_queue* wslay_queue_new(void);
|
||||||
|
void wslay_queue_free(struct wslay_queue *queue);
|
||||||
|
int wslay_queue_push(struct wslay_queue *queue, void *data);
|
||||||
|
int wslay_queue_push_front(struct wslay_queue *queue, void *data);
|
||||||
|
void wslay_queue_pop(struct wslay_queue *queue);
|
||||||
|
void* wslay_queue_top(struct wslay_queue *queue);
|
||||||
|
void* wslay_queue_tail(struct wslay_queue *queue);
|
||||||
|
int wslay_queue_empty(struct wslay_queue *queue);
|
||||||
|
|
||||||
|
#endif /* WSLAY_QUEUE_H */
|
50
websocket/include/wslay/wslay_stack.h
Normal file
50
websocket/include/wslay/wslay_stack.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAY_STACK_H
|
||||||
|
#define WSLAY_STACK_H
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include "config.h"
|
||||||
|
#endif /* HAVE_CONFIG_H */
|
||||||
|
|
||||||
|
#include <wslay/wslay.h>
|
||||||
|
|
||||||
|
struct wslay_stack_cell {
|
||||||
|
void *data;
|
||||||
|
struct wslay_stack_cell *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_stack {
|
||||||
|
struct wslay_stack_cell *top;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wslay_stack* wslay_stack_new();
|
||||||
|
void wslay_stack_free(struct wslay_stack *stack);
|
||||||
|
int wslay_stack_push(struct wslay_stack *stack, void *data);
|
||||||
|
void wslay_stack_pop(struct wslay_stack *stack);
|
||||||
|
void* wslay_stack_top(struct wslay_stack *stack);
|
||||||
|
int wslay_stack_empty(struct wslay_stack *stack);
|
||||||
|
|
||||||
|
#endif /* WSLAY_STACK_H */
|
31
websocket/include/wslay/wslayver.h
Normal file
31
websocket/include/wslay/wslayver.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef WSLAYVER_H
|
||||||
|
#define WSLAYVER_H
|
||||||
|
|
||||||
|
/* Version number of wslay release */
|
||||||
|
#define WSLAY_VERSION "1.0.1-DEV"
|
||||||
|
|
||||||
|
#endif /* WSLAYVER_H */
|
239
websocket/src/handshake.cpp
Normal file
239
websocket/src/handshake.cpp
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#include "websocket.h"
|
||||||
|
#include <dmsdk/dlib/socket.h>
|
||||||
|
|
||||||
|
namespace dmWebsocket
|
||||||
|
{
|
||||||
|
|
||||||
|
const char* RFC_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // as per the rfc document on page 7 (https://tools.ietf.org/html/rfc6455)
|
||||||
|
|
||||||
|
static void CreateKey(uint8_t* key, size_t len)
|
||||||
|
{
|
||||||
|
pcg32_random_t rnd;
|
||||||
|
pcg32_srandom_r(&rnd, dmTime::GetTime(), 31452);
|
||||||
|
for (unsigned int i = 0; i < len; i++) {
|
||||||
|
key[i] = (char)(uint8_t)(pcg32_random_r(&rnd) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WS_SENDALL(s) \
|
||||||
|
sr = Send(conn, s, strlen(s), 0);\
|
||||||
|
if (sr != dmSocket::RESULT_OK)\
|
||||||
|
{\
|
||||||
|
goto bail;\
|
||||||
|
}\
|
||||||
|
|
||||||
|
static Result SendClientHandshakeHeaders(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
CreateKey(conn->m_Key, sizeof(conn->m_Key));
|
||||||
|
|
||||||
|
char encoded_key[64] = {0};
|
||||||
|
uint32_t encoded_key_len = sizeof(encoded_key);
|
||||||
|
|
||||||
|
if (!dmCrypt::Base64Encode((const unsigned char*)conn->m_Key, sizeof(conn->m_Key), (unsigned char*)encoded_key, &encoded_key_len))
|
||||||
|
{
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Failed to base64 encode key");
|
||||||
|
}
|
||||||
|
|
||||||
|
char port[8] = "";
|
||||||
|
if (!(conn->m_Url.m_Port == 80 || conn->m_Url.m_Port == 443))
|
||||||
|
dmSnPrintf(port, sizeof(port), ":%d", conn->m_Url.m_Port);
|
||||||
|
|
||||||
|
dmSocket::Result sr;
|
||||||
|
WS_SENDALL("GET /");
|
||||||
|
WS_SENDALL(conn->m_Url.m_Path);
|
||||||
|
WS_SENDALL(" HTTP/1.1\r\n");
|
||||||
|
WS_SENDALL("Host: ");
|
||||||
|
WS_SENDALL(conn->m_Url.m_Hostname);
|
||||||
|
WS_SENDALL(port);
|
||||||
|
WS_SENDALL("\r\n");
|
||||||
|
WS_SENDALL("Upgrade: websocket\r\n");
|
||||||
|
WS_SENDALL("Connection: Upgrade\r\n");
|
||||||
|
WS_SENDALL("Sec-WebSocket-Key: ");
|
||||||
|
WS_SENDALL(encoded_key);
|
||||||
|
WS_SENDALL("\r\n");
|
||||||
|
WS_SENDALL("Sec-WebSocket-Version: 13\r\n");
|
||||||
|
|
||||||
|
// Add custom protocols
|
||||||
|
|
||||||
|
// Add custom headers
|
||||||
|
|
||||||
|
WS_SENDALL("\r\n");
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (sr != dmSocket::RESULT_OK)
|
||||||
|
{
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "SendClientHandshake failed: %s", dmSocket::ResultToString(sr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef WS_SENDALL
|
||||||
|
|
||||||
|
Result SendClientHandshake(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
dmSocket::Result sr = WaitForSocket(conn, dmSocket::SELECTOR_KIND_WRITE, SOCKET_WAIT_TIMEOUT);
|
||||||
|
if (dmSocket::RESULT_WOULDBLOCK == sr)
|
||||||
|
{
|
||||||
|
return RESULT_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
if (dmSocket::RESULT_OK != sr)
|
||||||
|
{
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Connection not ready for sending data: %s", dmSocket::ResultToString(sr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// In emscripten, the sockets are actually already websockets, so no handshake necessary
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
return RESULT_OK;
|
||||||
|
#else
|
||||||
|
return SendClientHandshakeHeaders(conn);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
Result ReceiveHeaders(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
Result ReceiveHeaders(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
dmSocket::Selector selector;
|
||||||
|
dmSocket::SelectorZero(&selector);
|
||||||
|
dmSocket::SelectorSet(&selector, dmSocket::SELECTOR_KIND_READ, conn->m_Socket);
|
||||||
|
|
||||||
|
dmSocket::Result sr = dmSocket::Select(&selector, 200*1000);
|
||||||
|
|
||||||
|
if (dmSocket::RESULT_OK != sr)
|
||||||
|
{
|
||||||
|
if (dmSocket::RESULT_WOULDBLOCK)
|
||||||
|
{
|
||||||
|
dmLogWarning("Waiting for socket to be available for reading");
|
||||||
|
return RESULT_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Failed waiting for more handshake headers: %s", dmSocket::ResultToString(sr));
|
||||||
|
}
|
||||||
|
|
||||||
|
int max_to_recv = (int)(conn->m_BufferCapacity - 1) - conn->m_BufferSize; // allow for a terminating null character
|
||||||
|
|
||||||
|
if (max_to_recv <= 0)
|
||||||
|
{
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Receive buffer full: %u bytes", conn->m_BufferCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
int recv_bytes = 0;
|
||||||
|
sr = Receive(conn, conn->m_Buffer + conn->m_BufferSize, max_to_recv, &recv_bytes);
|
||||||
|
|
||||||
|
if( sr == dmSocket::RESULT_WOULDBLOCK )
|
||||||
|
{
|
||||||
|
sr = dmSocket::RESULT_TRY_AGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sr == dmSocket::RESULT_TRY_AGAIN)
|
||||||
|
return RESULT_WOULDBLOCK;
|
||||||
|
|
||||||
|
if (sr != dmSocket::RESULT_OK)
|
||||||
|
{
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Receive error: %s", dmSocket::ResultToString(sr));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->m_BufferSize += recv_bytes;
|
||||||
|
|
||||||
|
// NOTE: We have an extra byte for null-termination so no buffer overrun here.
|
||||||
|
conn->m_Buffer[conn->m_BufferSize] = '\0';
|
||||||
|
|
||||||
|
// Check if the end of the response has arrived
|
||||||
|
if (conn->m_BufferSize >= 4 && strcmp(conn->m_Buffer + conn->m_BufferSize - 4, "\r\n\r\n") == 0)
|
||||||
|
{
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RESULT_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
Result VerifyHeaders(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Result VerifyHeaders(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
char* r = conn->m_Buffer;
|
||||||
|
|
||||||
|
// According to protocol, the response should start with "HTTP/1.1 <statuscode> <message>"
|
||||||
|
const char* http_version_and_status_protocol = "HTTP/1.1 101";
|
||||||
|
if (strstr(r, http_version_and_status_protocol) != r) {
|
||||||
|
return SetStatus(conn, RESULT_HANDSHAKE_FAILED, "Missing: '%s' in header", http_version_and_status_protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = strstr(r, "\r\n") + 2;
|
||||||
|
|
||||||
|
bool upgraded = false;
|
||||||
|
bool valid_key = false;
|
||||||
|
const char* protocol = "";
|
||||||
|
|
||||||
|
// TODO: Perhaps also support the Sec-WebSocket-Protocol
|
||||||
|
|
||||||
|
// parse the headers in place
|
||||||
|
while (r)
|
||||||
|
{
|
||||||
|
// Tokenize the each header line: "Key: Value\r\n"
|
||||||
|
const char* key = r;
|
||||||
|
r = strchr(r, ':');
|
||||||
|
*r = 0;
|
||||||
|
++r;
|
||||||
|
const char* value = r;
|
||||||
|
while(*value == ' ')
|
||||||
|
++value;
|
||||||
|
r = strstr(r, "\r\n");
|
||||||
|
*r = 0;
|
||||||
|
r += 2;
|
||||||
|
|
||||||
|
if (strcmp(key, "Connection") == 0 && strcmp(value, "Upgrade") == 0)
|
||||||
|
upgraded = true;
|
||||||
|
else if (strcmp(key, "Sec-WebSocket-Accept") == 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
uint8_t client_key[32 + 40];
|
||||||
|
uint32_t client_key_len = sizeof(client_key);
|
||||||
|
dmCrypt::Base64Encode(conn->m_Key, sizeof(conn->m_Key), client_key, &client_key_len);
|
||||||
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
memcpy(client_key + client_key_len, RFC_MAGIC, strlen(RFC_MAGIC));
|
||||||
|
client_key_len += strlen(RFC_MAGIC);
|
||||||
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
uint8_t client_key_sha1[20];
|
||||||
|
dmCrypt::HashSha1(client_key, client_key_len, client_key_sha1);
|
||||||
|
|
||||||
|
client_key_len = sizeof(client_key);
|
||||||
|
dmCrypt::Base64Encode(client_key_sha1, sizeof(client_key_sha1), client_key, &client_key_len);
|
||||||
|
client_key[client_key_len] = 0;
|
||||||
|
|
||||||
|
if (strcmp(value, (const char*)client_key) == 0)
|
||||||
|
valid_key = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(r, "\r\n") == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!upgraded)
|
||||||
|
dmLogError("Failed to find the Upgrade keyword in the response headers");
|
||||||
|
if (!valid_key)
|
||||||
|
dmLogError("Failed to find valid key in the response headers");
|
||||||
|
|
||||||
|
if (!(upgraded && valid_key)) {
|
||||||
|
dmLogError("Response:\n\"%s\"\n", conn->m_Buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (upgraded && valid_key) ? RESULT_OK : RESULT_HANDSHAKE_FAILED;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
24
websocket/src/pcg.cpp
Normal file
24
websocket/src/pcg.cpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#include "websocket.h"
|
||||||
|
|
||||||
|
// https://www.pcg-random.org/using-pcg-c-basic.html
|
||||||
|
|
||||||
|
namespace dmWebsocket
|
||||||
|
{
|
||||||
|
uint32_t pcg32_random_r(pcg32_random_t* rng)
|
||||||
|
{
|
||||||
|
uint64_t oldstate = rng->state;
|
||||||
|
rng->state = oldstate * 6364136223846793005ULL + rng->inc;
|
||||||
|
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
|
||||||
|
uint32_t rot = oldstate >> 59u;
|
||||||
|
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
|
||||||
|
{
|
||||||
|
rng->state = 0U;
|
||||||
|
rng->inc = (initseq << 1u) | 1u;
|
||||||
|
pcg32_random_r(rng);
|
||||||
|
rng->state += initstate;
|
||||||
|
pcg32_random_r(rng);
|
||||||
|
}
|
||||||
|
}
|
79
websocket/src/script_util.cpp
Normal file
79
websocket/src/script_util.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#include "script_util.h"
|
||||||
|
|
||||||
|
namespace dmWebsocket {
|
||||||
|
|
||||||
|
bool luaL_checkbool(lua_State *L, int numArg)
|
||||||
|
{
|
||||||
|
bool b = false;
|
||||||
|
if (lua_isboolean(L, numArg))
|
||||||
|
{
|
||||||
|
b = lua_toboolean(L, numArg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
luaL_typerror(L, numArg, lua_typename(L, LUA_TBOOLEAN));
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool luaL_checkboold(lua_State *L, int numArg, int def)
|
||||||
|
{
|
||||||
|
int type = lua_type(L, numArg);
|
||||||
|
if (type != LUA_TNONE && type != LUA_TNIL)
|
||||||
|
{
|
||||||
|
return luaL_checkbool(L, numArg);
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_Number luaL_checknumberd(lua_State *L, int numArg, lua_Number def)
|
||||||
|
{
|
||||||
|
int type = lua_type(L, numArg);
|
||||||
|
if (type != LUA_TNONE && type != LUA_TNIL)
|
||||||
|
{
|
||||||
|
return luaL_checknumber(L, numArg);
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* luaL_checkstringd(lua_State *L, int numArg, const char* def)
|
||||||
|
{
|
||||||
|
int type = lua_type(L, numArg);
|
||||||
|
if (type != LUA_TNONE && type != LUA_TNIL)
|
||||||
|
{
|
||||||
|
return (char*)luaL_checkstring(L, numArg);
|
||||||
|
}
|
||||||
|
return (char*)def;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_Number luaL_checktable_number(lua_State *L, int numArg, const char* field, lua_Number def)
|
||||||
|
{
|
||||||
|
lua_Number result = def;
|
||||||
|
if(lua_istable(L, numArg))
|
||||||
|
{
|
||||||
|
lua_getfield(L, numArg, field);
|
||||||
|
if(!lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
result = luaL_checknumber(L, -1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* luaL_checktable_string(lua_State *L, int numArg, const char* field, char* def)
|
||||||
|
{
|
||||||
|
char* result = def;
|
||||||
|
if(lua_istable(L, numArg))
|
||||||
|
{
|
||||||
|
lua_getfield(L, numArg, field);
|
||||||
|
if(!lua_isnil(L, -1))
|
||||||
|
{
|
||||||
|
result = (char*)luaL_checkstring(L, -1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
12
websocket/src/script_util.h
Normal file
12
websocket/src/script_util.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dmsdk/sdk.h>
|
||||||
|
|
||||||
|
namespace dmWebsocket {
|
||||||
|
bool luaL_checkbool(lua_State *L, int numArg);
|
||||||
|
bool luaL_checkboold(lua_State *L, int numArg, int def);
|
||||||
|
lua_Number luaL_checknumberd(lua_State *L, int numArg, lua_Number def);
|
||||||
|
char* luaL_checkstringd(lua_State *L, int numArg, const char* def);
|
||||||
|
lua_Number luaL_checktable_number(lua_State *L, int numArg, const char* field, lua_Number def);
|
||||||
|
char* luaL_checktable_string(lua_State *L, int numArg, const char* field, char* def);
|
||||||
|
} // namespace
|
56
websocket/src/socket.cpp
Normal file
56
websocket/src/socket.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include <dmsdk/dlib/socket.h>
|
||||||
|
#include <dmsdk/dlib/sslsocket.h>
|
||||||
|
#include "websocket.h"
|
||||||
|
|
||||||
|
namespace dmWebsocket
|
||||||
|
{
|
||||||
|
|
||||||
|
dmSocket::Result WaitForSocket(WebsocketConnection* conn, dmSocket::SelectorKind kind, int timeout)
|
||||||
|
{
|
||||||
|
dmSocket::Selector selector;
|
||||||
|
dmSocket::SelectorZero(&selector);
|
||||||
|
dmSocket::SelectorSet(&selector, kind, conn->m_Socket);
|
||||||
|
return dmSocket::Select(&selector, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
dmSocket::Result Send(WebsocketConnection* conn, const char* buffer, int length, int* out_sent_bytes)
|
||||||
|
{
|
||||||
|
int total_sent_bytes = 0;
|
||||||
|
int sent_bytes = 0;
|
||||||
|
|
||||||
|
while (total_sent_bytes < length) {
|
||||||
|
dmSocket::Result r;
|
||||||
|
|
||||||
|
if (conn->m_SSLSocket)
|
||||||
|
r = dmSSLSocket::Send(conn->m_SSLSocket, buffer + total_sent_bytes, length - total_sent_bytes, &sent_bytes);
|
||||||
|
else
|
||||||
|
r = dmSocket::Send(conn->m_Socket, buffer + total_sent_bytes, length - total_sent_bytes, &sent_bytes);
|
||||||
|
|
||||||
|
if( r == dmSocket::RESULT_WOULDBLOCK )
|
||||||
|
{
|
||||||
|
r = dmSocket::RESULT_TRY_AGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == dmSocket::RESULT_TRY_AGAIN)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (r != dmSocket::RESULT_OK) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_sent_bytes += sent_bytes;
|
||||||
|
}
|
||||||
|
if (out_sent_bytes)
|
||||||
|
*out_sent_bytes = total_sent_bytes;
|
||||||
|
return dmSocket::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes)
|
||||||
|
{
|
||||||
|
if (conn->m_SSLSocket)
|
||||||
|
return dmSSLSocket::Receive(conn->m_SSLSocket, buffer, length, received_bytes);
|
||||||
|
else
|
||||||
|
return dmSocket::Receive(conn->m_Socket, buffer, length, received_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
536
websocket/src/websocket.cpp
Normal file
536
websocket/src/websocket.cpp
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
// More info on websockets
|
||||||
|
// https://tools.ietf.org/html/rfc6455
|
||||||
|
|
||||||
|
#define LIB_NAME "Websocket"
|
||||||
|
#define MODULE_NAME "websocket"
|
||||||
|
|
||||||
|
#include "websocket.h"
|
||||||
|
#include "script_util.h"
|
||||||
|
#include <dmsdk/dlib/connection_pool.h>
|
||||||
|
#include <dmsdk/dlib/dns.h>
|
||||||
|
#include <dmsdk/dlib/sslsocket.h>
|
||||||
|
|
||||||
|
namespace dmWebsocket {
|
||||||
|
|
||||||
|
|
||||||
|
struct WebsocketContext
|
||||||
|
{
|
||||||
|
uint64_t m_BufferSize;
|
||||||
|
int m_Timeout;
|
||||||
|
dmArray<WebsocketConnection*> m_Connections;
|
||||||
|
dmConnectionPool::HPool m_Pool;
|
||||||
|
dmDNS::HChannel m_Channel;
|
||||||
|
uint32_t m_Initialized:1;
|
||||||
|
} g_Websocket;
|
||||||
|
|
||||||
|
|
||||||
|
static void HandleCallback(WebsocketConnection* conn, int event);
|
||||||
|
|
||||||
|
|
||||||
|
#define STRING_CASE(_X) case _X: return #_X;
|
||||||
|
|
||||||
|
const char* ResultToString(Result err)
|
||||||
|
{
|
||||||
|
switch(err) {
|
||||||
|
STRING_CASE(RESULT_OK);
|
||||||
|
STRING_CASE(RESULT_ERROR);
|
||||||
|
STRING_CASE(RESULT_FAIL_WSLAY_INIT);
|
||||||
|
STRING_CASE(RESULT_NOT_CONNECTED);
|
||||||
|
STRING_CASE(RESULT_HANDSHAKE_FAILED);
|
||||||
|
STRING_CASE(RESULT_WOULDBLOCK);
|
||||||
|
default: return "Unknown result";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* StateToString(State err)
|
||||||
|
{
|
||||||
|
switch(err) {
|
||||||
|
STRING_CASE(STATE_CONNECTING);
|
||||||
|
STRING_CASE(STATE_HANDSHAKE_WRITE);
|
||||||
|
STRING_CASE(STATE_HANDSHAKE_READ);
|
||||||
|
STRING_CASE(STATE_CONNECTED);
|
||||||
|
STRING_CASE(STATE_DISCONNECTED);
|
||||||
|
default: return "Unknown error";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef STRING_CASE
|
||||||
|
|
||||||
|
#define WS_DEBUG(...)
|
||||||
|
//#define WS_DEBUG(...) dmLogWarning(__VA_ARGS__);
|
||||||
|
|
||||||
|
#define CLOSE_CONN(...) \
|
||||||
|
SetStatus(conn, RESULT_ERROR, __VA_ARGS__); \
|
||||||
|
CloseConnection(conn);
|
||||||
|
|
||||||
|
|
||||||
|
static void SetState(WebsocketConnection* conn, State state)
|
||||||
|
{
|
||||||
|
State prev_state = conn->m_State;
|
||||||
|
if (prev_state != state)
|
||||||
|
{
|
||||||
|
conn->m_State = state;
|
||||||
|
WS_DEBUG("%s -> %s", StateToString(prev_state), StateToString(conn->m_State));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Result SetStatus(WebsocketConnection* conn, Result status, const char* format, ...)
|
||||||
|
{
|
||||||
|
if (conn->m_Status == RESULT_OK)
|
||||||
|
{
|
||||||
|
va_list lst;
|
||||||
|
va_start(lst, format);
|
||||||
|
|
||||||
|
conn->m_BufferSize = vsnprintf(conn->m_Buffer, conn->m_BufferCapacity, format, lst);
|
||||||
|
va_end(lst);
|
||||||
|
conn->m_Status = status;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************************************
|
||||||
|
// LUA functions
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static WebsocketConnection* CreateConnection(const char* url)
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)malloc(sizeof(WebsocketConnection));
|
||||||
|
memset(conn, 0, sizeof(WebsocketConnection));
|
||||||
|
conn->m_BufferCapacity = g_Websocket.m_BufferSize;
|
||||||
|
conn->m_Buffer = (char*)malloc(conn->m_BufferCapacity);
|
||||||
|
|
||||||
|
dmURI::Parts uri;
|
||||||
|
dmURI::Parse(url, &conn->m_Url);
|
||||||
|
|
||||||
|
if (strcmp(conn->m_Url.m_Scheme, "https") == 0)
|
||||||
|
strcpy(conn->m_Url.m_Scheme, "wss");
|
||||||
|
|
||||||
|
conn->m_SSL = strcmp(conn->m_Url.m_Scheme, "wss") == 0 ? 1 : 0;
|
||||||
|
conn->m_State = STATE_CONNECTING;
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DestroyConnection(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
if (conn->m_Ctx)
|
||||||
|
WSL_Exit(conn->m_Ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (conn->m_Callback)
|
||||||
|
dmScript::DestroyCallback(conn->m_Callback);
|
||||||
|
|
||||||
|
if (conn->m_Connection)
|
||||||
|
dmConnectionPool::Return(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
|
||||||
|
free((void*)conn->m_Buffer);
|
||||||
|
free((void*)conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void CloseConnection(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
State prev_state = conn->m_State;
|
||||||
|
|
||||||
|
// we want it to send this message in the polling
|
||||||
|
if (conn->m_State == STATE_CONNECTED) {
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
WSL_Close(conn->m_Ctx);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SetState(conn, STATE_DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FindConnection(WebsocketConnection* conn)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < g_Websocket.m_Connections.Size(); ++i )
|
||||||
|
{
|
||||||
|
if (g_Websocket.m_Connections[i] == conn)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static int LuaConnect(lua_State* L)
|
||||||
|
{
|
||||||
|
DM_LUA_STACK_CHECK(L, 1);
|
||||||
|
|
||||||
|
if (!g_Websocket.m_Initialized)
|
||||||
|
return DM_LUA_ERROR("The web socket module isn't initialized");
|
||||||
|
|
||||||
|
const char* url = luaL_checkstring(L, 1);
|
||||||
|
|
||||||
|
// long playedTime = luaL_checktable_number(L, 2, "playedTime", -1);
|
||||||
|
// long progressValue = luaL_checktable_number(L, 2, "progressValue", -1);
|
||||||
|
// char *description = luaL_checktable_string(L, 2, "description", NULL);
|
||||||
|
// char *coverImage = luaL_checktable_string(L, 2, "coverImage", NULL);
|
||||||
|
|
||||||
|
WebsocketConnection* conn = CreateConnection(url);
|
||||||
|
|
||||||
|
conn->m_Callback = dmScript::CreateCallback(L, 3);
|
||||||
|
|
||||||
|
if (g_Websocket.m_Connections.Full())
|
||||||
|
g_Websocket.m_Connections.OffsetCapacity(2);
|
||||||
|
g_Websocket.m_Connections.Push(conn);
|
||||||
|
|
||||||
|
lua_pushlightuserdata(L, conn);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int LuaDisconnect(lua_State* L)
|
||||||
|
{
|
||||||
|
DM_LUA_STACK_CHECK(L, 0);
|
||||||
|
|
||||||
|
if (!g_Websocket.m_Initialized)
|
||||||
|
return DM_LUA_ERROR("The web socket module isn't initialized");
|
||||||
|
|
||||||
|
if (!lua_islightuserdata(L, 1))
|
||||||
|
return DM_LUA_ERROR("The first argument must be a valid connection!");
|
||||||
|
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)lua_touserdata(L, 1);
|
||||||
|
|
||||||
|
int i = FindConnection(conn);
|
||||||
|
if (i != -1)
|
||||||
|
{
|
||||||
|
CloseConnection(conn);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int LuaSend(lua_State* L)
|
||||||
|
{
|
||||||
|
DM_LUA_STACK_CHECK(L, 0);
|
||||||
|
|
||||||
|
if (!g_Websocket.m_Initialized)
|
||||||
|
return DM_LUA_ERROR("The web socket module isn't initialized");
|
||||||
|
|
||||||
|
if (!lua_islightuserdata(L, 1))
|
||||||
|
return DM_LUA_ERROR("The first argument must be a valid connection!");
|
||||||
|
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)lua_touserdata(L, 1);
|
||||||
|
|
||||||
|
int i = FindConnection(conn);
|
||||||
|
if (i == -1)
|
||||||
|
return DM_LUA_ERROR("Invalid connection");
|
||||||
|
|
||||||
|
if (conn->m_State != STATE_CONNECTED)
|
||||||
|
return DM_LUA_ERROR("Connection isn't connected");
|
||||||
|
|
||||||
|
size_t string_length = 0;
|
||||||
|
const char* string = luaL_checklstring(L, 2, &string_length);
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
int write_mode = WSLAY_BINARY_FRAME; // WSLAY_TEXT_FRAME
|
||||||
|
|
||||||
|
struct wslay_event_msg msg;
|
||||||
|
msg.opcode = write_mode;
|
||||||
|
msg.msg = (const uint8_t*)string;
|
||||||
|
msg.msg_length = string_length;
|
||||||
|
|
||||||
|
wslay_event_queue_msg(conn->m_Ctx, &msg); // it makes a copy of the data
|
||||||
|
#else
|
||||||
|
|
||||||
|
dmSocket::Result sr = Send(conn, string, string_length, 0);
|
||||||
|
if (dmSocket::RESULT_OK != sr)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed to send on websocket");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleCallback(WebsocketConnection* conn, int event)
|
||||||
|
{
|
||||||
|
if (!dmScript::IsCallbackValid(conn->m_Callback))
|
||||||
|
return;
|
||||||
|
|
||||||
|
lua_State* L = dmScript::GetCallbackLuaContext(conn->m_Callback);
|
||||||
|
DM_LUA_STACK_CHECK(L, 0)
|
||||||
|
|
||||||
|
if (!dmScript::SetupCallback(conn->m_Callback))
|
||||||
|
{
|
||||||
|
dmLogError("Failed to setup callback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushlightuserdata(L, conn);
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
lua_pushinteger(L, event);
|
||||||
|
lua_setfield(L, -2, "event");
|
||||||
|
|
||||||
|
if (EVENT_ERROR == event) {
|
||||||
|
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
||||||
|
lua_setfield(L, -2, "error");
|
||||||
|
}
|
||||||
|
else if (EVENT_MESSAGE == event) {
|
||||||
|
lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize);
|
||||||
|
lua_setfield(L, -2, "message");
|
||||||
|
}
|
||||||
|
|
||||||
|
dmScript::PCall(L, 3, 0);
|
||||||
|
|
||||||
|
dmScript::TeardownCallback(conn->m_Callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ***************************************************************************************************
|
||||||
|
// Life cycle functions
|
||||||
|
|
||||||
|
// Functions exposed to Lua
|
||||||
|
static const luaL_reg Websocket_module_methods[] =
|
||||||
|
{
|
||||||
|
{"connect", LuaConnect},
|
||||||
|
{"disconnect", LuaDisconnect},
|
||||||
|
{"send", LuaSend},
|
||||||
|
{0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void LuaInit(lua_State* L)
|
||||||
|
{
|
||||||
|
int top = lua_gettop(L);
|
||||||
|
|
||||||
|
// Register lua names
|
||||||
|
luaL_register(L, MODULE_NAME, Websocket_module_methods);
|
||||||
|
|
||||||
|
#define SETCONSTANT(_X) \
|
||||||
|
lua_pushnumber(L, (lua_Number) _X); \
|
||||||
|
lua_setfield(L, -2, #_X);
|
||||||
|
|
||||||
|
SETCONSTANT(EVENT_CONNECTED);
|
||||||
|
SETCONSTANT(EVENT_DISCONNECTED);
|
||||||
|
SETCONSTANT(EVENT_MESSAGE);
|
||||||
|
SETCONSTANT(EVENT_ERROR);
|
||||||
|
|
||||||
|
#undef SETCONSTANT
|
||||||
|
|
||||||
|
lua_pop(L, 1);
|
||||||
|
assert(top == lua_gettop(L));
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result WebsocketAppInitialize(dmExtension::AppParams* params)
|
||||||
|
{
|
||||||
|
g_Websocket.m_BufferSize = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.buffer_size", 64 * 1024);
|
||||||
|
g_Websocket.m_Timeout = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.socket_timeout", 500 * 1000);
|
||||||
|
g_Websocket.m_Connections.SetCapacity(4);
|
||||||
|
g_Websocket.m_Channel = 0;
|
||||||
|
g_Websocket.m_Pool = 0;
|
||||||
|
|
||||||
|
dmConnectionPool::Params pool_params;
|
||||||
|
pool_params.m_MaxConnections = dmConfigFile::GetInt(params->m_ConfigFile, "websocket.max_connections", 2);
|
||||||
|
dmConnectionPool::Result result = dmConnectionPool::New(&pool_params, &g_Websocket.m_Pool);
|
||||||
|
|
||||||
|
if (dmConnectionPool::RESULT_OK != result)
|
||||||
|
{
|
||||||
|
dmLogError("Failed to create connection pool: %d", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can do without the channel, it will then fallback to the dmSocket::GetHostname (as opposed to dmDNS::GetHostname)
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
dmDNS::Result dns_result = dmDNS::NewChannel(&g_Websocket.m_Channel);
|
||||||
|
|
||||||
|
if (dmDNS::RESULT_OK != dns_result)
|
||||||
|
{
|
||||||
|
dmLogError("Failed to create connection pool: %d", dns_result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
g_Websocket.m_Initialized = 1;
|
||||||
|
if (!g_Websocket.m_Pool)
|
||||||
|
{
|
||||||
|
if (!g_Websocket.m_Pool)
|
||||||
|
{
|
||||||
|
dmLogInfo("pool is null!");
|
||||||
|
dmConnectionPool::Delete(g_Websocket.m_Pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
dmLogInfo("%s extension not initialized", MODULE_NAME);
|
||||||
|
g_Websocket.m_Initialized = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result WebsocketInitialize(dmExtension::Params* params)
|
||||||
|
{
|
||||||
|
if (!g_Websocket.m_Initialized)
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
|
||||||
|
LuaInit(params->m_L);
|
||||||
|
dmLogInfo("Registered %s extension", MODULE_NAME);
|
||||||
|
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result WebsocketAppFinalize(dmExtension::AppParams* params)
|
||||||
|
{
|
||||||
|
|
||||||
|
dmConnectionPool::Shutdown(g_Websocket.m_Pool, dmSocket::SHUTDOWNTYPE_READWRITE);
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result WebsocketFinalize(dmExtension::Params* params)
|
||||||
|
{
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params)
|
||||||
|
{
|
||||||
|
uint32_t size = g_Websocket.m_Connections.Size();
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = g_Websocket.m_Connections[i];
|
||||||
|
|
||||||
|
if (STATE_DISCONNECTED == conn->m_State)
|
||||||
|
{
|
||||||
|
if (RESULT_OK != conn->m_Status)
|
||||||
|
{
|
||||||
|
HandleCallback(conn, EVENT_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleCallback(conn, EVENT_DISCONNECTED);
|
||||||
|
|
||||||
|
g_Websocket.m_Connections.EraseSwap(i);
|
||||||
|
--i;
|
||||||
|
--size;
|
||||||
|
DestroyConnection(conn);
|
||||||
|
}
|
||||||
|
else if (STATE_CONNECTED == conn->m_State)
|
||||||
|
{
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
int r = WSL_Poll(conn->m_Ctx);
|
||||||
|
if (0 != r)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Websocket closing for %s (%s)", conn->m_Url.m_Hostname, WSL_ResultToString(r));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
r = WSL_WantsExit(conn->m_Ctx);
|
||||||
|
if (0 != r)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Websocket received close event for %s", conn->m_Url.m_Hostname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int recv_bytes = 0;
|
||||||
|
dmSocket::Result sr = Receive(conn, conn->m_Buffer, conn->m_BufferCapacity-1, &recv_bytes);
|
||||||
|
if( sr == dmSocket::RESULT_WOULDBLOCK )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dmSocket::RESULT_OK == sr)
|
||||||
|
{
|
||||||
|
conn->m_BufferSize += recv_bytes;
|
||||||
|
conn->m_Buffer[conn->m_BufferCapacity-1] = 0;
|
||||||
|
conn->m_HasMessage = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Websocket failed to receive data %s", dmSocket::ResultToString(sr));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (conn->m_HasMessage)
|
||||||
|
{
|
||||||
|
HandleCallback(conn, EVENT_MESSAGE);
|
||||||
|
conn->m_HasMessage = 0;
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (STATE_HANDSHAKE_READ == conn->m_State)
|
||||||
|
{
|
||||||
|
Result result = ReceiveHeaders(conn);
|
||||||
|
if (RESULT_WOULDBLOCK == result)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RESULT_OK != result)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed receiving handshake headers. %d", result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = VerifyHeaders(conn);
|
||||||
|
if (RESULT_OK != result)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed verifying handshake headers:\n%s\n\n", conn->m_Buffer);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
int r = WSL_Init(&conn->m_Ctx, g_Websocket.m_BufferSize, (void*)conn);
|
||||||
|
if (0 != r)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed initializing wslay: %s", WSL_ResultToString(r));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dmSocket::SetNoDelay(conn->m_Socket, true);
|
||||||
|
// Don't go lower than 1000 since some platforms might not have that good precision
|
||||||
|
dmSocket::SetReceiveTimeout(conn->m_Socket, 1000);
|
||||||
|
if (conn->m_SSLSocket)
|
||||||
|
dmSSLSocket::SetReceiveTimeout(conn->m_SSLSocket, 1000);
|
||||||
|
#endif
|
||||||
|
dmSocket::SetBlocking(conn->m_Socket, false);
|
||||||
|
|
||||||
|
conn->m_Buffer[0] = 0;
|
||||||
|
conn->m_BufferSize = 0;
|
||||||
|
|
||||||
|
SetState(conn, STATE_CONNECTED);
|
||||||
|
HandleCallback(conn, EVENT_CONNECTED);
|
||||||
|
}
|
||||||
|
else if (STATE_HANDSHAKE_WRITE == conn->m_State)
|
||||||
|
{
|
||||||
|
Result result = SendClientHandshake(conn);
|
||||||
|
if (RESULT_WOULDBLOCK == result)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (RESULT_OK != result)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed sending handshake: %d", result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetState(conn, STATE_HANDSHAKE_READ);
|
||||||
|
}
|
||||||
|
else if (STATE_CONNECTING == conn->m_State)
|
||||||
|
{
|
||||||
|
dmSocket::Result socket_result;
|
||||||
|
int timeout = g_Websocket.m_Timeout;
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
timeout = 0;
|
||||||
|
#endif
|
||||||
|
dmConnectionPool::Result pool_result = dmConnectionPool::Dial(g_Websocket.m_Pool, conn->m_Url.m_Hostname, conn->m_Url.m_Port, g_Websocket.m_Channel, conn->m_SSL, timeout, &conn->m_Connection, &socket_result);
|
||||||
|
if (dmConnectionPool::RESULT_OK != pool_result)
|
||||||
|
{
|
||||||
|
CLOSE_CONN("Failed to open connection: %s", dmSocket::ResultToString(socket_result));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection);
|
||||||
|
SetState(conn, STATE_HANDSHAKE_WRITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // dmWebsocket
|
||||||
|
|
||||||
|
DM_DECLARE_EXTENSION(Websocket, LIB_NAME, dmWebsocket::WebsocketAppInitialize, dmWebsocket::WebsocketAppFinalize, dmWebsocket::WebsocketInitialize, dmWebsocket::WebsocketOnUpdate, 0, dmWebsocket::WebsocketFinalize)
|
||||||
|
|
||||||
|
#undef CLOSE_CONN
|
124
websocket/src/websocket.h
Normal file
124
websocket/src/websocket.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// include the Defold SDK
|
||||||
|
#include <dmsdk/sdk.h>
|
||||||
|
|
||||||
|
#if !defined(__EMSCRIPTEN__)
|
||||||
|
#define HAVE_WSLAY 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
#include <wslay/wslay.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dmsdk/dlib/connection_pool.h>
|
||||||
|
#include <dmsdk/dlib/socket.h>
|
||||||
|
#include <dmsdk/dlib/dns.h>
|
||||||
|
#include <dmsdk/dlib/uri.h>
|
||||||
|
|
||||||
|
namespace dmCrypt
|
||||||
|
{
|
||||||
|
void HashSha1(const uint8_t* buf, uint32_t buflen, uint8_t* digest);
|
||||||
|
bool Base64Encode(const uint8_t* src, uint32_t src_len, uint8_t* dst, uint32_t* dst_len);
|
||||||
|
bool Base64Decode(const uint8_t* src, uint32_t src_len, uint8_t* dst, uint32_t* dst_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace dmWebsocket
|
||||||
|
{
|
||||||
|
// Maximum time to wait for a socket
|
||||||
|
static const int SOCKET_WAIT_TIMEOUT = 4*1000;
|
||||||
|
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
STATE_CONNECTING,
|
||||||
|
STATE_HANDSHAKE_WRITE,
|
||||||
|
STATE_HANDSHAKE_READ,
|
||||||
|
STATE_CONNECTED,
|
||||||
|
STATE_DISCONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Result
|
||||||
|
{
|
||||||
|
RESULT_OK,
|
||||||
|
RESULT_ERROR,
|
||||||
|
RESULT_FAIL_WSLAY_INIT,
|
||||||
|
RESULT_NOT_CONNECTED,
|
||||||
|
RESULT_HANDSHAKE_FAILED,
|
||||||
|
RESULT_WOULDBLOCK,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Event
|
||||||
|
{
|
||||||
|
EVENT_CONNECTED,
|
||||||
|
EVENT_DISCONNECTED,
|
||||||
|
EVENT_MESSAGE,
|
||||||
|
EVENT_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WebsocketConnection
|
||||||
|
{
|
||||||
|
dmScript::LuaCallbackInfo* m_Callback;
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
wslay_event_context_ptr m_Ctx;
|
||||||
|
#endif
|
||||||
|
dmURI::Parts m_Url;
|
||||||
|
dmConnectionPool::HConnection m_Connection;
|
||||||
|
dmSocket::Socket m_Socket;
|
||||||
|
dmSSLSocket::Socket m_SSLSocket;
|
||||||
|
uint8_t m_Key[16];
|
||||||
|
State m_State;
|
||||||
|
uint32_t m_SSL:1;
|
||||||
|
uint32_t m_HasMessage:1;
|
||||||
|
char* m_Buffer;
|
||||||
|
int m_BufferSize;
|
||||||
|
uint32_t m_BufferCapacity;
|
||||||
|
Result m_Status;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set error message
|
||||||
|
#ifdef __GNUC__
|
||||||
|
Result SetStatus(WebsocketConnection* conn, Result status, const char* fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||||
|
#else
|
||||||
|
Result SetStatus(WebsocketConnection* conn, Result status, const char* fmt, ...);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Communication
|
||||||
|
dmSocket::Result Send(WebsocketConnection* conn, const char* buffer, int length, int* out_sent_bytes);
|
||||||
|
dmSocket::Result Receive(WebsocketConnection* conn, void* buffer, int length, int* received_bytes);
|
||||||
|
dmSocket::Result WaitForSocket(WebsocketConnection* conn, dmSocket::SelectorKind kind, int timeout);
|
||||||
|
|
||||||
|
// Handshake
|
||||||
|
Result SendClientHandshake(WebsocketConnection* conn);
|
||||||
|
Result ReceiveHeaders(WebsocketConnection* conn);
|
||||||
|
Result VerifyHeaders(WebsocketConnection* conn);
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
// Wslay callbacks
|
||||||
|
int WSL_Init(wslay_event_context_ptr* ctx, ssize_t buffer_size, void* userctx);
|
||||||
|
void WSL_Exit(wslay_event_context_ptr ctx);
|
||||||
|
int WSL_Close(wslay_event_context_ptr ctx);
|
||||||
|
int WSL_Poll(wslay_event_context_ptr ctx);
|
||||||
|
int WSL_WantsExit(wslay_event_context_ptr ctx);
|
||||||
|
ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *user_data);
|
||||||
|
ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data);
|
||||||
|
void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data);
|
||||||
|
int WSL_GenmaskCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data);
|
||||||
|
const char* WSL_ResultToString(int err);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Random numbers (PCG)
|
||||||
|
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
|
||||||
|
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq);
|
||||||
|
uint32_t pcg32_random_r(pcg32_random_t* rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
1027
websocket/src/wslay/wslay_event.c
Normal file
1027
websocket/src/wslay/wslay_event.c
Normal file
File diff suppressed because it is too large
Load Diff
340
websocket/src/wslay/wslay_frame.c
Normal file
340
websocket/src/wslay/wslay_frame.c
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "wslay_frame.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "wslay_net.h"
|
||||||
|
|
||||||
|
#define wslay_min(A, B) (((A) < (B)) ? (A) : (B))
|
||||||
|
|
||||||
|
int wslay_frame_context_init(wslay_frame_context_ptr *ctx,
|
||||||
|
const struct wslay_frame_callbacks *callbacks,
|
||||||
|
void *user_data)
|
||||||
|
{
|
||||||
|
*ctx = (wslay_frame_context_ptr)malloc(sizeof(struct wslay_frame_context));
|
||||||
|
if(*ctx == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memset(*ctx, 0, sizeof(struct wslay_frame_context));
|
||||||
|
(*ctx)->istate = RECV_HEADER1;
|
||||||
|
(*ctx)->ireqread = 2;
|
||||||
|
(*ctx)->ostate = PREP_HEADER;
|
||||||
|
(*ctx)->user_data = user_data;
|
||||||
|
(*ctx)->ibufmark = (*ctx)->ibuflimit = (*ctx)->ibuf;
|
||||||
|
(*ctx)->callbacks = *callbacks;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wslay_frame_context_free(wslay_frame_context_ptr ctx)
|
||||||
|
{
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t wslay_frame_send(wslay_frame_context_ptr ctx,
|
||||||
|
struct wslay_frame_iocb *iocb)
|
||||||
|
{
|
||||||
|
if(iocb->data_length > iocb->payload_length) {
|
||||||
|
return WSLAY_ERR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
if(ctx->ostate == PREP_HEADER) {
|
||||||
|
uint8_t *hdptr = ctx->oheader;
|
||||||
|
memset(ctx->oheader, 0, sizeof(ctx->oheader));
|
||||||
|
*hdptr |= (iocb->fin << 7) & 0x80u;
|
||||||
|
*hdptr |= (iocb->rsv << 4) & 0x70u;
|
||||||
|
*hdptr |= iocb->opcode & 0xfu;
|
||||||
|
++hdptr;
|
||||||
|
*hdptr |= (iocb->mask << 7) & 0x80u;
|
||||||
|
if(wslay_is_ctrl_frame(iocb->opcode) && iocb->payload_length > 125) {
|
||||||
|
return WSLAY_ERR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
if(iocb->payload_length < 126) {
|
||||||
|
*hdptr |= iocb->payload_length;
|
||||||
|
++hdptr;
|
||||||
|
} else if(iocb->payload_length < (1 << 16)) {
|
||||||
|
uint16_t len = htons(iocb->payload_length);
|
||||||
|
*hdptr |= 126;
|
||||||
|
++hdptr;
|
||||||
|
memcpy(hdptr, &len, 2);
|
||||||
|
hdptr += 2;
|
||||||
|
} else if(iocb->payload_length < (1ull << 63)) {
|
||||||
|
uint64_t len = hton64(iocb->payload_length);
|
||||||
|
*hdptr |= 127;
|
||||||
|
++hdptr;
|
||||||
|
memcpy(hdptr, &len, 8);
|
||||||
|
hdptr += 8;
|
||||||
|
} else {
|
||||||
|
/* Too large payload length */
|
||||||
|
return WSLAY_ERR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
if(iocb->mask) {
|
||||||
|
if(ctx->callbacks.genmask_callback(ctx->omaskkey, 4,
|
||||||
|
ctx->user_data) != 0) {
|
||||||
|
return WSLAY_ERR_INVALID_CALLBACK;
|
||||||
|
} else {
|
||||||
|
ctx->omask = 1;
|
||||||
|
memcpy(hdptr, ctx->omaskkey, 4);
|
||||||
|
hdptr += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->ostate = SEND_HEADER;
|
||||||
|
ctx->oheadermark = ctx->oheader;
|
||||||
|
ctx->oheaderlimit = hdptr;
|
||||||
|
ctx->opayloadlen = iocb->payload_length;
|
||||||
|
ctx->opayloadoff = 0;
|
||||||
|
}
|
||||||
|
if(ctx->ostate == SEND_HEADER) {
|
||||||
|
ptrdiff_t len = ctx->oheaderlimit-ctx->oheadermark;
|
||||||
|
ssize_t r;
|
||||||
|
int flags = 0;
|
||||||
|
if(iocb->data_length > 0) {
|
||||||
|
flags |= WSLAY_MSG_MORE;
|
||||||
|
};
|
||||||
|
r = ctx->callbacks.send_callback(ctx->oheadermark, len, flags,
|
||||||
|
ctx->user_data);
|
||||||
|
if(r > 0) {
|
||||||
|
if(r > len) {
|
||||||
|
return WSLAY_ERR_INVALID_CALLBACK;
|
||||||
|
} else {
|
||||||
|
ctx->oheadermark += r;
|
||||||
|
if(ctx->oheadermark == ctx->oheaderlimit) {
|
||||||
|
ctx->ostate = SEND_PAYLOAD;
|
||||||
|
} else {
|
||||||
|
return WSLAY_ERR_WANT_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return WSLAY_ERR_WANT_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ctx->ostate == SEND_PAYLOAD) {
|
||||||
|
size_t totallen = 0;
|
||||||
|
if(iocb->data_length > 0) {
|
||||||
|
if(ctx->omask) {
|
||||||
|
uint8_t temp[4096];
|
||||||
|
const uint8_t *datamark = iocb->data,
|
||||||
|
*datalimit = iocb->data+iocb->data_length;
|
||||||
|
while(datamark < datalimit) {
|
||||||
|
size_t datalen = datalimit - datamark;
|
||||||
|
const uint8_t *writelimit = datamark+
|
||||||
|
wslay_min(sizeof(temp), datalen);
|
||||||
|
size_t writelen = writelimit-datamark;
|
||||||
|
ssize_t r;
|
||||||
|
size_t i;
|
||||||
|
for(i = 0; i < writelen; ++i) {
|
||||||
|
temp[i] = datamark[i]^ctx->omaskkey[(ctx->opayloadoff+i)%4];
|
||||||
|
}
|
||||||
|
r = ctx->callbacks.send_callback(temp, writelen, 0, ctx->user_data);
|
||||||
|
if(r > 0) {
|
||||||
|
if((size_t)r > writelen) {
|
||||||
|
return WSLAY_ERR_INVALID_CALLBACK;
|
||||||
|
} else {
|
||||||
|
datamark += r;
|
||||||
|
ctx->opayloadoff += r;
|
||||||
|
totallen += r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(totallen > 0) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return WSLAY_ERR_WANT_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ssize_t r;
|
||||||
|
r = ctx->callbacks.send_callback(iocb->data, iocb->data_length, 0,
|
||||||
|
ctx->user_data);
|
||||||
|
if(r > 0) {
|
||||||
|
if((size_t)r > iocb->data_length) {
|
||||||
|
return WSLAY_ERR_INVALID_CALLBACK;
|
||||||
|
} else {
|
||||||
|
ctx->opayloadoff += r;
|
||||||
|
totallen = r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return WSLAY_ERR_WANT_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ctx->opayloadoff == ctx->opayloadlen) {
|
||||||
|
ctx->ostate = PREP_HEADER;
|
||||||
|
}
|
||||||
|
return totallen;
|
||||||
|
}
|
||||||
|
return WSLAY_ERR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wslay_shift_ibuf(wslay_frame_context_ptr ctx)
|
||||||
|
{
|
||||||
|
ptrdiff_t len = ctx->ibuflimit-ctx->ibufmark;
|
||||||
|
memmove(ctx->ibuf, ctx->ibufmark, len);
|
||||||
|
ctx->ibuflimit = ctx->ibuf+len;
|
||||||
|
ctx->ibufmark = ctx->ibuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t wslay_recv(wslay_frame_context_ptr ctx)
|
||||||
|
{
|
||||||
|
ssize_t r;
|
||||||
|
if(ctx->ibufmark != ctx->ibuf) {
|
||||||
|
wslay_shift_ibuf(ctx);
|
||||||
|
}
|
||||||
|
r = ctx->callbacks.recv_callback
|
||||||
|
(ctx->ibuflimit, ctx->ibuf+sizeof(ctx->ibuf)-ctx->ibuflimit,
|
||||||
|
0, ctx->user_data);
|
||||||
|
if(r > 0) {
|
||||||
|
ctx->ibuflimit += r;
|
||||||
|
} else {
|
||||||
|
r = WSLAY_ERR_WANT_READ;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WSLAY_AVAIL_IBUF(ctx) ((size_t)(ctx->ibuflimit - ctx->ibufmark))
|
||||||
|
|
||||||
|
ssize_t wslay_frame_recv(wslay_frame_context_ptr ctx,
|
||||||
|
struct wslay_frame_iocb *iocb)
|
||||||
|
{
|
||||||
|
ssize_t r;
|
||||||
|
if(ctx->istate == RECV_HEADER1) {
|
||||||
|
uint8_t fin, opcode, rsv, payloadlen;
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
|
||||||
|
if((r = wslay_recv(ctx)) <= 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
|
||||||
|
return WSLAY_ERR_WANT_READ;
|
||||||
|
}
|
||||||
|
fin = (ctx->ibufmark[0] >> 7) & 1;
|
||||||
|
rsv = (ctx->ibufmark[0] >> 4) & 7;
|
||||||
|
opcode = ctx->ibufmark[0] & 0xfu;
|
||||||
|
ctx->iom.opcode = opcode;
|
||||||
|
ctx->iom.fin = fin;
|
||||||
|
ctx->iom.rsv = rsv;
|
||||||
|
++ctx->ibufmark;
|
||||||
|
ctx->imask = (ctx->ibufmark[0] >> 7) & 1;
|
||||||
|
payloadlen = ctx->ibufmark[0] & 0x7fu;
|
||||||
|
++ctx->ibufmark;
|
||||||
|
if(wslay_is_ctrl_frame(opcode) && (payloadlen > 125 || !fin)) {
|
||||||
|
return WSLAY_ERR_PROTO;
|
||||||
|
}
|
||||||
|
if(payloadlen == 126) {
|
||||||
|
ctx->istate = RECV_EXT_PAYLOADLEN;
|
||||||
|
ctx->ireqread = 2;
|
||||||
|
} else if(payloadlen == 127) {
|
||||||
|
ctx->istate = RECV_EXT_PAYLOADLEN;
|
||||||
|
ctx->ireqread = 8;
|
||||||
|
} else {
|
||||||
|
ctx->ipayloadlen = payloadlen;
|
||||||
|
ctx->ipayloadoff = 0;
|
||||||
|
if(ctx->imask) {
|
||||||
|
ctx->istate = RECV_MASKKEY;
|
||||||
|
ctx->ireqread = 4;
|
||||||
|
} else {
|
||||||
|
ctx->istate = RECV_PAYLOAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ctx->istate == RECV_EXT_PAYLOADLEN) {
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
|
||||||
|
if((r = wslay_recv(ctx)) <= 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
|
||||||
|
return WSLAY_ERR_WANT_READ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->ipayloadlen = 0;
|
||||||
|
ctx->ipayloadoff = 0;
|
||||||
|
memcpy((uint8_t*)&ctx->ipayloadlen+(8-ctx->ireqread),
|
||||||
|
ctx->ibufmark, ctx->ireqread);
|
||||||
|
ctx->ipayloadlen = ntoh64(ctx->ipayloadlen);
|
||||||
|
ctx->ibufmark += ctx->ireqread;
|
||||||
|
if(ctx->ireqread == 8) {
|
||||||
|
if(ctx->ipayloadlen < (1 << 16) ||
|
||||||
|
ctx->ipayloadlen & (1ull << 63)) {
|
||||||
|
return WSLAY_ERR_PROTO;
|
||||||
|
}
|
||||||
|
} else if(ctx->ipayloadlen < 126) {
|
||||||
|
return WSLAY_ERR_PROTO;
|
||||||
|
}
|
||||||
|
if(ctx->imask) {
|
||||||
|
ctx->istate = RECV_MASKKEY;
|
||||||
|
ctx->ireqread = 4;
|
||||||
|
} else {
|
||||||
|
ctx->istate = RECV_PAYLOAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ctx->istate == RECV_MASKKEY) {
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
|
||||||
|
if((r = wslay_recv(ctx)) <= 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) < ctx->ireqread) {
|
||||||
|
return WSLAY_ERR_WANT_READ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(ctx->imaskkey, ctx->ibufmark, 4);
|
||||||
|
ctx->ibufmark += 4;
|
||||||
|
ctx->istate = RECV_PAYLOAD;
|
||||||
|
}
|
||||||
|
if(ctx->istate == RECV_PAYLOAD) {
|
||||||
|
uint8_t *readlimit, *readmark;
|
||||||
|
uint64_t rempayloadlen = ctx->ipayloadlen-ctx->ipayloadoff;
|
||||||
|
if(WSLAY_AVAIL_IBUF(ctx) == 0 && rempayloadlen > 0) {
|
||||||
|
if((r = wslay_recv(ctx)) <= 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readmark = ctx->ibufmark;
|
||||||
|
readlimit = WSLAY_AVAIL_IBUF(ctx) < rempayloadlen ?
|
||||||
|
ctx->ibuflimit : ctx->ibufmark+rempayloadlen;
|
||||||
|
if(ctx->imask) {
|
||||||
|
for(; ctx->ibufmark != readlimit;
|
||||||
|
++ctx->ibufmark, ++ctx->ipayloadoff) {
|
||||||
|
ctx->ibufmark[0] ^= ctx->imaskkey[ctx->ipayloadoff % 4];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx->ibufmark = readlimit;
|
||||||
|
ctx->ipayloadoff += readlimit-readmark;
|
||||||
|
}
|
||||||
|
iocb->fin = ctx->iom.fin;
|
||||||
|
iocb->rsv = ctx->iom.rsv;
|
||||||
|
iocb->opcode = ctx->iom.opcode;
|
||||||
|
iocb->payload_length = ctx->ipayloadlen;
|
||||||
|
iocb->mask = ctx->imask;
|
||||||
|
iocb->data = readmark;
|
||||||
|
iocb->data_length = ctx->ibufmark-readmark;
|
||||||
|
if(ctx->ipayloadlen == ctx->ipayloadoff) {
|
||||||
|
ctx->istate = RECV_HEADER1;
|
||||||
|
ctx->ireqread = 2;
|
||||||
|
}
|
||||||
|
return iocb->data_length;
|
||||||
|
}
|
||||||
|
return WSLAY_ERR_INVALID_ARGUMENT;
|
||||||
|
}
|
36
websocket/src/wslay/wslay_net.c
Normal file
36
websocket/src/wslay/wslay_net.c
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "wslay_net.h"
|
||||||
|
|
||||||
|
#ifndef WORDS_BIGENDIAN
|
||||||
|
|
||||||
|
uint64_t wslay_byteswap64(uint64_t x)
|
||||||
|
{
|
||||||
|
uint64_t u = ntohl(x & 0xffffffffllu);
|
||||||
|
uint64_t l = ntohl(x >> 32);
|
||||||
|
return (u << 32) | l;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !WORDS_BIGENDIAN */
|
117
websocket/src/wslay/wslay_queue.c
Normal file
117
websocket/src/wslay/wslay_queue.c
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "wslay_queue.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
struct wslay_queue* wslay_queue_new(void)
|
||||||
|
{
|
||||||
|
struct wslay_queue *queue = (struct wslay_queue*)malloc
|
||||||
|
(sizeof(struct wslay_queue));
|
||||||
|
if(!queue) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
queue->top = queue->tail = NULL;
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wslay_queue_free(struct wslay_queue *queue)
|
||||||
|
{
|
||||||
|
if(!queue) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
struct wslay_queue_cell *p = queue->top;
|
||||||
|
while(p) {
|
||||||
|
struct wslay_queue_cell *next = p->next;
|
||||||
|
free(p);
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
free(queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int wslay_queue_push(struct wslay_queue *queue, void *data)
|
||||||
|
{
|
||||||
|
struct wslay_queue_cell *new_cell = (struct wslay_queue_cell*)malloc
|
||||||
|
(sizeof(struct wslay_queue_cell));
|
||||||
|
if(!new_cell) {
|
||||||
|
return WSLAY_ERR_NOMEM;
|
||||||
|
}
|
||||||
|
new_cell->data = data;
|
||||||
|
new_cell->next = NULL;
|
||||||
|
if(queue->tail) {
|
||||||
|
queue->tail->next = new_cell;
|
||||||
|
queue->tail = new_cell;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
queue->top = queue->tail = new_cell;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wslay_queue_push_front(struct wslay_queue *queue, void *data)
|
||||||
|
{
|
||||||
|
struct wslay_queue_cell *new_cell = (struct wslay_queue_cell*)malloc
|
||||||
|
(sizeof(struct wslay_queue_cell));
|
||||||
|
if(!new_cell) {
|
||||||
|
return WSLAY_ERR_NOMEM;
|
||||||
|
}
|
||||||
|
new_cell->data = data;
|
||||||
|
new_cell->next = queue->top;
|
||||||
|
queue->top = new_cell;
|
||||||
|
if(!queue->tail) {
|
||||||
|
queue->tail = queue->top;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wslay_queue_pop(struct wslay_queue *queue)
|
||||||
|
{
|
||||||
|
struct wslay_queue_cell *top = queue->top;
|
||||||
|
assert(top);
|
||||||
|
queue->top = top->next;
|
||||||
|
if(top == queue->tail) {
|
||||||
|
queue->tail = NULL;
|
||||||
|
}
|
||||||
|
free(top);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* wslay_queue_top(struct wslay_queue *queue)
|
||||||
|
{
|
||||||
|
assert(queue->top);
|
||||||
|
return queue->top->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* wslay_queue_tail(struct wslay_queue *queue)
|
||||||
|
{
|
||||||
|
assert(queue->tail);
|
||||||
|
return queue->tail->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wslay_queue_empty(struct wslay_queue *queue)
|
||||||
|
{
|
||||||
|
return queue->top == NULL;
|
||||||
|
}
|
86
websocket/src/wslay/wslay_stack.c
Normal file
86
websocket/src/wslay/wslay_stack.c
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Wslay - The WebSocket Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "wslay_stack.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
struct wslay_stack* wslay_stack_new()
|
||||||
|
{
|
||||||
|
struct wslay_stack *stack = (struct wslay_stack*)malloc
|
||||||
|
(sizeof(struct wslay_stack));
|
||||||
|
if(!stack) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
stack->top = NULL;
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wslay_stack_free(struct wslay_stack *stack)
|
||||||
|
{
|
||||||
|
struct wslay_stack_cell *p;
|
||||||
|
if(!stack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p = stack->top;
|
||||||
|
while(p) {
|
||||||
|
struct wslay_stack_cell *next = p->next;
|
||||||
|
free(p);
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
free(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
int wslay_stack_push(struct wslay_stack *stack, void *data)
|
||||||
|
{
|
||||||
|
struct wslay_stack_cell *new_cell = (struct wslay_stack_cell*)malloc
|
||||||
|
(sizeof(struct wslay_stack_cell));
|
||||||
|
if(!new_cell) {
|
||||||
|
return WSLAY_ERR_NOMEM;
|
||||||
|
}
|
||||||
|
new_cell->data = data;
|
||||||
|
new_cell->next = stack->top;
|
||||||
|
stack->top = new_cell;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wslay_stack_pop(struct wslay_stack *stack)
|
||||||
|
{
|
||||||
|
struct wslay_stack_cell *top = stack->top;
|
||||||
|
assert(top);
|
||||||
|
stack->top = top->next;
|
||||||
|
free(top);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* wslay_stack_top(struct wslay_stack *stack)
|
||||||
|
{
|
||||||
|
assert(stack->top);
|
||||||
|
return stack->top->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wslay_stack_empty(struct wslay_stack *stack)
|
||||||
|
{
|
||||||
|
return stack->top == NULL;
|
||||||
|
}
|
151
websocket/src/wslay_callbacks.cpp
Normal file
151
websocket/src/wslay_callbacks.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#include "websocket.h"
|
||||||
|
|
||||||
|
#if defined(HAVE_WSLAY)
|
||||||
|
|
||||||
|
namespace dmWebsocket
|
||||||
|
{
|
||||||
|
|
||||||
|
const struct wslay_event_callbacks g_WslCallbacks = {
|
||||||
|
WSL_RecvCallback,
|
||||||
|
WSL_SendCallback,
|
||||||
|
WSL_GenmaskCallback,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
WSL_OnMsgRecvCallback
|
||||||
|
};
|
||||||
|
|
||||||
|
#define WSLAY_CASE(_X) case _X: return #_X;
|
||||||
|
|
||||||
|
const char* WSL_ResultToString(int err)
|
||||||
|
{
|
||||||
|
switch(err) {
|
||||||
|
WSLAY_CASE(WSLAY_ERR_WANT_READ);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_WANT_WRITE);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_PROTO);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_INVALID_ARGUMENT);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_INVALID_CALLBACK);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_NO_MORE_MSG);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_CALLBACK_FAILURE);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_WOULDBLOCK);
|
||||||
|
WSLAY_CASE(WSLAY_ERR_NOMEM);
|
||||||
|
default: return "Unknown error";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef WSLAY_CASE
|
||||||
|
|
||||||
|
|
||||||
|
int WSL_Init(wslay_event_context_ptr* ctx, ssize_t buffer_size, void* userctx)
|
||||||
|
{
|
||||||
|
// Currently only supports client implementation
|
||||||
|
int ret = -1;
|
||||||
|
ret = wslay_event_context_client_init(ctx, &g_WslCallbacks, userctx);
|
||||||
|
if (ret == 0)
|
||||||
|
wslay_event_config_set_max_recv_msg_length(*ctx, buffer_size);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WSL_Exit(wslay_event_context_ptr ctx)
|
||||||
|
{
|
||||||
|
wslay_event_context_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WSL_Close(wslay_event_context_ptr ctx)
|
||||||
|
{
|
||||||
|
const char* reason = "Client wants to close";
|
||||||
|
wslay_event_queue_close(ctx, 0, (const uint8_t*)reason, strlen(reason));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WSL_Poll(wslay_event_context_ptr ctx)
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
if ((r = wslay_event_recv(ctx)) != 0 || (r = wslay_event_send(ctx)) != 0) {
|
||||||
|
dmLogError("Websocket poll error: %s", WSL_ResultToString(r));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WSL_WantsExit(wslay_event_context_ptr ctx)
|
||||||
|
{
|
||||||
|
if ((wslay_event_get_close_sent(ctx) && wslay_event_get_close_received(ctx))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *user_data)
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
||||||
|
|
||||||
|
int r = -1; // received bytes if >=0, error if < 0
|
||||||
|
|
||||||
|
dmSocket::Result socket_result = Receive(conn, buf, len, &r);
|
||||||
|
|
||||||
|
if (dmSocket::RESULT_OK == socket_result && r == 0)
|
||||||
|
socket_result = dmSocket::RESULT_WOULDBLOCK;
|
||||||
|
|
||||||
|
if (dmSocket::RESULT_OK != socket_result)
|
||||||
|
{
|
||||||
|
if (socket_result == dmSocket::RESULT_WOULDBLOCK || socket_result == dmSocket::RESULT_TRY_AGAIN) {
|
||||||
|
wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data)
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
||||||
|
|
||||||
|
int sent_bytes = 0;
|
||||||
|
dmSocket::Result socket_result = Send(conn, (const char*)data, len, &sent_bytes);
|
||||||
|
|
||||||
|
if (socket_result != dmSocket::RESULT_OK)
|
||||||
|
{
|
||||||
|
if (socket_result == dmSocket::RESULT_WOULDBLOCK || socket_result == dmSocket::RESULT_TRY_AGAIN)
|
||||||
|
wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
|
||||||
|
else
|
||||||
|
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return (ssize_t)sent_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data)
|
||||||
|
{
|
||||||
|
WebsocketConnection* conn = (WebsocketConnection*)user_data;
|
||||||
|
if (arg->opcode == WSLAY_TEXT_FRAME || arg->opcode == WSLAY_BINARY_FRAME)
|
||||||
|
{
|
||||||
|
if (arg->msg_length >= conn->m_BufferCapacity)
|
||||||
|
conn->m_Buffer = (char*)realloc(conn->m_Buffer, arg->msg_length + 1);
|
||||||
|
memcpy(conn->m_Buffer, arg->msg, arg->msg_length);
|
||||||
|
conn->m_BufferSize = arg->msg_length;
|
||||||
|
conn->m_HasMessage = 1;
|
||||||
|
|
||||||
|
} else if (arg->opcode == WSLAY_CONNECTION_CLOSE)
|
||||||
|
{
|
||||||
|
// TODO: Store the reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
int WSL_GenmaskCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) {
|
||||||
|
pcg32_random_t rnd;
|
||||||
|
pcg32_srandom_r(&rnd, dmTime::GetTime(), 31452);
|
||||||
|
for (unsigned int i = 0; i < len; i++) {
|
||||||
|
buf[i] = (uint8_t)(pcg32_random_r(&rnd) & 0xFF);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#endif // HAVE_WSLAY
|
Loading…
x
Reference in New Issue
Block a user