//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2011 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  This program 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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: tcp/ip low level socket handling (and protocol apis)

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_TCPIP

#include "diskdriv.h"
#include "tcpcomon.h"
#include "display/display.h"
#include <stdio.h>

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
FILE *debug_fp;
#endif

//-------------------------------------------------------------------------
void drvftp_message_write_error(char *message)
{
 char sout[256];
 snprintf(sout,sizeof(sout),"FTP: %s",message);
 display_timed_message(sout);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 if(debug_fp)
  fprintf(debug_fp,"%s\n",sout);
#endif
}

void drvftp_message_timeout_init(struct fptdrive_timeoutmsg_s *tos,unsigned long endtime,char *msgmask)
{
 mpxp_uint64_t currtime=pds_gettimem();
 pds_memset(tos,0,sizeof(*tos));
 tos->endtime_response=endtime;
 tos->begintime_disp=currtime+DRVFTP_DEFAULT_TIMEOUTMS_DISP;
 pds_strcpy(tos->msgmask,msgmask);
}

void drvftp_message_timeout_reset(struct fptdrive_timeoutmsg_s *tos,unsigned long endtime)
{
 mpxp_uint64_t currtime=pds_gettimem();
 if(currtime>tos->begintime_disp)
  display_clear_timed_message();
 tos->endtime_response=endtime;
 tos->begintime_disp=currtime+DRVFTP_DEFAULT_TIMEOUTMS_DISP;
}

void drvftp_message_timeout_write(struct fptdrive_timeoutmsg_s *tos)
{
 mpxp_uint64_t currtime=pds_gettimem(),disptime;
 char sout[256];
 if(currtime>tos->begintime_disp){
  disptime=(unsigned long)((tos->endtime_response+500-currtime)/1000);
  if(tos->lasttime_disp!=disptime){
   sprintf(sout,(const char *)tos->msgmask,disptime);
   drvftp_message_write_error(sout);
   tos->lasttime_disp=disptime;
  }
 }
#ifdef MPXPLAY_WIN32
 Sleep(0);
#endif
}

void drvftp_message_timeout_close(struct fptdrive_timeoutmsg_s *tos)
{
 if(pds_gettimem()>tos->begintime_disp)
  display_clear_timed_message();
}

//-------------------------------------------------------------------------
void tcpcomon_str_localname_to_remote(char *remotename,char *pathname)
{
 int drivenum=pds_getdrivenum_from_path(pathname);
 if(drivenum>=0)
  pathname+=sizeof(PDS_DIRECTORY_DRIVE_STR)-1;
 pds_strcpy(remotename,pathname);
#if (PDS_DIRECTORY_SEPARATOR_CHAR!=PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP)
 while(*remotename){ // convert '\' to '/'
  if(*remotename==PDS_DIRECTORY_SEPARATOR_CHAR)
   *remotename=PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP;
  remotename++;
 }
#endif
}

void tcpcomon_str_remotename_to_local(char *localname,char *remotename,unsigned int buflen)
{
 if(*remotename==PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP) // skip '/'
  remotename++;

 snprintf(localname,buflen,"%c%s",PDS_DIRECTORY_SEPARATOR_CHAR,remotename);
#if (PDS_DIRECTORY_SEPARATOR_CHAR!=PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP)
 while(*localname){ // convert '/' to '\'
  if(*localname==PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP)
   *localname=PDS_DIRECTORY_SEPARATOR_CHAR;
  localname++;
 }
#endif
}

//-------------------------------------------------------------------------
//directory cache (filename,attribs,filesize,filedate)
static unsigned int tcpcomon_dircache_dirs_expand(struct tcpcomon_cached_drive_info_s *drvi)
{
 struct tcpcomon_cached_directory_info_s *dirdatas;

 if(drvi->cached_dirs_max>=DRVFTP_CACHED_DIRS_MAX)
  return 0;
 dirdatas=(struct tcpcomon_cached_directory_info_s *)malloc((drvi->cached_dirs_max+DRVFTP_CACHED_DIRS_INITSTEP)*sizeof(*dirdatas));
 if(!dirdatas)
  return 0;
 if(drvi->cached_dir_datas){
  pds_memcpy(dirdatas,drvi->cached_dir_datas,(drvi->cached_dirs_max*sizeof(*dirdatas)));
  free(drvi->cached_dir_datas);
 }
 pds_memset(dirdatas+drvi->cached_dirs_max,0,(DRVFTP_CACHED_DIRS_INITSTEP*sizeof(*dirdatas)));
 drvi->cached_dir_datas=dirdatas;
 drvi->cached_dirs_max+=DRVFTP_CACHED_DIRS_INITSTEP;
 return 1;
}

struct tcpcomon_cached_directory_info_s *tcpcomon_dircache_dir_realloc(struct tcpcomon_cached_drive_info_s *drvi,struct tcpcomon_cached_directory_info_s *dirdatas,char *dirname)
{
 unsigned int step;
 if(dirdatas){
  tcpcomon_dircache_dir_dealloc(dirdatas);
  step=0;
 }else{
  if(drvi->cached_dirs_num>=drvi->cached_dirs_max)
   if(!tcpcomon_dircache_dirs_expand(drvi))
    return NULL;
  dirdatas=drvi->cached_dir_datas+drvi->cached_dirs_num;
  step=1;
 }
 dirdatas->dirname=malloc(pds_strlen(dirname)+1);
 if(!dirdatas->dirname)
  return NULL;
 pds_strcpy(dirdatas->dirname,dirname);
 drvi->cached_dirs_num+=step;
 return dirdatas;
}

