diff --git a/.gitignore b/.gitignore index a32d29f..7d5711e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.internal /build +/bundle* .externalToolBuilders .DS_Store Thumbs.db @@ -7,4 +8,7 @@ Thumbs.db *.pyc .project .cproject -builtins \ No newline at end of file +builtins +lws_source +lws_build +*.profraw \ No newline at end of file diff --git a/docs/extension-websocket/archive/game.dmanifest0 b/docs/extension-websocket/archive/game.dmanifest0 index d376dc3..4b3eb2b 100644 Binary files a/docs/extension-websocket/archive/game.dmanifest0 and b/docs/extension-websocket/archive/game.dmanifest0 differ diff --git a/docs/extension-websocket/archive/game.public.der0 b/docs/extension-websocket/archive/game.public.der0 index eb255c5..d7f9da5 100644 Binary files a/docs/extension-websocket/archive/game.public.der0 and b/docs/extension-websocket/archive/game.public.der0 differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0606a94 --- /dev/null +++ b/docs/index.md @@ -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/ \ No newline at end of file diff --git a/example/example.script b/example/example.script deleted file mode 100644 index 53b4483..0000000 --- a/example/example.script +++ /dev/null @@ -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 diff --git a/examples/assets/button.gui b/examples/assets/button.gui new file mode 100644 index 0000000..daf8d2e --- /dev/null +++ b/examples/assets/button.gui @@ -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 diff --git a/examples/assets/fonts/example.font b/examples/assets/fonts/example.font new file mode 100644 index 0000000..4929518 --- /dev/null +++ b/examples/assets/fonts/example.font @@ -0,0 +1,3 @@ +font: "/builtins/fonts/vera_mo_bd.ttf" +material: "/builtins/fonts/font.material" +size: 15 diff --git a/examples/assets/images/green_button08.png b/examples/assets/images/green_button08.png new file mode 100755 index 0000000..b9d40d0 Binary files /dev/null and b/examples/assets/images/green_button08.png differ diff --git a/examples/assets/ui.atlas b/examples/assets/ui.atlas new file mode 100644 index 0000000..8e003c0 --- /dev/null +++ b/examples/assets/ui.atlas @@ -0,0 +1,6 @@ +images { + image: "/examples/assets/images/green_button08.png" +} +margin: 0 +extrude_borders: 2 +inner_padding: 0 diff --git a/example/example.collection b/examples/websocket.collection old mode 100755 new mode 100644 similarity index 83% rename from example/example.collection rename to examples/websocket.collection index ff3608f..dc2a6aa --- a/example/example.collection +++ b/examples/websocket.collection @@ -1,10 +1,10 @@ -name: "main" +name: "default" scale_along_z: 0 embedded_instances { id: "go" data: "components {\n" - " id: \"example\"\n" - " component: \"/example/example.script\"\n" + " id: \"gui\"\n" + " component: \"/examples/websocket.gui\"\n" " position {\n" " x: 0.0\n" " y: 0.0\n" diff --git a/examples/websocket.gui b/examples/websocket.gui new file mode 100644 index 0000000..aecb3b6 --- /dev/null +++ b/examples/websocket.gui @@ -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: "" + 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 diff --git a/examples/websocket.gui_script b/examples/websocket.gui_script new file mode 100644 index 0000000..abbaabc --- /dev/null +++ b/examples/websocket.gui_script @@ -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 diff --git a/game.project b/game.project index 814f6bd..bbf4266 100755 --- a/game.project +++ b/game.project @@ -1,16 +1,17 @@ [bootstrap] -main_collection = /example/example.collectionc +main_collection = /examples/websocket.collectionc [script] shared_state = 1 [display] -width = 960 -height = 640 +width = 640 +height = 960 [project] -title = myextension +title = extension-websocket +_dependencies = https://github.com/GameAnalytics/defold-openssl/archive/1.0.0.zip [library] -include_dirs = myextension +include_dirs = websocket diff --git a/myextension/ext.manifest b/myextension/ext.manifest deleted file mode 100644 index 23bb57f..0000000 --- a/myextension/ext.manifest +++ /dev/null @@ -1,2 +0,0 @@ -# C++ symbol in your extension -name: "MyExtension" diff --git a/myextension/src/myextension.cpp b/myextension/src/myextension.cpp deleted file mode 100644 index 3cb4010..0000000 --- a/myextension/src/myextension.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// myextension.cpp -// Extension lib defines -#define LIB_NAME "MyExtension" -#define MODULE_NAME "myextension" - -// include the Defold SDK -#include - -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) diff --git a/websocket/api/api.script_api b/websocket/api/api.script_api new file mode 100644 index 0000000..b2c8b86 --- /dev/null +++ b/websocket/api/api.script_api @@ -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 diff --git a/websocket/ext.manifest b/websocket/ext.manifest new file mode 100644 index 0000000..523ed73 --- /dev/null +++ b/websocket/ext.manifest @@ -0,0 +1,8 @@ +# C++ symbol in your extension +name: "Websocket" + +platforms: + common: + context: + includes: ["upload/websocket/include/wslay"] + defines: ["HAVE_CONFIG_H"] diff --git a/websocket/include/wslay/config.h b/websocket/include/wslay/config.h new file mode 100644 index 0000000..242cefa --- /dev/null +++ b/websocket/include/wslay/config.h @@ -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 */ diff --git a/websocket/include/wslay/wslay.h b/websocket/include/wslay/wslay.h new file mode 100644 index 0000000..2fde81a --- /dev/null +++ b/websocket/include/wslay/wslay.h @@ -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 +#include +#include + + +/* + * 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 +#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 */ diff --git a/websocket/include/wslay/wslay_event.h b/websocket/include/wslay/wslay_event.h new file mode 100644 index 0000000..36feb90 --- /dev/null +++ b/websocket/include/wslay/wslay_event.h @@ -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 +#endif /* HAVE_CONFIG_H */ + +#include + +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/**/ *send_queue; + /* Queue for control frames */ + struct wslay_queue/**/ *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 */ diff --git a/websocket/include/wslay/wslay_frame.h b/websocket/include/wslay/wslay_frame.h new file mode 100644 index 0000000..6a75858 --- /dev/null +++ b/websocket/include/wslay/wslay_frame.h @@ -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 +#endif /* HAVE_CONFIG_H */ + +#include + +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 */ diff --git a/websocket/include/wslay/wslay_net.h b/websocket/include/wslay/wslay_net.h new file mode 100644 index 0000000..2310870 --- /dev/null +++ b/websocket/include/wslay/wslay_net.h @@ -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 +#endif /* HAVE_CONFIG_H */ + +#include + +#ifdef HAVE_ARPA_INET_H +# include +#endif /* HAVE_ARPA_INET_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +/* For Mingw build */ +#ifdef HAVE_WINSOCK2_H +# include +#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 */ diff --git a/websocket/include/wslay/wslay_queue.h b/websocket/include/wslay/wslay_queue.h new file mode 100644 index 0000000..55e78a0 --- /dev/null +++ b/websocket/include/wslay/wslay_queue.h @@ -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 + +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 */ diff --git a/websocket/include/wslay/wslay_stack.h b/websocket/include/wslay/wslay_stack.h new file mode 100644 index 0000000..16e4e96 --- /dev/null +++ b/websocket/include/wslay/wslay_stack.h @@ -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 + +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 */ diff --git a/websocket/include/wslay/wslayver.h b/websocket/include/wslay/wslayver.h new file mode 100644 index 0000000..e153b07 --- /dev/null +++ b/websocket/include/wslay/wslayver.h @@ -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 */ diff --git a/websocket/src/handshake.cpp b/websocket/src/handshake.cpp new file mode 100644 index 0000000..0b81930 --- /dev/null +++ b/websocket/src/handshake.cpp @@ -0,0 +1,239 @@ +#include "websocket.h" +#include + +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 " + 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 \ No newline at end of file diff --git a/websocket/src/pcg.cpp b/websocket/src/pcg.cpp new file mode 100644 index 0000000..60bcf34 --- /dev/null +++ b/websocket/src/pcg.cpp @@ -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); + } +} \ No newline at end of file diff --git a/websocket/src/script_util.cpp b/websocket/src/script_util.cpp new file mode 100644 index 0000000..2568f62 --- /dev/null +++ b/websocket/src/script_util.cpp @@ -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 \ No newline at end of file diff --git a/websocket/src/script_util.h b/websocket/src/script_util.h new file mode 100644 index 0000000..5d825a0 --- /dev/null +++ b/websocket/src/script_util.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +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 \ No newline at end of file diff --git a/websocket/src/socket.cpp b/websocket/src/socket.cpp new file mode 100644 index 0000000..2935c45 --- /dev/null +++ b/websocket/src/socket.cpp @@ -0,0 +1,56 @@ +#include +#include +#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 \ No newline at end of file diff --git a/websocket/src/websocket.cpp b/websocket/src/websocket.cpp new file mode 100644 index 0000000..ce8ff00 --- /dev/null +++ b/websocket/src/websocket.cpp @@ -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 +#include +#include + +namespace dmWebsocket { + + +struct WebsocketContext +{ + uint64_t m_BufferSize; + int m_Timeout; + dmArray 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 diff --git a/websocket/src/websocket.h b/websocket/src/websocket.h new file mode 100644 index 0000000..81f8673 --- /dev/null +++ b/websocket/src/websocket.h @@ -0,0 +1,124 @@ +#pragma once + +// include the Defold SDK +#include + +#if !defined(__EMSCRIPTEN__) + #define HAVE_WSLAY 1 +#endif + +#if defined(HAVE_WSLAY) + #include +#endif + +#include +#include +#include +#include + +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); +} + + + + + + + + + + + diff --git a/websocket/src/wslay/wslay_event.c b/websocket/src/wslay/wslay_event.c new file mode 100644 index 0000000..4aa407a --- /dev/null +++ b/websocket/src/wslay/wslay_event.c @@ -0,0 +1,1027 @@ +/* + * 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_event.h" + +#include +#include +#include + +#include "wslay_queue.h" +#include "wslay_frame.h" +#include "wslay_net.h" +/* Start of utf8 dfa */ +/* Copyright (c) 2008-2010 Bjoern Hoehrmann + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + * + * Copyright (c) 2008-2009 Bjoern Hoehrmann + * + * 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. + */ +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +static const uint8_t utf8d[] = { + /* + * The first part of the table maps bytes to character classes that + * to reduce the size of the transition table and create bitmasks. + */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + /* + * The second part is a transition table that maps a combination + * of a state of the automaton and a character class to a state. + */ + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static uint32_t +decode(uint32_t* state, uint32_t* codep, uint32_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +/* End of utf8 dfa */ + +static ssize_t wslay_event_frame_recv_callback(uint8_t *buf, size_t len, + int flags, void *user_data) +{ + struct wslay_event_frame_user_data *e = + (struct wslay_event_frame_user_data*)user_data; + return e->ctx->callbacks.recv_callback(e->ctx, buf, len, flags, e->user_data); +} + +static ssize_t wslay_event_frame_send_callback(const uint8_t *data, size_t len, + int flags, void *user_data) +{ + struct wslay_event_frame_user_data *e = + (struct wslay_event_frame_user_data*)user_data; + return e->ctx->callbacks.send_callback(e->ctx, data, len, flags, + e->user_data); +} + +static int wslay_event_frame_genmask_callback(uint8_t *buf, size_t len, + void *user_data) +{ + struct wslay_event_frame_user_data *e = + (struct wslay_event_frame_user_data*)user_data; + return e->ctx->callbacks.genmask_callback(e->ctx, buf, len, e->user_data); +} + +static int wslay_event_byte_chunk_init +(struct wslay_event_byte_chunk **chunk, size_t len) +{ + *chunk = (struct wslay_event_byte_chunk*)malloc + (sizeof(struct wslay_event_byte_chunk)); + if(*chunk == NULL) { + return WSLAY_ERR_NOMEM; + } + memset(*chunk, 0, sizeof(struct wslay_event_byte_chunk)); + if(len) { + (*chunk)->data = (uint8_t*)malloc(len); + if((*chunk)->data == NULL) { + free(*chunk); + return WSLAY_ERR_NOMEM; + } + (*chunk)->data_length = len; + } + return 0; +} + +static void wslay_event_byte_chunk_free(struct wslay_event_byte_chunk *c) +{ + if(!c) { + return; + } + free(c->data); + free(c); +} + +static void wslay_event_byte_chunk_copy(struct wslay_event_byte_chunk *c, + size_t off, + const uint8_t *data, size_t data_length) +{ + memcpy(c->data+off, data, data_length); +} + +static void wslay_event_imsg_set(struct wslay_event_imsg *m, + uint8_t fin, uint8_t rsv, uint8_t opcode) +{ + m->fin = fin; + m->rsv = rsv; + m->opcode = opcode; + m->msg_length = 0; +} + +static void wslay_event_imsg_chunks_free(struct wslay_event_imsg *m) +{ + if(!m->chunks) { + return; + } + while(!wslay_queue_empty(m->chunks)) { + wslay_event_byte_chunk_free((struct wslay_event_byte_chunk*)wslay_queue_top(m->chunks)); + wslay_queue_pop(m->chunks); + } +} + +static void wslay_event_imsg_reset(struct wslay_event_imsg *m) +{ + m->opcode = 0xffu; + m->utf8state = UTF8_ACCEPT; + wslay_event_imsg_chunks_free(m); +} + +static int wslay_event_imsg_append_chunk(struct wslay_event_imsg *m, size_t len) +{ + if(len == 0) { + return 0; + } else { + int r; + struct wslay_event_byte_chunk *chunk; + if((r = wslay_event_byte_chunk_init(&chunk, len)) != 0) { + return r; + } + if((r = wslay_queue_push(m->chunks, chunk)) != 0) { + return r; + } + m->msg_length += len; + return 0; + } +} + +static int wslay_event_omsg_non_fragmented_init +(struct wslay_event_omsg **m, uint8_t opcode, uint8_t rsv, + const uint8_t *msg, size_t msg_length) +{ + *m = (struct wslay_event_omsg*)malloc(sizeof(struct wslay_event_omsg)); + if(!*m) { + return WSLAY_ERR_NOMEM; + } + memset(*m, 0, sizeof(struct wslay_event_omsg)); + (*m)->fin = 1; + (*m)->opcode = opcode; + (*m)->rsv = rsv; + (*m)->type = WSLAY_NON_FRAGMENTED; + if(msg_length) { + (*m)->data = (uint8_t*)malloc(msg_length); + if(!(*m)->data) { + free(*m); + return WSLAY_ERR_NOMEM; + } + memcpy((*m)->data, msg, msg_length); + (*m)->data_length = msg_length; + } + return 0; +} + +static int wslay_event_omsg_fragmented_init +(struct wslay_event_omsg **m, uint8_t opcode, uint8_t rsv, + const union wslay_event_msg_source source, + wslay_event_fragmented_msg_callback read_callback) +{ + *m = (struct wslay_event_omsg*)malloc(sizeof(struct wslay_event_omsg)); + if(!*m) { + return WSLAY_ERR_NOMEM; + } + memset(*m, 0, sizeof(struct wslay_event_omsg)); + (*m)->opcode = opcode; + (*m)->rsv = rsv; + (*m)->type = WSLAY_FRAGMENTED; + (*m)->source = source; + (*m)->read_callback = read_callback; + return 0; +} + +static void wslay_event_omsg_free(struct wslay_event_omsg *m) +{ + if(!m) { + return; + } + free(m->data); + free(m); +} + +static uint8_t* wslay_event_flatten_queue(struct wslay_queue *queue, size_t len) +{ + if(len == 0) { + return NULL; + } else { + size_t off = 0; + uint8_t *buf = (uint8_t*)malloc(len); + if(!buf) { + return NULL; + } + while(!wslay_queue_empty(queue)) { + struct wslay_event_byte_chunk *chunk = (struct wslay_event_byte_chunk *)wslay_queue_top(queue); + memcpy(buf+off, chunk->data, chunk->data_length); + off += chunk->data_length; + wslay_event_byte_chunk_free(chunk); + wslay_queue_pop(queue); + assert(off <= len); + } + assert(len == off); + return buf; + } +} + +static int wslay_event_is_msg_queueable(wslay_event_context_ptr ctx) +{ + return ctx->write_enabled && (ctx->close_status & WSLAY_CLOSE_QUEUED) == 0; +} + +int wslay_event_queue_close(wslay_event_context_ptr ctx, uint16_t status_code, + const uint8_t *reason, size_t reason_length) +{ + if(!wslay_event_is_msg_queueable(ctx)) { + return WSLAY_ERR_NO_MORE_MSG; + } else if(reason_length > 123) { + return WSLAY_ERR_INVALID_ARGUMENT; + } else { + uint8_t msg[128]; + size_t msg_length; + struct wslay_event_msg arg; + uint16_t ncode; + int r; + if(status_code == 0) { + msg_length = 0; + } else { + ncode = htons(status_code); + memcpy(msg, &ncode, 2); + if(reason_length) { + memcpy(msg+2, reason, reason_length); + } + msg_length = reason_length+2; + } + arg.opcode = WSLAY_CONNECTION_CLOSE; + arg.msg = msg; + arg.msg_length = msg_length; + r = wslay_event_queue_msg(ctx, &arg); + if(r == 0) { + ctx->close_status |= WSLAY_CLOSE_QUEUED; + } + return r; + } +} + +static int wslay_event_queue_close_wrapper +(wslay_event_context_ptr ctx, uint16_t status_code, + const uint8_t *reason, size_t reason_length) +{ + int r; + ctx->read_enabled = 0; + if((r = wslay_event_queue_close(ctx, status_code, reason, reason_length)) && + r != WSLAY_ERR_NO_MORE_MSG) { + return r; + } + return 0; +} + +static int wslay_event_verify_rsv_bits(wslay_event_context_ptr ctx, uint8_t rsv) +{ + return ((rsv & ~ctx->allowed_rsv_bits) == 0); +} + +int wslay_event_queue_msg(wslay_event_context_ptr ctx, + const struct wslay_event_msg *arg) +{ + return wslay_event_queue_msg_ex(ctx, arg, WSLAY_RSV_NONE); +} + +int wslay_event_queue_msg_ex(wslay_event_context_ptr ctx, + const struct wslay_event_msg *arg, uint8_t rsv) +{ + int r; + struct wslay_event_omsg *omsg; + if(!wslay_event_is_msg_queueable(ctx)) { + return WSLAY_ERR_NO_MORE_MSG; + } + /* RSV1 is not allowed for control frames */ + if((wslay_is_ctrl_frame(arg->opcode) && + (arg->msg_length > 125 || wslay_get_rsv1(rsv))) + || !wslay_event_verify_rsv_bits(ctx, rsv)) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if((r = wslay_event_omsg_non_fragmented_init + (&omsg, arg->opcode, rsv, arg->msg, arg->msg_length)) != 0) { + return r; + } + if(wslay_is_ctrl_frame(arg->opcode)) { + if((r = wslay_queue_push(ctx->send_ctrl_queue, omsg)) != 0) { + return r; + } + } else { + if((r = wslay_queue_push(ctx->send_queue, omsg)) != 0) { + return r; + } + } + ++ctx->queued_msg_count; + ctx->queued_msg_length += arg->msg_length; + return 0; +} + +int wslay_event_queue_fragmented_msg +(wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg) +{ + return wslay_event_queue_fragmented_msg_ex(ctx, arg, WSLAY_RSV_NONE); +} + +int wslay_event_queue_fragmented_msg_ex(wslay_event_context_ptr ctx, + const struct wslay_event_fragmented_msg *arg, uint8_t rsv) +{ + int r; + struct wslay_event_omsg *omsg; + if(!wslay_event_is_msg_queueable(ctx)) { + return WSLAY_ERR_NO_MORE_MSG; + } + if(wslay_is_ctrl_frame(arg->opcode) || + !wslay_event_verify_rsv_bits(ctx, rsv)) { + return WSLAY_ERR_INVALID_ARGUMENT; + } + if((r = wslay_event_omsg_fragmented_init + (&omsg, arg->opcode, rsv, arg->source, arg->read_callback)) != 0) { + return r; + } + if((r = wslay_queue_push(ctx->send_queue, omsg)) != 0) { + return r; + } + ++ctx->queued_msg_count; + return 0; +} + +void wslay_event_config_set_callbacks +(wslay_event_context_ptr ctx, const struct wslay_event_callbacks *callbacks) +{ + ctx->callbacks = *callbacks; +} + +static int wslay_event_context_init +(wslay_event_context_ptr *ctx, + const struct wslay_event_callbacks *callbacks, + void *user_data) +{ + int i, r; + struct wslay_frame_callbacks frame_callbacks = { + wslay_event_frame_send_callback, + wslay_event_frame_recv_callback, + wslay_event_frame_genmask_callback + }; + *ctx = (wslay_event_context_ptr)malloc(sizeof(struct wslay_event_context)); + if(!*ctx) { + return WSLAY_ERR_NOMEM; + } + memset(*ctx, 0, sizeof(struct wslay_event_context)); + wslay_event_config_set_callbacks(*ctx, callbacks); + (*ctx)->user_data = user_data; + (*ctx)->frame_user_data.ctx = *ctx; + (*ctx)->frame_user_data.user_data = user_data; + if((r = wslay_frame_context_init(&(*ctx)->frame_ctx, &frame_callbacks, + &(*ctx)->frame_user_data)) != 0) { + wslay_event_context_free(*ctx); + return r; + } + (*ctx)->read_enabled = (*ctx)->write_enabled = 1; + (*ctx)->send_queue = wslay_queue_new(); + if(!(*ctx)->send_queue) { + wslay_event_context_free(*ctx); + return WSLAY_ERR_NOMEM; + } + (*ctx)->send_ctrl_queue = wslay_queue_new(); + if(!(*ctx)->send_ctrl_queue) { + wslay_event_context_free(*ctx); + return WSLAY_ERR_NOMEM; + } + (*ctx)->queued_msg_count = 0; + (*ctx)->queued_msg_length = 0; + for(i = 0; i < 2; ++i) { + wslay_event_imsg_reset(&(*ctx)->imsgs[i]); + (*ctx)->imsgs[i].chunks = wslay_queue_new(); + if(!(*ctx)->imsgs[i].chunks) { + wslay_event_context_free(*ctx); + return WSLAY_ERR_NOMEM; + } + } + (*ctx)->imsg = &(*ctx)->imsgs[0]; + (*ctx)->obufmark = (*ctx)->obuflimit = (*ctx)->obuf; + (*ctx)->status_code_sent = WSLAY_CODE_ABNORMAL_CLOSURE; + (*ctx)->status_code_recv = WSLAY_CODE_ABNORMAL_CLOSURE; + (*ctx)->max_recv_msg_length = (1u << 31)-1; + return 0; +} + +int wslay_event_context_server_init +(wslay_event_context_ptr *ctx, + const struct wslay_event_callbacks *callbacks, + void *user_data) +{ + int r; + if((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) { + return r; + } + (*ctx)->server = 1; + return 0; +} + +int wslay_event_context_client_init +(wslay_event_context_ptr *ctx, + const struct wslay_event_callbacks *callbacks, + void *user_data) +{ + int r; + if((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) { + return r; + } + (*ctx)->server = 0; + return 0; +} + +void wslay_event_context_free(wslay_event_context_ptr ctx) +{ + int i; + if(!ctx) { + return; + } + for(i = 0; i < 2; ++i) { + wslay_event_imsg_chunks_free(&ctx->imsgs[i]); + wslay_queue_free(ctx->imsgs[i].chunks); + } + if(ctx->send_queue) { + while(!wslay_queue_empty(ctx->send_queue)) { + wslay_event_omsg_free((struct wslay_event_omsg *)wslay_queue_top(ctx->send_queue)); + wslay_queue_pop(ctx->send_queue); + } + wslay_queue_free(ctx->send_queue); + } + if(ctx->send_ctrl_queue) { + while(!wslay_queue_empty(ctx->send_ctrl_queue)) { + wslay_event_omsg_free((struct wslay_event_omsg *)wslay_queue_top(ctx->send_ctrl_queue)); + wslay_queue_pop(ctx->send_ctrl_queue); + } + wslay_queue_free(ctx->send_ctrl_queue); + } + wslay_frame_context_free(ctx->frame_ctx); + wslay_event_omsg_free(ctx->omsg); + free(ctx); +} + +static void wslay_event_call_on_frame_recv_start_callback +(wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb) +{ + if(ctx->callbacks.on_frame_recv_start_callback) { + struct wslay_event_on_frame_recv_start_arg arg; + arg.fin = iocb->fin; + arg.rsv = iocb->rsv; + arg.opcode = iocb->opcode; + arg.payload_length = iocb->payload_length; + ctx->callbacks.on_frame_recv_start_callback(ctx, &arg, ctx->user_data); + } +} + +static void wslay_event_call_on_frame_recv_chunk_callback +(wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb) +{ + if(ctx->callbacks.on_frame_recv_chunk_callback) { + struct wslay_event_on_frame_recv_chunk_arg arg; + arg.data = iocb->data; + arg.data_length = iocb->data_length; + ctx->callbacks.on_frame_recv_chunk_callback(ctx, &arg, ctx->user_data); + } +} + +static void wslay_event_call_on_frame_recv_end_callback +(wslay_event_context_ptr ctx) +{ + if(ctx->callbacks.on_frame_recv_end_callback) { + ctx->callbacks.on_frame_recv_end_callback(ctx, ctx->user_data); + } +} + +static int wslay_event_is_valid_status_code(uint16_t status_code) +{ + return (1000 <= status_code && status_code <= 1011 && + status_code != 1004 && status_code != 1005 && status_code != 1006) || + (3000 <= status_code && status_code <= 4999); +} + +static int wslay_event_config_get_no_buffering(wslay_event_context_ptr ctx) +{ + return (ctx->config & WSLAY_CONFIG_NO_BUFFERING) > 0; +} + +int wslay_event_recv(wslay_event_context_ptr ctx) +{ + struct wslay_frame_iocb iocb; + ssize_t r; + while(ctx->read_enabled) { + memset(&iocb, 0, sizeof(iocb)); + r = wslay_frame_recv(ctx->frame_ctx, &iocb); + if(r >= 0) { + int new_frame = 0; + /* RSV1 is not allowed on control and continuation frames */ + if((!wslay_event_verify_rsv_bits(ctx, iocb.rsv)) || + (wslay_get_rsv1(iocb.rsv) && (wslay_is_ctrl_frame(iocb.opcode) || + iocb.opcode == WSLAY_CONTINUATION_FRAME)) || + (ctx->server && !iocb.mask) || (!ctx->server && iocb.mask)) { + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return r; + } + break; + } + if(ctx->imsg->opcode == 0xffu) { + if(iocb.opcode == WSLAY_TEXT_FRAME || + iocb.opcode == WSLAY_BINARY_FRAME || + iocb.opcode == WSLAY_CONNECTION_CLOSE || + iocb.opcode == WSLAY_PING || + iocb.opcode == WSLAY_PONG) { + wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode); + new_frame = 1; + } else { + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return r; + } + break; + } + } else if(ctx->ipayloadlen == 0 && ctx->ipayloadoff == 0) { + if(iocb.opcode == WSLAY_CONTINUATION_FRAME) { + ctx->imsg->fin = iocb.fin; + } else if(iocb.opcode == WSLAY_CONNECTION_CLOSE || + iocb.opcode == WSLAY_PING || + iocb.opcode == WSLAY_PONG) { + ctx->imsg = &ctx->imsgs[1]; + wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode); + } else { + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return r; + } + break; + } + new_frame = 1; + } + if(new_frame) { + if(ctx->imsg->msg_length+iocb.payload_length > + ctx->max_recv_msg_length) { + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_MESSAGE_TOO_BIG, NULL, 0)) != 0) { + return r; + } + break; + } + ctx->ipayloadlen = iocb.payload_length; + wslay_event_call_on_frame_recv_start_callback(ctx, &iocb); + if(!wslay_event_config_get_no_buffering(ctx) || + wslay_is_ctrl_frame(iocb.opcode)) { + if((r = wslay_event_imsg_append_chunk(ctx->imsg, + iocb.payload_length)) != 0) { + ctx->read_enabled = 0; + return r; + } + } + } + /* If RSV1 bit is set then it is too early for utf-8 validation */ + if((!wslay_get_rsv1(ctx->imsg->rsv) && + ctx->imsg->opcode == WSLAY_TEXT_FRAME) || + ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) { + size_t i; + if(ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) { + i = 2; + } else { + i = 0; + } + for(; i < iocb.data_length; ++i) { + uint32_t codep; + if(decode(&ctx->imsg->utf8state, &codep, + iocb.data[i]) == UTF8_REJECT) { + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != 0) { + return r; + } + break; + } + } + } + if(ctx->imsg->utf8state == UTF8_REJECT) { + break; + } + wslay_event_call_on_frame_recv_chunk_callback(ctx, &iocb); + if(iocb.data_length > 0) { + if(!wslay_event_config_get_no_buffering(ctx) || + wslay_is_ctrl_frame(iocb.opcode)) { + struct wslay_event_byte_chunk *chunk; + chunk = (struct wslay_event_byte_chunk *)wslay_queue_tail(ctx->imsg->chunks); + wslay_event_byte_chunk_copy(chunk, ctx->ipayloadoff, + iocb.data, iocb.data_length); + } + ctx->ipayloadoff += iocb.data_length; + } + if(ctx->ipayloadoff == ctx->ipayloadlen) { + if(ctx->imsg->fin && + (ctx->imsg->opcode == WSLAY_TEXT_FRAME || + ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) && + ctx->imsg->utf8state != UTF8_ACCEPT) { + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != 0) { + return r; + } + break; + } + wslay_event_call_on_frame_recv_end_callback(ctx); + if(ctx->imsg->fin) { + if(ctx->callbacks.on_msg_recv_callback || + ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE || + ctx->imsg->opcode == WSLAY_PING) { + struct wslay_event_on_msg_recv_arg arg; + uint16_t status_code = 0; + uint8_t *msg = NULL; + size_t msg_length = 0; + if(!wslay_event_config_get_no_buffering(ctx) || + wslay_is_ctrl_frame(iocb.opcode)) { + msg = wslay_event_flatten_queue(ctx->imsg->chunks, + ctx->imsg->msg_length); + if(ctx->imsg->msg_length && !msg) { + ctx->read_enabled = 0; + return WSLAY_ERR_NOMEM; + } + msg_length = ctx->imsg->msg_length; + } + if(ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) { + const uint8_t *reason; + size_t reason_length; + if(ctx->imsg->msg_length >= 2) { + memcpy(&status_code, msg, 2); + status_code = ntohs(status_code); + if(!wslay_event_is_valid_status_code(status_code)) { + free(msg); + if((r = wslay_event_queue_close_wrapper + (ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) { + return r; + } + break; + } + reason = msg+2; + reason_length = ctx->imsg->msg_length-2; + } else { + reason = NULL; + reason_length = 0; + } + ctx->close_status |= WSLAY_CLOSE_RECEIVED; + ctx->status_code_recv = + status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code; + if((r = wslay_event_queue_close_wrapper + (ctx, status_code, reason, reason_length)) != 0) { + free(msg); + return r; + } + } else if(ctx->imsg->opcode == WSLAY_PING) { + struct wslay_event_msg pong_arg; + pong_arg.opcode = WSLAY_PONG; + pong_arg.msg = msg; + pong_arg.msg_length = ctx->imsg->msg_length; + if((r = wslay_event_queue_msg(ctx, &pong_arg)) && + r != WSLAY_ERR_NO_MORE_MSG) { + ctx->read_enabled = 0; + free(msg); + return r; + } + } + if(ctx->callbacks.on_msg_recv_callback) { + arg.rsv = ctx->imsg->rsv; + arg.opcode = ctx->imsg->opcode; + arg.msg = msg; + arg.msg_length = msg_length; + arg.status_code = status_code; + ctx->error = 0; + ctx->callbacks.on_msg_recv_callback(ctx, &arg, ctx->user_data); + } + free(msg); + } + wslay_event_imsg_reset(ctx->imsg); + if(ctx->imsg == &ctx->imsgs[1]) { + ctx->imsg = &ctx->imsgs[0]; + } + } + ctx->ipayloadlen = ctx->ipayloadoff = 0; + } + } else { + if(r != WSLAY_ERR_WANT_READ || + (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) { + if((r = wslay_event_queue_close_wrapper(ctx, 0, NULL, 0)) != 0) { + return r; + } + return WSLAY_ERR_CALLBACK_FAILURE; + } + break; + } + } + return 0; +} + +static void wslay_event_on_non_fragmented_msg_popped +(wslay_event_context_ptr ctx) +{ + ctx->omsg->fin = 1; + ctx->opayloadlen = ctx->omsg->data_length; + ctx->opayloadoff = 0; +} + +static struct wslay_event_omsg* wslay_event_send_ctrl_queue_pop +(wslay_event_context_ptr ctx) +{ + /* + * If Close control frame is queued, we don't send any control frame + * other than Close. + */ + if(ctx->close_status & WSLAY_CLOSE_QUEUED) { + while(!wslay_queue_empty(ctx->send_ctrl_queue)) { + struct wslay_event_omsg *msg = (struct wslay_event_omsg *)wslay_queue_top(ctx->send_ctrl_queue); + wslay_queue_pop(ctx->send_ctrl_queue); + if(msg->opcode == WSLAY_CONNECTION_CLOSE) { + return msg; + } else { + wslay_event_omsg_free(msg); + } + } + return NULL; + } else { + struct wslay_event_omsg *msg = (struct wslay_event_omsg *)wslay_queue_top(ctx->send_ctrl_queue); + wslay_queue_pop(ctx->send_ctrl_queue); + return msg; + } +} + +int wslay_event_send(wslay_event_context_ptr ctx) +{ + struct wslay_frame_iocb iocb; + ssize_t r; + while(ctx->write_enabled && + (!wslay_queue_empty(ctx->send_queue) || + !wslay_queue_empty(ctx->send_ctrl_queue) || ctx->omsg)) { + if(!ctx->omsg) { + if(wslay_queue_empty(ctx->send_ctrl_queue)) { + ctx->omsg = (struct wslay_event_omsg *)wslay_queue_top(ctx->send_queue); + wslay_queue_pop(ctx->send_queue); + } else { + ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx); + if(ctx->omsg == NULL) { + break; + } + } + if(ctx->omsg->type == WSLAY_NON_FRAGMENTED) { + wslay_event_on_non_fragmented_msg_popped(ctx); + } + } else if(!wslay_is_ctrl_frame(ctx->omsg->opcode) && + ctx->frame_ctx->ostate == PREP_HEADER && + !wslay_queue_empty(ctx->send_ctrl_queue)) { + if((r = wslay_queue_push_front(ctx->send_queue, ctx->omsg)) != 0) { + ctx->write_enabled = 0; + return r; + } + ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx); + if(ctx->omsg == NULL) { + break; + } + /* ctrl message has WSLAY_NON_FRAGMENTED */ + wslay_event_on_non_fragmented_msg_popped(ctx); + } + if(ctx->omsg->type == WSLAY_NON_FRAGMENTED) { + memset(&iocb, 0, sizeof(iocb)); + iocb.fin = 1; + iocb.opcode = ctx->omsg->opcode; + iocb.rsv = ctx->omsg->rsv; + iocb.mask = ctx->server^1; + iocb.data = ctx->omsg->data+ctx->opayloadoff; + iocb.data_length = ctx->opayloadlen-ctx->opayloadoff; + iocb.payload_length = ctx->opayloadlen; + r = wslay_frame_send(ctx->frame_ctx, &iocb); + if(r >= 0) { + ctx->opayloadoff += r; + if(ctx->opayloadoff == ctx->opayloadlen) { + --ctx->queued_msg_count; + ctx->queued_msg_length -= ctx->omsg->data_length; + if(ctx->omsg->opcode == WSLAY_CONNECTION_CLOSE) { + uint16_t status_code = 0; + ctx->write_enabled = 0; + ctx->close_status |= WSLAY_CLOSE_SENT; + if(ctx->omsg->data_length >= 2) { + memcpy(&status_code, ctx->omsg->data, 2); + status_code = ntohs(status_code); + } + ctx->status_code_sent = + status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code; + } + wslay_event_omsg_free(ctx->omsg); + ctx->omsg = NULL; + } else { + break; + } + } else { + if(r != WSLAY_ERR_WANT_WRITE || + (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + break; + } + } else { + if(ctx->omsg->fin == 0 && ctx->obuflimit == ctx->obufmark) { + int eof = 0; + r = ctx->omsg->read_callback(ctx, ctx->obuf, sizeof(ctx->obuf), + &ctx->omsg->source, + &eof, ctx->user_data); + if(r == 0 && eof == 0) { + break; + } else if(r < 0) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + ctx->obuflimit = ctx->obuf+r; + if(eof) { + ctx->omsg->fin = 1; + } + ctx->opayloadlen = r; + ctx->opayloadoff = 0; + } + memset(&iocb, 0, sizeof(iocb)); + iocb.fin = ctx->omsg->fin; + iocb.opcode = ctx->omsg->opcode; + iocb.rsv = ctx->omsg->rsv; + iocb.mask = ctx->server ? 0 : 1; + iocb.data = ctx->obufmark; + iocb.data_length = ctx->obuflimit-ctx->obufmark; + iocb.payload_length = ctx->opayloadlen; + r = wslay_frame_send(ctx->frame_ctx, &iocb); + if(r >= 0) { + ctx->obufmark += r; + if(ctx->obufmark == ctx->obuflimit) { + ctx->obufmark = ctx->obuflimit = ctx->obuf; + if(ctx->omsg->fin) { + --ctx->queued_msg_count; + wslay_event_omsg_free(ctx->omsg); + ctx->omsg = NULL; + } else { + ctx->omsg->opcode = WSLAY_CONTINUATION_FRAME; + /* RSV1 is not set on continuation frames */ + ctx->omsg->rsv = ctx->omsg->rsv & ~WSLAY_RSV1_BIT; + } + } else { + break; + } + } else { + if(r != WSLAY_ERR_WANT_WRITE || + (ctx->error != WSLAY_ERR_WOULDBLOCK && + ctx->error != 0)) { + ctx->write_enabled = 0; + return WSLAY_ERR_CALLBACK_FAILURE; + } + break; + } + } + } + return 0; +} + +void wslay_event_set_error(wslay_event_context_ptr ctx, int val) +{ + ctx->error = val; +} + +int wslay_event_want_read(wslay_event_context_ptr ctx) +{ + return ctx->read_enabled; +} + +int wslay_event_want_write(wslay_event_context_ptr ctx) +{ + return ctx->write_enabled && + (!wslay_queue_empty(ctx->send_queue) || + !wslay_queue_empty(ctx->send_ctrl_queue) || ctx->omsg); +} + +void wslay_event_shutdown_read(wslay_event_context_ptr ctx) +{ + ctx->read_enabled = 0; +} + +void wslay_event_shutdown_write(wslay_event_context_ptr ctx) +{ + ctx->write_enabled = 0; +} + +int wslay_event_get_read_enabled(wslay_event_context_ptr ctx) +{ + return ctx->read_enabled; +} + +int wslay_event_get_write_enabled(wslay_event_context_ptr ctx) +{ + return ctx->write_enabled; +} + +int wslay_event_get_close_received(wslay_event_context_ptr ctx) +{ + return (ctx->close_status & WSLAY_CLOSE_RECEIVED) > 0; +} + +int wslay_event_get_close_sent(wslay_event_context_ptr ctx) +{ + return (ctx->close_status & WSLAY_CLOSE_SENT) > 0; +} + +void wslay_event_config_set_allowed_rsv_bits(wslay_event_context_ptr ctx, + uint8_t rsv) +{ + /* We currently only allow WSLAY_RSV1_BIT or WSLAY_RSV_NONE */ + ctx->allowed_rsv_bits = rsv & WSLAY_RSV1_BIT; +} + +void wslay_event_config_set_no_buffering(wslay_event_context_ptr ctx, int val) +{ + if(val) { + ctx->config |= WSLAY_CONFIG_NO_BUFFERING; + } else { + ctx->config &= ~WSLAY_CONFIG_NO_BUFFERING; + } +} + +void wslay_event_config_set_max_recv_msg_length(wslay_event_context_ptr ctx, + uint64_t val) +{ + ctx->max_recv_msg_length = val; +} + +uint16_t wslay_event_get_status_code_received(wslay_event_context_ptr ctx) +{ + return ctx->status_code_recv; +} + +uint16_t wslay_event_get_status_code_sent(wslay_event_context_ptr ctx) +{ + return ctx->status_code_sent; +} + +size_t wslay_event_get_queued_msg_count(wslay_event_context_ptr ctx) +{ + return ctx->queued_msg_count; +} + +size_t wslay_event_get_queued_msg_length(wslay_event_context_ptr ctx) +{ + return ctx->queued_msg_length; +} diff --git a/websocket/src/wslay/wslay_frame.c b/websocket/src/wslay/wslay_frame.c new file mode 100644 index 0000000..445e750 --- /dev/null +++ b/websocket/src/wslay/wslay_frame.c @@ -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 +#include +#include + +#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; +} diff --git a/websocket/src/wslay/wslay_net.c b/websocket/src/wslay/wslay_net.c new file mode 100644 index 0000000..d3867c2 --- /dev/null +++ b/websocket/src/wslay/wslay_net.c @@ -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 */ diff --git a/websocket/src/wslay/wslay_queue.c b/websocket/src/wslay/wslay_queue.c new file mode 100644 index 0000000..8d26696 --- /dev/null +++ b/websocket/src/wslay/wslay_queue.c @@ -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 +#include + +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; +} diff --git a/websocket/src/wslay/wslay_stack.c b/websocket/src/wslay/wslay_stack.c new file mode 100644 index 0000000..0e05d74 --- /dev/null +++ b/websocket/src/wslay/wslay_stack.c @@ -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 +#include + +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; +} diff --git a/websocket/src/wslay_callbacks.cpp b/websocket/src/wslay_callbacks.cpp new file mode 100644 index 0000000..a2ffc25 --- /dev/null +++ b/websocket/src/wslay_callbacks.cpp @@ -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 \ No newline at end of file