CORE-POS/IS4C

View on GitHub
pos/is4c-nf/scale-drivers/drivers/NewMagellan/SPH_IngenicoRBA_Common.cs

Summary

Maintainability
F
6 days
Test Coverage
/*******************************************************************************

    Copyright 2010 Whole Foods Co-op

    This file is part of IT CORE.

    IT CORE 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 2 of the License, or
    (at your option) any later version.

    IT CORE 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
    in the file license.txt along with IT CORE; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*********************************************************************************/

/*************************************************************
 * SPH_Ingenico_i6550
 *     SerialPortHandler implementation for the Ingenico 
 *     signature capture devices using Retail Base
 *     Application (RBA). Tested with i6550, should work
 *     with i6580, i6770, and i6780 as well. Two other devices,
 *    i3070 and i6510, speak the same language but minor
 *    tweaking would need to be done to account for those
 *    devices not doing signature capture.
 *
 * Sets up a serial connection in the constructor
 *
 * Polls for data in Read(), writing responses back to the
 * device as needed
 *
*************************************************************/
using System;
using System.IO.Ports;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;

using BitmapBPP;

namespace SPH {

public class Signature {
    private int sig_length;
    private byte[][] sig_blocks;

    public Signature(int l){
        sig_length = l;
        sig_blocks = new byte[sig_length][];
    }

    public bool WriteBlock(int num, byte[] data){
        if (num >= sig_length) return false;
        sig_blocks[num] = data;
        return true;
    }

    public bool SigFull(){
        for(int i=0; i<sig_length;i++){
            if(sig_blocks[i] == null)
                return false;
        }
        return true;
    }

    // helpful example provided @ 
    // http://combustibleknowledge.com/2009/08/29/3-byte-ascii-format-deciphered/
    public string BuildImage(string path){
        List<Point> points = new List<Point>();
        List<byte> byteList = new List<byte>();

        for(int i=0;i<sig_length;i++){
            for(int j=0; j<sig_blocks[i].Length;j++){
                //Mask out most significant bit
                byte value = (byte)(sig_blocks[i][j] & 0x7f);
                byteList.Add(value);
            }
        }

        byte[] image = byteList.ToArray();
        Point lastPoint = new Point();
        int hiX, hiY;

        for (int i = 0; i < image.Length; i++){
            if (image[i] >= 96 && image[i] <= 111){
                hiX = (image[i] & 0x0C) * 128;
                hiY = (image[i] & 0x03) * 512;

                byte n1 = (byte)(image[i + 1] - 32);
                byte n2 = (byte)(image[i + 2] - 32);
                byte n3 = (byte)(image[i + 3] - 32);

                lastPoint.X = hiX + (n1 * 8) +
                    (n3 >> 3);
                lastPoint.Y = hiY + (n2 * 8) +
                    (n3 & 0x07);

                points.Add(lastPoint);
                i = i + 3;
            }
            else if (image[i] == 112){
                points.Add(new Point());
            }
            else {
                if (image[i] != 10 && image[i] != 13){
                    int a, b;
                    Point p = new Point();

                    byte m1 = (byte)(image[i] - 32);
                    byte m2 = (byte)(image[i + 1] - 32);
                    byte m3 = (byte)(image[i + 2] - 32);

                    a = (m1 * 8) + (m3 >> 3);
                    b = (m2 * 8) + (m3 & 0x07);

                    if (a > 256)
                        p.X = lastPoint.X + (a - 512);
                    else
                        p.X = lastPoint.X + a;

                    if (b > 256)
                        p.Y = lastPoint.Y + (b - 512);
                    else
                        p.Y = lastPoint.Y + b;

                    lastPoint.X = p.X;
                    lastPoint.Y = p.Y;

                    points.Add(p);
                    i = i + 2;
                }
            }
        }

        return DrawSignatureImage(points,path);
    }

    private string DrawSignatureImage(List<Point> Points,string path){
        int width=512;
        int height=128;
        Bitmap bmp = new Bitmap(width, height);

        Graphics g = Graphics.FromImage(bmp);
        Brush whiteBrush = new SolidBrush(Color.White);
        Brush blackBrush = new SolidBrush(Color.Black);
        Pen p = new Pen(blackBrush);

        bmp.SetResolution(height/2, width/2);
        g.TranslateTransform(0f, height);
        g.ScaleTransform(1f, -1f);
        g.FillRegion(whiteBrush,
            new Region(new Rectangle(0, 0, width, height)));

        p.Width = 10;
        p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
        p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
        p.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;

        List<Point> line = new List<Point>();
        foreach (Point point in Points){
            if (point.IsEmpty){
                try {
                    g.DrawLines(p, line.ToArray());
                    line.Clear();
                } catch (Exception) {
                    System.Console.Write("BAD LINE: ");
                    foreach (Point pt in line) {
                        System.Console.Write(pt);
                        System.Console.Write(" ");
                    }
                    System.Console.WriteLine("");
                }
            }
            else{
                line.Add(point);
            }
        }

        // silly rigamarole to get a unique file name
        System.Security.Cryptography.MD5 hasher = System.Security.Cryptography.MD5.Create();
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] hash = hasher.ComputeHash(enc.GetBytes(DateTime.Now.ToString()));
        System.Text.StringBuilder sBuilder = new System.Text.StringBuilder();
        for (int i = 0; i < hash.Length; i++)
            sBuilder.Append(hash[i].ToString("x2"));
        string base_fn = path + "\\" + sBuilder.ToString()+".bmp";