void tcpcomon_dircache_dir_dealloc(struct tcpcomon_cached_directory_info_s *diri)
{
 struct tcpcomon_cached_direntry_info_s *ed;
 unsigned int i;
 if(diri){
  if(diri->dirname)
   free(diri->dirname);
  ed=diri->entrydatas;
  if(ed){
   i=diri->cached_entries_num;
   if(i)
    do{
     if(ed->filename)
      free(ed->filename);
     ed++;
    }while(--i);
   free(diri->entrydatas);
  }
  pds_memset(diri,0,sizeof(*diri));
 }
}

void tcpcomon_dircache_alldirs_dealloc(struct tcpcomon_cached_drive_info_s *drvi)
{
 struct tcpcomon_cached_directory_info_s *diri=drvi->cached_dir_datas;
 unsigned int i;
 if(diri){
  i=drvi->cached_dirs_num;
  if(i)
   do{
    tcpcomon_dircache_dir_dealloc(diri);
    diri++;
   }while(--i);
  free(drvi->cached_dir_datas);
  drvi->cached_dir_datas=NULL;
 }
 drvi->cached_dirs_num=drvi->cached_dirs_max=0;
}

struct tcpcomon_cached_directory_info_s *tcpcomon_dircache_dir_searchby_name(struct tcpcomon_cached_drive_info_s *drvi,char *dirname)
{
 struct tcpcomon_cached_directory_info_s *diri=drvi->cached_dir_datas;
 unsigned int i;
 if(diri){
  i=drvi->cached_dirs_num;
  if(i)
   do{
    if(diri->dirname && (pds_stricmp(diri->dirname,dirname)==0))
     return diri;
    diri++;
   }while(--i);
 }
 return NULL;
}

static unsigned int tcpcomon_dircache_entries_expand(struct tcpcomon_cached_directory_info_s *diri)
{
 struct tcpcomon_cached_direntry_info_s *ed;
 if(diri->cached_entries_max>=DRVFTP_CACHED_DIRENT_MAX)
  return 0;
 ed=(struct tcpcomon_cached_direntry_info_s *)malloc((diri->cached_entries_max+DRVFTP_CACHED_DIRENT_INITSTEP)*sizeof(*ed));
 if(!ed)
  return 0;
 if(diri->entrydatas){
  pds_memcpy(ed,diri->entrydatas,(diri->cached_entries_max*sizeof(*ed)));
  free(diri->entrydatas);
 }
 pds_memset(ed+diri->cached_entries_num,0,(DRVFTP_CACHED_DIRENT_INITSTEP*sizeof(*ed)));
 diri->entrydatas=ed;
 diri->cached_entries_max+=DRVFTP_CACHED_DIRENT_INITSTEP;
 return 1;
}

struct tcpcomon_cached_direntry_info_s *tcpcomon_dircache_entry_alloc(struct tcpcomon_cached_directory_info_s *diri,char *filename)
{
 struct tcpcomon_cached_direntry_info_s *ed;
 if(diri->cached_entries_num>=diri->cached_entries_max)
  if(!tcpcomon_dircache_entries_expand(diri))
   return NULL;
 ed=diri->entrydatas+diri->cached_entries_num;
 ed->filename=malloc(pds_strlen(filename)+1);
 if(!ed->filename)
  return NULL;
 pds_strcpy(ed->filename,filename);
 diri->cached_entries_num++;
 return ed;
}

char *tcpcomon_str_getpath_from_fullname(char *path,char *fullname)
{
 char *filenamep;

 if(!path)
  return NULL;
 if(!fullname){
  *path=0;
  return NULL;
 }

 if(path!=fullname)
  pds_strcpy(path,fullname);
 filenamep=pds_strrchr(path,PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP);
 if(filenamep){
  if((filenamep==path) || (path[1]==':' && filenamep==(path+2))) // "\\filename.xxx" or "c:\\filename.xxx"
   filenamep[1]=0;
  else
   filenamep[0]=0;                        // c:\\subdir\\filename.xxx
  filenamep++;
 }else{
  filenamep=pds_strchr(path,':');
  if(filenamep)
   *(++filenamep)=0;
  else{
   filenamep=path;
   *path=0;
  }
 }
 filenamep=fullname+(filenamep-path);
 return filenamep;
}

struct tcpcomon_cached_direntry_info_s *tcpcomon_dircache_entry_searchby_fullname(struct tcpcomon_cached_drive_info_s *drvi,char *fullname)
{
 struct tcpcomon_cached_directory_info_s *diri;
 struct tcpcomon_cached_direntry_info_s *ed;
 unsigned int en;
 char *filename,dirname[MAX_PATHNAMELEN];

 filename=tcpcomon_str_getpath_from_fullname(dirname,fullname);

