Peer-to-peer GUI adapter

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

diep
Posts: 1822
Joined: Thu Mar 09, 2006 11:54 pm
Location: The Netherlands

Re: Peer-to-peer GUI adapter

Post by diep »

hgm wrote:I am now checking how I can port this to Linux, but I get the impression that all this socket stuff is pretty much platform independent. Is it really a matter of just changing a few #includes?
hi HGM,

Good project, very useful as well.

Sockets work diff under linux than windows. Both claim to be easier, but it's a pain for communication over the internet.

I have Diep already for many years working over network via GUI.
Your protocol is not sufficient. It's too easy to hack and mess up in this manner.

What's needed is things like password as well and in accepting GUI you need also DoS.

What i do there for chess server i built is use non-blocking manners. Also the protocol specs i have here for DEP protocol seem interesting.

A thing i stumbled upon is you need a reconnect. I have implemented that in DEP protocol.

So DEP protocol sees no difference between whether data gets from the NET or from a GUI.

You use a special GUI to GUI protocol here. Maybe let's set it up correct, will be most interesting :)

I'll dig up what i do there in Diep for you (engine can act as a server) for DEP protocol (diep engine protocol by the way):
diep
Posts: 1822
Joined: Thu Mar 09, 2006 11:54 pm
Location: The Netherlands

Re: Peer-to-peer GUI adapter

Post by diep »

hgm wrote:I am now checking how I can port this to Linux, but I get the impression that all this socket stuff is pretty much platform independent. Is it really a matter of just changing a few #includes?
Some cut'n pastes follow from my code:

Code: Select all

#if UNIXPII

#define SOCKET                int

#endif

volatile SOCKET remotesocket;


...



