-Lots of refactoring

-Only start the card polling thread when the pinpad is shown
-Added an info button to all entrys in the CertificateDescriptionWindow that might not be clear to the user


git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@226 96b47cad-a561-4643-ad3b-153ac7d7599c
This commit is contained in:
oepen
2010-07-17 11:35:31 +00:00
parent f508f0debb
commit 04e3a84da3
2 changed files with 146 additions and 80 deletions

View File

@@ -7,16 +7,17 @@ try:
import pygtk import pygtk
pygtk.require("2.0") pygtk.require("2.0")
import gtk, gobject, glib, pango import gtk, gobject, glib, pango
except: except ImportError:
sys.exit(1) sys.exit(1)
try: try:
import pace import pace
except: except ImportError:
print >> sys.stderr, "Could not import pace module, please install pyPACE" print >> sys.stderr, "Could not import pace module, please install pyPACE"
sys.exit(1) sys.exit(1)
from pace_gui_globals import * from pace_gui_globals import AT_CHAT_STRINGS, STR_CARD_FOUND, STR_NO_CARD, IMAGES #FIXME:Fuckup
import pace_gui_globals as gui_globals
class MokoWindow(gtk.Window): class MokoWindow(gtk.Window):
""" """
@@ -101,6 +102,9 @@ class MokoWindow(gtk.Window):
self.hide() self.hide()
class CertificateDescriptionWindow(MokoWindow): class CertificateDescriptionWindow(MokoWindow):
"""This window is used to display information about the service provider,
extracted from the terminal certificate and the appendant certificate
description"""
def __init__(self, binDescription, binCert): def __init__(self, binDescription, binCert):
super(CertificateDescriptionWindow, self).__init__("Dienstanbieter", None) super(CertificateDescriptionWindow, self).__init__("Dienstanbieter", None)
@@ -208,8 +212,8 @@ class CVCWindow(MokoWindow):
chat = pace.cvc_get_chat(cvc) chat = pace.cvc_get_chat(cvc)
self.chat = pace.get_binary_chat(chat) self.chat = pace.get_binary_chat(chat)
self.rel_auth = [] self.rel_auth = []
for c in self.chat: for char in self.chat:
self.rel_auth.append(ord(c)) self.rel_auth.append(ord(char))
self.rel_auth_len = len(self.rel_auth) self.rel_auth_len = len(self.rel_auth)
#Extract the access rights from the CHAT and display them in the window #Extract the access rights from the CHAT and display them in the window
@@ -219,7 +223,7 @@ class CVCWindow(MokoWindow):
if (i % 8 == 0): if (i % 8 == 0):
j += 1 j += 1
if self.rel_auth[self.rel_auth_len - j] & (1 << (i % 8)): if self.rel_auth[self.rel_auth_len - j] & (1 << (i % 8)):
chk = customCheckButton(AT_CHAT_STRINGS[i], i, self.body) chk = customCheckButton(i, self.body)
self.access_rights.append(chk) self.access_rights.append(chk)
def btnForward_clicked(self, widget, data=None): def btnForward_clicked(self, widget, data=None):
@@ -238,11 +242,13 @@ class CVCWindow(MokoWindow):
self.hide() self.hide()
def __formatHexString(self, int_list): def __formatHexString(self, int_list):
"""Take a list of integers and convert it to a hex string, where the
individual bytes are seperated by a colon (e.g. "0f:00:00")"""
hex_str = "" hex_str = ""
for i in int_list: for i in int_list:
c = hex(i)[2:] byte = hex(i)[2:]
if len(c) == 1: c = "0" + c if len(byte) == 1: byte = "0" + byte
hex_str += c + ":" hex_str += byte + ":"
return hex_str[:-1] return hex_str[:-1]
@@ -251,26 +257,41 @@ class customCheckButton(object):
The main difference isthat the checkbox can be placed at the right The main difference isthat the checkbox can be placed at the right
side of the label and that we can store an index with the button""" side of the label and that we can store an index with the button"""
def __init__(self, label, index, vbox): def __init__(self, index, vbox):
self.idx = index
#Fetch name and (possibly empty) helptext
strings = AT_CHAT_STRINGS[index]
self.label = strings[0]
self.helptext = strings[1]
#Setup a label with the name of the access right #Setup a label with the name of the access right
self.lbl = gtk.Label(label) self.lbl = gtk.Label(self.label)
self.lbl.set_alignment(0.0, 0.5) self.lbl.set_alignment(0.0, 0.5)
self.lbl.set_padding(20, 0) self.lbl.set_padding(20, 0)
self.lbl.modify_font(pango.FontDescription("bold")) self.lbl.modify_font(pango.FontDescription("bold"))
#an image at the left side of the label #If we have a helptext, put an image at the left side of the label
self.img = gtk.Image() self.info_box = None
self.img.set_from_file(image_dir + "/info.png") if self.helptext is not None:
self.info_box = gtk.EventBox()
img = gtk.Image()
img.set_from_file(IMAGES["info"])
self.info_box.add(img)
self.info_box.connect("button_release_event", self._show_info)
#...and a checkbox on the right side of the label #...and a checkbox on the right side of the label
self.idx = index
self.chk = gtk.CheckButton("") self.chk = gtk.CheckButton("")
self.chk.set_active(True) self.chk.set_active(True)
self.chk.set_alignment(1.0, 0.5) self.chk.set_alignment(1.0, 0.5)
#Insert the label and the checkbox in the vbox and add a seperator #Insert the label and the checkbox in the vbox and add a seperator
hbox = gtk.HBox() hbox = gtk.HBox()
hbox.pack_start(self.img, False, False) if self.info_box is not None:
hbox.pack_start(self.info_box, False, False)
else: #pack empty label for spacing
spacer = gtk.Label("")
spacer.set_size_request(32, 32)
hbox.pack_start(spacer, False, False)
hbox.pack_start(self.lbl, True, True) hbox.pack_start(self.lbl, True, True)
hbox.pack_start(self.chk, False, False) hbox.pack_start(self.chk, False, False)
vbox.pack_start(gtk.HSeparator()) vbox.pack_start(gtk.HSeparator())
@@ -279,6 +300,11 @@ class customCheckButton(object):
def is_active(self): def is_active(self):
return self.chk.get_active() return self.chk.get_active()
def _show_info(self, widget=None, data=None):
"""Show a messagebox containing info about the access right in
question"""
print self.helptext
class PinpadGTK: class PinpadGTK:
"""This a simple GTK based GUI to enter a PIN/PUK/CAN""" """This a simple GTK based GUI to enter a PIN/PUK/CAN"""
@@ -297,23 +323,23 @@ class PinpadGTK:
self.secret_len = 10 self.secret_len = 10
else: else:
raise ValueError("Unknwon secret type: %s" % self.secret) raise ValueError("Unknwon secret type: %s" % self.secret)
gtk.main_quit() #gtk.main_quit()
self.chat = chat self.chat = chat
self.cert = cert self.cert = cert
#Set the Glade file #Set the Glade file
self.gladefile = glade_dir + "/pinpad.glade" self.gladefile = gui_globals.GLADE_FILE
self.builder = gtk.Builder() self.builder = gtk.Builder()
try: try:
self.builder.add_from_file(self.gladefile) self.builder.add_from_file(self.gladefile)
except glib.GError, e: except glib.GError, err:
#If we encounter an exception at startup, we display a popup with #If we encounter an exception at startup, we display a popup with
#the error message #the error message
popup = gtk.MessageDialog(None, gtk.DIALOG_MODAL, popup = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, e.message) gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, err.message)
popup.run() popup.run()
popup.destroy() popup.destroy()
raise e raise err
#Get the Main Window, and connect the "destroy" event #Get the Main Window, and connect the "destroy" event
#Create our dictionay and connect it #Create our dictionay and connect it
@@ -344,10 +370,11 @@ class PinpadGTK:
lbl_cardStatus = self.builder.get_object("lbl_cardStatus") lbl_cardStatus = self.builder.get_object("lbl_cardStatus")
#Fetch the status image #Fetch the status image
img_cardStatus = self.builder.get_object("img_cardStatus") img_cardStatus = self.builder.get_object("img_cardStatus")
img_cardStatus.set_from_file(image_dir + "/error.png") img_cardStatus.set_from_file(IMAGES["error"])
self.cardChecker = cardChecker(lbl_cardStatus, img_cardStatus, ePA_ATR) #We only start the thread for polling the card when the window
gobject.idle_add(self.cardChecker.start) #is shown
self.cardChecker = None
#Change the font for the buttons #Change the font for the buttons
#For this you have to retrieve the label of each button and change #For this you have to retrieve the label of each button and change
@@ -372,6 +399,11 @@ class PinpadGTK:
self.window.hide() self.window.hide()
def show(self): def show(self):
"""Start the polling thread, then show the window"""
if self.cardChecker is None:
self.cardChecker = cardChecker(lbl_cardStatus, img_cardStatus,
gui_globals.ePA_ATR)
gobject.idle_add(self.cardChecker.start)
self.window.show_all() self.window.show_all()
def set_chat(self, chat): def set_chat(self, chat):
@@ -379,6 +411,7 @@ class PinpadGTK:
def shutdown(self, widget): def shutdown(self, widget):
"""Stop the cardChecker thread before exiting the application""" """Stop the cardChecker thread before exiting the application"""
if self.cardChecker is not None:
self.cardChecker.stop() self.cardChecker.stop()
self.cardChecker.join() self.cardChecker.join()
gtk.main_quit() gtk.main_quit()
@@ -442,9 +475,9 @@ class PinpadGTK:
try: try:
#Stop polling the card while PACE is running #Stop polling the card while PACE is running
self.cardChecker.pause() self.cardChecker.pause()
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env_args, proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env_args,
close_fds=True) close_fds=True)
except OSError, e: except OSError:
popup = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | popup = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK, "pace-tool wurde nicht gefunden.") gtk.BUTTONS_OK, "pace-tool wurde nicht gefunden.")
@@ -455,7 +488,7 @@ class PinpadGTK:
#Show the animation to indicate that the program is not dead #Show the animation to indicate that the program is not dead
waiting = gtk.Window(gtk.WINDOW_POPUP) waiting = gtk.Window(gtk.WINDOW_POPUP)
animation = gtk.gdk.PixbufAnimation(image_dir + "/wait.gif") animation = gtk.gdk.PixbufAnimation(IMAGES["wait"])
img = gtk.Image() img = gtk.Image()
img.set_from_animation(animation) img.set_from_animation(animation)
waiting.add(img) waiting.add(img)
@@ -466,14 +499,14 @@ class PinpadGTK:
waiting.show_all() waiting.show_all()
#Try to keep the GUI responsive by taking care of the event queue #Try to keep the GUI responsive by taking care of the event queue
line = p.stdout.readline() line = proc.stdout.readline()
while line: while line:
while gtk.events_pending(): while gtk.events_pending():
gtk.main_iteration() gtk.main_iteration()
line = p.stdout.readline() line = proc.stdout.readline()
#Get the return value of the pace-tool process #Get the return value of the pace-tool process
ret = p.poll() ret = proc.poll()
waiting.destroy() waiting.destroy()
self.cardChecker.resume() self.cardChecker.resume()
@@ -514,10 +547,10 @@ class cardChecker(Thread):
self._paused = Event() self._paused = Event()
self.lbl = lbl self.lbl = lbl
self.img = img self.img = img
self.targetATR = atr self.target_atr = atr
self.intervall = intervall self.intervall = intervall
def __cardCheck(self): def _check_card(self):
""" """
Actually this method should make use of pyscard, but I couldn't make Actually this method should make use of pyscard, but I couldn't make
it run on the OpenMoko yet. Therefor we call opensc-tool instead, which it run on the OpenMoko yet. Therefor we call opensc-tool instead, which
@@ -527,12 +560,12 @@ class cardChecker(Thread):
stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
line = proc.stdout.readline().rstrip() line = proc.stdout.readline().rstrip()
if line == self.targetATR: if line == self.target_atr:
gobject.idle_add(self.lbl.set_label, STR_CARD_FOUND) gobject.idle_add(self.lbl.set_label, STR_CARD_FOUND)
gobject.idle_add(self.img.set_from_file, FILE_CARD_FOUND) gobject.idle_add(self.img.set_from_file, IMAGES["apply"])
else: else:
gobject.idle_add(self.lbl.set_label, STR_NO_CARD) gobject.idle_add(self.lbl.set_label, STR_NO_CARD)
gobject.idle_add(self.img.set_from_file, FILE_NO_CARD) gobject.idle_add(self.img.set_from_file, IMAGES["error"])
def run(self): def run(self):
"""Main loop: Poll the card if the thread is not paused or finished""" """Main loop: Poll the card if the thread is not paused or finished"""
@@ -541,7 +574,7 @@ class cardChecker(Thread):
return return
if self._paused.isSet(): if self._paused.isSet():
continue continue
self.__cardCheck() self._check_card()
self._finished.wait(self.intervall) self._finished.wait(self.intervall)
def stop(self): def stop(self):
@@ -555,5 +588,6 @@ class cardChecker(Thread):
if __name__ == "__main__": if __name__ == "__main__":
gobject.threads_init() gobject.threads_init()
CertificateDescriptionWindow(TEST_DESCRIPTION, TEST_CVC) CertificateDescriptionWindow(gui_globals.TEST_DESCRIPTION,
gui_globals.TEST_CVC)
gtk.main() gtk.main()

