ShreyasD
ShreyasD

Reputation: 115

Multiple Sub-protocol Websocket Javascript Client and Libwebsockets Server not working

I am trying to create a simple two protocol client and server for a demo. But when I specify two sub-protocols in the client initialization of the websocket I always get this message on the server side:

[1363089953:1535] ERR: Req protocol SIGN_IN_OUT_REQUEST_PROTOCOL, GET_REQUEST_PROTOCOL not supported

Client Javascript Code:

    var host = 'ws://10.0.96.32:9000';
var webSocket = {};
var websocketStatus = {};

$(function() {

        function webSocketSupported() {
            if(window.MozWebSocket) {
                window.WebSocket = window.MozWebSocket;
            }
            return (window.webSocket != null);
        }

        if(!webSocketSupported) { 
           $(document).simpledialog2({
               mode: 'blank',
               headerClose: true,
               blankContent :
                       "<p>Websockets not supported by this browser. How about <a href='http://www.google.com/chrome'>Google Chrome</a>?</p>"+
                       "<a rel='close' data-role='button' href='#'>Close</a>"         
           }); 
        } else {

            $("#login_form").validate({
               rules:{
                   username: {required: true, minlength: 2},
                   password: {required: true, minlength: 4}
               },
               messages:{
                   username: {required: "Username is required",
                              minlength: "Username must be atleast 2 characters"},
                   password: {required: "Password is required",
                              minlength: "Password must be atleast 4 characters"}
               } 
            });

            /** Setting up WebSocket **/
            webSocket = new WebSocket(host, 'SIGN_IN_OUT_REQUEST_PROTOCOL', 'GET_REQUEST_PROTOCOL');
            webSocket.binaryType = "arraybuffer";
            webSocket.onopen = onopen;
            webSocket.onmessage = onmessage;
            webSocket.onclose = onclose;
            webSocket.onerror = onerror;           
        }
});

function onopen() {
    $('#connectionStatus').text("Connected");
    $('#connectionStatus').buttonMarkup({theme: 'b'});
    $("#connectionStatus").data('icon', 'check'); 
    $("#connectionStatus .ui-icon").addClass("ui-icon-check").removeClass("ui-icon-alert");
    $('#login_info').append("Sub-protocol: "+webSocket.protocol+"\n");
}

function onmessage(event) {
    var data = new DataView(event.data);
    if(data.getUint8(data.byteLength - 1) == 1) {
        $('#login_info').append("Login Attempt Successful");
        $.mobile.loadPage("mobileApp.html");
        $.mobile.changePage("mobileApp.html")
    }
    else {
        $('#login_info').append("Login Attempt Unsuccessful");
        $('#username').val("");
        $('#password').val("");
    }
}

function onclose() {
    $('#connectionStatus').text("Connection Closed");
    $('#connectionStatus').buttonMarkup({theme: 'a'});
    $("#connectionStatus").data('icon', 'alert'); 
    $("#connectionStatus .ui-icon").addClass("ui-icon-alert").removeClass("ui-icon-alert");
}

function onerror() {
    $('#connectionStatus').text("Connection Error");
    $('#connectionStatus').buttonMarkup({theme: 'a'});
    $("#connectionStatus").data('icon', 'alert'); 
    $("#connectionStatus .ui-icon").addClass("ui-icon-alert").removeClass("ui-icon-alert");
}

function initialize() {
    $('#login_info').val("");
    $('#username').val("");
    $('#password').val("");
    $('#connectionStatus').text("Connection Closed");
    $('#connectionStatus').buttonMarkup({theme: 'a'});
    $("#connectionStatus").data('icon', 'alert'); 
    $("#connectionStatus .ui-icon").addClass("ui-icon-alert").removeClass("ui-icon-alert");
}

function sendLoginRequest(event) {
    var i = 0;
    event.preventDefault();
    if($("#login_form").valid()) {
        var username = $("#username").val();
        var password = $("#password").val();
        var loginRequest = new Uint8Array(calculateBufferLength());
        loginRequest[i++] = 0x13; //User Accounts IP
        loginRequest[i++] = 0x13; //Sign In/Sign Out Request
        loginRequest[i++] = 0x07; //Sign In
        loginRequest[i++] = username.length;
        loginRequest[i++] = password.length;

        //Pushing username
        for(var j=0; j < username.length; j++) {
            loginRequest[i++] = username.charCodeAt(j);
        }

        //Pushing password
        for(var j=0; j < password.length; j++) {
            loginRequest[i++] = password.charCodeAt(j);
        }

        webSocket.send(loginRequest.buffer);
        $("#login_info").append("Login request sent with\nUsername: "+username+"\nPassword: "+password+"\n");
    }
}

