/*
 * Version 1.6
 *
 * Copyright 2013 - Vector Informatik GmbH
 *
 * This CAPL model simulates a File Server (ISO11783 Part 13)
 *
 * Dependencies
 *       Nodelayer DLLs ISO11783DLL.dll
 *       ISO11783.dbc database
 *       FileServer.dbc database
 *       FileServer.xvp Panel (optional)
 *
 * History
 * 1.0   2004-09-10 Created
 * 1.1   2004-04-28 Changed PGN of FSClient and FSServer
 * 1.2	 2005-11-24 Adapted to meet draft international standard ISO/DIS 11783-13
 * 1.3   2007-06-29 Remove some compiler warnings
 * 1.4   2007-11-12 Support FDIS ISO11783-13 from 2007-05-15
 * 1.5   2010-11-12 Update File Server Status and Get Server Properties message 
 * 1.5   2013-03-06 Improve write operations
 */
variables
{
  //
  // Constants
  //

  const BYTE kNullAddr           = 0xfe;   // Null address
  const BYTE kGlobalAddr         = 0xff;   // Global address
  const LONG kSuccess            = 0;      // Function was executed successfully

  const int kNotConnected        = 0;      // State: not connected
  const int kStarting            = 1;      // State: ECU is claiming an address
  const int kOperational         = 2;      // State: operational

  const int kMaxClient           = 10;     // Max. number of clients
  const int kMaxPath             = 256;    // Max. size of a path
  const int kMaxHandles          = 16;     // Max. of files which one client can open simultaniously

  //
  // Global variables for communication
  //
  int   gECUAddress   = kNullAddr;         // Address of the WSM
  char  gECULabel[32] = "FileServer";      // Label for output to write window
  char  gECUBus[20]   = "default";
  int   gECUState     = kNotConnected;     // State of the communication state machine of this WSM
  dword gECUHandle;                        // ECU handle
  char  gECUName[8];                       // Device name of this ECU
  long  ci, i, l, k;                       // index for 'for' loops
  long  result;                            // return value of Nodelayer functions

  // File Server variables
  char        gVolumes[1][32] = { "Volume" };
  char        gCurrentDir[kMaxClient][kMaxPath];    // Current directory for each client
  long        gFileHandle[kMaxClient][kMaxHandles]; // Open file handles for each client
  dword       gMaintainTime[kMaxClient];            // Last maintenance time in 10us since measurement start
  byte        gClientAddress[kMaxClient];           // Address of the client
  WORD        gMfcCode[kMaxClient];                 // Manufacturer of the client
  long        gOpenFiles = 0;                       // Number of all open files;
  byte        gFileServerStatus = 0;                // Current file server status

  msTimer     FSStatusTimer;               // Time for status message
  msTimer     FSTimeoutTimer;              // Observe status timeout
  pg FSServer TX_FSStatus;                 // Status PG
  char        TX_Buffer[65535+4];
  char        RX_Buffer[65535+4];
  pg FSServer RX_FSResponse = { DLC = 1785 }; // Status PG
  pg FSClient RX_FSRequest = { DLC = 1785 };  // Last received File Server Command

  // Definition of debugging constants
  const int kDbgInfo    = 10;
  const int kDbgWarning = 5;
  const int kDbgError   = 1;
  const int kDbgQuiet   = 0;

  // General global variables
  int gDbgLevel         = kDbgWarning; // Set debug level for output to write window
}

on preStart
{
  setWriteDbgLevel( gDbgLevel );

  EcuInit(); // initialize global variables
}

on start
{
  if ((getValue( EvISOBUS_ECU_PWR ) > 10.0) && (getValue(EvFS_Connection))) {
    EcuStartUp();
  }
}

/*
 * File Server Status Timer
 */
on timer FSStatusTimer
{
  char statusData[8] = { 0x00, // FS function
                         0x00, // FS status
                         0x00, // Num of open files
                         -1,
                         -1,
                         -1,
                         -1,
                         -1 };

  // file server status
  statusData[1] = gFileServerStatus;
  // number of open files
  statusData[2] = gOpenFiles;

  if (gECUState == kOperational) {
    statusData[2] = gOpenFiles;

    Iso11783TxReqPG( gECUHandle, RX_FSResponse.pgn, kGlobalAddr, 7, 8, statusData ); 

    setTimer( FSStatusTimer, 2000 );
  }
}

/*
 * Initialize all global variables in this function.
 */
void EcuInit()
{
  int l;

  //
  // Initialize the global variables
  //
  gECUAddress     = kNullAddr;
  gECUState       = kNotConnected;
  gECUHandle      = 0;

  // set working set master name
  Iso11783MakeName( gECUName, 
                    FileServer.NmJ1939AAC,
                    FileServer.NmJ1939IndustryGroup,
                    FileServer.NmJ1939SystemInstance,
                    FileServer.NmJ1939System,
                    FileServer.NmJ1939Function,
                    FileServer.NmJ1939FunctionInstance,
                    FileServer.NmJ1939ECUInstance,
                    FileServer.NmJ1939ManufacturerCode,
                    FileServer.NmJ1939IdentityNumber
                  );

  // init file server variables
  for( i = 0; i < kMaxClient; i++ ) {
    strncpy( gCurrentDir[i], "\\", kMaxPath );
    gMaintainTime[i]  = 0;
    gClientAddress[i] = kNullAddr;
    gMfcCode[i]       = 0;

    for( l = 0; l < kMaxHandles; l++ ) {
      gFileHandle[i][l] = 0;
    }
  }

  // status command
  TX_FSStatus.FSServerCommand     = 0; // File Server Status command
  TX_FSStatus.FSNumberOfOpenFiles = 0;
  TX_FSStatus.FSBusyReading       = 0;
  TX_FSStatus.FSBusyWriting       = 0;
}

/*
 * Call this function to connect the node to the bus
 */
