//-*-c++-*--------------------------------------------------------------
//  devTest.cpp
//   Test program for testing the NSR SSVolume object and the
//   DriveDevice object. 
//
//      Unpublished Confidential Information of Hewlett-Packard
//      Company. Do not disclose.  Copyright (c) Hewlett-Packard
//      Company 1995. All rights reserved
// 
// The contents of this software are proprietary and confidential to
// the Hewlett- Packard Company, and are limited in distribution to
// those with a direct need to know.  Individuals having access to
// this software are responsible for maintaining the confidentiality
// of the content and for keeping the software secure when not in use.
// Transfer to any party is strictly forbidden other than as expressly
// permitted in writing by Hewlett-Packard Company.  Unauthorized
// transfer to or possession by any unauthorized party may be a
// criminal offense.
//
//                  RESTRICTED RIGHTS LEGEND
//
//      Use,  duplication,  or disclosure by the U. S. Government is
//      subject to restrictions as set forth in subparagraph (c) (1)
//      (ii) in the Rights in Technical Data and Computer Software
//      clause in DFARS 252.227-7013 or any other successor clause.
//
//                      HEWLETT-PACKARD COMPANY
//                      3000 Hanover St.
//                      Palo Alto, CA  94304 U.S.A.
//
//      Rights for non-DOD U.S. Government Departments and Agencies
//      are as set forth in FAR 52.227-19 (c) (1,2) or any other
//      successor clause.
//
// REVISION HISTORY:
//
// 12-13-94:  original revision
// 01-17-95:  rewritten to use the new DriveDevice interface (using PhysExtents
//            rather than ExtLists); also tests the PhysicalVolume object.
//----------------------------------------------------------------------
//
// $Source: /projects/HMS/nsr/udfAncil/ssTest/devTest.cpp,v $
// $Revision: 1.21 $    $Author: hmgr $
// $State: Exp $        $Locker:  $
// $Date: 96/05/24 11:10:59 $
//
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>               
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <nsrscsi.h>
#include <assert.h>
#include "nsrLib.h"
#include "nsrRSSdv.h"

// Defines

#define BYTEARRAY_MAX   49152   // Max size of a ByteArray, in bytes

// Function prototypes

static INT32 CheckSum(BYTE_PTR,UINT32);
static void initData(void);

static void testDDev(DriveDevice *,CONST_CHAR_PTR);
static void testLext(DriveDevice *,CONST_CHAR_PTR);
static void testBound(DriveDevice *,CONST_CHAR_PTR);
static void write_sectors(INT32 fd,const PhysExtent& extent,UINT32 sectorsize);
static INT32 sectors_erased(INT32 fd,const PhysExtent& extent,UINT32 sectorsize);

// Some global data

static BYTE     data_buffer[BYTEARRAY_MAX];
static BYTE     zero_buffer[BYTEARRAY_MAX];
static UINT32 retcode = 0;


//-----------------------------------------------------------------------------
// main():
//
// We expect one argument, the device file to test on; if we don't
// get any arguments, or an argument we don't understand, just use some
// default ("/dev/rdsk/c201d4s0")
//-----------------------------------------------------------------------------


INT32 main (INT16 argc, CHAR_PTR argv[])
{
    INT32       iter;
    INT32       ssize,nsect;
    DriveDevice *ddev;
    String      str;
    CONST_CHAR_PTR devfile;
    Error       error;

    // Prepare a String variable for use with the SSVolume and DriveDevice
    // constructors

    if(argc != 2)
    {
        str = String("/dev/rdsk/c201d4s0");
    }
    else
    {
        str = String(argv[1]);
    }
    devfile = (CONST_CHAR_PTR) str;
    printf("Using device file %s\n",devfile);

    // Create a DriveDevice and an SSVolume object.

    ddev = new DriveDevice(str);
    if(ddev == NULL)
    {
        (void) fprintf(stderr,"ddev construction failed:  no free store!\n");
        exit(1);
    }
    if(ddev->error() != error_None)
    {
        (void) fprintf(stderr,"ddev construction failed, error = %d: %s\n",
                       (INT32) ddev->error(),
                       (ddev->error()).message());
        delete ddev;
        exit(1);
    }

    initData();
    testDDev(ddev,devfile);
    if(retcode) {
        delete ddev;
        return retcode;
    }
    testLext(ddev,devfile);
    if(retcode) {
        delete ddev;
        return retcode;
    }
    testBound(ddev,devfile);
    delete ddev;
    return retcode;
}