function calculateBufferLength() {
    var opIPLength = 1;
    var modeLength = 1;
    var subModeLength = 1;
    var UserNamePasswordlengths = 2;
    var userNameLength = $("#username").val().length;
    var passwordLength = $("#password").val().length;
    return (opIPLength + modeLength + subModeLength + UserNamePasswordlengths + userNameLength + passwordLength);
}

Server C/C++ code (Libwebsockets)

    #include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string>
#include <vector>
#include <libwebsockets.h>

using namespace std;

typedef enum {
    USER_ACCOUNTS_IP_REQUEST,
    MODE,
    SUB_MODE,
    USER_NAME_LENGTH,
    PASSWORD_LENGTH
}user_accounts_ip_request_t;

typedef enum {
    USER_ACCOUNTS_IP_RESPONSE,
    RESPONSE_IP
}user_accounts_ip_response_t;

int Hex_to_Dec(unsigned char *array, int size);
void sendLoginResponse(struct libwebsocket *wsi, void *in_buffer, size_t bufferlen, struct per_session_data__binary *out_buffer);
void printSocketInfo(struct libwebsocket *wsi, struct libwebsocket_context *currentContext);

struct per_session_data__binary
{
    unsigned int len;
    unsigned char buf[sizeof(LWS_SEND_BUFFER_PRE_PADDING) + 1024 + sizeof(LWS_SEND_BUFFER_POST_PADDING)];
};

static int callback_http(struct libwebsocket_context *currentContext,
                         struct libwebsocket *wsi,
                         enum libwebsocket_callback_reasons reason, void *user,
                         void *in, size_t len)
{
    return 0;
}

static int callback_sign_in_out_request(struct libwebsocket_context *currentContext,
                                   struct libwebsocket *wsi,
                                   enum libwebsocket_callback_reasons reason,
                                   void *user, void *in, size_t len)
{
    struct per_session_data__binary *psb = (per_session_data__binary *)user;   ////////

    switch (reason) {
        case LWS_CALLBACK_ESTABLISHED: // just log message that someone is connecting
            printf("connection established\n");
            break;
        case LWS_CALLBACK_PROTOCOL_INIT:
            printf("LWS_CALLBACK_PROTOCOL_INIT\n");
            break;
        case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
            printf("LWS_CALLBACK_CLIENT_CONNECTION_ERROR\n");
            break;
        case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH:
            printf("LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH\n");
            break;
        case LWS_CALLBACK_RECEIVE: {
            printf("Binary frame received decoding packet...\n");
            sendLoginResponse(wsi, in, len, psb);
        }
            break;
        default:
                    break;
    }
        return 0;
}

static int callback_get_request(struct libwebsocket_context *currentContext,
        struct libwebsocket *wsi,
        enum libwebsocket_callback_reasons reason,
        void *user, void *in, size_t len)
{
    return 0;
}

static struct libwebsocket_protocols protocols[] = {
    /* first protocol must always be HTTP handler */
    {
        "http-only",   // name
        callback_http, // callback
        0              // per_session_data_size
    },
    {
        "SIGN_IN_OUT_REQUEST_PROTOCOL", // protocol name - very important!
        callback_sign_in_out_request,   // callback
        sizeof(struct per_session_data__binary)              // we don't use any per session data
    },
    {
        "GET_REQUEST_PROTOCOL",
        callback_get_request,
        sizeof(struct per_session_data__binary)
    },
    {
        NULL, NULL, 0   /* End of list */
    }
};

