/*  dvdisaster: Additional error correction for optical media.
 *  Copyright (C) 2004-2017 Carsten Gnoerlich.
 *
 *  Email: carsten@dvdisaster.org  -or-  cgnoerlich@fsfe.org
 *  Project homepage: http://www.dvdisaster.org
 *
 *  This file is part of dvdisaster.
 *
 *  dvdisaster is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  dvdisaster 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with dvdisaster. If not, see <http://www.gnu.org/licenses/>.
 */

#include "dvdisaster.h"

#include "scsi-layer.h"
#include "rs02-includes.h"

/***
 *** Create a CRC buffer ready for accumulating CRC and MD5 sums
 ***/

CrcBuf *CreateCrcBuf(Image *image)
{  CrcBuf *cb = g_malloc0(sizeof(CrcBuf));

  /* MD5 sum related data */
  
   cb->md5Ctxt = g_malloc(sizeof(struct MD5Context));
   MD5Init(cb->md5Ctxt);
   cb->md5State = MD5_BUILDING;
   cb->lastSector = 0;
   memset(cb->dataMD5sum, 0, 16);
   memset(cb->imageMD5sum, 0, 16);

   /* image identification */
   
   if(image->type == IMAGE_FILE)
      cb->imageName = g_strdup(image->file->path);

   /* For augmented images we need both the pure data size and the full size
      in order to compute MD5 sums for both. Depending on what the user wants
      to do with the image (create an ecc file or re-augment the image) we'll
      need the first or the last version. 
      There's one catch, though: If the augmented image is accompanied with
      an ecc file, we must treat the image as raw data; e.g. do not set 
      the data size to the portion protected by the augmented ecc.
      Otherwise, the CRC sums from the ecc file which do also protect
      the augmented ecc part will become unreachable. */
   
   if(image->eccHeader && !image->eccFileHeader)  /* augmented image, and no ecc file? */
   {  cb->dataSectors = uchar_to_gint64(image->eccHeader->sectors);
      if(image->type == IMAGE_FILE)
           cb->allSectors = image->sectorSize;
      else cb->allSectors = image->dh->sectors;
   }
   else
   {  if(image->type == IMAGE_FILE)
            cb->dataSectors = cb->allSectors = image->sectorSize;
      else  cb->dataSectors = cb->allSectors = image->dh->sectors;
   }

   /* Note: The following statement is not correct for RS03.
      It does not hurt since RS03 will set the correct value when
      the CrcBuf is created via RS03GetCrcBuf(), and in all other
      cases it does currently not matter. Especially, CRC sums 
      created during an image read are not used when subsequently
      creating new ecc data. */
   
   cb->coveredSectors = cb->dataSectors;

   /* Extract the fingerprint */

   if(image->fpState == FP_PRESENT)
   {  memcpy(cb->mediumFP, image->imageFP, 16);
      cb->fpValid = TRUE;
   }
   cb->fpSector = image->fpSector;

   /* CRC sum array */
   
   cb->crcbuf  = g_malloc(cb->allSectors * sizeof(guint32));
   cb->crcSize = cb->allSectors;
   cb->valid   = CreateBitmap0(cb->allSectors);
   
   image->crcCache = cb;
   return cb;
}

/*
 * Add a 2048 byte block into the checksum buffer
 */

int AddSectorToCrcBuffer(CrcBuf *cb, int mode, guint64 idx, unsigned char *buf, int buf_size)
{  guint32 crc;

   if(idx < 0 || idx >= cb->crcSize)
     return CRC_OUTSIDE_BOUND;

   /* Update the CRC sums */

   if(   (mode & CRCBUF_UPDATE_CRC)
      || ((mode & CRCBUF_UPDATE_CRC_AFTER_DATA) && idx >= cb->coveredSectors))
   {  crc = Crc32(buf, 2048);  /* should be buf_size, but remains at 2048 for backwards compatibility. */
      cb->crcbuf[idx] = crc;   /* does not harm except that the last sector is padded with the contents */
      SetBit(cb->valid, idx);  /* of the previous sector when reading an image file whole size is not */
   }                           /* a multiple of 2048 */
   
   /* Update the MD5 sums */

   if(!(mode & CRCBUF_UPDATE_MD5))
     return CRC_GOOD;
   
   if(cb->lastSector != idx)  /* sector out of order -> md5sum dead */
   {  cb->md5State = MD5_INVALID;
     return CRC_BAD;
   }
   cb->lastSector++;

   if(idx <= cb->allSectors-1)
      MD5Update(cb->md5Ctxt, buf, buf_size);

   if(idx == cb->dataSectors-1)
   {  MD5Context *dataCtxt = alloca(sizeof(MD5Context));

      memcpy(dataCtxt, cb->md5Ctxt, sizeof(MD5Context));
      MD5Final(cb->dataMD5sum, dataCtxt);
      cb->md5State |= MD5_DATA_COMPLETE;
   }
      
   if(idx == cb->allSectors-1)
   {  MD5Final(cb->imageMD5sum, cb->md5Ctxt);
      cb->md5State |= MD5_IMAGE_COMPLETE;
      cb->md5State &= ~MD5_BUILDING;
   }

   return CRC_GOOD;
}
   
