#include #include #include #include #include #include #include #include #include #include #include #include #include "bofs.h" #include "websocket-client.h" #include "bolixo.m" #include "bolixo.h" #include "json.h" #define INSTRUMENT_EXTERN #include "instrument.h" #include "proto/bod_client.protodef" #define webapi_test_NOTNEED #define webapi_login_NOTNEED #define webapi_logout_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_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_list_lists_NOTNEED #define webapi_list_groups_NOTNEED #define webapi_list_contacts_NOTNEED #define webapi_create_group_list_NOTNEED #define webapi_create_group_NOTNEED #define webapi_delete_group_NOTNEED #define webapi_delete_list_NOTNEED #define webapi_set_group_NOTNEED #define webapi_set_member_NOTNEED #define webapi_list_members_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_contact_remove_NOTNEED #define webapi_playstep_NOTNEED #define webapi_playstep_more_NOTNEED #include "proto/webapi.protoch" using namespace std; using HANDLE_WSS_P = shared_ptr; struct HANDLE_INFO: public ARRAY_OBJ{ HANDLE_WSS_P wss=nullptr; }; #define _TLMP_vidconf_sock struct _F_vidconf_sock{ HANDLE_INFO *h = nullptr; void send (PARAM_STRING line); #define _F_vidconf_sock_receive(x) void x receive(const char *line, bool &end) virtual _F_vidconf_sock_receive( ); #define _F_vidconf_sock_init(x) void init(bool &end) virtual _F_vidconf_sock_init( ); #define _F_vidconf_sock_idle(x) void idle(bool &end) virtual _F_vidconf_sock_idle( ); }; void _F_vidconf_sock::init(bool &end) { } void _F_vidconf_sock::idle(bool &end) { } void _F_vidconf_sock::receive(const char *line, bool &end) { } void _F_vidconf_sock::send (PARAM_STRING line) { //printf ("send %zu\n",strlen(line.ptr)); h->wss->send (line); } static int vidconf_send (_F_vidconf_sock *c, FILE *fin, int nbchunk) { int ret = -1; const size_t line_size = REQ_CONTENT_CHUNK; for (int i=0; i0){ string base64 = base64_encode(buf,nb); string longline = "append:"; // Remove line feed auto pt = base64.begin(); while (pt != base64.end()){ if (*pt > ' ') longline += *pt; pt++; } c->send (longline); ret = 0; }else{ break; } } //printf ("send ret = %d\n",ret); return ret; } static void vidconf_sock(_F_vidconf_sock &c, CONTEXT &ctx, const char *docname, int timeout, bool splitlines) { glocal c; glocal splitlines; (); glocal.c.idle(endserver); if (getnbclients() <= 1){ endserver = true; } HANDLE_INFO *c = (HANDLE_INFO*)info.data; if (!c->wss){ endserver = true; }else{ bool now_running = false; string msg; c->wss->process(endclient,now_running,msg); if (now_running){ glocal.c.init(endserver); } //printf ("now_running=%d msg=%zu\n",now_running,msg.size()); if (msg.size() > 0){ if (glocal.splitlines){ auto tb = str_cnv2lines(msg.c_str()); for (auto &l:tb){ glocal.c.receive(l.c_str(),endserver); if (endserver) break; } }else{ glocal.c.receive(msg.c_str(),endserver); } } } int fd; BIO_get_fd(ctx.hcon.bio,&fd); HANDLE_INFO *h = new HANDLE_INFO; c.h = h; h->wss = make_shared(); h->wss->setsession(ctx.hsessionid); string host = ctx.hcon.host; h->wss->moveconnectinfo(ctx.hcon); h->wss->addinitmsg (string_f("gameid=\"%s\" 100 100 100 100 0 10",docname)); h->wss->addinitmsg ("gamesequence=0"); h->wss->sendheader (host); o.inject (fd,h); o.setmonitormode(fd,true); o.set_idle_timeout(timeout); o.loop(); } static void vidconf_getjs(CONTEXT &ctx, const char *docname, FILE *fout) { glocal fout; (ctx,docname,60,true); send ("getjs:"); if (is_eq(line,"end")){ end = true; }else{ fprintf (glocal.fout,"%s\n",line); } } int bofs_vidconf (CONTEXT &ctx, int argc, char *argv[]) { glocal int ret = -1; glocal ctx; glocal const char *docname = nullptr; glocal const char *file = nullptr; glocal bool netusage = false; glocal bool connected = false; glocal bool statsdump = false; setlocale(LC_ALL,""); glocal.ret = (argc,argv); setproginfo ("bofs vidconf",VERSION ,MSG_U(I_CMDVIDCONF ,"Command line tool to interact with video conference\n" "\n" "bofs vidconf command ...\n" "\n" "Commands are:\n" "\tgetlogs\n" "\tgetstates\n" "\tgetstats\n" "\tlistusers\n" "\trecord\n" "\tsendvideo\n" "\tshow user|connectionid\n" "\n" "Test/dev commands:\n" "\tgetjs\n" "\twrtctest output-file\n" ) ); setarg ('d',"docname",MSG_R(O_DOCNAME),glocal.docname,true); setarg ('f',"filevideo",MSG_U(O_FILEVIDEO,"Video to send to the conference"),glocal.file,false); setgrouparg (MSG_U(I_GETSTATS,"getstats options")); setarg (' ',"netusage",MSG_U(I_NETUSAGE,"Network usage statistics"),glocal.netusage,false); setarg (' ',"connected",MSG_U(I_CONNECTED,"Show how each peers are connected"),glocal.connected,false); setarg (' ',"dump",MSG_U(I_STATSDUMP,"Print the complete JSON"),glocal.statsdump,false); int ret = -1; const char *arg = argv[0]; if (glocal.ctx.is_internal()){ tlmp_error ("Ne fonctionne pour le protocole interne\n"); }else if (glocal.ctx.login()!=-1){ glocal bool exist = false; (glocal.ctx.hcon,glocal.ctx.hsessionid,glocal.docname,""); if (internal_error){ tlmp_error (MSG_R(E_INTERNAL),msg); }else if (!success){ tlmp_error (MSG_U(E_CONFERENCEMISSING,"Conference file is missing\n")); }else if (file.file_type != FILE_DOC_VIDCONF){ tlmp_error (MSG_U(E_NOTVIDCONF,"The document is not a conference\n")); }else{ glocal.exist = true; } if (!glocal.exist){ if (!is_start_any_of(glocal.docname,NONEED,"/projects/")){ tlmp_error (MSG_U(E_DOCNAME,"Document name (option docname) must start with /projects/\n")); } }else if (is_eq(arg,"sendvideo")){ if (glocal.file == nullptr){ tlmp_error (MSG_U(E_NOVIDEOTOSEND,"Option --filevideo missing, nothing to send\n")); }else{ glocal FILE *fin = nullptr; if (is_eq(glocal.file,"-")){ glocal.fin = stdin; }else{ glocal.fin = fopen(glocal.file,"r"); if (glocal.fin == nullptr){ tlmp_error (MSG_U(E_CANTOPENVIDEO,"Can't open video file %s (%s)\n"),glocal.file,strerror(errno)); exit (-1); } } (glocal.ctx,glocal.docname,1,false); if (vidconf_send (this,glocal.fin,10) == -1) end = true; if (vidconf_send (this,glocal.fin,10)==-1) end = true; //printf ("rec=%s\n",line); if (glocal.fin != stdin) fclose (glocal.fin); } }else if (is_eq(arg,"record")){ (glocal.ctx,glocal.docname,60,false); printf ("line=%s\n",line); }else if (is_eq(arg,"getstats")){ glocal map> all; // All parsed stats line, by ID glocal map peerid2start; // Get the start time for a peer (glocal.ctx,glocal.docname,60,false); send ("getstats:"); auto tb = str_cnv2lines (line); for (auto &l:tb){ // printf ("line=%s\n",l.c_str()); const char *pt; string peerid; time_t start; if (is_start_any_of(l,pt,"statserror ")){ printf ("%s\n",pt); }else if (splitline(l,match("statspeerid"),peerid,start)){ //printf ("statspeerid %s %ld\n",peerid.c_str(),start); glocal.peerid2start[peerid] = start; }else if (is_start_any_of(l,pt,"stats ")){ glocal string user; pt = str_copyword(glocal.user,pt); auto tb2 = str_splitline (pt,'\r'); for (auto &l2:tb2){ glocal string peerid; pt = str_copyword(glocal.peerid,l2.c_str()); pt = str_skip(pt); glocal map kvals; glocal.kvals["user"] = glocal.user; glocal.kvals["peerid"] = glocal.peerid; (pt); glocal.kvals[name] = value; if (glocal.statsdump){ printf ("%s %s type=%s %s\n",glocal.user.c_str(),glocal.peerid.c_str(),glocal.kvals["type"].c_str(),pt); } glocal.all[glocal.kvals["id"]] = move(glocal.kvals); } } if (l=="end") end = true; } if (glocal.netusage){ // Show how network usage }else if (glocal.connected){ // Tells how the various RTCPeerConnections are connected time_t now = time(nullptr); for (auto &a:glocal.all){ if (a.second["type"] == "candidate-pair" && a.second["nominated"] == "true"){ const string &localid = a.second["localCandidateId"]; const string &remoteid = a.second["remoteCandidateId"]; const string &peerid = a.second["peerid"]; long duration = now-glocal.peerid2start[peerid]; long sent = atol(a.second["bytesSent"].c_str()); long received = atol(a.second["bytesReceived"].c_str()); printf ("user=%s peerid=%s bytesSent=%ld (%.2lf/s) bytesReceived=%ld (%.2lf/s) (%s %s)\n" ,a.second["user"].c_str(),peerid.c_str() ,sent,(double)sent/duration,received,(double)received/duration ,localid.c_str(),remoteid.c_str()); auto &local = glocal.all[localid]; printf ("\tlocal address=%s port=%s protocol=%s candidateType=%s\n" ,local["ip"].c_str(),local["port"].c_str(),local["protocol"].c_str(),local["candidateType"].c_str()); auto &remote = glocal.all[remoteid]; printf ("\tremote address=%s port=%s protocol=%s candidateType=%s\n" ,remote["ip"].c_str(),remote["port"].c_str(),remote["protocol"].c_str(),remote["candidateType"].c_str()); } } } }else if (is_eq(arg,"getstates")){ (glocal.ctx,glocal.docname,60,true); send ("getstates:"); const char *pt; if (is_start_any_of(line,pt,"states ")){ printf ("%s\n",pt); }else if (is_eq(line,"end")){ end = true; } }else if (is_eq(arg,"getlogs")){ (glocal.ctx,glocal.docname,60,true); send ("getlogs:"); const char *pt; if (is_start_any_of(line,pt,"logserror ")){ printf ("%s\n",pt); }else if (is_start_any_of(line,pt,"logs ")){ string user,peerid; pt = str_copyword (user,pt); pt = str_copyword (peerid,pt); pt = str_skip(pt); auto tb = str_splitline (pt,'\r'); printf ("%s %s\n",peerid.c_str(),user.c_str()); for (auto &t:tb){ printf ("\t\t%s\n",t.c_str()); } }else if (is_eq(line,"end")){ end = true; } }else if (is_eq(arg,"listusers")){ (glocal.ctx,glocal.docname,60,true); send ("listusers:"); string peerid,user; time_t start; if (splitline(line,match("user"),peerid,user,start)){ struct tm *t = localtime(&start); char datestr[100]; strftime(datestr,sizeof(datestr),"%c",t); printf ("%s %-20s %s\n",peerid.c_str(),user.c_str(),datestr); }else if (is_eq(line,"end")){ end = true; } }else if (is_eq(arg,"show") && argc == 2){ glocal const char *user = argv[1]; (glocal.ctx,glocal.docname,60,true); send (string_f("show:%s",glocal.user)); printf ("line=%s\n",line); end = true; }else if (is_eq(arg,"getjs")){ // Get the client javascript used to establish a video conference vidconf_getjs (glocal.ctx,glocal.docname,stdout); }else if (is_eq(arg,"wrtctest") && argc == 2){ // Generate a javascript file and execute it (argv[1],false); fprintf (fout,"var location={hostname: 'test1.bolixo.org'};\n"); vidconf_getjs (glocal.ctx,glocal.docname,fout); glocal fout; ("scripts/notesocket.js",false); fputs(line,glocal.fout); return 0; fprintf (fout,"noteconnect('ws','test1.bolixo.org','%s','%s');\n",glocal.docname,glocal.ctx.hsessionid.c_str()); return 0; }else{ usage(); } } return ret; return glocal.ret; }