int main(void) {
        // server url will be http://localhost:9000
        int port = 9000;
        const char *interface = NULL;
        struct libwebsocket_context *context;
        struct lws_context_creation_info info;
        // we're not using ssl
        const char *cert_path = NULL;
        const char *key_path = NULL;
        // no special options
        int opts = 0;

    info.port = port;
    info.iface = interface;
    info.protocols = protocols;
    info.extensions = libwebsocket_get_internal_extensions();
    info.ssl_cert_filepath = cert_path;
    info.ssl_private_key_filepath = key_path;
    info.ssl_ca_filepath = NULL;
    info.gid = -1;
    info.uid = -1;
    info.options = opts;
    info.user = NULL;
    //info.ka_time = 0;
    //info.ka_probes = 100;
    //info.ka_interval= 60;

    context = libwebsocket_create_context(&info);

        if (context == NULL) {
            fprintf(stderr, "libwebsocket init failed\n");
            return -1;
    }

        printf("starting server...\n");

    // infinite loop, to end this server send SIGTERM. (CTRL+C)
    while (1) {
            libwebsocket_service(context, 50);
        //printf("after service\n");
            // libwebsocket_service will process all waiting events with their
            // callback functions and then wait 50 ms.
            // (this is a single threaded webserver and this will keep our server
            // from generating load while there are not requests to process)
    }
        libwebsocket_context_destroy(context);

        return 0;
}

void sendLoginResponse(struct libwebsocket *wsi, void *in_buffer, size_t bufferlen, struct per_session_data__binary *out_buffer)
{
    vector<string> userNames, passwords;
    string userName, password;
    bool loginValid = false;
    int usernameLength, passwordLength;
    int usernamePos, passwordPos;


    userNames.push_back("Nautel");
    passwords.push_back("password");

    //Print Login Request Data
    char* loginRequest = (char*) in_buffer;
    usernameLength = loginRequest[USER_NAME_LENGTH];
    passwordLength = loginRequest[PASSWORD_LENGTH];
    usernamePos = PASSWORD_LENGTH + 1;
    passwordPos = usernamePos + usernameLength;

    //Get Username
    for(int i=usernamePos; i < (usernamePos+usernameLength); i++) {
        userName.push_back(loginRequest[i]);
    }

    //Get Password
    for(int i=passwordPos; i < (passwordPos + passwordLength); i++) {
        password.push_back(loginRequest[i]);
    }

    printf("Username: %s Password: %s\n", userName.c_str(), password.c_str());

    for(int i=0; i < userNames.size(); i++) {
        loginValid = (!userName.compare(userNames[i]) && !password.compare(passwords[i]));
        if(loginValid) break;
    }

    out_buffer->buf[USER_ACCOUNTS_IP_RESPONSE] = 0x13;
    out_buffer->buf[RESPONSE_IP] = (loginValid) ? 0x01 : 0x00;
    out_buffer->len = 2;
    libwebsocket_write(wsi, out_buffer->buf,  out_buffer->len, LWS_WRITE_BINARY);
}

void printSocketInfo(struct libwebsocket *wsi, struct libwebsocket_context *currentContext){
    char name[128];
    int name_len=sizeof(name);
    char rip[16];
    int rip_len= sizeof(rip);
    int fd;

    fd = libwebsocket_get_socket_fd(wsi);
    libwebsockets_get_peer_addresses (currentContext, wsi, fd, name, name_len, rip, rip_len);

    printf("Name: %s, IP: %s\n", name, rip);
}

Is it possible to have a client with multiple subprotocols supported by a single server or vice-versa. Or would I need multiple websocket connections from the client to the server for different protocols.

Regards, Shreyas

Upvotes: 2

Views: 2999

Answers (2)

Lo&#239;c Guzzetta
Lo&#239;c Guzzetta

Reputation: 158

During the handshake, a list of supported subprotocol server side is send to the client. The client send its list too. They must have at least one subprotocol in common.

You can implement your own subprotocol or use the existing ones.

For instance, in your case for a websocket server using the libwebsockets library (C/C++) and a javascript websocket client, if you implement your own subprotocol :

Server side :

static struct libwebsocket_protocols protocols[] = {
    {
        "http-only",
        callback_http, 
        0              
    },
    {
        "MySubProtocol",
        dedicated_callback_function,
        sizeof(struct per_session_data__binary)              
    }
}

Client side :

var exampleSocket = new WebSocket("ws://www.example.com/socketserver", "MySubProtocol");

More info here and here

Upvotes: 0

oberstet
oberstet

Reputation: 22051

A WebSocket client can announce support for multiple WebSocket subprotocols, but a server MUST choose ONE subprotocol (it supports) from the list announced by the client.

Upvotes: 1

Related Questions