/*
    protocol_sync.c
    MCT v0.9 part 7 interindustry command set for synchronous cards 

    This file is part of the Unix driver for Towitoko smartcard readers
    Copyright (C) 2000, 2001, 2002 Carlos Prados <cprados@yahoo.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>

#include "defines.h"
#include "protocol_sync.h"
#include "tlv_object.h"
#include <string.h>
#include <stdlib.h>

/*
 * Not exported constants definition
 */

/* Selectable Data Section Files */
#define PROTOCOL_SYNC_DS_MF	0x3F00
#define PROTOCOL_SYNC_DS_DIR	0x2F00
#define PROTOCOL_SYNC_DS_ATR	0x2F01

/* Max size of application ID */
#define PROTOCOL_SYNC_AID_SIZE	16

/*
 * Not exported macros definition
 */

#define PROTOCOL_SYNC_TLV(icc, address) (TLV_Object_New ((icc), \
					Protocol_Sync_GetData, \
					ICC_Sync_GetLength (icc), \
					(address)))

/*
 * Not exported functions declaration
 */

static int Protocol_Sync_SelectFile (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp);
static int Protocol_Sync_ReadBinary (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp);
static int Protocol_Sync_UpdateBinary (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp);
static int Protocol_Sync_Verify (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp);
static int Protocol_Sync_ChangeVerifyData (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp);
static int Protocol_Sync_BadCommand (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp);
static bool Protocol_Sync_GetData (void * data, unsigned short address, unsigned short length, BYTE * buffer);
static void Protocol_Sync_Clear (Protocol_Sync * ps);

/*
 * Exported functions definition
 */

Protocol_Sync *
Protocol_Sync_New (void)
{
  Protocol_Sync *ps;

  ps = (Protocol_Sync *) malloc (sizeof (Protocol_Sync));

  if (ps != NULL)
    Protocol_Sync_Clear (ps);

  return ps;
}

int
Protocol_Sync_Init (Protocol_Sync * ps, ICC_Sync * icc)
{
  ps->icc = icc;

  /* Select Master File by default */
  ps->path = 0;
  ps->length = ICC_Sync_GetLength (ps->icc);

  return PROTOCOL_SYNC_OK;
}

int
Protocol_Sync_Command (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  int ret;

  ICC_Sync_BeginTransmission (ps->icc);

  /* Interindustry Commands */
  switch (APDU_Cmd_Ins (cmd))
    {
    case 0xA4:
      ret = Protocol_Sync_SelectFile (ps, cmd, rsp);
      break;
    case 0xB0:
      ret = Protocol_Sync_ReadBinary (ps, cmd, rsp);
      break;
    case 0xD6:
      ret = Protocol_Sync_UpdateBinary (ps, cmd, rsp);
      break;
    case 0x20:
      ret = Protocol_Sync_Verify (ps, cmd, rsp);
      break;
    case 0x24:
      ret = Protocol_Sync_ChangeVerifyData (ps, cmd, rsp);
      break;
    default:
      ret = Protocol_Sync_BadCommand (ps, cmd, rsp);
    }

  return ret;
}

int
Protocol_Sync_Close (Protocol_Sync * ps)
{
  Protocol_Sync_Clear (ps);

  return PROTOCOL_SYNC_OK;
}

void
Protocol_Sync_Delete (Protocol_Sync * ps)
{
  free (ps);
}

/* 
 * Not exported functions definition 
 */