 diri=tcpcomon_dircache_dir_searchby_name(drvi,dirname);
 if(!diri)
  goto err_out_remove;
 ed=diri->entrydatas;
 if(!ed)
  goto err_out_remove;
 en=diri->cached_entries_num;
 if(!en)
  goto err_out_remove;
 do{
  if(ed->filename && (pds_stricmp(ed->filename,filename)==0))
   return ed;
  ed++;
 }while(--en);
err_out_remove:
 return NULL;
}

struct tcpcomon_cached_direntry_info_s *tcpcomon_dircache_entry_removeby_fullname(struct tcpcomon_cached_drive_info_s *drvi,char *fullname)
{
 struct tcpcomon_cached_direntry_info_s *ed=tcpcomon_dircache_entry_searchby_fullname(drvi,fullname);
 if(ed)
  if(ed->filename){
   free(ed->filename);
   ed->filename=NULL;
  }
 return ed;
}

struct tcpcomon_cached_direntry_info_s *tcpcomon_dircache_entry_addby_fullname(struct tcpcomon_cached_drive_info_s *drvi,char *fullname)
{
 struct tcpcomon_cached_directory_info_s *diri;
 struct tcpcomon_cached_direntry_info_s *ed;
 unsigned int en;
 char *filename,dirname[MAX_PATHNAMELEN];

 filename=tcpcomon_str_getpath_from_fullname(dirname,fullname);

 diri=tcpcomon_dircache_dir_searchby_name(drvi,dirname);
 if(!diri)
  return NULL;
 ed=diri->entrydatas;
 if(!ed)
  goto err_out_add;
 en=diri->cached_entries_max;
 if(!en)
  goto err_out_add;
 do{
  if(!ed->filename){ // deleted by entry_removeby_fullname
   pds_memset(ed,0,sizeof(*ed));
   ed->filename=malloc(pds_strlen(filename)+1);
   if(!ed->filename)
    return NULL;
   pds_strcpy(ed->filename,filename);
   diri->cached_entries_num++;
   return ed;
  }
  ed++;
 }while(--en);
err_out_add:
 return tcpcomon_dircache_entry_alloc(diri,filename);
}

void tcpcomon_dircache_entry_copyto_ffblk(struct pds_find_t *ffblk,struct tcpcomon_cached_direntry_info_s *ed)
{
 ffblk->attrib=ed->attrib;
 ffblk->size=ed->filesize;
 pds_memcpy(&ffblk->fdate,&ed->fdate,sizeof(ffblk->fdate));
 pds_strcpy(ffblk->name,ed->filename);
}

//-------------------------------------------------------------------------
// socket handling

#ifdef MPXPLAY_WIN32
 #include <winsock.h>
 static WSADATA wsadata;
#elif defined(MPXPLAY_LINK_SWSCK32)
 #include "config.h"
 #include "sws_cfg.h"
 #include "sws_net.h"
 #include "sws_sock.h"
 #ifndef BOOL
 typedef int BOOL;
 #endif
 typedef struct sockaddr SOCKADDR;
 static unsigned int drvftp_socklib_initialized;
 extern char *freeopts[MAXFREEOPTS];
#elif defined(MPXPLAY_LINK_WATTCP32)
 #define MPXPLAY_WATTCP_USETICK 1
 #include <tcp.h>
 #include "netinet/in.h"
 #include "netdb.h"
 #include "sys/socket.h"
 #include "sys/ioctl.h"
 #ifndef BOOL
 typedef int BOOL;
 #endif
 typedef int SOCKET;
 typedef struct sockaddr SOCKADDR;
 extern int _watt_do_exit;
 static unsigned int drvftp_wattcp_initialized;
#endif

#ifndef SD_BOTH
#define SD_BOTH 0x02
#endif

static long ftpdrv_lowlevel_ioctl_socket(SOCKET sock,unsigned int control,unsigned long *data)
{
 if(sock){
#if defined(MPXPLAY_LINK_WATTCP32)
 #ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(&sock);
 #endif
  return ioctlsocket(sock,control,(char *)data);
#else
  return ioctlsocket(sock,control,data);
#endif
 }
 return SOCKET_ERROR;
}

static int ftpdrv_lowlevel_global_init(void)
{
#ifdef MPXPLAY_WIN32
 if(!wsadata.wVersion && !wsadata.wHighVersion)
  if(WSAStartup(MAKEWORD(2,2),&wsadata)!=NO_ERROR)
   return 0;
#elif defined(MPXPLAY_LINK_WATTCP32)
 _watt_do_exit = 0;   // don't exit from the program in sock_init()
 if(!drvftp_wattcp_initialized){
  if(sock_init()!=0)
   return 0;
  drvftp_wattcp_initialized=1;
 }
#elif defined(MPXPLAY_LINK_SWSCK32)
 if(!drvftp_socklib_initialized){
  if(!SWS_CfgSetName(NULL,freeopts[OPT_PROGNAME]))
   drvftp_message_write_error("SWSSOCK cfg file load failed!");
  if(SWS_SockStartup(NULL,NULL)){
   drvftp_message_write_error("SWSSOCK lib init failed!");
   return 0;
  }
  drvftp_socklib_initialized=1;
 }
#else
 return 0;
#endif
 return 1;
}

