//==================================================================================================
// S t a n d a r d                                                                        Interface
// E n c r y p t
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library 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. See
// the GNU Library General Public License for more details (http://www.gnu.org).

/*DESCRIPTION*/
/* This module provides functions to encode or decode a folder using a given key. */

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "standard/encrypt.hpp"

// Guardian //--------------------------------------------------------------------------------------
#ifndef guStandardEncrypt
#define guStandardEncrypt

// Headers //---------------------------------------------------------------------------------------
#include <bpp/standard/maths.hpp> /*INCLUDE*/

namespace bpp {

// Importation/Exportation //-----------------------------------------------------------------------
#ifdef STANDARD_DLL
 #define dll_export DLL_EXPORT
#else
 #define dll_export DLL_IMPORT
#endif

// Namespaces //------------------------------------------------------------------------------------
#define public_area  standardEncrypt
#define private_area standardEncrypt_private

namespace public_area  { /*NAMESPACE*/ using namespace standardMaths; }
namespace private_area { using namespace public_area; }

extern_module_name;

// Initialization //--------------------------------------------------------------------------------
#define iniStandardEncrypt
has_initializer;

// Macrocommands //---------------------------------------------------------------------------------

// Types & Classes //-------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area { class clFolderEncoding; }

// Functions Interface //---------------------------------------------------------------------------
namespace public_area {
 function tyCardinal buildEncodingTables(tyByte *,tyByte *,tyByte *,ctString &);
 function void       decodeFolder(ctString &,ctString &,ctString &,tyBoolean=false);
 function void       encodeFolder(ctString &,ctString &,ctString &,tyBoolean=false);
}

namespace private_area {
 function void       decodeBlock(tyByte *,tyByte *,tyByte *,tyCardinal);
 function tyCardinal decodeCardinal(clInStream &);
 function void       decodeFile(clInStream &,ctString &,tyByte *,tyByte *,tyBoolean);
 function void       decodeFolder(clInStream &,ctString &,tyByte *,tyByte *);
 function void       decodeString(clInStream &,clString &,tyByte *,tyByte *);
 function void       encodeBlock(tyByte *,tyByte *,tyByte *,tyCardinal);
 function void       encodeCardinal(clOutStream &,tyCardinal);
 function void       encodeFile(clOutStream &,ctString &,ctString &,tyByte *,tyByte *,tyBoolean);
 function void       encodeString(clOutStream &,ctString &,tyByte *,tyByte *);
}

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 /*ERROR*/ extern_error erKeyTooSmall; /* The encoding key is too small. */
}

// Constants & Variables //-------------------------------------------------------------------------
extern_static_constant(private,tyByte,goFileFlag,?);
extern_static_constant(private,tyByte,goFolderFlag,?);

// F o l d e r E n c o d i n g  Interface //--------------------------------------------------------
namespace private_area {
 class clFolderEncoding : public clFolderProcessing {
  //-----------------------------------------------------------------------------------------Private
  private_property clString      atFolder;
  private_property clOutStream & atStream;
  private_property tyByte *      atMask;
  private_property tyByte *      atTable;
  private_property tyBoolean     atDisplayed;

  private_property constructor clFolderEncoding(const clFolderEncoding &);
  private_property clFolderEncoding & operator = (const clFolderEncoding &);
  //------------------------------------------------------------------------------------------Public
  public_property constructor clFolderEncoding(clOutStream &,tyByte *,tyByte *,tyBoolean=true);
  public_property virtual destructor clFolderEncoding(void);

  public_property void startFolder(ctString &);
  public_property void endFolder(ctString &);
  public_property void file(ctString &);
  public_property void unknown(ctString &);
 };
}

// Functions Inline //------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// X X X  Inline //---------------------------------------------------------------------------------
namespace {}

// End //-------------------------------------------------------------------------------------------
}
#undef dll_export
#undef public_area
#undef private_area
#endif
 
//==================================================================================================
// S t a n d a r d                                                                   Implementation
// E n c r y p t
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library 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. See
// the GNU Library General Public License for more details (http://www.gnu.org).

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "standard/encrypt.cpp"