//-----------------------------------------------------------------------------
// testDDev():  Tests read/write/erase functions.
//
// This function performs a set number of random reads and writes.
//-----------------------------------------------------------------------------

static void
testDDev(DriveDevice *ddev,CONST_CHAR_PTR devfile)
{
    INT32               i,j;
    INT32               numextents;
    INT32               checksum,csum;
    INT32               nbytes;
    UINT32              offset;                 // In sectors
    UINT32              transfer;               // In sectors
    UINT32              extentsize;             // In sectors
    UINT32              maxtransfer;            // In sectors
    PhysicalVolume      pvol(ddev);
    Error               error;

    srand(time((time_t *) NULL));
    maxtransfer = BYTEARRAY_MAX / ddev->sectorSize();

    // In each iteration, we generate a PhysExtent with random starting
    // sector and a random length.  We do a write, then a read, CRC'ing each
    // time.  We follow up with a read using the raw disk device (bypassing
    // the NSR objects) to verify that the writing and reading were 
    // successful.

    for(i = 0; i < 100; i++)            // Do 100 iterations
    {
        printf("\n-------------------------------------ITERATION %d----------------------------\n",i);
        offset = rand() % (ddev->numSectors() - maxtransfer);
        transfer = (rand() % maxtransfer) + 1;

        // Make the PhysExtent now.
        
        PhysExtent extent(&pvol,offset,transfer);

        // Make the ByteArray, copy the data to it.
        
        ByteArray       arr(transfer*ddev->sectorSize());

        arr.fromMem((BYTE_PTR) data_buffer);
        checksum = CheckSum(data_buffer,transfer*ddev->sectorSize());
        
        // Do the write

        nbytes = extent.write(arr,error);
        printf("WRITING %d sectors at offset sector %d...", transfer,offset);
        if(nbytes < extent.numBytes())
        {
            printf("FAILED\n\tGot short count of %d bytes, error code = %d\n",
                   nbytes,(INT32) error);
            retcode = 2;
        }
        else
        {
            printf("%d bytes SUCCEEDED\n",nbytes);
        }

        // Do the read

        nbytes = extent.read(arr,error);
              
        printf("READING %d sectors at offset %d...",transfer,offset);
        if(nbytes < extent.numBytes())
        {
            printf("FAILED!\n\tGot short count of %d bytes, error code = %d\n",
                   nbytes,(INT32)error);
            retcode = 2;
        }
        else
        {
            printf("%d bytes SUCCEEDED\n",nbytes);
        }

        // Verify checksum

        csum = CheckSum((BYTE_PTR) arr,extent.numBytes());
        printf("Verifying checksum...");
        if(csum != checksum)
        {
            printf("FAILED!  Read csum %d != written csum %d\n",csum,checksum);
            retcode = 2;
            break;
        }
        else
        {
            printf("SUCCEEDED\n");
        }

        // Now do the read through the raw device for a sanity check.

        INT32 fd = open(devfile,O_RDONLY);
        if(fd < 0)
        {
            printf("Unable to open raw device file, skipping raw check!\n");
            perror("open");
            continue;
        }
        lseek(fd,offset*ddev->sectorSize(),SEEK_SET);
        nbytes = read(fd,(BYTE_PTR) arr,transfer*ddev->sectorSize());
        printf("REREADING %d sectors at offset %d...",transfer,offset);
        if(nbytes < (transfer*ddev->sectorSize()))
        {
            printf("FAILED!\n\tGot short count of %d bytes, errno = %d\n",
                   nbytes,errno);
            retcode = 2;
        }
        else
        {
            printf("SUCCEEDED\n");
        }

        // Compare the checksums

        printf("Reverifying checksum...");
        csum = CheckSum((BYTE_PTR) arr,extent.numBytes());
        if(csum != checksum)
        {
            printf("FAILED!  Read csum %d != written csum %d\n",csum,checksum);
            retcode = 2;
        }
        else
        {
            printf("SUCCEEDED\n");
        }
        close(fd);

        // Now do a quick-and-dirty erase test.  
        // Then we verify using a disk-driver ioctl that the sectors were
        // erased, then overwrite them.

        printf("Erasing %d sectors at offset %d...",extent.numSectors(),
                                                    extent.startSector());
        if(pvol.erase(extent,error)) 
        {
            printf("FAILED!  Error code = %d\n",(INT32) error);
            retcode = 2;
        }
        else 
        {
            // Now verify

            fd = open(devfile,O_RDWR);
            if(sectors_erased(fd,extent,extent.numSectors()))
            {
                close(fd);
                printf("SUCCEEDED\n");
            }
            else
            {
                close(fd);
                printf("ddev->erase succeeded, erase verify FAILED!\n");
            }

            // Now overwrite, since it's rude to leave erased sectors lying
            // around

            fd = open(devfile,O_RDWR);
            write_sectors(fd,extent,pvol.sectorSize());
            close(fd);
        }
    }                           // End of for loop
}

