//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2012 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 "control/cntfuncs.h"
#include <stdio.h>

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
FILE *debug_fp;
#endif

//-------------------------------------------------------------------------
void drvftp_message_write_error(char *message)
{
 char sout[1024];
 //pds_textdisplay_printf(message);
 snprintf(sout,(sizeof(sout)-1),"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);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
   if(debug_fp)
    fprintf(debug_fp,"%s\n",sout);
#endif
   display_timed_message(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,unsigned int maxlen)
{
 int drivenum=pds_getdrivenum_from_path(pathname);
 if(drivenum>=0)
  pathname+=sizeof(PDS_DIRECTORY_DRIVE_STR)-1;
 pds_strncpy(remotename,pathname,maxlen-1);
 remotename[maxlen-1] = 0;
#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 <ws2tcpip.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 <string.h>
 #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 void ftpdrv_lowlevel_socket_tick(SOCKET sock)
{
#if defined(MPXPLAY_LINK_WATTCP32)
 #ifdef MPXPLAY_WATTCP_USETICK
  tcp_tick(NULL);
  tcp_tick(&sock);
 #endif
#elif defined(MPXPLAY_LINK_SWSCK32)
 SWS_SockSleep(0);
#endif
}

void tcpcommon_socket_sleep(ftpdrive_socket_t socknum)
{
#if defined(MPXPLAY_LINK_WATTCP32)
 #ifdef MPXPLAY_WATTCP_USETICK
  SOCKET sock = (SOCKET)socknum;
  tcp_tick(NULL);
  tcp_tick(&sock);
 #endif
#elif defined(MPXPLAY_LINK_SWSCK32)
 SWS_SockSleep(1);
#elif defined(MPXPLAY_WIN32)
 Sleep(0);
#endif
}

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

/*static void ftpdrv_lowlevel_socket_abort(SOCKET sock)
{
 struct linger lgr;

 // Reset the connection when closed
 lgr.l_onoff = 1;
 lgr.l_linger = 0;
 setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&lgr, sizeof lgr);
}*/

static void tcpcommon_socket_set_mode_block(SOCKET sock)
{
 unsigned long nonblock = 0;
 ftpdrv_lowlevel_ioctl_socket(sock, FIONBIO, &nonblock);
}

static void tcpcommon_socket_set_mode_nonblock(SOCKET sock)
{
 unsigned long nonblock = 1;
 ftpdrv_lowlevel_ioctl_socket(sock, FIONBIO, &nonblock);
}

/*static int ConnectAsync( SOCKET s, struct sockaddr* pAddr, int len)
  {
  int iRet = 0;

  tcpcommon_socket_set_mode_nonblock(s);

  if ( connect( s, pAddr, len)
    && (EWOULDBLOCK == (iRet = GetLastError()) || EINPROGRESS == iRet || EAGAIN == iRet)
  ) {
    int count;

    for ( count = 0; ; ++count)
      {
      fd_set setw, sete;
      struct timeval tval;

      FD_ZERO( &setw);
      FD_SET( s, &setw);
      FD_ZERO( &sete);
      FD_SET( s, &sete);

      tval.tv_usec = 333 * 1000UL;
      tval.tv_sec = 0;

      iRet = select( FD_SETSIZE, NULL, &setw, &sete, &tval);
      if ( 0 == iRet)
        {
        if(count >= 5)
         return -1;
        continue;
        }
      else if ( SOCKET_ERROR == iRet)
        {
        iRet = GetLastError();
        break;
        }
      else if ( FD_ISSET( s, &sete) || FD_ISSET( s, &setw))
        {
        socklen_t n = sizeof iRet;
        getsockopt( s, SOL_SOCKET, SO_ERROR, (char*)&iRet, &n);
        break;
        }
      }
    }

  tcpcommon_socket_set_mode_block( s);

  return iRet;
}*/

// !!! required to call before every socket open!
void mpxplay_tcpcommon_socket_reset(struct ftpdrive_socket_info_s *socketinfo_any)
{
 funcbit_smp_value_put(socketinfo_any->socknum, (ftpdrive_socket_t)INVALID_SOCKET);
}

unsigned int mpxplay_tcpcommon_socket_check(struct ftpdrive_socket_info_s *socketinfo_any)
{
 if(((SOCKET)socketinfo_any->socknum) == INVALID_SOCKET)
  return 0;
 return 1;
}

#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_IP4_LEN) && next);
 if((i<DRVFTP_IP4_LEN) || (!ipnums[0]))
  return 0;
 return 1;
}
#endif

//----------------------------------------------------------------------
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
}

/*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;
 struct addrinfo *result, *ptr, hints;
 char portnumstr[16], hostname_local[MAX_PATHNAMELEN];

 if(ip_local){
  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_IP4_LEN); // !!!
 }

 if(ip_remote){
  pds_ltoa(socketinfo_session->portnum, portnumstr);
  pds_memset((void *)&hints, 0, sizeof(hints));
  hints.ai_family   = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;

  if(getaddrinfo(servername, portnumstr, &hints, &result) != 0){
   drvftp_message_write_error("getaddrinfo failed!");
   return 0;
  }

  for(ptr = result; ptr!=NULL; ptr=ptr->ai_next){
   if(ptr->ai_family == AF_INET){
    struct sockaddr_in *sockaddr_ipv4 = (struct sockaddr_in *)ptr->ai_addr;
    pds_memcpy(ip_remote, &sockaddr_ipv4->sin_addr.s_addr, DRVFTP_IP4_LEN);
    return 1;
   }
  }
 }

 return 0;
}*/

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(ip_local){
  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_IP4_LEN); // !!!
 }

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

