Use Linux-like indentation in mptp.c
[swifty.git] / src / libswift / cmdgw.cpp
1 /*
2  *  cmdgw.cpp
3  *  command gateway for controling swift engine via a TCP connection
4  *
5  *  Created by Arno Bakker
6  *  Copyright 2010-2012 TECHNISCHE UNIVERSITEIT DELFT. All rights reserved.
7  *
8  */
9 #include <math.h>
10 #include <iostream>
11 #include <sstream>
12
13 #include "swift.h"
14 #include "compat.h"
15 #include <event2/buffer.h>
16 #include <event2/bufferevent.h>
17 #include <event2/listener.h>
18
19
20 using namespace swift;
21
22 // Send PLAY after receiving 2^layer * chunksize bytes
23 #define CMDGW_MAX_PREBUF_BYTES          (256*1024)
24
25
26 // Status of the swarm download
27 #define DLSTATUS_HASHCHECKING  2
28 #define DLSTATUS_DOWNLOADING  3
29 #define DLSTATUS_SEEDING 4
30
31 #define MAX_CMD_MESSAGE 1024
32
33 #define ERROR_NO_ERROR          0
34 #define ERROR_UNKNOWN_CMD       -1
35 #define ERROR_MISS_ARG          -2
36 #define ERROR_BAD_ARG           -3
37
38 #define CMDGW_MAX_CLIENT 1024   // Arno: == maximum number of swarms per proc
39
40 struct cmd_gw_t {
41     int      id;
42     evutil_socket_t   cmdsock;
43     int          transfer; // swift FD
44     char        *contentfilename; // basename of content file
45     bool        moreinfo;                 // whether to report detailed stats (see SETMOREINFO cmd)
46     tint        startt;                   // ARNOSMPTODO: debug speed measurements, remove
47 } cmd_requests[CMDGW_MAX_CLIENT];
48
49
50 int cmd_gw_reqs_open = 0;
51 int cmd_gw_reqs_count = 0;
52
53 struct evconnlistener *cmd_evlistener = NULL;
54 struct evbuffer *cmd_evbuffer = NULL; // Data received on cmd socket : WARNING: one for all cmd sockets
55 Address cmd_gw_httpaddr;                  // HTTP gateway address for PLAY cmd
56
57
58 bool cmd_gw_debug=false;
59
60
61 // Fwd defs
62 void CmdGwDataCameInCallback(struct bufferevent *bev, void *ctx);
63 bool CmdGwReadLine(evutil_socket_t cmdsock);
64 void CmdGwNewRequestCallback(evutil_socket_t cmdsock, char *line);
65
66
67
68 void CmdGwFreeRequest(cmd_gw_t* req)
69 {
70     if (req->contentfilename != NULL)
71         free(req->contentfilename);
72     // Arno, 2012-02-06: Reset, in particular moreinfo flag.
73     memset(req,'\0',sizeof(cmd_gw_t));
74 }
75
76
77 void CmdGwCloseConnection(evutil_socket_t sock)
78 {
79         // Close cmd connection and stop all associated downloads.
80         // Doesn't remove .mhash state or content
81
82         bool scanning = true;
83         while (scanning)
84         {
85                 scanning = false;
86             for(int i=0; i<cmd_gw_reqs_open; i++)
87             {
88                 cmd_gw_t* req = &cmd_requests[i];
89                 if (req->cmdsock==sock)
90                 {
91                 dprintf("%s @%i stopping-on-close transfer %i\n",tintstr(),req->id,req->transfer);
92                 swift::Close(req->transfer);
93
94                 // Remove from list and reiterate over it
95                 CmdGwFreeRequest(req);
96                         *req = cmd_requests[--cmd_gw_reqs_open];
97                         scanning = true;
98                         break;
99                 }
100             }
101         }
102 }
103
104
105 cmd_gw_t* CmdGwFindRequestByTransfer (int transfer)
106 {
107     for(int i=0; i<cmd_gw_reqs_open; i++)
108         if (cmd_requests[i].transfer==transfer)
109             return cmd_requests+i;
110     return NULL;
111 }
112
113 cmd_gw_t* CmdGwFindRequestByRootHash(Sha1Hash &want_hash)
114 {
115         FileTransfer *ft = NULL;
116     for(int i=0; i<cmd_gw_reqs_open; i++) {
117         cmd_gw_t* req = &cmd_requests[i];
118         ft = FileTransfer::file(req->transfer);
119         Sha1Hash got_hash = ft->root_hash();
120         if (want_hash == got_hash)
121                 return req;
122     }
123     return NULL;
124 }
125
126
127 void CmdGwGotCHECKPOINT(Sha1Hash &want_hash)
128 {
129         // Checkpoint the specified download
130         fprintf(stderr,"cmd: GotCHECKPOINT: %s\n",want_hash.hex().c_str());
131
132         cmd_gw_t* req = CmdGwFindRequestByRootHash(want_hash);
133         if (req == NULL)
134         return;
135     FileTransfer *ft = FileTransfer::file(req->transfer);
136
137         std::string binmap_filename = ft->file().filename();
138         binmap_filename.append(".mbinmap");
139         fprintf(stderr,"cmdgw: GotCHECKPOINT: checkpointing to %s\n", binmap_filename.c_str() );
140         FILE *fp = fopen(binmap_filename.c_str(),"wb");
141         if (!fp) {
142                 print_error("cannot open mbinmap for writing");
143                 return;
144         }
145         if (ft->file().serialize(fp) < 0)
146                 print_error("writing to mbinmap");
147         fclose(fp);
148 }
149
150
151 void CmdGwGotREMOVE(Sha1Hash &want_hash, bool removestate, bool removecontent)
152 {
153         // Remove the specified download
154         fprintf(stderr,"cmd: GotREMOVE: %s %d %d\n",want_hash.hex().c_str(),removestate,removecontent);
155
156         cmd_gw_t* req = CmdGwFindRequestByRootHash(want_hash);
157         if (req == NULL)
158         return;
159     FileTransfer *ft = FileTransfer::file(req->transfer);
160
161         fprintf(stderr, "%s @%i remove transfer %i\n",tintstr(),req->id,req->transfer);
162         dprintf("%s @%i remove transfer %i\n",tintstr(),req->id,req->transfer);
163         swift::Close(req->transfer);
164
165         // Delete content + .mhash from filesystem, if desired
166         if (removecontent)
167                 remove(req->contentfilename);
168
169         if (removestate)
170         {
171                 char *mhashfilename = (char *)malloc(strlen(req->contentfilename)+strlen(".mhash")+1);
172                 strcpy(mhashfilename,req->contentfilename);
173                 strcat(mhashfilename,".mhash");
174
175                 remove(mhashfilename);
176                 free(mhashfilename);
177
178                 // Arno, 2012-01-10: .mbinmap gots to go too.
179                 char *mbinmapfilename = (char *)malloc(strlen(req->contentfilename)+strlen(".mbinmap")+1);
180                 strcpy(mbinmapfilename,req->contentfilename);
181                 strcat(mbinmapfilename,".mbinmap");
182
183                 remove(mbinmapfilename);
184                 free(mbinmapfilename);
185         }
186
187         CmdGwFreeRequest(req);
188         *req = cmd_requests[--cmd_gw_reqs_open];
189 }
190
191
192 void CmdGwGotMAXSPEED(Sha1Hash &want_hash, data_direction_t ddir, double speed)
193 {
194         // Set maximum speed on the specified download
195         fprintf(stderr,"cmd: GotMAXSPEED: %s %d %lf\n",want_hash.hex().c_str(),ddir,speed);
196
197         cmd_gw_t* req = CmdGwFindRequestByRootHash(want_hash);
198         if (req == NULL)
199         return;
200     FileTransfer *ft = FileTransfer::file(req->transfer);
201         ft->SetMaxSpeed(ddir,speed);
202 }
203
204
205 void CmdGwGotSETMOREINFO(Sha1Hash &want_hash, bool enable)
206 {
207         cmd_gw_t* req = CmdGwFindRequestByRootHash(want_hash);
208         if (req == NULL)
209         return;
210         req->moreinfo = enable;
211 }
212
213
214 void CmdGwSendINFOHashChecking(cmd_gw_t* req, Sha1Hash root_hash)
215 {
216         // Send INFO DLSTATUS_HASHCHECKING message.
217
218     char cmd[MAX_CMD_MESSAGE];
219         sprintf(cmd,"INFO %s %d %lli/%lli %lf %lf %u %u\r\n",root_hash.hex().c_str(),DLSTATUS_HASHCHECKING,(uint64_t)0,(uint64_t)0,0.0,0.0,0,0);
220
221     //fprintf(stderr,"cmd: SendINFO: %s", cmd);
222     send(req->cmdsock,cmd,strlen(cmd),0);
223 }
224
225
226 void CmdGwSendINFO(cmd_gw_t* req, int dlstatus)
227 {
228         // Send INFO message.
229         if (cmd_gw_debug)
230                 fprintf(stderr,"cmd: SendINFO: %d %d\n", req->transfer, dlstatus );
231
232         FileTransfer *ft = FileTransfer::file(req->transfer);
233         if (ft == NULL)
234                 // Download was removed or closed somehow.
235                 return;
236
237     Sha1Hash root_hash = ft->root_hash();
238
239     char cmd[MAX_CMD_MESSAGE];
240     uint64_t size = swift::Size(req->transfer);
241     uint64_t complete = swift::Complete(req->transfer);
242     if (size == complete)
243         dlstatus = DLSTATUS_SEEDING;
244
245     uint32_t numleech = ft->GetNumLeechers();
246     uint32_t numseeds = ft->GetNumSeeders();
247     sprintf(cmd,"INFO %s %d %lli/%lli %lf %lf %u %u\r\n",root_hash.hex().c_str(),dlstatus,complete,size,ft->GetCurrentSpeed(DDIR_DOWNLOAD),ft->GetCurrentSpeed(DDIR_UPLOAD),numleech,numseeds);
248
249     //fprintf(stderr,"cmd: SendINFO: %s", cmd);
250     send(req->cmdsock,cmd,strlen(cmd),0);
251
252     // MORESTATS
253     if (req->moreinfo) {
254         // Send detailed ul/dl stats in JSON format.
255
256         std::ostringstream oss;
257         oss.setf(std::ios::fixed,std::ios::floatfield);
258         oss.precision(5);
259         std::set<Channel *>::iterator iter;
260         std::set<Channel *> peerchans = ft->GetChannels();
261
262         oss << "MOREINFO" << " " << root_hash.hex() << " ";
263
264         double tss = (double)Channel::Time() / 1000000.0L;
265         oss << "{\"timestamp\":\"" << tss << "\", ";
266         oss << "\"channels\":";
267         oss << "[";
268         for (iter=peerchans.begin(); iter!=peerchans.end(); iter++) {
269                 Channel *c = *iter;
270                 if (c != NULL) {
271                         if (iter!=peerchans.begin())
272                                 oss << ", ";
273                         oss << "{";
274                         oss << "\"ip\": \"" << c->peer().ipv4str() << "\", ";
275                         oss << "\"port\": " << c->peer().port() << ", ";
276                         oss << "\"raw_bytes_up\": " << c->raw_bytes_up() << ", ";
277                         oss << "\"raw_bytes_down\": " << c->raw_bytes_down() << ", ";
278                         oss << "\"bytes_up\": " << c->bytes_up() << ", ";
279                         oss << "\"bytes_down\": " << c->bytes_down() << " ";
280                         oss << "}";
281                 }
282         }
283         oss << "], ";
284         oss << "\"raw_bytes_up\": " << Channel::global_raw_bytes_up << ", ";
285         oss << "\"raw_bytes_down\": " << Channel::global_raw_bytes_down << ", ";
286         oss << "\"bytes_up\": " << Channel::global_bytes_up << ", ";
287         oss << "\"bytes_down\": " << Channel::global_bytes_down << " ";
288         oss << "}";
289
290         oss << "\r\n";
291
292         std::stringbuf *pbuf=oss.rdbuf();
293         size_t slen = strlen(pbuf->str().c_str());
294         send(req->cmdsock,pbuf->str().c_str(),slen,0);
295     }
296 }
297
298
299 void CmdGwSendPLAY(int transfer)
300 {
301         // Send PLAY message to user
302         if (cmd_gw_debug)
303                 fprintf(stderr,"cmd: SendPLAY: %d\n", transfer );
304
305     cmd_gw_t* req = CmdGwFindRequestByTransfer(transfer);
306     Sha1Hash root_hash = FileTransfer::file(transfer)->root_hash();
307
308     char cmd[MAX_CMD_MESSAGE];
309     // Slightly diff format: roothash as ID after CMD
310     sprintf(cmd,"PLAY %s http://%s/%s\r\n",root_hash.hex().c_str(),cmd_gw_httpaddr.str(),root_hash.hex().c_str());
311
312     fprintf(stderr,"cmd: SendPlay: %s", cmd);
313
314     send(req->cmdsock,cmd,strlen(cmd),0);
315 }
316
317
318 void CmdGwSwiftFirstProgressCallback (int transfer, bin_t bin)
319 {
320         // First CMDGW_MAX_PREBUF_BYTES bytes received via swift,
321         // tell user to PLAY
322         // ARNOSMPTODO: bitrate-dependent prebuffering?
323         if (cmd_gw_debug)
324                 fprintf(stderr,"cmd: SwiftFirstProgress: %d\n", transfer );
325
326         swift::RemoveProgressCallback(transfer,&CmdGwSwiftFirstProgressCallback);
327
328         CmdGwSendPLAY(transfer);
329 }
330
331
332 void CmdGwSwiftErrorCallback (evutil_socket_t cmdsock)
333 {
334         // Error on swift socket callback
335
336         const char *response = "ERROR Swift Engine Problem\r\n";
337         send(cmdsock,response,strlen(response),0);
338
339         //swift::close_socket(sock);
340 }
341
342
343
344 void CmdGwUpdateDLStateCallback(cmd_gw_t* req)
345 {
346         // Periodic callback, tell user INFO
347         CmdGwSendINFO(req,DLSTATUS_DOWNLOADING);
348
349         // Update speed measurements such that they decrease when DL/UL stops
350         FileTransfer *ft = FileTransfer::file(req->transfer);
351         ft->OnRecvData(0);
352         ft->OnSendData(0);
353
354         if (false)
355         {
356                 // DEBUG download speed rate limit
357                 double dlspeed = ft->GetCurrentSpeed(DDIR_DOWNLOAD);
358 #ifdef WIN32
359                 double dt = max(0.000001,(double)(usec_time() - req->startt)/TINT_SEC);
360 #else
361                 double dt = std::max(0.000001,(double)(usec_time() - req->startt)/TINT_SEC);
362 #endif
363                 double exspeed = (double)(swift::Complete(req->transfer)) / dt;
364                 fprintf(stderr,"cmd: UpdateDLStateCallback: SPEED %lf == %lf\n", dlspeed, exspeed );
365         }
366 }
367
368
369 void CmdGwUpdateDLStatesCallback()
370 {
371         // Called by swift main approximately every second
372         // Loop over all swarms
373     for(int i=0; i<cmd_gw_reqs_open; i++)
374     {
375         cmd_gw_t* req = &cmd_requests[i];
376         CmdGwUpdateDLStateCallback(req);
377     }
378 }
379
380
381
382 void CmdGwDataCameInCallback(struct bufferevent *bev, void *ctx)
383 {
384         // Turn TCP stream into lines deliniated by \r\n
385         evutil_socket_t cmdsock = bufferevent_getfd(bev);
386         if (cmd_gw_debug)
387                 fprintf(stderr,"CmdGwDataCameIn: ENTER %d\n", cmdsock );
388
389         struct evbuffer *inputevbuf = bufferevent_get_input(bev);
390         int ret = evbuffer_add_buffer(cmd_evbuffer,inputevbuf);
391         if (ret == -1) {
392                 CmdGwCloseConnection(cmdsock);
393                 return;
394         }
395
396
397         while (CmdGwReadLine(cmdsock))
398                 ;
399 }
400
401 bool CmdGwReadLine(evutil_socket_t cmdsock)
402 {
403         // Parse cmd_evbuffer for lines, and call NewRequest when found
404
405         size_t rd=0;
406     char *cmd = evbuffer_readln(cmd_evbuffer,&rd, EVBUFFER_EOL_CRLF_STRICT);
407     if (cmd != NULL)
408     {
409         CmdGwNewRequestCallback(cmdsock,cmd);
410         free(cmd);
411         return true;
412     }
413     else
414         return false;
415 }
416
417 int CmdGwHandleCommand(evutil_socket_t cmdsock, char *copyline);
418
419 void CmdGwNewRequestCallback(evutil_socket_t cmdsock, char *line)
420 {
421         // New command received from user
422
423     // CMD request line
424         char *copyline = (char *)malloc(strlen(line)+1);
425         strcpy(copyline,line);
426
427         int ret = CmdGwHandleCommand(cmdsock,copyline);
428         if (ret < 0) {
429                 dprintf("cmd: Error parsing command %s\n", line );
430                 char *cmd = NULL;
431                 if (ret == ERROR_UNKNOWN_CMD)
432                         cmd = "ERROR unknown command\r\n";
433                 else if (ret == ERROR_MISS_ARG)
434                         cmd = "ERROR missing parameter\r\n";
435                 else
436                         cmd = "ERROR bad parameter\r\n";
437                 send(cmdsock,cmd,strlen(cmd),0);
438         CmdGwCloseConnection(cmdsock);
439         }
440
441     free(copyline);
442 }
443
444
445 int CmdGwHandleCommand(evutil_socket_t cmdsock, char *copyline)
446 {
447         char *method=NULL,*paramstr = NULL;
448         char * token = strchr(copyline,' '); // split into CMD PARAM
449         if (token != NULL) {
450                 *token = '\0';
451                 paramstr = token+1;
452         }
453         else
454                 paramstr = "";
455
456         method = copyline;
457
458     fprintf(stderr,"cmd: GOT %s %s\n", method, paramstr);
459
460     char *savetok = NULL;
461     if (!strcmp(method,"START"))
462     {
463         // New START request
464         cmd_gw_t* req = cmd_requests + cmd_gw_reqs_open++;
465         req->id = ++cmd_gw_reqs_count;
466         req->cmdsock = cmdsock;
467
468         //fprintf(stderr,"cmd: START: new request %i\n",req->id);
469
470         char *url = paramstr;
471         // parse URL
472                 // tswift://tracker/roothash-as-hex$chunksize@duration-in-secs
473         char *trackerstr=NULL,*hashstr=NULL,*durationstr=NULL,*chunksizestr=NULL;
474
475         bool haschunksize = (bool)(strchr(paramstr,'$') != NULL);
476         bool hasduration = (bool)(strchr(paramstr,'@') != NULL); // FAXME: user@ in tracker URL
477
478         token = strtok_r(url,"/",&savetok); // tswift://
479         if (token == NULL)
480                 return ERROR_MISS_ARG;
481         token = strtok_r(NULL,"/",&savetok);      // tracker:port
482         if (token == NULL)
483                 return ERROR_MISS_ARG;
484         trackerstr = token;
485
486         if (haschunksize && hasduration) {
487                 token = strtok_r(NULL,"$",&savetok);       // roothash
488                 if (token == NULL)
489                         return ERROR_BAD_ARG;
490                 hashstr = token;
491
492             token = strtok_r(NULL,"@",&savetok);                        // chunksize
493             if (token == NULL)
494                 return ERROR_BAD_ARG;
495             chunksizestr = token;
496
497                 token = strtok_r(NULL,"",&savetok);             // duration
498                 if (token == NULL)
499                         return ERROR_BAD_ARG;
500                 durationstr = token;
501         }
502         else if (haschunksize) {
503                 token = strtok_r(NULL,"$",&savetok);       // roothash
504                 if (token == NULL)
505                         return ERROR_BAD_ARG;
506                 hashstr = token;
507
508             token = strtok_r(NULL,"",&savetok);                 // chunksize
509             if (token == NULL)
510                         return ERROR_BAD_ARG;
511             chunksizestr = token;
512         }
513         else if (hasduration) {
514                 token = strtok_r(NULL,"@",&savetok);       // roothash
515                 if (token == NULL)
516                         return ERROR_BAD_ARG;
517                 hashstr = token;
518
519             token = strtok_r(NULL,"",&savetok);                 // duration
520             if (token == NULL)
521                         return ERROR_BAD_ARG;
522             durationstr = token;
523         }
524         else {
525                 token = strtok_r(NULL,"",&savetok);       // roothash
526                 if (token == NULL)
527                         return ERROR_BAD_ARG;
528                 hashstr = token;
529         }
530
531         dprintf("cmd: START: parsed tracker %s hash %s dur %s cs %s\n",trackerstr,hashstr,durationstr,chunksizestr);
532
533         if (strlen(hashstr)!=40) {
534                 dprintf("cmd: START: roothash too short %i\n", strlen(hashstr) );
535             return ERROR_BAD_ARG;
536         }
537         uint32_t chunksize=SWIFT_DEFAULT_CHUNK_SIZE;
538         if (haschunksize) {
539                 int n = sscanf(chunksizestr,"%i",&chunksize);
540                 if (n != 1)
541                         return ERROR_BAD_ARG;
542         }
543         int duration=0;
544         if (hasduration) {
545                 int n = sscanf(durationstr,"%i",&duration);
546                 if (n != 1)
547                         return ERROR_BAD_ARG;
548         }
549
550         dprintf("cmd: START: %s with tracker %s chunksize %i duration %i\n",hashstr,trackerstr,chunksize,duration);
551
552         // FAXME: return duration in HTTPGW
553
554         Address trackaddr;
555                 trackaddr = Address(trackerstr);
556                 if (trackaddr==Address())
557                 {
558                         dprintf("cmd: START: tracker address must be hostname:port, ip:port or just port\n");
559                 return ERROR_BAD_ARG;
560                 }
561                 // SetTracker(trackaddr); == set default tracker
562
563         // initiate transmission
564         Sha1Hash root_hash = Sha1Hash(true,hashstr);
565
566         // Send INFO DLSTATUS_HASHCHECKING
567                 CmdGwSendINFOHashChecking(req,root_hash);
568
569                 // ARNOSMPTODO: disable/interleave hashchecking at startup
570         int transfer = swift::Find(root_hash);
571         if (transfer==-1)
572             transfer = swift::Open(hashstr,root_hash,trackaddr,false,true,chunksize);
573
574         // RATELIMIT
575         //FileTransfer::file(transfer)->SetMaxSpeed(DDIR_DOWNLOAD,512*1024);
576
577         req->transfer = transfer;
578         req->startt = usec_time();
579
580         // See HashTree::HashTree
581         req->contentfilename = (char *)malloc(strlen(hashstr)+1);
582         strcpy(req->contentfilename,hashstr);
583
584         if (cmd_gw_debug)
585                 fprintf(stderr,"cmd: Already on disk is %lli/%lli\n", swift::Complete(transfer), swift::Size(transfer));
586
587         // Wait for prebuffering and then send PLAY to user
588         // ARNOSMPTODO: OUTOFORDER: breaks with out-of-order download
589         if (swift::Size(transfer) >= CMDGW_MAX_PREBUF_BYTES)
590         {
591             CmdGwSwiftFirstProgressCallback(transfer,bin_t(0,0));
592             CmdGwSendINFO(req, DLSTATUS_DOWNLOADING);
593         }
594         else
595         {
596                 int progresslayer = bytes2layer(CMDGW_MAX_PREBUF_BYTES,swift::ChunkSize(transfer));
597             swift::AddProgressCallback(transfer,&CmdGwSwiftFirstProgressCallback,progresslayer);
598         }
599     }
600     else if (!strcmp(method,"REMOVE"))
601     {
602         // REMOVE roothash removestate removecontent\r\n
603         bool removestate = false, removecontent = false;
604
605         token = strtok_r(paramstr," ",&savetok); //
606         if (token == NULL)
607                 return ERROR_MISS_ARG;
608         char *hashstr = token;
609         token = strtok_r(NULL," ",&savetok);      // removestate
610         if (token == NULL)
611                 return ERROR_MISS_ARG;
612         removestate = !strcmp(token,"1");
613         token = strtok_r(NULL,"",&savetok);       // removecontent
614         if (token == NULL)
615                 return ERROR_MISS_ARG;
616         removecontent = !strcmp(token,"1");
617
618         Sha1Hash root_hash = Sha1Hash(true,hashstr);
619         CmdGwGotREMOVE(root_hash,removestate,removecontent);
620     }
621     else if (!strcmp(method,"MAXSPEED"))
622     {
623         // MAXSPEED roothash direction speed-float-kb/s\r\n
624         data_direction_t ddir;
625         double speed;
626
627         token = strtok_r(paramstr," ",&savetok); //
628         if (token == NULL)
629                 return ERROR_MISS_ARG;
630         char *hashstr = token;
631         token = strtok_r(NULL," ",&savetok);      // direction
632         if (token == NULL)
633                 return ERROR_MISS_ARG;
634         ddir = !strcmp(token,"DOWNLOAD") ? DDIR_DOWNLOAD : DDIR_UPLOAD;
635         token = strtok_r(NULL,"",&savetok);       // speed
636         if (token == NULL)
637                 return ERROR_MISS_ARG;
638         int n = sscanf(token,"%lf",&speed);
639         if (n == 0) {
640                 dprintf("cmd: MAXSPEED: speed is not a float\n");
641                         return ERROR_MISS_ARG;
642         }
643         Sha1Hash root_hash = Sha1Hash(true,hashstr);
644         CmdGwGotMAXSPEED(root_hash,ddir,speed*1024.0);
645     }
646     else if (!strcmp(method,"CHECKPOINT"))
647     {
648         // CHECKPOINT roothash\r\n
649         Sha1Hash root_hash = Sha1Hash(true,paramstr);
650         CmdGwGotCHECKPOINT(root_hash);
651     }
652     else if (!strcmp(method,"SETMOREINFO"))
653     {
654         // GETMOREINFO roothash toggle\r\n
655         token = strtok_r(paramstr," ",&savetok); //
656         if (token == NULL)
657                 return ERROR_MISS_ARG;
658         char *hashstr = token;
659         token = strtok_r(NULL," ",&savetok);      // direction
660         if (token == NULL)
661                 return ERROR_MISS_ARG;
662         bool enable = (bool)!strcmp(token,"1");
663         Sha1Hash root_hash = Sha1Hash(true,hashstr);
664         CmdGwGotSETMOREINFO(root_hash,enable);
665     }
666     else if (!strcmp(method,"SHUTDOWN"))
667     {
668         CmdGwCloseConnection(cmdsock);
669         // Tell libevent to stop processing events
670         event_base_loopexit(Channel::evbase, NULL);
671     }
672     else
673     {
674         return ERROR_UNKNOWN_CMD;
675     }
676
677     return ERROR_NO_ERROR;
678 }
679
680
681
682 void CmdGwEventCameInCallback(struct bufferevent *bev, short events, void *ctx)
683 {
684         if (events & BEV_EVENT_ERROR)
685                 print_error("cmdgw: Error from bufferevent");
686     if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
687     {
688         // Called when error on cmd connection
689         evutil_socket_t cmdsock = bufferevent_getfd(bev);
690         CmdGwCloseConnection(cmdsock);
691                 bufferevent_free(bev);
692     }
693 }
694
695
696 void CmdGwNewConnectionCallback(struct evconnlistener *listener,
697     evutil_socket_t fd, struct sockaddr *address, int socklen,
698     void *ctx)
699 {
700         // New TCP connection on cmd listen socket
701
702         fprintf(stderr,"cmd: Got new cmd connection %i\n",fd);
703     dprintf("DBG cmd: Got new cmd connection %i\n",fd);
704
705         struct event_base *base = evconnlistener_get_base(listener);
706         struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
707
708         bufferevent_setcb(bev, CmdGwDataCameInCallback, NULL, CmdGwEventCameInCallback, NULL);
709         bufferevent_enable(bev, EV_READ|EV_WRITE);
710
711
712         // One buffer for all cmd connections, reset
713         if (cmd_evbuffer != NULL)
714                 evbuffer_free(cmd_evbuffer);
715     cmd_evbuffer = evbuffer_new();
716 }
717
718
719 void CmdGwListenErrorCallback(struct evconnlistener *listener, void *ctx)
720 {
721         // libevent got error on cmd listener
722     struct event_base *base = evconnlistener_get_base(listener);
723     int err = EVUTIL_SOCKET_ERROR();
724     char errmsg[1024];
725     sprintf(errmsg, "cmdgw: Got a fatal error %d (%s) on the listener.\n", err, evutil_socket_error_to_string(err));
726
727     print_error(errmsg);
728     dprintf("%s @0 closed cmd gateway\n",tintstr());
729
730         evconnlistener_free(cmd_evlistener);
731 }
732
733
734 bool InstallCmdGateway (struct event_base *evbase,Address cmdaddr,Address httpaddr)
735 {
736         // Allocate libevent listener for cmd connections
737         // From http://www.wangafu.net/~nickm/libevent-book/Ref8_listener.html
738
739     fprintf(stderr,"cmdgw: Creating new listener on addr %s\n", cmdaddr.str() );
740   
741     struct sockaddr_in sin;
742         sin.sin_addr.s_addr = cmdaddr.addr->dests[0].addr;
743         sin.sin_port = cmdaddr.addr->dests[0].port;
744         sin.sin_family = AF_INET;
745
746     cmd_evlistener = evconnlistener_new_bind(evbase, CmdGwNewConnectionCallback, NULL,
747         LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
748         (const struct sockaddr *)&sin, sizeof(sin));
749     if (!cmd_evlistener) {
750             print_error("Couldn't create listener");
751             return false;
752     }
753     evconnlistener_set_error_cb(cmd_evlistener, CmdGwListenErrorCallback);
754
755     cmd_gw_httpaddr = httpaddr;
756
757     cmd_evbuffer = evbuffer_new();
758
759     return true;
760 }
761