Class: Tem::MultiProxy::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/tem_multi_proxy/manager.rb

Overview

Manages the mapping between live smart-cards and proxy processes.

This class is intended to live as a singleton instance, though that is not enforced. The instance does its work in a dedicated thread which spins inside the management_loop method. Information about the live smartcards is returned by the tem_ports method.

While the management thread is spinning, it actively maintains a one-to-one mapping between connected smart-cards and proxy processes, by spawning new processes when cards are connected, and killing processes when cards are disconnected.

Instance Method Summary collapse

Constructor Details

#initializeManager

Returns a new instance of Manager.



28
29
30
31
32
33
34
35
# File 'lib/tem_multi_proxy/manager.rb', line 28

def initialize
  @pcsc_context = Smartcard::PCSC::Context.new :system
  @proxy_pids = {}
  @proxy_ports = {}
  @free_ports = RBTree.new
  (9001...9050).each { |port| @free_ports[port] = true }
  @logger = Logger.new STDERR
end

Instance Method Details

#alloc_proxy_port(reader_name) ⇒ Object

Allocates a port for a TEM proxy.



74
75
76
77
78
79
# File 'lib/tem_multi_proxy/manager.rb', line 74

def alloc_proxy_port(reader_name)
  port = @free_ports.min.first
  @free_ports.delete port
  @proxy_ports[reader_name] = port
  port
end

#free_proxy_port(reader_name) ⇒ Object

Marks a TEM proxy port as free.



82
83
84
85
86
87
# File 'lib/tem_multi_proxy/manager.rb', line 82

def free_proxy_port(reader_name)
  port = @proxy_ports[reader_name]
  return unless port
  @proxy_ports.delete reader_name
  @free_ports[port] = true
end

#kill_all_proxiesObject

Kills all the proxy processes.



113
114
115
116
117
118
119
120
# File 'lib/tem_multi_proxy/manager.rb', line 113

def kill_all_proxies
  processes = Zerg::Support::Process.processes
  processes.each do |proc_info|
    next unless /tem_proxy/ =~ proc_info[:command_line]
    @logger.info "Mass-killing TEM proxy (pid #{proc_info[:pid]})"
    Zerg::Support::Process::kill_tree proc_info[:pid]
  end
end

#kill_proxy(reader_name) ⇒ Object

Kills the tem_proxy process for a reader.



105
106
107
108
109
110
# File 'lib/tem_multi_proxy/manager.rb', line 105

def kill_proxy(reader_name)
  if proxy_pid = @proxy_pids[reader_name]
    Zerg::Support::Process.kill_tree proxy_pid
  end
  proxy_died reader_name
end

#management_loopObject

Never-ending loop mananging TEM proxies for all the readers.



153
154
155
156
157
158
159
# File 'lib/tem_multi_proxy/manager.rb', line 153

def management_loop
  kill_all_proxies
  loop do
    sync_reader_proxies
    Kernel.sleep 1
  end
end

#poll_readersObject

Polls each smartcard reader to see if there’s a card present.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/tem_multi_proxy/manager.rb', line 43

def poll_readers
  begin
    readers = @pcsc_context.readers.map { |name| { :name => name }}
  rescue
    # Linux PC/SC raises an exception if no readers are connected.
    readers = []
  end        
  return readers if readers.empty?
  
  queries = Smartcard::PCSC::ReaderStateQueries.new readers.length
  readers.each_with_index do |reader, i|
    queries[i].reader_name = reader[:name]
    queries[i].current_state = :unaware
  end    
  @pcsc_context.wait_for_status_change queries, 100
  
  readers.each_with_index do |reader, i|
    reader[:atr] = queries[i].atr
    # event_state is buggy on pcsclite, using the ATR for card detection.
    if reader[:atr].length == 0
      reader[:card] = false
      reader[:atr] = nil
    else
      reader[:card] = true
    end
  end
  
  readers
end

#proxy_died(reader_name) ⇒ Object

Updates the internal status to reflect that a tem_proxy process died.



99
100
101
102
# File 'lib/tem_multi_proxy/manager.rb', line 99

def proxy_died(reader_name)
  free_proxy_port reader_name
  @proxy_pids.delete reader_name
end

#spawn_proxy_for_reader(reader) ⇒ Object

Launches a tem_proxy process connecting to a reader.



90
91
92
93
94
95
96
# File 'lib/tem_multi_proxy/manager.rb', line 90

def spawn_proxy_for_reader(reader)
  proxy_port = alloc_proxy_port reader[:name]
  proxy_pid = Zerg::Support::Process.spawn 'tem_proxy', [proxy_port.to_s],
      :env => {'SCARD_PORT' => reader[:name], 'DEBUG' => 'no'},
      :pgroup => true
  @proxy_pids[reader[:name]] = proxy_pid
end

#sync_reader_proxiesObject

Synchronizes the running tem_proxy processes with the list of readers.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/tem_multi_proxy/manager.rb', line 123

def sync_reader_proxies
  processes = Zerg::Support::Process.processes_by_id
  # Check for crashed tem_proxy processes.
  @proxy_pids.each do |reader_name, pid|
    proc_info = processes[pid]
    unless proc_info and /tem_proxy/ =~ proc_info[:command_line]
      @logger.warn "TEM proxy for #{reader_name} (pid #{pid}) died."
      proxy_died reader_name
    end
  end

  live_readers = poll_readers.select { |reader| reader[:card] }
  # Kill proxies whose readers aren't available.
  live_reader_names = Set.new live_readers.map { |reader| reader[:name] }
  @proxy_pids.keys.each do |reader_name|
    next if live_reader_names.include? reader_name
    @logger.info "Killing TEM proxy for #{reader_name}. " +
                 "(pid #{@proxy_pids[reader_name]})"
    kill_proxy reader_name
  end
  
  # Spawn proxies for readers without them.
  live_readers.each do |reader|
    next if @proxy_pids[reader[:name]]
    @logger.info "Spawning new TEM proxy for #{reader[:name]}."
    spawn_proxy_for_reader reader
  end
end

#tem_portsObject

Returns an array of ports that TEMs can listen to.



38
39
40
# File 'lib/tem_multi_proxy/manager.rb', line 38

def tem_ports
  @proxy_ports.values.sort
end