#ifdef MPXPLAY_WIN32
static int ftpdrv_lowlevel_socket_open(struct ftpdrive_socket_info_s *socketinfo_any,unsigned long bufsize)
{
 SOCKET portsock;
 int rcvbufsize = bufsize;
 BOOL optval;

 if(!mpxplay_tcpcommon_socket_check(socketinfo_any)){
  portsock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if(portsock == INVALID_SOCKET)
   return 0;
  funcbit_smp_value_put(socketinfo_any->socknum,(ftpdrive_socket_t)portsock);
 }else
  portsock = (SOCKET)socketinfo_any->socknum;

 optval=1;
 setsockopt(portsock,IPPROTO_TCP,TCP_NODELAY,(char *)&optval,sizeof(optval));

 tcpcommon_socket_set_mode_block(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;
}

#else

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

 while(portsock == INVALID_SOCKET){
  switch(retry){
   case 0: break;
   case 1: tcpcommon_socket_sleep((ftpdrive_socket_t)portsock);
           break;
   case 2: ftpdrv_lowlevel_global_deinit();
           if(!ftpdrv_lowlevel_global_init())
            return 0;
           break;
   default: return 0;
  }
  portsock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  retry++;
 }

 funcbit_smp_value_put(socketinfo_any->socknum,(ftpdrive_socket_t)portsock);

#if defined(MPXPLAY_LINK_SWSCK32)
 optval=1;
 setsockopt(portsock,IPPROTO_TCP,TCP_NODELAY,(char *)&optval,sizeof(optval));
#endif

 //tcpcommon_socket_set_mode_block(portsock);

 return 1;
}

#endif

static void ftpdrv_lowlevel_socket_shutdown(struct ftpdrive_socket_info_s *socketinfo_any)
{
 if(mpxplay_tcpcommon_socket_check(socketinfo_any)){
  SOCKET portsock=(SOCKET)socketinfo_any->socknum;
  shutdown(portsock,SD_BOTH);
  ftpdrv_lowlevel_socket_tick(portsock);
 }
}

static void ftpdrv_lowlevel_socket_close(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int full)
{
 if(mpxplay_tcpcommon_socket_check(socketinfo_any)){
  SOCKET portsock=(SOCKET)socketinfo_any->socknum;
#ifndef MPXPLAY_WIN32
  tcpcommon_socket_set_mode_block(portsock);
#endif
  shutdown(portsock,SD_BOTH);
  //ftpdrv_lowlevel_socket_tick(portsock);
  //ftpdrv_lowlevel_socket_abort(portsock);
  closesocket(portsock);
  //tcpcommon_socket_sleep((ftpdrive_socket_t)portsock);
 }
 mpxplay_tcpcommon_socket_reset(socketinfo_any);
}

