blob: df6e41e4eff181f45ba98f56ddc5ad8d86b3f4b4 [file] [log] [blame]
/*
* libwebsockets-test-echo
*
* Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* The person who associated a work with this deed has dedicated
* the work to the public domain by waiving all of his or her rights
* to the work worldwide under copyright law, including all related
* and neighboring rights, to the extent allowed by law. You can copy,
* modify, distribute and perform the work, even for commercial purposes,
* all without asking permission.
*
* The test apps are intended to be adapted for use in your code, which
* may be proprietary. So unlike the library itself, they are licensed
* Public Domain.
*/
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include "../lib/libwebsockets.h"
#ifndef _WIN32
#include <syslog.h>
#include <sys/time.h>
#include <unistd.h>
#else
#include "gettimeofday.h"
#include <process.h>
#endif
static volatile int force_exit = 0;
static int versa, state;
static int times = -1;
#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
#define MAX_ECHO_PAYLOAD 1024
struct per_session_data__echo {
size_t rx, tx;
unsigned char buf[LWS_PRE + MAX_ECHO_PAYLOAD];
unsigned int len;
unsigned int index;
int final;
int continuation;
int binary;
};
static int
callback_echo(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len)
{
struct per_session_data__echo *pss =
(struct per_session_data__echo *)user;
int n;
switch (reason) {
#ifndef LWS_NO_SERVER
case LWS_CALLBACK_ESTABLISHED:
pss->index = 0;
pss->len = -1;
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
do_tx:
n = LWS_WRITE_CONTINUATION;
if (!pss->continuation) {
if (pss->binary)
n = LWS_WRITE_BINARY;
else
n = LWS_WRITE_TEXT;
pss->continuation = 1;
}
if (!pss->final)
n |= LWS_WRITE_NO_FIN;
lwsl_info("+++ test-echo: writing %d, with final %d\n",
pss->len, pss->final);
pss->tx += pss->len;
n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, n);
if (n < 0) {
lwsl_err("ERROR %d writing to socket, hanging up\n", n);
return 1;
}
if (n < (int)pss->len) {
lwsl_err("Partial write\n");
return -1;
}
pss->len = -1;
if (pss->final)
pss->continuation = 0;
lws_rx_flow_control(wsi, 1);
break;
case LWS_CALLBACK_RECEIVE:
do_rx:
pss->final = lws_is_final_fragment(wsi);
pss->binary = lws_frame_is_binary(wsi);
lwsl_info("+++ test-echo: RX len %ld final %ld, pss->len=%ld\n",
(long)len, (long)pss->final, (long)pss->len);
memcpy(&pss->buf[LWS_PRE], in, len);
assert((int)pss->len == -1);
pss->len = (unsigned int)len;
pss->rx += len;
lws_rx_flow_control(wsi, 0);
lws_callback_on_writable(wsi);
break;
#endif
#ifndef LWS_NO_CLIENT
/* when the callback is used for client operations --> */
case LWS_CALLBACK_CLOSED:
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_debug("closed\n");
state = 0;
break;
case LWS_CALLBACK_CLIENT_ESTABLISHED:
lwsl_debug("Client has connected\n");
pss->index = 0;
pss->len = -1;
state = 2;
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
#ifndef LWS_NO_SERVER
if (versa)
goto do_rx;
#endif
lwsl_notice("Client RX: %s", (char *)in);
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
#ifndef LWS_NO_SERVER
if (versa) {
if (pss->len != (unsigned int)-1)
goto do_tx;
break;
}
#endif
/* we will send our packet... */
pss->len = sprintf((char *)&pss->buf[LWS_PRE],
"hello from libwebsockets-test-echo client pid %d index %d\n",
getpid(), pss->index++);
lwsl_notice("Client TX: %s", &pss->buf[LWS_PRE]);
n = lws_write(wsi, &pss->buf[LWS_PRE], pss->len, LWS_WRITE_TEXT);
if (n < 0) {
lwsl_err("ERROR %d writing to socket, hanging up\n", n);
return -1;
}
if (n < (int)pss->len) {
lwsl_err("Partial write\n");
return -1;
}
break;
#endif
case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
/* reject everything else except permessage-deflate */
if (strcmp(in, "permessage-deflate"))
return 1;
break;
default:
break;
}
return 0;
}
static struct lws_protocols protocols[] = {
/* first protocol must always be HTTP handler */
{
"", /* name - can be overridden with -e */
callback_echo,
sizeof(struct per_session_data__echo), /* per_session_data_size */
MAX_ECHO_PAYLOAD,
},
{
NULL, NULL, 0 /* End of list */
}
};
static const struct lws_extension exts[] = {
{
"permessage-deflate",
lws_extension_callback_pm_deflate,
"permessage-deflate; client_no_context_takeover; client_max_window_bits"
},
{
"deflate-frame",
lws_extension_callback_pm_deflate,
"deflate_frame"
},
{ NULL, NULL, NULL /* terminator */ }
};
void sighandler(int sig)
{
force_exit = 1;
}
static struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "debug", required_argument, NULL, 'd' },
{ "port", required_argument, NULL, 'p' },
{ "ssl-cert", required_argument, NULL, 'C' },
{ "ssl-key", required_argument, NULL, 'k' },
#ifndef LWS_NO_CLIENT
{ "client", required_argument, NULL, 'c' },
{ "ratems", required_argument, NULL, 'r' },
#endif
{ "ssl", no_argument, NULL, 's' },
{ "versa", no_argument, NULL, 'v' },
{ "uri", required_argument, NULL, 'u' },
{ "passphrase", required_argument, NULL, 'P' },
{ "interface", required_argument, NULL, 'i' },
{ "times", required_argument, NULL, 'n' },
{ "echogen", no_argument, NULL, 'e' },
#ifndef LWS_NO_DAEMONIZE
{ "daemonize", no_argument, NULL, 'D' },
#endif
{ NULL, 0, 0, 0 }
};
int main(int argc, char **argv)
{
int n = 0;
int port = 7681;
int use_ssl = 0;
struct lws_context *context;
int opts = 0;
char interface_name[128] = "";
const char *_interface = NULL;
char ssl_cert[256] = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
char ssl_key[256] = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
#ifndef _WIN32
/* LOG_PERROR is not POSIX standard, and may not be portable */
#ifdef __sun
int syslog_options = LOG_PID;
#else
int syslog_options = LOG_PID | LOG_PERROR;
#endif
#endif
int client = 0;
int listen_port = 80;
struct lws_context_creation_info info;
char passphrase[256];
char uri[256] = "/";
#ifndef LWS_NO_CLIENT
char address[256], ads_port[256 + 30];
int rate_us = 250000;
unsigned long long oldus;
struct lws *wsi;
int disallow_selfsigned = 0;
struct timeval tv;
const char *connect_protocol = NULL;
struct lws_client_connect_info i;
#endif
int debug_level = 7;
#ifndef LWS_NO_DAEMONIZE
int daemonize = 0;
#endif
memset(&info, 0, sizeof info);
#ifndef LWS_NO_CLIENT
lwsl_notice("Built to support client operations\n");
#endif
#ifndef LWS_NO_SERVER
lwsl_notice("Built to support server operations\n");
#endif
while (n >= 0) {
n = getopt_long(argc, argv, "i:hsp:d:DC:k:P:vu:n:e"
#ifndef LWS_NO_CLIENT
"c:r:"
#endif
, options, NULL);
if (n < 0)
continue;
switch (n) {
case 'P':
strncpy(passphrase, optarg, sizeof(passphrase));
passphrase[sizeof(passphrase) - 1] = '\0';
info.ssl_private_key_password = passphrase;
break;
case 'C':
strncpy(ssl_cert, optarg, sizeof(ssl_cert));
ssl_cert[sizeof(ssl_cert) - 1] = '\0';
disallow_selfsigned = 1;
break;
case 'k':
strncpy(ssl_key, optarg, sizeof(ssl_key));
ssl_key[sizeof(ssl_key) - 1] = '\0';
break;
case 'u':
strncpy(uri, optarg, sizeof(uri));
uri[sizeof(uri) - 1] = '\0';
break;
#ifndef LWS_NO_DAEMONIZE
case 'D':
daemonize = 1;
#if !defined(_WIN32) && !defined(__sun)
syslog_options &= ~LOG_PERROR;
#endif
break;
#endif
#ifndef LWS_NO_CLIENT
case 'c':
client = 1;
strncpy(address, optarg, sizeof(address) - 1);
address[sizeof(address) - 1] = '\0';
port = 80;
break;
case 'r':
rate_us = atoi(optarg) * 1000;
break;
#endif
case 'd':
debug_level = atoi(optarg);
break;
case 's':
use_ssl = 1; /* 1 = take care about cert verification, 2 = allow anything */
break;
case 'p':
port = atoi(optarg);
break;
case 'v':
versa = 1;
break;
case 'e':
protocols[0].name = "lws-echogen";
connect_protocol = protocols[0].name;
lwsl_err("using lws-echogen\n");
break;
case 'i':
strncpy(interface_name, optarg, sizeof interface_name);
interface_name[(sizeof interface_name) - 1] = '\0';
_interface = interface_name;
break;
case 'n':
times = atoi(optarg);
break;
case '?':
case 'h':
fprintf(stderr, "Usage: libwebsockets-test-echo\n"
" --debug / -d <debug bitfield>\n"
" --port / -p <port>\n"
" --ssl-cert / -C <cert path>\n"
" --ssl-key / -k <key path>\n"
#ifndef LWS_NO_CLIENT
" --client / -c <server IP>\n"
" --ratems / -r <rate in ms>\n"
#endif
" --ssl / -s\n"
" --passphrase / -P <passphrase>\n"
" --interface / -i <interface>\n"
" --uri / -u <uri path>\n"
" --times / -n <-1 unlimited or times to echo>\n"
#ifndef LWS_NO_DAEMONIZE
" --daemonize / -D\n"
#endif
);
exit(1);
}
}
#ifndef LWS_NO_DAEMONIZE
/*
* normally lock path would be /var/lock/lwsts or similar, to
* simplify getting started without having to take care about
* permissions or running as root, set to /tmp/.lwsts-lock
*/
#if defined(WIN32) || defined(_WIN32)
#else
if (!client && daemonize && lws_daemonize("/tmp/.lwstecho-lock")) {
fprintf(stderr, "Failed to daemonize\n");
return 1;
}
#endif
#endif
#ifndef _WIN32
/* we will only try to log things according to our debug_level */
setlogmask(LOG_UPTO (LOG_DEBUG));
openlog("lwsts", syslog_options, LOG_DAEMON);
#endif
/* tell the library what debug level to emit and to send it to syslog */
lws_set_log_level(debug_level, lwsl_emit_syslog);
lwsl_notice("libwebsockets test server echo - license LGPL2.1+SLE\n");
lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
#ifndef LWS_NO_CLIENT
if (client) {
lwsl_notice("Running in client mode\n");
listen_port = CONTEXT_PORT_NO_LISTEN;
if (use_ssl && !disallow_selfsigned) {
lwsl_info("allowing selfsigned\n");
use_ssl = 2;
} else {
lwsl_info("requiring server cert validation against %s\n",
ssl_cert);
info.ssl_ca_filepath = ssl_cert;
}
} else {
#endif
#ifndef LWS_NO_SERVER
lwsl_notice("Running in server mode\n");
listen_port = port;
#endif
#ifndef LWS_NO_CLIENT
}
#endif
info.port = listen_port;
info.iface = _interface;
info.protocols = protocols;
if (use_ssl && !client) {
info.ssl_cert_filepath = ssl_cert;
info.ssl_private_key_filepath = ssl_key;
} else
if (use_ssl && client) {
info.ssl_cert_filepath = NULL;
info.ssl_private_key_filepath = NULL;
}
info.gid = -1;
info.uid = -1;
info.extensions = exts;
info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8;
if (use_ssl)
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
#ifndef LWS_NO_EXTENSIONS
info.extensions = exts;
#endif
context = lws_create_context(&info);
if (context == NULL) {
lwsl_err("libwebsocket init failed\n");
return -1;
}
signal(SIGINT, sighandler);
#ifndef LWS_NO_CLIENT
gettimeofday(&tv, NULL);
oldus = ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
#endif
n = 0;
while (n >= 0 && !force_exit) {
#ifndef LWS_NO_CLIENT
if (client && !state && times) {
state = 1;
lwsl_notice("Client connecting to %s:%u....\n",
address, port);
/* we are in client mode */
address[sizeof(address) - 1] = '\0';
sprintf(ads_port, "%s:%u", address, port & 65535);
if (times > 0)
times--;
memset(&i, 0, sizeof(i));
i.context = context;
i.address = address;
i.port = port;
i.ssl_connection = use_ssl;
i.path = uri;
i.host = ads_port;
i.origin = ads_port;
i.protocol = connect_protocol;
wsi = lws_client_connect_via_info(&i);
if (!wsi) {
lwsl_err("Client failed to connect to %s:%u\n",
address, port);
goto bail;
}
}
if (client && !versa && times) {
gettimeofday(&tv, NULL);
if (((((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec) - oldus) > rate_us) {
lws_callback_on_writable_all_protocol(context,
&protocols[0]);
oldus = ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
if (times > 0)
times--;
}
}
if (client && !state && !times)
break;
#endif
n = lws_service(context, 10);
}
#ifndef LWS_NO_CLIENT
bail:
#endif
lws_context_destroy(context);
lwsl_notice("libwebsockets-test-echo exited cleanly\n");
#ifndef _WIN32
closelog();
#endif
return 0;
}