        bmp.Save(base_fn, System.Drawing.Imaging.ImageFormat.Bmp);

        // pass through 1bpp conversion
        byte[] fixbpp = BitmapBPP.BitmapConverter.To1bpp(base_fn);
        System.IO.File.WriteAllBytes(base_fn,fixbpp);

        return base_fn;
    }

}

/**
  This class contains all the functionality for building
  and dealing with RBA protocl messages. Subclasses that
  handle a different hardware interface are responsible for
  the following:
  - method Read() that gets ACK, NACK, and message bytes
    from the device. The subclass is responsible for sending
    its own ACKs and NACKs. The message bytes starting with 
    STX and ending with ETX followed by LRC should be passed
    to the HandleMessageFromDevice() method.
  - method WriteMessageToDevice() is responsible for sending
    an array of bytes to the device. This method must add an
    LRC byte at the end as the parameter does not include it
*/
public abstract class SPH_IngenicoRBA_Common : SerialPortHandler 
{
    protected byte[] last_message;
    /** not used with on-demand implementation
    private string terminal_serial_number;
    private string pos_trans_no;
    */
    private bool getting_signature;
    private Signature sig_object;
    protected bool auto_state_change = true;
    private string masked_pan = "";

    private static String MAGELLAN_OUTPUT_DIR = "ss-output/";

    // spacing matters on these
    protected const string EBT_CA = "1 0 4  0  14 10000 1 1 1 0      0 132 0 1 0 D 0 0 406";
    protected const string EBT_FS = "1 0 4  0  14     0 1 1 1 0      0 133 0 1 0 D 0 0 406";

    // to allow RBA_Stub
    public SPH_IngenicoRBA_Common() { }

    public SPH_IngenicoRBA_Common(string p) : base(p)
    {
        last_message = null;
        getting_signature = false;
        if (auto_state_change) {
            System.Console.WriteLine("SPH_Ingenico starting in AUTO mode");
        } else {
            System.Console.WriteLine("SPH_Ingenico starting in COORDINATED mode");
        }
    }

    // see if array has a valid check character
    private bool CheckLRC(byte[] b)
    {
        byte lrc = 0;
        for (int i=1; i < b.Length-1; i++) {
            lrc ^= b[i];
        }
        return (lrc == b[b.Length-1]) ? true : false;
    }


    // main read loop
    override public void Read()
    {
        // child class must override again
    }

    /**
      Handle messages from the device
      @param buffer - the message
    */
    public void HandleMessageFromDevice(byte[] buffer)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        if (this.verbose_mode > 0) {
            System.Console.WriteLine("Received:");
            foreach (byte b in buffer) {
                if (b < 10) {
                    System.Console.Write("0{0} ",b);
                } else {
                    System.Console.Write("{0} ",b);
                }
            }
            System.Console.WriteLine();

            System.Console.WriteLine(enc.GetString(buffer));

            System.Console.WriteLine("LRC "+(CheckLRC(buffer)?"Valid":"Invalid"));
            System.Console.WriteLine();
        }