static void ftpdrv_lowlevel_global_deinit(void)
{
#ifdef MPXPLAY_WIN32
 if(wsadata.wVersion || wsadata.wHighVersion)
  WSACleanup();
 pds_memset(&wsadata,0,sizeof(wsadata));
#elif defined(MPXPLAY_LINK_WATTCP32)
 if(drvftp_wattcp_initialized){
  sock_exit(); // ???
  drvftp_wattcp_initialized=0;
 }
#elif defined(MPXPLAY_LINK_SWSCK32)
 if(drvftp_socklib_initialized){
  SWS_SockCleanup();
  drvftp_socklib_initialized=0;
 }
#endif
}

#ifndef MPXPLAY_WIN32
static int ftpdrv_str_urlipnums_convert(char *urlname,mpxp_uint8_t *ipnums)
{
 unsigned int i=0;
 char *next=urlname;
 do{
  ipnums[i]=pds_atol(next);
  next=pds_strchr(next,'.');
  if(next)
   next++;
  i++;
 }while((i<DRVFTP_IP_LEN) && next);
 if(i<DRVFTP_IP_LEN)
  return 0;
 return 1;
}
#endif

static int ftpdrv_lowlevel_addressinfo_init(struct ftpdrive_socket_info_s *socketinfo_session,char *servername,mpxp_uint8_t *ip_local,mpxp_uint8_t *ip_remote)
{
 struct hostent *ht;
 char hostname_local[MAX_PATHNAMELEN];

 if(gethostname(hostname_local,sizeof(hostname_local))!=0)
  return 0;
 ht=gethostbyname(hostname_local);
 if(!ht || !ht->h_addr_list)
  return 0;
 pds_memcpy(ip_local,ht->h_addr_list[0],DRVFTP_IP_LEN); // !!!

 ht=gethostbyname(servername);
 if(ht && ht->h_addr_list){
  pds_memcpy(ip_remote,ht->h_addr_list[0],DRVFTP_IP_LEN); // !!!
  return 1;
 }
#ifndef MPXPLAY_WIN32
 if(ftpdrv_str_urlipnums_convert(servername,ip_remote))
  return 1;
#endif
 return 0;
}

static int ftpdrv_lowlevel_socket_open(struct ftpdrive_socket_info_s *socketinfo_any,unsigned long bufsize)
{
 SOCKET portsock=(SOCKET)socketinfo_any->socknum;
 int rcvbufsize=bufsize;
 unsigned long ioctlnum;
#if defined(MPXPLAY_WIN32) || defined(MPXPLAY_LINK_SWSCK32)
 BOOL optval;
#endif

 if(!portsock){
  portsock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(portsock==INVALID_SOCKET)
   return 0;
#if defined(MPXPLAY_WIN32) || defined(MPXPLAY_LINK_SWSCK32)
  optval=1;
  setsockopt(portsock,IPPROTO_TCP,TCP_NODELAY,(char *)&optval,sizeof(optval));
#endif
  ioctlnum=0;                                            // block ioctl
  ftpdrv_lowlevel_ioctl_socket(portsock,FIONBIO,&ioctlnum); //
  funcbit_smp_value_put(socketinfo_any->socknum,(ftpdrive_socket_t)portsock);
 }
 if(rcvbufsize){
  setsockopt(portsock,SOL_SOCKET,SO_RCVBUF,(char *)&rcvbufsize,sizeof(rcvbufsize));
  //setsockopt(portsock,SOL_SOCKET,SO_SNDBUF,(char *)&rcvbufsize,sizeof(rcvbufsize));
 }
 return 1;
}

static void ftpdrv_lowlevel_socket_shutdown(struct ftpdrive_socket_info_s *socketinfo_any)
{
 if(socketinfo_any->socknum){
  SOCKET sock=(SOCKET)socketinfo_any->socknum;
#ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(&sock);
#endif
  shutdown(sock,SD_BOTH);
#ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(NULL);
#endif
 }
}

static void ftpdrv_lowlevel_socket_close(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int full)
{
 if(socketinfo_any->socknum){
  shutdown((SOCKET)socketinfo_any->socknum,SD_BOTH);
  closesocket((SOCKET)socketinfo_any->socknum);
  funcbit_smp_value_put(socketinfo_any->socknum,0);
 }
}

/*static int ftpdrv_lowlevel_socket_select(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int selmode)
{
 int retcode,retry=3;//,usec=128;
 struct fd_set fds;
 struct timeval tv;
 do{
  tv.tv_sec=0;
  tv.tv_usec=0;//usec;
  fds.fd_count=1;
  fds.fd_array[0]=(SOCKET)socketinfo_any->socknum;
  retcode=select(0,((selmode&DRVFTP_SOCKSELECT_MODE_READ)? &fds:NULL),((selmode&DRVFTP_SOCKSELECT_MODE_WRITE)? &fds:NULL),NULL,&tv);
  if(retcode>=0)
   break;
  //usec<<=1;
 }while(--retry);
 return retcode;
}*/

static int ftpdrv_lowlevel_socket_select(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int selmode)
{
 struct fd_set fds;
 const struct timeval tv={0,0};
 fds.fd_count=1;
 fds.fd_array[0]=(SOCKET)socketinfo_any->socknum;
 return select(0,((selmode&DRVFTP_SOCKSELECT_MODE_READ)? &fds:NULL),((selmode&DRVFTP_SOCKSELECT_MODE_WRITE)? &fds:NULL),NULL,&tv);
}

