Merge pull request #1 from defold/initial

Add first version of websocket extension
This commit is contained in:
Mathias Westerdahl 2020-09-03 08:45:42 +02:00 committed by GitHub
commit 2bddcac495
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 5256 additions and 153 deletions

6
.gitignore vendored
View File

@ -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

57
docs/index.md Normal file
View 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/

View File

@ -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
View 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

View File

@ -0,0 +1,3 @@
font: "/builtins/fonts/vera_mo_bd.ttf"
material: "/builtins/fonts/font.material"
size: 15

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

6
examples/assets/ui.atlas Normal file
View File

@ -0,0 +1,6 @@
images {
image: "/examples/assets/images/green_button08.png"
}
margin: 0
extrude_borders: 2
inner_padding: 0

View 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
View 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

View 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

View File

@ -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

View File

@ -1,2 +0,0 @@
# C++ symbol in your extension
name: "MyExtension"

View File

@ -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)

View 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
View File

@ -0,0 +1,8 @@
# C++ symbol in your extension
name: "Websocket"
platforms:
common:
context:
includes: ["upload/websocket/include/wslay"]
defines: ["HAVE_CONFIG_H"]

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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
View 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
View 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);
}
}

View 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

View 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
View 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
View 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
View 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);
}

File diff suppressed because it is too large Load Diff

View 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;
}

View 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 */

View 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;
}

View 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;
}

View 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