/*
This file is part of Bolixo.
Bolixo is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bolixo is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Bolixo. If not, see .
*/
/*
websocket client using CONNECT_HTTP_INFO.
Works with TCPSERVER.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define INSTRUMENT_EXTERN
#include "instrument.h"
#include
#include "websocket-client.h"
using namespace std;
#define webapi_test_NOTNEED
#define webapi_addfile_NOTNEED
#define webapi_addfile_bob_NOTNEED
#define webapi_appendfile_NOTNEED
#define webapi_delfile_NOTNEED
#define webapi_undelete_NOTNEED
#define webapi_modifyfile_NOTNEED
#define webapi_modifyfile_bob_NOTNEED
#define webapi_rename_NOTNEED
#define webapi_copy_NOTNEED
#define webapi_readfile_NOTNEED
#define webapi_readfile_bob_NOTNEED
#define webapi_readmore_NOTNEED
#define webapi_mkdir_NOTNEED
#define webapi_rmdir_NOTNEED
#define webapi_listdir_NOTNEED
#define webapi_stat_NOTNEED
#define webapi_set_access_NOTNEED
#define webapi_markview_NOTNEED
#define webapi_list_inboxes_NOTNEED
#define webapi_list_msgs_NOTNEED
#define webapi_sendmsg_NOTNEED
#define webapi_sendmsg_project_NOTNEED
#define webapi_replymsg_NOTNEED
#define webapi_replymsg_project_NOTNEED
#define webapi_sendattach_NOTNEED
#define webapi_sendtalk_NOTNEED
#define webapi_sendtalk_file_NOTNEED
#define webapi_list_talk_NOTNEED
#define webapi_public_listdir_NOTNEED
#define webapi_public_readfile_NOTNEED
#define webapi_public_list_talk_NOTNEED
#define webapi_systempubkey_NOTNEED
#define webapi_verifysign_NOTNEED
#define webapi_getpubkey_NOTNEED
#define webapi_registernode_NOTNEED
#define webapi_remotelogin_NOTNEED
#define webapi_remotepass_NOTNEED
#define webapi_remote_interest_set_NOTNEED
#define webapi_remote_interest_unset_NOTNEED
#define webapi_nodelogin_NOTNEED
#define webapi_nodepass_NOTNEED
#define webapi_config_read_NOTNEED
#define webapi_config_write_NOTNEED
#define webapi_contact_request_NOTNEED
#define webapi_contact_manage_NOTNEED
#define webapi_contact_list_NOTNEED
#define webapi_list_contacts_NOTNEED
#define webapi_list_lists_NOTNEED
#define webapi_list_groups_NOTNEED
#define webapi_create_group_NOTNEED
#define webapi_delete_group_NOTNEED
#define webapi_delete_list_NOTNEED
#define webapi_set_member_NOTNEED
#define webapi_contact_remove_NOTNEED
#define webapi_list_members_NOTNEED
#define webapi_create_group_list_NOTNEED
#define webapi_set_group_NOTNEED
#define webapi_playstep_NOTNEED
#define webapi_playstep_more_NOTNEED
#define webapi_login_NOTNEED
#include "proto/webapi.protoch"
static DEBUG_KEY D_HEADER("header","Print request header");
static DEBUG_KEY D_PROTO("proto","HTTP protocol");
static void bo_websocket_send (CONNECT_HTTP_INFO &con, unsigned opcode, PARAM_STRING msg)
{
size_t lenmsg = strlen(msg.ptr);
size_t lenframe = 2+2+4+lenmsg; // Longest frame for message < 65536
char buf[lenframe];
buf[0] = 128+opcode;
char *ptmask = buf+2;
if (lenmsg <= 125){
buf[1] = (char)lenmsg;
lenframe = 6 + lenmsg;
}else if (lenmsg < 65536){
buf[1] = 126;
buf[2] = (lenmsg >> 8) &0xff;
buf[3] = lenmsg & 0xff;
ptmask = buf+4;
}else{
tlmp_error ("bo_websocket_send lenmsg=%zu not supported",lenmsg);
return;
}
buf[1] |= 0x80; // There is a 4 bytes mask
char *ptbuf = ptmask+4;
long long usec = fdpass_getnow();
ptmask[0] = usec & 0xff;
ptmask[1] = (usec >> 8) &0xff;
ptmask[2] = (usec >> 16) & 0xff;
ptmask[3] = (usec >> 24) & 0xff;
for (unsigned i=0; i 4){
bool fin = (line[0] &0x80) != 0;
if (!fin){
tlmp_error ("fin != %d",fin);
return 0;
}
unsigned opcode = line[0] &0xf;
if (opcode == 8){ // Close
//
}else{
bool mask = (line[1]&0x80)!=0;
unsigned len1 = line[1] &0x7f;
unsigned data_len = len1;
int frame_len = 2 + len1;
const unsigned char *data = line+2;
const unsigned char *maskb = line+2;
if (len1 == 126){
data_len = (line[2]<<8)+line[3];
frame_len = 2 + 2 + data_len;
data = line + 4;
maskb = line + 4;
}
if (mask){
frame_len += 4;
data += 4;
}
if (frame_len <= len){
ret = frame_len;
//FILE *fout = fopen ("/tmp/log","a");
//fprintf (fout,"data:");
if (mask){
for (unsigned i=0; i
void HANDLE_WSS::process(
bool &endclient,
bool &now_running, // Will be true if the websocket received the confirmation header and is not running.
int clientfd, // Once the websocket is running, we simply copy to this handle.
std::string *msg) // or we copy the message here, if not null
{
glocal HANDLE_WSS *wss = this;
glocal msg;
glocal now_running;
glocal initmsgs;
now_running = false;
char buf[100*1024];
if (clientfd == -1 && msg == nullptr){
endclient = true;
tlmp_error ("websocket-client: clientfd == -1 and msg == nullptr");
return;
}
int ret = con.receive (buf,sizeof(buf));
if (0 && con.ssl != nullptr){
int ssl_error = SSL_get_error(con.ssl,ret);
int pending = SSL_pending(con.ssl);
printf ("ssl_error=%d pending=%d\n",ssl_error,pending);
}
// printf ("receive ret=%d\n",ret);
if (ret <= 0){
debug_printf (D_PROTO,"fill ret=%d %d(%s)\n",ret,errno,strerror(errno));
endclient = true;
}else if (clientfd != -1 && state == WSS_RUNNING){
//tlmp_warning ("write to clientfd ret=%d",ret);
::write (clientfd,buf,ret);
}else{
(sbuf,buf,ret);
int ret = 0;
auto line = (const char *)buf;
if (glocal.wss->state == WSS_IN_HEADER){
// We parse lines until the end of the header
auto pt = line;
int offset = 0;
string hline;
while (offset < len){
offset++;
auto car = *pt++;
if (is_any_of(car,'\r','\n')){
hline = string(line,offset-1);
if (*pt == '\n'){
pt++;
offset++;
}
ret = offset;
break;
}
}
if (ret > 0){
//tlmp_warning ("line=%s\n",hline.c_str());
const char *pt;
if (hline.size() == 0){
glocal.now_running = true;
glocal.wss->state = WSS_RUNNING;
for (auto &m:glocal.initmsgs) glocal.wss->send (m);
}else if (is_start_any_ofnc(hline,pt,"Sec-WebSocket-Accept:")){
pt = str_skip(pt);
string tmp = glocal.wss->challenge + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
unsigned char* sha_str = SHA1(reinterpret_cast(tmp.c_str()), tmp.length(), nullptr);
string acceptkey = base64_encode(reinterpret_cast(sha_str), 20);
if (acceptkey != pt){
tlmp_warning ("websocket: Invalid challenge %s(%zu) <> %s(%zu)\n",acceptkey.c_str(),acceptkey.size(),pt,strlen(pt));
}
}
}
}else if (glocal.wss->state == WSS_RUNNING){
// This is a websocket packet, potentially incomplete
//tlmp_warning ("running len=%d\n",len);
ret = bo_websocket_receive (glocal.wss->con,buf,len,*glocal.msg);
//if (glocal.msg->size() > 0) tlmp_warning ("msg=%s\n",glocal.msg->c_str());
}
//printf ("ret=%d len=%d\n",ret,len);
return ret;
}
}
void HANDLE_WSS::process(
bool &endclient,
bool &now_running, // Will be true if the websocket received the confirmation header and is not running.
int clientfd) // Once the websocket is running, we simply copy to this handle.
{
process (endclient,now_running,clientfd,nullptr);
}
void HANDLE_WSS::process(
bool &endclient,
bool &now_running, // Will be true if the websocket received the confirmation header and is not running.
std::string &msg) // Will contain the message received
{
process (endclient,now_running,-1,&msg);
}
void HANDLE_WSS::sendheader (PARAM_STRING hostname)
{
string buf;
buf = string_f ("GET /wss HTTP/1.1\r\nhost: %s\r\nUser-Agent: bo-webtest\r\n",hostname.ptr);
if (session.size() > 0){
buf += string_f ("cookie: session=%s;\r\n",session.c_str());
}
buf += "Upgrade: websocket\r\n";
buf += "Connection: Upgrade\r\n";
challenge = string_f ("%Ld",fdpass_getnow());
buf += string_f("Sec-WebSocket-Key: %s\r\n",challenge.c_str());
buf += "Sec-WebSocket-Version: 13\r\n";
buf += "\r\n";
con.send (buf.c_str());
debug_printf (D_HEADER,"%s-----\n",buf.c_str());
}
void HANDLE_WSS::send (PARAM_STRING msg)
{
//tlmp_warning ("send :%s:",msg.ptr);
bo_websocket_send(con,1,msg);
}
// Write directly to the socket, no websocket encoding.
// This is used when we proxy a websocket. We simply copy the data back and forth
void HANDLE_WSS::write (const void *buf, size_t size)
{
con.write (buf,size);
}
/*
Add a message to send at connection time.
(When the state change to running)
*/
void HANDLE_WSS::addinitmsg(PARAM_STRING msg)
{
initmsgs.push_back(msg.ptr);
}
void HANDLE_WSS::setsession(PARAM_STRING sessionid)
{
session = sessionid.ptr;
}
// This grab the connection info (see the move())
// CONNECT_HTTP_INFO can't be copied
void HANDLE_WSS::moveconnectinfo(CONNECT_HTTP_INFO &hcon)
{
con = move(hcon);
}
HANDLE_WSS::~HANDLE_WSS()
{
if (session.size() > 0){
//tlmp_warning ("HANDLE_WSS logout\n");
// We were running in websocket mode, so the current connection can't be reuse to talk to webapi.
con.close();
(con,session);
//printf ("Internal error=%d\n",internal_error);
}
}