static int ftpdrv_lowlevel_socket_connect(struct ftpdrive_socket_info_s *socketinfo_any)
{
 SOCKET portsock=(SOCKET)socketinfo_any->socknum;
 struct sockaddr_in clientservice;
 pds_memset(&clientservice,0,sizeof(clientservice));
 clientservice.sin_family=AF_INET;
 pds_memcpy(&(clientservice.sin_addr.s_addr),socketinfo_any->conn_ip_addr,DRVFTP_IP_LEN);
 clientservice.sin_port=htons(socketinfo_any->portnum);
 if(connect(portsock,(SOCKADDR *)&clientservice,sizeof(clientservice))==SOCKET_ERROR)
  return 0;
#ifdef MPXPLAY_WATTCP_USETICK
 tcp_tick(NULL);
 tcp_tick(&portsock);
#endif
 return 1;
}

static int ftpdrv_lowlevel_socket_listen(struct ftpdrive_socket_info_s *socketinfo_filehand,mpxp_uint8_t *ip_local)
{
 SOCKET portsock=(SOCKET)socketinfo_filehand->socknum;
 struct sockaddr_in service,add;
 int socksize,portnum=0,success=0,retry=DRVFTP_DEFAULT_TIMEOUTRETRY_DATACONN;

 do{
#ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(NULL);
#endif

  pds_memset(&service,0,sizeof(service));
  service.sin_family=AF_INET;
  pds_memcpy(&(service.sin_addr.s_addr),ip_local,DRVFTP_IP_LEN);
  service.sin_port=0;
  if(bind(portsock,(SOCKADDR *)&service,sizeof(service))==SOCKET_ERROR)
   continue;
  pds_memset(&add,0,sizeof(add));
  socksize=sizeof(add);
  if(getsockname(portsock, (SOCKADDR *) &add,&socksize)==SOCKET_ERROR)
   continue;
  portnum=ntohs(add.sin_port);
  if(!portnum)
   continue;

  if(listen(portsock,1)!=SOCKET_ERROR){
   success=1;
   break;
  }
#ifdef MPXPLAY_WIN32
  Sleep(0);
#endif
 }while(--retry);

 if(success && portnum)
  funcbit_smp_value_put(socketinfo_filehand->portnum,portnum);

#ifdef MPXPLAY_WATTCP_USETICK
 tcp_tick(NULL);
#endif

 return portnum;
}

static int ftpdrv_lowlevel_socket_accept(struct ftpdrive_socket_info_s *socketinfo_filehand)
{
 SOCKET portsock=(SOCKET)socketinfo_filehand->socknum,ps;
 unsigned long nonblock;
 int retcode=0;

#ifdef MPXPLAY_WATTCP_USETICK
 tcp_tick(NULL);
 tcp_tick(&portsock);
#endif
 nonblock=1;                              //
 ftpdrv_lowlevel_ioctl_socket(portsock,FIONBIO,&nonblock); // else accept can freeze
 ps=accept(portsock,NULL,NULL);
 if(ps!=INVALID_SOCKET){
  closesocket(portsock);
  portsock=ps;
  nonblock=0;                              //
  ftpdrv_lowlevel_ioctl_socket(portsock,FIONBIO,&nonblock); // else write/send can fail
#ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(NULL);
  tcp_tick(&portsock);
#endif
  retcode=1;
 }

 if(retcode)
  funcbit_smp_value_put(socketinfo_filehand->socknum,(ftpdrive_socket_t)portsock);
#ifdef MPXPLAY_WIN32
 else
  Sleep(0);
#endif

 return retcode;
}

static long ftpdrv_lowlevel_send(struct ftpdrive_socket_info_s *socket_info,char *data,unsigned long bytes_to_send)
{
 long total_bytes_sent=0,leftbytes=bytes_to_send;
 mpxp_uint64_t endtime_response=pds_gettimem()+DRVFTP_DEFAULT_TIMEOUTMS_RESPONSE;
 if(socket_info->socknum){
  SOCKET sock=(SOCKET)socket_info->socknum;
  do{
   long sentbytes;
#ifdef MPXPLAY_WATTCP_USETICK
   tcp_tick(NULL);
   tcp_tick(&sock);
#endif
   sentbytes=send(sock,data,leftbytes,0);
#ifdef MPXPLAY_WATTCP_USETICK
   tcp_tick(&sock);
#endif
   if(sentbytes<0)
    break;
   if(sentbytes>0){
    total_bytes_sent+=sentbytes;
    if(total_bytes_sent>=bytes_to_send)
     break;
    leftbytes-=sentbytes;
    data+=sentbytes;
    endtime_response=pds_gettimem()+DRVFTP_DEFAULT_TIMEOUTMS_RESPONSE;
   }
#ifdef MPXPLAY_WIN32
   Sleep(0);
#endif
  }while((pds_gettimem()<=endtime_response));
 }
 return total_bytes_sent;
}