        int code = ((buffer[1]-0x30)*10) + (buffer[2]-0x30);
        switch (code) {
            case 1: break;     // online response from device
            case 4: break;     // set payment response from device

            case 11:    
                // status response from device
                int status = ((buffer[4]-0x30)*10) + (buffer[5]-0x30);
                if (status == 11) { // signature ready
                    WriteMessageToDevice(GetVariableMessage("000712"));
                } else if (status == 6) {
                    WriteMessageToDevice(GetVariableMessage("000712"));
                } else if (status == 10) {
                    Thread.Sleep(500);
                    WriteMessageToDevice(StatusRequestMessage());
                }
                break;

            case 23:
            case 87:
                // get card info repsponse
                if (buffer[4] != 0x30) { // invalid status
                    HandleMsg("termReset");
                    break;
                }
                string card_msg = enc.GetString(buffer);
                card_msg = card_msg.Substring(1, card_msg.Length - 3); // trim STX, ETX, LRC 
                if (card_msg.Length > 5) {
                    card_msg = card_msg.Replace(new String((char)0x1c, 1), "@@");
                    PushOutput("PANCACHE:" + card_msg);
                    if (this.verbose_mode > 0) {
                        System.Console.WriteLine(card_msg);
                    }
                    if (card_msg.Contains("%") && card_msg.Contains("^")) {
                        string[] parts = card_msg.Split(new char[]{'%'}, 2);
                        parts = parts[1].Split(new char[]{'^'}, 2);
                        masked_pan = parts[0].Substring(1);
                    } else if (card_msg.Contains(";") && card_msg.Contains("=")) {
                        string[] parts = card_msg.Split(new char[]{';'}, 2);
                        parts = parts[1].Split(new char[]{'='}, 2);
                        masked_pan = parts[0];
                    }
                    if (auto_state_change) {
                        WriteMessageToDevice(GetCardType());
                        Thread.Sleep(2000);
                        char fs = (char)0x1c;
                        string buttons = "Bbtna,S"+fs+"Bbtnb,S"+fs+"Bbtnc,S"+fs+"Bbtnd,S";
                        WriteMessageToDevice(UpdateScreenMessage(buttons));
                    }
                } else {
                    WriteMessageToDevice(SwipeCardScreen());
                }
                break;

                case 24:
                    // response from a form message
                    // should normally mean "card type selected"
                    // if the buffer is undersized or the status byte
                    // is not ASCII zero, go back the beginning
                    // otherwise see which type was selected
                    if (buffer.Length < 6 || buffer[4] != 0x30 || buffer[5] == 0x1b) {
                        WriteMessageToDevice(SwipeCardScreen());
                        PushOutput("TERMCLEARALL");
                    } else if (buffer[5] == 0x41) {
                        PushOutput("TERM:Debit");
                        if (auto_state_change) {
                            WriteMessageToDevice(PinEntryScreen());
                        }
                    } else if (buffer[5] == 0x42) {
                        PushOutput("TERM:Credit");
                        if (auto_state_change) {
                            WriteMessageToDevice(TermWaitScreen());
                        }
                    } else if (buffer[5] == 0x43) {
                        PushOutput("TERM:EbtCash");
                        if (auto_state_change) {
                            WriteMessageToDevice(PinEntryScreen());
                        }
                    } else if (buffer[5] == 0x44) {
                        PushOutput("TERM:EbtFood");
                        if (auto_state_change) {
                            WriteMessageToDevice(PinEntryScreen());
                        }
                    }
                    break;

                case 29:    
                    // get variable response from device
                    status = buffer[4] - 0x30;
                    int var_code = 0;
                    for (int i=0;i<6;i++) {
                        var_code = var_code*10 + (buffer[i+6]-0x30);
                    }
                    if (var_code == 712) {
                        ParseSigLengthMessage(status,buffer);
                    } else if (var_code >= 700 && var_code <= 709) {
                        ParseSigBlockMessage(status,buffer);
                    }
                    break;

                case 31:
                    // PIN entry response
                    if (buffer.Length < 5 || buffer[4] != 0x30) {
                        // problem; start over
                        WriteMessageToDevice(SwipeCardScreen());
                        PushOutput("TERMCLEARALL");
                    } else {
                        string pin_msg = enc.GetString(buffer);
                        // trim STX, command prefix, status byte, ETX, and LRC
                        pin_msg = pin_msg.Substring(5, pin_msg.Length - 7);
                        PushOutput("PINCACHE:" + pin_msg);
                        if (auto_state_change) {
                            WriteMessageToDevice(TermWaitScreen());
                        }
                    }
                    break;

                case 50:    
                    // auth request from device
                    ParseAuthMessage(buffer);
                    break;
            }
    }

    /**
        Write a message to the device
    */
    public virtual void WriteMessageToDevice(byte[] msg)
    {
    }

    override public void HandleMsg(String msg)
    {
        // optional predicate for "termSig" message
        // predicate string is displayed on sig capture screen
        if (msg.Length > 7 && msg.Substring(0, 7) == "termSig") {
            //string sig_message = msg.Substring(7);
            msg = "termSig";
        }

        if (msg == "termReset" || msg == "termReboot") {
            last_message = null;
            getting_signature = false;
            WriteMessageToDevice(HardResetMessage());
            WriteMessageToDevice(SwipeCardScreen());
            WriteMessageToDevice(SaveStateMessage());

            if (this.verbose_mode > 0) {
                System.Console.WriteLine("Sent reset");
            }

        } else if (!getting_signature && msg == "termSig") {
            WriteMessageToDevice(SigRequestMessage());
            getting_signature = true;    
            last_message = null;
            WriteMessageToDevice(StatusRequestMessage());
        } else if (!auto_state_change && !getting_signature && (msg == "termGetType" || msg == "termGetTypeWithFS")) {
            WriteMessageToDevice(GetCardType());
            Thread.Sleep(2000);
            char fs = (char)0x1c;
            string buttons = "Bbtna,S"+fs+"Bbtnb,S"+fs+"Bbtnc,S"+fs+"Bbtnd,S";
            WriteMessageToDevice(UpdateScreenMessage(buttons));
        } else if (!auto_state_change && !getting_signature && msg == "termWait") {
            WriteMessageToDevice(TermWaitScreen());
        } else if (!auto_state_change && !getting_signature && msg == "termApproved") {
            WriteMessageToDevice(TermApprovedScreen());
        } else if (!auto_state_change && !getting_signature && msg == "termGetPin") {
            WriteMessageToDevice(PinEntryScreen());
        } else if (msg == "termReConfig") {
            WriteMessageToDevice(OfflineMessage());
            // enable ebt cash
            WriteMessageToDevice(WriteConfigMessage("11", "3", EBT_CA));
            // enable ebt food
            WriteMessageToDevice(WriteConfigMessage("11", "4", EBT_FS));
            // mute beep volume
            WriteMessageToDevice(WriteConfigMessage("7", "14", "5"));
            // new style save/restore state
            WriteMessageToDevice(WriteConfigMessage("7", "15", "1"));
            // do not show messages between screens
            WriteMessageToDevice(WriteConfigMessage("7", "1", "0"));
            // send reset reply
            WriteMessageToDevice(WriteConfigMessage("7", "9", "1"));
            WriteMessageToDevice(OnlineMessage());
        }

        if (this.verbose_mode > 0) {
            System.Console.WriteLine(msg);
        }
    }

    /***********************************************
     * Messages
    ***********************************************/

    protected byte[] OnlineMessage()
    {
        byte[] msg = new byte[13];
        msg[0] = 0x2; // STX

        msg[1] = 0x30; // Online Code
        msg[2] = 0x31;
        msg[3] = 0x2e;

        /*
        msg[4] = (application_id >> 24) & 0xff;
        msg[5] = (application_id >> 16) & 0xff;
        msg[6] = (application_id >> 8) & 0xff;
        msg[7] = application_id & 0xff;

        msg[8] = (parameter_id >> 24) & 0xff;
        msg[9] = (parameter_id >> 16) & 0xff;
        msg[10] = (parameter_id >> 8) & 0xff;
        msg[11] = parameter_id & 0xff;
        */
        msg[4] = 0x30;
        msg[5] = 0x30;
        msg[6] = 0x30;
        msg[7] = 0x30;
        msg[8] = 0x30;
        msg[9] = 0x30;
        msg[10] = 0x30;
        msg[11] = 0x30;

        msg[12] = 0x3; // ETX

        return msg;
    }

    protected byte[] OfflineMessage()
    {
        byte[] msg = new byte[9];
        msg[0] = 0x2; // STX

        msg[1] = 0x30; // Offline Code
        msg[2] = 0x30;
        msg[3] = 0x2e;

        msg[4] = 0x30;
        msg[5] = 0x30;
        msg[6] = 0x30;
        msg[7] = 0x30;

        msg[8] = 0x3; // ETX

        return msg;
    }

    protected byte[] HardResetMessage()
    {
        byte[] msg = new byte[5];
        msg[0] = 0x2; // STX

        msg[1] = 0x31; // Reset Code
        msg[2] = 0x30;
        msg[3] = 0x2e;

        msg[4] = 0x3; // ETX
        
        return msg;
    }

    protected byte[] RebootMessage()
    {
        byte[] msg = new byte[5];
        msg[0] = 0x2; // STX

        msg[1] = 0x39; // Reset Code
        msg[2] = 0x37;
        msg[3] = 0x2e;

        msg[4] = 0x3; // ETX
        
        return msg;
    }

    protected byte[] ScreenLinesReset()
    {
        byte[] msg = new byte[6];
        msg[0] = 0x2; // STX

        msg[1] = 0x31; // Reset Code
        msg[2] = 0x35;
        msg[3] = 0x2e;

        msg[4] = 0x38;

        msg[5] = 0x3; // ETX
        
        return msg;
    }


    protected byte[] StatusRequestMessage()
    {
        byte[] msg = new byte[5];
        msg[0] = 0x2; // STX

        msg[1] = 0x31; // Status Code
        msg[2] = 0x31;
        msg[3] = 0x2e;

        msg[4] = 0x3; // ETX
        
        return msg;
    }

    // valid ptypes: 1 through 5 -OR- A through P
    /**
      Not used in current implementation. Commented to
      reduce compilation warnings.
      29Dec2014
    protected byte[] SetPaymentTypeMessage(string ptype)
    {
        byte[] p = new System.Text.ASCIIEncoding().GetBytes(ptype);
        byte[] msg = new byte[7];
        msg[0] = 0x2; // STX

        msg[1] = 0x30; // Set Payment Code
        msg[2] = 0x34;
        msg[3] = 0x2e;

        msg[4] = 0x30; // unconditional force
        msg[5] = p[0];
        
        msg[6] = 0x3; // ETX
        
        return msg;
    }
    */

    // amount format: 5.99 = 599 (just like POS input)
    /**
      Not used in current implementation. Commented to
      reduce compilation warnings.
      29Dec2014
    protected byte[] AmountMessage(string amt)
    {
        byte[] a = new System.Text.ASCIIEncoding().GetBytes(amt);
        byte[] msg = new byte[4 + a.Length + 1];
        msg[0] = 0x2; // STX

        msg[1] = 0x31; // Amount Code
        msg[2] = 0x33;
        msg[3] = 0x2e;

        for(int i=0; i < a.Length; i++)
            msg[i+4] = a[i];

        msg[msg.Length-1] = 0x3; // ETX

        return msg;
    }
    */

    protected byte[] SigRequestMessage()
    {
        string display = "Please sign";
        byte[] m = new System.Text.ASCIIEncoding().GetBytes(display);
        byte[] msg = new byte[4 + m.Length + 1]; 

        msg[0] = 0x2; // STX

        msg[1] = 0x32; // Sig Request Code
        msg[2] = 0x30;
        msg[3] = 0x2e;

        for (int i=0; i < m.Length; i++) {
            msg[i+4] = m[i];
        }

        msg[msg.Length-1] = 0x3; // ETX

        return msg;
    }

    // var_code should be length 6
    protected byte[] GetVariableMessage(string var_code)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

        byte[] msg = new byte[13];

        msg[0] = 0x2; // STX
        msg[1] = 0x32; // get var code
        msg[2] = 0x39;
        msg[3] = 0x2e;

        msg[4] = 0x31; // documentation says this should be ASII 00,
        msg[5] = 0x30; // but example says it should be ASII 10

        byte[] tmp = enc.GetBytes(var_code);
        for (int i=0; i<tmp.Length;i++) {
            msg[i+6] = tmp[i];
        }

        msg[12] = 0x3; // ETX

        if (this.verbose_mode > 0) {
            System.Console.WriteLine("Sent: "+enc.GetString(msg));
        }

        return msg;
    }

    // again var_code should have length 6
    /**
      Not used in current implementation. Commented to
      reduce compilation warnings.
      29Dec2014
    */
    protected byte[] SetVariableMessage(string var_code, string var_value)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();

        byte[] valbytes = enc.GetBytes(var_value);
        byte[] msg = new byte[12 + valbytes.Length + 1]; 

        msg[0] = 0x2; // STX
        msg[1] = 0x32; // set var code
        msg[2] = 0x38;
        msg[3] = 0x2e;

        msg[4] = 0x39; // no response
        msg[5] = 0x31; // constant

        byte[] tmp = enc.GetBytes(var_code);
        for(int i=0; i<tmp.Length;i++)
            msg[i+6] = tmp[i];

        for(int i=0; i<valbytes.Length;i++)
            msg[i+12] = valbytes[i];

        msg[msg.Length-1] = 0x3; //ETX

        if (this.verbose_mode > 0)
            System.Console.WriteLine("Sent: "+enc.GetString(msg));

        return msg;
    }

    /**
      Not used in current implementation. Commented to
      reduce compilation warnings.
      29Dec2014
    protected byte[] AuthMessage(string approval_code)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        string display_message = "Thank You!";

        byte[] msg = new byte[31 + display_message.Length + 2];

        msg[0] = 0x2; // STX
        msg[1] = 0x35; // Auth Code
        msg[2] = 0x30;
        msg[3] = 0x2e;

        byte[] tmp = enc.GetBytes(terminal_serial_number);
        for(int i=0; i<8; i++)
            msg[i+4] = tmp[i];

        msg[12] = 0x0;

        tmp = enc.GetBytes(pos_trans_no);
        for(int i=0; i<4; i++)
            msg[i+13] = tmp[i];

        if(approval_code == "denied"){
            msg[17] = 0x45;
            msg[18] = 0x3f;
        }
        else{
            msg[17] = 0x41;
            msg[18] = 0x3f;
        }

        tmp = enc.GetBytes(approval_code);
        for(int i=0;i<6;i++)
            msg[i+19] = tmp[i];

        string today = String.Format("(0:yyMMdd)",DateTime.Today);
        tmp = enc.GetBytes(today);
        for(int i=0;i<4;i++)
            msg[i+25] = tmp[i];

        tmp = enc.GetBytes(display_message);
        for(int i=0;i<tmp.Length;i++)
            msg[i+31] = tmp[i];

        msg[msg.Length-2] = 0x1c; // ASCII FS delimiter

        msg[msg.Length-1] = 0x3; // ETX

        return msg;
    }
    */

    // write DFS configuration values
    /**
      Not used in current implementation. Commented to
      reduce compilation warnings.
      29Dec2014
    */
    protected byte[] WriteConfigMessage(string group_num, string index_num, string val)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] gb = enc.GetBytes(group_num);
        byte[] ib = enc.GetBytes(index_num);
        byte[] vb = enc.GetBytes(val);

        byte[] msg = new byte[4 + gb.Length + ib.Length + vb.Length + 4];

        msg[0] = 0x2; // STX
        msg[1] = 0x36; // Write Code
        msg[2] = 0x30;
        msg[3] = 0x2e;

        int pos = 4;

        // write group
        for(int i=0; i<gb.Length; i++)
            msg[pos++] = gb[i];

        msg[pos++] = 0x1d; // ASII GS delimiter

        // write index
        for(int i=0; i<ib.Length; i++)
            msg[pos++] = ib[i];

        // write value
        msg[pos++] = 0x1d; // ASII GS delimiter

        for(int i=0; i<vb.Length; i++)
            msg[pos++] = vb[i];

        msg[msg.Length-2] = 0x1c; // ASCII FS delimiter

        msg[msg.Length-1] = 0x3; // ETX
        return msg;
    }

    protected byte[] ReadConfigMessage(string group_num, string index_num)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] gb = enc.GetBytes(group_num);
        byte[] ib = enc.GetBytes(index_num);

        byte[] msg = new byte[4 + gb.Length + ib.Length + 3];

        msg[0] = 0x2; // STX
        msg[1] = 0x36; // Write Code
        msg[2] = 0x31;
        msg[3] = 0x2e;

        int pos = 4;
        // group number
        for (int i=0; i<gb.Length; i++) {
            msg[pos++] = gb[i];
        }

        msg[pos++] = 0x1d; // ASII GS delimiter

        // index number
        for (int i=0; i<ib.Length; i++) {
            msg[pos++] = ib[i];
        }

        msg[pos++] = 0x1d; // ASII GS delimiter
        msg[msg.Length-1] = 0x3; // ETX

        return msg;
    }

    protected byte[] SwipeCardScreen()
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] prompt = enc.GetBytes("Swipe Card");
        byte[] msg = new byte[5 + prompt.Length];

        msg[0] = 0x2;
        msg[1] = 0x38;
        msg[2] = 0x37;
        msg[3] = 0x2e;
        int pos = 4;
        foreach (byte b in prompt) {
            msg[pos] = b;
            pos++;
        }
        msg[pos] = 0x3;

        return msg;
    }

    protected byte[] GetCashBack()
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] form_name = enc.GetBytes("cashb.K3Z");
        byte[] msg = new byte[79];

        msg[0] = 0x2;
        msg[1] = 0x32;
        msg[2] = 0x34;
        msg[3] = 0x2e;

        int pos = 4;
        foreach (byte b in form_name) {
            msg[pos] = b;
            pos++;
        }
        msg[pos] = 0x1c;
        pos++;

        byte[] next = enc.GetBytes("BA,S");
        foreach (byte b in next) {
            msg[pos] = b;
            pos++;
        }
        msg[pos] = 0x1c;
        pos++;

        next = enc.GetBytes("Bbtn1,10.00");
        foreach (byte b in next) {
            msg[pos] = b;
            pos++;
        }
        msg[pos] = 0x1c;
        pos++;

        next = enc.GetBytes("Bbtn2,20.00");
        foreach (byte b in next) {
            msg[pos] = b;
            pos++;
        }
        msg[pos] = 0x1c;
        pos++;

        next = enc.GetBytes("Bbtn3,30.00");
        foreach (byte b in next) {
            msg[pos] = b;
            pos++;
        }
        msg[pos] = 0x1c;
        pos++;

        next = enc.GetBytes("Bbtn4,40.00");
        foreach (byte b in next) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x1c;
        pos++;

        next = enc.GetBytes("Bbtno,50.00");
        foreach (byte b in next) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x3;

        return msg;
    }

    /**
      Draw select-card-type screen on demand
    */
    protected byte[] GetCardType()
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] form_name = enc.GetBytes("pay1.K3Z");
        byte[] msg = new byte[6 + form_name.Length + 0];

        msg[0] = 0x2;
        msg[1] = 0x32;
        msg[2] = 0x34;
        msg[3] = 0x2e;

        int pos = 4;
        foreach (byte b in form_name) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x1c; // FS

        /*
        msg[pos+1] = 0x42;
        msg[pos+2] = 0x62;
        msg[pos+3] = 0x74;
        msg[pos+4] = 0x6e;
        msg[pos+5] = 0x61;
        msg[pos+6] = 0x2c;
        msg[pos+7] = 0x53;
        */

        msg[pos+1] = 0x3;

        return msg;
    }

    protected byte[] SimpleMessageScreen(string the_message)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        the_message = "Tpromptline1,"+the_message;
        byte[] text = enc.GetBytes(the_message);
        byte[] form = enc.GetBytes("msg.k3z");
        byte[] msg = new byte[6 + form.Length + text.Length];

        msg[0] = 0x2;
        msg[1] = 0x32;
        msg[2] = 0x34;
        msg[3] = 0x2e;

        int pos = 4;
        foreach (byte b in form) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x1c;
        pos++;

        foreach (byte b in text) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x3;

        return msg;
    }

    protected byte[] TermApprovedScreen()
    {
        return SimpleMessageScreen("Approved - Thank You");
    }

    protected byte[] TermWaitScreen()
    {
        return SimpleMessageScreen("Waiting for total");
    }

    protected byte[] PinEntryScreen()
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] pan = enc.GetBytes(masked_pan);
        byte[] form = enc.GetBytes("pin.K3Z");
        byte[] msg = new byte[10 + pan.Length + form.Length];

        msg[0] = 0x2;
        msg[1] = 0x33;
        msg[2] = 0x31;
        msg[3] = 0x2e;

        msg[4] = 0x44; // DUKPT, default settings
        msg[5] = 0x2a;

        msg[6] = 0x31;
        msg[7] = 0x1c; 

        int pos = 8;
        foreach (byte b in pan) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x1c; 
        pos++;
        
        foreach (byte b in form) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x3;

        System.Console.WriteLine("get pin for:" + masked_pan);

        return msg;
    }

    protected byte[] SaveStateMessage()
    {
        return new byte[6]{ 0x2, 0x33, 0x34, 0x2e, 0x53, 0x3 };
    }

    protected byte[] UpdateScreenMessage(string update)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] encode = enc.GetBytes(update);
        byte[] msg = new byte[5 + encode.Length];

        msg[0] = 0x2;
        msg[1] = 0x37;
        msg[2] = 0x30;
        msg[3] = 0x2e;

        int pos = 4;
        foreach (byte b in encode) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x3;

        return msg;
    }
    
    protected byte[] GetEmailAddress()
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        byte[] encode = enc.GetBytes("alphaNew.K3Z");

        byte[] msg = new byte[14 + encode.Length];

        msg[0] = 0x2;
        msg[1] = 0x32;
        msg[2] = 0x31;
        msg[3] = 0x2e;

        msg[4] = 0x30;

        msg[5] = 0x30;
        msg[6] = 0x31;

        msg[7] = 0x34;
        msg[8] = 0x30;

        msg[9] = 0x31;
        msg[10] = 0x1c;
        msg[11] = 0x31;
        msg[12] = 0x1c;

        int pos = 13;
        foreach (byte b in encode) {
            msg[pos] = b;
            pos++;
        }

        msg[pos] = 0x3;

        return msg;
    }

    protected void ParseSigLengthMessage(int status, byte[] msg)
    {
        if (status == 2) {
            int num_blocks = 0;
            int pos = 12;
            while (msg[pos] != 0x3) {
                num_blocks = (num_blocks*10) + (msg[pos]-0x30);
                pos++;
            }
            if (num_blocks == 0) {
                // should never happen, but just in case...
                WriteMessageToDevice(StatusRequestMessage());
            } else {
                sig_object = new Signature(num_blocks);
                WriteMessageToDevice(GetVariableMessage("000700"));
            }
        } else {
            // didn't get data; re-request
            WriteMessageToDevice(GetVariableMessage("000712"));
        }
    }

    protected void ParseSigBlockMessage(int status, byte[] msg)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        
        if (status == 2) {
            byte[] var_data = new byte[msg.Length-14];
            for (int i=0;i<var_data.Length;i++) {
                var_data[i] = msg[i+12];
            }
            sig_object.WriteBlock(msg[11]-0x30,var_data);
            msg[11]++; // move to next sig block
        }

        if (sig_object.SigFull()) {
            // signature capture complete
            string sigfile="";
            try {
                char sep = System.IO.Path.DirectorySeparatorChar;
                sigfile = sig_object.BuildImage(MAGELLAN_OUTPUT_DIR+sep+"tmp");
            } catch (Exception e) {
                if (this.verbose_mode > 0) {
                    System.Console.WriteLine(e);
                }
            }
            getting_signature = false;
            FileInfo fi = new FileInfo(sigfile);
            PushOutput("TERMBMP" + fi.Name);
            HandleMsg("termReset");
        } else {
            // get the next sig block or re-request the
            // current one
            string var_num = enc.GetString(new byte[6]
                    {msg[6],msg[7],msg[8],msg[9],msg[10],msg[11]});
            WriteMessageToDevice(GetVariableMessage(var_num));
        }
    }

    protected void ParseAuthMessage(byte[] msg)
    {
        System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
        
        // skipping 0 (stx), 1-3 (message #)
    
        /** don't need any of these values for anything
        string aquirer = enc.GetString(new byte[6]{msg[4],msg[5],msg[6],msg[7],msg[8],msg[9]});    

        string merch_id = enc.GetString(new byte[12]{msg[10],msg[11],msg[12],msg[13],
                            msg[14],msg[15],msg[16],msg[17],
                            msg[18],msg[19],msg[20],msg[21]});

        string store_id = enc.GetString(new byte[4]{msg[22],msg[23],msg[24],msg[25]});

        string pinpad_id = enc.GetString(new byte[4]{msg[26],msg[27],msg[28],msg[29]});

        string std_ind_class = enc.GetString(new byte[4]{msg[30],msg[31],msg[32],msg[33]});

        string country = enc.GetString(new byte[3]{msg[34],msg[35],msg[36]});

        string zipcode = enc.GetString(new byte[5]{msg[37],msg[38],msg[39],msg[40],msg[41]});

        string timezone = enc.GetString(new byte[3]{msg[42],msg[43],msg[44]});

        string trans_code = enc.GetString(new byte[2]{msg[45],msg[46]});

        terminal_serial_number = enc.GetString(new byte[8]{msg[47],msg[48],msg[49],msg[50],
                            msg[51],msg[52],msg[53],msg[54]});

        // skipping 55 (constant 0)
        
        pos_trans_no = enc.GetString(new byte[4]{msg[56],msg[57],msg[58],msg[59]});
        */

        // skipping 60 (constant @)

        int data_source = (int)msg[61];

        // variable length from here on, fields termed by 0x1c

        int pos = 62;
        while(msg[pos] != 0x1c) pos++;
        byte[] stripe_bytes = new byte[pos-62];
        Array.Copy(msg,62,stripe_bytes,0,pos-62);
        string stripe = enc.GetString(stripe_bytes);

        pos++; // sitting at the 0x1c;

        // read PIN info
        int pin_start = pos;
        while(msg[pos] != 0x1c) pos++;
        byte[] pin_bytes = new byte[pos-pin_start];
        Array.Copy(msg,pin_start,pin_bytes,0,pos-pin_start);
        string pin_enc_block = "";
        string pin_ksi = "";
        string pin_device_id = "";
        string pin_enc_counter = "";
        // M/S or DUKPT
        if (pin_bytes.Length == 23 || pin_bytes.Length == 43){
            byte[] block = new byte[16];
            Array.Copy(pin_bytes,7,block,0,16);
            pin_enc_block = enc.GetString(block);
        }
        // only DUKPT
        if (pin_bytes.Length == 43){
            byte[] ksi = new byte[6];
            Array.Copy(pin_bytes,27,ksi,0,6);
            pin_ksi = enc.GetString(ksi);

            byte[] did = new byte[5];
            Array.Copy(pin_bytes,33,did,0,5);
            pin_device_id = enc.GetString(did);

            byte[] ec = new byte[5];
            Array.Copy(pin_bytes,38,ec,0,5);
            pin_enc_counter = enc.GetString(ec);
        }
        
        pos++; // should be at next 0x1c;

        int amount = 0;
        while(msg[pos] != 0x1c){
            amount = (amount*10) + ((int)msg[pos] - 0x30);
            pos++;
        }

        if (data_source == 0x48 || data_source == 0x58){
            // track 1
            stripe = "%"+stripe+"?";
        }
        else if (data_source == 0x44 || data_source == 0x54){
            // track 2
            stripe = ";"+stripe+"?";    
        }
        stripe = "T"+amount+"?"+stripe;
        if (pin_bytes.Length == 23){
            stripe += "PM"+pin_enc_block+"?";
        }
        else if (pin_bytes.Length == 43){
            stripe += "PD"+pin_enc_block;
            stripe += ((char)0x1e)+pin_ksi;
            stripe += ((char)0x1e)+pin_device_id;
            stripe += ((char)0x1e)+pin_enc_counter;
            stripe += "?";
        }

        PushOutput(stripe);
        stripe = null;
    }

    private void PushOutput(string s)
    {
        parent.MsgSend(s);
    }
}

}