#ifdef MPXPLAY_LINK_WATTCP32

static int ftpdrv_lowlevel_socket_select(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int selmode)
{
 const struct timeval tv={0,1};
 struct fd_set fds;
 if(!mpxplay_tcpcommon_socket_check(socketinfo_any))
  return 0;
 ftpdrv_lowlevel_socket_tick((SOCKET)socketinfo_any->socknum);
 FD_ZERO(&fds);
 FD_SET((SOCKET)socketinfo_any->socknum, &fds);
 return select_s(0,((selmode&DRVFTP_SOCKSELECT_MODE_READ)? &fds:NULL),((selmode&DRVFTP_SOCKSELECT_MODE_WRITE)? &fds:NULL),NULL,(struct timeval *)&tv);
}
#else
static int ftpdrv_lowlevel_socket_select(struct ftpdrive_socket_info_s *socketinfo_any,unsigned int selmode)
{
 const struct timeval tv={0,0};
 struct fd_set fds;
 if(!mpxplay_tcpcommon_socket_check(socketinfo_any))
  return 0;
 ftpdrv_lowlevel_socket_tick((SOCKET)socketinfo_any->socknum);
 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,(struct timeval *)&tv);
}
#endif

static int ftpdrv_lowlevel_socket_connect(struct ftpdrive_socket_info_s *socketinfo_any)
{
 SOCKET portsock;
 struct sockaddr_in clientservice;

 if(!mpxplay_tcpcommon_socket_check(socketinfo_any))
  return 0;
 portsock = (SOCKET)socketinfo_any->socknum;

 pds_memset(&clientservice,0,sizeof(clientservice));
 clientservice.sin_family=AF_INET;
 pds_memcpy(&(clientservice.sin_addr.s_addr),socketinfo_any->conn_ip_addr,DRVFTP_IP4_LEN);
 clientservice.sin_port=htons(socketinfo_any->portnum);
 if(connect(portsock,(SOCKADDR *)&clientservice,sizeof(clientservice))==SOCKET_ERROR){
 //if(ConnectAsync(portsock,(SOCKADDR *)&clientservice,sizeof(clientservice))!=0){
  closesocket(portsock);
  mpxplay_tcpcommon_socket_reset(socketinfo_any);
  return 0;
 }
 ftpdrv_lowlevel_socket_tick(portsock);
 return 1;
}