static long ftpdrv_lowlevel_bytes_buffered(struct ftpdrive_socket_info_s *socket_info)
{
 unsigned long bytes_stored=0;
 if(socket_info->socknum){
  long retcode=ftpdrv_lowlevel_ioctl_socket((SOCKET)socket_info->socknum,FIONREAD,&bytes_stored);
  if(retcode==SOCKET_ERROR)
   return -1;
 }
 return bytes_stored;
}

static long ftpdrv_lowlevel_receive(struct ftpdrive_socket_info_s *socket_info,char *data,unsigned long buflen)
{
 long bytes_received=0;

 if(socket_info->socknum){
  SOCKET sock=(SOCKET)socket_info->socknum;
#ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(NULL);
  tcp_tick(&sock);
#endif
  bytes_received=recv(sock,data,buflen,0);
  if(bytes_received<0)
   bytes_received=0;
 }
 return bytes_received;
}

ftpdrive_lowlevel_func_s FTPDRV_lowlevel_funcs={
 "ftp:",
 21,
 &ftpdrv_lowlevel_global_init,
 &ftpdrv_lowlevel_global_deinit,
 &ftpdrv_lowlevel_addressinfo_init,
 NULL,
 &ftpdrv_lowlevel_socket_open,
 &ftpdrv_lowlevel_socket_shutdown,
 &ftpdrv_lowlevel_socket_close,
 &ftpdrv_lowlevel_socket_select,
 &ftpdrv_lowlevel_socket_connect,
 NULL,
 &ftpdrv_lowlevel_socket_listen,
 &ftpdrv_lowlevel_socket_accept,
 &ftpdrv_lowlevel_send,
 &ftpdrv_lowlevel_bytes_buffered,
 &ftpdrv_lowlevel_receive,
};

ftpdrive_lowlevel_func_s HTTPDRV_lowlevel_funcs={
 "http:",
 80,
 &ftpdrv_lowlevel_global_init,
 &ftpdrv_lowlevel_global_deinit,
 &ftpdrv_lowlevel_addressinfo_init,
 NULL,
 &ftpdrv_lowlevel_socket_open,
 &ftpdrv_lowlevel_socket_shutdown,
 &ftpdrv_lowlevel_socket_close,
 &ftpdrv_lowlevel_socket_select,
 &ftpdrv_lowlevel_socket_connect,
 NULL,
 &ftpdrv_lowlevel_socket_listen,
 &ftpdrv_lowlevel_socket_accept,
 &ftpdrv_lowlevel_send,
 &ftpdrv_lowlevel_bytes_buffered,
 &ftpdrv_lowlevel_receive,
};

//------------------------------------------------------------------------
//SSL/TLS (ftps: implicit SSL, ftpes: explicit TLS) handling with OpenSSL's SSLEAY32.DLL library (under win32 only)

#ifdef MPXPLAY_WIN32

enum ssleayfuncnums{
 FUNC_SSL_library_init,
 FUNC_SSLv23_method,
 FUNC_SSL_CTX_new,
 FUNC_SSL_new,
 FUNC_SSL_set_fd,
 FUNC_SSL_connect,
 FUNC_SSL_write,
 FUNC_SSL_read,
 FUNC_SSL_peek,
 FUNC_SSL_shutdown,
 FUNC_SSL_free,
 FUNC_SSL_CTX_free,
};

static struct pds_win32dllcallfunc_t ftpsdrv_ssleay_funcs[]={
 {"SSL_library_init",NULL,0},
 {"SSLv23_method",NULL,0},     // returns method
 {"SSL_CTX_new",NULL,1},       // arg1=method     returns ctx
 {"SSL_new",NULL,1},           // arg1=ctx        returns ssl
 {"SSL_set_fd",NULL,2},        // arg1=ssl,arg2=socknum
 {"SSL_connect",NULL,1},       // arg1=ssl        returns err
 {"SSL_write",NULL,3},         // arg1=ssl,arg2=string,arg3=len returns err
 {"SSL_read",NULL,3},          // arg1=ssl,arg2=buf,arg3=buflen returns err
 {"SSL_peek",NULL,3},          // arg1=ssl,arg2=buf,arg3=buflen returns err
 {"SSL_shutdown",NULL,1},      // arg1=ssl
 {"SSL_free",NULL,1},          // arg1=ssl
 {"SSL_CTX_free",NULL,1},      // arg1=ctx
 {NULL,NULL,0}
};

extern unsigned int drvftp_cmdctrl_send_command_check_respcode(struct ftpdrive_info_s *ftpi,struct ftpdrive_socket_info_s *socketinfo_session,char *command,unsigned int expected_respcode);

static int ftpsdrv_llwin32_socket_ssl_connect(struct ftpdrive_socket_info_s *socketinfo_any);

static HMODULE ftpsdrv_ssleay32_dllhand;
static void *ftpsdrv_ssleay_ctx;