void EcuStartUp()
{
  char absPath[kMaxPath];
  long directory;

  if ((gECUHandle == 0) && (gECUState == kNotConnected)) {
    // create ECU handle
    gECUHandle  = Iso11783CreateECU( gECUBus, gECUName );
    if (gECUHandle != 0) {
		
	    gECUAddress = FileServer.NmStationAddress;
      if  (Iso11783ECUGoOnline( gECUHandle, gECUAddress ) == 0) {

		    // set root directory
		    if (iso11783FSSetPath( "FSRoot" ) != kSuccess) {
		      writeDbgLevel( kDbgError, "<%s> iso11783FSSetPath failed !!! ", gECULabel );
		    }

        // create volume directories
        for( i = 0; i < elCount(gVolumes); i++ ) {
          if (iso11783FSMakeAbsolutePath( gVolumes[0], "\\\\", 0, kMaxPath, absPath, gVolumes[0], 0 ) == 0) {
            directory = Iso11783FSOpenFile( absPath, 0x3 );
           if (directory) {
              Iso11783FSCloseFile(directory);
            }
          }
        }

		    gECUState = kStarting;
		}
		else {
			writeDbgLevel( kDbgError, "<%s> Iso11783ECUGoOnline failed !!! ", gECULabel );
		}	
	}
	else {
		writeDbgLevel( kDbgError, "<%s> Iso11783CreateECU failed !!! ", gECULabel );
	}
  }
  else {
    writeDbgLevel( kDbgInfo, "<%s> function 'EcuStartUp' failed, already connected", gECULabel );
  }
}

//
// Call this function to disconnect the node from the bus
//
void EcuShutDown()
{
  if (gECUState != kNotConnected) {
    gECUState   = kNotConnected;
    gECUAddress = kNullAddr;

    cancelTimer( FSStatusTimer );

    putValue( EvFS_EcuAddress, kNullAddr );

    // close all open files
    for( i = 0; i < kMaxClient; i++ ) {
      if (gClientAddress[i] != kNullAddr) {
        for( k = 0; k < kMaxHandles; k++ ) {
          if (gFileHandle[i][k] > 0) {
            // file is open close it
            iso11783FSCloseFile( gFileHandle[i][k] );
            gFileHandle[i][k] = 0;
          }
        }
      }
    }
    fsUpdateEnvVars();

    Iso11783DestroyECU( gECUHandle );
    gECUHandle = 0;
    gOpenFiles = 0;
  }
}

/*
 * Sends a File Server Command. The data is taken from
 * the global TX_Buffer.
 *
 * dest  - Destination address
 * size  - Number of byte in TX_Buffer
 */
void fsSendResponse2( BYTE dest, LONG size )
{
  int i;

  // fill up with 0xFF, if necessary
  if (size < 8) {
    for( i=size; i < 8; i++ ) {
      TX_Buffer[i] = 0xff;
    }
    size = 8;
  }
  Iso11783TxReqPG( gECUHandle, TX_FSStatus.pgn, dest, 7, size, TX_Buffer ); 
}

/*
 * Copies the data of an array to a parameter group.
 */
utilCopyDataToPG( pg * _pg, char data[], long length )
{
  int i;

  for( i = 0; i < length; i++ ) {
    _pg.byte(i) = data[i];
  }
}

/*
 * The VT lost its address. It tries to find a new address in the range
 * of 128..238. 
 */
EcuLostAddress()
{
  dword result;

  putValue( EvFS_EcuAddress, kNullAddr );

  // calculate new address
  if ((gECUAddress < 128) || (gECUAddress > 238)) {
    gECUAddress = 128;
  }
  else {
    gECUAddress++;
    if (gECUAddress > 238) {
      return;
    }
  }

  // ecu(s) going online
  result = Iso11783ECUGoOnline( gECUHandle, gECUAddress );
  if (result) {
    write( "<%s> Iso11783ECUGoOnline failed, result=%d", gECULabel, result );
  }
}

on envVar EvISOBUS_ECU_PWR
{
  double ecuPWR, ecuCurrent;;
  ecuPWR = getValue( EvISOBUS_ECU_PWR );

  if (getValue( EvFS_Connection )) {
    if ((gECUAddress == kNullAddr) && (ecuPWR > 10.0)) {
      EcuStartUp();
    }
    else if ((gECUAddress < kNullAddr) && (ecuPWR < 8.0)) {
      if (gECUHandle) {
        EcuShutDown();
      }
    }
  }
}

//
// Connect/unconnect the implement to the implement bus
//
on envVar EvFS_Connection
{
  switch(getValue(this)) {
    case 0:
      EcuShutDown();
      break;
    case 1:
      EcuStartUp();
      break;
  }
}