// DLL Belonging //---------------------------------------------------------------------------------
#define STANDARD_DLL

// Headers //---------------------------------------------------------------------------------------
#include <bpp/standard/encrypt.hpp> /*INTERFACE*/

namespace bpp {

// Namespaces //------------------------------------------------------------------------------------
#define public_area  standardEncrypt
#define private_area standardEncrypt_private
#define dll_export   DLL_EXPORT

namespace public_area  {}
namespace private_area {}

static_module_name("Standard/Encrypt");

// Initialization //--------------------------------------------------------------------------------
#undef iniStandardEncrypt
static_constant(private_area::clInitializer,goInitializer);

// Errors //----------------------------------------------------------------------------------------
namespace public_area { static_error erKeyTooSmall; }

// Constants & Variables //-------------------------------------------------------------------------
static_constant(tyByte,goFileFlag);
static_constant(tyByte,goFolderFlag);

// Static Members //--------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// Functions Implementation //----------------------------------------------------------------------
namespace public_area {
 //------------------------------------------------------------------------------BuildEncodingTables
 /*FUNCTION*/
 /* Builds the mask, the encoding and the decoding tables from a given key. These tables are used
    to encode or decode a file. */
 function tyCardinal buildEncodingTables(tyByte * agMask,tyByte * agEncodingTable,
                                         tyByte * agDecodingTable,ctString & agKey) {
  method_name("buildEncodingTables");

  tyCardinal lcCounter     = 0;
  tyCardinal lcLength      = agKey.size();
  tyByte *   lcLine        = new_array(tyByte,lcLength);
  tyBoolean  lcMarkS[256]  = { false };
  tyCardinal lcNbIteration = 0;
  tyCardinal lcNbMark      = 0;
  tyCardinal lcNbMask      = 0;

  while (lcCounter<lcLength) {
   lcLine[lcCounter]=byte(agKey[lcCounter]);
   ++lcCounter;
  }

  while (lcNbMark<256 and lcNbIteration<500) {
   ++lcNbIteration;
   lcCounter=lcLength;

   while (lcCounter>1) {
    --lcCounter;
    lcLine[lcCounter]=byte((tyInteger(256)+lcLine[lcCounter]-lcLine[lcCounter-1])%256);
   }

   lcCounter=1;

   while (lcCounter<lcLength) {
    if (lcNbMask<256) {
     if (lcCounter>0) {
      agMask[lcNbMask]=lcLine[lcCounter];
      ++lcNbMask;
     }
    }
    else if (not lcMarkS[lcLine[lcCounter]] and lcCounter>0) {
     lcMarkS[lcLine[lcCounter]]=true;
     agEncodingTable[lcLine[lcCounter]]=byte(lcNbMark);
     agDecodingTable[lcNbMark]=lcLine[lcCounter];
     ++lcNbMark;
    }

    lcCounter++;
   }
  }

  delete_array(lcLine);
  if (lcNbMark<256) send_error(erKeyTooSmall);
  return (lcNbIteration);
 }
 //-------------------------------------------------------------------------------------DecodeFolder
 /*FUNCTION*/
 /* Decodes a folder from an archive using a given key. Information about the decoding can be
    displayed (default is not). */
 function void decodeFolder(ctString & agPath,ctString & agArchive,ctString & agKey,
                            tyBoolean agDisplayed) {
  method_name("decodeFolder");

  tyByte   lcDecodingTable[256];
  tyByte   lcEncodingTable[256];
  clInFile lcFin;
  tyByte   lcFlag;
  tyByte   lcMask[256];

  if (agDisplayed) environment::inform("Decoding Preparation...");
  private_area::buildEncodingTables(lcMask,lcEncodingTable,lcDecodingTable,agKey);

  if (agDisplayed) {
   environment::inform("Files Decoding...");
   environment::nextLine();
  }

  if (not folderExists(agPath.data())) createFolder(agPath.data());
  open(lcFin,agArchive.data(),ios::in|ios::binary);

  while (lcFin.peek()!=-1) {
   lcFin.read(tyString(&lcFlag),1);

   if (lcFlag==private_area::goFileFlag)
    private_area::decodeFile(lcFin,agPath,lcMask,lcDecodingTable,agDisplayed);
   else private_area::decodeFolder(lcFin,agPath,lcMask,lcDecodingTable);
  }

  close(lcFin);
  if (lcFin.fail()) send_error(erFileReading);
 }
 //-------------------------------------------------------------------------------------EncodeFolder
 /*FUNCTION*/
 /* Encodes a folder into an archive using a given key. Information about the encoding can be
    displayed (default is not). */
 function void encodeFolder(ctString & agArchive,ctString & agFolder,ctString & agKey,
                            tyBoolean agDisplayed) {
  method_name("encodeFolder");

  tyByte    lcDecodingTable[256];
  tyByte    lcEncodingTable[256];
  clOutFile lcFout;
  tyByte    lcMask[256];

  private_area::clFolderEncoding lcProcessing(lcFout,lcMask,lcEncodingTable,agDisplayed);

  if (agDisplayed) environment::inform("Encoding Preparation...");
  private_area::buildEncodingTables(lcMask,lcEncodingTable,lcDecodingTable,agKey);

  if (agDisplayed) {
   environment::inform("Files Encoding...");
   environment::nextLine();
  }

  open(lcFout,agArchive.data(),ios::out|ios::trunc|ios::binary);
  scanFolder(agFolder.data(),lcProcessing,true);
  close(lcFout);
  if (lcFout.fail()) send_error(erFileWriting);
 }
}

namespace private_area {
 //--------------------------------------------------------------------------------------DecodeBlock
 function void decodeBlock(tyByte * agBlock,tyByte * agMask,tyByte * agDecodingTable,
                           tyCardinal agLength) {
  tyCardinal lcCounter;
  tyCardinal lcLength;

  tyCardinal lcOffset = 0;

  while (lcOffset<agLength) {
   lcLength=mini(256u,agLength-lcOffset);
   agBlock[lcOffset]=agDecodingTable[agBlock[lcOffset]];
   lcCounter=1;

   while (lcCounter<lcLength) {
    agBlock[lcOffset+lcCounter]=byte((tyInteger(256)+agDecodingTable[agBlock[lcOffset+lcCounter]]
                                      +agBlock[lcOffset+lcCounter-1])%256);

    ++lcCounter;
   }

   lcCounter=0;

   while (lcCounter<lcLength) {
    agBlock[lcOffset+lcCounter]=byte((tyInteger(256)+agBlock[lcOffset+lcCounter]
                                      +agMask[lcCounter])%256);

    ++lcCounter;
   }

   lcOffset+=lcLength;
  }
 }
 //-----------------------------------------------------------------------------------DecodeCardinal
 function tyCardinal decodeCardinal(clInStream & agStream) {
  tyByte lcBlock[4];

  tyCardinal lcCounter = 0;
  tyCardinal lcNumber  = 0;

  agStream.read(tyString(lcBlock),4);

  while (lcCounter<4) {
   lcNumber=256*lcNumber+cardinal(lcBlock[lcCounter]);
   ++lcCounter;
  }

  return (lcNumber);
 }
 //---------------------------------------------------------------------------------------DecodeFile
 function void decodeFile(clInStream & agStream,ctString & agPath,tyByte * agMask,
                          tyByte * agDecodingTable,tyBoolean agDisplayed) {
  tyByte     lcBlock[65535];
  clOutFile  lcFout;
  clString   lcFullName;
  tyCardinal lcLength;
  clString   lcShortName;
  tyCardinal lcSize;

  tyCardinal lcOffset = 0;

  decodeString(agStream,lcShortName,agMask,agDecodingTable);
  if (agDisplayed) environment::out(lcShortName.data(),false,true);
  buildFullName(lcFullName,agPath,lcShortName);
  open(lcFout,lcFullName.data(),ios::out|ios::trunc|ios::binary);
  lcLength=decodeCardinal(agStream);

  if (agDisplayed) {
   environment::out(" (");
   environment::out(lcLength);
   environment::out(" bytes)",true);
  }

  while (lcOffset<lcLength) {
   lcSize=mini(65535u,lcLength-lcOffset);
   agStream.read(tyString(lcBlock),lcSize);
   decodeBlock(lcBlock,agMask,agDecodingTable,lcSize);
   lcFout.write(tyString(lcBlock),lcSize);
   lcOffset+=lcSize;
  }

  close(lcFout);
 }
 //-------------------------------------------------------------------------------------DecodeFolder
 function void decodeFolder(clInStream & agStream,ctString & agPath,tyByte * agMask,
                            tyByte * agDecodingTable) {
  clString lcFullName;
  clString lcShortName;

  decodeString(agStream,lcShortName,agMask,agDecodingTable);
  buildFullName(lcFullName,agPath,lcShortName);
  if (not folderExists(lcFullName.data())) createFolder(lcFullName.data());
 }
 //-------------------------------------------------------------------------------------DecodeString
 function void decodeString(clInStream & agStream,clString & agString,tyByte * agMask,
                            tyByte * agDecodingTable) {
  tyByte      lcBlock[65535];
  tyCardinal  lcCounter;
  tyCardinal  lcLength;
  tyCharacter lcString[65535];

  lcLength=decodeCardinal(agStream);
  agStream.read(tyString(lcBlock),lcLength);
  decodeBlock(lcBlock,agMask,agDecodingTable,lcLength);
  lcCounter=0;

  while (lcCounter<lcLength) {
   lcString[lcCounter]=character(lcBlock[lcCounter]);
   ++lcCounter;
  }

  lcString[lcLength]=end_string;
  agString=lcString;
 }
 //--------------------------------------------------------------------------------------EncodeBlock
 function void encodeBlock(tyByte * agBlock,tyByte * agMask,tyByte * agEncodingTable,
                           tyCardinal agLength) {
  tyCardinal lcCounter;
  tyCardinal lcLength;

  tyCardinal lcOffset = 0;

  while (lcOffset<agLength) {
   lcLength=mini(256u,agLength-lcOffset);
   lcCounter=0;

   while (lcCounter<lcLength) {
    agBlock[lcOffset+lcCounter]=byte((tyInteger(256)+agBlock[lcOffset+lcCounter]
                                      -agMask[lcCounter])%256);

    ++lcCounter;
   }

   lcCounter=lcLength;

   while (lcCounter>1) {
    --lcCounter;
    agBlock[lcOffset+lcCounter]=agEncodingTable[(tyInteger(256)+agBlock[lcOffset+lcCounter]
                                                 -agBlock[lcOffset+lcCounter-1])%256];
   }

   agBlock[lcOffset]=agEncodingTable[agBlock[lcOffset]];
   lcOffset+=lcLength;
  }
 }
 //-----------------------------------------------------------------------------------EncodeCardinal
 function void encodeCardinal(clOutStream & agStream,tyCardinal agNumber) {
  tyByte     lcBlock[4];
  tyCardinal lcCounter = 4;

  while (lcCounter>0) {
   --lcCounter;
   lcBlock[lcCounter]=byte(agNumber%256);
   agNumber/=256;
  }

  agStream.write(tyString(lcBlock),4);
 }
 //---------------------------------------------------------------------------------------EncodeFile
 function void encodeFile(clOutStream & agStream,ctString & agFullName,ctString & agShortName,
                          tyByte * agMask,tyByte * agEncodingTable,tyBoolean agDisplayed) {
  tyByte     lcBlock[65535];
  clInFile   lcFin;
  tyCardinal lcLength;
  tyCardinal lcSize;

  tyCardinal lcOffset = 0;

  encodeString(agStream,agShortName,agMask,agEncodingTable);
  open(lcFin,agFullName.data(),ios::in|ios::binary);
  lcLength=size(lcFin);

  if (agDisplayed) {
   environment::out(agShortName.data(),false,true);
   environment::out(" (");
   environment::out(lcLength);
   environment::out(" bytes)",true);
  }

  encodeCardinal(agStream,lcLength);

  while (lcOffset<lcLength) {
   lcSize=mini(65535u,lcLength-lcOffset);
   lcFin.read(tyString(lcBlock),lcSize);
   encodeBlock(lcBlock,agMask,agEncodingTable,lcSize);
   agStream.write(tyString(lcBlock),lcSize);
   lcOffset+=lcSize;
  }

  close(lcFin);
 }
 //-------------------------------------------------------------------------------------EncodeString
 function void encodeString(clOutStream & agStream,ctString & agString,tyByte * agMask,
                            tyByte * agEncodingTable) {
  tyCardinal lcCounter = 0;
  tyCardinal lcLength  = agString.size();
  tyByte *   lcBlock   = new_array(tyByte,lcLength+1);

  encodeCardinal(agStream,lcLength);

  while (lcCounter<lcLength) {
   lcBlock[lcCounter]=byte(agString[lcCounter]);
   ++lcCounter;
  }

  encodeBlock(lcBlock,agMask,agEncodingTable,lcLength);
  agStream.write(tyString(lcBlock),lcLength);
  delete_array(lcBlock);
 }
}

// F o l d e r E n c o d i n g  Implementation //---------------------------------------------------
namespace private_area {
 //--------------------------------------------------------------------------------------Constructor
 property clFolderEncoding::clFolderEncoding(clOutStream & agStream,tyByte * agMask,
                                             tyByte * agTable,tyBoolean agDisplayed)
 : atFolder(),atStream(agStream),atMask(agMask),atTable(agTable),atDisplayed(agDisplayed) {}
 //---------------------------------------------------------------------------------------Destructor
 property clFolderEncoding::~clFolderEncoding(void) {}
 //--------------------------------------------------------------------------------------StartFolder
 property void clFolderEncoding::startFolder(ctString & agFullName) {
  clString lcPath;
  clString lcShortName;

  extractShortName(lcPath,lcShortName,agFullName);
  lcPath=atFolder;
  buildFullName(atFolder,lcPath,lcShortName);
  atStream.write(tyString(&private_area::goFolderFlag),1);
  encodeString(atStream,atFolder,atMask,atTable);
 }
 //----------------------------------------------------------------------------------------EndFolder
 property void clFolderEncoding::endFolder(ctString &) {
  clString lcPath;
  clString lcShortName;

  extractShortName(lcPath,lcShortName,atFolder);
  atFolder=lcPath;
  buildFileName(atFolder);
 }
 //---------------------------------------------------------------------------------------------File
 property void clFolderEncoding::file(ctString & agFullName) {
  clString lcFullName;
  clString lcPath;
  clString lcShortName;

  extractShortName(lcPath,lcShortName,agFullName);
  buildFullName(lcFullName,atFolder,lcShortName);
  atStream.write(tyString(&private_area::goFileFlag),1);
  encodeFile(atStream,agFullName,lcFullName,atMask,atTable,atDisplayed);
 }
 //------------------------------------------------------------------------------------------Unknown
 property void clFolderEncoding::unknown(ctString &) {}
}

// I n i t i a l i z e r  Implementation //---------------------------------------------------------
namespace private_area {
 //--------------------------------------------------------------------------------------------Start
 property void clInitializer::start(void) {
  if (atCounter++ == 0) {
   try {
    #include <bpp/modules.hpp> /*NEED*/
    registerStop(this);
    environment::informInitialization(goModuleName);

    erKeyTooSmall.create("Encrypt - The key is too small for encoding.");

    goFileFlag   = 0;
    goFolderFlag = 1;
   }

   initializer_catch;
  }
 }
 //---------------------------------------------------------------------------------------------Stop
 property void clInitializer::stop(void) { environment::informTermination(goModuleName); }
}

// End //-------------------------------------------------------------------------------------------
}