static int ftpsdrv_llwin32_global_init(void)
{
 if(!wsadata.wVersion && !wsadata.wHighVersion)
  if(WSAStartup(MAKEWORD(2,2),&wsadata)!=NO_ERROR)
   return 0;

 if(!ftpsdrv_ssleay32_dllhand){
  ftpsdrv_ssleay32_dllhand=newfunc_dllload_winlib_load("ssleay32.dll");
  if(!ftpsdrv_ssleay32_dllhand){
   drvftp_message_write_error("Couldn't load OPENSSL's SSLEAY32.DLL !");
   goto err_out_ftpsinit;
  }
  if(!newfunc_dllload_winlib_getfuncs(ftpsdrv_ssleay32_dllhand,ftpsdrv_ssleay_funcs)){
   drvftp_message_write_error("A function is missing from SSLEAY32.DLL !");
   goto err_out_ftpsinit;
  }
 }
 if(!ftpsdrv_ssleay_ctx){
  void *meth;
  newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_library_init],NULL,NULL,NULL);
  meth=(void *)newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSLv23_method],NULL,NULL,NULL);
  ftpsdrv_ssleay_ctx=(void *)newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_CTX_new],meth,NULL,NULL);
 }
 if(!ftpsdrv_ssleay_ctx){
  drvftp_message_write_error("Couldn't init SSLEAY32! (ctx=NULL)");
  goto err_out_ftpsinit;
 }
 return 1;
err_out_ftpsinit:
 newfunc_dllload_winlib_close(ftpsdrv_ssleay32_dllhand);
 return 0;
}

static void ftpsdrv_llwin32_global_deinit(void)
{
 if(ftpsdrv_ssleay_ctx){
  newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_CTX_free],ftpsdrv_ssleay_ctx,NULL,NULL);
  funcbit_smp_pointer_put(ftpsdrv_ssleay_ctx,NULL);
 }
 newfunc_dllload_winlib_close(ftpsdrv_ssleay32_dllhand);
 funcbit_smp_pointer_put(ftpsdrv_ssleay32_dllhand,NULL);
 if(wsadata.wVersion || wsadata.wHighVersion)
  WSACleanup();
 pds_memset(&wsadata,0,sizeof(wsadata));
}

static int ftpesdrv_llwin32_addressinfo_init(struct ftpdrive_socket_info_s *socketinfo_session,char *servername,mpxp_uint8_t *ip_local,mpxp_uint8_t *ip_remote)
{
 funcbit_smp_enable(socketinfo_session->flags,DRVFTP_SOCKINFO_FLAG_SSL_DISABLED);
 return ftpdrv_lowlevel_addressinfo_init(socketinfo_session,servername,ip_local,ip_remote);
}

static int ftpesdrv_llwin32_login_preprocess(void *ftpi,struct ftpdrive_socket_info_s *socketinfo_session)
{
 if(!drvftp_cmdctrl_send_command_check_respcode((struct ftpdrive_info_s *)ftpi,socketinfo_session,"AUTH TLS",234))
  return 0;
 funcbit_disable(socketinfo_session->flags,DRVFTP_SOCKINFO_FLAG_SSL_DISABLED);
 return ftpsdrv_llwin32_socket_ssl_connect(socketinfo_session);
}

static int ftpsdrv_llwin32_socket_open(struct ftpdrive_socket_info_s *socketinfo_any,unsigned long bufsize)
{
 if(!ftpdrv_lowlevel_socket_open(socketinfo_any,bufsize))
  return 0;

 if(!socketinfo_any->sslhand){
  socketinfo_any->sslhand=(void *)newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_new],ftpsdrv_ssleay_ctx,NULL,NULL);
  if(!socketinfo_any->sslhand)
   return 0;
 }
 newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_set_fd],socketinfo_any->sslhand,(void *)(socketinfo_any->socknum),NULL);
 return 1;
}

static void ftpsdrv_llwin32_socket_shutdown(struct ftpdrive_socket_info_s *socketinfo_any)
{
 if(socketinfo_any->socknum){
  ftpdrv_lowlevel_socket_shutdown(socketinfo_any);
  if(socketinfo_any->sslhand)
   newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_shutdown],socketinfo_any->sslhand,NULL,NULL);
 }
}

static void ftpsdrv_llwin32_socket_close(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int full)
{
 if(socketinfo_any->sslhand)
  newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_shutdown],socketinfo_any->sslhand,NULL,NULL);
 ftpdrv_lowlevel_socket_close(socketinfo_any,full);
 if(full){
  if(socketinfo_any->sslhand){
   newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_free],socketinfo_any->sslhand,NULL,NULL);
   funcbit_smp_pointer_put(socketinfo_any->sslhand,NULL);
  }
 }
}

static int ftpsdrv_llwin32_socket_ssl_connect(struct ftpdrive_socket_info_s *socketinfo_any)
{
 int err;
 unsigned long nonblock;
 mpxp_uint64_t endtime_sslconnect;
 struct fptdrive_timeoutmsg_s tos;

 if(!socketinfo_any->sslhand || funcbit_test(socketinfo_any->flags,DRVFTP_SOCKINFO_FLAG_SSL_DISABLED))
  return 1;

 nonblock=1;                              // non block ioctl
 ftpdrv_lowlevel_ioctl_socket(socketinfo_any->socknum,FIONBIO,&nonblock); // else SSL_connect can freeze

 err=newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_set_fd],socketinfo_any->sslhand,(void *)(socketinfo_any->socknum),NULL);

 endtime_sslconnect=pds_gettimem()+DRVFTP_DEFAULT_TIMEOUTMS_RESPONSE;
 drvftp_message_timeout_init(&tos,endtime_sslconnect,"SSL connect retry %d sec ...");

 do{
  err=newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_connect],socketinfo_any->sslhand,NULL,NULL);
  if(err!=-1){
   nonblock=0;                              // block ioctl
   ftpdrv_lowlevel_ioctl_socket(socketinfo_any->socknum,FIONBIO,&nonblock); // else read will fail
   return 1;
  }
  drvftp_message_timeout_write(&tos);
 }while(pds_gettimem()<endtime_sslconnect);
 drvftp_message_timeout_close(&tos);
 ftpsdrv_llwin32_socket_shutdown(socketinfo_any);
 ftpsdrv_llwin32_socket_close(socketinfo_any,1);
 return 0;
}

