#!/usr/local/bin/ruby
require "readline"
require "PCSC"
include Readline
include PCSC

keywords =  [ "connect", "disconnect", "list", "send", "help", "exit", "quit", "leave", "reset", "off", "eject" ]

help_text = <<"EOT"
PC/SC readline client, Toni Andjelkovic <toni@soth.at> 2001 - 2004
Module version: v#{PCSC_RUBY_VERSION}

connect rdr [proto] -   connect to reader number rdr (shown by \"list\")
                        with protocol "proto". proto is one of "T0", "T1",
                        "RAW" or "ANY", the default being "ANY".

send apdu [proto]   -   send an APDU in hex, spaces are allowed. You must
                        be connected to a reader before sending this.
                        proto is one of "T0", "T1" or "RAW", the default
                        being "T0".

disconnect [arg]    -   disconnect the current reader connection. arg
                        is one of "leave", "reset", "off" or "eject",
                        the default being "leave".

list                -   list available readers
help, ?             -   show this text
exit, quit          -   quit program

EOT

Readline.completion_case_fold   =   false
Readline.completion_proc        =   Proc.new {
    |str|
    retval = []

    keywords.each do |word|
        if word[0..(str.length - 1)] == str
            retval.push(word)
        end
    end

    if str == ""
        retval = keywords # return all
    end

    retval
}

begin
    ctx = Context.new(SCARD_SCOPE_SYSTEM)
rescue => exc
    puts "Fatal: Can't initialize PCSC Context: %s." % exc.message
    puts "Fatal: Exiting."
    exit(2)
end

# these are globals, sort of
handle = nil
connected = false

while line = readline("PCSC-ruby> ", true) do

    line.strip!

    case line

        when /^connect\s*(\w*)(\s*(\w*))?$/
            usage = "usage: connect reader [protocol]"
            rdrs = ctx.ListReaders
            reader = $1.to_i
            if reader.to_i < 1 or reader.to_i > rdrs.length
                puts usage
                next
            end
            case $3
                when "T0"
                    proto_const = SCARD_PROTOCOL_T0
                when "T1"
                    proto_const = SCARD_PROTOCOL_T1
                when "RAW"
                    proto_const = SCARD_PROTOCOL_RAW
                when nil, "", "ANY" # default to ANY
                    proto_const = SCARD_PROTOCOL_ANY
                else
                    puts "Argument error: Invalid protocol \"#{$3}\"."
                    next
            end
            begin
                handle = Handle.new
                handle.Connect(ctx, rdrs[reader - 1],
                    SCARD_SHARE_SHARED, proto_const)
            rescue => exc
                puts "Command failed: %s" % exc.message
                next
            end
            connected = true
            puts "Connected: \"#{rdrs[reader - 1]}\", Proto #{proto_const}"
            puts "Card ATR: %s" % hexify(handle.Status[3])



        when /^disconnect(\s*(\w*))?$/
            case $2
                when nil, "", "leave" # default to SCARD_LEAVE_CARD
                    mode = SCARD_LEAVE_CARD
                when "reset"
                    mode = SCARD_RESET_CARD
                when "off"
                    mode = SCARD_UNPOWER_CARD
                when "eject"
                    mode = SCARD_EJECT_CARD
                else
                    puts "Argument error: Invalid mode \"#{$2}\"."
                    next
            end
            handle.Disconnect(mode) if handle
            handle = nil
            connected = false
            puts "Disconnected."




        when /^send\s*(.*)(\s*(\w*))?$/
            usage = "usage: send apdu [protocol]"
            if !connected
                puts "Error: Please connect first."
                next
            end
            apdu = $1
            if apdu.length < 1
                puts usage
                next
            end
            case $3
                when nil, "", "T0" # default to T0
                    ioreq = SCARD_PCI_T0
                when "T1"
                    ioreq = SCARD_PCI_T1
                when "RAW"
                    ioreq = SCARD_PCI_RAW
                else
                    puts "Argument error: Invalid protocol \"#{proto}\"."
                    next
            end
            apdu.gsub!(/\s*/, "") # trim spaces
            sendbuf = "" # new buffer
            apdu.scan(/../).each do |hexnum|
                sendbuf += hexnum.hex.chr
            end
            #puts "Debug: Sending APDU %s (%u Bytes)" %
            #    [ sendbuf.inspect, sendbuf.length ]
            begin
                handle.BeginTransaction
                result = handle.Transmit(ioreq, sendbuf);
                puts "Result: %s" % hexify(result[0])
                handle.EndTransaction(SCARD_LEAVE_CARD)
            rescue => exc
                puts "Command failed: %s" % exc.message
                next
            end




        when "list"
            counter = 1
            ctx.ListReaders.each do |reader|
                puts "%d: %s" % [ counter, reader ]
                counter += 1
            end

        when "help", "?"
            puts help_text



        when "exit", "quit"
            handle.Disconnect(SCARD_LEAVE_CARD) if handle
            handle = nil
            ctx.ReleaseContext
            exit


        when ""
            # nix

    else
        puts "Command \"#{line}\" not found, try \"help\"."

    end

end

handle.Disconnect(SCARD_LEAVE_CARD) if handle
ctx.ReleaseContext