static int
Protocol_Sync_SelectFile (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  BYTE buffer[2], aid[PROTOCOL_SYNC_AID_SIZE], path[2];
  unsigned short fid, aid_length, path_length;
  TLV_Object *tlv_dir, *tlv_template, *tlv_aid, *tlv_path, *tlv_app;
  ATR_Sync *atr;

  /* FID selected */
  if (APDU_Cmd_P1 (cmd) == 0x00)
    {
      /* Get FID */
      fid = (APDU_Cmd_Data (cmd)[0] << 8) | (APDU_Cmd_Data (cmd)[1]);

      if (fid == PROTOCOL_SYNC_DS_MF)
	{
	  ps->path = 0;
	  ps->length = ICC_Sync_GetLength (ps->icc);

	  buffer[0] = 0x90;
	  buffer[1] = 0x00;
	}

      else if (fid == PROTOCOL_SYNC_DS_ATR)
	{
	  atr = ICC_Sync_GetAtr (ps->icc);

	  if ((ATR_Sync_GetCategoryIndicator (atr) == ATR_SYNC_CATEGORY_INDICATOR) &&
	      (ATR_SYNC_IS_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr))) &&
	      (ATR_SYNC_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr))) > 4 &&
	      (ICC_Sync_GetLength (ps->icc) > 4))
	    {
	      ps->path = 4;
	      ps->length = MIN (ATR_SYNC_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr)),
				ICC_Sync_GetLength (ps->icc)) - 4;

	      buffer[0] = 0x90;
	      buffer[1] = 0x00;
	    }
	  
	  else
	    {
	      buffer[0] = 0x6A;
	      buffer[1] = 0x82;
	    }
	}

      else if (fid == PROTOCOL_SYNC_DS_DIR)
	{
	  atr = ICC_Sync_GetAtr (ps->icc);

	  if ((ATR_Sync_GetCategoryIndicator (atr) == ATR_SYNC_CATEGORY_INDICATOR) &&
	      (ATR_SYNC_IS_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr))))
	    {
	      if ((tlv_dir = PROTOCOL_SYNC_TLV (ps->icc, ATR_SYNC_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr)))))
		{
		  ps->path = TLV_Object_GetAddress (tlv_dir);
		  ps->length = TLV_Object_GetRawLength (tlv_dir);

		  buffer[0] = 0x90;
		  buffer[1] = 0x00;

		  TLV_Object_Delete (tlv_dir);
		}

	      else
		{
		  buffer[0] = 0x6A;
		  buffer[1] = 0x82;
		}
	    }
	  
	  else
	    {
	      buffer[0] = 0x6A;
	      buffer[1] = 0x82;
	    }
	}

      else
	{
	  buffer[0] = 0x6A;
	  buffer[1] = 0x82;
	}
    }

  /* AID selected */
  else if (APDU_Cmd_P1 (cmd) == 0x04)
    {
      /* Get AID */
      aid_length = MIN (APDU_Cmd_Lc (cmd), PROTOCOL_SYNC_AID_SIZE);
      memcpy (aid, APDU_Cmd_Data (cmd), aid_length);

      atr = ICC_Sync_GetAtr (ps->icc);

      if ((ATR_Sync_GetCategoryIndicator (atr) == ATR_SYNC_CATEGORY_INDICATOR) &&
	  (ATR_SYNC_IS_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr))))
	{
	  if ((tlv_dir = PROTOCOL_SYNC_TLV (ps->icc, ATR_SYNC_DIR_DATA_REFERENCE (ATR_Sync_GetDirDataReference (atr)))))
	    {
	      /* Mono-application card */
	      if (TLV_Object_GetTag (tlv_dir) == TLV_OBJECT_TAG_APPLICATION_ID)
		{
		  if (TLV_Object_CompareValue (tlv_dir, aid, &aid_length))
		    {
		      if (TLV_Object_Shift (&tlv_dir))
			{
			  ps->path = TLV_Object_GetAddress (tlv_dir);
			  ps->length = TLV_Object_GetRawLength (tlv_dir);

			  buffer[0] = 0x90;
			  buffer[1] = 0x00;

                          TLV_Object_Delete (tlv_dir);
			}

		      /* Cannot initialize app data section */
		      else
			{
			  buffer[0] = 0x6A;
			  buffer[1] = 0x82;
			}
		    }

		  /* Cannot find application ID */
		  else
		    {
		      buffer[0] = 0x6A;
		      buffer[1] = 0x82;

		      TLV_Object_Delete (tlv_dir);
		    }

		}

	      /* Mono-application card with template DO */
	      else if (TLV_Object_GetTag (tlv_dir) == TLV_OBJECT_TAG_TEMPLATE)
		{
		  if ((tlv_aid = TLV_Object_GetObjectByTag (tlv_dir, TLV_OBJECT_TAG_APPLICATION_ID)))
		    {
		      if (TLV_Object_CompareValue (tlv_aid, aid, &aid_length))
			{
			  if (TLV_Object_Shift (&tlv_dir))
			    {
			      ps->path = TLV_Object_GetAddress (tlv_dir);
			      ps->length = TLV_Object_GetRawLength (tlv_dir);

			      buffer[0] = 0x90;
			      buffer[1] = 0x00;

			      TLV_Object_Delete (tlv_dir);
			    }

			  /* Cannot initialize app data section */
			  else
			    {
			      buffer[0] = 0x6A;
			      buffer[1] = 0x82;
			    }
			}

		      /* Cannot find application ID */
		      else
			{
			  buffer[0] = 0x6A;
			  buffer[1] = 0x82;

			  TLV_Object_Delete (tlv_dir);
			}

		      TLV_Object_Delete (tlv_aid);
		    }

		  /* Cannot find application ID data object */
		  else
		    {
		      buffer[0] = 0x6A;
		      buffer[1] = 0x82;

		      TLV_Object_Delete (tlv_dir);
		    }

		}

	      /* Multi-application card */
	      else if (TLV_Object_GetTag (tlv_dir) == TLV_OBJECT_TAG_SEQUENCE)
		{
		  tlv_app = NULL;
		  tlv_template = NULL;

		  /* Iterate over dir data section sequence */
		  while ((!tlv_app) && (TLV_Object_Iterate (tlv_dir, &tlv_template)))
		    {
		      if ((tlv_aid = TLV_Object_GetObjectByTag (tlv_template, TLV_OBJECT_TAG_APPLICATION_ID)))
		        {
		          if (TLV_Object_CompareValue (tlv_aid, aid, &aid_length))
			    {
			      if ((tlv_path = TLV_Object_GetObjectByTag (tlv_template, TLV_OBJECT_TAG_PATH)))
			        {
				  if (TLV_Object_GetValue (tlv_path, path, &path_length))
				    {
				      if (path_length < 2)
					tlv_app = PROTOCOL_SYNC_TLV (ps->icc, path[0]);
				      else 
					tlv_app = PROTOCOL_SYNC_TLV (ps->icc, (path[path_length-2] << 8) | path[path_length-1]);
				    }

				  TLV_Object_Delete (tlv_path);
				}
			    }

                          TLV_Object_Delete (tlv_aid);
			}
		    }

		  /* End of secuence was not reached */
		  if (tlv_template != NULL)
		    TLV_Object_Delete (tlv_template);

		  /* Got it! */
		  if (tlv_app != NULL)
	            {
		      ps->path = TLV_Object_GetAddress (tlv_app);
		      ps->length = TLV_Object_GetRawLength (tlv_app);

		      buffer[0] = 0x90;
		      buffer[1] = 0x00;

		      TLV_Object_Delete (tlv_app);
	            }

		  /* Cannot find or cannot initialize app data section */
		  else
		    {
		      buffer[0] = 0x6A;
		      buffer[1] = 0x82;
		    }
		  
		  TLV_Object_Delete (tlv_dir);
		}

	      /* Invalid tag in dir data section */
	      else
		{
		  buffer[0] = 0x6A;
		  buffer[1] = 0x82;
	          
		  TLV_Object_Delete (tlv_dir);
		}
	    }

	  /* Cannot initialize dir data section */
	  else
	    {
	      buffer[0] = 0x6A;
	      buffer[1] = 0x82;
	    }
	}

      /* Cannot find dir data section */
      else
	{
	  buffer[0] = 0x6A;
	  buffer[1] = 0x82;
	}
    }

  /* Bad selection control */
  else
    {
      /* ID not found */
      buffer[0] = 0x6A;
      buffer[1] = 0x82;
    }

  (*rsp) = APDU_Rsp_New (buffer, 2);
  return PROTOCOL_SYNC_OK;
}