//-----------------------------------------------------------------------------
// testLext():  Tests read/write/erase functions using Logical Extents and
//              LogExtentLists.  In all other respects identical to testDDev.
//
// This function performs a set number of random reads and writes.
//
// Making a Logical Extent:
//
// Make a DriveDevice = ddev
// Make a PhysicalVolume(ddev) = pvol
// Make a PhysExtent(pvol) = pext
// Make a Partition(pext) = part
// Make a LogExtent(part,start,length) = lext
//-----------------------------------------------------------------------------

static void
testLext(DriveDevice *ddev,CONST_CHAR_PTR devfile)
{
    INT32               i,j;
    INT32               numextents;
    INT32               checksum,csum;
    INT32               nbytes;
    UINT32              offset;                 // In sectors
    UINT32              transfer;               // In sectors
    UINT32              extentsize;             // In sectors
    UINT32              maxtransfer;            // In sectors
    PhysicalVolume      pvol(ddev);             
    PhysExtent          pext(&pvol,257,pvol.numSectors() - 260);
    Partition           part(pext);
    Error               error;

    srand(time((time_t *) NULL));
    maxtransfer = BYTEARRAY_MAX / ddev->sectorSize();

    // We use a random offset, a random transfer length, and a random number
    // of sectors per Extent.  Basically as random as possible, even though
    // this is probably not a good usage model.  We do a write and then a 
    // read, checksumming each time.  We also then do a read using completely
    // raw access for a sanity check.

    printf("\n\n***** Testing Logical Extent read/write access ***** \n\n");

    for(i = 0; i < 100; i++)            // Do 100 iterations
    {
        printf("\n-------------------------------------ITERATION %d----------------------------\n",i);
        offset = rand() % (ddev->numSectors() - maxtransfer);
        transfer = (rand() % maxtransfer) + 1;
        extentsize = (rand() % transfer) + 1;

        // Make the Extent List now.
        
        numextents = (transfer / extentsize); 
        if(transfer % extentsize)
        {
            numextents++;
        }

        LogExtentList   extlist(*part.logVolumep());
        UINT32  esize;

        for(j = 0; j < numextents; j++)
        {

            if((offset + extentsize) > ddev->numSectors())
            {
                esize = pvol.numSectors() - offset; 
            }
            else
            {
                esize = extentsize;
            }
            if((j+1)*extentsize > transfer) {
                esize =  extentsize - ((j+1)*extentsize - transfer);
            }
            LogExtent extent(&part,offset+(j*extentsize),
                             esize*pvol.sectorSize());
            
            extlist.append(extent,error);
        }

        // Make the ByteArray, copy the data to it.
        
        ByteArray       arr(transfer*pvol.sectorSize());
        ByteArrayRef    arrRef(arr);

        arr.fromMem((BYTE_PTR) data_buffer);
        checksum = CheckSum(data_buffer,transfer*ddev->sectorSize());
        
        // Do the write

        nbytes = extlist.write(arrRef,error);
        printf("WRITING %d sectors at LOGICAL offset %d using %d extents...",
               transfer,offset,numextents);
        printf("extent size = %d\n",extentsize);
        if((signed) nbytes < (signed) (transfer*pvol.sectorSize()))
        {
            printf("FAILED\n\tGot short count of %d bytes, error code = %d\n",
                   nbytes,(INT32) error);
#ifdef DEBUG
            extlist.print(0,"");
#endif
            retcode = 2;
        }
        else
        {
            printf("%d bytes SUCCEEDED\n",nbytes);
        }

        arr.fromMem((BYTE_PTR) zero_buffer);

        // Do the read

        nbytes = extlist.read(arrRef,error);
        printf("arr length = %d\n",arrRef.length());
              
        printf("READING %d sectors at offset %d using %d extents...",
               transfer,offset,numextents);
        if(nbytes < (transfer*pvol.sectorSize()))
        {
            printf("FAILED!\n\tGot short count of %d bytes, error code = %d\n",
                   nbytes,(INT32) error);
            retcode = 2;
        }
        else
        {
            printf("%d bytes SUCCEEDED\n",nbytes);
        }

        // Verify checksum

        csum = CheckSum((BYTE_PTR) arr,transfer*pvol.sectorSize());
        printf("Verifying checksum...");
        if(csum != checksum)
        {
            printf("FAILED!  Read csum %d != written csum %d\n",csum,checksum);
            retcode = 2;
            break;
        }
        else
        {
            printf("SUCCEEDED\n");
        }

        // Now do the read through the raw device for a sanity check.

        INT32 fd = open(devfile,O_RDWR);
        if(fd < 0)
        {
            printf("Unable to open raw device file, skipping raw check!\n");
            perror("open");
            continue;
        }
        lseek(fd,257+offset*pvol.sectorSize(),SEEK_SET);
        nbytes = read(fd,(BYTE_PTR) arr,transfer*pvol.sectorSize());
        printf("REREADING %d sectors at offset %d...",transfer,offset);
        if(nbytes < (transfer*pvol.sectorSize()))
        {
            printf("FAILED!\n\tGot short count of %d bytes, errno = %d\n",
                   nbytes,errno);
            retcode = 2;
        }
        else
        {
            printf("SUCCEEDED\n");
        }

        // Compare the checksums

        printf("Reverifying checksum...");
        csum = CheckSum((BYTE_PTR) arr,transfer*ddev->sectorSize());
        if(csum != checksum)
        {
            printf("FAILED!  Read csum %d != written csum %d\n",csum,checksum);
            retcode = 2;
        }
        else
        {
            printf("SUCCEEDED\n");
        }
        close(fd);

#ifdef ERASE
        // Now do a quick-and-dirty erase test.  For simplicity, we'll just
        // erase the first extent we used for the write/read tests above.
        // Then we verify using a disk-driver ioctl that the sectors were
        // erased, then overwrite them.

        Extent          erase_extent = extlist[0];

        printf("Erasing %d sectors at offset %d...",extentsize,offset);
        if(ddev->erase(erase_extent)) 
        {
            printf("FAILED!  Error code = %d\n",(INT32) ddev->error());
            retcode = 2;
        }
        else 
        {
            // Now verify

            fd = open(devfile,O_RDWR);
            if(sectors_erased(fd,erase_extent,extentsize))
            {
                close(fd);
                printf("SUCCEEDED\n");
            }
            else
            {
                close(fd);
                printf("ddev->erase succeeded, erase verify FAILED!\n");
            retcode = 2;
            }

            // Now overwrite, since it's rude to leave erased sectors lying
            // around

            fd = open(devfile,O_RDWR);
            write_sectors(fd,erase_extent,ddev->sectorSize());
            close(fd);
        }
#endif // ERASE
    }                           // End of for loop
}

