Main Page   Class Hierarchy   Alphabetical List   Data Structures   File List   Data Fields   Globals  

STCPMEM Class Reference

#include <stcpmem.hh>

Inheritance diagram for STCPMEM:

Inheritance graph
[legend]
Collaboration diagram for STCPMEM:

Collaboration graph
[legend]

Public Methods

 STCPMEM (char *bufline, char *procline)
virtual ~STCPMEM ()
CMS_STATUS clear ()
int check_if_read ()
CMS_STATUS read ()
CMS_STATUS peek ()
CMS_STATUS write (void *data)
CMS_STATUS write_if_read (void *data)
int login (const char *name, const char *passwd)

Protected Methods

CMS_STATUS skip_lines ()

Protected Attributes

long serial_number
long returned_serial_number
hostentserver_host_entry
sockaddr_in server_socket_address
int socket_fd
char * temp_buffer
long temp_buffer_size
int read_request_issued
double read_request_time
int polling
int lines_to_skip
int bytes_at_beginning_of_line

Constructor & Destructor Documentation

STCPMEM::STCPMEM char *    _bufline,
char *    _procline
 

Definition at line 53 of file stcpmem.cc.

00053                                                 :
00054 CMS (_bufline, _procline)
00055 {
00056 #if defined(_Windows) && !defined(USE_PCNFS)
00057   WSA_count++;
00058 #endif
00059   if (load_socket_interface () < 0)
00060     {
00061       rcs_print_error ("Can't load socket interface.\n");
00062       status = CMS_LIBRARY_UNAVAILABLE_ERROR;
00063     }
00064   socket_fd = 0;
00065   read_request_issued = 0;
00066   temp_buffer = NULL;
00067   polling = 0;
00068   lines_to_skip = 0;
00069   bytes_at_beginning_of_line = 0;
00070 
00071 
00072 
00073   if (CMS_DISPLAY_ASCII_ENCODING != neutral_encoding_method)
00074     {
00075       rcs_print_error
00076         ("The neutral_encoding_method must be ASCII_DISPLAY_ENCODING to use STCPMEM.\n");
00077       status = CMS_CONFIG_ERROR;
00078     }
00079 
00080 
00081   temp_buffer = (char *) malloc (cms_encoded_data_explosion_factor * size);
00082   if (temp_buffer == NULL)
00083     {
00084       rcs_print_error ("Out of memory!\n");
00085       status = CMS_CREATE_ERROR;
00086       return;
00087     }
00088   temp_buffer_size = cms_encoded_data_explosion_factor * size;
00089 
00090 #ifndef VXWORKS
00091   server_host_entry = NULL;
00092 #endif
00093   serial_number = 0;
00094 
00095   /* Set up the socket address stucture. */
00096   memset (&server_socket_address, 0, sizeof (server_socket_address));
00097   server_socket_address.sin_family = AF_INET;
00098   server_socket_address.sin_port = dl_htons (((u_short) stcp_port_number));
00099 
00100   int hostname_was_address = 0;
00101   if (isdigit (BufferHost[0]))
00102     {
00103       server_socket_address.sin_addr.s_addr = dl_inet_addr (BufferHost);
00104       if (server_socket_address.sin_addr.s_addr != 0 &&
00105           ((long) server_socket_address.sin_addr.s_addr) != -1)
00106         {
00107           hostname_was_address = 1;
00108         }
00109     }
00110   if (!hostname_was_address)
00111     {
00112       /* Get the IP address of the server using it's BufferHost. */
00113 #ifndef VXWORKS
00114       dl_gethostbyname (BufferHost, &server_host_entry);
00115       if (NULL == server_host_entry)
00116         {
00117           status = CMS_CONFIG_ERROR;
00118           rcs_print_error ("STCPMEM: Couldn't get host address for (%s).\n",
00119                            BufferHost);
00120 #if defined(_WINDOWS) && !defined(gnuwin32)
00121           rcs_print_error ("WSA error = %d\n", dl_WSAGetLastError ());
00122 #endif
00123           return;
00124         }
00125 #ifdef __MSDOS__
00126       server_socket_address.sin_addr.s_addr =
00127         *((u_long *) server_host_entry->h_addr_list[0]);
00128 #else
00129       server_socket_address.sin_addr.s_addr =
00130         *((int *) server_host_entry->h_addr_list[0]);
00131 #endif
00132       server_socket_address.sin_family = server_host_entry->h_addrtype;
00133 #else
00134       server_socket_address.sin_addr.s_addr = hostGetByName (BufferHost);
00135       if (server_socket_address.sin_addr.s_addr == ERROR)
00136         {
00137           rcs_print_error ("STCPMEM: Couldn't get host address for (%s).\n",
00138                            BufferHost);
00139           status = CMS_CONFIG_ERROR;
00140           return;
00141         }
00142 #endif
00143     }
00144   rcs_print_debug (PRINT_CMS_CONFIG_INFO,
00145                    "Using server on %s with IP address %s and port %d.\n",
00146                    BufferHost,
00147                    dl_inet_ntoa (server_socket_address.sin_addr),
00148                    stcp_port_number);
00149 
00150   rcs_print_debug (PRINT_CMS_CONFIG_INFO, "Creating socket . . .\n");
00151 
00152   socket_fd = dl_socket (AF_INET, SOCK_STREAM, 0);
00153   if (socket_fd < 0)
00154     {
00155       rcs_print_error ("STCPMEM: Error from socket() (errno = %d:%s)\n",
00156                        errno, strerror (errno));
00157       status = CMS_CREATE_ERROR;
00158       return;
00159     }
00160   rcs_print_debug (PRINT_CMS_CONFIG_INFO, "Setting socket options . . . \n");
00161   if (set_stcp_socket_options (socket_fd) < 0)
00162     {
00163       return;
00164     }
00165   sockaddr_in cli_addr;
00166   cli_addr.sin_family = AF_INET;
00167   cli_addr.sin_addr.s_addr = dl_htonl (INADDR_ANY);
00168   cli_addr.sin_port = dl_htons (0);
00169   rcs_print_debug (PRINT_CMS_CONFIG_INFO, "Binding . . . \n");
00170   if (dl_bind (socket_fd, (struct sockaddr *) &cli_addr, sizeof (cli_addr)) <
00171       0)
00172     {
00173 #if defined(_Windows) && !defined(USE_PCNFS) && !defined(gnuwin32)
00174       rcs_print_error ("STCPMEM: bind error %d\n", dl_WSAGetLastError ());
00175 #else
00176       rcs_print_error ("STCPMEM: bind error %d = %s\n", errno,
00177                        strerror (errno));
00178 #endif
00179       status = CMS_CREATE_ERROR;
00180     }
00181   rcs_print_debug (PRINT_CMS_CONFIG_INFO, "Connecting . . .\n");
00182   if (dl_connect (socket_fd, (struct sockaddr *) &server_socket_address,
00183                   sizeof (server_socket_address)) < 0)
00184     {
00185 #if  defined(_Windows) && !defined(gnuwin32)
00186       if (WSAEWOULDBLOCK == dl_WSAGetLastError ())
00187         {
00188 #else
00189       if (EINPROGRESS == errno)
00190         {
00191 #endif
00192           struct timeval tm;
00193           int socket_ret;
00194           double start_time, current_time;
00195 #ifdef _Windows
00196           tm.tv_sec = 0;
00197           tm.tv_usec = 0;
00198 #else
00199           // Timeout after 30 seconds on connect even if we
00200           // have no timeouts on other operations.
00201           if (timeout < 0)
00202             {
00203               tm.tv_sec = 30;
00204               tm.tv_usec = 0;
00205             }
00206           else
00207             {
00208               tm.tv_sec = (long) timeout;
00209               tm.tv_usec = (long) (timeout * 1000000.0);
00210               if (tm.tv_usec >= 1000000)
00211                 {
00212                   tm.tv_usec = tm.tv_usec % 1000000;
00213                 }
00214             }
00215 #endif
00216           fd_set fds;
00217           FD_ZERO (&fds);
00218           RCS_FD_SET (socket_fd, &fds);
00219           start_time = etime ();
00220           while (!
00221                  (socket_ret =
00222                   dl_select (socket_fd + 1, NULL, &fds, NULL, &tm)))
00223             {
00224               RCS_FD_SET (socket_fd, &fds);
00225               esleep (0.001);
00226               current_time = etime ();
00227               if (current_time - start_time > timeout && timeout >= 0.0)
00228                 {
00229                   rcs_print_error
00230                     ("STCPMEM: Timed out waiting for connection.\n");
00231                   status = CMS_NO_SERVER_ERROR;
00232                   return;
00233                 }
00234             }
00235 #if  defined(_Windows) && !defined(gnuwin32)
00236           if (socket_ret == SOCKET_ERROR)
00237             {
00238               rcs_print_error ("select error: %d\n", dl_WSAGetLastError ());
00239 #else
00240           if (socket_ret == -1)
00241             {
00242               rcs_print_error ("select error: %d -- %s\n", errno,
00243                                strerror (errno));
00244 #endif
00245               rcs_print_error ("STCPMEM: Couldn't connect.\n");
00246               status = CMS_NO_SERVER_ERROR;
00247               return;
00248             }
00249         }
00250       else
00251         {
00252 #if defined(_WINDOWS) && !defined(gnuwin32)
00253           rcs_print_error ("connect error: %d\n", dl_WSAGetLastError ());
00254 #else
00255           rcs_print_error ("connect error: %d -- %s\n", errno,
00256                            strerror (errno));
00257 #endif
00258           rcs_print_error
00259             ("STCPMEM: Error trying to connect to STCP port %d of host %s.\n",
00260              dl_ntohs (server_socket_address.sin_port), BufferHost);
00261         }
00262     }
00263 
00264   polling = (NULL != strstr (ProcessLine, "poll"));
00265   if (polling)
00266     {
00267       timeout = 0;
00268     }
00269 }

STCPMEM::~STCPMEM   [virtual]
 

Definition at line 272 of file stcpmem.cc.

00273 {
00274   if (socket_fd > 0)
00275     {
00276       clean_prev_read_info (socket_fd);
00277       dl_closesocket (socket_fd);
00278       socket_fd = 0;
00279     }
00280 #if defined(_Windows) && !defined(USE_PCNFS)
00281   if (WSA_count == 0)
00282     {
00283       unload_socket_interface ();
00284     }
00285   WSA_count--;
00286 #endif
00287   if (NULL == temp_buffer)
00288     {
00289       delete temp_buffer;
00290       temp_buffer = NULL;
00291     }
00292 }


Member Function Documentation

CMS_STATUS STCPMEM::clear   [virtual]
 

Reimplemented from CMS.

Definition at line 588 of file stcpmem.cc.

00589 {
00590   // FIXME: unimplemented function.
00591   return (status);
00592 }

int STCPMEM::check_if_read   [virtual]
 

Reimplemented from CMS.

Definition at line 561 of file stcpmem.cc.

00562 {
00563   read_request_issued = 0;
00564   char request_string[32];
00565   sprintf (request_string, "check_if_read(%d): ", (int) buffer_number);
00566 
00567   if (socket_fd <= 0)
00568     {
00569       rcs_print_error ("STCPMEM::write: Invalid socket descriptor. (%d)\n",
00570                        socket_fd);
00571       return (status = CMS_MISC_ERROR);
00572     }
00573   if (recvline (socket_fd, temp_buffer, temp_buffer_size, 0, timeout, NULL) <
00574       0)
00575     {
00576       status = CMS_TIMED_OUT;
00577       return 0;
00578     }
00579   if (!strncmp (temp_buffer, "was_read", 8))
00580     {
00581       return (1);
00582     }
00583   return (0);
00584 }

CMS_STATUS STCPMEM::read   [virtual]
 

Reimplemented from CMS.

Definition at line 323 of file stcpmem.cc.

00324 {
00325   long message_size, id;
00326   id = in_buffer_id;
00327   int read_request_issued_last_time;
00328   char request_string[32];
00329   sprintf (request_string, "read(%d):", (int) buffer_number);
00330 
00331   if (socket_fd <= 0)
00332     {
00333       rcs_print_error ("STCPMEM::read: Invalid socket descriptor. (%d)\n",
00334                        socket_fd);
00335       return (status = CMS_MISC_ERROR);
00336     }
00337   if (((int) skip_lines ()) < 0)
00338     {
00339       return status;
00340     }
00341 
00342   read_request_issued_last_time = read_request_issued;
00343 
00344   if (!read_request_issued_last_time || !polling)
00345     {
00346       if (sendline (socket_fd, request_string, 0, timeout) < 0)
00347         {
00348           return (status = CMS_MISC_ERROR);
00349         }
00350       read_request_issued = 1;
00351       read_request_time = etime ();
00352     }
00353 
00354   if (read_request_issued_last_time || !polling)
00355     {
00356       read_request_issued = 0;
00357       if (
00358           (message_size =
00359            recvline (socket_fd,
00360                      ((char *) encoded_data) + bytes_at_beginning_of_line,
00361                      cms_encoded_data_explosion_factor * size, 0, timeout,
00362                      &bytes_at_beginning_of_line)) < 0)
00363         {
00364           if (recvline_timedout)
00365             {
00366               if (polling)
00367                 {
00368                   read_request_issued = 1;
00369                   return (status = CMS_READ_OLD);
00370                 }
00371               else
00372                 {
00373                   lines_to_skip++;
00374                   bytes_at_beginning_of_line = 0;
00375                   return (status = CMS_TIMED_OUT);
00376                 }
00377             }
00378           else
00379             {
00380               bytes_at_beginning_of_line = 0;
00381               return (status = CMS_MISC_ERROR);
00382             }
00383         }
00384       bytes_at_beginning_of_line = 0;
00385       if (!strncmp ((char *) encoded_data, "ERR", 3))
00386         {
00387           long errcode;
00388           errcode = strtol (((char *) encoded_data) + 4, NULL, 0);
00389           if (errcode == 0)
00390             {
00391               return (status = CMS_READ_OLD);
00392             }
00393           else
00394             {
00395               return (status = CMS_MISC_ERROR);
00396             }
00397         }
00398       id++;
00399     }
00400   header.was_read = 1;
00401   check_id (id);
00402   return (status);
00403 }

CMS_STATUS STCPMEM::peek   [virtual]
 

Reimplemented from CMS.

Definition at line 407 of file stcpmem.cc.

Referenced by login().

00408 {
00409   long message_size, id;
00410   id = in_buffer_id;
00411   int read_request_issued_last_time;
00412   char request_string[32];
00413   sprintf (request_string, "peek(%d):", (int) buffer_number);
00414 
00415   if (socket_fd <= 0)
00416     {
00417       rcs_print_error ("STCPMEM::peek: Invalid socket descriptor. (%d)\n",
00418                        socket_fd);
00419       return (status = CMS_MISC_ERROR);
00420     }
00421   if (((int) skip_lines ()) < 0)
00422     {
00423       return status;
00424     }
00425   read_request_issued_last_time = read_request_issued;
00426 
00427   if (!read_request_issued_last_time || !polling)
00428     {
00429       if (sendline (socket_fd, request_string, 0, timeout) < 0)
00430         {
00431           return (status = CMS_MISC_ERROR);
00432         }
00433       read_request_issued = 1;
00434       read_request_time = etime ();
00435     }
00436 
00437   if (read_request_issued_last_time || !polling)
00438     {
00439       read_request_issued = 0;
00440       if (
00441           (message_size =
00442            recvline (socket_fd,
00443                      ((char *) encoded_data) + bytes_at_beginning_of_line,
00444                      cms_encoded_data_explosion_factor * size, 0, timeout,
00445                      &bytes_at_beginning_of_line)) < 0)
00446         {
00447           if (recvline_timedout)
00448             {
00449               if (polling)
00450                 {
00451                   read_request_issued = 1;
00452                   return (status = CMS_READ_OLD);
00453                 }
00454               else
00455                 {
00456                   lines_to_skip++;
00457                   bytes_at_beginning_of_line = 0;
00458                   return (status = CMS_TIMED_OUT);
00459                 }
00460             }
00461           else
00462             {
00463               bytes_at_beginning_of_line = 0;
00464               return (status = CMS_MISC_ERROR);
00465             }
00466         }
00467       bytes_at_beginning_of_line = 0;
00468       if (!strncmp ((char *) encoded_data, "ERR", 3))
00469         {
00470           long errcode;
00471           errcode = strtol (((char *) encoded_data) + 4, NULL, 0);
00472           if (errcode == 0)
00473             {
00474               return (status = CMS_READ_OLD);
00475             }
00476           else
00477             {
00478               return (status = CMS_MISC_ERROR);
00479             }
00480         }
00481       id++;
00482     }
00483   header.was_read = 1;
00484   check_id (id);
00485   return (status);
00486 }

CMS_STATUS STCPMEM::write void *    user_data [virtual]
 

Reimplemented from CMS.

Definition at line 492 of file stcpmem.cc.

00493 {
00494   read_request_issued = 0;
00495   char request_string[32];
00496   sprintf (request_string, "write(%d): ", (int) buffer_number);
00497 
00498   if (socket_fd <= 0)
00499     {
00500       rcs_print_error ("STCPMEM::write: Invalid socket descriptor. (%d)\n",
00501                        socket_fd);
00502       return (status = CMS_MISC_ERROR);
00503     }
00504   memset (temp_buffer, 0, temp_buffer_size);
00505   strcpy (temp_buffer, request_string);
00506   strcat (temp_buffer, (char *) encoded_data);
00507   if (sendline (socket_fd, temp_buffer, 0, timeout) < 0)
00508     {
00509       return (status = CMS_MISC_ERROR);
00510     }
00511   status = CMS_WRITE_OK;
00512   return (status);
00513 }

CMS_STATUS STCPMEM::write_if_read void *    user_data [virtual]
 

Reimplemented from CMS.

Definition at line 516 of file stcpmem.cc.

00517 {
00518   read_request_issued = 0;
00519   char request_string[32];
00520   sprintf (request_string, "write_if_read(%d): ", (int) buffer_number);
00521   static int poll_error_message_sent = 0;
00522   if (socket_fd <= 0)
00523     {
00524       rcs_print_error ("STCPMEM::write: Invalid socket descriptor. (%d)\n",
00525                        socket_fd);
00526       return (status = CMS_MISC_ERROR);
00527     }
00528   if (polling)
00529     {
00530       if (poll_error_message_sent < 100)
00531         {
00532           rcs_print_error
00533             ("STCPMEM: Can not write_if_read when polling is enabled.\n");
00534         }
00535       poll_error_message_sent++;
00536       return (status = CMS_MISC_ERROR);
00537     }
00538 
00539   memset (temp_buffer, 0, temp_buffer_size);
00540   strcpy (temp_buffer, request_string);
00541   strcat (temp_buffer, (char *) encoded_data);
00542   if (sendline (socket_fd, temp_buffer, 0, timeout) < 0)
00543     {
00544       return (status = CMS_MISC_ERROR);
00545     }
00546   if (recvline (socket_fd, temp_buffer, temp_buffer_size, 0, timeout, NULL) <
00547       0)
00548     {
00549       rcs_print_error ("write_if_read: timed out.\n");
00550       return (status = CMS_TIMED_OUT);
00551     }
00552   if (!strncmp (temp_buffer, "write_if_read_succeeded", 22))
00553     {
00554       return (status = CMS_WRITE_OK);
00555     }
00556   rcs_print_error ("write_if_read_failed %s", temp_buffer);
00557   return (status = CMS_MISC_ERROR);
00558 }

int STCPMEM::login const char *    name,
const char *    passwd
[virtual]
 

Reimplemented from CMS.

Definition at line 595 of file stcpmem.cc.

00596 {
00597 
00598   status = CMS_STATUS_NOT_SET;
00599   double start_time = etime ();
00600   while (read_request_issued
00601          && (status == CMS_STATUS_NOT_SET || status == CMS_TIMED_OUT
00602              || status == CMS_READ_OLD) && start_time - etime () < 60.0)
00603     {
00604       peek ();
00605     }
00606   read_request_issued = 0;
00607   char request_string[32];
00608   sprintf (request_string, "get_keys(%d):%s", (int) buffer_number, name);
00609 
00610   if (sendline (socket_fd, request_string, 0, 30.0) < 0)
00611     {
00612       return (status = CMS_MISC_ERROR);
00613     }
00614   if (recvline (socket_fd, temp_buffer, temp_buffer_size, 0, 30.0, NULL) < 0)
00615     {
00616       rcs_print_error ("login: timed out.\n");
00617       return (0);
00618     }
00619   if (strncmp (temp_buffer, "keys:", 5))
00620     {
00621       rcs_print_error
00622         ("Bad reply from server on request for login keys: %s\n",
00623          temp_buffer);
00624       return 0;
00625     }
00626   char key1[8];
00627   memset (key1, 0, 8);
00628   int i = 0;
00629   for (i = 0; i < 8 && temp_buffer[i + 5] != ':'; i++)
00630     {
00631       key1[i] = temp_buffer[i + 5];
00632     }
00633   int key2_offset = i + 6;
00634   char key2[8];
00635   memset (key2, 0, 8);
00636   for (i = 0; i < 8 && temp_buffer[i + key2_offset] != ':'; i++)
00637     {
00638       key2[i] = temp_buffer[i + key2_offset];
00639     }
00640   char passwd_pass1[16];
00641   char *crypt1_ret;
00642   crypt1_ret = rcs_crypt (passwd, key1);
00643   if (NULL == crypt1_ret)
00644     {
00645       rcs_print_error ("STCPMEM:login -- crypt failed.\n");
00646       return 0;
00647     }
00648   strncpy (passwd_pass1, crypt1_ret, 16);
00649   char passwd_pass2[16];
00650   char *crypt2_ret;
00651   crypt2_ret = rcs_crypt (passwd_pass1, key2);
00652   if (NULL == crypt2_ret)
00653     {
00654       rcs_print_error ("STCPMEM:login -- crypt failed.\n");
00655       return 0;
00656     }
00657   strncpy (passwd_pass2, crypt2_ret, 16);
00658   sprintf (request_string, "login(%d):%s:%s", (int) buffer_number, name,
00659            passwd_pass2);
00660   if (sendline (socket_fd, request_string, 0, 30.0) < 0)
00661     {
00662       return (status = CMS_MISC_ERROR);
00663     }
00664   if (recvline (socket_fd, temp_buffer, temp_buffer_size, 0, 30.0, NULL) < 0)
00665     {
00666       rcs_print_error ("login: timed out.\n");
00667       return (0);
00668     }
00669   if (strncmp (temp_buffer, "login", 5))
00670     {
00671       rcs_print_error ("Bad reply from server on request for login: %s\n",
00672                        temp_buffer);
00673       return 0;
00674     }
00675   return !strcmp (temp_buffer, "login succeeded");
00676 }

CMS_STATUS STCPMEM::skip_lines   [protected]
 

Definition at line 295 of file stcpmem.cc.

Referenced by peek(), and read().

00296 {
00297 
00298   while (lines_to_skip > 0)
00299     {
00300       if (recvline
00301           (socket_fd, ((char *) encoded_data),
00302            cms_encoded_data_explosion_factor * size, 0, timeout, NULL) < 0)
00303         {
00304           if (recvline_timedout)
00305             {
00306               return (status = CMS_TIMED_OUT);
00307             }
00308           else
00309             {
00310               bytes_at_beginning_of_line = 0;
00311               return (status = CMS_MISC_ERROR);
00312             }
00313         }
00314       bytes_at_beginning_of_line = 0;
00315       lines_to_skip--;
00316     }
00317   bytes_at_beginning_of_line = 0;
00318   return (status);
00319 }


Field Documentation

long STCPMEM::serial_number [protected]
 

Definition at line 26 of file stcpmem.hh.

long STCPMEM::returned_serial_number [protected]
 

Definition at line 27 of file stcpmem.hh.

struct hostent* STCPMEM::server_host_entry [protected]
 

Definition at line 29 of file stcpmem.hh.

struct sockaddr_in STCPMEM::server_socket_address [protected]
 

Definition at line 31 of file stcpmem.hh.

int STCPMEM::socket_fd [protected]
 

Definition at line 32 of file stcpmem.hh.

char* STCPMEM::temp_buffer [protected]
 

Definition at line 33 of file stcpmem.hh.

long STCPMEM::temp_buffer_size [protected]
 

Definition at line 34 of file stcpmem.hh.

int STCPMEM::read_request_issued [protected]
 

Definition at line 35 of file stcpmem.hh.

double STCPMEM::read_request_time [protected]
 

Definition at line 36 of file stcpmem.hh.

int STCPMEM::polling [protected]
 

Definition at line 37 of file stcpmem.hh.

int STCPMEM::lines_to_skip [protected]
 

Definition at line 38 of file stcpmem.hh.

int STCPMEM::bytes_at_beginning_of_line [protected]
 

Definition at line 40 of file stcpmem.hh.


The documentation for this class was generated from the following files:
Generated on Sun Dec 2 15:59:19 2001 for rcslib by doxygen1.2.11.1 written by Dimitri van Heesch, © 1997-2001