/*
 * Test a 2048 byte block against the checksum in the buffer
 */

int CheckAgainstCrcBuffer(CrcBuf *cb, gint64 idx, unsigned char *buf)
{  guint32 crc;

   if(idx < 0 || idx >= cb->crcSize)
     return CRC_OUTSIDE_BOUND;
   
   crc = Crc32(buf, 2048);

   if(!GetBit(cb->valid, idx))
      return CRC_UNKNOWN;
   
   if(crc == cb->crcbuf[idx])
      return CRC_GOOD;

   return CRC_BAD;
}

/*
 * Make sure that the current image and ecc file match the
 * cached CRC and md5 informaton
 */

int CrcBufValid(CrcBuf *crcbuf, Image *image, EccHeader *eh)
{
  if(!crcbuf)
  {  Verbose("CrcBufValid: crcbuf==NULL\n");
     return FALSE;
  }

  /* if still in building state we do not have all CRC sums */

  if(crcbuf->md5State & MD5_BUILDING)
  {  Verbose("CrcBufValid: NO, still building\n");
     return FALSE;
  }

  /* presence of one MD5 sum suffices (data md5sum may not be present
     under some circumstances) */
  
  if((!(crcbuf->md5State & MD5_COMPLETE)))
  {  Verbose("CrcBufValid: NOT complete\n");
     return FALSE;
  }
    
  Verbose("CrcBufValid: buffer VALID\n");
  return TRUE;
}

/***
 *** Clean up
 ***/

void FreeCrcBuf(CrcBuf *cb)
{  
   if(!cb)
   {  Verbose("FreeCrcBuf - nothing to do\n");
      return;
   }
   
   g_free(cb->crcbuf);
   FreeBitmap(cb->valid);
   if(cb->imageName)
     g_free(cb->imageName);
   if(cb->md5Ctxt)
     g_free(cb->md5Ctxt);
   g_free(cb);

   Verbose("FreeCrcBuf - buffer cleared\n");
}

/***
 *** Debugging output 
 ***/

void PrintCrcBuf(CrcBuf *cb)
{  char digest[33];
  guint64 i,missing=0;
  
   if(!Closure->verbose)
     return;

   PrintLog("CrcBuf contents, image path %s:\n", cb->imageName ? cb->imageName : "none (medium)" );
   PrintLog("  crcSize: %lld, dataSectors: %lld, coveredSectors: %lld, allSectors: %lld\n",
	    cb->crcSize, cb->dataSectors, cb->coveredSectors, cb->allSectors);

   PrintLog("  md5State:");
   if(cb->md5State)
   {  if(cb->md5State & MD5_BUILDING) PrintLog(" building");
      if(cb->md5State & MD5_DATA_COMPLETE) PrintLog(" data_complete");
      if(cb->md5State & MD5_IMAGE_COMPLETE) PrintLog(" image_complete");
   }
   else PrintLog(" invalid");
   PrintLog("\n");

   if(cb->md5State & MD5_COMPLETE)
   {  AsciiDigest(digest, cb->dataMD5sum);
      PrintLog("  data: %s\n", digest);
      AsciiDigest(digest, cb->imageMD5sum);
      PrintLog("  full: %s\n", digest);
   }
   AsciiDigest(digest, cb->mediumFP);
   if(cb->fpValid)
        PrintLog("  fp sector: %d; %s\n", cb->fpSector, digest);
   else PrintLog("  fp sector: %d; invalid\n", cb->fpSector);

   for(i=0; i<cb->crcSize; i++)
     if(!GetBit(cb->valid, i))
     {   missing++;
     }
   PrintLog("  missing crcs: %lld\n", missing);
}