//-----------------------------------------------------------------------------
// testBound():  Perform boundary condition testing:  verify that LogExtents
//               can read/write with non-sector-multiple lengths.
//
// This test assumes logical blocks are equal in size to sectors.
//-----------------------------------------------------------------------------

static void
testBound(DriveDevice *ddev,CONST_CHAR_PTR devfile)
{
    INT32               i;
    INT32               offset;         // In sectors
    INT32               length;         // In bytes 
    INT32               maxoff;         // In sectors
    INT32               rval; 
    INT32               crc1,crc2;
    PhysicalVolume      pvol(ddev);             
    PhysExtent          pext(&pvol,257,pvol.numSectors() - 260);
    Partition           part(pext);
    ByteArray           barr(BYTEARRAY_MAX);
    ByteArrayRef        bref(barr);
    Error               error;

    assert(ddev != NULL);
    maxoff = ddev->numSectors() - (BYTEARRAY_MAX / ddev->sectorSize()) - 257;

    // For each iteration, we generate a LogExtent with a random starting
    // offset (in blocks).  

    printf("\n\n***** Testing boundary condition cases *****\n\n");

    for(i = 0; i < 100; i++) {
        printf("\n-------------------------------------ITERATION %d-----------\
-----------------\n",i);
        offset = lrand48() % maxoff;
        length = lrand48() % BYTEARRAY_MAX;

        LogExtent       lext(&part,offset,length);
        bref.length(length);

        // Perform a write.

        printf("Writing %d bytes. . .",length);
        bref.fromMem(data_buffer);
        rval = lext.write(bref,error);
        if(rval < bref.length()) {
            printf("FAILED:  wrote %d bytes, expected %d, skipping read\n",
                   rval,bref.length());
            retcode = 2;
            continue;
        }
        else
        {
            printf("succeeded.\n");
        }

        // Try to read the data back.

        bref.fromMem(zero_buffer);
        rval = lext.read(bref,error);
        printf("Reading %d bytes. . .",length);
        if(rval < bref.length()) {
            printf("FAILED:  read %d bytes, expected %d, skipping CRC check\n",
                   rval,bref.length());
            retcode = 2;
            continue;
        }
        else
        {
            printf("succeeded.\n");
        }

        // Now verify the CRC's match.

        crc1 = CheckSum(data_buffer,length);
        crc2 = CheckSum((BYTE_PTR) bref,length);
        if(crc1 != crc2) {
            printf("CRC FAILURE:  read crc %08x != written crc %08x!\n",
                   crc1,crc2);
            retcode = 2;
        }
        else {
            printf("CRC verified.\n");
        }
    }
}