// this is the worse under Windows
/*static int ftpdrv_lowlevel_socket_connect(struct ftpdrive_socket_info_s *socketinfo_any)
{
 SOCKET portsock=(SOCKET)socketinfo_any->socknum;
 int retcode = 0;
 mpxp_uint64_t endtime_connect=pds_gettimem()+DRVFTP_DEFAULT_TIMEOUTMS_RESPONSE;
 struct sockaddr_in clientservice;
 struct fptdrive_timeoutmsg_s tos;

 if(!mpxplay_tcpcommon_socket_check(socketinfo_any))
  return 0;

 drvftp_message_timeout_init(&tos,endtime_connect,"TCP connect retry %d sec ...");

 tcpcommon_socket_set_mode_nonblock(portsock);

 pds_memset(&clientservice, 0, sizeof(clientservice));
 clientservice.sin_family = AF_INET;
 pds_memcpy(&(clientservice.sin_addr.s_addr), socketinfo_any->conn_ip_addr, DRVFTP_IP4_LEN);
 clientservice.sin_port = htons(socketinfo_any->portnum);

 connect(portsock,(SOCKADDR *)&clientservice,sizeof(clientservice));

 do{
  if(ftpdrv_lowlevel_socket_select(socketinfo_any,DRVFTP_SOCKSELECT_MODE_WRITE) > 0){
   retcode = 1;
   break;
  }
  if((pds_look_extgetch() == KEY_ESC) && !mpxplay_control_keyboard_get_topfunc()){ // !!!
   pds_extgetch();
   break;
  }
  drvftp_message_timeout_write(&tos);
  ftpdrv_lowlevel_socket_tick(portsock);
 }while((pds_gettimem() <= endtime_connect));

 tcpcommon_socket_set_mode_block(portsock);

 drvftp_message_timeout_close(&tos);

 return retcode;
}*/

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

 if(!mpxplay_tcpcommon_socket_check(socketinfo_filehand))
  return 0;

 portsock=(SOCKET)socketinfo_filehand->socknum;

 do{
  pds_memset(&service,0,sizeof(service));
  service.sin_family = AF_INET;
  pds_memcpy(&(service.sin_addr.s_addr),ip_local,DRVFTP_IP4_LEN);
  service.sin_port = 0;

  if(bind(portsock,(SOCKADDR *)&service,sizeof(service))==SOCKET_ERROR)
   goto try_again;

  pds_memset(&add,0,sizeof(add));
  socksize=sizeof(add);

  if(getsockname(portsock, (SOCKADDR *) &add,&socksize)==SOCKET_ERROR)
   goto try_again;

  portnum=ntohs(add.sin_port);
  if(!portnum)
   goto try_again;

  if(listen(portsock,1)!=SOCKET_ERROR){
   success=1;
   break;
  }

try_again:
  tcpcommon_socket_sleep((ftpdrive_socket_t)portsock);
 }while(--retry);

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

 return portnum;
}

static int ftpdrv_lowlevel_socket_accept(struct ftpdrive_socket_info_s *socketinfo_filehand)
{
 SOCKET portsock, ps;
 int retcode = 0;

 if(!mpxplay_tcpcommon_socket_check(socketinfo_filehand))
  return retcode;

 portsock = (SOCKET)socketinfo_filehand->socknum;

 ftpdrv_lowlevel_socket_tick(portsock);

 tcpcommon_socket_set_mode_nonblock(portsock); // else accept can freeze

 ps = accept(portsock,NULL,NULL);
 if(ps != INVALID_SOCKET){
  closesocket(portsock);
  portsock = ps;
  tcpcommon_socket_set_mode_block(portsock); // else write/send can fail
  retcode = 1;
 }

 if(retcode){
  ftpdrv_lowlevel_socket_tick(portsock);
  funcbit_smp_value_put(socketinfo_filehand->socknum,(ftpdrive_socket_t)portsock);
 }else
  tcpcommon_socket_sleep((ftpdrive_socket_t)portsock); // ???

 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(mpxplay_tcpcommon_socket_check(socket_info)){
  SOCKET portsock=(SOCKET)socket_info->socknum;
  ftpdrv_lowlevel_socket_tick(portsock);
  do{
   long sentbytes = send(portsock,data,leftbytes,0);
   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;
   }
   tcpcommon_socket_sleep((ftpdrive_socket_t)portsock);
  }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(mpxplay_tcpcommon_socket_check(socket_info)){
  SOCKET portsock = (SOCKET)socket_info->socknum;
  ftpdrv_lowlevel_socket_tick(portsock);
  if(ftpdrv_lowlevel_ioctl_socket(portsock,FIONREAD,&bytes_stored) == 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(mpxplay_tcpcommon_socket_check(socket_info)){
  SOCKET portsock = (SOCKET)socket_info->socknum;
  ftpdrv_lowlevel_socket_tick(portsock);
  bytes_received = recv(portsock, 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(mpxplay_tcpcommon_socket_check(socketinfo_any)){
  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;
 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;

 tcpcommon_socket_set_mode_nonblock(socketinfo_any->socknum); // 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,"TCP/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){
   tcpcommon_socket_set_mode_block((SOCKET)socketinfo_any->socknum); // 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;
  char data[256];

  tcpcommon_socket_set_mode_nonblock((SOCKET)socket_info->socknum);

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

  tcpcommon_socket_set_mode_block((SOCKET)socket_info->socknum);

  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