static int ftpsdrv_llwin32_socket_accept(struct ftpdrive_socket_info_s *socketinfo_filehand)
{
 if(!ftpdrv_lowlevel_socket_accept(socketinfo_filehand))
  return 0;
 return ftpsdrv_llwin32_socket_ssl_connect(socketinfo_filehand);
}

static long ftpsdrv_llwin32_send(struct ftpdrive_socket_info_s *socket_info,char *data,unsigned long bytes_to_send)
{
 if(!socket_info->sslhand || funcbit_test(socket_info->flags,DRVFTP_SOCKINFO_FLAG_SSL_DISABLED))
  return ftpdrv_lowlevel_send(socket_info,data,bytes_to_send);
 else{
  long retcode;
  retcode=newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_write],socket_info->sslhand,data,(void *)(bytes_to_send));
  if(retcode<0)
   retcode=0;
  return retcode;
 }
}

static long ftpsdrv_llwin32_bytes_buffered(struct ftpdrive_socket_info_s *socket_info)
{
 if(!socket_info->sslhand || funcbit_test(socket_info->flags,DRVFTP_SOCKINFO_FLAG_SSL_DISABLED))
  return ftpdrv_lowlevel_bytes_buffered(socket_info);
 else{
  long bytes_received;
  unsigned long ioctlnum;
  char data[256];

  ioctlnum=1;                                           // non block ioctl
  ftpdrv_lowlevel_ioctl_socket(socket_info->socknum,FIONBIO,&ioctlnum);//

  bytes_received=newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_peek],socket_info->sslhand,(void *)(&data[0]),(void *)(sizeof(data)-1));

  ioctlnum=0;                                                // block ioctl
  ftpdrv_lowlevel_ioctl_socket(socket_info->socknum,FIONBIO,&ioctlnum);//

  if(bytes_received<0)
   bytes_received=0;
  return bytes_received;
 }
}

static long ftpsdrv_llwin32_receive(struct ftpdrive_socket_info_s *socket_info,char *data,unsigned long buflen)
{
 if(!socket_info->sslhand || funcbit_test(socket_info->flags,DRVFTP_SOCKINFO_FLAG_SSL_DISABLED))
  return ftpdrv_lowlevel_receive(socket_info,data,buflen);
 else{
  long bytes_received;
  bytes_received=newfunc_dllload_winlib_callfunc(&ftpsdrv_ssleay_funcs[FUNC_SSL_read],socket_info->sslhand,data,(void *)(buflen));
  if(bytes_received<0)
   bytes_received=0;
  return bytes_received;
 }
}

ftpdrive_lowlevel_func_s FTPSDRV_lowlevel_funcs={
 "ftps:",
 990,
 &ftpsdrv_llwin32_global_init,
 &ftpsdrv_llwin32_global_deinit,
 &ftpdrv_lowlevel_addressinfo_init,
 NULL,
 &ftpsdrv_llwin32_socket_open,
 &ftpsdrv_llwin32_socket_shutdown,
 &ftpsdrv_llwin32_socket_close,
 &ftpdrv_lowlevel_socket_select,
 &ftpdrv_lowlevel_socket_connect,
 &ftpsdrv_llwin32_socket_ssl_connect,
 &ftpdrv_lowlevel_socket_listen,
 &ftpsdrv_llwin32_socket_accept,
 &ftpsdrv_llwin32_send,
 &ftpsdrv_llwin32_bytes_buffered,
 &ftpsdrv_llwin32_receive
};

ftpdrive_lowlevel_func_s FTPESDRV_lowlevel_funcs={
 "ftpes:",
 21,
 &ftpsdrv_llwin32_global_init,
 &ftpsdrv_llwin32_global_deinit,
 &ftpesdrv_llwin32_addressinfo_init,
 &ftpesdrv_llwin32_login_preprocess,
 &ftpsdrv_llwin32_socket_open,
 &ftpsdrv_llwin32_socket_shutdown,
 &ftpsdrv_llwin32_socket_close,
 &ftpdrv_lowlevel_socket_select,
 &ftpdrv_lowlevel_socket_connect,
 &ftpsdrv_llwin32_socket_ssl_connect,
 &ftpdrv_lowlevel_socket_listen,
 &ftpsdrv_llwin32_socket_accept,
 &ftpsdrv_llwin32_send,
 &ftpsdrv_llwin32_bytes_buffered,
 &ftpsdrv_llwin32_receive
};

#endif // MPXPLAY_WIN32

#endif // MPXPLAY_LINK_TCPIP