void SendUnmodified(char *txt) {

  /* voeg \n\r toe aan null terminated string en verstuur het naar user

   * zonder de null

   */

  int n=0,len;



  if( remotesocket == -1 || ProcessNumber > 0 ) return;



  len = (int)strlen(txt);



  n = send(remotesocket,(char *)txt,len,

  // #if UNIXPII

   0//0x40

   //#else windows NT

  // 0

  // #endif

   );

  #if UNIXPII

  if( n == -1 ) {

    printf("Send generated problem. closing remote socket!\n");

  #else

  if( n == SOCKET_ERROR ) {

    mprint("Send generated problem. closing remote socket!\n");

    closesocket(remotesocket);

  #endif

    remotesocket = -1;

  }

}



#if UNIXPII

void *RemoteInputThread(void *serverpoort) { /* i/o wordt multithreaded gedaan! */

#else

void RemoteInputThread(void *serverpoort) {

#endif



  char buf[8192];

  int serverport = 5500;

  int descriptor=0,ti;

  int cli_len;

  struct sockaddr_in servAddr;



  #if UNIXPII



  #else

  int err;

  WSADATA wsadat;

  err = WSAStartup(MAKEWORD(2,0), &wsadat);



  if( err != NO_ERROR ) {

    printf("Error at WSA Startup\n");

    #if UNIXPII

    return NULL;

    #else

    return;

    #endif

  }



  #endif

  for&#40; ti = 0; ti < 8192; ti++ )

    buf&#91;ti&#93; = '\0';



  serverport = *(&#40;int *&#41;serverpoort&#41;;

  Slapen&#40;2000&#41;;



  cli_len = &#40;int&#41; sizeof&#40;struct sockaddr_in&#41;;



  printf&#40;"binding port %i\n",serverport&#41;;

  descriptor = socket&#40;AF_INET,SOCK_STREAM,0&#41;;

  if&#40; descriptor < 0 ) &#123;

    printf&#40;"Can't open socket because of &#58; ");

    #if UNIXPII

    return NULL;

    #else

    return;

    #endif

  &#125;



  memset&#40;&servAddr,'\0',sizeof&#40;servAddr&#41;);

  servAddr.sin_family      = AF_INET;

  servAddr.sin_addr.s_addr = htonl&#40;INADDR_ANY&#41;;

  servAddr.sin_port        = htons&#40;serverport&#41;;



  if&#40; bind&#40;descriptor, &#40;struct sockaddr *) &servAddr, sizeof&#40;servAddr&#41;)<0&#41; &#123;

    printf&#40;"cannot bind port %i\n",serverport&#41;;

    perror&#40;"cannot bind port");

    #if UNIXPII

    return NULL;

    #else

    return;

    #endif

  &#125;



  if&#40; !listen&#40;descriptor,5&#41; ) &#123;

    printf&#40;"listen succesful. Receiving thread installed correctly!\n");

  &#125;

  else &#123;

    printf&#40;"error listening!\n");

    #if UNIXPII

    return NULL;

    #else

    return;

    #endif

  &#125;



  for&#40; ;; ) &#123;

    SOCKET rsock;

    int terug,currentbytes=0;

    if&#40; &#40;rsock=accept&#40;descriptor,&#40;struct sockaddr *)&servAddr,/*&#40;socklen_t *)*/&cli_len&#41;) != -1 ) &#123;

      int gotpassword=0;

      remotesocket = rsock;

      printf&#40;"Accepted remote socket connection=%i. Awaiting password.\n",remotesocket&#41;;

      /* accepted a connection */

      for&#40; ;; ) &#123;

        terug=recv&#40;remotesocket,&#40;char *)&buf&#91;currentbytes&#93;,4095,0&#41;;

        if&#40; terug == -1 || terug == 0 ) &#123;

          printf&#40;"Error connection closed! Trying to get new. restart otherwise!\n");

          remotesocket = -1;

          break;

        &#125;

        else &#123; // DOS = 13 10, UNIX = 10

          char *pstr;

          currentbytes += terug;

          buf&#91;currentbytes&#93; = '\0';

          //printf&#40;"raw remote got %i bytes string='%s' total=%i bytes\n",terug,buf,currentbytes&#41;;

          if&#40; currentbytes > 4000 ) &#123;

            currentbytes = 0;

            continue; // we received nonsense, start a new buffer

            // we should actually disconnect this dude

          &#125;



          for&#40; ;; ) &#123;

            int i,j;

            if&#40; dinput.commandlinefilled ) &#123;

              Slapen&#40;100&#41;;

              continue;

            &#125;



            if&#40; &#40;pstr = strchr&#40;buf,'\n')) == NULL )

              break;



            for&#40; i = 0 ; i < currentbytes ; i++ ) &#123;

              if&#40; buf&#91;i&#93; == 10 || buf&#91;i&#93; == 13 )

                break;

              dinput.buf&#91;i&#93; = buf&#91;i&#93;;

            &#125;



            if&#40; i > 0 ) &#123;

              dinput.buf&#91;i&#93; = '\0';

              if&#40; !gotpassword && strcmp&#40;PasswordString,"none") ) &#123;

                printf&#40;"received remote password = '%s'\n",dinput.buf&#41;;

                if&#40; strcmp&#40;dinput.buf,"PasswordString") == 0 ) &#123;

                  currentbytes = 0;

                  gotpassword = 1;

                  break;

                &#125;

                else &#123; // kick the connection the password didn't match

                  printf&#40;"Closing socket - wrong password\n");

                  #if UNIXPII

                  close&#40;remotesocket&#41;;

                  #else

                  closesocket&#40;remotesocket&#41;;

                  #endif

                &#125;

              &#125;

              else &#123;

                dinput.commandlinefilled = i;

                printf&#40;"received remote %i bytes&#58; '%s'\n",i,dinput.buf&#41;;

              &#125;

            &#125;



            /* shift left the string out of buf now&#58; */

            if&#40; buf&#91;i&#93; == 13 && buf&#91;i+1&#93; == 10 ) // DOS

              i++;

            i++; // #bytes to be removed from buf

            j = 0;

            while&#40; i < currentbytes ) &#123;

              buf&#91;j&#93; = buf&#91;i&#93;;

              i++;

              j++;

            &#125;

            buf&#91;j&#93; = '\0';

            currentbytes = j;

          &#125;

        &#125;

      &#125;

    &#125;

    Slapen&#40;2000&#41;;

  &#125;

  #if UNIXPII

  return NULL;

  #else

  return;

  #endif

&#125;
heh cut'n pasting on os/x always same problem...
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Peer-to-peer GUI adapter

Post by hgm »

I don't know what the difference is in how the sockets work, but the Windows code I had made could be compiled without problems under Linux, after I changed the includes and defined some macros (like INVALID_SOCKET) which apparently are not standard in Linux. And the compiled version could connect to its peer on a Windows computer.

It is good you remind me of the security issues. I guess what I have now fals short of the mark there. I must really make sure there cannot be any buffer overruns when someone tries to send toxic lines. The actual recv call contains the buffer length, of course, but then I start pasting received packets together until the input contains a linefeed, and that is the dangerous part.

It could be OK, though, as I paste them together in the receive buffer, making sure it will not overflow, and only then copy the first line it contains to the line buffer, which is of equal size:

Code: Select all

int
ReadFromPeer &#40;SOCKET s, char *buf&#41;
&#123;
  int res;
  char *p, *q;
  static char rcvBuf&#91;8001&#93;;
  static int cnt;
  while&#40;&#40;p = strchr&#40;rcvBuf, '\n')) == NULL&#41; &#123;
    if&#40;state == DISCONNECTED&#41; return 0;
    res = recv&#40;s, rcvBuf+cnt, 8000-cnt, 0&#41;;
    if&#40;!res&#41; printf&#40;"telluser connection closed by peer\n");
    if&#40;res < 0&#41; &#123;
      printf&#40;"telluser recv failed with error&#58; %d\n", WSAGetLastError&#40;)), fflush&#40;stdout&#41;;
      res = 0;
    &#125;
    if&#40;!res&#41; return 0;
    cnt += res;
  &#125;
  rcvBuf&#91;cnt&#93; = 0; p++;
  for&#40;q = rcvBuf; q < p; ) *buf++ = *q++; *buf = 0;
  for&#40;q = rcvBuf; p < rcvBuf+cnt; ) *q++ = *p++; *q = 0;
  cnt = q - rcvBuf;
  return 1;
&#125;
So unless the recv call starts to act strange when the buffer length is specified as 0, it should generate an EOF condition (return 0;) when the buffer overflows, leading to the closing of the socket.

Adding a password is a good idea. I could add that as another engine string option: the client would send it after getting the 'hallo' message, and the server would then verify it for equality, and close the connection otherwise.
Last edited by hgm on Mon Jul 16, 2012 7:56 pm, edited 1 time in total.
diep
Posts: 1822
Joined: Thu Mar 09, 2006 11:54 pm
Location: The Netherlands

Re: Peer-to-peer GUI adapter

Post by diep »

hgm wrote:I don't know what the difference is in how the sockets work, but the Windows code I had made could be compiled without problems under Linux, after I changed the includes and defined some macros (like INVALID_SOCKET) which apparently are not standard in Linux. And the compiled version could connect to its peer on a Windows computer.

It is good you remind me of the security issues. I guess what I have now fals short of the mark there. I must really make sure there cannot be any buffer overruns when someone tries to send toxic lines. The actual recv call contains the buffer length, of course, but then I start pasting received packets together until the input contains a linefeed, and that is the dangerous part.

It could be OK, though, as I paste them together in the receive buffer, making sure it will not overflow, and only then copy the first line it contains to the line buffer, which is of equal size:

Code: Select all

int
ReadFromPeer &#40;SOCKET s, char *buf&#41;
&#123;
  int res;
  char *p, *q;
  static char rcvBuf&#91;8001&#93;;
  static int cnt;
  while&#40;&#40;p = strchr&#40;rcvBuf, '\n')) == NULL&#41; &#123;
    if&#40;state == DISCONNECTED&#41; return 0;
    res = recv&#40;s, rcvBuf+cnt, 8000-cnt, 0&#41;;
    if&#40;!res&#41; printf&#40;"telluser connection closed by peer\n");
    if&#40;res < 0&#41; &#123;
      printf&#40;"telluser recv failed with error&#58; %d\n", WSAGetLastError&#40;)), fflush&#40;stdout&#41;;
      res = 0;
    &#125;
    if&#40;!res&#41; return 0;
    cnt += res;
  &#125;
  rcvBuf&#91;cnt&#93; = 0; p++;
  for&#40;q = rcvBuf; q < p; ) *buf++ = *q++; *buf = 0;
  for&#40;q = rcvBuf; p < rcvBuf+cnt; ) *q++ = *p++; *q = 0;
  cnt = q - rcvBuf;
  return 1;
&#125;
So unless the recv all starts to act strange when the buffer length is specified as 0, it should generate en EOF condition (return 0;) when the buffer overflows, leading to the clocing of the socket.

Adding a password is a good idea. I could add that as another engine string option: the client would send it after getting the 'hallo' message, and the server would then verify it for equality, and close the connection otherwise.
Yeah the net is a buggy place.

Here is some more code. As you see, one of the big problems of winboard protocol is that it doesn't ship an ack. That's a bug.

Here is how i do thread to get input.

So you can both type at commandline and receive data at same time then over a socket.

That's the way to do it :)

Code: Select all

#if UNIXPII

void *DepInputThread&#40;void *dummy&#41; &#123; /* i/o wordt multithreaded gedaan! */

#else

void __cdecl DepInputThread&#40;void *dummy&#41; &#123;

#endif

  Slapen&#40;500&#41;;

  for&#40; ;; ) &#123;

    if&#40; dinput.commandlinefilled )

      Slapen&#40;50&#41;;

    else &#123; // receive input

      char *returncode;

      int len;

      returncode = fgets&#40;dinput.buf,8191,stdin&#41;;

      if&#40; returncode == NULL )

        Slapen&#40;100&#41;;

      else &#123;

        //char writeit&#91;1024&#93;;

        int orglen;

        len = orglen = strlen&#40;dinput.buf&#41;;

        //sprintf&#40;writeit,"inputbuf has %i characters having &#91;%s&#93;\n",

        // len,dinput.buf&#41;;

        //PrintToFile&#40;"happens.log",writeit&#41;;

        while&#40; len > 0 ) &#123;

          if&#40; dinput.buf&#91;len-1&#93; == 10 || dinput.buf&#91;len-1&#93; == 13 )

            len--;

          else

            break;

        &#125;

        // en wat als we meerdere regels at once krijgen

        // niet gesplit door 10 noch 13, maar door 0, namelijk end of string

        // ok dat werkt correct dan doordat fgets tot \n leest en DEP voorschrijft

        // dat \n er moet zijn

        dinput.buf&#91;len&#93; = '\0';

        printf&#40;"received %i bytes&#58; '%s'\n",orglen,dinput.buf&#41;;

        if&#40; FlagDep ) ShipAck&#40;);

        dinput.commandlinefilled = len;

        if&#40; dinput.buf&#91;0&#93; == 'q'

         && dinput.buf&#91;1&#93; == 'u'

         && dinput.buf&#91;2&#93; == 'i'

         && dinput.buf&#91;3&#93; == 't' )

          return; // terminate thread!

      &#125;

    &#125;

  &#125;

&#125;


diep
Posts: 1822
Joined: Thu Mar 09, 2006 11:54 pm
Location: The Netherlands

Re: Peer-to-peer GUI adapter

Post by diep »

hgm wrote:I don't know what the difference is in how the sockets work, but the Windows code I had made could be compiled without problems under Linux, after I changed the includes and defined some macros (like INVALID_SOCKET) which apparently are not standard in Linux. And the compiled version could connect to its peer on a Windows computer.

It is good you remind me of the security issues. I guess what I have now fals short of the mark there. I must really make sure there cannot be any buffer overruns when someone tries to send toxic lines. The actual recv call contains the buffer length, of course, but then I start pasting received packets together until the input contains a linefeed, and that is the dangerous part.

It could be OK, though, as I paste them together in the receive buffer, making sure it will not overflow, and only then copy the first line it contains to the line buffer, which is of equal size:

Code: Select all

int
ReadFromPeer &#40;SOCKET s, char *buf&#41;
&#123;
  int res;
  char *p, *q;
  static char rcvBuf&#91;8001&#93;;
  static int cnt;
  while&#40;&#40;p = strchr&#40;rcvBuf, '\n')) == NULL&#41; &#123;
    if&#40;state == DISCONNECTED&#41; return 0;
    res = recv&#40;s, rcvBuf+cnt, 8000-cnt, 0&#41;;
    if&#40;!res&#41; printf&#40;"telluser connection closed by peer\n");
    if&#40;res < 0&#41; &#123;
      printf&#40;"telluser recv failed with error&#58; %d\n", WSAGetLastError&#40;)), fflush&#40;stdout&#41;;
      res = 0;
    &#125;
    if&#40;!res&#41; return 0;
    cnt += res;
  &#125;
  rcvBuf&#91;cnt&#93; = 0; p++;
  for&#40;q = rcvBuf; q < p; ) *buf++ = *q++; *buf = 0;
  for&#40;q = rcvBuf; p < rcvBuf+cnt; ) *q++ = *p++; *q = 0;
  cnt = q - rcvBuf;
  return 1;
&#125;
So unless the recv call starts to act strange when the buffer length is specified as 0, it should generate an EOF condition (return 0;) when the buffer overflows, leading to the closing of the socket.

Adding a password is a good idea. I could add that as another engine string option: the client would send it after getting the 'hallo' message, and the server would then verify it for equality, and close the connection otherwise.
Note in your code you just catch newline. Better check for both 10 and 13.
So that your string doesn't have a 10 nor 13 behind it.
diep
Posts: 1822
Joined: Thu Mar 09, 2006 11:54 pm
Location: The Netherlands

Re: Peer-to-peer GUI adapter

Post by diep »

hgm wrote:I don't know what the difference is in how the sockets work, but the Windows code I had made could be compiled without problems under Linux, after I changed the includes and defined some macros (like INVALID_SOCKET) which apparently are not standard in Linux. And the compiled version could connect to its peer on a Windows computer.

It is good you remind me of the security issues. I guess what I have now fals short of the mark there. I must really make sure there cannot be any buffer overruns when someone tries to send toxic lines. The actual recv call contains the buffer length, of course, but then I start pasting received packets together until the input contains a linefeed, and that is the dangerous part.

It could be OK, though, as I paste them together in the receive buffer, making sure it will not overflow, and only then copy the first line it contains to the line buffer, which is of equal size:

Code: Select all

int
ReadFromPeer &#40;SOCKET s, char *buf&#41;
&#123;
  int res;
  char *p, *q;
  static char rcvBuf&#91;8001&#93;;
  static int cnt;
  while&#40;&#40;p = strchr&#40;rcvBuf, '\n')) == NULL&#41; &#123;
    if&#40;state == DISCONNECTED&#41; return 0;
    res = recv&#40;s, rcvBuf+cnt, 8000-cnt, 0&#41;;
    if&#40;!res&#41; printf&#40;"telluser connection closed by peer\n");
    if&#40;res < 0&#41; &#123;
      printf&#40;"telluser recv failed with error&#58; %d\n", WSAGetLastError&#40;)), fflush&#40;stdout&#41;;
      res = 0;
    &#125;
    if&#40;!res&#41; return 0;
    cnt += res;
  &#125;
  rcvBuf&#91;cnt&#93; = 0; p++;
  for&#40;q = rcvBuf; q < p; ) *buf++ = *q++; *buf = 0;
  for&#40;q = rcvBuf; p < rcvBuf+cnt; ) *q++ = *p++; *q = 0;
  cnt = q - rcvBuf;
  return 1;
&#125;
So unless the recv call starts to act strange when the buffer length is specified as 0, it should generate an EOF condition (return 0;) when the buffer overflows, leading to the closing of the socket.

Adding a password is a good idea. I could add that as another engine string option: the client would send it after getting the 'hallo' message, and the server would then verify it for equality, and close the connection otherwise.
Security issues. Yes. I had just made my remote connect work bugfree. Put it on the internet just before a world champs and within a second already i had someone connected.

*instantly*.

Then i realized a password was a good idea :)
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Peer-to-peer GUI adapter

Post by hgm »

Umm, I had quite some trouble getting the password to work. It seems that you cannot immediately start reading from a socket that just accepted an incoming call: the recv call fails with an error. The problem disappeared when I send something over the TCP connection ("Password required\n") before trying to read the password.

It should work now, though. I also added an option to limit length of lines sent to the GUI, in case that has a stricter limit than the 8000 that p2p can handle internally.

Btw, I use 3 threads in p2p. The initial one for listening to the GUI, a second for the server, which then handles incoming traffic from the peer when a client arrives, (and loops if the client disconnects), and one that is started by the main thread whenever it receives a request to actively connect from the GUI (which the dies when the connection closes). If there is a connection request when an outgoing connection is already active, the client is brushed off by the server thread.
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Peer-to-peer GUI adapter

Post by hgm »

ZirconiumX wrote:Are e's really that expensive?!
Oh, I guess that was Dutch. :oops:
diep
Posts: 1822
Joined: Thu Mar 09, 2006 11:54 pm
Location: The Netherlands

Re: Peer-to-peer GUI adapter

Post by diep »

hgm wrote:Umm, I had quite some trouble getting the password to work. It seems that you cannot immediately start reading from a socket that just accepted an incoming call: the recv call fails with an error. The problem disappeared when I send something over the TCP connection ("Password required\n") before trying to read the password.
Did you actually look in the code i posted?

You'll see several delays until it reads a socket. This is common knowledge already mid 90s. True for all OSes, especially windows...

I tend to remember Tim Mann had also written comments in winboard regarding that...

It should work now, though. I also added an option to limit length of lines sent to the GUI, in case that has a stricter limit than the 8000 that p2p can handle internally.
a line wno't be that long, yet the total buffer you sometimes get can be easily be a lot of kilobytes.

what happens sometimes is that a thread gets blocked by the system. You're in far endgame, and then BOOM , you get all 80 plies with a real long line from an engine which outputs 3 lines per iteration...

In meantime the engine has then received all sorts of commands and outputs new texts and lines and you get all that at once...
Btw, I use 3 threads in p2p. The initial one for listening to the GUI, a second for the server, which then handles incoming traffic from the peer when a client arrives, (and loops if the client disconnects), and one that is started by the main thread whenever it receives a request to actively connect from the GUI (which the dies when the connection closes). If there is a connection request when an outgoing connection is already active, the client is brushed off by the server thread.
The way how you design it in a GUI is dependant upon the language you use, the library you use and the type of OS you run onto. It's different for everyone.

In general the thread doing the graphics in most OSes needs to be a single threaded. Too many bugs otherwise, not much is threadsafe...

I wonder whether that will ever change...
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Peer-to-peer GUI adapter

Post by hgm »

I looked at your code, but it was not clear to me what the reason for the several delays was. This is really awful! One would expect that it would not be to difficult nowadays to make system calls that actually do what is requested, even if it takes time, rather than annoying the user with low-level sync problems. This is a blocking I/O call, after all...

My size for the buffer was mostly inspired by the idea that a FEN for a 36x36 board could be pretty long, especially if there are so many piece types that most pieces need two letters, and you then need additional delimiters to see which letters form a pair, so that you can need 3 or 4 characters per piece...

The p2p I made is a pure engine, so no graphics thread needed. Normally I do two threads in an adapter, one for each direction of the communication traffic. But here you need to listen for incoming connections too. You don't really need to do that at the same time as handling incoming traffic, but it was a bit difficult to terminate the listening when the stdin thread decides it is time to connect as client to a remote machine. I just hope that printf is thread-safe, because all threads do a lot of printing on stdout.