/*
 * File Server 'Mainenance' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSMaintenance()
{
  char deviceName[8];

  i = fsGetClientIndex( RX_FSRequest.SA );

  if (i >= 0) {
    // update the maintenance time for this known client
    gMaintainTime[i]  = timeNow();
  }
  else {
    // we don't know this client, so try to alloc a client entry
    for( i = 0; i < kMaxClient; i++ ) {
      if (gClientAddress[i] == kNullAddr) {
        // free entry found, use it for the new client
        gClientAddress[i] = RX_FSRequest.SA;
        gMaintainTime[i]  = timeNow();

        if (Iso11783GetName( "default", RX_FSRequest.SA, deviceName) == 0) {
          gMfcCode[i] = ((((WORD)deviceName[3] << 3) | (((WORD)deviceName[2] >> 5) & 0x07))) & 0x7ff;
          snprintf( gCurrentDir[i], kMaxPath, "\\\\%s\\MCMC%.4d", gVolumes[0], gMfcCode[i] );
          fsCreateManufacturerDirectory( gCurrentDir[i] );
        }
        else {
          gMfcCode[i] = 0;
          snprintf( gCurrentDir[i], kMaxPath, "\\\\%s", gVolumes[0] );
        }

        fsUpdateEnvVars();
        return;
      }
    }

    // there is no free entry, so we do not store the clients address
  }
}

/*
 * File Server 'Open File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSOpenFile()
{
  char path[kMaxPath];
  char absPath[kMaxPath];
  long file, result;;

  dword info[9];

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    // get file path
    for( i = 0; ((i+5) < RX_FSRequest.DLC) && (i < RX_FSRequest.SA_PathName); i++ ) {
      path[i] = RX_FSRequest.byte(5+i);
    }
    path[i] = 0;

    RX_FSResponse.FSServerCommand     = 0x20;
    RX_FSResponse.OF_TAN              = RX_FSRequest.OF_TAN;
    RX_FSResponse.OF_ATTR_VolumeRemovable = 0;
    RX_FSResponse.OF_ATTR_SupportsLongFilenames = 1;
    RX_FSResponse.OF_ATTR_Directory      = 0;
    RX_FSResponse.OF_ATTR_IsVolume = 0;
    RX_FSResponse.OF_ATTR_Hidden = 0;
    RX_FSResponse.OF_ATTR_ReadOnly = 0;
    RX_FSResponse.OF_ATTR_VolumeIsCaseSensitive = 0;
    RX_FSResponse.DLC             = 5;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    result = iso11783FSMakeAbsolutePath( path, gCurrentDir[ci], 0, kMaxPath, absPath, gVolumes[0], gMfcCode[ci] );

    if (result == 0) {
      if (fsPathIsAccessible( absPath, gMfcCode[ci] )) {
        // find a free handle entry
        for( i = 0; i < kMaxHandles; i++ ) {
          if (gFileHandle[ci][i] == 0) {
            // entry is free
            break;
          }
        }
        if (i < kMaxHandles) {
          file = iso11783FSOpenFile( absPath, RX_FSRequest.byte(2) /*flags*/ );

          if (file > 0) {
        	  if (iso11783FSGetFileInfo( file, elCount(info), info ) >= 0) {
              // get file attribute successfully
              RX_FSResponse.byte(4)   = info[1];
              RX_FSResponse.OF_ErrorCode = 0x0; // success
        	  }
        	  else {
              RX_FSResponse.OF_ErrorCode = 0x2; // Invalid Access
        	  }
    	
            // file successfully opened
            gFileHandle[ci][i]      = file;
            gOpenFiles             += 1;
            RX_FSResponse.OF_Handle = i+1;
            RX_FSResponse.OF_ErrorCode = 0x0; // success
          }
          else {
            RX_FSResponse.OF_Handle = 0x0;
            RX_FSResponse.OF_ErrorCode = 0x4; // file or path not found
          }
        }
        else {
          RX_FSResponse.OF_Handle = 0x0;
          RX_FSResponse.OF_ErrorCode = 0x3; // Too many files open
        }
      }
      else {
        RX_FSResponse.OF_Handle = 0x0;
        RX_FSResponse.OF_ErrorCode = 0x3; // Too many files open
      }
    }
    else if (result == -1002) {
      RX_FSResponse.OF_Handle     = 0x0;
      RX_FSResponse.CCD_ErrorCode = 0x7; // invalid destination name given
    }
    else {
      RX_FSResponse.OF_Handle    = 0x0;
      RX_FSResponse.OF_ErrorCode = 0x1; // access denied
    }

    fsSendResponse();
    fsUpdateEnvVars();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x20;
    RX_FSResponse.OF_TAN          = RX_FSRequest.OF_TAN;
    RX_FSResponse.OF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.OF_Handle       = 0x0;
		RX_FSResponse.OF_ATTR_VolumeRemovable = 0;
		RX_FSResponse.OF_ATTR_SupportsLongFilenames = 0;
		RX_FSResponse.OF_ATTR_IsVolume = 0;
		RX_FSResponse.OF_ATTR_Directory = 0;
		RX_FSResponse.OF_ATTR_Hidden = 0;
		RX_FSResponse.OF_ATTR_ReadOnly = 0;
    RX_FSResponse.OF_ATTR_VolumeIsCaseSensitive = 0;
    RX_FSResponse.DLC             = 5;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Seek File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSSeekFile()
{
  dword file;
  long  pos;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    RX_FSResponse.FSServerCommand = 0x21;
    RX_FSResponse.SF_TAN          = RX_FSRequest.SF_TAN;
    RX_FSResponse.DLC             = 8;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    if ((RX_FSRequest.SF_Handle <= kMaxHandles) && (RX_FSRequest.SF_Handle > 0) && (gFileHandle[ci][RX_FSRequest.SF_Handle-1] > 0)) {
      file = gFileHandle[ci][RX_FSRequest.SF_Handle-1 ];

      pos = iso11783FSSeekFile( file, RX_FSRequest.SF_PositionMode, RX_FSRequest.SF_Offset );
      if (pos >= 0) {
        // seek file successfully
        RX_FSResponse.SF_Position  = pos;
        RX_FSResponse.OF_ErrorCode = 0x0; // success
      }
      else {
        RX_FSResponse.SF_Position  = 0x0;
        RX_FSResponse.OF_ErrorCode = 0x2; // Invalid Access
      }
    }
    else {
      RX_FSResponse.SF_Position  = 0x0;
      RX_FSResponse.SF_ErrorCode = 0x5; // Invalid Handle
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x21;
    RX_FSResponse.SF_TAN          = RX_FSRequest.OF_TAN;
    RX_FSResponse.SF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.SF_Position     = 0x0;
    RX_FSResponse.DLC             = 8;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Read File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSReadFile()
{
  dword file;
  dword flags;
  long  count;
  word  entries;
  byte  buffer[65535];

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    TX_Buffer[0] = 0x22;
    TX_Buffer[1] = RX_FSRequest.RF_TAN;
    count        = 0;

    if ((RX_FSRequest.RF_Handle > 0) && (RX_FSRequest.RF_Handle <= kMaxHandles) && (gFileHandle[ci][RX_FSRequest.RF_Handle-1] != 0)) {
      file  = gFileHandle[ci][RX_FSRequest.RF_Handle-1 ];
      flags = (RX_FSRequest.RF_ReportHiddenFiles == 1) ? 0x01 : 0x00;

      count = iso11783FSReadFile( file, RX_FSRequest.RF_Count, buffer, flags );
      if (count >= 0) {
        entries = (buffer[0] | (((WORD)buffer[1]) << 8));
        // read file successfully
        //if (entries < RX_FSRequest.RF_Count) {
        //  TX_Buffer[2] = 45; // file pointer at end
        //}
        //else {
          TX_Buffer[2] = 0x0; // success
        //}

        for( i = 0; i < count; i++ ) {
          TX_Buffer[i+3] = buffer[i];
        }
      }
      else if (count == 0) {
        TX_Buffer[2] = 0x0; // success
      }
      else {
        TX_Buffer[2] = 45; // file pointer at end
      }
    }	
    else {
      TX_Buffer[2] = 0x5; // Invalid Handle
    }

	  if (count > 1)
    	fsSendResponse2( RX_FSRequest.SA, count + 3 );
	  else
    {
      TX_Buffer[3] = 0;
      TX_Buffer[4] = 0;
      fsSendResponse2(RX_FSRequest.SA, 5);
    }
  }
  else {
    RX_FSResponse.FSServerCommand = 0x22;
    RX_FSResponse.RF_TAN          = RX_FSRequest.RF_TAN;
    RX_FSResponse.RF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Write File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSWriteFile()
{
  dword file;
  long  count, writtenBytes;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    RX_FSResponse.FSServerCommand = 0x23;
    RX_FSResponse.WF_TAN          = RX_FSRequest.WF_TAN;
    RX_FSResponse.DLC             = 5;
    RX_FSResponse.DA              = RX_FSRequest.SA;
    RX_FSResponse.WF_Count        = 0;

    if ((RX_FSRequest.WF_Handle > 0) && (RX_FSRequest.WF_Handle <= kMaxHandles) && (gFileHandle[ci][RX_FSRequest.WF_Handle-1] != 0)) {
      file = gFileHandle[ci][RX_FSRequest.WF_Handle-1 ];

      // copy data
      count = _min( RX_FSRequest.WF_Count, RX_FSRequest.DLC-5 );
      for( i = 0; i < count; i++ ) {
        RX_Buffer[i] = RX_Buffer[i+5];
      }

      writtenBytes = iso11783FSWriteFile( file, count, RX_Buffer );
      RX_FSResponse.WF_Count = writtenBytes;
      if (count == writtenBytes) {
        // write file successful
        RX_FSResponse.WF_ErrorCode   = 0x0; // success

      }
      else {
        RX_FSResponse.WF_ErrorCode   = 0x2; // Invalid Access
      }
    }
    else {
      RX_FSResponse.WF_ErrorCode   = 0x5; // Invalid Handle
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.DWORD(0)        = RX_FSResponse.DWORD(4) = 0xffffffff;
    RX_FSResponse.FSServerCommand = 0x23;
    RX_FSResponse.WF_TAN          = RX_FSRequest.WF_TAN;
    RX_FSResponse.WF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.WF_Count        = 0;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Close File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSCloseFile()
{
  long  count;
  dword file;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    RX_FSResponse.FSServerCommand = 0x24;
    RX_FSResponse.CF_TAN          = RX_FSRequest.CF_TAN;
    RX_FSResponse.DLC             = 3;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    if ((RX_FSRequest.CF_Handle > 0) && (RX_FSRequest.CF_Handle <= kMaxHandles) && (gFileHandle[ci][RX_FSRequest.CF_Handle-1] != 0)) {
      file = gFileHandle[ci][RX_FSRequest.CF_Handle-1 ];

      if (iso11783FSCloseFile( file ) == 0) {
        // close file successfully
        gOpenFiles                                -= 1;
        gFileHandle[ci][RX_FSRequest.CF_Handle-1 ] = 0;
        RX_FSResponse.CF_ErrorCode                 = 0x0; // success
      }
      else {
        RX_FSResponse.CF_ErrorCode = 0x2; // Invalid Access
      }
    }
    else {
      RX_FSResponse.CF_ErrorCode   = 0x5; // Invalid Handle
    }

    fsSendResponse();
    fsUpdateEnvVars();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x24;
    RX_FSResponse.CF_TAN          = RX_FSRequest.CF_TAN;
    RX_FSResponse.CF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.DA              = RX_FSRequest.SA;
    RX_FSResponse.DLC             = 3;

    fsSendResponse();
  }
}

/*
 * File Server 'Delete File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSMoveFile()
{
  char  sourcepath[kMaxPath];
  char  destpath[kMaxPath];
  char  absSourcePath[kMaxPath];
  char  absDestPath[kMaxPath];
  dword file;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    // get file path
    for( i = 0; (i+7 < RX_FSRequest.DLC) && (i < RX_FSRequest.MF_SourcePathNameLength); i++ ) {
      sourcepath[i] = RX_FSRequest.byte(7+i);
    }
    sourcepath[i] = 0;

    for( l = 0; (l+7 < RX_FSRequest.DLC) && (l < RX_FSRequest.MF_DestinationPathNameLength); l++ ) {
      destpath[l] = RX_FSRequest.byte(7+i+l);
    }
    destpath[l] = 0;

    RX_FSResponse.FSServerCommand = 0x30;
    RX_FSResponse.MF_TAN          = RX_FSRequest.MF_TAN;
    RX_FSResponse.DLC             = 3;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    absDestPath[0] = 0;
    if (iso11783FSMakeAbsolutePath( sourcepath, gCurrentDir[ci], 1, kMaxPath, absSourcePath, gVolumes[0], gMfcCode[ci] ) == 0) {
      if (fsPathIsAccessible( absSourcePath, gMfcCode[ci] )) {
        if (RX_FSRequest.MF_DestinationPathNameLength != 0) {
          result = iso11783FSMakeAbsolutePath( destpath, gCurrentDir[ci], 0, kMaxPath, absDestPath, gVolumes[0], gMfcCode[ci] );
          if (result != 0) {
            RX_FSResponse.MF_ErrorCode = 0x2; // invalid access
          }
          else {
            result = iso11783FSMove( absSourcePath, absDestPath, RX_FSRequest.MF_MoveModeCopy + (RX_FSRequest.MF_MoveModeForce << 1) + (RX_FSRequest.MF_MoveModeRecursive << 2) );
            if (result == 0) {
              RX_FSResponse.MF_ErrorCode = 0x0; // success
            }
            else if (result == -1000) {
              RX_FSResponse.MF_ErrorCode = 0x4; // file or path unknown
            }
            else {
              RX_FSResponse.MF_ErrorCode = 0x2; // invalid access
            }
          }
        }
        else { 
          // destination path length is 0
          if (result == -1000) {
            RX_FSResponse.MF_ErrorCode = 0x4; // file or path unknown
          }
          else {
            RX_FSResponse.MF_ErrorCode = 0x2; // invalid access
          }       
        }
      }
      else {
        RX_FSResponse.MF_ErrorCode = 0x1; // access denied
      }
    }
    else {
      RX_FSResponse.MF_ErrorCode = 0x4; // file or path not found
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x30;
    RX_FSResponse.MF_TAN          = RX_FSRequest.MF_TAN;
    RX_FSResponse.MF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Get File Attributes' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSGetFileAttributes()
{
  dword info[9];
  char  pathName[kMaxPath];
  char  absPath[kMaxPath];
  long  i;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    RX_FSResponse.FSServerCommand = 0x32;
    RX_FSResponse.GA_TAN          = RX_FSRequest.GA_TAN;
    RX_FSResponse.byte(3)         = 0x0;
    RX_FSResponse.DLC             = 4;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    // get file name
    for( i = 0; (i+4 < RX_FSRequest.DLC) && (i < RX_FSRequest.GA_PathName); i++ ) {
      pathName[i] = RX_FSRequest.BYTE(i + 4 );
    }
    pathName[i] = 0;

    if (strlen(pathName) > 0) {
      result = iso11783FSMakeAbsolutePath( pathName, gCurrentDir[ci], 0, kMaxPath, absPath, gVolumes[0], gMfcCode[ci] );

      if (result == 0) {
        if (iso11783FSGetFileInfo( absPath, elCount(info), info ) >= 0) {
          // get file attribute successfully
          RX_FSResponse.byte(3)   = info[1];
          RX_FSResponse.GA_ErrorCode = 0x0; // success
        }
        else {
          RX_FSResponse.GA_ErrorCode = 0x2; // Invalid Access
        }
      }
      else if (result == -1002) {
        RX_FSResponse.OF_Handle     = 0x0;
        RX_FSResponse.CCD_ErrorCode = 0x7; // invalid destination name given
      }
      else {
        RX_FSResponse.OF_Handle    = 0x0;
        RX_FSResponse.OF_ErrorCode = 0x1; // access denied
      }
    }
    else {
      RX_FSResponse.GA_ErrorCode   = 0x5; // Invalid Handle
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x32;
    RX_FSResponse.GA_TAN          = RX_FSRequest.GA_TAN;
    RX_FSResponse.GA_ErrorCode    = 0x1; // access denied
    RX_FSResponse.byte(3)         = 0x0;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Set File Attributes' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSSetFileAttributes()
{
  dword info[9] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 };
  char  pathName[kMaxPath];
  char  absPath[kMaxPath];
  long  i;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    RX_FSResponse.DWORD(0)        = RX_FSResponse.DWORD(4) = 0xffffffff;
    RX_FSResponse.FSServerCommand = 0x33;
    RX_FSResponse.SA_TAN          = RX_FSRequest.SA_TAN;
    RX_FSResponse.DLC             = 3;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    // get file name
    for( i = 0; i < RX_FSRequest.SA_PathName; i++ ) {
      pathName[i] = RX_FSRequest.BYTE(i + 5 );
    }
    pathName[i] = 0;

    if (strlen(pathName) > 0) {
      result = iso11783FSMakeAbsolutePath( pathName, gCurrentDir[ci], 0, kMaxPath, absPath, gVolumes[0], gMfcCode[ci] );

      if (result == 0) {
        info[1] = RX_FSRequest.byte(2);
        if (iso11783FSSetFileInfo( absPath, 2, info ) == 0) {
          // set file attributes successfully
          RX_FSResponse.SA_ErrorCode = 0x0; // success
        }
        else {
          RX_FSResponse.SA_ErrorCode = 0x2; // Invalid Access
        }
      }
      else if (result == -1002) {
        RX_FSResponse.OF_Handle     = 0x0;
        RX_FSResponse.CCD_ErrorCode = 0x7; // invalid destination name given
      }
      else {
        RX_FSResponse.OF_Handle    = 0x0;
        RX_FSResponse.OF_ErrorCode = 0x1; // access denied
      }
    }
    else {
      RX_FSResponse.SA_ErrorCode   = 0x5; // Invalid Handle
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.DWORD(0)        = RX_FSResponse.DWORD(4) = 0xffffffff;
    RX_FSResponse.FSServerCommand = 0x33;
    RX_FSResponse.SA_TAN          = RX_FSRequest.SA_TAN;
    RX_FSResponse.SA_ErrorCode    = 0x1; // access denied
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * File Server 'Get Date And Time' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSGetDateAndTime()
{
  dword info[9];
  char  pathName[256];
  long  i;

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    RX_FSResponse.FSServerCommand = 0x34;
    RX_FSResponse.FDT_TAN         = RX_FSRequest.FDT_TAN;
    RX_FSResponse.word(3)         = 0x0;
    RX_FSResponse.word(5)         = 0x0;
    RX_FSResponse.DLC             = 7;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    // get file name
    for( i = 0; i < RX_FSRequest.FDT_PathName; i++ ) {
      pathName[i] = RX_FSRequest.BYTE(i + 4 );
    }
    pathName[i] = 0;

    if (strlen(pathName) > 0) {

      if (iso11783FSGetFileInfo( pathName, elCount(info), info ) >= 0) {
        // get data and time successfully
        RX_FSResponse.FDT_DateYear.phys    = info[2];
        RX_FSResponse.FDT_DateMonth.phys   = info[3];
        RX_FSResponse.FDT_DateDay.phys     = info[4];
        RX_FSResponse.FDT_TimeHour.phys    = info[5];
        RX_FSResponse.FDT_TimeMinutes.phys = info[6];
        RX_FSResponse.FDT_TimeSeconds.phys = info[7];
        RX_FSResponse.FDT_ErrorCode        = 0x0; // success
      }
      else {
        RX_FSResponse.FDT_ErrorCode = 0x2; // Invalid Access
      }
    }
    else {
      RX_FSResponse.FDT_ErrorCode   = 0x5; // Invalid Handle
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x34;
    RX_FSResponse.FDT_TAN         = RX_FSRequest.FDT_TAN;
    RX_FSResponse.FDT_ErrorCode   = 0x1; // access denied
    RX_FSResponse.word(3)         = 0x0;
    RX_FSResponse.word(5)         = 0x0;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }}

/*
 * File Server 'Get Current Directory' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSGetCurrentDirectory()
{
  DWORD info[3];

  ci = fsGetClientIndex( RX_FSRequest.SA );

  iso11783FSGetVolumeInfo( elCount(info), info );

  if (ci >= 0) {
    RX_FSResponse.FSServerCommand 		= 0x10;
    RX_FSResponse.GCD_TAN         		= RX_FSRequest.GCD_TAN;
    RX_FSResponse.GCD_ErrorCode   		= 0x0; // success
	  RX_FSResponse.GCD_TotalSpace	  	= info[1];
	  RX_FSResponse.GCD_FreeSpace		  	= info[2];
	  RX_FSResponse.GCD_PathName        = strlen( gCurrentDir[ci] );
    RX_FSResponse.DLC             		= 13 + strlen( gCurrentDir[ci] );
    RX_FSResponse.DA              		= RX_FSRequest.SA;

    l = strlen( gCurrentDir[ci] );
    for( i = 0; i < l; i++ ) {
      RX_FSResponse.byte(13+i) = gCurrentDir[ci][i];
    }

    fsSendResponse();
    
  }
  else {
    RX_FSResponse.FSServerCommand 		= 0x10;
    RX_FSResponse.GCD_TAN         		= RX_FSRequest.GCD_TAN;
    RX_FSResponse.GCD_ErrorCode  			= 0x1; // access denied
	  RX_FSResponse.GCD_TotalSpace	  	= info[1];
	  RX_FSResponse.GCD_FreeSpace		  	= info[2];
	  RX_FSResponse.GCD_PathName        = 0;
    RX_FSResponse.DLC             		= 13;
    RX_FSResponse.DA              		= RX_FSRequest.SA;

    fsSendResponse();
  }

}

/*
 * File Server 'Change Current Directory' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSChangeCurrentDirectory()
{
  int  result;
  char path[kMaxPath];
  char absPath[kMaxPath];

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    for( i = 0; ((i+4) < RX_FSRequest.DLC) && (i < RX_FSRequest.CCD_PathName); i++ ) {
      path[i] = RX_FSRequest.byte(4+i);
    }
    path[i] = 0;

    RX_FSResponse.FSServerCommand = 0x11;
    RX_FSResponse.GCD_TAN         = RX_FSRequest.CCD_TAN;
    RX_FSResponse.DLC             = 3;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    result = iso11783FSMakeAbsolutePath( path, gCurrentDir[ci], 1, kMaxPath, absPath, gVolumes[0], gMfcCode[ci] );
    if (result == 0) {
      if (fsPathIsAccessible( absPath, gMfcCode[ci] )) {
        // set new absolute path of current directory
        strncpy( gCurrentDir[ci], absPath, kMaxPath );

        RX_FSResponse.CCD_ErrorCode = 0x0; // success
      }
      else {
        RX_FSResponse.CCD_ErrorCode = 0x1; // access denied
      }
    }
    else if (result == -1002) {
      RX_FSResponse.CCD_ErrorCode = 0x7; // invalid destination name given
    }
    else {
      RX_FSResponse.CCD_ErrorCode = 0x4; // file or path not found
    }

    fsSendResponse();
    fsUpdateEnvVars();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x11;
    RX_FSResponse.CCD_TAN         = RX_FSRequest.CCD_TAN;
    RX_FSResponse.CCD_ErrorCode   = 0x1; // access denied
    RX_FSResponse.DLC             = 3;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * Sends a File Server Response. The data is taken from
 * the global RX_FSResponse.
 */
void fsSendResponse()
{
  for( i = 0; i < RX_FSResponse.DLC; i++ ) {
    TX_Buffer[i] = RX_FSResponse.BYTE(i);
  }

  // fill up with 0xFF, if necessary
  if (RX_FSResponse.DLC < 8) {
    RX_FSResponse.DLC = 8;
    for( ; i < RX_FSResponse.DLC; i++ ) {
      TX_Buffer[i] = 0xff;
    }
  }
 
  Iso11783TxReqPG( gECUHandle, RX_FSResponse.pgn, RX_FSResponse.DA, 7, RX_FSResponse.DLC, TX_Buffer ); 
}

/*
 * Find the index of a client by address
 *
 * Return  - Index of the client of -1
 * address - Address of the client
 */
int fsGetClientIndex( BYTE address )
{
  if (address >= kNullAddr) {
    return -1;
  }

  for( i = 0; i < kMaxClient; i++ ) {
    if (gClientAddress[i] == address) {
      // client found
      return i;
    }
  }

  // no client with this address available
  return -1;
}

/*
 * Observe the status timeout of the clients in this timer.
 */
on timer FSTimeoutTimer
{
  dword t;

  if (gECUState == kOperational) {
    t = timeNow() ; 

    for( i = 0; i < kMaxClient; i++ ) {
      if (gClientAddress[i] != kNullAddr) {
        if (gMaintainTime[i] + 600000 < t) { // 6 sec.
          // missing status message of the client
          // close open files and release client
          for( k = 0; k < kMaxHandles; k++ ) {
            if (gFileHandle[i][k] > 0) {
              // file is open close it
              iso11783FSCloseFile( gFileHandle[i][k] );
              gFileHandle[i][k] = 0;
              gOpenFiles       -= 1;
            }
          }

          writeDbgLevel( kDbgWarning, "<%s> Missing status of Client #%d", gECULabel, gClientAddress[i] );

          gMaintainTime[i]  = 0;
          gClientAddress[i] = kNullAddr;
          gMfcCode[i]       = 0;
          strncpy( gCurrentDir[i], "\\", kMaxPath );
          
          fsUpdateEnvVars();
        }
      }
    }


    setTimer( FSTimeoutTimer, 1000 );
  }
}

on stopMeasurement
{
  EcuShutDown();
}

/*
 * Update the environment variables for the panel
 */
void fsUpdateEnvVars()
{
  int count;

  // set info for client #1
  if (gClientAddress[0] != kNullAddr) {
    putValue( EvFS_Client1Address, gClientAddress[0] );
    putValue( EvFS_Client1CurrentDirectory, gCurrentDir[0] );
    count = 0;
    for( i = 0; i < kMaxHandles; i++ ) {
      if (gFileHandle[0][i] > 0) {
        count++;
      }
    }
    putValue( EvFS_Client1OpenFileCount, count);
  }
  else {
    putValue( EvFS_Client1Address, kNullAddr );
    putValue( EvFS_Client1CurrentDirectory, "" );
    putValue( EvFS_Client1OpenFileCount, 0);
  }

  // set info for client #2
  if (gClientAddress[1] != kNullAddr) {
    putValue( EvFS_Client2Address, gClientAddress[1] );
    putValue( EvFS_Client2CurrentDirectory, gCurrentDir[1] );
    count = 0;
    for( i = 0; i < kMaxHandles; i++ ) {
      if (gFileHandle[1][i] > 0) {
        count++;
      }
    }
    putValue( EvFS_Client2OpenFileCount, count);
  }
  else {
    putValue( EvFS_Client2Address, kNullAddr );
    putValue( EvFS_Client2CurrentDirectory, "" );
    putValue( EvFS_Client2OpenFileCount, 0);
  }

  // set info for client #3
  if (gClientAddress[2] != kNullAddr) {
    putValue( EvFS_Client3Address, gClientAddress[2] );
    putValue( EvFS_Client3CurrentDirectory, gCurrentDir[2] );
    count = 0;
    for( i = 0; i < kMaxHandles; i++ ) {
      if (gFileHandle[2][i] > 0) {
        count++;
      }
    }
    putValue( EvFS_Client3OpenFileCount, count);
  }
  else {
    putValue( EvFS_Client3Address, kNullAddr );
    putValue( EvFS_Client3CurrentDirectory, "" );
    putValue( EvFS_Client3OpenFileCount, 0);
  }

  // set info for client #4
  if (gClientAddress[3] != kNullAddr) {
    putValue( EvFS_Client4Address, gClientAddress[3] );
    putValue( EvFS_Client4CurrentDirectory, gCurrentDir[3] );
    count = 0;
    for( i = 0; i < kMaxHandles; i++ ) {
      if (gFileHandle[3][i] > 0) {
        count++;
      }
    }
    putValue( EvFS_Client4OpenFileCount, count);
  }
  else {
    putValue( EvFS_Client4Address, kNullAddr );
    putValue( EvFS_Client4CurrentDirectory, "" );
    putValue( EvFS_Client4OpenFileCount, 0);
  }

  // set info for client #5
  if (gClientAddress[4] != kNullAddr) {
    putValue( EvFS_Client5Address, gClientAddress[4] );
    putValue( EvFS_Client5CurrentDirectory, gCurrentDir[4] );
    count = 0;
    for( i = 0; i < kMaxHandles; i++ ) {
      if (gFileHandle[4][i] > 0) {
        count++;
      }
    }
    putValue( EvFS_Client5OpenFileCount, count);
  }
  else {
    putValue( EvFS_Client5Address, kNullAddr );
    putValue( EvFS_Client5CurrentDirectory, "" );
    putValue( EvFS_Client5OpenFileCount, 0);
  }
}

/*
 * This function is called from the Nodelayer, 
 * if the address is successfully claimed.
 */
DWORD Iso11783AppAddrClaimed ( long ecuHandle )
{
  char rqPGN[3];
  rqPGN[1] = 0xee;

  if (ecuHandle == gECUHandle) {
    writeDbgLevel( kDbgInfo, "<%s> online with address %d", gECULabel, Iso11783GetNodeAddr(gECUHandle) );
  }

  gECUState = kOperational;

  putValue( EvFS_EcuAddress, Iso11783GetNodeAddr(gECUHandle) );

  setTimer( FSStatusTimer, 0 );
  setTimer( FSTimeoutTimer, 1000 );

  // request ACL
  Iso11783TxReqPG( gECUHandle, 0xEA00, kGlobalAddr, 7, 3, rqPGN ); 

  return 0;
}

/*
 * A commanded address PG was received.
 */
DWORD Iso11783AppCmdAddrIndication(long ecuHdl, long length)
{
  // copy received data to buffer
  length = Iso11783GetRxData( elCount(RX_Buffer), RX_Buffer );

  Iso11783ECUGoOnline( gECUHandle, RX_Buffer[8] );
  
  return 0;
}

/*
 * If an error occured, this function is called from the Nodelayer DLL.
 */
DWORD Iso11783AppErrorIndication (long ecuhdl, long errorClass, long errorCode, long addCode )
{
  char strErrInfo[64];

  switch( errorCode ) {
    case 0x1: snprintf( strErrInfo, elCount(strErrInfo), "cts error" ); break;
    case 0x2: snprintf( strErrInfo, elCount(strErrInfo), "peer to peer data timeout" ); break;
    case 0x3: snprintf( strErrInfo, elCount(strErrInfo), "BAM data timeout" ); break;
    case 0x4: snprintf( strErrInfo, elCount(strErrInfo), "acknowledge timeout" ); break;
    case 0x10: //snprintf( strErrInfo, elCount(strErrInfo), "lost address claiming or received address claim with higher priority" ); break;
      EcuLostAddress();
      return 0;
    case 0x15: snprintf( strErrInfo, elCount(strErrInfo), "init error: node name detected two times" ); break;
    case 0x17: snprintf( strErrInfo, elCount(strErrInfo), "init error: node address detected two times" ); break;
    case 0x30: snprintf( strErrInfo, elCount(strErrInfo), "exeption while choosing protokoll BAM or CMDT" ); break;
    case 0x40: snprintf( strErrInfo, elCount(strErrInfo), "internal data buffer full" ); break;
    case 0x60: snprintf( strErrInfo, elCount(strErrInfo), "user sends system pg " ); break;
    case 0x71: snprintf( strErrInfo, elCount(strErrInfo), "transmission abort by receiver" ); break;
    case 0x72: snprintf( strErrInfo, elCount(strErrInfo), "transmission abort by sender" ); break;
  }

  writeDbgLevel( kDbgError, "<%s> ERROR(0x%x) %s", gECULabel, errorCode, strErrInfo );
  
  return 0;
}

/*
 * A node has changed its address or claimed an address successfully
 */
DWORD Iso11783AppNmtIndication( LONG busHandle, LONG address, LONG state )
{
  return 0;
}

/*
 * A request PGN was received
 */
DWORD Iso11783AppRequestIndication(long ecuHdl, long source, long dest, long page, long pgNum)
{
  return 0;
}

/*
 * This function is called from the Nodelayer DLL, if a PG is received.
 */
dword Iso11783AppRxIndication( long ecuHandle, long src, long dst, long length, long _pgn )
{
  // dispatch Parameter Group
  switch(_pgn) {
    //
    // File Server Command from a client
    //
    case 0xAA00:
      // copy received data to buffer
      length = Iso11783GetRxData( elCount(RX_Buffer), RX_Buffer );
      // copy data to RX parameter group
      for( i = 0; i < length; i++ ) {
        RX_FSRequest.BYTE(i) = RX_Buffer[i];
      }
      RX_FSRequest.SA  = src;
      RX_FSRequest.DA  = dst;
      RX_FSRequest.DLC = length;

      // dispatch command
      switch(RX_FSRequest.FSClientCommand) {
        case 0x00: // Maintenance
          onFSMaintenance();
          break;
        case 0x01: // Get File Server Properties
          onFSGetServerProperties( src );
          break;
        case 0x20: // Open File
          onFSOpenFile();
          break;
        case 0x21: // Seek File
          onFSSeekFile();
          break;
        case 0x22: // Read File
          onFSReadFile();
          break;
        case 0x23: // Write File
          onFSWriteFile();
          break;
        case 0x24: // Close File
          onFSCloseFile();
          break;
        case 0x30: // Move File
          onFSMoveFile();
          break;
        case 0x31: // Delete File
          onFSDeleteFile();
          break;
        case 0x32: // Get File Attributes
          onFSGetFileAttributes();
          break;
        case 0x33: // Set File Attributes
          onFSSetFileAttributes();
          break;
        case 0x34: // Get Date and Time
          onFSGetDateAndTime();
          break;
        case 0x10: // Get Current Directory
          onFSGetCurrentDirectory();
          break;
        case 0x11: // Change Current Directory
          onFSChangeCurrentDirectory();
          break;
        default:   // invalid command
          break;
      }

      break;
  }
   
  return 1;
}

/*
 * This function is called from the Nodelayer DLL, if a PG is successfully sent.
 */
DWORD Iso11783AppTxIndication(long ecuhdl, long TxPgn, long source, long dest) 
{
  return 0;
}

void onFSGetServerProperties( BYTE requestSA )
{
  RX_FSResponse.FSServerCommand                 = 0x01; // Get File Server Properties
  RX_FSResponse.GSP_VersionNumber               = 3;    // First published edition and Amd1 of the International Standard 
  RX_FSResponse.GSP_MaxNrSimultaneousFilesOpen  = kMaxHandles;
  RX_FSResponse.GSP_SupportMultipleVolumes      = 0;
  RX_FSResponse.DA                              = requestSA;
  RX_FSResponse.DLC                             = 4;

  fsSendResponse();
}

/*
 * File Server 'Delete File' command received (variable RX_FSCommand
 * contains the data).
 */
void onFSDeleteFile()
{
  char  path[kMaxPath];
  char  absPath[kMaxPath];

  ci = fsGetClientIndex( RX_FSRequest.SA );

  if (ci >= 0) {
    // get file path
    for( i = 0; (i+5 < RX_FSRequest.DLC) && (i < RX_FSRequest.DF_PathName); i++ ) {
      path[i] = RX_FSRequest.byte(5+i);
    }
    path[i] = 0;

    RX_FSResponse.FSServerCommand = 0x31;
    RX_FSResponse.DF_TAN          = RX_FSRequest.DF_TAN;
    RX_FSResponse.DLC             = 3;
    RX_FSResponse.DA              = RX_FSRequest.SA;

    if (iso11783FSMakeAbsolutePath( path, gCurrentDir[ci], 1, kMaxPath, absPath, gVolumes[0], gMfcCode[ci] ) == 0) {
      if (fsPathIsAccessible( absPath, gMfcCode[ci] )) {
        result = iso11783FSMove( absPath, "\0", RX_FSRequest.DF_DeleteMode + (RX_FSRequest.DF_DeleteModeForce << 1) + (RX_FSRequest.DF_DeleteModeRecursive << 2) );
        if (result == 0) {
          RX_FSResponse.DF_ErrorCode = 0x0; // success
        }
        else if (result == -1000) {
          RX_FSResponse.DF_ErrorCode = 0x4; // file or path unknown
        }
        else {
          RX_FSResponse.DF_ErrorCode = 0x1; // access denied
        }
      }
      else {
        RX_FSResponse.DF_ErrorCode = 0x1; // access denied
      }
    }
    else {
      RX_FSResponse.DF_ErrorCode = 0x4; // file or path not found
    }

    fsSendResponse();
  }
  else {
    RX_FSResponse.FSServerCommand = 0x31;
    RX_FSResponse.DF_TAN          = RX_FSRequest.MF_TAN;
    RX_FSResponse.DF_ErrorCode    = 0x1; // access denied
    RX_FSResponse.DA              = RX_FSRequest.SA;

    fsSendResponse();
  }
}

/*
 * Create a manufacturer directory (MCMC0000)
 */
int fsCreateManufacturerDirectory( char path[] )
{
  long file;
  int  result;
  
  result = 0;
  file   = iso11783FSOpenFile( path, 0x3 );
  if (file != 0) {
    if (Iso11783FSCloseFile( file ) == 0) {
      result = 1;
    }
  }

  return result;
}

/*
 * Check, if directory is accessible by client
 */
int fsPathIsAccessible( char path[], WORD mfcCode )
{
  char folder[kMaxPath];
  int  result;

  result = 0;

  if (mfcCode > 0) {
    if (utilFindStr( path, "\\MCMC" ) >= 0) {
      snprintf( folder, kMaxPath, "\\MCMC%.4d", mfcCode );

      if (utilFindStr( path, folder ) >= 0) {
        result = 1;
      }
    }
    else {
      result = 1;
    }
  }
  else {
    result = 1;
  }

  return result;
}

int utilFindStr( char text[], char find[] )
{
  int findLength;
  int textLength;

  findLength = strlen(find);
  textLength = strlen(text);

  if (textLength >= findLength) {
    for( i = 0; i <= textLength-findLength; i++ ) {
      if (strncmp( find, text, i, findLength ) == 0) {
        return i;
      }
    }
  }

  return -1;
}