static int
Protocol_Sync_ReadBinary (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  unsigned offset, available;
  unsigned long expected;
  BYTE *buffer;

  offset = (APDU_Cmd_P1 (cmd) << 8) | APDU_Cmd_P2 (cmd);
  available = MAX ((signed) (ps->length) - (signed) (offset), 0);

  if (APDU_Cmd_Le_Available (cmd))
    expected = available;
  else
    expected = APDU_Cmd_Le (cmd);

  /* Cannot return more than APDU_MAX_RSP_SIZE - 2 data bytes */
  expected = MIN (expected, APDU_MAX_RSP_SIZE - 2);

  if (expected > available)
    {
      /* Get memory for response */
      buffer = (BYTE *) calloc (available + 2, sizeof (BYTE));

      /* Read data */
      if (ICC_Sync_Read (ps->icc, ps->path + offset, available, buffer) != ICC_SYNC_OK)
	{
	  buffer[0] = 0x65;
	  buffer[1] = 0x01;

	  (*rsp) = APDU_Rsp_New (buffer, 2);
	  free (buffer);

	  return PROTOCOL_SYNC_ICC_ERROR;
	}

      buffer[available] = 0x62;
      buffer[available + 1] = 0x82;

      (*rsp) = APDU_Rsp_New (buffer, available + 2);
      free (buffer);
    }

  else
    {
      /* Get memory for response */
      buffer = (BYTE *) calloc (expected + 2, sizeof (BYTE));

      /* Read data */
      if (ICC_Sync_Read (ps->icc, ps->path + offset, expected, buffer) != ICC_SYNC_OK)
	{
	  buffer[0] = 0x65;
	  buffer[1] = 0x01;

	  (*rsp) = APDU_Rsp_New (buffer, 2);
	  free (buffer);

	  return PROTOCOL_SYNC_ICC_ERROR;
	}

      buffer[expected] = 0x90;
      buffer[expected + 1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, expected + 2);
      free (buffer);
    }

  return PROTOCOL_SYNC_OK;
}