//-----------------------------------------------------------------------------
// CheckSum():  The usual checksum/CRC code, stolen once again from 
//              distress.c...
//-----------------------------------------------------------------------------

static INT32
CheckSum(BYTE_PTR data,UINT32 size)
{
    INT32 i;
    INT32 csum = 0;

    for(i = 0; i < size; i++) {
        csum = ((unsigned)csum<<1 | (unsigned)csum>>31) ^ (unsigned)data[i];
    }
    return csum;
}

//-----------------------------------------------------------------------------
// initData():  Fill up the data array with random data.
//-----------------------------------------------------------------------------

static void
initData(void)
{
    INT32       i;

    srand(time((time_t *) NULL));
    for(i = 0; i < BYTEARRAY_MAX; i++)
    {
        data_buffer[i] = rand() % 256;
        zero_buffer[i] = 0;
    }
}

//-----------------------------------------------------------------------------
// sectors_erased():  Returns 1 if the sectors in the given extent are erased,
//                    0 otherwise.
//-----------------------------------------------------------------------------

static INT32
sectors_erased(INT32 fd,const PhysExtent& extent,UINT32 numsects)
{ 
    struct scsi_verify  verify;

    verify.start_lba = (UINT32) extent.startSector();
    verify.block_cnt = numsects;

    // The ioctl returns 0 on success (i.e., all sectors in the given range
    // are blank, so we return 1.  The ioctl returns -1 on failure, so we
    // return 0 in that case.

    if(ioctl(fd,SIOC_VERIFY_BLANK,&verify))
    {
        perror("ioctl(SIOC_VERIFY_BLANK)");
        return 0;
    }
    return 1;
}
    
//-----------------------------------------------------------------------------
// write_sectors():  Write some data over the given extent.  
//-----------------------------------------------------------------------------

static void
write_sectors(INT32 fd,const PhysExtent& extent,UINT32 sectorsize)
{
    lseek(fd,extent.startSector()*sectorsize,SEEK_SET);
    write(fd,data_buffer,extent.numBytes());
}