View File

@@ -5,6 +5,8 @@ version = "@VERSION@"
image_dir = "@prefix@/share/" + name image_dir = "@prefix@/share/" + name
glade_dir = "@prefix@/share/" + name glade_dir = "@prefix@/share/" + name
GLADE_FILE = glade_dir + "/pinpad.glade"
STR_NO_CARD = "<span fgcolor=\"red\" font=\"12\">Ausweis</span>" STR_NO_CARD = "<span fgcolor=\"red\" font=\"12\">Ausweis</span>"
STR_CARD_FOUND = "<span fgcolor=\"green\" font=\"12\">Ausweis</span>" STR_CARD_FOUND = "<span fgcolor=\"green\" font=\"12\">Ausweis</span>"
@@ -13,46 +15,76 @@ FILE_CARD_FOUND = image_dir + "/apply.png"
ePA_ATR = "3b:84:80:01:00:00:90:00:95" ePA_ATR = "3b:84:80:01:00:00:90:00:95"
AT_CHAT_STRINGS = [ #The following strings are used as help texts for eID access rights
"Altersverifikation", #Age Verification DOC_AGE_VERIFICATION = "foo"
u"Gemeindeschlüssel Verifikation", #Community ID Verification DOC_COMMUNITY_ID_VERIFICATION = "foo"
"Pseudonymfunktion", #Restrictied Identification DOC_RESTRICTED_ID = "foo"
"Priviligiertes Terminal", #Privileged Terminal DOC_PRIVILEGED_TERMINAL = "foo"
"Verwendung der CAN", #CAN allowed DOC_CAN_ALLOWED = "foo"
"PIN Managment", DOC_PIN_MANAGMENT = "foo"
"Zertifikat einspielen", #Install Certificate DOC_INSTALL_CERTIFICATE = "foo"
"Qualifiziertes Zertifikat einspielen", #Install Qualified Certificate DOC_INSTALL_QC = "foo"
"Dokumenttyp lesen", #Read DG 1 DOC_DOCUMENT_TYPE = "foo"
"Staat lesen", #Read DG 2 DOC_STATE = "foo"
"Ablaufdatum lesen", #Read DG 3 DOC_EXPIRY_DATE = "foo"
"Vorname lesen", #Read DG 4
"Nachname lesen", #Read DG 5 IMAGES = {
u"Künstlername lesen", #Read DG 6 "apply": image_dir + "/apply.png",
"Doktorgrad lesen", #Read DG 7 "error": image_dir + "/error.png",
"Geburtsdatum lesen", #Read DG 8 "info" : image_dir + "/info.png",
"Geburtsort lesen", #Read DG 9 "wait" : image_dir + "/wait.gif"
u"Ungültig", #Read DG 10 }
u"Ungültig", #Read DG 11
u"Ungültig", #Read DG 12 #Authentication Terminal CHAT
u"Ungültig", #Read DG 13 AT_CHAT_STRINGS = {
u"Ungültig", #Read DG 14 #Age Verification
u"Ungültig", #Read DG 15 0: ("Altersverifikation", DOC_AGE_VERIFICATION),
u"Ungültig", #Read DG 16 #Community ID Verification
"Adresse lesen", #Read DG 17 1: (u"Gemeindeschlüssel Verifikation", DOC_COMMUNITY_ID_VERIFICATION),
u"Gemeindeschlüssel lesen", #Read DG 18 #Restrictied Identification
u"Ungültig", #Read DG 19 2: ("Pseudonymfunktion", DOC_RESTRICTED_ID),
u"Ungültig", #Read DG 20 #Privileged Terminal
u"Ungültig", #Read DG 21 3: ("Priviligiertes Terminal", DOC_PRIVILEGED_TERMINAL),
u"Ungültig", #RFU #CAN allowed
u"Ungültig", #RFU 4: ("Verwendung der CAN", DOC_CAN_ALLOWED),
u"Ungültig", #RFU 5: ("PIN Managment", DOC_PIN_MANAGMENT),
u"Ungültig", #RFU #Install Certificate
u"Ungültig", #Write DG 21 6: ("Zertifikat einspielen", DOC_INSTALL_CERTIFICATE),
u"Ungültig", #Write DG 20 #Install Qualified Certificate
u"Ungültig", #Write DG 19 7: ("Qualifiziertes Zertifikat einspielen", DOC_INSTALL_QC),
u"Gemeindeschlüssel schreiben", #Write DG 18 #Read DG 1
u"Adresse schreiben", #Write DG 17" 8: ("Dokumenttyp", DOC_DOCUMENT_TYPE),
] #Read DG 2
9: ("Ausstellender Staat", DOC_STATE),
10: ("Ablaufdatum", DOC_EXPIRY_DATE), #Read DG 3
11: ("Vorname", None), #Read DG 4
12: ("Nachname", None), #Read DG 5
13: (u"Künstlername", None), #Read DG 6
14: ("Doktorgrad", None), #Read DG 7
15: ("Geburtsdatum", None), #Read DG 8
16: ("Geburtsort", None), #Read DG 9
17: (u"Ungültig", None), #Read DG 10
18: (u"Ungültig", None), #Read DG 11
19: (u"Ungültig", None),#Read DG 12
20: (u"Ungültig", None),#Read DG 13
21: (u"Ungültig", None),#Read DG 14
22: (u"Ungültig", None),#Read DG 15
23: (u"Ungültig", None),#Read DG 16
24: ("Adresse", None), #Read DG 17
25: (u"Gemeindeschlüssel lesen", None),#Read DG 18
26: (u"Ungültig", None),#Read DG 19
27: (u"Ungültig", None),#Read DG 20
28: (u"Ungültig", None),#Read DG 21
29: (u"Ungültig", None),#RFU
30: (u"Ungültig", None),#RFU
31: (u"Ungültig", None),#RFU
32: (u"Ungültig", None),#RFU
33: (u"Ungültig", None),#Write DG 21
34: (u"Ungültig", None),#Write DG 20
35: (u"Ungültig", None),#Write DG 19
36: (u"Gemeindeschlüssel schreiben", None),#Write DG 18
37: (u"Adresse schreiben", None)#Write DG 17"
}
TEST_CVC = "\x7F\x21\x82\x01\x41\x7F\x4E\x81\xFA\x5F\x29\x01\x00\x42\x0D\x5A\x5A\x44\x56\x43\x41\x41\x54\x41\x30\x30\x30\x33\x7F\x49\x4F\x06\x0A\x04\x00\x7F\x00\x07\x02\x02\x02\x02\x03\x86\x41\x04\x19\xD1\x75\x45\xD3\xFE\x0B\x34\x3E\x7E\xE2\xAE\x4E\x2B\xC9\x2D\x51\x35\x1C\xC1\x17\xA4\x7F\xA9\x51\x9A\xDB\x1E\x40\x5E\xE6\xB8\x12\x12\x80\xBC\xC2\xFF\xF0\x35\x7A\x19\x7D\xE7\x39\xA7\xFD\x2E\xF0\x22\x10\xEF\x34\x3C\xDB\xE7\x9E\xF9\x4B\x8E\x28\x59\x1B\xB9\x5F\x20\x0B\x5A\x5A\x44\x4B\x42\x32\x30\x30\x30\x30\x52\x7F\x4C\x12\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x02\x02\x53\x05\x00\x03\x01\xDF\x04\x5F\x25\x06\x01\x00\x00\x02\x01\x07\x5F\x24\x06\x01\x00\x00\x03\x03\x01\x65\x5E\x73\x2D\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x03\x01\x80\x20\x75\xE0\xC4\xAC\x36\xC2\x5A\x33\xAC\x0E\x9A\x75\xEB\x79\x2A\x72\xF3\x31\xA5\x1E\x28\x63\x4E\xCC\x2E\xD6\x2E\x54\xF3\xC6\x93\xDA\x73\x2D\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x03\x02\x80\x20\x18\x12\x65\x74\x49\xFC\xF1\xD3\xDA\xD8\x3D\x13\x14\x29\x17\x5C\x61\x8B\x21\xBA\xF0\xAF\x44\xAC\xE3\x8C\xB2\xC1\x2C\xEB\x2A\x56\x5F\x37\x40\x54\x0F\x85\x09\x12\xAB\xD3\x51\xF8\xF5\x56\x9B\x53\x4A\x5C\x8F\x64\x54\x5B\x51\xA7\x34\x70\xBE\x5A\xD2\x89\xC1\x9A\x5E\x13\x52\x53\xD3\xBB\x15\x52\x26\x21\x7B\x41\xE7\xF0\x68\xB3\x52\x3F\x3A\x63\x92\x22\xAF\x2B\x62\x8C\x39\x7D\x4F\xD4\x02\x1E\xDE\x00\xDC" TEST_CVC = "\x7F\x21\x82\x01\x41\x7F\x4E\x81\xFA\x5F\x29\x01\x00\x42\x0D\x5A\x5A\x44\x56\x43\x41\x41\x54\x41\x30\x30\x30\x33\x7F\x49\x4F\x06\x0A\x04\x00\x7F\x00\x07\x02\x02\x02\x02\x03\x86\x41\x04\x19\xD1\x75\x45\xD3\xFE\x0B\x34\x3E\x7E\xE2\xAE\x4E\x2B\xC9\x2D\x51\x35\x1C\xC1\x17\xA4\x7F\xA9\x51\x9A\xDB\x1E\x40\x5E\xE6\xB8\x12\x12\x80\xBC\xC2\xFF\xF0\x35\x7A\x19\x7D\xE7\x39\xA7\xFD\x2E\xF0\x22\x10\xEF\x34\x3C\xDB\xE7\x9E\xF9\x4B\x8E\x28\x59\x1B\xB9\x5F\x20\x0B\x5A\x5A\x44\x4B\x42\x32\x30\x30\x30\x30\x52\x7F\x4C\x12\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x02\x02\x53\x05\x00\x03\x01\xDF\x04\x5F\x25\x06\x01\x00\x00\x02\x01\x07\x5F\x24\x06\x01\x00\x00\x03\x03\x01\x65\x5E\x73\x2D\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x03\x01\x80\x20\x75\xE0\xC4\xAC\x36\xC2\x5A\x33\xAC\x0E\x9A\x75\xEB\x79\x2A\x72\xF3\x31\xA5\x1E\x28\x63\x4E\xCC\x2E\xD6\x2E\x54\xF3\xC6\x93\xDA\x73\x2D\x06\x09\x04\x00\x7F\x00\x07\x03\x01\x03\x02\x80\x20\x18\x12\x65\x74\x49\xFC\xF1\xD3\xDA\xD8\x3D\x13\x14\x29\x17\x5C\x61\x8B\x21\xBA\xF0\xAF\x44\xAC\xE3\x8C\xB2\xC1\x2C\xEB\x2A\x56\x5F\x37\x40\x54\x0F\x85\x09\x12\xAB\xD3\x51\xF8\xF5\x56\x9B\x53\x4A\x5C\x8F\x64\x54\x5B\x51\xA7\x34\x70\xBE\x5A\xD2\x89\xC1\x9A\x5E\x13\x52\x53\xD3\xBB\x15\x52\x26\x21\x7B\x41\xE7\xF0\x68\xB3\x52\x3F\x3A\x63\x92\x22\xAF\x2B\x62\x8C\x39\x7D\x4F\xD4\x02\x1E\xDE\x00\xDC"