static int
Protocol_Sync_UpdateBinary (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  unsigned available, offset;
  unsigned long provided;
  BYTE buffer[2];
  int ret;

  offset = (APDU_Cmd_P1 (cmd) << 8) | APDU_Cmd_P2 (cmd);
  available = MAX ((signed) (ps->length) - (signed) (offset), 0);
  provided = APDU_Cmd_Lc (cmd);

  /* Write data */
  ret = ICC_Sync_Write (ps->icc, ps->path + offset, MIN (available, provided), APDU_Cmd_Data (cmd));

  /* Read only error */
  if (ret == ICC_SYNC_RO_ERROR)
    {
      buffer[0] = 0x62;
      buffer[1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_OK;
    }

  /* Communication error */
  else if (ret != ICC_SYNC_OK)
    {
      buffer[0] = 0x62;
      buffer[1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_ICC_ERROR;
    }

  /* Wrote less bytes than given */
  if (available < provided)
    {
      buffer[0] = 0x62;
      buffer[1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_OK;
    }

  buffer[0] = 0x90;
  buffer[1] = 0x00;

  (*rsp) = APDU_Rsp_New (buffer, 2);
  return PROTOCOL_SYNC_OK;
}

static int
Protocol_Sync_Verify (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  BYTE buffer[2], pin[ICC_SYNC_PIN_SIZE];
  unsigned trials;
  int ret;

  memset (pin, 0x00, ICC_SYNC_PIN_SIZE);
  memcpy (pin, APDU_Cmd_Data (cmd), MIN (APDU_Cmd_Lc (cmd), ICC_SYNC_PIN_SIZE));

  ret = ICC_Sync_EnterPin (ps->icc, pin, &trials);

  /* Verificarion unsuccessfull */
  if (ret == ICC_SYNC_PIN_ERROR)
    {
      buffer[0] = 0x63;
      buffer[1] = (0xC0 | ((BYTE) trials & 0x0F));
    }

  /* ICC blocked */
  else if (ret == ICC_SYNC_BLOCKED_ERROR)
    {
      buffer[0] = 0x69;
      buffer[1] = 0x83;
    }

  /* Communication error */
  else if (ret != ICC_SYNC_OK)
    {
      buffer[0] = 0x63;
      buffer[1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_ICC_ERROR;
    }

  /* Enter PIN OK */
  else
    {
      buffer[0] = 0x90;
      buffer[1] = 0x00;
    }

  (*rsp) = APDU_Rsp_New (buffer, 2);
  return PROTOCOL_SYNC_OK;
}

static int
Protocol_Sync_ChangeVerifyData (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  BYTE pin[ICC_SYNC_PIN_SIZE], newpin[ICC_SYNC_PIN_SIZE];
  BYTE buffer[2];
  unsigned trials;
  int ret;

  memcpy (pin, APDU_Cmd_Data (cmd), ICC_SYNC_PIN_SIZE);
  memcpy (newpin, APDU_Cmd_Data (cmd) + ICC_SYNC_PIN_SIZE, ICC_SYNC_PIN_SIZE);

  /* Verify current pin */
  ret = ICC_Sync_EnterPin (ps->icc, pin, &trials);

  /* Verificarion unsuccessfull */
  if (ret == ICC_SYNC_PIN_ERROR)
    {
      buffer[0] = 0x63;
      buffer[1] = (0xC0 | ((BYTE) trials & 0x0F));

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_OK;
    }

  /* ICC blocked */
  else if (ret == ICC_SYNC_BLOCKED_ERROR)
    {
      buffer[0] = 0x69;
      buffer[1] = 0x83;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_OK;
    }

  /* Communication error */
  else if (ret != ICC_SYNC_OK)
    {
      buffer[0] = 0x63;
      buffer[1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_ICC_ERROR;
    }

  /* Change PIN */
  ret = ICC_Sync_ChangePin (ps->icc, newpin);

  if (ret != ICC_SYNC_OK)
    {
      buffer[0] = 0x63;
      buffer[1] = 0x00;

      (*rsp) = APDU_Rsp_New (buffer, 2);
      return PROTOCOL_SYNC_ICC_ERROR;
    }

  buffer[0] = 0x90;
  buffer[1] = 0x00;

  (*rsp) = APDU_Rsp_New (buffer, 2);
  return PROTOCOL_SYNC_OK;
}

static int
Protocol_Sync_BadCommand (Protocol_Sync * ps, APDU_Cmd * cmd, APDU_Rsp ** rsp)
{
  BYTE buffer[2];

  buffer[0] = 0x6E;
  buffer[1] = 0x00;

  (*rsp) = APDU_Rsp_New (buffer, 2);

  return PROTOCOL_SYNC_OK;
}

static bool 
Protocol_Sync_GetData (void * data, unsigned short address, unsigned short length, BYTE * buffer)
{
  return (ICC_Sync_Read ((ICC_Sync *) data,  address, length,  buffer) == ICC_SYNC_OK);
}

static void
Protocol_Sync_Clear (Protocol_Sync * ps)
{
  ps->icc = NULL;
  ps->path = 0;
  ps->length = 0;
}


syntax highlighted by Code2HTML, v. 0.9.1