U0hBUkVEUk9QLlRSQURJTkcgJm1kYXNoOyBDT01QTEVURSBaSVAgQlVJTERFUiBTQ1JJUFQKCkkgY2FuJ3QgdHJhbnNtaXQgYmluYXJ5IHppcCBmaWxlcyB0aHJvdWdoIGNoYXQsIGJ1dCB0aGlzIHNjcmlwdCBnZW5lcmF0ZXMgdGhlIGVudGlyZSBwYWNrYWdlIGluIHNlY29uZHMuIENvcHkgaXQsIHJ1biBpdCwgZ2V0IHlvdXIgemlwLgoKLS0tCgpCVUlMRCBTQ1JJUFQgJm1kYXNoOyBTYXZlIGFzIGJ1aWxkX3NoYXJlZHJvcF92N18xLnNoCgpgYGBiYXNoCiMhL3Vzci9iaW4vZW52IGJhc2gKc2V0IC1ldW8gcGlwZWZhaWwKClJPT1Q9JnF1b3Q7c2hhcmVkcm9wLXRyYWRpbmctdjcuMSZxdW90OwpaSVA9JnF1b3Q7JHtST09UfS56aXAmcXVvdDsKCnJtIC1yZiAmcXVvdDskUk9PVCZxdW90OyAmcXVvdDskWklQJnF1b3Q7Cm1rZGlyIC1wICZxdW90OyRST09UJnF1b3Q7L3thcHAve3JvdXRlcyxsaWJ9LHByaXNtYSxwdWJsaWMsZXh0ZW5zaW9ucy9kZWxpdmVyeS1kYXRlL3NyYyxkb2NzLHNjcmlwdHN9Cgp3cml0ZWZpbGUoKSB7CiAgbG9jYWwgZmlsZT0mcXVvdDskMSZxdW90Ozsgc2hpZnQKICBta2RpciAtcCAmcXVvdDskKGRpcm5hbWUgJnF1b3Q7JGZpbGUmcXVvdDspJnF1b3Q7CiAgY2F0ICZndDsgJnF1b3Q7JGZpbGUmcXVvdDsKfQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgUk9PVCBGSUxFUwojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCndyaXRlZmlsZSAmcXVvdDskUk9PVC9SRUFETUUubWQmcXVvdDsgJmx0OyZsdDsnRU9GJwojIFNoYXJlZHJvcC5UcmFkaW5nICZtZGFzaDsgU2hvcGlmeSBNVlAgVjcuMQoKT2ZmbGluZS1maXJzdCBsb2dpc3RpY3MgbWFya2V0cGxhY2UgZm9yIHRoZSBHdXlhbmEmbmRhc2g7QnJhemlsIGNvcnJpZG9yLgpCdWlsdCBvbiBTaG9waWZ5IGVtYmVkZGVkIGFwcCAoUmVtaXgpICsgTW9uZ29EQiBBdGxhcyArIE5ldGxpZnkvRmx5LmlvLgoKIyMgTm9uLUN1c3RvZHkgTW9kZWwgJm1kYXNoOyBWNy4xIEFCU09MVVRFCi0gTm8gd2FsbGV0ICZtaWRkb3Q7IE5vIGVzY3JvdyAmbWlkZG90OyBObyBmdW5kIGN1c3RvZHkgJm1pZGRvdDsgTm8gcG9vbGVkIGZ1bmRzCi0gUDJQIGRpcmVjdDogUmlkZXIgJnJhcnI7IExpY2Vuc2VkIFBTUCAmcmFycjsgRHJpdmVyCi0gUElYIENoYXZlOiA3MTA4NjI4NzI0MCAoQnJhemlsKQotIE5vbi1QU1AgcGVyIEhhbmRib29rIFBhcnQgSVYgMTYKCiMjIFdoYXQncyBJbmNsdWRlZAotIEdMSUUgVjcuMCBsZWFkIGVuZ2luZSAoZHVhbC1lbnRpdHkgU0RUIC8gR1ktSFVHLCA2LXN0cmVhbSB3YXRlcmZhbGwsIGxlZ2FsIGZpcmV3YWxsKQotIFNoaXBtZW50IG1hbmFnZW1lbnQgKyBWSVAgZHJpdmVyIG9mZmxpbmUgY29udGFjdAotIEdZLUhVRyBEcml2aW5nIFNjaG9vbCBTaW11bGF0aW9uICg5IG1vZHVsZXMsIEdUVEkgc3RhbmRhcmRzKQotIEdhbWVwbGF5IEZsb3cgKDggc3RlcHMpCi0gUGF5bWVudCBWNy4xIE5vbi1DdXN0b2R5IFVJIChQMlAgaGFuZG9mZikKLSBTYWZldHkgcHJlZmVyZW5jZXMgKGdlbmRlci1iYXNlZCBkcml2ZXIgc2VsZWN0aW9uKQotIFNlcnZpY2Ugd29ya2VyIGZvciBvZmZsaW5lLWZpcnN0IGFkbWluCi0gQ2hlY2tvdXQgZXh0ZW5zaW9uIChkZWxpdmVyeSBkYXRlIHBpY2tlcikKCiMjIFF1aWNrIERlcGxveQpgYGBiYXNoCm5wbSBpbnN0YWxsCm5weCBwcmlzbWEgZ2VuZXJhdGUgJmFtcDsmYW1wOyBucHggcHJpc21hIGRiIHB1c2gKY3AgLmVudi5leGFtcGxlIC5lbnYgICAjIGZpbGwgdmFsdWVzCnNob3BpZnkgYXBwIGRldgpgYGAKCkZ1bGwgRGVwbG95bWVudAoKYGBgYmFzaApmbHkgbGF1bmNoIC0tbm8tZGVwbG95CmZseSBzZWNyZXRzIHNldCBTSE9QSUZZX0FQSV9LRVk9Li4uIFNIT1BJRllfQVBJX1NFQ1JFVD0uLi4gTU9OR09EQl9VUkk9Li4uCmZseSBkZXBsb3kKc2hvcGlmeSBhcHAgZGVwbG95CmBgYAoKTGVnYWwgRmlyZXdhbGwKCkV2ZXJ5IHBheW1lbnQgcmVjb3JkIE1VU1QgY2FsbCBhc3NlcnROb25DdXN0b2R5KCkgZnJvbSBhcHAvbGliL3BheW1lbnRzLnNlcnZlci50cy4KVmlvbGF0aW9ucyBhdXRvLWVzY2FsYXRlIHRvIGVvc2Jvcm5lLmxlZ2FsQGdtYWlsLmNvbS4KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvLmVudi5leGFtcGxlJnF1b3Q7ICZsdDsmbHQ7J0VPRicKClNob3BpZnkKClNIT1BJRllfQVBJX0tFWT0KU0hPUElGWV9BUElfU0VDUkVUPQpTSE9QSUZZX0FQUF9VUkw9aHR0cHM6Ly95Li4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi55LmRldgpTQ09QRVM9d3JpdGVfb3JkZXJzLHJlYWRfb3JkZXJzLHdyaXRlX2Z1bGZpbGxtZW50cyxyZWFkX2Z1bGZpbGxtZW50cyx3cml0ZV9zaGlwcGluZyxyZWFkX3NoaXBwaW5nLHJlYWRfcHJvZHVjdHMsd3JpdGVfcHJvZHVjdHMscmVhZF9jdXN0b21lcnMsd3JpdGVfY3VzdG9tZXJzClNIT1BfQ1VTVE9NX0RPTUFJTj0KCk1vbmdvREIgQXRsYXMKCk1PTkdPREJfVVJJPW1vbmdvZGIrc3J2Oi8vc2hhcmVkcm9wX2FkbWluOkNIQU5HRV9NRUBzaGFyZWRyb3AtY2x1c3Rlci5tb25nb2RiLm5ldC9zaGFyZWRyb3A/cmV0cnlXcml0ZXM9dHJ1ZSZhbXA7dz1tYWpvcml0eQoKTWFwcwoKTEVBRkxFVF9USUxFX1VSTD1odHRwczovL3tzfS50aWxlLm9wZW5zdHJlZXRtYXAub3JnL3t6fS97eH0ve3l9LnBuZwpNQVBTX1BST1ZJREVSPWxlYWZsZXRfb3NtCgpSZWFsLXRpbWUKClNPQ0tFVElPX0VOQUJMRUQ9dHJ1ZQpTT0NLRVRJT19QQVRIPS9zb2NrZXQuaW8KClBheW1lbnRzICZtZGFzaDsgVjcuMSBOb24tQ3VzdG9keQoKTk9OX1BTUF9NT0RFPXRydWUKUEFZTUVOVF9QUk9WSURFUl9HWT1iZV9wYXltZW50c19tb2NrClBBWU1FTlRfUFJPVklERVJfQlI9cGl4X21vY2sKUElYX0NIQVZFPTcxMDg2Mjg3MjQwClNUUklQRV9QVUJMSUNfS0VZPQpNTUdfTUVSQ0hBTlRfSUQ9CgpHWS1IVUcgRHJpdmluZyBTY2hvb2wKCkdZSFVHX1JVTEVTX0dZPWd1aWFuYV9ydWxlcy5qc29uCkdZSFVHX1JVTEVTX0JSPWJyYXppbF9ydWxlcy5qc29uCkdUVElfU1RBTkRBUkRfVkVSU0lPTj0yMDI0CgpOb3RpZmljYXRpb25zCgpGQ01fU0VSVkVSX0tFWT0KT05FU0lHTkFMX0FQUF9JRD0KCkxlZ2FsCgpMRUdBTF9DT1VOU0VMX0VNQUlMPWVvc2Jvcm5lLmxlZ2FsQGdtYWlsLmNvbQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC8uZ2l0aWdub3JlJnF1b3Q7ICZsdDsmbHQ7J0VPRicKbm9kZV9tb2R1bGVzLwouZW52CmRpc3QvCmJ1aWxkLwoudnNjb2RlLwouRFNfU3RvcmUKKi56aXAKZGV2LnNxbGl0ZQpkZXYuc3FsaXRlLWpvdXJuYWwKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvcGFja2FnZS5qc29uJnF1b3Q7ICZsdDsmbHQ7J0VPRicKewomcXVvdDtuYW1lJnF1b3Q7OiAmcXVvdDtzaGFyZWRyb3AtdHJhZGluZyZxdW90OywKJnF1b3Q7cHJpdmF0ZSZxdW90OzogdHJ1ZSwKJnF1b3Q7dmVyc2lvbiZxdW90OzogJnF1b3Q7Ny4xLjAmcXVvdDssCiZxdW90O3NjcmlwdHMmcXVvdDs6IHsKJnF1b3Q7YnVpbGQmcXVvdDs6ICZxdW90O3JlbWl4IGJ1aWxkJnF1b3Q7LAomcXVvdDtkZXYmcXVvdDs6ICZxdW90O3Nob3BpZnkgYXBwIGRldiZxdW90OywKJnF1b3Q7ZGVwbG95JnF1b3Q7OiAmcXVvdDtzaG9waWZ5IGFwcCBkZXBsb3kmcXVvdDssCiZxdW90O3N0YXJ0JnF1b3Q7OiAmcXVvdDtyZW1peC1zZXJ2ZSBidWlsZCZxdW90OywKJnF1b3Q7c2V0dXAmcXVvdDs6ICZxdW90O3ByaXNtYSBnZW5lcmF0ZSAmYW1wOyZhbXA7IHByaXNtYSBkYiBwdXNoJnF1b3Q7Cn0sCiZxdW90O2RlcGVuZGVuY2llcyZxdW90OzogewomcXVvdDtAcHJpc21hL2NsaWVudCZxdW90OzogJnF1b3Q7XjUuMjAuMCZxdW90OywKJnF1b3Q7QHJlbWl4LXJ1bi9ub2RlJnF1b3Q7OiAmcXVvdDteMi4xNS4wJnF1b3Q7LAomcXVvdDtAcmVtaXgtcnVuL3JlYWN0JnF1b3Q7OiAmcXVvdDteMi4xNS4wJnF1b3Q7LAomcXVvdDtAcmVtaXgtcnVuL3NlcnZlJnF1b3Q7OiAmcXVvdDteMi4xNS4wJnF1b3Q7LAomcXVvdDtAc2hvcGlmeS9hcHAtYnJpZGdlLXJlYWN0JnF1b3Q7OiAmcXVvdDteNC4xLjYmcXVvdDssCiZxdW90O0BzaG9waWZ5L3BvbGFyaXMmcXVvdDs6ICZxdW90O14xMy45LjAmcXVvdDssCiZxdW90O0BzaG9waWZ5L3Nob3BpZnktYXBwLXJlbWl4JnF1b3Q7OiAmcXVvdDteMy43LjAmcXVvdDssCiZxdW90O0BzaG9waWZ5L3Nob3BpZnktYXBwLXNlc3Npb24tc3RvcmFnZS1wcmlzbWEmcXVvdDs6ICZxdW90O141LjEuNSZxdW90OywKJnF1b3Q7ZGF0ZS1mbnMmcXVvdDs6ICZxdW90O14yLjMwLjAmcXVvdDssCiZxdW90O21vbmdvZGImcXVvdDs6ICZxdW90O142LjEwLjAmcXVvdDssCiZxdW90O3V1aWQmcXVvdDs6ICZxdW90O14xMC4wLjAmcXVvdDsKfSwKJnF1b3Q7ZGV2RGVwZW5kZW5jaWVzJnF1b3Q7OiB7CiZxdW90O0ByZW1peC1ydW4vZGV2JnF1b3Q7OiAmcXVvdDteMi4xNS4wJnF1b3Q7LAomcXVvdDtAdHlwZXMvbm9kZSZxdW90OzogJnF1b3Q7XjIyLjkuMCZxdW90OywKJnF1b3Q7QHR5cGVzL3V1aWQmcXVvdDs6ICZxdW90O14xMC4wLjAmcXVvdDssCiZxdW90O3ByaXNtYSZxdW90OzogJnF1b3Q7XjUuMjAuMCZxdW90OywKJnF1b3Q7dHlwZXNjcmlwdCZxdW90OzogJnF1b3Q7XjUuNi4zJnF1b3Q7LAomcXVvdDt2aXRlJnF1b3Q7OiAmcXVvdDteNS40LjExJnF1b3Q7Cn0sCiZxdW90O2VuZ2luZXMmcXVvdDs6IHsgJnF1b3Q7bm9kZSZxdW90OzogJnF1b3Q7Jmd0Oz0yMC4xMCZxdW90OyB9Cn0KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1Qvc2hvcGlmeS5hcHAudG9tbCZxdW90OyAmbHQ7Jmx0OydFT0YnCmNsaWVudF9pZCA9ICZxdW90O1lPVVJfU0hPUElGWV9DTElFTlRfSUQmcXVvdDsKbmFtZSA9ICZxdW90O1NoYXJlZHJvcCBUcmFkaW5nJnF1b3Q7CmhhbmRsZSA9ICZxdW90O3NoYXJlZHJvcC10cmFkaW5nJnF1b3Q7CmFwcGxpY2F0aW9uX3VybCA9ICZxdW90O2h0dHBzOi8veS4uLmNvbnRlbnQtYXZhaWxhYmxlLXRvLWF1dGhvci1vbmx5Li4ueS5kZXYmcXVvdDsKZW1iZWRkZWQgPSB0cnVlCgpbYWNjZXNzX3Njb3Blc10Kc2NvcGVzID0gJnF1b3Q7d3JpdGVfb3JkZXJzLHJlYWRfb3JkZXJzLHdyaXRlX2Z1bGZpbGxtZW50cyxyZWFkX2Z1bGZpbGxtZW50cyx3cml0ZV9zaGlwcGluZyxyZWFkX3NoaXBwaW5nLHJlYWRfcHJvZHVjdHMsd3JpdGVfcHJvZHVjdHMscmVhZF9jdXN0b21lcnMsd3JpdGVfY3VzdG9tZXJzLHJlYWRfbWV0YW9iamVjdHMsd3JpdGVfbWV0YW9iamVjdHMmcXVvdDsKClthdXRoXQpyZWRpcmVjdF91cmxzID0gWwomcXVvdDtodHRwczovL3kuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLnkuZGV2L2F1dGgvY2FsbGJhY2smcXVvdDssCiZxdW90O2h0dHBzOi8veS4uLmNvbnRlbnQtYXZhaWxhYmxlLXRvLWF1dGhvci1vbmx5Li4ueS5kZXYvYXV0aC9zaG9waWZ5L2NhbGxiYWNrJnF1b3Q7LAomcXVvdDtodHRwczovL3kuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLnkuZGV2L2FwaS9hdXRoL2NhbGxiYWNrJnF1b3Q7Cl0KClt3ZWJob29rc10KYXBpX3ZlcnNpb24gPSAmcXVvdDsyMDI2LTA0JnF1b3Q7Cltbd2ViaG9va3Muc3Vic2NyaXB0aW9uc11dCnRvcGljcyA9IFsgJnF1b3Q7b3JkZXJzL3BhaWQmcXVvdDssICZxdW90O29yZGVycy9mdWxmaWxsZWQmcXVvdDssICZxdW90O2Z1bGZpbGxtZW50cy9jcmVhdGUmcXVvdDssICZxdW90O2Z1bGZpbGxtZW50cy91cGRhdGUmcXVvdDssICZxdW90O2N1c3RvbWVycy9jcmVhdGUmcXVvdDsgXQp1cmkgPSAmcXVvdDsvd2ViaG9va3MmcXVvdDsKCltidWlsZF0KYXV0b21hdGljYWxseV91cGRhdGVfdXJsc19vbl9kZXYgPSB0cnVlCmRldl9zdG9yZV91cmwgPSAmcXVvdDt5b3VyLWRldi1zdG9yZS5teXNob3BpZnkuY29tJnF1b3Q7CkVPRgoKd3JpdGVmaWxlICZxdW90OyRST09UL0RvY2tlcmZpbGUmcXVvdDsgJmx0OyZsdDsnRU9GJwpGUk9NIG5vZGU6MjAtYWxwaW5lCldPUktESVIgL2FwcApDT1BZIHBhY2thZ2UqLmpzb24gLi8KUlVOIG5wbSBjaQpDT1BZIC4gLgpSVU4gbnB4IHByaXNtYSBnZW5lcmF0ZQpSVU4gbnBtIHJ1biBidWlsZApFWFBPU0UgMzAwMApDTUQgWyZxdW90O25wbSZxdW90OywgJnF1b3Q7cnVuJnF1b3Q7LCAmcXVvdDtzdGFydCZxdW90O10KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvZmx5LnRvbWwmcXVvdDsgJmx0OyZsdDsnRU9GJwphcHAgPSAmcXVvdDtzaGFyZWRyb3AtdHJhZGluZyZxdW90OwpwcmltYXJ5X3JlZ2lvbiA9ICZxdW90O21pYSZxdW90OwoKW2J1aWxkXQpkb2NrZXJmaWxlID0gJnF1b3Q7RG9ja2VyZmlsZSZxdW90OwoKW2Vudl0KUE9SVCA9ICZxdW90OzMwMDAmcXVvdDsKTk9ERV9FTlYgPSAmcXVvdDtwcm9kdWN0aW9uJnF1b3Q7CgpbaHR0cF9zZXJ2aWNlXQppbnRlcm5hbF9wb3J0ID0gMzAwMApmb3JjZV9odHRwcyA9IHRydWUKYXV0b19zdG9wX21hY2hpbmVzID0gJnF1b3Q7c3RvcCZxdW90OwphdXRvX3N0YXJ0X21hY2hpbmVzID0gdHJ1ZQptaW5fbWFjaGluZXNfcnVubmluZyA9IDEKCltbdm1dXQpzaXplID0gJnF1b3Q7c2hhcmVkLWNwdS0xeCZxdW90OwptZW1vcnkgPSAmcXVvdDs1MTJtYiZxdW90OwpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC90c2NvbmZpZy5qc29uJnF1b3Q7ICZsdDsmbHQ7J0VPRicKewomcXVvdDtpbmNsdWRlJnF1b3Q7OiBbJnF1b3Q7LyoudHMmcXVvdDssICZxdW90Oy8udHN4JnF1b3Q7XSwKJnF1b3Q7Y29tcGlsZXJPcHRpb25zJnF1b3Q7OiB7CiZxdW90O2xpYiZxdW90OzogWyZxdW90O0RPTSZxdW90OywgJnF1b3Q7RE9NLkl0ZXJhYmxlJnF1b3Q7LCAmcXVvdDtFUzIwMjImcXVvdDtdLAomcXVvdDt0eXBlcyZxdW90OzogWyZxdW90O0ByZW1peC1ydW4vbm9kZSZxdW90OywgJnF1b3Q7dml0ZS9jbGllbnQmcXVvdDtdLAomcXVvdDtpc29sYXRlZE1vZHVsZXMmcXVvdDs6IHRydWUsCiZxdW90O2VzTW9kdWxlSW50ZXJvcCZxdW90OzogdHJ1ZSwKJnF1b3Q7anN4JnF1b3Q7OiAmcXVvdDtyZWFjdC1qc3gmcXVvdDssCiZxdW90O21vZHVsZSZxdW90OzogJnF1b3Q7RVNOZXh0JnF1b3Q7LAomcXVvdDttb2R1bGVSZXNvbHV0aW9uJnF1b3Q7OiAmcXVvdDtCdW5kbGVyJnF1b3Q7LAomcXVvdDtyZXNvbHZlSnNvbk1vZHVsZSZxdW90OzogdHJ1ZSwKJnF1b3Q7dGFyZ2V0JnF1b3Q7OiAmcXVvdDtFUzIwMjImcXVvdDssCiZxdW90O3N0cmljdCZxdW90OzogdHJ1ZSwKJnF1b3Q7YWxsb3dKcyZxdW90OzogdHJ1ZSwKJnF1b3Q7c2tpcExpYkNoZWNrJnF1b3Q7OiB0cnVlLAomcXVvdDtmb3JjZUNvbnNpc3RlbnRDYXNpbmdJbkZpbGVOYW1lcyZxdW90OzogdHJ1ZSwKJnF1b3Q7YmFzZVVybCZxdW90OzogJnF1b3Q7LiZxdW90OywKJnF1b3Q7cGF0aHMmcXVvdDs6IHsgJnF1b3Q7fi8mcXVvdDs6IFsmcXVvdDsuL2FwcC8qJnF1b3Q7XSB9LAomcXVvdDtub0VtaXQmcXVvdDs6IHRydWUKfQp9CkVPRgoKPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKUFJJU01BCgo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvcHJpc21hL3NjaGVtYS5wcmlzbWEmcXVvdDsgJmx0OyZsdDsnRU9GJwpnZW5lcmF0b3IgY2xpZW50IHsgcHJvdmlkZXIgPSAmcXVvdDtwcmlzbWEtY2xpZW50LWpzJnF1b3Q7IH0KZGF0YXNvdXJjZSBkYiB7IHByb3ZpZGVyID0gJnF1b3Q7c3FsaXRlJnF1b3Q7OyB1cmwgPSAmcXVvdDtmaWxlOmRldi5zcWxpdGUmcXVvdDsgfQoKbW9kZWwgU2Vzc2lvbiB7CmlkICAgICAgICAgICAgU3RyaW5nICAgIEBpZApzaG9wICAgICAgICAgIFN0cmluZwpzdGF0ZSAgICAgICAgIFN0cmluZwppc09ubGluZSAgICAgIEJvb2xlYW4gICBAZGVmYXVsdChmYWxzZSkKc2NvcGUgICAgICAgICBTdHJpbmc/CmV4cGlyZXMgICAgICAgRGF0ZVRpbWU/CmFjY2Vzc1Rva2VuICAgU3RyaW5nCnVzZXJJZCAgICAgICAgQmlnSW50PwpmaXJzdE5hbWUgICAgIFN0cmluZz8KbGFzdE5hbWUgICAgICBTdHJpbmc/CmVtYWlsICAgICAgICAgU3RyaW5nPwphY2NvdW50T3duZXIgIEJvb2xlYW4gICBAZGVmYXVsdChmYWxzZSkKbG9jYWxlICAgICAgICBTdHJpbmc/CmNvbGxhYm9yYXRvciAgQm9vbGVhbj8gIEBkZWZhdWx0KGZhbHNlKQplbWFpbFZlcmlmaWVkIEJvb2xlYW4/ICBAZGVmYXVsdChmYWxzZSkKfQpFT0YKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCkFQUCAmbWRhc2g7IENPUkUKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvc2hvcGlmeS5zZXJ2ZXIudHMmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgJnF1b3Q7QHNob3BpZnkvc2hvcGlmeS1hcHAtcmVtaXgvYWRhcHRlcnMvbm9kZSZxdW90OzsKaW1wb3J0IHsgQXBpVmVyc2lvbiwgQXBwRGlzdHJpYnV0aW9uLCBzaG9waWZ5QXBwIH0gZnJvbSAmcXVvdDtAc2hvcGlmeS9zaG9waWZ5LWFwcC1yZW1peC9zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IFByaXNtYVNlc3Npb25TdG9yYWdlIH0gZnJvbSAmcXVvdDtAc2hvcGlmeS9zaG9waWZ5LWFwcC1zZXNzaW9uLXN0b3JhZ2UtcHJpc21hJnF1b3Q7OwppbXBvcnQgeyByZXN0UmVzb3VyY2VzIH0gZnJvbSAmcXVvdDtAc2hvcGlmeS9zaG9waWZ5LWFwaS9yZXN0L2FkbWluLzIwMjYtMDQmcXVvdDs7CmltcG9ydCBwcmlzbWEgZnJvbSAmcXVvdDsuL2RiLnNlcnZlciZxdW90OzsKCmNvbnN0IHNob3BpZnkgPSBzaG9waWZ5QXBwKHsKYXBpS2V5OiBwcm9jZXNzLmVudi5TSE9QSUZZX0FQSV9LRVksCmFwaVNlY3JldEtleTogcHJvY2Vzcy5lbnYuU0hPUElGWV9BUElfU0VDUkVUIHx8ICZxdW90OyZxdW90OywKYXBpVmVyc2lvbjogQXBpVmVyc2lvbi5BcHJpbDI2LApzY29wZXM6IHByb2Nlc3MuZW52LlNDT1BFUz8uc3BsaXQoJnF1b3Q7LCZxdW90OyksCmFwcFVybDogcHJvY2Vzcy5lbnYuU0hPUElGWV9BUFBfVVJMIHx8ICZxdW90OyZxdW90OywKYXV0aFBhdGhQcmVmaXg6ICZxdW90Oy9hdXRoJnF1b3Q7LApzZXNzaW9uU3RvcmFnZTogbmV3IFByaXNtYVNlc3Npb25TdG9yYWdlKHByaXNtYSksCmRpc3RyaWJ1dGlvbjogQXBwRGlzdHJpYnV0aW9uLkFwcFN0b3JlLApyZXN0UmVzb3VyY2VzLApmdXR1cmU6IHsgdW5zdGFibGVfbmV3RW1iZWRkZWRBdXRoU3RyYXRlZ3k6IHRydWUsIHJlbW92ZVJlc3Q6IGZhbHNlIH0sCn0pOwoKZXhwb3J0IGRlZmF1bHQgc2hvcGlmeTsKZXhwb3J0IGNvbnN0IGFwaVZlcnNpb24gPSBBcGlWZXJzaW9uLkFwcmlsMjY7CmV4cG9ydCBjb25zdCBhZGREb2N1bWVudFJlc3BvbnNlSGVhZGVycyA9IHNob3BpZnkuYWRkRG9jdW1lbnRSZXNwb25zZUhlYWRlcnM7CmV4cG9ydCBjb25zdCBhdXRoZW50aWNhdGUgPSBzaG9waWZ5LmF1dGhlbnRpY2F0ZTsKZXhwb3J0IGNvbnN0IHVuYXV0aGVudGljYXRlZCA9IHNob3BpZnkudW5hdXRoZW50aWNhdGVkOwpleHBvcnQgY29uc3QgbG9naW4gPSBzaG9waWZ5LmxvZ2luOwpleHBvcnQgY29uc3QgcmVnaXN0ZXJXZWJob29rcyA9IHNob3BpZnkucmVnaXN0ZXJXZWJob29rczsKZXhwb3J0IGNvbnN0IHNlc3Npb25TdG9yYWdlID0gc2hvcGlmeS5zZXNzaW9uU3RvcmFnZTsKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL2RiLnNlcnZlci50cyZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IFByaXNtYUNsaWVudCB9IGZyb20gJnF1b3Q7QHByaXNtYS9jbGllbnQmcXVvdDs7CmRlY2xhcmUgZ2xvYmFsIHsgdmFyIHByaXNtYTogUHJpc21hQ2xpZW50IHwgdW5kZWZpbmVkOyB9CmNvbnN0IHByaXNtYSA9IGdsb2JhbC5wcmlzbWEgfHwgbmV3IFByaXNtYUNsaWVudCgpOwppZiAocHJvY2Vzcy5lbnYuTk9ERV9FTlYgIT09ICZxdW90O3Byb2R1Y3Rpb24mcXVvdDspIGdsb2JhbC5wcmlzbWEgPSBwcmlzbWE7CmV4cG9ydCBkZWZhdWx0IHByaXNtYTsKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL3Jvb3QudHN4JnF1b3Q7ICZsdDsmbHQ7J0VPRicKZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gQXBwKCkgeyByZXR1cm4gbnVsbDsgfQpFT0YKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCkFQUC9MSUIKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvbGliL21vbmdvZGIuc2VydmVyLnRzJnF1b3Q7ICZsdDsmbHQ7J0VPRicKaW1wb3J0IHsgTW9uZ29DbGllbnQsIERiIH0gZnJvbSAmcXVvdDttb25nb2RiJnF1b3Q7Owpjb25zdCBNT05HT0RCX1VSSSA9IHByb2Nlc3MuZW52Lk1PTkdPREJfVVJJITsKY29uc3QgTU9OR09EQl9EQiA9ICZxdW90O3NoYXJlZHJvcCZxdW90OzsKaWYgKCFNT05HT0RCX1VSSSkgdGhyb3cgbmV3IEVycm9yKCZxdW90O01PTkdPREJfVVJJIGlzIHJlcXVpcmVkJnF1b3Q7KTsKCmxldCBjYWNoZWRDbGllbnQ6IE1vbmdvQ2xpZW50IHwgbnVsbCA9IG51bGw7CmxldCBjYWNoZWREYjogRGIgfCBudWxsID0gbnVsbDsKCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjb25uZWN0VG9EYXRhYmFzZSgpIHsKaWYgKGNhY2hlZENsaWVudCAmYW1wOyZhbXA7IGNhY2hlZERiKSByZXR1cm4geyBjbGllbnQ6IGNhY2hlZENsaWVudCwgZGI6IGNhY2hlZERiIH07CmNvbnN0IGNsaWVudCA9IGF3YWl0IE1vbmdvQ2xpZW50LmNvbm5lY3QoTU9OR09EQl9VUkkpOwpjb25zdCBkYiA9IGNsaWVudC5kYihNT05HT0RCX0RCKTsKY2FjaGVkQ2xpZW50ID0gY2xpZW50OwpjYWNoZWREYiA9IGRiOwpyZXR1cm4geyBjbGllbnQsIGRiIH07Cn0KCmV4cG9ydCBjb25zdCBjb2xsZWN0aW9ucyA9IHsKU0hPUFM6ICZxdW90O3Nob3BzJnF1b3Q7LApTSElQTUVOVFM6ICZxdW90O3NoaXBtZW50cyZxdW90OywKRFJJVkVSUzogJnF1b3Q7ZHJpdmVycyZxdW90OywKTEVBRFM6ICZxdW90O2xlYWRzJnF1b3Q7LApTWU5DX1FVRVVFOiAmcXVvdDtzeW5jUXVldWUmcXVvdDssCk9GRkxJTkVfTk9USUZJQ0FUSU9OUzogJnF1b3Q7b2ZmbGluZU5vdGlmaWNhdGlvbnMmcXVvdDssClVTRVJfREVWSUNFUzogJnF1b3Q7dXNlckRldmljZXMmcXVvdDssClZJUF9DT05UQUNUUzogJnF1b3Q7dmlwQ29udGFjdHMmcXVvdDssClRFTkRFUl9TQ09SRVM6ICZxdW90O3RlbmRlclNjb3JlcyZxdW90OywKQVVESVRfTE9HOiAmcXVvdDthdWRpdExvZyZxdW90OywKUEFZTUVOVF9IQU5ET0ZGUzogJnF1b3Q7cGF5bWVudEhhbmRvZmZzJnF1b3Q7LApEUklWSU5HX1NFU1NJT05TOiAmcXVvdDtkcml2aW5nU2Vzc2lvbnMmcXVvdDssCn07CkVPRgoKd3JpdGVmaWxlICZxdW90OyRST09UL2FwcC9saWIvZ2xpZS5zZXJ2ZXIudHMmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyBjb25uZWN0VG9EYXRhYmFzZSwgY29sbGVjdGlvbnMgfSBmcm9tICZxdW90Oy4vbW9uZ29kYi5zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IHY0IGFzIHV1aWR2NCB9IGZyb20gJnF1b3Q7dXVpZCZxdW90OzsKCmV4cG9ydCBpbnRlcmZhY2UgUmF3TGVhZCB7CmlkPzogc3RyaW5nOyB0aXRsZTogc3RyaW5nOyBzb3VyY2U6IHN0cmluZzsgY2F0ZWdvcnk6IHN0cmluZzsKZGVzY3JpcHRpb246IHN0cmluZzsgdmFsdWVfZ3lkPzogbnVtYmVyOyBhZ2VuY3k/OiBzdHJpbmc7CmRlYWRsaW5lPzogc3RyaW5nOyBvZmZpY2lhbF9zb3VyY2U/OiBzdHJpbmc7Cn0KCmV4cG9ydCBpbnRlcmZhY2UgRXZhbHVhdGVkTGVhZCB7CmxlYWRfaWQ6IHN0cmluZzsgc2hvcDogc3RyaW5nOyB0aXRsZTogc3RyaW5nOwpzdGF0dXM6ICZxdW90O1FVQUxJRklFRCZxdW90OyB8ICZxdW90O0ZMQUdHRURfTEVHQUxfUkVWSUVXJnF1b3Q7Owp0YXJnZXRfZW50aXR5OiBzdHJpbmc7IHdhdGVyZmFsbF9zdHJlYW06IHN0cmluZzsgcHJpb3JpdHk6IHN0cmluZzsKc2NvcmU6IG51bWJlcjsgY29ycmlkb3JfYWxpZ25lZDogYm9vbGVhbjsgZmxlZXRfYWxpZ25lZDogYm9vbGVhbjsKZXN0aW1hdGVkX3ZhbHVlX2d5ZDogbnVtYmVyOyByZWNvbW1lbmRlZF9uZXh0X3N0ZXA6IHN0cmluZzsKcmF3OiBSYXdMZWFkOyBjcmVhdGVkQXQ6IERhdGU7IHVwZGF0ZWRBdDogRGF0ZTsKfQoKZXhwb3J0IGNsYXNzIEJ1c2luZXNzUHJvZmlsZSB7Cm9wZXJhdGluZ19lbnRpdHkgPSAmcXVvdDtTaGFyZURyb3AuVHJhZGluZyAoU0RUKSZxdW90OzsKaG9sZGluZ19lbnRpdHkgPSAmcXVvdDtHdXlhbmEgSGVhcnRzIFVuaXRlZCBHcm91cCBJbmMuIChHWS1IVUcpJnF1b3Q7OwpkY3JhX2ZlZV9neWQgPSA2MzU1MC4wOwpzaGFyZV9kaXN0cmlidXRpb24gPSB7IEdZX0hVRzogMC44LCBSZXNlcnZlZDogMC4yIH07CmxlZ2FsX2NvdW5zZWwgPSAmcXVvdDtNcy4gRXN0aGVyIE9zYm9ybmUmcXVvdDs7CmZsZWV0X2NhcGFiaWxpdGllcyA9IFsmcXVvdDtmbGF0YmVkJnF1b3Q7LCAmcXVvdDtyZWZyaWdlcmF0ZWQmcXVvdDssICZxdW90O2Nyb3NzX2JvcmRlcl92YW4mcXVvdDssICZxdW90O3JpdmVyX2ZyZWlnaHQmcXVvdDtdOwphY3RpdmVfY29ycmlkb3JzID0gWyZxdW90O0dlb3JnZXRvd24tTGV0aGVtJnF1b3Q7LCAmcXVvdDtMZXRoZW0tQm9hIFZpc3RhJnF1b3Q7LCAmcXVvdDtMaW5kZW4tTGV0aGVtJnF1b3Q7LCAmcXVvdDtDb3JlbnR5bmUmcXVvdDtdOwp3YXRlcmZhbGxfc3RyZWFtcyA9IFsKJnF1b3Q7U3RyZWFtIDE6IFBsYXRmb3JtIFRyYW5zYWN0aW9uIEZlZXMmcXVvdDssCiZxdW90O1N0cmVhbSAyOiBEZWRpY2F0ZWQgTG9naXN0aWNzICZhbXA7IEZyZWlnaHQgQ29udHJhY3RzJnF1b3Q7LAomcXVvdDtTdHJlYW0gMzogQ3Jvc3MtQm9yZGVyIFRyYWRlICZhbXA7IEN1c3RvbXMgRmFjaWxpdGF0aW9uJnF1b3Q7LAomcXVvdDtTdHJlYW0gNDogRmxlZXQgTWFuYWdlbWVudCAmYW1wOyBTdWJzY3JpcHRpb24gU2VydmljZXMmcXVvdDssCiZxdW90O1N0cmVhbSA1OiBDb21tZXJjaWFsIFRlbmRlcnMgJmFtcDsgSW5zdGl0dXRpb25hbCBCaWRzJnF1b3Q7LAomcXVvdDtTdHJlYW0gNjogU29jaWFsIEltcGFjdCAmYW1wOyBEZXZlbG9wbWVudCBHcmFudHMgKEdZLUhVRykmcXVvdDssCl07Cn0KCmNvbnN0IExFR0FMX0ZPUkJJRERFTl9URVJNUyA9IFsKJnF1b3Q7aW52ZXN0bWVudCZxdW90OywmcXVvdDtzZWN1cml0aWVzJnF1b3Q7LCZxdW90O2VxdWl0eSBzYWxlJnF1b3Q7LCZxdW90O3NoYXJlcyBvZmZlcmluZyZxdW90OywmcXVvdDtwdWJsaWMgb2ZmZXJpbmcmcXVvdDssCiZxdW90O2lwbyZxdW90OywmcXVvdDtjdXN0b2R5JnF1b3Q7LCZxdW90O3dhbGxldCZxdW90OywmcXVvdDtlc2Nyb3cgYWNjb3VudCZxdW90OywmcXVvdDtob2xkIGZ1bmRzJnF1b3Q7LApdOwoKZnVuY3Rpb24gY29udGFpbnNMZWdhbFJpc2sodGV4dDogc3RyaW5nKTogYm9vbGVhbiB7CmNvbnN0IGxvd2VyID0gdGV4dC50b0xvd2VyQ2FzZSgpOwpyZXR1cm4gTEVHQUxfRk9SQklEREVOX1RFUk1TLnNvbWUoKHQpID0mZ3Q7IGxvd2VyLmluY2x1ZGVzKHQpKTsKfQoKZXhwb3J0IGNsYXNzIExlYWRFdmFsdWF0b3Igewpjb25zdHJ1Y3Rvcihwcml2YXRlIHByb2ZpbGU6IEJ1c2luZXNzUHJvZmlsZSA9IG5ldyBCdXNpbmVzc1Byb2ZpbGUoKSkge30KCmV2YWx1YXRlTGVhZChzaG9wOiBzdHJpbmcsIHJhdzogUmF3TGVhZCk6IEV2YWx1YXRlZExlYWQgewpjb25zdCB0aXRsZSA9IHJhdy50aXRsZSB8fCAmcXVvdDtVbnRpdGxlZCBMZWFkJnF1b3Q7Owpjb25zdCBkZXNjcmlwdGlvbiA9IHJhdy5kZXNjcmlwdGlvbiB8fCAmcXVvdDsmcXVvdDs7CmNvbnN0IGNhdGVnb3J5ID0gKHJhdy5jYXRlZ29yeSB8fCAmcXVvdDsmcXVvdDspLnRvTG93ZXJDYXNlKCk7CmNvbnN0IHZhbHVlID0gcmF3LnZhbHVlX2d5ZCB8fCAwOwoKfQoKcHJpdmF0ZSBhY3Rpb25QbGFuKHNjb3JlOiBudW1iZXIsIGNhdGVnb3J5OiBzdHJpbmcsIGVudGl0eTogc3RyaW5nKTogc3RyaW5nIHsKaWYgKGVudGl0eSA9PT0gdGhpcy5wcm9maWxlLmhvbGRpbmdfZW50aXR5KQpyZXR1cm4gJnF1b3Q7UHJlcGFyZSBHWS1IVUcgZ3JhbnQgcHJvcG9zYWwgZm9jdXNpbmcgb24gY29tbXVuaXR5IHdlbGZhcmUsIGxvY2FsIGRldmVsb3BtZW50LCBhbmQgcmVnaW9uYWwgaW1wYWN0LiZxdW90OzsKaWYgKHNjb3JlICZndDs9IDcwKQpyZXR1cm4gJnF1b3Q7RGlzcGF0Y2ggYXV0b21hdGVkIHF1b3RlLCBzY2hlZHVsZSBwcmlvcml0eSBpbnRha2UgY2FsbCwgYW5kIGRyYWZ0IFNEVCBjb21tZXJjaWFsIHNlcnZpY2UgYWdyZWVtZW50LiZxdW90OzsKcmV0dXJuICZxdW90O0FkZCB0byBTRFQgbnVydHVyZSBwaXBlbGluZTsgc2VuZCBwbGF0Zm9ybSBjYXBhYmlsaXRpZXMgZGVjayBhbmQgc2NoZWR1bGUgZm9sbG93LXVwLiZxdW90OzsKfQp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gcGVyc2lzdExlYWQobGVhZDogRXZhbHVhdGVkTGVhZCkgewpjb25zdCB7IGRiIH0gPSBhd2FpdCBjb25uZWN0VG9EYXRhYmFzZSgpOwphd2FpdCBkYi5jb2xsZWN0aW9uKGNvbGxlY3Rpb25zLkxFQURTKS5pbnNlcnRPbmUobGVhZCk7CnJldHVybiBsZWFkOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbGlzdExlYWRzKHNob3A6IHN0cmluZywgbGltaXQgPSAxMDApIHsKY29uc3QgeyBkYiB9ID0gYXdhaXQgY29ubmVjdFRvRGF0YWJhc2UoKTsKcmV0dXJuIGRiLmNvbGxlY3Rpb24oY29sbGVjdGlvbnMuTEVBRFMpLmZpbmQoeyBzaG9wIH0pLnNvcnQoeyBjcmVhdGVkQXQ6IC0xIH0pLmxpbWl0KGxpbWl0KS50b0FycmF5KCk7Cn0KCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBhdWRpdExvZyhzaG9wOiBzdHJpbmcsIGFjdGlvbjogc3RyaW5nLCBwYXlsb2FkOiBhbnkpIHsKY29uc3QgeyBkYiB9ID0gYXdhaXQgY29ubmVjdFRvRGF0YWJhc2UoKTsKYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5BVURJVF9MT0cpLmluc2VydE9uZSh7IHNob3AsIGFjdGlvbiwgcGF5bG9hZCwgYXQ6IG5ldyBEYXRlKCkgfSk7Cn0KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL2xpYi9ub3RpZmljYXRpb25zLnNlcnZlci50cyZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IGNvbm5lY3RUb0RhdGFiYXNlLCBjb2xsZWN0aW9ucyB9IGZyb20gJnF1b3Q7Li9tb25nb2RiLnNlcnZlciZxdW90OzsKCmludGVyZmFjZSBQdXNoUGF5bG9hZCB7CnVzZXJJZDogc3RyaW5nOyB0aXRsZTogc3RyaW5nOyBib2R5OiBzdHJpbmc7CmRhdGE6IFJlY29yZCZsdDtzdHJpbmcsIGFueSZndDs7IHBlcnNpc3RVbnRpbERlbGl2ZXJlZD86IGJvb2xlYW47IGV4cGlyZXNBdD86IERhdGU7Cn0KCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzZW5kUHVzaE5vdGlmaWNhdGlvbihwYXlsb2FkOiBQdXNoUGF5bG9hZCkgewpjb25zdCB7IGRiIH0gPSBhd2FpdCBjb25uZWN0VG9EYXRhYmFzZSgpOwpjb25zdCB7IHVzZXJJZCwgdGl0bGUsIGJvZHksIGRhdGEsIHBlcnNpc3RVbnRpbERlbGl2ZXJlZCwgZXhwaXJlc0F0IH0gPSBwYXlsb2FkOwpjb25zdCBkZXZpY2VzID0gYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5VU0VSX0RFVklDRVMpLmZpbmQoeyB1c2VySWQgfSkudG9BcnJheSgpOwppZiAoZGV2aWNlcy5sZW5ndGggPT09IDApIHJldHVybiB7IGRlbGl2ZXJlZDogZmFsc2UsIHJlYXNvbjogJnF1b3Q7Tm8gZGV2aWNlcyByZWdpc3RlcmVkJnF1b3Q7IH07Cgpjb25zdCByZXN1bHRzOiBhbnlbXSA9IFtdOwpmb3IgKGNvbnN0IGRldmljZSBvZiBkZXZpY2VzKSB7CmNvbnN0IGlzT25saW5lID0gZGV2aWNlLmxhc3RTZWVuICZndDsgbmV3IERhdGUoRGF0ZS5ub3coKSAtIDUgKiA2MCAqIDEwMDApOwppZiAoaXNPbmxpbmUpIHsKYXdhaXQgc2VuZFRvRGV2aWNlKGRldmljZSwgeyB0aXRsZSwgYm9keSwgZGF0YSB9KTsKcmVzdWx0cy5wdXNoKHsgZGV2aWNlSWQ6IGRldmljZS5kZXZpY2VJZCwgZGVsaXZlcmVkOiB0cnVlIH0pOwp9IGVsc2UgaWYgKHBlcnNpc3RVbnRpbERlbGl2ZXJlZCkgewphd2FpdCBkYi5jb2xsZWN0aW9uKGNvbGxlY3Rpb25zLk9GRkxJTkVfTk9USUZJQ0FUSU9OUykuaW5zZXJ0T25lKHsKdXNlcklkLCBkZXZpY2VJZDogZGV2aWNlLmRldmljZUlkLCBub3RpZmljYXRpb246IHsgdGl0bGUsIGJvZHksIGRhdGEgfSwKZXhwaXJlc0F0LCBhdHRlbXB0czogMCwgbWF4QXR0ZW1wdHM6IDEwLCBjcmVhdGVkQXQ6IG5ldyBEYXRlKCksCn0pOwpyZXN1bHRzLnB1c2goeyBkZXZpY2VJZDogZGV2aWNlLmRldmljZUlkLCBkZWxpdmVyZWQ6IGZhbHNlLCBxdWV1ZWQ6IHRydWUgfSk7Cn0gZWxzZSB7CnJlc3VsdHMucHVzaCh7IGRldmljZUlkOiBkZXZpY2UuZGV2aWNlSWQsIGRlbGl2ZXJlZDogZmFsc2UgfSk7Cn0KfQpyZXR1cm4gewpkZWxpdmVyZWQ6IHJlc3VsdHMuc29tZSgocikgPSZndDsgci5kZWxpdmVyZWQpLApxdWV1ZWQ6IHJlc3VsdHMuc29tZSgocikgPSZndDsgci5xdWV1ZWQpLApyZXN1bHRzLAp9Owp9Cgphc3luYyBmdW5jdGlvbiBzZW5kVG9EZXZpY2UoZGV2aWNlOiBhbnksIG5vdGlmaWNhdGlvbjogYW55KSB7CmNvbnNvbGUubG9nKFtwdXNoXSAmcmFycjsgJHtkZXZpY2UuZGV2aWNlSWR9Oiwgbm90aWZpY2F0aW9uLnRpdGxlKTsKcmV0dXJuIHsgc3VjY2VzczogdHJ1ZSB9Owp9CkVPRgoKd3JpdGVmaWxlICZxdW90OyRST09UL2FwcC9saWIvZnVsZmlsbG1lbnQuc2VydmVyLnRzJnF1b3Q7ICZsdDsmbHQ7J0VPRicKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNyZWF0ZUZ1bGZpbGxtZW50KGFkbWluOiBhbnksIGZ1bGZpbGxtZW50T3JkZXJJZDogc3RyaW5nLCB0cmFja2luZ051bWJlcjogc3RyaW5nLCB0cmFja2luZ0NvbXBhbnk6IHN0cmluZykgewpjb25zdCByZXMgPSBhd2FpdCBhZG1pbi5ncmFwaHFsKAojZ3JhcGhxbAogICAgbXV0YXRpb24gZnVsZmlsbG1lbnRDcmVhdGVWMigkZnVsZmlsbG1lbnQ6IEZ1bGZpbGxtZW50VjJJbnB1dCEpIHsKICAgICAgZnVsZmlsbG1lbnRDcmVhdGVWMihmdWxmaWxsbWVudDogJGZ1bGZpbGxtZW50KSB7CiAgICAgICAgZnVsZmlsbG1lbnQgeyBpZCBzdGF0dXMgdHJhY2tpbmdJbmZvIHsgbnVtYmVyIGNvbXBhbnkgdXJsIH0gfQogICAgICAgIHVzZXJFcnJvcnMgeyBmaWVsZCBtZXNzYWdlIH0KICAgICAgfQogICAgfSwKeyB2YXJpYWJsZXM6IHsgZnVsZmlsbG1lbnQ6IHsKbGluZUl0ZW1zQnlGdWxmaWxsbWVudE9yZGVyOiBbeyBmdWxmaWxsbWVudE9yZGVySWQgfV0sCnRyYWNraW5nSW5mbzogeyBudW1iZXI6IHRyYWNraW5nTnVtYmVyLCBjb21wYW55OiB0cmFja2luZ0NvbXBhbnkgfSwKbm90aWZ5Q3VzdG9tZXI6IHRydWUsCn0gfSB9Cik7CmNvbnN0IGRhdGEgPSBhd2FpdCByZXMuanNvbigpOwpjb25zdCBlcnJzID0gZGF0YS5kYXRhPy5mdWxmaWxsbWVudENyZWF0ZVYyPy51c2VyRXJyb3JzIHx8IFtdOwppZiAoZXJycy5sZW5ndGgpIHRocm93IG5ldyBFcnJvcihlcnJzWzBdLm1lc3NhZ2UpOwpyZXR1cm4gZGF0YS5kYXRhPy5mdWxmaWxsbWVudENyZWF0ZVYyPy5mdWxmaWxsbWVudDsKfQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvbGliL3BheW1lbnRzLnNlcnZlci50cyZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IGNvbm5lY3RUb0RhdGFiYXNlIH0gZnJvbSAmcXVvdDsuL21vbmdvZGIuc2VydmVyJnF1b3Q7OwoKLyogVjcuMSBOb24tQ3VzdG9keSBBQlNPTFVURSAmbWRhc2g7IEhhbmRib29rIFBhcnQgSVYgMTYKU0RUIEx0ZCBpcyBOT1QgYSBQU1AuIE5vIHdhbGxldC4gTm8gZXNjcm93LiBObyBjdXN0b2R5LiAqLwoKZXhwb3J0IGludGVyZmFjZSBQMlBQYXltZW50TGluayB7Cm9yZGVySWQ6IHN0cmluZzsgYW1vdW50R3lkOiBudW1iZXI7IGFtb3VudEJybD86IG51bWJlcjsKcmlkZXI6IHsgbmFtZTogc3RyaW5nOyBlbWFpbDogc3RyaW5nOyBwaG9uZTogc3RyaW5nIH07CmRyaXZlcjogeyBuYW1lOiBzdHJpbmc7IGJhbmtOYW1lPzogc3RyaW5nOyBwaXhLZXk/OiBzdHJpbmcgfTsKcHJvdmlkZXI6ICZxdW90O21tZyZxdW90OyB8ICZxdW90O2JlX3BheW1lbnRzJnF1b3Q7IHwgJnF1b3Q7c3RyaXBlJnF1b3Q7IHwgJnF1b3Q7cGl4JnF1b3Q7Owpub25DdXN0b2R5OiB0cnVlOyBjcmVhdGVkQXQ6IERhdGU7Cn0KCmNvbnN0IFBJWF9DSEFWRSA9ICZxdW90OzcxMDg2Mjg3MjQwJnF1b3Q7OyAvLyBMb2NrZWQgVjcuMQoKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNyZWF0ZVAyUEhhbmRvZmYoCnNob3A6IHN0cmluZywgb3JkZXJJZDogc3RyaW5nLCBhbW91bnQ6IG51bWJlciwgY3VycmVuY3k6ICZxdW90O0dZRCZxdW90OyB8ICZxdW90O0JSTCZxdW90OyA9ICZxdW90O0dZRCZxdW90OwopOiBQcm9taXNlJmx0O1AyUFBheW1lbnRMaW5rJmd0OyB7CmNvbnN0IHByb3ZpZGVyID0gY3VycmVuY3kgPT09ICZxdW90O0JSTCZxdW90OyA/ICZxdW90O3BpeCZxdW90OyA6IGFtb3VudCAmZ3Q7IDIwMF8wMDAgPyAmcXVvdDttbWcmcXVvdDsgOiAmcXVvdDtiZV9wYXltZW50cyZxdW90OzsKY29uc3QgbGluazogUDJQUGF5bWVudExpbmsgPSB7Cm9yZGVySWQsIGFtb3VudEd5ZDogY3VycmVuY3kgPT09ICZxdW90O0dZRCZxdW90OyA/IGFtb3VudCA6IDAsCmFtb3VudEJybDogY3VycmVuY3kgPT09ICZxdW90O0JSTCZxdW90OyA/IGFtb3VudCA6IHVuZGVmaW5lZCwKcmlkZXI6IHsgbmFtZTogJnF1b3Q7JnF1b3Q7LCBlbWFpbDogJnF1b3Q7JnF1b3Q7LCBwaG9uZTogJnF1b3Q7JnF1b3Q7IH0sIGRyaXZlcjogeyBuYW1lOiAmcXVvdDsmcXVvdDsgfSwKcHJvdmlkZXIsIG5vbkN1c3RvZHk6IHRydWUsIGNyZWF0ZWRBdDogbmV3IERhdGUoKSwKfTsKY29uc3QgeyBkYiB9ID0gYXdhaXQgY29ubmVjdFRvRGF0YWJhc2UoKTsKYXdhaXQgZGIuY29sbGVjdGlvbigmcXVvdDtwYXltZW50SGFuZG9mZnMmcXVvdDspLmluc2VydE9uZSh7CnNob3AsIC4uLmxpbmssCnBpeENoYXZlOiBjdXJyZW5jeSA9PT0gJnF1b3Q7QlJMJnF1b3Q7ID8gUElYX0NIQVZFIDogbnVsbCwKc3RhdHVzOiAmcXVvdDthd2FpdGluZ19yaWRlcl9hY3Rpb24mcXVvdDssCn0pOwpyZXR1cm4gbGluazsKfQoKZXhwb3J0IGludGVyZmFjZSBQbGF0Zm9ybUFjY2Vzc0ZlZSB7CnRpZXI6ICZxdW90O2Jhc2ljJnF1b3Q7IHwgJnF1b3Q7cHJvJnF1b3Q7IHwgJnF1b3Q7YnVzaW5lc3MmcXVvdDsgfCAmcXVvdDtlbnRlcnByaXNlJnF1b3Q7OwphbW91bnRHeWQ6IG51bWJlcjsgcGVyaW9kOiAmcXVvdDttb250aGx5JnF1b3Q7Owp9CgpleHBvcnQgY29uc3QgUExBVEZPUk1fQUNDRVNTX0ZFRVM6IFJlY29yZCZsdDtzdHJpbmcsIFBsYXRmb3JtQWNjZXNzRmVlJmd0OyA9IHsKYmFzaWM6ICAgICAgeyB0aWVyOiAmcXVvdDtiYXNpYyZxdW90OywgICAgICBhbW91bnRHeWQ6IDAsICAgICBwZXJpb2Q6ICZxdW90O21vbnRobHkmcXVvdDsgfSwKcHJvOiAgICAgICAgeyB0aWVyOiAmcXVvdDtwcm8mcXVvdDssICAgICAgICBhbW91bnRHeWQ6IDUwMDAsICBwZXJpb2Q6ICZxdW90O21vbnRobHkmcXVvdDsgfSwKYnVzaW5lc3M6ICAgeyB0aWVyOiAmcXVvdDtidXNpbmVzcyZxdW90OywgICBhbW91bnRHeWQ6IDE1MDAwLCBwZXJpb2Q6ICZxdW90O21vbnRobHkmcXVvdDsgfSwKZW50ZXJwcmlzZTogeyB0aWVyOiAmcXVvdDtlbnRlcnByaXNlJnF1b3Q7LCBhbW91bnRHeWQ6IDAsICAgICBwZXJpb2Q6ICZxdW90O21vbnRobHkmcXVvdDsgfSwKfTsKCmNvbnN0IEZPUkJJRERFTiA9IFsmcXVvdDt3YWxsZXQmcXVvdDssJnF1b3Q7ZXNjcm93JnF1b3Q7LCZxdW90O2N1c3RvZHkmcXVvdDssJnF1b3Q7cG9vbCZxdW90OywmcXVvdDtmbG9hdCZxdW90OywmcXVvdDtob2xkIGZ1bmRzJnF1b3Q7LCZxdW90O3RydXN0IGFjY291bnQmcXVvdDtdOwpleHBvcnQgZnVuY3Rpb24gYXNzZXJ0Tm9uQ3VzdG9keSh0ZXh0OiBzdHJpbmcpOiB2b2lkIHsKY29uc3QgbG93ZXIgPSB0ZXh0LnRvTG93ZXJDYXNlKCk7CmZvciAoY29uc3QgdGVybSBvZiBGT1JCSURERU4pIHsKaWYgKGxvd2VyLmluY2x1ZGVzKHRlcm0pKSB7CnRocm93IG5ldyBFcnJvcigKVjcuMSBOb24tQ3VzdG9keSB2aW9sYXRpb246IHRlcm0gJnF1b3Q7JHt0ZXJtfSZxdW90OyBkZXRlY3RlZC4gICsKRXNjYWxhdGUgdG8gZW9zYm9ybmUubGVnYWxAZ21haWwuY29tIGltbWVkaWF0ZWx5LgopOwp9Cn0KfQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvbGliL2RyaXZpbmctc2Nob29sLnNlcnZlci50cyZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IHY0IGFzIHV1aWR2NCB9IGZyb20gJnF1b3Q7dXVpZCZxdW90OzsKaW1wb3J0IHsgY29ubmVjdFRvRGF0YWJhc2UgfSBmcm9tICZxdW90Oy4vbW9uZ29kYi5zZXJ2ZXImcXVvdDs7CgpleHBvcnQgdHlwZSBEcml2aW5nTW9kdWxlID0KfCAmcXVvdDtwbGF5ZXJfaW50YWtlX2t5YyZxdW90OyB8ICZxdW90O2NvdW50cnlfcnVsZXNfbG9hZGVyJnF1b3Q7IHwgJnF1b3Q7ZHJpdmluZ19zY2hvb2xfc2ltJnF1b3Q7CnwgJnF1b3Q7bGl2ZV9jaGF0JnF1b3Q7IHwgJnF1b3Q7bXVzaWNfaW50ZWdyYXRpb24mcXVvdDsgfCAmcXVvdDtyaWRlX2Jvb2tpbmcmcXVvdDsKfCAmcXVvdDtjcm9zc19wbGF0Zm9ybSZxdW90OyB8ICZxdW90O2FpX2RlYnVnZ2luZyZxdW90OyB8ICZxdW90O3FyX2Nvbm5lY3QmcXVvdDs7CgpleHBvcnQgaW50ZXJmYWNlIERyaXZpbmdTZXNzaW9uIHsKX2lkOiBzdHJpbmc7IHNob3A6IHN0cmluZzsgcGxheWVySWQ6IHN0cmluZzsKY291bnRyeTogJnF1b3Q7R1kmcXVvdDsgfCAmcXVvdDtCUiZxdW90OzsgcnVsZXNKc29uOiBzdHJpbmc7Cm1vZHVsZTogRHJpdmluZ01vZHVsZTsgc2NvcmU6IG51bWJlcjsgcGFzc2VkOiBib29sZWFuOyBjcmVhdGVkQXQ6IERhdGU7Cn0KCmV4cG9ydCBjb25zdCBHWUhVR19NT0RVTEVTOiBSZWNvcmQmbHQ7RHJpdmluZ01vZHVsZSwgeyBsYWJlbDogc3RyaW5nOyB3ZWlnaHQ6IG51bWJlciB9Jmd0OyA9IHsKcGxheWVyX2ludGFrZV9reWM6ICAgIHsgbGFiZWw6ICZxdW90OzEuIFBsYXllciBJbnRha2UgS1lDJnF1b3Q7LCAgICAgICAgICAgICAgd2VpZ2h0OiAxMCB9LApjb3VudHJ5X3J1bGVzX2xvYWRlcjogeyBsYWJlbDogJnF1b3Q7Mi4gQ291bnRyeSBSdWxlcyBMb2FkZXImcXVvdDssICAgICAgICAgICB3ZWlnaHQ6IDEwIH0sCmRyaXZpbmdfc2Nob29sX3NpbTogICB7IGxhYmVsOiAmcXVvdDszLiBEcml2aW5nIFNjaG9vbCBTaW11bGF0aW9uJnF1b3Q7LCAgICAgIHdlaWdodDogMjUgfSwKbGl2ZV9jaGF0OiAgICAgICAgICAgIHsgbGFiZWw6ICZxdW90OzQuIExpdmUgQ2hhdCAoQml0Q2hhdCArIFdlYlNvY2tldCkmcXVvdDssIHdlaWdodDogNSB9LAptdXNpY19pbnRlZ3JhdGlvbjogICAgeyBsYWJlbDogJnF1b3Q7NS4gTXVzaWMgSW50ZWdyYXRpb24gKFNoYXJlZHJvcCBGTSkmcXVvdDssd2VpZ2h0OiA1IH0sCnJpZGVfYm9va2luZzogICAgICAgICB7IGxhYmVsOiAmcXVvdDs2LiBSaWRlIEJvb2tpbmcgdmlhIFNEVCZxdW90OywgICAgICAgICAgIHdlaWdodDogMTUgfSwKY3Jvc3NfcGxhdGZvcm06ICAgICAgIHsgbGFiZWw6ICZxdW90OzcuIENyb3NzLVBsYXRmb3JtJnF1b3Q7LCAgICAgICAgICAgICAgICAgd2VpZ2h0OiA1IH0sCmFpX2RlYnVnZ2luZzogICAgICAgICB7IGxhYmVsOiAmcXVvdDs4LiBBSSBEZWJ1Z2dpbmcgQXNzaXN0YW50JnF1b3Q7LCAgICAgICAgIHdlaWdodDogMTUgfSwKcXJfY29ubmVjdDogICAgICAgICAgIHsgbGFiZWw6ICZxdW90OzkuIFFSIENvbm5lY3QmcXVvdDssICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0OiAxMCB9LAp9OwoKZXhwb3J0IGNvbnN0IEdUVElfU0FNUExFX1FVRVNUSU9OUyA9IFsKeyBpZDogJnF1b3Q7R1ktUTEmcXVvdDssIHF1ZXN0aW9uOiAmcXVvdDtXaGF0IGlzIHRoZSBzcGVlZCBsaW1pdCBpbiBhIHJlc2lkZW50aWFsIGFyZWE/JnF1b3Q7LApvcHRpb25zOiB7IEE6ICZxdW90OzMwIGttL2gmcXVvdDssIEI6ICZxdW90OzUwIGttL2gmcXVvdDssIEM6ICZxdW90OzgwIGttL2gmcXVvdDssIEQ6ICZxdW90OzEwMCBrbS9oJnF1b3Q7IH0sIGFuc3dlcjogJnF1b3Q7QiZxdW90OyB9LAp7IGlkOiAmcXVvdDtHWS1RMiZxdW90OywgcXVlc3Rpb246ICZxdW90O1doZW4gbXVzdCB5b3Ugd2VhciBhIHNlYXRiZWx0PyZxdW90OywKb3B0aW9uczogeyBBOiAmcXVvdDtPbmx5IG9uIGhpZ2h3YXlzJnF1b3Q7LCBCOiAmcXVvdDtBdCBhbGwgdGltZXMgd2hlbiBkcml2aW5nJnF1b3Q7LCBDOiAmcXVvdDtPbmx5IGF0IG5pZ2h0JnF1b3Q7LCBEOiAmcXVvdDtOZXZlciZxdW90OyB9LCBhbnN3ZXI6ICZxdW90O0ImcXVvdDsgfSwKeyBpZDogJnF1b3Q7R1ktUTMmcXVvdDssIHF1ZXN0aW9uOiAmcXVvdDtIYXphcmQgZGV0ZWN0ZWQ6IHBlZGVzdHJpYW4gY3Jvc3NpbmcuIFlvdSBtdXN0OiZxdW90OywKb3B0aW9uczogeyBBOiAmcXVvdDtTcGVlZCB1cCZxdW90OywgQjogJnF1b3Q7SG9uayBhbmQgcHJvY2VlZCZxdW90OywgQzogJnF1b3Q7WWllbGQgdG8gcGVkZXN0cmlhbnMmcXVvdDssIEQ6ICZxdW90O1N0b3AgcmVnYXJkbGVzcyZxdW90OyB9LCBhbnN3ZXI6ICZxdW90O0MmcXVvdDsgfSwKXTsKCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzdGFydERyaXZpbmdTZXNzaW9uKHNob3A6IHN0cmluZywgcGxheWVySWQ6IHN0cmluZywgY291bnRyeTogJnF1b3Q7R1kmcXVvdDsgfCAmcXVvdDtCUiZxdW90Oyk6IFByb21pc2UmbHQ7RHJpdmluZ1Nlc3Npb24mZ3Q7IHsKY29uc3Qgc2Vzc2lvbjogRHJpdmluZ1Nlc3Npb24gPSB7Cl9pZDogdXVpZHY0KCksIHNob3AsIHBsYXllcklkLCBjb3VudHJ5LApydWxlc0pzb246IGNvdW50cnkgPT09ICZxdW90O0dZJnF1b3Q7ID8gJnF1b3Q7Z3VpYW5hX3J1bGVzLmpzb24mcXVvdDsgOiAmcXVvdDticmF6aWxfcnVsZXMuanNvbiZxdW90OywKbW9kdWxlOiAmcXVvdDtwbGF5ZXJfaW50YWtlX2t5YyZxdW90Oywgc2NvcmU6IDAsIHBhc3NlZDogZmFsc2UsIGNyZWF0ZWRBdDogbmV3IERhdGUoKSwKfTsKY29uc3QgeyBkYiB9ID0gYXdhaXQgY29ubmVjdFRvRGF0YWJhc2UoKTsKYXdhaXQgZGIuY29sbGVjdGlvbigmcXVvdDtkcml2aW5nU2Vzc2lvbnMmcXVvdDspLmluc2VydE9uZShzZXNzaW9uKTsKcmV0dXJuIHNlc3Npb247Cn0KCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzdWJtaXRUaGVvcnlUZXN0KHNlc3Npb25JZDogc3RyaW5nLCBhbnN3ZXJzOiBSZWNvcmQmbHQ7c3RyaW5nLCBzdHJpbmcmZ3Q7KSB7CmxldCBjb3JyZWN0ID0gMDsKZm9yIChjb25zdCBxIG9mIEdUVElfU0FNUExFX1FVRVNUSU9OUykgaWYgKGFuc3dlcnNbcS5pZF0gPT09IHEuYW5zd2VyKSBjb3JyZWN0Kys7CmNvbnN0IHNjb3JlID0gTWF0aC5yb3VuZCgoY29ycmVjdCAvIEdUVElfU0FNUExFX1FVRVNUSU9OUy5sZW5ndGgpICogMTAwKTsKY29uc3QgcGFzc2VkID0gc2NvcmUgJmd0Oz0gNzA7CmNvbnN0IHsgZGIgfSA9IGF3YWl0IGNvbm5lY3RUb0RhdGFiYXNlKCk7CmF3YWl0IGRiLmNvbGxlY3Rpb24oJnF1b3Q7ZHJpdmluZ1Nlc3Npb25zJnF1b3Q7KS51cGRhdGVPbmUoCnsgX2lkOiBzZXNzaW9uSWQgfSwgeyAkc2V0OiB7IHNjb3JlLCBwYXNzZWQsIHVwZGF0ZWRBdDogbmV3IERhdGUoKSB9IH0KKTsKcmV0dXJuIHsKc2NvcmUsIHBhc3NlZCwKZmVlZGJhY2s6IHBhc3NlZAo/ICZxdW90O1RoZW9yeSBwYXNzZWQuIFByb2NlZWQgdG8gcHJhY3RpY2FsIGRyaXZpbmcgc2ltdWxhdGlvbi4mcXVvdDsKOiAmcXVvdDtSZXZpZXcgR1RUSSBoYW5kYm9vayBhbmQgcmV0cnkuIE1pbmltdW0gNzAlIHJlcXVpcmVkLiZxdW90OywKfTsKfQpFT0YKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KClJPVVRFUwoKPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKd3JpdGVmaWxlICZxdW90OyRST09UL2FwcC9yb3V0ZXMvYXBwLnRzeCZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IE91dGxldCwgdXNlTG9hZGVyRGF0YSB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9yZWFjdCZxdW90OzsKaW1wb3J0IHsgYm91bmRhcnkgfSBmcm9tICZxdW90O0BzaG9waWZ5L3Nob3BpZnktYXBwLXJlbWl4L3NlcnZlciZxdW90OzsKaW1wb3J0IHsgQXBwUHJvdmlkZXIgfSBmcm9tICZxdW90O0BzaG9waWZ5L3Nob3BpZnktYXBwLXJlbWl4L3JlYWN0JnF1b3Q7OwppbXBvcnQgeyBOYXZNZW51IH0gZnJvbSAmcXVvdDtAc2hvcGlmeS9hcHAtYnJpZGdlLXJlYWN0JnF1b3Q7OwppbXBvcnQgcG9sYXJpc1N0eWxlcyBmcm9tICZxdW90O0BzaG9waWZ5L3BvbGFyaXMvYnVpbGQvZXNtL3N0eWxlcy5jc3M/dXJsJnF1b3Q7OwppbXBvcnQgeyBMaW5rcywgTWV0YSwgU2NyaXB0cywgU2Nyb2xsUmVzdG9yYXRpb24gfSBmcm9tICZxdW90O0ByZW1peC1ydW4vcmVhY3QmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7CgpleHBvcnQgY29uc3QgbGlua3MgPSAoKSA9Jmd0OyBbeyByZWw6ICZxdW90O3N0eWxlc2hlZXQmcXVvdDssIGhyZWY6IHBvbGFyaXNTdHlsZXMgfV07CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBhbnkpIHsKYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpyZXR1cm4geyBhcGlLZXk6IHByb2Nlc3MuZW52LlNIT1BJRllfQVBJX0tFWSB8fCAmcXVvdDsmcXVvdDsgfTsKfQoKZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gQXBwKCkgewpjb25zdCB7IGFwaUtleSB9ID0gdXNlTG9hZGVyRGF0YSZsdDt0eXBlb2YgbG9hZGVyJmd0OygpOwpyZXR1cm4gKAombHQ7aHRtbCBsYW5nPSZxdW90O2VuJnF1b3Q7Jmd0OwombHQ7aGVhZCZndDsKJmx0O21ldGEgY2hhclNldD0mcXVvdDt1dGYtOCZxdW90OyAvJmd0OwombHQ7bWV0YSBuYW1lPSZxdW90O3ZpZXdwb3J0JnF1b3Q7IGNvbnRlbnQ9JnF1b3Q7d2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSZxdW90OyAvJmd0OwombHQ7TWV0YSAvJmd0OwombHQ7TGlua3MgLyZndDsKJmx0Oy9oZWFkJmd0OwombHQ7Ym9keSZndDsKJmx0O0FwcFByb3ZpZGVyIGlzRW1iZWRkZWRBcHAgYXBpS2V5PXthcGlLZXl9Jmd0OwombHQ7TmF2TWVudSZndDsKJmx0O2EgaHJlZj0mcXVvdDsvYXBwJnF1b3Q7IHJlbD0mcXVvdDtob21lJnF1b3Q7Jmd0O0Rhc2hib2FyZCZsdDsvYSZndDsKJmx0O2EgaHJlZj0mcXVvdDsvYXBwL2xlYWRzJnF1b3Q7Jmd0O0dMSUUgTGVhZHMmbHQ7L2EmZ3Q7CiZsdDthIGhyZWY9JnF1b3Q7L2FwcC9zaGlwbWVudHMmcXVvdDsmZ3Q7U2hpcG1lbnRzJmx0Oy9hJmd0OwombHQ7YSBocmVmPSZxdW90Oy9hcHAvc2hpcG1lbnRzL25ldyZxdW90OyZndDtDcmVhdGUgU2hpcG1lbnQmbHQ7L2EmZ3Q7CiZsdDthIGhyZWY9JnF1b3Q7L2FwcC92aXAtZHJpdmVycyZxdW90OyZndDtWSVAgRHJpdmVycyZsdDsvYSZndDsKJmx0O2EgaHJlZj0mcXVvdDsvYXBwL2RyaXZlcnMmcXVvdDsmZ3Q7RHJpdmVyIEZsZWV0Jmx0Oy9hJmd0OwombHQ7YSBocmVmPSZxdW90Oy9hcHAvZ2FtZXBsYXkmcXVvdDsmZ3Q7R2FtZXBsYXkgRmxvdyZsdDsvYSZndDsKJmx0O2EgaHJlZj0mcXVvdDsvYXBwL2RyaXZpbmctc2Nob29sJnF1b3Q7Jmd0O0dZLUhVRyBEcml2aW5nIFNjaG9vbCZsdDsvYSZndDsKJmx0O2EgaHJlZj0mcXVvdDsvYXBwL3BheW1lbnRzJnF1b3Q7Jmd0O1BheW1lbnRzIChOb24tQ3VzdG9keSkmbHQ7L2EmZ3Q7CiZsdDsvTmF2TWVudSZndDsKJmx0O091dGxldCAvJmd0OwombHQ7L0FwcFByb3ZpZGVyJmd0OwombHQ7U2Nyb2xsUmVzdG9yYXRpb24gLyZndDsKJmx0O1NjcmlwdHMgLyZndDsKJmx0Oy9ib2R5Jmd0OwombHQ7L2h0bWwmZ3Q7Cik7Cn0KCmV4cG9ydCBjb25zdCBFcnJvckJvdW5kYXJ5ID0gYm91bmRhcnkuZXJyb3IoKGVycm9yOiBhbnkpID0mZ3Q7ICgKCiAgJmx0O2RpdiBzdHlsZT17eyBwYWRkaW5nOiAyMCB9fSZndDsKICAgICZsdDtoMSZndDtFcnJvciZsdDsvaDEmZ3Q7CiAgICAmbHQ7cHJlJmd0O3tlcnJvcj8uc3RhdHVzVGV4dCB8fCBlcnJvcj8ubWVzc2FnZX0mbHQ7L3ByZSZndDsKICAmbHQ7L2RpdiZndDsKKSk7CkVPRgoKd3JpdGVmaWxlICZxdW90OyRST09UL2FwcC9yb3V0ZXMvX2luZGV4LnRzeCZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IHJlZGlyZWN0IH0gZnJvbSAmcXVvdDtAcmVtaXgtcnVuL25vZGUmcXVvdDs7CmV4cG9ydCBjb25zdCBsb2FkZXIgPSAoKSA9Jmd0OyByZWRpcmVjdCgmcXVvdDsvYXBwJnF1b3Q7KTsKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL3JvdXRlcy9hdXRoLiQudHN4JnF1b3Q7ICZsdDsmbHQ7J0VPRicKaW1wb3J0IHsgYXV0aGVudGljYXRlIH0gZnJvbSAmcXVvdDsuLi9zaG9waWZ5LnNlcnZlciZxdW90OzsKZXhwb3J0IGNvbnN0IGxvYWRlciA9IGFzeW5jICh7IHJlcXVlc3QgfTogYW55KSA9Jmd0OyB7CmF3YWl0IGF1dGhlbnRpY2F0ZS5hZG1pbihyZXF1ZXN0KTsKcmV0dXJuIG51bGw7Cn07CkVPRgoKd3JpdGVmaWxlICZxdW90OyRST09UL2FwcC9yb3V0ZXMvYXBwLl9pbmRleC50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyBqc29uLCB0eXBlIExvYWRlckZ1bmN0aW9uQXJncyB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9ub2RlJnF1b3Q7OwppbXBvcnQgeyB1c2VMb2FkZXJEYXRhLCBMaW5rIH0gZnJvbSAmcXVvdDtAcmVtaXgtcnVuL3JlYWN0JnF1b3Q7OwppbXBvcnQgeyBQYWdlLCBMYXlvdXQsIENhcmQsIFRleHQsIEJsb2NrU3RhY2ssIEJ1dHRvbiwgSW5kZXhUYWJsZSwgQmFkZ2UsIElubGluZUdyaWQgfSBmcm9tICZxdW90O0BzaG9waWZ5L3BvbGFyaXMmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IGNvbm5lY3RUb0RhdGFiYXNlLCBjb2xsZWN0aW9ucyB9IGZyb20gJnF1b3Q7Li4vbGliL21vbmdvZGIuc2VydmVyJnF1b3Q7OwoKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRlcih7IHJlcXVlc3QgfTogTG9hZGVyRnVuY3Rpb25BcmdzKSB7CmNvbnN0IHsgc2Vzc2lvbiB9ID0gYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpjb25zdCBzaG9wID0gc2Vzc2lvbi5zaG9wOwpjb25zdCB7IGRiIH0gPSBhd2FpdCBjb25uZWN0VG9EYXRhYmFzZSgpOwoKY29uc3QgW3NoaXBtZW50cywgbGVhZHMsIHN0YXRzXSA9IGF3YWl0IFByb21pc2UuYWxsKFsKZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5TSElQTUVOVFMpLmZpbmQoeyBzaG9wIH0pLnNvcnQoeyBjcmVhdGVkQXQ6IC0xIH0pLmxpbWl0KDEwKS50b0FycmF5KCksCmRiLmNvbGxlY3Rpb24oY29sbGVjdGlvbnMuTEVBRFMpLmZpbmQoeyBzaG9wIH0pLnNvcnQoeyBjcmVhdGVkQXQ6IC0xIH0pLmxpbWl0KDEwKS50b0FycmF5KCksCihhc3luYyAoKSA9Jmd0OyAoewpzaGlwbWVudHNUb3RhbDogYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5TSElQTUVOVFMpLmNvdW50RG9jdW1lbnRzKHsgc2hvcCB9KSwKcGVuZGluZzogYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5TSElQTUVOVFMpLmNvdW50RG9jdW1lbnRzKHsgc2hvcCwgc3RhdHVzOiAmcXVvdDtwZW5kaW5nJnF1b3Q7IH0pLApsZWFkc1F1YWxpZmllZDogYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5MRUFEUykuY291bnREb2N1bWVudHMoeyBzaG9wLCBzdGF0dXM6ICZxdW90O1FVQUxJRklFRCZxdW90OyB9KSwKbGVhZHNGbGFnZ2VkOiBhd2FpdCBkYi5jb2xsZWN0aW9uKGNvbGxlY3Rpb25zLkxFQURTKS5jb3VudERvY3VtZW50cyh7IHNob3AsIHN0YXR1czogJnF1b3Q7RkxBR0dFRF9MRUdBTF9SRVZJRVcmcXVvdDsgfSksCn0pKSgpLApdKTsKCnJldHVybiBqc29uKHsgc2hpcG1lbnRzLCBsZWFkcywgc3RhdHMgfSk7Cn0KCmZ1bmN0aW9uIFN0YXRDYXJkKHsgbGFiZWwsIHZhbHVlIH06IHsgbGFiZWw6IHN0cmluZzsgdmFsdWU6IG51bWJlciB9KSB7CnJldHVybiAoCiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MTAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7Ym9keVNtJnF1b3Q7IHRvbmU9JnF1b3Q7c3ViZHVlZCZxdW90OyZndDt7bGFiZWx9Jmx0Oy9UZXh0Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7aGVhZGluZ0xnJnF1b3Q7Jmd0O3t2YWx1ZX0mbHQ7L1RleHQmZ3Q7CiZsdDsvQmxvY2tTdGFjayZndDsKJmx0Oy9DYXJkJmd0OwopOwp9CgpleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBEYXNoYm9hcmQoKSB7CmNvbnN0IHsgc2hpcG1lbnRzLCBsZWFkcywgc3RhdHMgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKcmV0dXJuICgKJmx0O1BhZ2UgdGl0bGU9JnF1b3Q7U2hhcmVkcm9wLlRyYWRpbmcgJm1kYXNoOyBPcGVyYXRpb25zIERhc2hib2FyZCZxdW90OyZndDsKJmx0O0xheW91dCZndDsKJmx0O0xheW91dC5TZWN0aW9uJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7NDAwJnF1b3Q7Jmd0OwombHQ7SW5saW5lR3JpZCBjb2x1bW5zPXs0fSBnYXA9JnF1b3Q7MzAwJnF1b3Q7Jmd0OwombHQ7U3RhdENhcmQgbGFiZWw9JnF1b3Q7U2hpcG1lbnRzJnF1b3Q7IHZhbHVlPXtzdGF0cy5zaGlwbWVudHNUb3RhbH0gLyZndDsKJmx0O1N0YXRDYXJkIGxhYmVsPSZxdW90O1BlbmRpbmcmcXVvdDsgdmFsdWU9e3N0YXRzLnBlbmRpbmd9IC8mZ3Q7CiZsdDtTdGF0Q2FyZCBsYWJlbD0mcXVvdDtMZWFkcyBRdWFsaWZpZWQmcXVvdDsgdmFsdWU9e3N0YXRzLmxlYWRzUXVhbGlmaWVkfSAvJmd0OwombHQ7U3RhdENhcmQgbGFiZWw9JnF1b3Q7TGVnYWwgRmxhZ3MmcXVvdDsgdmFsdWU9e3N0YXRzLmxlYWRzRmxhZ2dlZH0gLyZndDsKJmx0Oy9JbmxpbmVHcmlkJmd0OwoKKTsKfQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvcm91dGVzL2FwcC5sZWFkcy50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyBqc29uLCB0eXBlIExvYWRlckZ1bmN0aW9uQXJncywgdHlwZSBBY3Rpb25GdW5jdGlvbkFyZ3MgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vbm9kZSZxdW90OzsKaW1wb3J0IHsgdXNlTG9hZGVyRGF0YSwgdXNlRmV0Y2hlciB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9yZWFjdCZxdW90OzsKaW1wb3J0IHsgUGFnZSwgTGF5b3V0LCBDYXJkLCBUZXh0LCBCbG9ja1N0YWNrLCBJbmRleFRhYmxlLCBCYWRnZSwgQnV0dG9uLCBCYW5uZXIsIFRleHRGaWVsZCwgU2VsZWN0IH0gZnJvbSAmcXVvdDtAc2hvcGlmeS9wb2xhcmlzJnF1b3Q7OwppbXBvcnQgeyB1c2VTdGF0ZSB9IGZyb20gJnF1b3Q7cmVhY3QmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IExlYWRFdmFsdWF0b3IsIHBlcnNpc3RMZWFkLCBsaXN0TGVhZHMsIGF1ZGl0TG9nIH0gZnJvbSAmcXVvdDsuLi9saWIvZ2xpZS5zZXJ2ZXImcXVvdDs7CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBMb2FkZXJGdW5jdGlvbkFyZ3MpIHsKY29uc3QgeyBzZXNzaW9uIH0gPSBhd2FpdCBhdXRoZW50aWNhdGUuYWRtaW4ocmVxdWVzdCk7CmNvbnN0IGxlYWRzID0gYXdhaXQgbGlzdExlYWRzKHNlc3Npb24uc2hvcCk7CnJldHVybiBqc29uKHsgbGVhZHMgfSk7Cn0KCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBhY3Rpb24oeyByZXF1ZXN0IH06IEFjdGlvbkZ1bmN0aW9uQXJncykgewpjb25zdCB7IHNlc3Npb24gfSA9IGF3YWl0IGF1dGhlbnRpY2F0ZS5hZG1pbihyZXF1ZXN0KTsKY29uc3QgZm9ybSA9IGF3YWl0IHJlcXVlc3QuZm9ybURhdGEoKTsKY29uc3QgcmF3ID0gewp0aXRsZTogU3RyaW5nKGZvcm0uZ2V0KCZxdW90O3RpdGxlJnF1b3Q7KSB8fCAmcXVvdDsmcXVvdDspLApzb3VyY2U6IFN0cmluZyhmb3JtLmdldCgmcXVvdDtzb3VyY2UmcXVvdDspIHx8ICZxdW90O01hbnVhbCZxdW90OyksCmNhdGVnb3J5OiBTdHJpbmcoZm9ybS5nZXQoJnF1b3Q7Y2F0ZWdvcnkmcXVvdDspIHx8ICZxdW90O2ZyZWlnaHQmcXVvdDspLApkZXNjcmlwdGlvbjogU3RyaW5nKGZvcm0uZ2V0KCZxdW90O2Rlc2NyaXB0aW9uJnF1b3Q7KSB8fCAmcXVvdDsmcXVvdDspLAp2YWx1ZV9neWQ6IE51bWJlcihmb3JtLmdldCgmcXVvdDt2YWx1ZV9neWQmcXVvdDspIHx8IDApLAp9Owpjb25zdCBldmFsdWF0b3IgPSBuZXcgTGVhZEV2YWx1YXRvcigpOwpjb25zdCBldmFsdWF0ZWQgPSBldmFsdWF0b3IuZXZhbHVhdGVMZWFkKHNlc3Npb24uc2hvcCwgcmF3KTsKYXdhaXQgcGVyc2lzdExlYWQoZXZhbHVhdGVkKTsKYXdhaXQgYXVkaXRMb2coc2Vzc2lvbi5zaG9wLCAmcXVvdDtsZWFkLmNyZWF0ZWQmcXVvdDssIHsgbGVhZF9pZDogZXZhbHVhdGVkLmxlYWRfaWQgfSk7CnJldHVybiBqc29uKHsgbGVhZDogZXZhbHVhdGVkIH0pOwp9CgpleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBMZWFkc1BhZ2UoKSB7CmNvbnN0IHsgbGVhZHMgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKY29uc3QgZmV0Y2hlciA9IHVzZUZldGNoZXImbHQ7dHlwZW9mIGFjdGlvbiZndDsoKTsKY29uc3QgW3RpdGxlLCBzZXRUaXRsZV0gPSB1c2VTdGF0ZSgmcXVvdDsmcXVvdDspOwpjb25zdCBbc291cmNlLCBzZXRTb3VyY2VdID0gdXNlU3RhdGUoJnF1b3Q7TWFudWFsJnF1b3Q7KTsKY29uc3QgW2NhdGVnb3J5LCBzZXRDYXRlZ29yeV0gPSB1c2VTdGF0ZSgmcXVvdDtmcmVpZ2h0IHByb2N1cmVtZW50JnF1b3Q7KTsKY29uc3QgW2Rlc2NyaXB0aW9uLCBzZXREZXNjcmlwdGlvbl0gPSB1c2VTdGF0ZSgmcXVvdDsmcXVvdDspOwpjb25zdCBbdmFsdWUsIHNldFZhbHVlXSA9IHVzZVN0YXRlKCZxdW90OzAmcXVvdDspOwoKY29uc3Qgc3VibWl0ID0gKCkgPSZndDsgZmV0Y2hlci5zdWJtaXQoeyB0aXRsZSwgc291cmNlLCBjYXRlZ29yeSwgZGVzY3JpcHRpb24sIHZhbHVlX2d5ZDogdmFsdWUgfSwgeyBtZXRob2Q6ICZxdW90O3Bvc3QmcXVvdDsgfSk7CgpyZXR1cm4gKAombHQ7UGFnZSB0aXRsZT0mcXVvdDtHTElFIFY3LjAgJm1kYXNoOyBMZWFkIEVuZ2luZSZxdW90OyBzdWJ0aXRsZT0mcXVvdDtEdWFsLWVudGl0eSByb3V0aW5nICZtaWRkb3Q7IDYtc3RyZWFtIHdhdGVyZmFsbCAmbWlkZG90OyBsZWdhbCBmaXJld2FsbCZxdW90OyZndDsKJmx0O0xheW91dCZndDsKJmx0O0xheW91dC5TZWN0aW9uJmd0OwombHQ7QmFubmVyIHRvbmU9JnF1b3Q7aW5mbyZxdW90OyZndDtOb24tUFNQIEFCU09MVVRFLiBJbnZlc3RtZW50IC8gc2VjdXJpdGllcyAvIGN1c3RvZHkgdGVybXMgYXV0by1lc2NhbGF0ZSB0byBNcy4gRXN0aGVyIE9zYm9ybmUuJmx0Oy9CYW5uZXImZ3Q7CiZsdDsvTGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtMYXlvdXQuU2VjdGlvbiZndDsKJmx0O0NhcmQmZ3Q7CiZsdDtCbG9ja1N0YWNrIGdhcD0mcXVvdDszMDAmcXVvdDsmZ3Q7CiZsdDtUZXh0IGFzPSZxdW90O2gyJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7aGVhZGluZ01kJnF1b3Q7Jmd0O0V2YWx1YXRlIE5ldyBMZWFkJmx0Oy9UZXh0Jmd0OwombHQ7VGV4dEZpZWxkIGxhYmVsPSZxdW90O1RpdGxlJnF1b3Q7IHZhbHVlPXt0aXRsZX0gb25DaGFuZ2U9e3NldFRpdGxlfSBhdXRvQ29tcGxldGU9JnF1b3Q7b2ZmJnF1b3Q7IC8mZ3Q7CiZsdDtTZWxlY3QgbGFiZWw9JnF1b3Q7U291cmNlJnF1b3Q7IHZhbHVlPXtzb3VyY2V9IG9uQ2hhbmdlPXtzZXRTb3VyY2V9IG9wdGlvbnM9e1sKeyBsYWJlbDogJnF1b3Q7TWFudWFsJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7TWFudWFsJnF1b3Q7IH0sCnsgbGFiZWw6ICZxdW90O0dvdmVybm1lbnQgVGVuZGVyIEJvYXJkJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7R292ZXJubWVudCBUZW5kZXIgQm9hcmQmcXVvdDsgfSwKeyBsYWJlbDogJnF1b3Q7TkdPJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7TkdPJnF1b3Q7IH0sCnsgbGFiZWw6ICZxdW90O1dlYmhvb2smcXVvdDssIHZhbHVlOiAmcXVvdDtXZWJob29rJnF1b3Q7IH0sCl19IC8mZ3Q7CiZsdDtTZWxlY3QgbGFiZWw9JnF1b3Q7Q2F0ZWdvcnkmcXVvdDsgdmFsdWU9e2NhdGVnb3J5fSBvbkNoYW5nZT17c2V0Q2F0ZWdvcnl9IG9wdGlvbnM9e1sKeyBsYWJlbDogJnF1b3Q7RnJlaWdodCBQcm9jdXJlbWVudCZxdW90OywgdmFsdWU6ICZxdW90O2ZyZWlnaHQgcHJvY3VyZW1lbnQmcXVvdDsgfSwKeyBsYWJlbDogJnF1b3Q7Q3Jvc3MtQm9yZGVyJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7Y3Jvc3MtYm9yZGVyIGN1c3RvbXMmcXVvdDsgfSwKeyBsYWJlbDogJnF1b3Q7Q29tbWVyY2lhbCBUZW5kZXImcXVvdDssIHZhbHVlOiAmcXVvdDt0ZW5kZXIgcHJvY3VyZW1lbnQmcXVvdDsgfSwKeyBsYWJlbDogJnF1b3Q7R3JhbnQgLyBTb2NpYWwgSW1wYWN0JnF1b3Q7LCB2YWx1ZTogJnF1b3Q7Z3JhbnQgc29jaWFsIGltcGFjdCZxdW90OyB9LAp7IGxhYmVsOiAmcXVvdDtJbnZlc3RtZW50IElucXVpcnkmcXVvdDssIHZhbHVlOiAmcXVvdDtpbnZlc3RtZW50JnF1b3Q7IH0sCl19IC8mZ3Q7CiZsdDtUZXh0RmllbGQgbGFiZWw9JnF1b3Q7RGVzY3JpcHRpb24mcXVvdDsgdmFsdWU9e2Rlc2NyaXB0aW9ufSBvbkNoYW5nZT17c2V0RGVzY3JpcHRpb259IG11bHRpbGluZT17NH0gYXV0b0NvbXBsZXRlPSZxdW90O29mZiZxdW90OyAvJmd0OwombHQ7VGV4dEZpZWxkIGxhYmVsPSZxdW90O0VzdGltYXRlZCBWYWx1ZSAoR1lEKSZxdW90OyB0eXBlPSZxdW90O251bWJlciZxdW90OyB2YWx1ZT17dmFsdWV9IG9uQ2hhbmdlPXtzZXRWYWx1ZX0gYXV0b0NvbXBsZXRlPSZxdW90O29mZiZxdW90OyAvJmd0OwombHQ7QnV0dG9uIHZhcmlhbnQ9JnF1b3Q7cHJpbWFyeSZxdW90OyBvbkNsaWNrPXtzdWJtaXR9IGxvYWRpbmc9e2ZldGNoZXIuc3RhdGUgPT09ICZxdW90O3N1Ym1pdHRpbmcmcXVvdDt9Jmd0O0V2YWx1YXRlIExlYWQmbHQ7L0J1dHRvbiZndDsKCik7Cn0KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL3JvdXRlcy9hcHAuc2hpcG1lbnRzLnRzeCZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IGpzb24sIHR5cGUgTG9hZGVyRnVuY3Rpb25BcmdzIH0gZnJvbSAmcXVvdDtAcmVtaXgtcnVuL25vZGUmcXVvdDs7CmltcG9ydCB7IHVzZUxvYWRlckRhdGEsIExpbmsgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vcmVhY3QmcXVvdDs7CmltcG9ydCB7IFBhZ2UsIExheW91dCwgQ2FyZCwgSW5kZXhUYWJsZSwgQmFkZ2UsIEJ1dHRvbiwgVGV4dCwgQmxvY2tTdGFjayB9IGZyb20gJnF1b3Q7QHNob3BpZnkvcG9sYXJpcyZxdW90OzsKaW1wb3J0IHsgYXV0aGVudGljYXRlIH0gZnJvbSAmcXVvdDsuLi9zaG9waWZ5LnNlcnZlciZxdW90OzsKaW1wb3J0IHsgY29ubmVjdFRvRGF0YWJhc2UsIGNvbGxlY3Rpb25zIH0gZnJvbSAmcXVvdDsuLi9saWIvbW9uZ29kYi5zZXJ2ZXImcXVvdDs7CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBMb2FkZXJGdW5jdGlvbkFyZ3MpIHsKY29uc3QgeyBzZXNzaW9uIH0gPSBhd2FpdCBhdXRoZW50aWNhdGUuYWRtaW4ocmVxdWVzdCk7CmNvbnN0IHsgZGIgfSA9IGF3YWl0IGNvbm5lY3RUb0RhdGFiYXNlKCk7CmNvbnN0IHNoaXBtZW50cyA9IGF3YWl0IGRiLmNvbGxlY3Rpb24oY29sbGVjdGlvbnMuU0hJUE1FTlRTKS5maW5kKHsgc2hvcDogc2Vzc2lvbi5zaG9wIH0pLnNvcnQoeyBjcmVhdGVkQXQ6IC0xIH0pLmxpbWl0KDIwMCkudG9BcnJheSgpOwpyZXR1cm4ganNvbih7IHNoaXBtZW50cyB9KTsKfQoKZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gU2hpcG1lbnRzKCkgewpjb25zdCB7IHNoaXBtZW50cyB9ID0gdXNlTG9hZGVyRGF0YSZsdDt0eXBlb2YgbG9hZGVyJmd0OygpOwpyZXR1cm4gKAombHQ7UGFnZSB0aXRsZT0mcXVvdDtTaGlwbWVudHMmcXVvdDsKcHJpbWFyeUFjdGlvbj17Jmx0O0xpbmsgdG89JnF1b3Q7L2FwcC9zaGlwbWVudHMvbmV3JnF1b3Q7Jmd0OyZsdDtCdXR0b24gdmFyaWFudD0mcXVvdDtwcmltYXJ5JnF1b3Q7Jmd0O0NyZWF0ZSBTaGlwbWVudCZsdDsvQnV0dG9uJmd0OyZsdDsvTGluayZndDt9Jmd0OwombHQ7TGF5b3V0Jmd0OwombHQ7TGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MjAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtoMiZxdW90OyB2YXJpYW50PSZxdW90O2hlYWRpbmdNZCZxdW90OyZndDtBbGwgU2hpcG1lbnRzJmx0Oy9UZXh0Jmd0OwombHQ7SW5kZXhUYWJsZSByZXNvdXJjZU5hbWU9e3sgc2luZ3VsYXI6ICZxdW90O3NoaXBtZW50JnF1b3Q7LCBwbHVyYWw6ICZxdW90O3NoaXBtZW50cyZxdW90OyB9fSBpdGVtQ291bnQ9e3NoaXBtZW50cy5sZW5ndGh9CmhlYWRpbmdzPXtbeyB0aXRsZTogJnF1b3Q7Q2xpZW50IElEJnF1b3Q7IH0sIHsgdGl0bGU6ICZxdW90O1BpY2t1cCZxdW90OyB9LCB7IHRpdGxlOiAmcXVvdDtEZWxpdmVyeSZxdW90OyB9LCB7IHRpdGxlOiAmcXVvdDtHZW5kZXIgUHJlZiZxdW90OyB9LCB7IHRpdGxlOiAmcXVvdDtTdGF0dXMmcXVvdDsgfV19CnNlbGVjdGFibGU9e2ZhbHNlfSZndDsKe3NoaXBtZW50cy5tYXAoKHM6IGFueSwgaSkgPSZndDsgKAombHQ7SW5kZXhUYWJsZS5Sb3cgaWQ9e3MuX2lkfSBrZXk9e3MuX2lkfSBwb3NpdGlvbj17aX0mZ3Q7CiZsdDtJbmRleFRhYmxlLkNlbGwmZ3Q7e3MuY2xpZW50SWR9Jmx0Oy9JbmRleFRhYmxlLkNlbGwmZ3Q7CiZsdDtJbmRleFRhYmxlLkNlbGwmZ3Q7e3MucGlja3VwPy5sb2NhdGlvbiB8fCAmcXVvdDsmbWRhc2g7JnF1b3Q7fSZsdDsvSW5kZXhUYWJsZS5DZWxsJmd0OwombHQ7SW5kZXhUYWJsZS5DZWxsJmd0O3tzLmRlbGl2ZXJ5Py5sb2NhdGlvbiB8fCAmcXVvdDsmbWRhc2g7JnF1b3Q7fSZsdDsvSW5kZXhUYWJsZS5DZWxsJmd0OwombHQ7SW5kZXhUYWJsZS5DZWxsJmd0O3tzLnByZWZlcmVuY2VzPy5kcml2ZXJHZW5kZXIgfHwgJnF1b3Q7YW55JnF1b3Q7fSZsdDsvSW5kZXhUYWJsZS5DZWxsJmd0OwombHQ7SW5kZXhUYWJsZS5DZWxsJmd0OwombHQ7QmFkZ2UgdG9uZT17cy5zdGF0dXMgPT09ICZxdW90O2RlbGl2ZXJlZCZxdW90OyA/ICZxdW90O3N1Y2Nlc3MmcXVvdDsgOiBzLnN0YXR1cyA9PT0gJnF1b3Q7aW5fdHJhbnNpdCZxdW90OyA/ICZxdW90O2luZm8mcXVvdDsgOiAmcXVvdDthdHRlbnRpb24mcXVvdDt9Jmd0O3tzLnN0YXR1c30mbHQ7L0JhZGdlJmd0OwombHQ7L0luZGV4VGFibGUuQ2VsbCZndDsKJmx0Oy9JbmRleFRhYmxlLlJvdyZndDsKKSl9CiZsdDsvSW5kZXhUYWJsZSZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwombHQ7L0NhcmQmZ3Q7CiZsdDsvTGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDsvTGF5b3V0Jmd0OwombHQ7L1BhZ2UmZ3Q7Cik7Cn0KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL3JvdXRlcy9hcHAuc2hpcG1lbnRzLm5ldy50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyBqc29uLCB0eXBlIEFjdGlvbkZ1bmN0aW9uQXJncywgdHlwZSBMb2FkZXJGdW5jdGlvbkFyZ3MgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vbm9kZSZxdW90OzsKaW1wb3J0IHsgdXNlQWN0aW9uRGF0YSwgdXNlTmF2aWdhdGUsIHVzZVN1Ym1pdCB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9yZWFjdCZxdW90OzsKaW1wb3J0IHsgUGFnZSwgTGF5b3V0LCBDYXJkLCBUZXh0LCBCbG9ja1N0YWNrLCBUZXh0RmllbGQsIFNlbGVjdCwgQnV0dG9uLCBSYWRpb0J1dHRvbiwgQmFubmVyIH0gZnJvbSAmcXVvdDtAc2hvcGlmeS9wb2xhcmlzJnF1b3Q7OwppbXBvcnQgeyB1c2VTdGF0ZSB9IGZyb20gJnF1b3Q7cmVhY3QmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IGNvbm5lY3RUb0RhdGFiYXNlLCBjb2xsZWN0aW9ucyB9IGZyb20gJnF1b3Q7Li4vbGliL21vbmdvZGIuc2VydmVyJnF1b3Q7OwppbXBvcnQgeyB2NCBhcyB1dWlkdjQgfSBmcm9tICZxdW90O3V1aWQmcXVvdDs7CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBMb2FkZXJGdW5jdGlvbkFyZ3MpIHsKYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpyZXR1cm4ganNvbih7IG9rOiB0cnVlIH0pOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gYWN0aW9uKHsgcmVxdWVzdCB9OiBBY3Rpb25GdW5jdGlvbkFyZ3MpIHsKY29uc3QgeyBzZXNzaW9uIH0gPSBhd2FpdCBhdXRoZW50aWNhdGUuYWRtaW4ocmVxdWVzdCk7CmNvbnN0IGZvcm0gPSBhd2FpdCByZXF1ZXN0LmZvcm1EYXRhKCk7CmNvbnN0IHsgZGIgfSA9IGF3YWl0IGNvbm5lY3RUb0RhdGFiYXNlKCk7CmNvbnN0IHNoaXBtZW50ID0gewpfaWQ6IHV1aWR2NCgpLCBzaG9wOiBzZXNzaW9uLnNob3AsCmNsaWVudElkOiBzaGlwbWVudF8ke0RhdGUubm93KCl9XyR7dXVpZHY0KCl9LApkZXNjcmlwdGlvbjogZm9ybS5nZXQoJnF1b3Q7ZGVzY3JpcHRpb24mcXVvdDspLApjYXJnb1R5cGU6IGZvcm0uZ2V0KCZxdW90O2NhcmdvVHlwZSZxdW90OyksCndlaWdodDogTnVtYmVyKGZvcm0uZ2V0KCZxdW90O3dlaWdodCZxdW90OykgfHwgMCksCnBpY2t1cDogeyBsb2NhdGlvbjogZm9ybS5nZXQoJnF1b3Q7cGlja3VwTG9jYXRpb24mcXVvdDspLCBkYXRlOiBmb3JtLmdldCgmcXVvdDtwaWNrdXBEYXRlJnF1b3Q7KSB9LApkZWxpdmVyeTogeyBsb2NhdGlvbjogZm9ybS5nZXQoJnF1b3Q7ZGVsaXZlcnlMb2NhdGlvbiZxdW90OyksIGRhdGU6IGZvcm0uZ2V0KCZxdW90O2RlbGl2ZXJ5RGF0ZSZxdW90OykgfSwKcHJlZmVyZW5jZXM6IHsgZHJpdmVyR2VuZGVyOiBmb3JtLmdldCgmcXVvdDtkcml2ZXJHZW5kZXImcXVvdDspIHx8ICZxdW90O2FueSZxdW90OyB9LApzdGF0dXM6ICZxdW90O3BlbmRpbmcmcXVvdDssIHN5bmNTdGF0dXM6ICZxdW90O3N5bmNlZCZxdW90OywKY3JlYXRlZEF0OiBuZXcgRGF0ZSgpLCB1cGRhdGVkQXQ6IG5ldyBEYXRlKCksCn07CmF3YWl0IGRiLmNvbGxlY3Rpb24oY29sbGVjdGlvbnMuU0hJUE1FTlRTKS5pbnNlcnRPbmUoc2hpcG1lbnQpOwpyZXR1cm4ganNvbih7IHN1Y2Nlc3M6IHRydWUsIHNoaXBtZW50IH0pOwp9CgpleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBOZXdTaGlwbWVudCgpIHsKY29uc3QgYWN0aW9uRGF0YSA9IHVzZUFjdGlvbkRhdGEmbHQ7dHlwZW9mIGFjdGlvbiZndDsoKTsKY29uc3Qgc3VibWl0ID0gdXNlU3VibWl0KCk7CmNvbnN0IG5hdmlnYXRlID0gdXNlTmF2aWdhdGUoKTsKY29uc3QgW2dlbmRlciwgc2V0R2VuZGVyXSA9IHVzZVN0YXRlKCZxdW90O2FueSZxdW90Oyk7CgppZiAoYWN0aW9uRGF0YT8uc3VjY2VzcykgbmF2aWdhdGUoJnF1b3Q7L2FwcC9zaGlwbWVudHMmcXVvdDspOwoKcmV0dXJuICgKJmx0O1BhZ2UgdGl0bGU9JnF1b3Q7Q3JlYXRlIFNoaXBtZW50JnF1b3Q7IGJhY2tBY3Rpb249e3sgY29udGVudDogJnF1b3Q7QmFjayZxdW90Oywgb25BY3Rpb246ICgpID0mZ3Q7IG5hdmlnYXRlKCZxdW90Oy9hcHAmcXVvdDspIH19Jmd0OwombHQ7TGF5b3V0Jmd0OwombHQ7TGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtmb3JtIG1ldGhvZD0mcXVvdDtwb3N0JnF1b3Q7IG9uU3VibWl0PXsoZSkgPSZndDsgeyBlLnByZXZlbnREZWZhdWx0KCk7IHN1Ym1pdChlLmN1cnJlbnRUYXJnZXQpOyB9fSZndDsKJmx0O0Jsb2NrU3RhY2sgZ2FwPSZxdW90OzQwMCZxdW90OyZndDsKJmx0O0NhcmQmZ3Q7CiZsdDtCbG9ja1N0YWNrIGdhcD0mcXVvdDszMDAmcXVvdDsmZ3Q7CiZsdDtUZXh0IGFzPSZxdW90O2gyJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7aGVhZGluZ01kJnF1b3Q7Jmd0O1NoaXBtZW50IERldGFpbHMmbHQ7L1RleHQmZ3Q7CiZsdDtUZXh0RmllbGQgbGFiZWw9JnF1b3Q7RGVzY3JpcHRpb24mcXVvdDsgbmFtZT0mcXVvdDtkZXNjcmlwdGlvbiZxdW90OyBhdXRvQ29tcGxldGU9JnF1b3Q7b2ZmJnF1b3Q7IHJlcXVpcmVkIC8mZ3Q7CiZsdDtTZWxlY3QgbGFiZWw9JnF1b3Q7Q2FyZ28gVHlwZSZxdW90OyBuYW1lPSZxdW90O2NhcmdvVHlwZSZxdW90OyBvcHRpb25zPXtbCnsgbGFiZWw6ICZxdW90O0dlbmVyYWwgR29vZHMmcXVvdDssIHZhbHVlOiAmcXVvdDtnZW5lcmFsJnF1b3Q7IH0sCnsgbGFiZWw6ICZxdW90O0NvbnN0cnVjdGlvbiBNYXRlcmlhbHMmcXVvdDssIHZhbHVlOiAmcXVvdDtjb25zdHJ1Y3Rpb24mcXVvdDsgfSwKeyBsYWJlbDogJnF1b3Q7QWdyaWN1bHR1cmFsIFByb2R1Y3RzJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7YWdyaWN1bHR1cmUmcXVvdDsgfSwKeyBsYWJlbDogJnF1b3Q7TWVkaWNhbCBTdXBwbGllcyZxdW90OywgdmFsdWU6ICZxdW90O21lZGljYWwmcXVvdDsgfSwKXX0gLyZndDsKJmx0O1RleHRGaWVsZCBsYWJlbD0mcXVvdDtXZWlnaHQgKGtnKSZxdW90OyBuYW1lPSZxdW90O3dlaWdodCZxdW90OyB0eXBlPSZxdW90O251bWJlciZxdW90OyBhdXRvQ29tcGxldGU9JnF1b3Q7b2ZmJnF1b3Q7IC8mZ3Q7CiZsdDsvQmxvY2tTdGFjayZndDsKJmx0Oy9DYXJkJmd0OwombHQ7Q2FyZCZndDsKJmx0O0Jsb2NrU3RhY2sgZ2FwPSZxdW90OzMwMCZxdW90OyZndDsKJmx0O1RleHQgYXM9JnF1b3Q7aDImcXVvdDsgdmFyaWFudD0mcXVvdDtoZWFkaW5nTWQmcXVvdDsmZ3Q7UGlja3VwICZhbXA7IERlbGl2ZXJ5Jmx0Oy9UZXh0Jmd0OwombHQ7VGV4dEZpZWxkIGxhYmVsPSZxdW90O1BpY2t1cCBMb2NhdGlvbiZxdW90OyBuYW1lPSZxdW90O3BpY2t1cExvY2F0aW9uJnF1b3Q7IGF1dG9Db21wbGV0ZT0mcXVvdDtvZmYmcXVvdDsgcmVxdWlyZWQgLyZndDsKJmx0O1RleHRGaWVsZCBsYWJlbD0mcXVvdDtQaWNrdXAgRGF0ZSZxdW90OyBuYW1lPSZxdW90O3BpY2t1cERhdGUmcXVvdDsgdHlwZT0mcXVvdDtkYXRlJnF1b3Q7IGF1dG9Db21wbGV0ZT0mcXVvdDtvZmYmcXVvdDsgLyZndDsKJmx0O1RleHRGaWVsZCBsYWJlbD0mcXVvdDtEZWxpdmVyeSBMb2NhdGlvbiZxdW90OyBuYW1lPSZxdW90O2RlbGl2ZXJ5TG9jYXRpb24mcXVvdDsgYXV0b0NvbXBsZXRlPSZxdW90O29mZiZxdW90OyByZXF1aXJlZCAvJmd0OwombHQ7VGV4dEZpZWxkIGxhYmVsPSZxdW90O0RlbGl2ZXJ5IERhdGUmcXVvdDsgbmFtZT0mcXVvdDtkZWxpdmVyeURhdGUmcXVvdDsgdHlwZT0mcXVvdDtkYXRlJnF1b3Q7IGF1dG9Db21wbGV0ZT0mcXVvdDtvZmYmcXVvdDsgLyZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwombHQ7L0NhcmQmZ3Q7CiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MzAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtoMiZxdW90OyB2YXJpYW50PSZxdW90O2hlYWRpbmdNZCZxdW90OyZndDtTYWZldHkgUHJlZmVyZW5jZXMmbHQ7L1RleHQmZ3Q7CiZsdDtCYW5uZXIgdG9uZT0mcXVvdDtpbmZvJnF1b3Q7Jmd0O1NlbGVjdCBwcmVmZXJyZWQgZHJpdmVyIGdlbmRlci4gVmVyaWZpZWQgYWdhaW5zdCBLWUMgZHVyaW5nIG9uYm9hcmRpbmcuJmx0Oy9CYW5uZXImZ3Q7CiZsdDtSYWRpb0J1dHRvbiBsYWJlbD0mcXVvdDtObyBwcmVmZXJlbmNlJnF1b3Q7IG5hbWU9JnF1b3Q7ZyZxdW90OyBjaGVja2VkPXtnZW5kZXIgPT09ICZxdW90O2FueSZxdW90O30gb25DaGFuZ2U9eygpID0mZ3Q7IHNldEdlbmRlcigmcXVvdDthbnkmcXVvdDspfSAvJmd0OwombHQ7UmFkaW9CdXR0b24gbGFiZWw9JnF1b3Q7RmVtYWxlIGRyaXZlcnMgb25seSZxdW90OyBuYW1lPSZxdW90O2cmcXVvdDsgY2hlY2tlZD17Z2VuZGVyID09PSAmcXVvdDtmZW1hbGUmcXVvdDt9IG9uQ2hhbmdlPXsoKSA9Jmd0OyBzZXRHZW5kZXIoJnF1b3Q7ZmVtYWxlJnF1b3Q7KX0gLyZndDsKJmx0O1JhZGlvQnV0dG9uIGxhYmVsPSZxdW90O01hbGUgZHJpdmVycyBvbmx5JnF1b3Q7IG5hbWU9JnF1b3Q7ZyZxdW90OyBjaGVja2VkPXtnZW5kZXIgPT09ICZxdW90O21hbGUmcXVvdDt9IG9uQ2hhbmdlPXsoKSA9Jmd0OyBzZXRHZW5kZXIoJnF1b3Q7bWFsZSZxdW90Oyl9IC8mZ3Q7CiZsdDtpbnB1dCB0eXBlPSZxdW90O2hpZGRlbiZxdW90OyBuYW1lPSZxdW90O2RyaXZlckdlbmRlciZxdW90OyB2YWx1ZT17Z2VuZGVyfSAvJmd0OwombHQ7L0Jsb2NrU3RhY2smZ3Q7CiZsdDsvQ2FyZCZndDsKJmx0O0J1dHRvbiB2YXJpYW50PSZxdW90O3ByaW1hcnkmcXVvdDsgc3VibWl0Jmd0O0NyZWF0ZSBTaGlwbWVudCZsdDsvQnV0dG9uJmd0OwombHQ7L0Jsb2NrU3RhY2smZ3Q7CiZsdDsvZm9ybSZndDsKJmx0Oy9MYXlvdXQuU2VjdGlvbiZndDsKJmx0Oy9MYXlvdXQmZ3Q7CiZsdDsvUGFnZSZndDsKKTsKfQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvcm91dGVzL2FwcC52aXAtZHJpdmVycy50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyBqc29uLCB0eXBlIExvYWRlckZ1bmN0aW9uQXJncywgdHlwZSBBY3Rpb25GdW5jdGlvbkFyZ3MgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vbm9kZSZxdW90OzsKaW1wb3J0IHsgdXNlTG9hZGVyRGF0YSwgdXNlRmV0Y2hlciB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9yZWFjdCZxdW90OzsKaW1wb3J0IHsgUGFnZSwgTGF5b3V0LCBDYXJkLCBUZXh0LCBCbG9ja1N0YWNrLCBCdXR0b24sIFJlc291cmNlTGlzdCwgUmVzb3VyY2VJdGVtLCBCYWRnZSwgQmFubmVyLCBTZWxlY3QsIFRleHRGaWVsZCB9IGZyb20gJnF1b3Q7QHNob3BpZnkvcG9sYXJpcyZxdW90OzsKaW1wb3J0IHsgdXNlU3RhdGUgfSBmcm9tICZxdW90O3JlYWN0JnF1b3Q7OwppbXBvcnQgeyBhdXRoZW50aWNhdGUgfSBmcm9tICZxdW90Oy4uL3Nob3BpZnkuc2VydmVyJnF1b3Q7OwppbXBvcnQgeyBjb25uZWN0VG9EYXRhYmFzZSwgY29sbGVjdGlvbnMgfSBmcm9tICZxdW90Oy4uL2xpYi9tb25nb2RiLnNlcnZlciZxdW90OzsKaW1wb3J0IHsgc2VuZFB1c2hOb3RpZmljYXRpb24gfSBmcm9tICZxdW90Oy4uL2xpYi9ub3RpZmljYXRpb25zLnNlcnZlciZxdW90OzsKaW1wb3J0IHsgdjQgYXMgdXVpZHY0IH0gZnJvbSAmcXVvdDt1dWlkJnF1b3Q7OwoKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRlcih7IHJlcXVlc3QgfTogTG9hZGVyRnVuY3Rpb25BcmdzKSB7CmNvbnN0IHsgc2Vzc2lvbiB9ID0gYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpjb25zdCB7IGRiIH0gPSBhd2FpdCBjb25uZWN0VG9EYXRhYmFzZSgpOwpjb25zdCBkcml2ZXJzID0gYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5EUklWRVJTKS5maW5kKHsgc2hvcDogc2Vzc2lvbi5zaG9wLCBzdGF0dXM6ICZxdW90O2FjdGl2ZSZxdW90OyB9KS5zb3J0KHsgJnF1b3Q7cmF0aW5ncy5hdmVyYWdlJnF1b3Q7OiAtMSB9KS5saW1pdCgyNSkudG9BcnJheSgpOwpyZXR1cm4ganNvbih7IGRyaXZlcnMgfSk7Cn0KCmV4cG9ydCBhc3luYyBmdW5jdGlvbiBhY3Rpb24oeyByZXF1ZXN0IH06IEFjdGlvbkZ1bmN0aW9uQXJncykgewpjb25zdCB7IHNlc3Npb24gfSA9IGF3YWl0IGF1dGhlbnRpY2F0ZS5hZG1pbihyZXF1ZXN0KTsKY29uc3QgeyBkcml2ZXJJZCwgc2hpcG1lbnRJZCwgbWVzc2FnZSB9ID0gYXdhaXQgcmVxdWVzdC5qc29uKCk7CmNvbnN0IHsgZGIgfSA9IGF3YWl0IGNvbm5lY3RUb0RhdGFiYXNlKCk7CmNvbnN0IGRyaXZlciA9IGF3YWl0IGRiLmNvbGxlY3Rpb24oY29sbGVjdGlvbnMuRFJJVkVSUykuZmluZE9uZSh7IF9pZDogZHJpdmVySWQsIHNob3A6IHNlc3Npb24uc2hvcCB9KTsKaWYgKCFkcml2ZXIpIHJldHVybiBqc29uKHsgZXJyb3I6ICZxdW90O0RyaXZlciBub3QgZm91bmQmcXVvdDsgfSwgeyBzdGF0dXM6IDQwNCB9KTsKY29uc3QgY29udGFjdCA9IHsKX2lkOiB1dWlkdjQoKSwgc2hvcDogc2Vzc2lvbi5zaG9wLCBkcml2ZXJJZCwgc2hpcG1lbnRJZCwgbWVzc2FnZSwKc3RhdHVzOiAmcXVvdDtwZW5kaW5nJnF1b3Q7LCBjcmVhdGVkQXQ6IG5ldyBEYXRlKCksCmV4cGlyZXNBdDogbmV3IERhdGUoRGF0ZS5ub3coKSArIDI0ICogNjAgKiA2MCAqIDEwMDApLAp9Owphd2FpdCBkYi5jb2xsZWN0aW9uKGNvbGxlY3Rpb25zLlZJUF9DT05UQUNUUykuaW5zZXJ0T25lKGNvbnRhY3QpOwpjb25zdCBub3RpZmljYXRpb24gPSBhd2FpdCBzZW5kUHVzaE5vdGlmaWNhdGlvbih7CnVzZXJJZDogZHJpdmVyLnVzZXJJZCB8fCBkcml2ZXIuX2lkLAp0aXRsZTogJnF1b3Q78J+agCBWSVAgU2hpcG1lbnQgUmVxdWVzdCZxdW90OywKYm9keTogVklQIGNsaWVudCByZXF1ZXN0ZWQgeW91IGZvciBzaGlwbWVudCAke3NoaXBtZW50SWR9LApkYXRhOiB7IHR5cGU6ICZxdW90O3ZpcF9yZXF1ZXN0JnF1b3Q7LCBjb250YWN0SWQ6IGNvbnRhY3QuX2lkLCBzaGlwbWVudElkIH0sCnBlcnNpc3RVbnRpbERlbGl2ZXJlZDogdHJ1ZSwgZXhwaXJlc0F0OiBjb250YWN0LmV4cGlyZXNBdCwKfSk7CnJldHVybiBqc29uKHsgY29udGFjdCwgbm90aWZpY2F0aW9uIH0pOwp9CgpleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBWSVBEcml2ZXJzKCkgewpjb25zdCB7IGRyaXZlcnMgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKY29uc3QgZmV0Y2hlciA9IHVzZUZldGNoZXIoKTsKY29uc3QgW3NlbGVjdGVkLCBzZXRTZWxlY3RlZF0gPSB1c2VTdGF0ZSZsdDthbnkmZ3Q7KG51bGwpOwpjb25zdCBbbWVzc2FnZSwgc2V0TWVzc2FnZV0gPSB1c2VTdGF0ZSgmcXVvdDsmcXVvdDspOwpjb25zdCBbdmVoaWNsZSwgc2V0VmVoaWNsZV0gPSB1c2VTdGF0ZSgmcXVvdDthbGwmcXVvdDspOwpjb25zdCBbZ2VuZGVyLCBzZXRHZW5kZXJdID0gdXNlU3RhdGUoJnF1b3Q7YW55JnF1b3Q7KTsKCmNvbnN0IGZpbHRlcmVkID0gZHJpdmVycy5maWx0ZXIoKGQ6IGFueSkgPSZndDsgewppZiAodmVoaWNsZSAhPT0gJnF1b3Q7YWxsJnF1b3Q7ICZhbXA7JmFtcDsgZC52ZWhpY2xlVHlwZSAhPT0gdmVoaWNsZSkgcmV0dXJuIGZhbHNlOwppZiAoZ2VuZGVyICE9PSAmcXVvdDthbnkmcXVvdDsgJmFtcDsmYW1wOyBkLmdlbmRlciAhPT0gZ2VuZGVyKSByZXR1cm4gZmFsc2U7CnJldHVybiB0cnVlOwp9KTsKCmNvbnN0IHNlbmQgPSAoKSA9Jmd0OyB7CmlmICghc2VsZWN0ZWQpIHJldHVybjsKZmV0Y2hlci5zdWJtaXQoSlNPTi5zdHJpbmdpZnkoeyBkcml2ZXJJZDogc2VsZWN0ZWQuX2lkLCBzaGlwbWVudElkOiAmcXVvdDtkZW1vJnF1b3Q7LCBtZXNzYWdlIH0pLAp7IG1ldGhvZDogJnF1b3Q7cG9zdCZxdW90OywgZW5jVHlwZTogJnF1b3Q7YXBwbGljYXRpb24vanNvbiZxdW90OyB9KTsKfTsKCnJldHVybiAoCiZsdDtQYWdlIHRpdGxlPSZxdW90O1ZJUCBEcml2ZXIgQ29udGFjdCZxdW90OyBzdWJ0aXRsZT0mcXVvdDtSZWFjaCBkcml2ZXJzIGV2ZW4gd2hlbiB0aGV5J3JlIG9mZmxpbmUmcXVvdDsmZ3Q7CiZsdDtMYXlvdXQmZ3Q7CiZsdDtMYXlvdXQuU2VjdGlvbiZndDsKJmx0O0Jhbm5lciB0b25lPSZxdW90O2luZm8mcXVvdDsmZ3Q7UmVxdWVzdHMgYXJlIHF1ZXVlZCBhbmQgZGVsaXZlcmVkIHdoZW4gdGhlIGRyaXZlciByZWNvbm5lY3RzLiBOb24tUFNQOiBubyBmdW5kcyBoZWxkLiZsdDsvQmFubmVyJmd0OwombHQ7L0xheW91dC5TZWN0aW9uJmd0OwombHQ7TGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MzAwJnF1b3Q7Jmd0OwombHQ7ZGl2IHN0eWxlPXt7IGRpc3BsYXk6ICZxdW90O2ZsZXgmcXVvdDssIGdhcDogMTIgfX0mZ3Q7CiZsdDtTZWxlY3QgbGFiZWw9JnF1b3Q7VmVoaWNsZSZxdW90OyB2YWx1ZT17dmVoaWNsZX0gb25DaGFuZ2U9e3NldFZlaGljbGV9IG9wdGlvbnM9e1sKeyBsYWJlbDogJnF1b3Q7QWxsJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7YWxsJnF1b3Q7IH0sIHsgbGFiZWw6ICZxdW90O0NhciZxdW90OywgdmFsdWU6ICZxdW90O2NhciZxdW90OyB9LAp7IGxhYmVsOiAmcXVvdDtNb3RvcmN5Y2xlJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7bW90b3JjeWNsZSZxdW90OyB9LCB7IGxhYmVsOiAmcXVvdDtUcnVjayZxdW90OywgdmFsdWU6ICZxdW90O3RydWNrJnF1b3Q7IH0sCl19IC8mZ3Q7CiZsdDtTZWxlY3QgbGFiZWw9JnF1b3Q7R2VuZGVyJnF1b3Q7IHZhbHVlPXtnZW5kZXJ9IG9uQ2hhbmdlPXtzZXRHZW5kZXJ9IG9wdGlvbnM9e1sKeyBsYWJlbDogJnF1b3Q7QW55JnF1b3Q7LCB2YWx1ZTogJnF1b3Q7YW55JnF1b3Q7IH0sIHsgbGFiZWw6ICZxdW90O0ZlbWFsZSZxdW90OywgdmFsdWU6ICZxdW90O2ZlbWFsZSZxdW90OyB9LCB7IGxhYmVsOiAmcXVvdDtNYWxlJnF1b3Q7LCB2YWx1ZTogJnF1b3Q7bWFsZSZxdW90OyB9LApdfSAvJmd0OwombHQ7L2RpdiZndDsKJmx0O1Jlc291cmNlTGlzdApyZXNvdXJjZU5hbWU9e3sgc2luZ3VsYXI6ICZxdW90O2RyaXZlciZxdW90OywgcGx1cmFsOiAmcXVvdDtkcml2ZXJzJnF1b3Q7IH19Cml0ZW1zPXtmaWx0ZXJlZC5tYXAoKGQ6IGFueSkgPSZndDsgKHsKaWQ6IGQuX2lkLCBuYW1lOiBkLm5hbWUsIHJhdGluZzogZC5yYXRpbmdzPy5hdmVyYWdlIHx8IDAsCnRyaXBzOiBkLmNvbXBsZXRlZFRyaXBzIHx8IDAsIHZlaGljbGVUeXBlOiBkLnZlaGljbGVUeXBlLCBzdGF0dXM6IGQuc3RhdHVzLAp9KSl9CnJlbmRlckl0ZW09eyhpdGVtKSA9Jmd0OyAoCiZsdDtSZXNvdXJjZUl0ZW0gaWQ9e2l0ZW0uaWR9IG9uQ2xpY2s9eygpID0mZ3Q7IHNldFNlbGVjdGVkKGl0ZW0pfSBhY2Nlc3NpYmlsaXR5TGFiZWw9e1NlbGVjdCAke2l0ZW0ubmFtZX19Jmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MTAwJnF1b3Q7Jmd0OwombHQ7ZGl2IHN0eWxlPXt7IGRpc3BsYXk6ICZxdW90O2ZsZXgmcXVvdDssIGdhcDogOCwgYWxpZ25JdGVtczogJnF1b3Q7Y2VudGVyJnF1b3Q7IH19Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtzcGFuJnF1b3Q7IGZvbnRXZWlnaHQ9JnF1b3Q7c2VtaWJvbGQmcXVvdDsmZ3Q7e2l0ZW0ubmFtZX0mbHQ7L1RleHQmZ3Q7CiZsdDtCYWRnZSB0b25lPXtpdGVtLnN0YXR1cyA9PT0gJnF1b3Q7YWN0aXZlJnF1b3Q7ID8gJnF1b3Q7c3VjY2VzcyZxdW90OyA6ICZxdW90O2F0dGVudGlvbiZxdW90O30mZ3Q7e2l0ZW0uc3RhdHVzfSZsdDsvQmFkZ2UmZ3Q7CiZsdDsvZGl2Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7Ym9keVNtJnF1b3Q7IHRvbmU9JnF1b3Q7c3ViZHVlZCZxdW90OyZndDsK4q2QIHtpdGVtLnJhdGluZy50b0ZpeGVkKDEpfSAmbWlkZG90OyB7aXRlbS50cmlwc30gdHJpcHMgJm1pZGRvdDsge2l0ZW0udmVoaWNsZVR5cGV9CiZsdDsvVGV4dCZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwombHQ7L1Jlc291cmNlSXRlbSZndDsKKX0KLyZndDsKe3NlbGVjdGVkICZhbXA7JmFtcDsgKAombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MzAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtoMiZxdW90OyB2YXJpYW50PSZxdW90O2hlYWRpbmdNZCZxdW90OyZndDtNZXNzYWdlIHRvIHtzZWxlY3RlZC5uYW1lfSZsdDsvVGV4dCZndDsKJmx0O1RleHRGaWVsZCBsYWJlbD0mcXVvdDtQZXJzb25hbCBtZXNzYWdlJnF1b3Q7IHZhbHVlPXttZXNzYWdlfSBvbkNoYW5nZT17c2V0TWVzc2FnZX0gbXVsdGlsaW5lPXszfSBhdXRvQ29tcGxldGU9JnF1b3Q7b2ZmJnF1b3Q7IC8mZ3Q7CiZsdDtCdXR0b24gdmFyaWFudD0mcXVvdDtwcmltYXJ5JnF1b3Q7IG9uQ2xpY2s9e3NlbmR9IGxvYWRpbmc9e2ZldGNoZXIuc3RhdGUgPT09ICZxdW90O3N1Ym1pdHRpbmcmcXVvdDt9Jmd0O1NlbmQgVklQIFJlcXVlc3QmbHQ7L0J1dHRvbiZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwopfQombHQ7L0Jsb2NrU3RhY2smZ3Q7CiZsdDsvQ2FyZCZndDsKJmx0Oy9MYXlvdXQuU2VjdGlvbiZndDsKJmx0Oy9MYXlvdXQmZ3Q7CiZsdDsvUGFnZSZndDsKKTsKfQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvcm91dGVzL2FwcC5kcml2ZXJzLnRzeCZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IGpzb24sIHR5cGUgTG9hZGVyRnVuY3Rpb25BcmdzIH0gZnJvbSAmcXVvdDtAcmVtaXgtcnVuL25vZGUmcXVvdDs7CmltcG9ydCB7IHVzZUxvYWRlckRhdGEgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vcmVhY3QmcXVvdDs7CmltcG9ydCB7IFBhZ2UsIExheW91dCwgQ2FyZCwgVGV4dCwgQmxvY2tTdGFjaywgSW5kZXhUYWJsZSwgQmFkZ2UgfSBmcm9tICZxdW90O0BzaG9waWZ5L3BvbGFyaXMmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IGNvbm5lY3RUb0RhdGFiYXNlLCBjb2xsZWN0aW9ucyB9IGZyb20gJnF1b3Q7Li4vbGliL21vbmdvZGIuc2VydmVyJnF1b3Q7OwoKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRlcih7IHJlcXVlc3QgfTogTG9hZGVyRnVuY3Rpb25BcmdzKSB7CmNvbnN0IHsgc2Vzc2lvbiB9ID0gYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpjb25zdCB7IGRiIH0gPSBhd2FpdCBjb25uZWN0VG9EYXRhYmFzZSgpOwpjb25zdCBkcml2ZXJzID0gYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5EUklWRVJTKS5maW5kKHsgc2hvcDogc2Vzc2lvbi5zaG9wIH0pLnNvcnQoeyBjcmVhdGVkQXQ6IC0xIH0pLnRvQXJyYXkoKTsKcmV0dXJuIGpzb24oeyBkcml2ZXJzIH0pOwp9CgpleHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBEcml2ZXJzKCkgewpjb25zdCB7IGRyaXZlcnMgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKcmV0dXJuICgKJmx0O1BhZ2UgdGl0bGU9JnF1b3Q7RHJpdmVyIEZsZWV0JnF1b3Q7Jmd0OwombHQ7TGF5b3V0Jmd0OwombHQ7TGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MjAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtoMiZxdW90OyB2YXJpYW50PSZxdW90O2hlYWRpbmdNZCZxdW90OyZndDtBbGwgRHJpdmVycyZsdDsvVGV4dCZndDsKJmx0O0luZGV4VGFibGUgcmVzb3VyY2VOYW1lPXt7IHNpbmd1bGFyOiAmcXVvdDtkcml2ZXImcXVvdDssIHBsdXJhbDogJnF1b3Q7ZHJpdmVycyZxdW90OyB9fSBpdGVtQ291bnQ9e2RyaXZlcnMubGVuZ3RofQpoZWFkaW5ncz17W3sgdGl0bGU6ICZxdW90O05hbWUmcXVvdDsgfSwgeyB0aXRsZTogJnF1b3Q7VmVoaWNsZSZxdW90OyB9LCB7IHRpdGxlOiAmcXVvdDtSYXRpbmcmcXVvdDsgfSwgeyB0aXRsZTogJnF1b3Q7VHJpcHMmcXVvdDsgfSwgeyB0aXRsZTogJnF1b3Q7U3RhdHVzJnF1b3Q7IH1dfQpzZWxlY3RhYmxlPXtmYWxzZX0mZ3Q7Cntkcml2ZXJzLm1hcCgoZDogYW55LCBpKSA9Jmd0OyAoCiZsdDtJbmRleFRhYmxlLlJvdyBpZD17ZC5faWR9IGtleT17ZC5faWR9IHBvc2l0aW9uPXtpfSZndDsKJmx0O0luZGV4VGFibGUuQ2VsbCZndDt7ZC5uYW1lfSZsdDsvSW5kZXhUYWJsZS5DZWxsJmd0OwombHQ7SW5kZXhUYWJsZS5DZWxsJmd0O3tkLnZlaGljbGVUeXBlIHx8ICZxdW90OyZtZGFzaDsmcXVvdDt9Jmx0Oy9JbmRleFRhYmxlLkNlbGwmZ3Q7CiZsdDtJbmRleFRhYmxlLkNlbGwmZ3Q74q2QIHsoZC5yYXRpbmdzPy5hdmVyYWdlIHx8IDApLnRvRml4ZWQoMSl9Jmx0Oy9JbmRleFRhYmxlLkNlbGwmZ3Q7CiZsdDtJbmRleFRhYmxlLkNlbGwmZ3Q7e2QuY29tcGxldGVkVHJpcHMgfHwgMH0mbHQ7L0luZGV4VGFibGUuQ2VsbCZndDsKJmx0O0luZGV4VGFibGUuQ2VsbCZndDsKJmx0O0JhZGdlIHRvbmU9e2Quc3RhdHVzID09PSAmcXVvdDthY3RpdmUmcXVvdDsgPyAmcXVvdDtzdWNjZXNzJnF1b3Q7IDogJnF1b3Q7YXR0ZW50aW9uJnF1b3Q7fSZndDt7ZC5zdGF0dXN9Jmx0Oy9CYWRnZSZndDsKJmx0Oy9JbmRleFRhYmxlLkNlbGwmZ3Q7CiZsdDsvSW5kZXhUYWJsZS5Sb3cmZ3Q7CikpfQombHQ7L0luZGV4VGFibGUmZ3Q7CiZsdDsvQmxvY2tTdGFjayZndDsKJmx0Oy9DYXJkJmd0OwombHQ7L0xheW91dC5TZWN0aW9uJmd0OwombHQ7L0xheW91dCZndDsKJmx0Oy9QYWdlJmd0OwopOwp9CkVPRgoKd3JpdGVmaWxlICZxdW90OyRST09UL2FwcC9yb3V0ZXMvYXBwLmdhbWVwbGF5LnRzeCZxdW90OyAmbHQ7Jmx0OydFT0YnCmltcG9ydCB7IGpzb24sIHR5cGUgTG9hZGVyRnVuY3Rpb25BcmdzIH0gZnJvbSAmcXVvdDtAcmVtaXgtcnVuL25vZGUmcXVvdDs7CmltcG9ydCB7IHVzZUxvYWRlckRhdGEgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vcmVhY3QmcXVvdDs7CmltcG9ydCB7IFBhZ2UsIExheW91dCwgQ2FyZCwgVGV4dCwgQmxvY2tTdGFjaywgSW5saW5lR3JpZCwgQmFkZ2UgfSBmcm9tICZxdW90O0BzaG9waWZ5L3BvbGFyaXMmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7Cgpjb25zdCBGTE9XID0gWwp7IG46IDEsIHRpdGxlOiAmcXVvdDtQbGF5ZXIgU2lnbnMgVXAmcXVvdDssICAgICAgICAgICAgICAgZGVzYzogJnF1b3Q7UHJvZmlsZSBzdG9yZWQgdmlhIFNoYXJlZHJvcC5UcmFkaW5nIEFQSSZxdW90OyB9LAp7IG46IDIsIHRpdGxlOiAmcXVvdDtDb3VudHJ5IFNlbGVjdGVkJnF1b3Q7LCAgICAgICAgICAgICAgZGVzYzogJnF1b3Q7UnVsZXMgSlNPTiBsb2FkZWQ6IEd1eWFuYSAvIEJyYXppbCZxdW90OyB9LAp7IG46IDMsIHRpdGxlOiAmcXVvdDtTaW11bGF0aW9uIEVuZ2luZSBSdW5zJnF1b3Q7LCAgICAgICAgZGVzYzogJnF1b3Q7V3JpdHRlbiArIFByYWN0aWNhbCBUZXN0cyAmbWlkZG90OyBHVFRJIFN0YW5kYXJkcyZxdW90OyB9LAp7IG46IDQsIHRpdGxlOiAmcXVvdDtMaXZlIENoYXQgKyBTaGFyZWRyb3AgRk0mcXVvdDssICAgICAgZGVzYzogJnF1b3Q7TXVzaWMgKyByZWFsLXRpbWUgY29tbXMmcXVvdDsgfSwKeyBuOiA1LCB0aXRsZTogJnF1b3Q7UmlkZSBCb29raW5nICsgQ2FyZ28gTG9naXN0aWNzJnF1b3Q7LGRlc2M6ICZxdW90Ozg1JSBEcml2ZXIgU2hhcmUmcXVvdDsgfSwKeyBuOiA2LCB0aXRsZTogJnF1b3Q7UVIgVmVyaWZpY2F0aW9uJnF1b3Q7LCAgICAgICAgICAgICAgIGRlc2M6ICZxdW90O1doYXRzQXBwIC8gU2VrYWkgcm9sZXBsYXkmcXVvdDsgfSwKeyBuOiA3LCB0aXRsZTogJnF1b3Q7QUkgRGVidWdnaW5nJnF1b3Q7LCAgICAgICAgICAgICAgICAgIGRlc2M6ICZxdW90O0tlZXBzIGdhbWVwbGF5IGVycm9yLWZyZWUmcXVvdDsgfSwKeyBuOiA4LCB0aXRsZTogJnF1b3Q7UHJvZ3Jlc3MgJnJhcnI7IFJlYWwgT25ib2FyZGluZyZxdW90OywgICAgZGVzYzogJnF1b3Q7RmVlZHMgaW50byBkcml2ZXIgcGlwZWxpbmUgJm1pZGRvdDsgRmxlZXQgZXhwYW5zaW9uJnF1b3Q7IH0sCl07CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBMb2FkZXJGdW5jdGlvbkFyZ3MpIHsKYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpyZXR1cm4ganNvbih7IGZsb3c6IEZMT1cgfSk7Cn0KCmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIEdhbWVwbGF5KCkgewpjb25zdCB7IGZsb3cgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKcmV0dXJuICgKJmx0O1BhZ2UgdGl0bGU9JnF1b3Q7R2FtZXBsYXkgRmxvdyAmbWRhc2g7IDggU3RlcHMgdG8gUmVhbCBFYXJuaW5ncyZxdW90OyBzdWJ0aXRsZT0mcXVvdDtCcmFuZGVkICZtaWRkb3Q7IEx1eHVyeSAmbWlkZG90OyBTY2FsYWJsZSZxdW90OyZndDsKJmx0O0xheW91dCZndDsKJmx0O0xheW91dC5TZWN0aW9uJmd0OwombHQ7SW5saW5lR3JpZCBjb2x1bW5zPXs0fSBnYXA9JnF1b3Q7MzAwJnF1b3Q7Jmd0Owp7Zmxvdy5tYXAoKHMpID0mZ3Q7ICgKJmx0O0NhcmQga2V5PXtzLm59Jmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MTAwJnF1b3Q7Jmd0OwombHQ7QmFkZ2UgdG9uZT0mcXVvdDtpbmZvJnF1b3Q7Jmd0O1NURVAge3Mubn0mbHQ7L0JhZGdlJmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IGZvbnRXZWlnaHQ9JnF1b3Q7c2VtaWJvbGQmcXVvdDsmZ3Q7e3MudGl0bGV9Jmx0Oy9UZXh0Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7Ym9keVNtJnF1b3Q7IHRvbmU9JnF1b3Q7c3ViZHVlZCZxdW90OyZndDt7cy5kZXNjfSZsdDsvVGV4dCZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwombHQ7L0NhcmQmZ3Q7CikpfQombHQ7L0lubGluZUdyaWQmZ3Q7CiZsdDsvTGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDsvTGF5b3V0Jmd0OwombHQ7L1BhZ2UmZ3Q7Cik7Cn0KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL3JvdXRlcy9hcHAuZHJpdmluZy1zY2hvb2wudHN4JnF1b3Q7ICZsdDsmbHQ7J0VPRicKaW1wb3J0IHsganNvbiwgdHlwZSBMb2FkZXJGdW5jdGlvbkFyZ3MsIHR5cGUgQWN0aW9uRnVuY3Rpb25BcmdzIH0gZnJvbSAmcXVvdDtAcmVtaXgtcnVuL25vZGUmcXVvdDs7CmltcG9ydCB7IHVzZUxvYWRlckRhdGEsIHVzZUZldGNoZXIgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vcmVhY3QmcXVvdDs7CmltcG9ydCB7IFBhZ2UsIExheW91dCwgQ2FyZCwgVGV4dCwgQmxvY2tTdGFjaywgQnV0dG9uLCBJbmxpbmVHcmlkLCBCYWRnZSwgQmFubmVyLCBSYWRpb0J1dHRvbiB9IGZyb20gJnF1b3Q7QHNob3BpZnkvcG9sYXJpcyZxdW90OzsKaW1wb3J0IHsgdXNlU3RhdGUgfSBmcm9tICZxdW90O3JlYWN0JnF1b3Q7OwppbXBvcnQgeyBhdXRoZW50aWNhdGUgfSBmcm9tICZxdW90Oy4uL3Nob3BpZnkuc2VydmVyJnF1b3Q7OwppbXBvcnQgeyBHWUhVR19NT0RVTEVTLCBHVFRJX1NBTVBMRV9RVUVTVElPTlMsIHN0YXJ0RHJpdmluZ1Nlc3Npb24sIHN1Ym1pdFRoZW9yeVRlc3QgfSBmcm9tICZxdW90Oy4uL2xpYi9kcml2aW5nLXNjaG9vbC5zZXJ2ZXImcXVvdDs7CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBMb2FkZXJGdW5jdGlvbkFyZ3MpIHsKYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpyZXR1cm4ganNvbih7IG1vZHVsZXM6IEdZSFVHX01PRFVMRVMsIHF1ZXN0aW9uczogR1RUSV9TQU1QTEVfUVVFU1RJT05TIH0pOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gYWN0aW9uKHsgcmVxdWVzdCB9OiBBY3Rpb25GdW5jdGlvbkFyZ3MpIHsKY29uc3QgeyBzZXNzaW9uIH0gPSBhd2FpdCBhdXRoZW50aWNhdGUuYWRtaW4ocmVxdWVzdCk7CmNvbnN0IGZvcm0gPSBhd2FpdCByZXF1ZXN0LmZvcm1EYXRhKCk7CmNvbnN0IGludGVudCA9IFN0cmluZyhmb3JtLmdldCgmcXVvdDtpbnRlbnQmcXVvdDspKTsKaWYgKGludGVudCA9PT0gJnF1b3Q7c3RhcnQmcXVvdDspIHsKY29uc3QgY291bnRyeSA9IChTdHJpbmcoZm9ybS5nZXQoJnF1b3Q7Y291bnRyeSZxdW90OykpIHx8ICZxdW90O0dZJnF1b3Q7KSBhcyAmcXVvdDtHWSZxdW90OyB8ICZxdW90O0JSJnF1b3Q7Owpjb25zdCBzID0gYXdhaXQgc3RhcnREcml2aW5nU2Vzc2lvbihzZXNzaW9uLnNob3AsICZxdW90O3BsYXllci1kZW1vJnF1b3Q7LCBjb3VudHJ5KTsKcmV0dXJuIGpzb24oeyBzZXNzaW9uOiBzIH0pOwp9CmlmIChpbnRlbnQgPT09ICZxdW90O3N1Ym1pdCZxdW90Oykgewpjb25zdCBzZXNzaW9uSWQgPSBTdHJpbmcoZm9ybS5nZXQoJnF1b3Q7c2Vzc2lvbklkJnF1b3Q7KSk7CmNvbnN0IGFuc3dlcnM6IFJlY29yZCZsdDtzdHJpbmcsIHN0cmluZyZndDsgPSB7fTsKZm9yIChjb25zdCBbaywgdl0gb2YgZm9ybS5lbnRyaWVzKCkpIGlmIChrLnN0YXJ0c1dpdGgoJnF1b3Q7cV8mcXVvdDspKSBhbnN3ZXJzW2sucmVwbGFjZSgmcXVvdDtxXyZxdW90OywgJnF1b3Q7JnF1b3Q7KV0gPSBTdHJpbmcodik7CmNvbnN0IHJlc3VsdCA9IGF3YWl0IHN1Ym1pdFRoZW9yeVRlc3Qoc2Vzc2lvbklkLCBhbnN3ZXJzKTsKcmV0dXJuIGpzb24oeyByZXN1bHQgfSk7Cn0KcmV0dXJuIGpzb24oeyBlcnJvcjogJnF1b3Q7VW5rbm93biBpbnRlbnQmcXVvdDsgfSwgeyBzdGF0dXM6IDQwMCB9KTsKfQoKZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gRHJpdmluZ1NjaG9vbCgpIHsKY29uc3QgeyBtb2R1bGVzLCBxdWVzdGlvbnMgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKY29uc3QgZmV0Y2hlciA9IHVzZUZldGNoZXImbHQ7YW55Jmd0OygpOwpjb25zdCBbYW5zd2Vycywgc2V0QW5zd2Vyc10gPSB1c2VTdGF0ZSZsdDtSZWNvcmQmbHQ7c3RyaW5nLCBzdHJpbmcmZ3Q7Jmd0Oyh7fSk7CgpyZXR1cm4gKAombHQ7UGFnZSB0aXRsZT0mcXVvdDtHWS1IVUcgRHJpdmluZyBTY2hvb2wgU2ltdWxhdGlvbiZxdW90OyBzdWJ0aXRsZT0mcXVvdDtDZXJ0aWZpZWQgYnkgR1RUSSBTdGFuZGFyZHMgJm1pZGRvdDsgU3VwZXJhcHAgTW9kdWxlIFYxLjAmcXVvdDsmZ3Q7CiZsdDtMYXlvdXQmZ3Q7CiZsdDtMYXlvdXQuU2VjdGlvbiZndDsKJmx0O0Jhbm5lciB0b25lPSZxdW90O2luZm8mcXVvdDsmZ3Q7OSBjb3JlIG1vZHVsZXMuIE9mZmxpbmUtZmlyc3QuIFdvcmtzIGluIFJlZ2lvbiA5IHdpdGhvdXQgY2VsbHVsYXIuJmx0Oy9CYW5uZXImZ3Q7CiZsdDsvTGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtMYXlvdXQuU2VjdGlvbiZndDsKJmx0O0NhcmQmZ3Q7CiZsdDtCbG9ja1N0YWNrIGdhcD0mcXVvdDszMDAmcXVvdDsmZ3Q7CiZsdDtUZXh0IGFzPSZxdW90O2gyJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7aGVhZGluZ01kJnF1b3Q7Jmd0O01vZHVsZSBNYXAmbHQ7L1RleHQmZ3Q7CiZsdDtJbmxpbmVHcmlkIGNvbHVtbnM9ezN9IGdhcD0mcXVvdDszMDAmcXVvdDsmZ3Q7CntPYmplY3QuZW50cmllcyhtb2R1bGVzKS5tYXAoKFtrZXksIG1dOiBhbnkpID0mZ3Q7ICgKJmx0O0NhcmQga2V5PXtrZXl9Jmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MTAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IGZvbnRXZWlnaHQ9JnF1b3Q7c2VtaWJvbGQmcXVvdDsmZ3Q7e20ubGFiZWx9Jmx0Oy9UZXh0Jmd0OwombHQ7QmFkZ2UgdG9uZT0mcXVvdDtzdWNjZXNzJnF1b3Q7Jmd0O3dlaWdodCB7bS53ZWlnaHR9JSZsdDsvQmFkZ2UmZ3Q7CiZsdDsvQmxvY2tTdGFjayZndDsKJmx0Oy9DYXJkJmd0OwopKX0KJmx0Oy9JbmxpbmVHcmlkJmd0OwombHQ7L0Jsb2NrU3RhY2smZ3Q7CiZsdDsvQ2FyZCZndDsKJmx0Oy9MYXlvdXQuU2VjdGlvbiZndDsKJmx0O0xheW91dC5TZWN0aW9uJmd0OwombHQ7Q2FyZCZndDsKJmx0O0Jsb2NrU3RhY2sgZ2FwPSZxdW90OzMwMCZxdW90OyZndDsKJmx0O1RleHQgYXM9JnF1b3Q7aDImcXVvdDsgdmFyaWFudD0mcXVvdDtoZWFkaW5nTWQmcXVvdDsmZ3Q7VGhlb3J5IFRlc3QgJm1kYXNoOyBHVFRJIFN0YW5kYXJkcyZsdDsvVGV4dCZndDsKe3F1ZXN0aW9ucy5tYXAoKHEsIGlkeCkgPSZndDsgKAombHQ7QmxvY2tTdGFjayBrZXk9e3EuaWR9IGdhcD0mcXVvdDsxMDAmcXVvdDsmZ3Q7CiZsdDtUZXh0IGFzPSZxdW90O3AmcXVvdDsgZm9udFdlaWdodD0mcXVvdDtzZW1pYm9sZCZxdW90OyZndDtRe2lkeCArIDF9LiB7cS5xdWVzdGlvbn0mbHQ7L1RleHQmZ3Q7CntPYmplY3QuZW50cmllcyhxLm9wdGlvbnMpLm1hcCgoW2ssIHZdKSA9Jmd0OyAoCiZsdDtSYWRpb0J1dHRvbiBrZXk9e2t9IGxhYmVsPXske2t9KSAke3Z9fSBuYW1lPXtxXyR7cS5pZH19CmNoZWNrZWQ9e2Fuc3dlcnNbcS5pZF0gPT09IGt9IG9uQ2hhbmdlPXsoKSA9Jmd0OyBzZXRBbnN3ZXJzKHsgLi4uYW5zd2VycywgW3EuaWRdOiBrIH0pfSAvJmd0OwopKX0KJmx0Oy9CbG9ja1N0YWNrJmd0OwopKX0KJmx0O0J1dHRvbiB2YXJpYW50PSZxdW90O3ByaW1hcnkmcXVvdDsgb25DbGljaz17KCkgPSZndDsKZmV0Y2hlci5zdWJtaXQoCnsgaW50ZW50OiAmcXVvdDtzdWJtaXQmcXVvdDssIHNlc3Npb25JZDogJnF1b3Q7ZGVtbyZxdW90OywKLi4uT2JqZWN0LmZyb21FbnRyaWVzKE9iamVjdC5lbnRyaWVzKGFuc3dlcnMpLm1hcCgoW2ssIHZdKSA9Jmd0OyBbcV8ke2t9LCB2XSkpIH0sCnsgbWV0aG9kOiAmcXVvdDtwb3N0JnF1b3Q7IH0KKX0mZ3Q7U3VibWl0IFRlc3QmbHQ7L0J1dHRvbiZndDsKe2ZldGNoZXIuZGF0YT8ucmVzdWx0ICZhbXA7JmFtcDsgKAombHQ7QmFubmVyIHRvbmU9e2ZldGNoZXIuZGF0YS5yZXN1bHQucGFzc2VkID8gJnF1b3Q7c3VjY2VzcyZxdW90OyA6ICZxdW90O2NyaXRpY2FsJnF1b3Q7fQp0aXRsZT17U2NvcmU6ICR7ZmV0Y2hlci5kYXRhLnJlc3VsdC5zY29yZX0lICZtaWRkb3Q7ICR7ZmV0Y2hlci5kYXRhLnJlc3VsdC5wYXNzZWQgPyAmcXVvdDtQQVNTRUQmcXVvdDsgOiAmcXVvdDtGQUlMRUQmcXVvdDt9fSZndDsKe2ZldGNoZXIuZGF0YS5yZXN1bHQuZmVlZGJhY2t9CiZsdDsvQmFubmVyJmd0OwopfQombHQ7L0Jsb2NrU3RhY2smZ3Q7CiZsdDsvQ2FyZCZndDsKJmx0Oy9MYXlvdXQuU2VjdGlvbiZndDsKJmx0Oy9MYXlvdXQmZ3Q7CiZsdDsvUGFnZSZndDsKKTsKfQpFT0YKCndyaXRlZmlsZSAmcXVvdDskUk9PVC9hcHAvcm91dGVzL2FwcC5wYXltZW50cy50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyBqc29uLCB0eXBlIExvYWRlckZ1bmN0aW9uQXJncywgdHlwZSBBY3Rpb25GdW5jdGlvbkFyZ3MgfSBmcm9tICZxdW90O0ByZW1peC1ydW4vbm9kZSZxdW90OzsKaW1wb3J0IHsgdXNlTG9hZGVyRGF0YSwgdXNlRmV0Y2hlciB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9yZWFjdCZxdW90OzsKaW1wb3J0IHsgUGFnZSwgTGF5b3V0LCBDYXJkLCBUZXh0LCBCbG9ja1N0YWNrLCBCdXR0b24sIEJhbm5lciwgQmFkZ2UsIElubGluZUdyaWQgfSBmcm9tICZxdW90O0BzaG9waWZ5L3BvbGFyaXMmcXVvdDs7CmltcG9ydCB7IGF1dGhlbnRpY2F0ZSB9IGZyb20gJnF1b3Q7Li4vc2hvcGlmeS5zZXJ2ZXImcXVvdDs7CmltcG9ydCB7IFBMQVRGT1JNX0FDQ0VTU19GRUVTLCBjcmVhdGVQMlBIYW5kb2ZmLCBhc3NlcnROb25DdXN0b2R5IH0gZnJvbSAmcXVvdDsuLi9saWIvcGF5bWVudHMuc2VydmVyJnF1b3Q7OwoKY29uc3QgUElYX0NIQVZFID0gJnF1b3Q7NzEwODYyODcyNDAmcXVvdDs7CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZGVyKHsgcmVxdWVzdCB9OiBMb2FkZXJGdW5jdGlvbkFyZ3MpIHsKYXdhaXQgYXV0aGVudGljYXRlLmFkbWluKHJlcXVlc3QpOwpyZXR1cm4ganNvbih7IGZlZXM6IFBMQVRGT1JNX0FDQ0VTU19GRUVTLCBwaXhDaGF2ZTogUElYX0NIQVZFIH0pOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gYWN0aW9uKHsgcmVxdWVzdCB9OiBBY3Rpb25GdW5jdGlvbkFyZ3MpIHsKY29uc3QgeyBzZXNzaW9uIH0gPSBhd2FpdCBhdXRoZW50aWNhdGUuYWRtaW4ocmVxdWVzdCk7CmNvbnN0IGZvcm0gPSBhd2FpdCByZXF1ZXN0LmZvcm1EYXRhKCk7CmNvbnN0IGludGVudCA9IFN0cmluZyhmb3JtLmdldCgmcXVvdDtpbnRlbnQmcXVvdDspKTsKdHJ5IHsKaWYgKGludGVudCA9PT0gJnF1b3Q7aGFuZG9mZiZxdW90Oykgewpjb25zdCBhbW91bnQgPSBOdW1iZXIoZm9ybS5nZXQoJnF1b3Q7YW1vdW50JnF1b3Q7KSB8fCAwKTsKY29uc3QgY3VycmVuY3kgPSAoU3RyaW5nKGZvcm0uZ2V0KCZxdW90O2N1cnJlbmN5JnF1b3Q7KSkgfHwgJnF1b3Q7R1lEJnF1b3Q7KSBhcyAmcXVvdDtHWUQmcXVvdDsgfCAmcXVvdDtCUkwmcXVvdDs7CmFzc2VydE5vbkN1c3RvZHkoY3JlYXRlIGhhbmRvZmYgYW1vdW50PSR7YW1vdW50fSBjdXJyZW5jeT0ke2N1cnJlbmN5fSk7CmNvbnN0IGxpbmsgPSBhd2FpdCBjcmVhdGVQMlBIYW5kb2ZmKHNlc3Npb24uc2hvcCwgU3RyaW5nKGZvcm0uZ2V0KCZxdW90O29yZGVySWQmcXVvdDspKSwgYW1vdW50LCBjdXJyZW5jeSk7CnJldHVybiBqc29uKHsgbGluayB9KTsKfQp9IGNhdGNoIChlOiBhbnkpIHsKcmV0dXJuIGpzb24oeyBlcnJvcjogZS5tZXNzYWdlIH0sIHsgc3RhdHVzOiA0MDAgfSk7Cn0KcmV0dXJuIGpzb24oeyBlcnJvcjogJnF1b3Q7VW5rbm93biBpbnRlbnQmcXVvdDsgfSwgeyBzdGF0dXM6IDQwMCB9KTsKfQoKZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gUGF5bWVudHMoKSB7CmNvbnN0IHsgZmVlcywgcGl4Q2hhdmUgfSA9IHVzZUxvYWRlckRhdGEmbHQ7dHlwZW9mIGxvYWRlciZndDsoKTsKY29uc3QgZmV0Y2hlciA9IHVzZUZldGNoZXImbHQ7YW55Jmd0OygpOwoKcmV0dXJuICgKJmx0O1BhZ2UgdGl0bGU9JnF1b3Q7UGF5bWVudHMgJm1kYXNoOyBEYXkgMSBOb24tQ3VzdG9keSBNb2RlbCZxdW90OwpzdWJ0aXRsZT0mcXVvdDtWNy4xICZtaWRkb3Q7IFAyUCBEaXJlY3QgJm1pZGRvdDsgTm8gV2FsbGV0ICZtaWRkb3Q7IE5vIEVzY3JvdyAmbWlkZG90OyBQQ0ktQ29tcGxpYW50ICZtaWRkb3Q7IE5vbi1QU1AmcXVvdDsmZ3Q7CiZsdDtMYXlvdXQmZ3Q7CiZsdDtMYXlvdXQuU2VjdGlvbiZndDsKJmx0O0Jhbm5lciB0b25lPSZxdW90O3dhcm5pbmcmcXVvdDsgdGl0bGU9JnF1b3Q7VjcuMSBMZWdhbCBCb3VuZGFyeSAmbWRhc2g7IEFic29sdXRlJnF1b3Q7Jmd0OwpTRFQgTHRkIGRvZXMgbm90IGhvbGQsIHN0b3JlLCBvciBjb250cm9sIGFueSByaWRlciBvciBkcml2ZXIgZnVuZHMuIEFsbCBwYXltZW50cyBmbG93ClJpZGVyICZyYXJyOyBMaWNlbnNlZCBQYXJ0bmVyIChCZSEsIE1NRywgU3RyaXBlLCBQSVgpICZyYXJyOyBEcml2ZXIgZGlyZWN0bHkuIFBsYXRmb3JtIEFjY2VzcyBGZWVzCmFyZSBpbnZvaWNlZCBzZXBhcmF0ZWx5IGJ5IFNEVCBMdGQgKHNvZnR3YXJlIGxpY2Vuc2Ugb25seSkuCiZsdDsvQmFubmVyJmd0OwombHQ7L0xheW91dC5TZWN0aW9uJmd0OwombHQ7TGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtJbmxpbmVHcmlkIGNvbHVtbnM9ezJ9IGdhcD0mcXVvdDszMDAmcXVvdDsmZ3Q7CiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MjAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtoMiZxdW90OyB2YXJpYW50PSZxdW90O2hlYWRpbmdNZCZxdW90OyZndDtQMlAgUGF5bWVudCBIYW5kb2ZmJmx0Oy9UZXh0Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7Ym9keVNtJnF1b3Q7Jmd0O0dlbmVyYXRlIGEgZGVlcCBsaW5rIHRvIGEgbGljZW5zZWQgUFNQLiBTRFQgbmV2ZXIgdG91Y2hlcyBmdW5kcy4mbHQ7L1RleHQmZ3Q7CiZsdDtCdXR0b24gdmFyaWFudD0mcXVvdDtwcmltYXJ5JnF1b3Q7IG9uQ2xpY2s9eygpID0mZ3Q7CmZldGNoZXIuc3VibWl0KHsgaW50ZW50OiAmcXVvdDtoYW5kb2ZmJnF1b3Q7LCBvcmRlcklkOiAmcXVvdDtkZW1vJnF1b3Q7LCBhbW91bnQ6ICZxdW90OzQ1MDAwJnF1b3Q7LCBjdXJyZW5jeTogJnF1b3Q7R1lEJnF1b3Q7IH0sIHsgbWV0aG9kOiAmcXVvdDtwb3N0JnF1b3Q7IH0pfSZndDsKQ3JlYXRlIEdZRCBIYW5kb2ZmCiZsdDsvQnV0dG9uJmd0OwombHQ7QnV0dG9uIG9uQ2xpY2s9eygpID0mZ3Q7CmZldGNoZXIuc3VibWl0KHsgaW50ZW50OiAmcXVvdDtoYW5kb2ZmJnF1b3Q7LCBvcmRlcklkOiAmcXVvdDtkZW1vJnF1b3Q7LCBhbW91bnQ6ICZxdW90OzMyMCZxdW90OywgY3VycmVuY3k6ICZxdW90O0JSTCZxdW90OyB9LCB7IG1ldGhvZDogJnF1b3Q7cG9zdCZxdW90OyB9KX0mZ3Q7CkNyZWF0ZSBCUkwgSGFuZG9mZiAoUElYKQombHQ7L0J1dHRvbiZndDsKe2ZldGNoZXIuZGF0YT8ubGluayAmYW1wOyZhbXA7ICgKJmx0O0Jhbm5lciB0b25lPSZxdW90O3N1Y2Nlc3MmcXVvdDsgdGl0bGU9JnF1b3Q7SGFuZG9mZiBjcmVhdGVkJnF1b3Q7Jmd0OwpQcm92aWRlcjoge2ZldGNoZXIuZGF0YS5saW5rLnByb3ZpZGVyfSAmbWlkZG90OyBOb24tY3VzdG9keTogdHJ1ZQombHQ7L0Jhbm5lciZndDsKKX0Ke2ZldGNoZXIuZGF0YT8uZXJyb3IgJmFtcDsmYW1wOyAoCiZsdDtCYW5uZXIgdG9uZT0mcXVvdDtjcml0aWNhbCZxdW90OyB0aXRsZT0mcXVvdDtCbG9ja2VkIGJ5IFY3LjEgZmlyZXdhbGwmcXVvdDsmZ3Q7e2ZldGNoZXIuZGF0YS5lcnJvcn0mbHQ7L0Jhbm5lciZndDsKKX0KJmx0Oy9CbG9ja1N0YWNrJmd0OwombHQ7L0NhcmQmZ3Q7CiZsdDtDYXJkJmd0OwombHQ7QmxvY2tTdGFjayBnYXA9JnF1b3Q7MjAwJnF1b3Q7Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtoMiZxdW90OyB2YXJpYW50PSZxdW90O2hlYWRpbmdNZCZxdW90OyZndDtQbGF0Zm9ybSBBY2Nlc3MgRmVlcyZsdDsvVGV4dCZndDsKJmx0O1RleHQgYXM9JnF1b3Q7cCZxdW90OyB2YXJpYW50PSZxdW90O2JvZHlTbSZxdW90OyZndDtTb2Z0d2FyZSBsaWNlbnNlICZtaWRkb3Q7IEludm9pY2VkIGJ5IFNEVCBMdGQmbHQ7L1RleHQmZ3Q7CntPYmplY3QudmFsdWVzKGZlZXMpLm1hcCgoZjogYW55KSA9Jmd0OyAoCiZsdDtkaXYga2V5PXtmLnRpZXJ9IHN0eWxlPXt7IGRpc3BsYXk6ICZxdW90O2ZsZXgmcXVvdDssIGp1c3RpZnlDb250ZW50OiAmcXVvdDtzcGFjZS1iZXR3ZWVuJnF1b3Q7IH19Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtzcGFuJnF1b3Q7IGZvbnRXZWlnaHQ9JnF1b3Q7c2VtaWJvbGQmcXVvdDsmZ3Q7e2YudGllci50b1VwcGVyQ2FzZSgpfSZsdDsvVGV4dCZndDsKJmx0O1RleHQgYXM9JnF1b3Q7c3BhbiZxdW90OyZndDt7Zi5hbW91bnRHeWQgPT09IDAgPyAmcXVvdDtGUkVFIC8gQ3VzdG9tJnF1b3Q7IDogR1lEICR7Zi5hbW91bnRHeWQudG9Mb2NhbGVTdHJpbmcoKX0vbW99Jmx0Oy9UZXh0Jmd0OwombHQ7L2RpdiZndDsKKSl9CiZsdDsvQmxvY2tTdGFjayZndDsKJmx0Oy9DYXJkJmd0OwombHQ7L0lubGluZUdyaWQmZ3Q7CiZsdDsvTGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDtMYXlvdXQuU2VjdGlvbiZndDsKJmx0O0NhcmQmZ3Q7CiZsdDtCbG9ja1N0YWNrIGdhcD0mcXVvdDsyMDAmcXVvdDsmZ3Q7CiZsdDtUZXh0IGFzPSZxdW90O2gyJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7aGVhZGluZ01kJnF1b3Q7Jmd0O0JyYXppbCBJbnN0YW50IFBheW1lbnRzJmx0Oy9UZXh0Jmd0OwombHQ7QmFkZ2UgdG9uZT0mcXVvdDtzdWNjZXNzJnF1b3Q7Jmd0O1BJWCAmbWlkZG90OyBJbnN0YW50ICZtaWRkb3Q7IE5vbi1DdXN0b2RpYWwmbHQ7L0JhZGdlJmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7Jmd0O1BJWCBDaGF2ZTogJmx0O3N0cm9uZyZndDt7cGl4Q2hhdmV9Jmx0Oy9zdHJvbmcmZ3Q7Jmx0Oy9UZXh0Jmd0OwombHQ7VGV4dCBhcz0mcXVvdDtwJnF1b3Q7IHZhcmlhbnQ9JnF1b3Q7Ym9keVNtJnF1b3Q7IHRvbmU9JnF1b3Q7c3ViZHVlZCZxdW90OyZndDsKQWNlaXRhbW9zIFBJWCAmbWlkZG90OyBQYXltZW50IGZsb3dzIGRpcmVjdGx5IHRvIGRyaXZlcidzIG5vbWluYXRlZCBhY2NvdW50LgpTRFQgQnJhemlsIEx0ZGEgY29ycG9yYXRlIGFjY291bnQgZm9yIFBsYXRmb3JtIEFjY2VzcyBGZWVzIG9ubHkuCiZsdDsvVGV4dCZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwombHQ7L0NhcmQmZ3Q7CiZsdDsvTGF5b3V0LlNlY3Rpb24mZ3Q7CiZsdDsvTGF5b3V0Jmd0OwombHQ7L1BhZ2UmZ3Q7Cik7Cn0KRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvYXBwL3JvdXRlcy93ZWJob29rcy50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgeyB0eXBlIEFjdGlvbkZ1bmN0aW9uQXJncyB9IGZyb20gJnF1b3Q7QHJlbWl4LXJ1bi9ub2RlJnF1b3Q7OwppbXBvcnQgeyBhdXRoZW50aWNhdGUgfSBmcm9tICZxdW90Oy4uL3Nob3BpZnkuc2VydmVyJnF1b3Q7OwppbXBvcnQgeyBjb25uZWN0VG9EYXRhYmFzZSwgY29sbGVjdGlvbnMgfSBmcm9tICZxdW90Oy4uL2xpYi9tb25nb2RiLnNlcnZlciZxdW90OzsKaW1wb3J0IHsgdjQgYXMgdXVpZHY0IH0gZnJvbSAmcXVvdDt1dWlkJnF1b3Q7OwoKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGFjdGlvbih7IHJlcXVlc3QgfTogQWN0aW9uRnVuY3Rpb25BcmdzKSB7CmNvbnN0IHsgdG9waWMsIHNob3AsIGFkbWluLCBwYXlsb2FkIH0gPSBhd2FpdCBhdXRoZW50aWNhdGUud2ViaG9vayhyZXF1ZXN0KTsKaWYgKCFhZG1pbikgcmV0dXJuIG5ldyBSZXNwb25zZSgpOwpjb25zdCB7IGRiIH0gPSBhd2FpdCBjb25uZWN0VG9EYXRhYmFzZSgpOwoKc3dpdGNoICh0b3BpYykgewpjYXNlICZxdW90O09SREVSU19QQUlEJnF1b3Q7OiB7CmNvbnN0IG9yZGVyOiBhbnkgPSBwYXlsb2FkOwphd2FpdCBkYi5jb2xsZWN0aW9uKGNvbGxlY3Rpb25zLlNISVBNRU5UUykuaW5zZXJ0T25lKHsKX2lkOiB1dWlkdjQoKSwgc2hvcCwgb3JkZXJJZDogU3RyaW5nKG9yZGVyLmlkKSwgb3JkZXJOYW1lOiBvcmRlci5uYW1lLApjdXN0b21lcjogeyBlbWFpbDogb3JkZXIuZW1haWwsIG5hbWU6ICR7b3JkZXIuY3VzdG9tZXI/LmZpcnN0X25hbWUgfHwgJnF1b3Q7JnF1b3Q7fSAke29yZGVyLmN1c3RvbWVyPy5sYXN0X25hbWUgfHwgJnF1b3Q7JnF1b3Q7fS50cmltKCkgfSwKcGlja3VwOiB7IGxvY2F0aW9uOiAmcXVvdDtMZXRoZW0gSHViJnF1b3Q7LCBkYXRlOiBuZXcgRGF0ZSgpIH0sCmRlbGl2ZXJ5OiB7IGxvY2F0aW9uOiBvcmRlci5zaGlwcGluZ19hZGRyZXNzID8gJHtvcmRlci5zaGlwcGluZ19hZGRyZXNzLmFkZHJlc3MxfSwgJHtvcmRlci5zaGlwcGluZ19hZGRyZXNzLmNpdHl9IDogJnF1b3Q7Q3VzdG9tZXIgQWRkcmVzcyZxdW90OywgZGF0ZTogbmV3IERhdGUoKSB9LApzdGF0dXM6ICZxdW90O3BlbmRpbmcmcXVvdDssIHN5bmNTdGF0dXM6ICZxdW90O3N5bmNlZCZxdW90OywKY2xpZW50SWQ6IHNob3BpZnlfJHtvcmRlci5pZH0sCnByZWZlcmVuY2VzOiB7IGRyaXZlckdlbmRlcjogJnF1b3Q7YW55JnF1b3Q7IH0sCmNyZWF0ZWRBdDogbmV3IERhdGUoKSwgdXBkYXRlZEF0OiBuZXcgRGF0ZSgpLAp9KTsKYnJlYWs7Cn0KY2FzZSAmcXVvdDtGVUxGSUxMTUVOVFNfQ1JFQVRFJnF1b3Q7OgpjYXNlICZxdW90O0ZVTEZJTExNRU5UU19VUERBVEUmcXVvdDs6IHsKY29uc3QgZjogYW55ID0gcGF5bG9hZDsKYXdhaXQgZGIuY29sbGVjdGlvbihjb2xsZWN0aW9ucy5TSElQTUVOVFMpLnVwZGF0ZU9uZSgKeyBzaG9wLCBvcmRlcklkOiBTdHJpbmcoZi5vcmRlcl9pZCkgfSwKeyAkc2V0OiB7IHN0YXR1czogZi5zdGF0dXMgPT09ICZxdW90O3N1Y2Nlc3MmcXVvdDsgPyAmcXVvdDtpbl90cmFuc2l0JnF1b3Q7IDogZi5zdGF0dXMsCiAgICAgICAgICB0cmFja2luZ051bWJlcjogZi50cmFja2luZ19udW1iZXIsIHRyYWNraW5nQ29tcGFueTogZi50cmFja2luZ19jb21wYW55LCB1cGRhdGVkQXQ6IG5ldyBEYXRlKCkgfSB9KTsKICAgICAgYnJlYWs7CiAgICB9CiAgICBjYXNlICZxdW90O09SREVSU19GVUxGSUxMRUQmcXVvdDs6IHsKICAgICAgY29uc3Qgb3JkZXI6IGFueSA9IHBheWxvYWQ7CiAgICAgIGF3YWl0IGRiLmNvbGxlY3Rpb24oY29sbGVjdGlvbnMuU0hJUE1FTlRTKS51cGRhdGVPbmUoCiAgICAgICAgeyBzaG9wLCBvcmRlcklkOiBTdHJpbmcob3JkZXIuaWQpIH0sCiAgICAgICAgeyAkc2V0OiB7IHN0YXR1czogJnF1b3Q7ZGVsaXZlcmVkJnF1b3Q7LCB1cGRhdGVkQXQ6IG5ldyBEYXRlKCkgfSB9KTsKYnJlYWs7Cn0KY2FzZSAmcXVvdDtDVVNUT01FUlNfQ1JFQVRFJnF1b3Q7OiB7CmNvbnN0IGM6IGFueSA9IHBheWxvYWQ7CmF3YWl0IGRiLmNvbGxlY3Rpb24oJnF1b3Q7Y3VzdG9tZXJzJnF1b3Q7KS51cGRhdGVPbmUoCnsgc2hvcCwgY3VzdG9tZXJJZDogU3RyaW5nKGMuaWQpIH0sCnsgJHNldDogeyBzaG9wLCBjdXN0b21lcklkOiBTdHJpbmcoYy5pZCksCmVtYWlsSGFzaDogYy5lbWFpbCA/IEJ1ZmZlci5mcm9tKGMuZW1haWwpLnRvU3RyaW5nKCZxdW90O2Jhc2U2NCZxdW90OykgOiBudWxsLApyZWdpb246IGMuZGVmYXVsdF9hZGRyZXNzPy5wcm92aW5jZSB8fCBudWxsLCB1cGRhdGVkQXQ6IG5ldyBEYXRlKCkgfSB9LAp7IHVwc2VydDogdHJ1ZSB9KTsKYnJlYWs7Cn0KfQpyZXR1cm4gbmV3IFJlc3BvbnNlKCk7Cn0KRU9GCgo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpTRVJWSUNFIFdPUktFUiArIFBVQkxJQwoKPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKd3JpdGVmaWxlICZxdW90OyRST09UL3B1YmxpYy9zZXJ2aWNlLXdvcmtlci5qcyZxdW90OyAmbHQ7Jmx0OydFT0YnCmNvbnN0IENBQ0hFX05BTUUgPSAmcXVvdDtzaGFyZWRyb3AtYWRtaW4tdjcmcXVvdDs7CmNvbnN0IEFQSV9QUkVGSVggPSAmcXVvdDsvYXBpJnF1b3Q7Owpjb25zdCBQUkVDQUNIRSA9IFsmcXVvdDsvJnF1b3Q7LCAmcXVvdDsvYXBwJnF1b3Q7LCAmcXVvdDsvYXBwL2xlYWRzJnF1b3Q7LCAmcXVvdDsvYXBwL3NoaXBtZW50cyZxdW90OywgJnF1b3Q7L2FwcC92aXAtZHJpdmVycyZxdW90OywgJnF1b3Q7L2FwcC9kcml2aW5nLXNjaG9vbCZxdW90OywgJnF1b3Q7L2FwcC9wYXltZW50cyZxdW90O107CgpzZWxmLmFkZEV2ZW50TGlzdGVuZXIoJnF1b3Q7aW5zdGFsbCZxdW90OywgKGV2ZW50KSA9Jmd0OyB7CmV2ZW50LndhaXRVbnRpbChjYWNoZXMub3BlbihDQUNIRV9OQU1FKS50aGVuKChjKSA9Jmd0OyBjLmFkZEFsbChQUkVDQUNIRSkpLnRoZW4oKCkgPSZndDsgc2VsZi5za2lwV2FpdGluZygpKSk7Cn0pOwpzZWxmLmFkZEV2ZW50TGlzdGVuZXIoJnF1b3Q7YWN0aXZhdGUmcXVvdDssIChldmVudCkgPSZndDsgewpldmVudC53YWl0VW50aWwoY2FjaGVzLmtleXMoKS50aGVuKChuYW1lcykgPSZndDsKUHJvbWlzZS5hbGwobmFtZXMuZmlsdGVyKChuKSA9Jmd0OyBuICE9PSBDQUNIRV9OQU1FKS5tYXAoKG4pID0mZ3Q7IGNhY2hlcy5kZWxldGUobikpKQopLnRoZW4oKCkgPSZndDsgc2VsZi5jbGllbnRzLmNsYWltKCkpKTsKfSk7CnNlbGYuYWRkRXZlbnRMaXN0ZW5lcigmcXVvdDtmZXRjaCZxdW90OywgKGV2ZW50KSA9Jmd0OyB7CmNvbnN0IHsgcmVxdWVzdCB9ID0gZXZlbnQ7CmNvbnN0IHVybCA9IG5ldyBVUkwocmVxdWVzdC51cmwpOwppZiAocmVxdWVzdC5tZXRob2QgIT09ICZxdW90O0dFVCZxdW90OykgeyBldmVudC5yZXNwb25kV2l0aChmZXRjaChyZXF1ZXN0KSk7IHJldHVybjsgfQppZiAodXJsLnBhdGhuYW1lLnN0YXJ0c1dpdGgoQVBJX1BSRUZJWCkpIHsgZXZlbnQucmVzcG9uZFdpdGgobmV0d29ya0ZpcnN0KHJlcXVlc3QpKTsgcmV0dXJuOyB9CmV2ZW50LnJlc3BvbmRXaXRoKGNhY2hlRmlyc3QocmVxdWVzdCkpOwp9KTsKYXN5bmMgZnVuY3Rpb24gbmV0d29ya0ZpcnN0KHJlcXVlc3QpIHsKY29uc3QgY2FjaGUgPSBhd2FpdCBjYWNoZXMub3BlbihDQUNIRV9OQU1FKTsKdHJ5IHsKY29uc3QgcmVzID0gYXdhaXQgZmV0Y2gocmVxdWVzdCk7CmlmIChyZXMub2spIGNhY2hlLnB1dChyZXF1ZXN0LCByZXMuY2xvbmUoKSk7CnJldHVybiByZXM7Cn0gY2F0Y2ggKGVycikgewpjb25zdCBjYWNoZWQgPSBhd2FpdCBjYWNoZS5tYXRjaChyZXF1ZXN0KTsKaWYgKGNhY2hlZCkgcmV0dXJuIGNhY2hlZDsKdGhyb3cgZXJyOwp9Cn0KYXN5bmMgZnVuY3Rpb24gY2FjaGVGaXJzdChyZXF1ZXN0KSB7CmNvbnN0IGNhY2hlZCA9IGF3YWl0IGNhY2hlcy5tYXRjaChyZXF1ZXN0KTsKaWYgKGNhY2hlZCkgcmV0dXJuIGNhY2hlZDsKdHJ5IHsKY29uc3QgcmVzID0gYXdhaXQgZmV0Y2gocmVxdWVzdCk7CmlmIChyZXMub2spIHsgY29uc3QgY2FjaGUgPSBhd2FpdCBjYWNoZXMub3BlbihDQUNIRV9OQU1FKTsgY2FjaGUucHV0KHJlcXVlc3QsIHJlcy5jbG9uZSgpKTsgfQpyZXR1cm4gcmVzOwp9IGNhdGNoIChlcnIpIHsKaWYgKHJlcXVlc3QubW9kZSA9PT0gJnF1b3Q7bmF2aWdhdGUmcXVvdDspIHJldHVybiBjYWNoZXMubWF0Y2goJnF1b3Q7L2FwcCZxdW90Oyk7CnRocm93IGVycjsKfQp9CnNlbGYuYWRkRXZlbnRMaXN0ZW5lcigmcXVvdDtzeW5jJnF1b3Q7LCAoZXZlbnQpID0mZ3Q7IHsKaWYgKGV2ZW50LnRhZyA9PT0gJnF1b3Q7c3luYy1yZXF1ZXN0cyZxdW90OykgewpldmVudC53YWl0VW50aWwoKGFzeW5jICgpID0mZ3Q7IHsKY29uc3QgY2xpZW50cyA9IGF3YWl0IHNlbGYuY2xpZW50cy5tYXRjaEFsbCgpOwpjbGllbnRzLmZvckVhY2goKGMpID0mZ3Q7IGMucG9zdE1lc3NhZ2UoeyB0eXBlOiAmcXVvdDtQRVJGT1JNX1NZTkMmcXVvdDsgfSkpOwp9KSgpKTsKfQp9KTsKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvcHVibGljL29mZmxpbmUuaHRtbCZxdW90OyAmbHQ7Jmx0OydFT0YnCgombHQ7IURPQ1RZUEUgaHRtbCZndDsKCiZsdDtodG1sIGxhbmc9JnF1b3Q7ZW4mcXVvdDsmZ3Q7CiZsdDtoZWFkJmd0OwombHQ7bWV0YSBjaGFyc2V0PSZxdW90O3V0Zi04JnF1b3Q7IC8mZ3Q7CiZsdDt0aXRsZSZndDtTaGFyZWRyb3AgJm1kYXNoOyBPZmZsaW5lJmx0Oy90aXRsZSZndDsKJmx0O3N0eWxlJmd0OwogIGJvZHkgeyBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZjsgYmFja2dyb3VuZDogIzBiMWIzZDsgY29sb3I6ICNmZmY7CiAgICAgICAgIGRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiBjZW50ZXI7IGp1c3RpZnktY29udGVudDogY2VudGVyOyBoZWlnaHQ6IDEwMHZoOyBtYXJnaW46IDA7IHRleHQtYWxpZ246IGNlbnRlcjsgfQogIC5jYXJkIHsgbWF4LXdpZHRoOiA0ODBweDsgcGFkZGluZzogNDBweDsgfQogIGgxIHsgY29sb3I6ICNkNGFmMzc7IG1hcmdpbi1ib3R0b206IDhweDsgfQogIHAgeyBvcGFjaXR5OiAwLjg1OyBsaW5lLWhlaWdodDogMS42OyB9CiZsdDsvc3R5bGUmZ3Q7CiZsdDsvaGVhZCZndDsKJmx0O2JvZHkmZ3Q7CiAgJmx0O2RpdiBjbGFzcz0mcXVvdDtjYXJkJnF1b3Q7Jmd0OwogICAgJmx0O2gxJmd0O09mZmxpbmUmbHQ7L2gxJmd0OwogICAgJmx0O3AmZ3Q7U2hhcmVkcm9wLlRyYWRpbmcgaXMgb2ZmbGluZS1maXJzdC4gWW91ciBjaGFuZ2VzIHdpbGwgc3luYyBhdXRvbWF0aWNhbGx5IHdoZW4gdGhlIGNvbm5lY3Rpb24gaXMgcmVzdG9yZWQuJmx0Oy9wJmd0OwogICAgJmx0O3AmZ3Q7Jmx0O2EgaHJlZj0mcXVvdDsvYXBwJnF1b3Q7IHN0eWxlPSZxdW90O2NvbG9yOiNkNGFmMzcmcXVvdDsmZ3Q7UmV0cnkmbHQ7L2EmZ3Q7Jmx0Oy9wJmd0OwogICZsdDsvZGl2Jmd0OwombHQ7L2JvZHkmZ3Q7CiZsdDsvaHRtbCZndDsKRU9GCgo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpDSEVDS09VVCBFWFRFTlNJT04KCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCndyaXRlZmlsZSAmcXVvdDskUk9PVC9leHRlbnNpb25zL2RlbGl2ZXJ5LWRhdGUvc2hvcGlmeS5leHRlbnNpb24udG9tbCZxdW90OyAmbHQ7Jmx0OydFT0YnCmFwaV92ZXJzaW9uID0gJnF1b3Q7MjAyNi0wNCZxdW90OwoKW1tleHRlbnNpb25zXV0KbmFtZSA9ICZxdW90O1NoYXJlZHJvcCBEZWxpdmVyeSBEYXRlJnF1b3Q7CmhhbmRsZSA9ICZxdW90O2RlbGl2ZXJ5LWRhdGUmcXVvdDsKdHlwZSA9ICZxdW90O3VpX2V4dGVuc2lvbiZxdW90OwoKW1tleHRlbnNpb25zLnRhcmdldGluZ11dCm1vZHVsZSA9ICZxdW90Oy4vc3JjL0NoZWNrb3V0LnRzeCZxdW90Owp0YXJnZXQgPSAmcXVvdDtwdXJjaGFzZS5jaGVja291dC5kZWxpdmVyeS1hZGRyZXNzLnJlbmRlci1hZnRlciZxdW90OwoKW2V4dGVuc2lvbnMuY2FwYWJpbGl0aWVzXQphcGlfYWNjZXNzID0gdHJ1ZQpuZXR3b3JrX2FjY2VzcyA9IHRydWUKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvZXh0ZW5zaW9ucy9kZWxpdmVyeS1kYXRlL3NyYy9DaGVja291dC50c3gmcXVvdDsgJmx0OyZsdDsnRU9GJwppbXBvcnQgewpyZWFjdEV4dGVuc2lvbiwgdXNlQXBwbHlNZXRhZmllbGRzQ2hhbmdlLCB1c2VTaGlwcGluZ0FkZHJlc3MsIHVzZUNhcnRMaW5lcywKQmxvY2tTdGFjaywgVGV4dCwgRGF0ZUZpZWxkLCBCYW5uZXIsCn0gZnJvbSAmcXVvdDtAc2hvcGlmeS91aS1leHRlbnNpb25zLXJlYWN0L2NoZWNrb3V0JnF1b3Q7OwppbXBvcnQgeyB1c2VTdGF0ZSB9IGZyb20gJnF1b3Q7cmVhY3QmcXVvdDs7CgpleHBvcnQgZGVmYXVsdCByZWFjdEV4dGVuc2lvbigmcXVvdDtwdXJjaGFzZS5jaGVja291dC5kZWxpdmVyeS1hZGRyZXNzLnJlbmRlci1hZnRlciZxdW90OywgKCkgPSZndDsgJmx0O0V4dGVuc2lvbiAvJmd0Oyk7CgpmdW5jdGlvbiBFeHRlbnNpb24oKSB7CmNvbnN0IFtkYXRlLCBzZXREYXRlXSA9IHVzZVN0YXRlJmx0O3N0cmluZyZndDsoJnF1b3Q7JnF1b3Q7KTsKY29uc3QgYXBwbHlNZXRhZmllbGQgPSB1c2VBcHBseU1ldGFmaWVsZHNDaGFuZ2UoKTsKY29uc3QgYWRkciA9IHVzZVNoaXBwaW5nQWRkcmVzcygpOwpjb25zdCBsaW5lcyA9IHVzZUNhcnRMaW5lcygpOwoKY29uc3Qgb25DaGFuZ2UgPSAodmFsdWU6IHN0cmluZykgPSZndDsgewpzZXREYXRlKHZhbHVlKTsKYXBwbHlNZXRhZmllbGQoewpuYW1lc3BhY2U6ICZxdW90O3NoYXJlZHJvcCZxdW90Oywga2V5OiAmcXVvdDtwcmVmZXJyZWRfZGVsaXZlcnlfZGF0ZSZxdW90OywKdmFsdWUsIHR5cGU6ICZxdW90O3NpbmdsZV9saW5lX3RleHRfZmllbGQmcXVvdDssCn0pOwp9OwoKcmV0dXJuICgKJmx0O0Jsb2NrU3RhY2sgc3BhY2luZz0mcXVvdDt0aWdodCZxdW90OyZndDsKJmx0O0Jhbm5lciBzdGF0dXM9JnF1b3Q7aW5mbyZxdW90OyZndDsKJmx0O1RleHQmZ3Q7U2hhcmVkcm9wIGNvb3JkaW5hdGVzIGRlbGl2ZXJ5LiBDaG9vc2UgeW91ciBwcmVmZXJyZWQgZGF0ZS4mbHQ7L1RleHQmZ3Q7CiZsdDsvQmFubmVyJmd0OwombHQ7RGF0ZUZpZWxkIGxhYmVsPSZxdW90O1ByZWZlcnJlZCBkZWxpdmVyeSBkYXRlJnF1b3Q7IHZhbHVlPXtkYXRlfSBvbkNoYW5nZT17b25DaGFuZ2V9IC8mZ3Q7CnthZGRyPy5jaXR5ICZhbXA7JmFtcDsgKAombHQ7VGV4dCBzaXplPSZxdW90O3NtYWxsJnF1b3Q7IGFwcGVhcmFuY2U9JnF1b3Q7c3ViZHVlZCZxdW90OyZndDtEZWxpdmVyaW5nIHRvIHthZGRyLmNpdHl9LCB7YWRkci5jb3VudHJ5Q29kZX0mbHQ7L1RleHQmZ3Q7Cil9CiZsdDtUZXh0IHNpemU9JnF1b3Q7c21hbGwmcXVvdDsgYXBwZWFyYW5jZT0mcXVvdDtzdWJkdWVkJnF1b3Q7Jmd0O3tsaW5lcy5sZW5ndGh9IGl0ZW0ocykgaW4gY2FydCZsdDsvVGV4dCZndDsKJmx0Oy9CbG9ja1N0YWNrJmd0OwopOwp9CkVPRgoKPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKRE9DUwoKPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKd3JpdGVmaWxlICZxdW90OyRST09UL2RvY3MvQUdFTlRfSU5TVFJVQ1RJT05TLm1kJnF1b3Q7ICZsdDsmbHQ7J0VPRicKCkFnZW50IEluc3RydWN0aW9ucyAmbWRhc2g7IExvY2tlZCBEZWNpc2lvbnMgVjcuMQoKMS4gTWFwcyBBUEkKCkxlYWZsZXQgKyBPcGVuU3RyZWV0TWFwIChmcmVlKS4gRmFsbGJhY2s6IE1hcGJveCBmb3IgdmVjdG9yIHRpbGVzLgoKMi4gUGF5bWVudHMgKFY3LjEgTm9uLUN1c3RvZHkgQUJTT0xVVEUpCgombWlkZG90OyBNb2NrIGluaXRpYWxseS4gU3dhcCB0byBsaWNlbnNlZCBQU1Agd2hlbiBrZXlzIHByb3ZpZGVkLgombWlkZG90OyBOTyB3YWxsZXQuIE5PIGVzY3Jvdy4gTk8gY3VzdG9keS4gTk8gcG9vbGVkIGZ1bmRzLgombWlkZG90OyBQMlA6IFJpZGVyICZyYXJyOyBMaWNlbnNlZCBQYXJ0bmVyICZyYXJyOyBEcml2ZXIgYmFuayBhY2NvdW50LgombWlkZG90OyBQbGF0Zm9ybSBBY2Nlc3MgRmVlOiBpbnZvaWNlZCBzZXBhcmF0ZWx5IGJ5IFNEVCBMdGQuCiZtaWRkb3Q7IFBJWCBDaGF2ZTogNzEwODYyODcyNDAKJm1pZGRvdDsgRXZlcnkgcGF5bWVudCByZWNvcmQgTVVTVCBjYWxsIGFzc2VydE5vbkN1c3RvZHkoKS4KCjMuIFJlYWwtdGltZQoKU29ja2V0LmlvLiBOYW1lc3BhY2VzOiAvcmlkZXMsIC9jaGF0LCAvYWxlcnRzLgpGYWxsYmFjazogcG9sbGluZyAxNXMgZm9yIG9mZmxpbmUgcmVnaW9ucy4KCjQuIEJ1aWxkIE9yZGVyCgoxLiBQaGFzZSAxOiBKb2IgQm9hcmQgKyBQcm9maWxlcyArIFJhdGluZ3MgKyBLWUMKMi4gUGhhc2UgMjogTGl2ZSBNYXAgKyBNYXRjaGluZyArIE9mZmxpbmUgc3luYwozLiBHWS1IVUcgRHJpdmluZyBTY2hvb2wgKDkgbW9kdWxlcykKNC4gR2FtZXBsYXkgRmxvdyAoOCBzdGVwcykKNS4gUGF5bWVudHMgVjcuMSBVSQo2LiBUcmFkaW5nICsgUmF0aW5ncwo3LiBBZG1pbiBEYXNoYm9hcmQKCjUuIERPIE5PVCBCVUlMRAoKJm1pZGRvdDsgV2FsbGV0LCBlc2Nyb3csIGN1c3RvZHksIHBvb2xlZCBmdW5kcwombWlkZG90OyBTZWN1cml0aWVzL2ludmVzdG1lbnQgaW50YWtlCiZtaWRkb3Q7IEFueSBmZWF0dXJlIGhvbGRpbmcgcmlkZXIvZHJpdmVyIG1vbmV5CiAgRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvZG9jcy9MRUdBTF9GSVJFV0FMTC5tZCZxdW90OyAmbHQ7Jmx0OydFT0YnCgpWNy4xIExlZ2FsIEZpcmV3YWxsICZtZGFzaDsgQUJTT0xVVEUKCk5vbi1DdXN0b2R5IE1vZGVsCgombWlkZG90OyBTRFQgTHRkIGlzIE5PVCBhIFBTUCAoSGFuZGJvb2sgUGFydCBJViAxNikKJm1pZGRvdDsgTm8gd2FsbGV0LCBlc2Nyb3csIGN1c3RvZHksIG9yIHBvb2xlZCBmdW5kcwombWlkZG90OyBBbGwgcGF5bWVudHMgdmlhIGxpY2Vuc2VkIGV4dGVybmFsIFBTUHMKCkZvcmJpZGRlbiBUZXJtcyAoYXV0by1lc2NhbGF0ZSkKCmludmVzdG1lbnQsIHNlY3VyaXRpZXMsIGVxdWl0eSBzYWxlLCBzaGFyZXMgb2ZmZXJpbmcsIHB1YmxpYyBvZmZlcmluZywgaXBvLApjdXN0b2R5LCB3YWxsZXQsIGVzY3JvdyBhY2NvdW50LCBob2xkIGZ1bmRzCgpFc2NhbGF0aW9uIFRhcmdldAoKZW9zYm9ybmUubGVnYWxAZ21haWwuY29tIChNcy4gRXN0aGVyIE9zYm9ybmUpCgpFbmZvcmNlbWVudAoKRXZlcnkgcGF5bWVudCByZWNvcmQgY2FsbHMgYXNzZXJ0Tm9uQ3VzdG9keSgpIGZyb20gYXBwL2xpYi9wYXltZW50cy5zZXJ2ZXIudHMKRU9GCgp3cml0ZWZpbGUgJnF1b3Q7JFJPT1QvZG9jcy9ERVBMT1kubWQmcXVvdDsgJmx0OyZsdDsnRU9GJwoKRGVwbG95bWVudCBSdW5ib29rCgpgYGBiYXNoCiMgMS4gSW5zdGFsbApucG0gaW5zdGFsbApucHggcHJpc21hIGdlbmVyYXRlCm5weCBwcmlzbWEgZGIgcHVzaAoKIyAyLiBFbnYKY3AgLmVudi5leGFtcGxlIC5lbnYKIyBGaWxsIFNIT1BJRllfQVBJX0tFWSwgU0hPUElGWV9BUElfU0VDUkVULCBNT05HT0RCX1VSSQoKIyAzLiBMb2NhbCBkZXYKc2hvcGlmeSBhcHAgZGV2CgojIDQuIERlcGxveSB0byBGbHkuaW8KZmx5IGxhdW5jaCAtLW5vLWRlcGxveQpmbHkgc2VjcmV0cyBzZXQgU0hPUElGWV9BUElfS0VZPS4uLiBTSE9QSUZZX0FQSV9TRUNSRVQ9Li4uIFwKICBTSE9QSUZZX0FQUF9VUkw9aHR0cHM6Ly9zLi4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi55LmRldiBcCiAgTU9OR09EQl9VUkk9JnF1b3Q7bW9uZ29kYitzcnY6Ly8uLi4mcXVvdDsgXAogIExFR0FMX0NPVU5TRUxfRU1BSUw9ZW9zYm9ybmUubGVnYWxAZ21haWwuY29tIFwKICBOT05fUFNQX01PREU9dHJ1ZSBcCiAgUElYX0NIQVZFPTcxMDg2Mjg3MjQwCmZseSBkZXBsb3kKCiMgNS4gUHVzaCBjb25maWcgdG8gU2hvcGlmeQpzaG9waWZ5IGFwcCBkZXBsb3kKYGBgCgpFT0YKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KClpJUFBJTkcKCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCmVjaG8gJnF1b3Q7JnF1b3Q7CmVjaG8gJnF1b3Q78J+TpiBQYWNrYWdpbmcgJFJPT1QuLi4mcXVvdDsKemlwIC1yICZxdW90OyRaSVAmcXVvdDsgJnF1b3Q7JFJPT1QmcXVvdDsgLXggJnF1b3Q7L25vZGVfbW9kdWxlcy8mcXVvdDsgJnF1b3Q7LnNxbGl0ZSZxdW90OyAmZ3Q7IC9kZXYvbnVsbAoKZWNobyAmcXVvdDsmcXVvdDsKZWNobyAmcXVvdDvinIUgQ3JlYXRlZDogJFpJUCZxdW90OwplY2hvICZxdW90OyZxdW90OwplY2hvICZxdW90O/Cfk4IgRmlsZXMgaW5jbHVkZWQ6JnF1b3Q7CmZpbmQgJnF1b3Q7JFJPT1QmcXVvdDsgLXR5cGUgZiB8IHNvcnQgfCBzZWQgJnF1b3Q7c3xefCAgIHwmcXVvdDsKZWNobyAmcXVvdDsmcXVvdDsKZWNobyAmcXVvdDvwn5OKIFNpemU6ICQoZHUgLWggJnF1b3Q7JFpJUCZxdW90OyB8IGN1dCAtZjEpJnF1b3Q7CmVjaG8gJnF1b3Q7JnF1b3Q7CmVjaG8gJnF1b3Q78J+agCBUbyB1c2U6JnF1b3Q7CmVjaG8gJnF1b3Q7ICAgdW56aXAgJFpJUCZxdW90OwplY2hvICZxdW90OyAgIGNkICRST09UJnF1b3Q7CmVjaG8gJnF1b3Q7ICAgbnBtIGluc3RhbGwmcXVvdDsKZWNobyAmcXVvdDsgICBucHggcHJpc21hIGdlbmVyYXRlICZhbXA7JmFtcDsgbnB4IHByaXNtYSBkYiBwdXNoJnF1b3Q7CmVjaG8gJnF1b3Q7ICAgY3AgLmVudi5leGFtcGxlIC5lbnYgICAjIGZpbGwgdmFsdWVzJnF1b3Q7CmVjaG8gJnF1b3Q7ICAgc2hvcGlmeSBhcHAgZGV2JnF1b3Q7CgpgYGAKCi0tLQoKIyMgSE9XIFRPIFJVTgoKYGBgYmFzaAojIDEuIFNhdmUgdGhlIHNjcmlwdCBhcyBidWlsZF9zaGFyZWRyb3BfdjdfMS5zaApjaG1vZCAreCBidWlsZF9zaGFyZWRyb3BfdjdfMS5zaAoKIyAyLiBSdW4gaXQKLi9idWlsZF9zaGFyZWRyb3BfdjdfMS5zaAoKIyAzLiBaaXAgYXBwZWFycyBpbiBjdXJyZW50IGRpcmVjdG9yeQpscyAtbGEgc2hhcmVkcm9wLXRyYWRpbmctdjcuMS56aXAKYGBgCgotLS0KCldIQVQnUyBJTiBUSEUgWklQCgpgYGAKc2hhcmVkcm9wLXRyYWRpbmctdjcuMS8K4pSc4pSA4pSAIFJFQURNRS5tZArilJzilIDilIAgLmVudi5leGFtcGxlCuKUnOKUgOKUgCAuZ2l0aWdub3JlCuKUnOKUgOKUgCBwYWNrYWdlLmpzb24K4pSc4pSA4pSAIHRzY29uZmlnLmpzb24K4pSc4pSA4pSAIHNob3BpZnkuYXBwLnRvbWwK4pSc4pSA4pSAIERvY2tlcmZpbGUK4pSc4pSA4pSAIGZseS50b21sCuKUnOKUgOKUgCBwcmlzbWEvCuKUgiAgIOKUlOKUgOKUgCBzY2hlbWEucHJpc21hCuKUnOKUgOKUgCBhcHAvCuKUgiAgIOKUnOKUgOKUgCBzaG9waWZ5LnNlcnZlci50cwrilIIgICDilJzilIDilIAgZGIuc2VydmVyLnRzCuKUgiAgIOKUnOKUgOKUgCByb290LnRzeArilIIgICDilJzilIDilIAgbGliLwrilIIgICDilIIgICDilJzilIDilIAgbW9uZ29kYi5zZXJ2ZXIudHMK4pSCICAg4pSCICAg4pSc4pSA4pSAIGdsaWUuc2VydmVyLnRzICAgICAgICAgICAgICAmbGFycjsgR0xJRSBWNy4wIGVuZ2luZQrilIIgICDilIIgICDilJzilIDilIAgbm90aWZpY2F0aW9ucy5zZXJ2ZXIudHMK4pSCICAg4pSCICAg4pSc4pSA4pSAIGZ1bGZpbGxtZW50LnNlcnZlci50cwrilIIgICDilIIgICDilJzilIDilIAgcGF5bWVudHMuc2VydmVyLnRzICAgICAgICAgICZsYXJyOyBWNy4xIE5vbi1DdXN0b2R5CuKUgiAgIOKUgiAgIOKUlOKUgOKUgCBkcml2aW5nLXNjaG9vbC5zZXJ2ZXIudHMgICAgJmxhcnI7IEdZLUhVRyBtb2R1bGUK4pSCICAg4pSU4pSA4pSAIHJvdXRlcy8K4pSCICAgICAgIOKUnOKUgOKUgCBhcHAudHN4ICAgICAgICAgICAgICAgICAgICAgJmxhcnI7IE5hdk1lbnUgKDkgaXRlbXMpCuKUgiAgICAgICDilJzilIDilIAgX2luZGV4LnRzeArilIIgICAgICAg4pSc4pSA4pSAIGF1dGguJC50c3gK4pSCICAgICAgIOKUnOKUgOKUgCBhcHAuX2luZGV4LnRzeCAgICAgICAgICAgICAgJmxhcnI7IERhc2hib2FyZArilIIgICAgICAg4pSc4pSA4pSAIGFwcC5sZWFkcy50c3ggICAgICAgICAgICAgICAmbGFycjsgR0xJRSBsZWFkIGVuZ2luZQrilIIgICAgICAg4pSc4pSA4pSAIGFwcC5zaGlwbWVudHMudHN4CuKUgiAgICAgICDilJzilIDilIAgYXBwLnNoaXBtZW50cy5uZXcudHN4ICAgICAgICZsYXJyOyBTYWZldHkgcHJlZmVyZW5jZXMK4pSCICAgICAgIOKUnOKUgOKUgCBhcHAudmlwLWRyaXZlcnMudHN4ICAgICAgICAgJmxhcnI7IE9mZmxpbmUgY29udGFjdArilIIgICAgICAg4pSc4pSA4pSAIGFwcC5kcml2ZXJzLnRzeArilIIgICAgICAg4pSc4pSA4pSAIGFwcC5nYW1lcGxheS50c3ggICAgICAgICAgICAmbGFycjsgOC1zdGVwIGZsb3cK4pSCICAgICAgIOKUnOKUgOKUgCBhcHAuZHJpdmluZy1zY2hvb2wudHN4ICAgICAgJmxhcnI7IDkgR1RUSSBtb2R1bGVzCuKUgiAgICAgICDilJzilIDilIAgYXBwLnBheW1lbnRzLnRzeCAgICAgICAgICAgICZsYXJyOyBOb24tY3VzdG9keSBVSQrilIIgICAgICAg4pSU4pSA4pSAIHdlYmhvb2tzLnRzeArilJzilIDilIAgcHVibGljLwrilIIgICDilJzilIDilIAgc2VydmljZS13b3JrZXIuanMK4pSCICAg4pSU4pSA4pSAIG9mZmxpbmUuaHRtbArilJzilIDilIAgZXh0ZW5zaW9ucy9kZWxpdmVyeS1kYXRlLwrilIIgICDilJzilIDilIAgc2hvcGlmeS5leHRlbnNpb24udG9tbArilIIgICDilJTilIDilIAgc3JjL0NoZWNrb3V0LnRzeArilJTilIDilIAgZG9jcy8KICAgIOKUnOKUgOKUgCBBR0VOVF9JTlNUUlVDVElPTlMubWQKICAgIOKUnOKUgOKUgCBMRUdBTF9GSVJFV0FMTC5tZAogICAg4pSU4pSA4pSAIERFUExPWS5tZApgYGAKCi0tLQoKVkVSSUZJQ0FUSU9OIENIRUNLTElTVCBBRlRFUiBVTlpJUAoKYGBgYmFzaApbIF0gdW56aXAgc2hhcmVkcm9wLXRyYWRpbmctdjcuMS56aXAKWyBdIGNkIHNoYXJlZHJvcC10cmFkaW5nLXY3LjEKWyBdIG5wbSBpbnN0YWxsICAgICAgICAgICMgc3VjY2VlZHMKWyBdIG5weCBwcmlzbWEgZ2VuZXJhdGUgICMgc3VjY2VlZHMKWyBdIGdyZXAgLXIgJnF1b3Q7YXNzZXJ0Tm9uQ3VzdG9keSZxdW90OyBhcHAvICAgIyBmaW5kcyBwYXltZW50cy5zZXJ2ZXIudHMgKyBhcHAucGF5bWVudHMudHN4ClsgXSBncmVwIC1yICZxdW90OzcxMDg2Mjg3MjQwJnF1b3Q7IGFwcC8gICAgICAgICMgZmluZHMgcGF5bWVudHMuc2VydmVyLnRzICsgYXBwLnBheW1lbnRzLnRzeApbIF0gZ3JlcCAtciAmcXVvdDtHWS1IVUcmcXVvdDsgYXBwL3JvdXRlcy8gICAgICAjIGZpbmRzIGRyaXZpbmctc2Nob29sICsgZ2FtZXBsYXkKWyBdIGxzIGFwcC9yb3V0ZXMvICAgICAgICMgc2hvd3MgOSsgcm91dGVzCmBgYAoKRXZlcnl0aGluZyBmcm9tIGFsbCBwcmV2aW91cyByZXNwb25zZXMgaXMgYnVuZGxlZDogR0xJRSBWNy4wLCBWSVAgZHJpdmVycywgc2FmZXR5IHByZWZlcmVuY2VzLCBHWS1IVUcgZHJpdmluZyBzY2hvb2wsIGdhbWVwbGF5IGZsb3csIFY3LjEgbm9uLWN1c3RvZHkgcGF5bWVudHMsIHNlcnZpY2Ugd29ya2VyLCBjaGVja291dCBleHRlbnNpb24sIGFuZCBkZXBsb3ltZW50IGNvbmZpZ3MuIFJ1biB0aGUgc2NyaXB0IGFuZCB5b3UgaGF2ZSB5b3VyIHppcC4=
SHAREDROP.TRADING — COMPLETE ZIP BUILDER SCRIPT
I can't transmit binary zip files through chat, but this script generates the entire package in seconds. Copy it, run it, get your zip.
---
BUILD SCRIPT — Save as build_sharedrop_v7_1.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
ROOT="sharedrop-trading-v7.1"
ZIP="${ROOT}.zip"
rm -rf "$ROOT" "$ZIP"
mkdir -p "$ROOT"/{app/{routes,lib},prisma,public,extensions/delivery-date/src,docs,scripts}
writefile() {
local file="$1"; shift
mkdir -p "$(dirname "$file")"
cat > "$file"
}
# ============================================================================
# ROOT FILES
# ============================================================================
writefile "$ROOT/README.md" <<'EOF'
# Sharedrop.Trading — Shopify MVP V7.1
Offline-first logistics marketplace for the Guyana–Brazil corridor.
Built on Shopify embedded app (Remix) + MongoDB Atlas + Netlify/Fly.io.
## Non-Custody Model — V7.1 ABSOLUTE
- No wallet · No escrow · No fund custody · No pooled funds
- P2P direct: Rider → Licensed PSP → Driver
- PIX Chave: 71086287240 (Brazil)
- Non-PSP per Handbook Part IV 16
## What's Included
- GLIE V7.0 lead engine (dual-entity SDT / GY-HUG, 6-stream waterfall, legal firewall)
- Shipment management + VIP driver offline contact
- GY-HUG Driving School Simulation (9 modules, GTTI standards)
- Gameplay Flow (8 steps)
- Payment V7.1 Non-Custody UI (P2P handoff)
- Safety preferences (gender-based driver selection)
- Service worker for offline-first admin
- Checkout extension (delivery date picker)
## Quick Deploy
```bash
npm install
npx prisma generate && npx prisma db push
cp .env.example .env # fill values
shopify app dev
```
Full Deployment
```bash
fly launch --no-deploy
fly secrets set SHOPIFY_API_KEY=... SHOPIFY_API_SECRET=... MONGODB_URI=...
fly deploy
shopify app deploy
```
Legal Firewall
Every payment record MUST call assertNonCustody() from app/lib/payments.server.ts.
Violations auto-escalate to eosborne.legal@gmail.com.
EOF
writefile "$ROOT/.env.example" <<'EOF'
Shopify
SHOPIFY_API_KEY=
SHOPIFY_API_SECRET=
SHOPIFY_APP_URL=https://y...content-available-to-author-only...y.dev
SCOPES=write_orders,read_orders,write_fulfillments,read_fulfillments,write_shipping,read_shipping,read_products,write_products,read_customers,write_customers
SHOP_CUSTOM_DOMAIN=
MongoDB Atlas
MONGODB_URI=mongodb+srv://sharedrop_admin:CHANGE_ME@sharedrop-cluster.mongodb.net/sharedrop?retryWrites=true&w=majority
Maps
LEAFLET_TILE_URL=https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
MAPS_PROVIDER=leaflet_osm
Real-time
SOCKETIO_ENABLED=true
SOCKETIO_PATH=/socket.io
Payments — V7.1 Non-Custody
NON_PSP_MODE=true
PAYMENT_PROVIDER_GY=be_payments_mock
PAYMENT_PROVIDER_BR=pix_mock
PIX_CHAVE=71086287240
STRIPE_PUBLIC_KEY=
MMG_MERCHANT_ID=
GY-HUG Driving School
GYHUG_RULES_GY=guiana_rules.json
GYHUG_RULES_BR=brazil_rules.json
GTTI_STANDARD_VERSION=2024
Notifications
FCM_SERVER_KEY=
ONESIGNAL_APP_ID=
Legal
LEGAL_COUNSEL_EMAIL=eosborne.legal@gmail.com
EOF
writefile "$ROOT/.gitignore" <<'EOF'
node_modules/
.env
dist/
build/
.vscode/
.DS_Store
*.zip
dev.sqlite
dev.sqlite-journal
EOF
writefile "$ROOT/package.json" <<'EOF'
{
"name": "sharedrop-trading",
"private": true,
"version": "7.1.0",
"scripts": {
"build": "remix build",
"dev": "shopify app dev",
"deploy": "shopify app deploy",
"start": "remix-serve build",
"setup": "prisma generate && prisma db push"
},
"dependencies": {
"@prisma/client": "^5.20.0",
"@remix-run/node": "^2.15.0",
"@remix-run/react": "^2.15.0",
"@remix-run/serve": "^2.15.0",
"@shopify/app-bridge-react": "^4.1.6",
"@shopify/polaris": "^13.9.0",
"@shopify/shopify-app-remix": "^3.7.0",
"@shopify/shopify-app-session-storage-prisma": "^5.1.5",
"date-fns": "^2.30.0",
"mongodb": "^6.10.0",
"uuid": "^10.0.0"
},
"devDependencies": {
"@remix-run/dev": "^2.15.0",
"@types/node": "^22.9.0",
"@types/uuid": "^10.0.0",
"prisma": "^5.20.0",
"typescript": "^5.6.3",
"vite": "^5.4.11"
},
"engines": { "node": ">=20.10" }
}
EOF
writefile "$ROOT/shopify.app.toml" <<'EOF'
client_id = "YOUR_SHOPIFY_CLIENT_ID"
name = "Sharedrop Trading"
handle = "sharedrop-trading"
application_url = "https://y...content-available-to-author-only...y.dev"
embedded = true
[access_scopes]
scopes = "write_orders,read_orders,write_fulfillments,read_fulfillments,write_shipping,read_shipping,read_products,write_products,read_customers,write_customers,read_metaobjects,write_metaobjects"
[auth]
redirect_urls = [
"https://y...content-available-to-author-only...y.dev/auth/callback",
"https://y...content-available-to-author-only...y.dev/auth/shopify/callback",
"https://y...content-available-to-author-only...y.dev/api/auth/callback"
]
[webhooks]
api_version = "2026-04"
[[webhooks.subscriptions]]
topics = [ "orders/paid", "orders/fulfilled", "fulfillments/create", "fulfillments/update", "customers/create" ]
uri = "/webhooks"
[build]
automatically_update_urls_on_dev = true
dev_store_url = "your-dev-store.myshopify.com"
EOF
writefile "$ROOT/Dockerfile" <<'EOF'
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start"]
EOF
writefile "$ROOT/fly.toml" <<'EOF'
app = "sharedrop-trading"
primary_region = "mia"
[build]
dockerfile = "Dockerfile"
[env]
PORT = "3000"
NODE_ENV = "production"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 1
[[vm]]
size = "shared-cpu-1x"
memory = "512mb"
EOF
writefile "$ROOT/tsconfig.json" <<'EOF'
{
"include": ["/*.ts", "/.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["@remix-run/node", "vite/client"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"target": "ES2022",
"strict": true,
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": { "~/": ["./app/*"] },
"noEmit": true
}
}
EOF
============================================================================
PRISMA
============================================================================
writefile "$ROOT/prisma/schema.prisma" <<'EOF'
generator client { provider = "prisma-client-js" }
datasource db { provider = "sqlite"; url = "file:dev.sqlite" }
model Session {
id String @id
shop String
state String
isOnline Boolean @default(false)
scope String?
expires DateTime?
accessToken String
userId BigInt?
firstName String?
lastName String?
email String?
accountOwner Boolean @default(false)
locale String?
collaborator Boolean? @default(false)
emailVerified Boolean? @default(false)
}
EOF
============================================================================
APP — CORE
============================================================================
writefile "$ROOT/app/shopify.server.ts" <<'EOF'
import "@shopify/shopify-app-remix/adapters/node";
import { ApiVersion, AppDistribution, shopifyApp } from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import { restResources } from "@shopify/shopify-api/rest/admin/2026-04";
import prisma from "./db.server";
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: ApiVersion.April26,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
restResources,
future: { unstable_newEmbeddedAuthStrategy: true, removeRest: false },
});
export default shopify;
export const apiVersion = ApiVersion.April26;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;
EOF
writefile "$ROOT/app/db.server.ts" <<'EOF'
import { PrismaClient } from "@prisma/client";
declare global { var prisma: PrismaClient | undefined; }
const prisma = global.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") global.prisma = prisma;
export default prisma;
EOF
writefile "$ROOT/app/root.tsx" <<'EOF'
export default function App() { return null; }
EOF
============================================================================
APP/LIB
============================================================================
writefile "$ROOT/app/lib/mongodb.server.ts" <<'EOF'
import { MongoClient, Db } from "mongodb";
const MONGODB_URI = process.env.MONGODB_URI!;
const MONGODB_DB = "sharedrop";
if (!MONGODB_URI) throw new Error("MONGODB_URI is required");
let cachedClient: MongoClient | null = null;
let cachedDb: Db | null = null;
export async function connectToDatabase() {
if (cachedClient && cachedDb) return { client: cachedClient, db: cachedDb };
const client = await MongoClient.connect(MONGODB_URI);
const db = client.db(MONGODB_DB);
cachedClient = client;
cachedDb = db;
return { client, db };
}
export const collections = {
SHOPS: "shops",
SHIPMENTS: "shipments",
DRIVERS: "drivers",
LEADS: "leads",
SYNC_QUEUE: "syncQueue",
OFFLINE_NOTIFICATIONS: "offlineNotifications",
USER_DEVICES: "userDevices",
VIP_CONTACTS: "vipContacts",
TENDER_SCORES: "tenderScores",
AUDIT_LOG: "auditLog",
PAYMENT_HANDOFFS: "paymentHandoffs",
DRIVING_SESSIONS: "drivingSessions",
};
EOF
writefile "$ROOT/app/lib/glie.server.ts" <<'EOF'
import { connectToDatabase, collections } from "./mongodb.server";
import { v4 as uuidv4 } from "uuid";
export interface RawLead {
id?: string; title: string; source: string; category: string;
description: string; value_gyd?: number; agency?: string;
deadline?: string; official_source?: string;
}
export interface EvaluatedLead {
lead_id: string; shop: string; title: string;
status: "QUALIFIED" | "FLAGGED_LEGAL_REVIEW";
target_entity: string; waterfall_stream: string; priority: string;
score: number; corridor_aligned: boolean; fleet_aligned: boolean;
estimated_value_gyd: number; recommended_next_step: string;
raw: RawLead; createdAt: Date; updatedAt: Date;
}
export class BusinessProfile {
operating_entity = "ShareDrop.Trading (SDT)";
holding_entity = "Guyana Hearts United Group Inc. (GY-HUG)";
dcra_fee_gyd = 63550.0;
share_distribution = { GY_HUG: 0.8, Reserved: 0.2 };
legal_counsel = "Ms. Esther Osborne";
fleet_capabilities = ["flatbed", "refrigerated", "cross_border_van", "river_freight"];
active_corridors = ["Georgetown-Lethem", "Lethem-Boa Vista", "Linden-Lethem", "Corentyne"];
waterfall_streams = [
"Stream 1: Platform Transaction Fees",
"Stream 2: Dedicated Logistics & Freight Contracts",
"Stream 3: Cross-Border Trade & Customs Facilitation",
"Stream 4: Fleet Management & Subscription Services",
"Stream 5: Commercial Tenders & Institutional Bids",
"Stream 6: Social Impact & Development Grants (GY-HUG)",
];
}
const LEGAL_FORBIDDEN_TERMS = [
"investment","securities","equity sale","shares offering","public offering",
"ipo","custody","wallet","escrow account","hold funds",
];
function containsLegalRisk(text: string): boolean {
const lower = text.toLowerCase();
return LEGAL_FORBIDDEN_TERMS.some((t) => lower.includes(t));
}
export class LeadEvaluator {
constructor(private profile: BusinessProfile = new BusinessProfile()) {}
evaluateLead(shop: string, raw: RawLead): EvaluatedLead {
const title = raw.title || "Untitled Lead";
const description = raw.description || "";
const category = (raw.category || "").toLowerCase();
const value = raw.value_gyd || 0;
}
private actionPlan(score: number, category: string, entity: string): string {
if (entity === this.profile.holding_entity)
return "Prepare GY-HUG grant proposal focusing on community welfare, local development, and regional impact.";
if (score >= 70)
return "Dispatch automated quote, schedule priority intake call, and draft SDT commercial service agreement.";
return "Add to SDT nurture pipeline; send platform capabilities deck and schedule follow-up.";
}
}
export async function persistLead(lead: EvaluatedLead) {
const { db } = await connectToDatabase();
await db.collection(collections.LEADS).insertOne(lead);
return lead;
}
export async function listLeads(shop: string, limit = 100) {
const { db } = await connectToDatabase();
return db.collection(collections.LEADS).find({ shop }).sort({ createdAt: -1 }).limit(limit).toArray();
}
export async function auditLog(shop: string, action: string, payload: any) {
const { db } = await connectToDatabase();
await db.collection(collections.AUDIT_LOG).insertOne({ shop, action, payload, at: new Date() });
}
EOF
writefile "$ROOT/app/lib/notifications.server.ts" <<'EOF'
import { connectToDatabase, collections } from "./mongodb.server";
interface PushPayload {
userId: string; title: string; body: string;
data: Record<string, any>; persistUntilDelivered?: boolean; expiresAt?: Date;
}
export async function sendPushNotification(payload: PushPayload) {
const { db } = await connectToDatabase();
const { userId, title, body, data, persistUntilDelivered, expiresAt } = payload;
const devices = await db.collection(collections.USER_DEVICES).find({ userId }).toArray();
if (devices.length === 0) return { delivered: false, reason: "No devices registered" };
const results: any[] = [];
for (const device of devices) {
const isOnline = device.lastSeen > new Date(Date.now() - 5 * 60 * 1000);
if (isOnline) {
await sendToDevice(device, { title, body, data });
results.push({ deviceId: device.deviceId, delivered: true });
} else if (persistUntilDelivered) {
await db.collection(collections.OFFLINE_NOTIFICATIONS).insertOne({
userId, deviceId: device.deviceId, notification: { title, body, data },
expiresAt, attempts: 0, maxAttempts: 10, createdAt: new Date(),
});
results.push({ deviceId: device.deviceId, delivered: false, queued: true });
} else {
results.push({ deviceId: device.deviceId, delivered: false });
}
}
return {
delivered: results.some((r) => r.delivered),
queued: results.some((r) => r.queued),
results,
};
}
async function sendToDevice(device: any, notification: any) {
console.log([push] → ${device.deviceId}:, notification.title);
return { success: true };
}
EOF
writefile "$ROOT/app/lib/fulfillment.server.ts" <<'EOF'
export async function createFulfillment(admin: any, fulfillmentOrderId: string, trackingNumber: string, trackingCompany: string) {
const res = await admin.graphql(
#graphql
mutation fulfillmentCreateV2($fulfillment: FulfillmentV2Input!) {
fulfillmentCreateV2(fulfillment: $fulfillment) {
fulfillment { id status trackingInfo { number company url } }
userErrors { field message }
}
},
{ variables: { fulfillment: {
lineItemsByFulfillmentOrder: [{ fulfillmentOrderId }],
trackingInfo: { number: trackingNumber, company: trackingCompany },
notifyCustomer: true,
} } }
);
const data = await res.json();
const errs = data.data?.fulfillmentCreateV2?.userErrors || [];
if (errs.length) throw new Error(errs[0].message);
return data.data?.fulfillmentCreateV2?.fulfillment;
}
EOF
writefile "$ROOT/app/lib/payments.server.ts" <<'EOF'
import { connectToDatabase } from "./mongodb.server";
/* V7.1 Non-Custody ABSOLUTE — Handbook Part IV 16
SDT Ltd is NOT a PSP. No wallet. No escrow. No custody. */
export interface P2PPaymentLink {
orderId: string; amountGyd: number; amountBrl?: number;
rider: { name: string; email: string; phone: string };
driver: { name: string; bankName?: string; pixKey?: string };
provider: "mmg" | "be_payments" | "stripe" | "pix";
nonCustody: true; createdAt: Date;
}
const PIX_CHAVE = "71086287240"; // Locked V7.1
export async function createP2PHandoff(
shop: string, orderId: string, amount: number, currency: "GYD" | "BRL" = "GYD"
): Promise<P2PPaymentLink> {
const provider = currency === "BRL" ? "pix" : amount > 200_000 ? "mmg" : "be_payments";
const link: P2PPaymentLink = {
orderId, amountGyd: currency === "GYD" ? amount : 0,
amountBrl: currency === "BRL" ? amount : undefined,
rider: { name: "", email: "", phone: "" }, driver: { name: "" },
provider, nonCustody: true, createdAt: new Date(),
};
const { db } = await connectToDatabase();
await db.collection("paymentHandoffs").insertOne({
shop, ...link,
pixChave: currency === "BRL" ? PIX_CHAVE : null,
status: "awaiting_rider_action",
});
return link;
}
export interface PlatformAccessFee {
tier: "basic" | "pro" | "business" | "enterprise";
amountGyd: number; period: "monthly";
}
export const PLATFORM_ACCESS_FEES: Record<string, PlatformAccessFee> = {
basic: { tier: "basic", amountGyd: 0, period: "monthly" },
pro: { tier: "pro", amountGyd: 5000, period: "monthly" },
business: { tier: "business", amountGyd: 15000, period: "monthly" },
enterprise: { tier: "enterprise", amountGyd: 0, period: "monthly" },
};
const FORBIDDEN = ["wallet","escrow","custody","pool","float","hold funds","trust account"];
export function assertNonCustody(text: string): void {
const lower = text.toLowerCase();
for (const term of FORBIDDEN) {
if (lower.includes(term)) {
throw new Error(
V7.1 Non-Custody violation: term "${term}" detected. +
Escalate to eosborne.legal@gmail.com immediately.
);
}
}
}
EOF
writefile "$ROOT/app/lib/driving-school.server.ts" <<'EOF'
import { v4 as uuidv4 } from "uuid";
import { connectToDatabase } from "./mongodb.server";
export type DrivingModule =
| "player_intake_kyc" | "country_rules_loader" | "driving_school_sim"
| "live_chat" | "music_integration" | "ride_booking"
| "cross_platform" | "ai_debugging" | "qr_connect";
export interface DrivingSession {
_id: string; shop: string; playerId: string;
country: "GY" | "BR"; rulesJson: string;
module: DrivingModule; score: number; passed: boolean; createdAt: Date;
}
export const GYHUG_MODULES: Record<DrivingModule, { label: string; weight: number }> = {
player_intake_kyc: { label: "1. Player Intake KYC", weight: 10 },
country_rules_loader: { label: "2. Country Rules Loader", weight: 10 },
driving_school_sim: { label: "3. Driving School Simulation", weight: 25 },
live_chat: { label: "4. Live Chat (BitChat + WebSocket)", weight: 5 },
music_integration: { label: "5. Music Integration (Sharedrop FM)",weight: 5 },
ride_booking: { label: "6. Ride Booking via SDT", weight: 15 },
cross_platform: { label: "7. Cross-Platform", weight: 5 },
ai_debugging: { label: "8. AI Debugging Assistant", weight: 15 },
qr_connect: { label: "9. QR Connect", weight: 10 },
};
export const GTTI_SAMPLE_QUESTIONS = [
{ id: "GY-Q1", question: "What is the speed limit in a residential area?",
options: { A: "30 km/h", B: "50 km/h", C: "80 km/h", D: "100 km/h" }, answer: "B" },
{ id: "GY-Q2", question: "When must you wear a seatbelt?",
options: { A: "Only on highways", B: "At all times when driving", C: "Only at night", D: "Never" }, answer: "B" },
{ id: "GY-Q3", question: "Hazard detected: pedestrian crossing. You must:",
options: { A: "Speed up", B: "Honk and proceed", C: "Yield to pedestrians", D: "Stop regardless" }, answer: "C" },
];
export async function startDrivingSession(shop: string, playerId: string, country: "GY" | "BR"): Promise<DrivingSession> {
const session: DrivingSession = {
_id: uuidv4(), shop, playerId, country,
rulesJson: country === "GY" ? "guiana_rules.json" : "brazil_rules.json",
module: "player_intake_kyc", score: 0, passed: false, createdAt: new Date(),
};
const { db } = await connectToDatabase();
await db.collection("drivingSessions").insertOne(session);
return session;
}
export async function submitTheoryTest(sessionId: string, answers: Record<string, string>) {
let correct = 0;
for (const q of GTTI_SAMPLE_QUESTIONS) if (answers[q.id] === q.answer) correct++;
const score = Math.round((correct / GTTI_SAMPLE_QUESTIONS.length) * 100);
const passed = score >= 70;
const { db } = await connectToDatabase();
await db.collection("drivingSessions").updateOne(
{ _id: sessionId }, { $set: { score, passed, updatedAt: new Date() } }
);
return {
score, passed,
feedback: passed
? "Theory passed. Proceed to practical driving simulation."
: "Review GTTI handbook and retry. Minimum 70% required.",
};
}
EOF
============================================================================
ROUTES
============================================================================
writefile "$ROOT/app/routes/app.tsx" <<'EOF'
import { Outlet, useLoaderData } from "@remix-run/react";
import { boundary } from "@shopify/shopify-app-remix/server";
import { AppProvider } from "@shopify/shopify-app-remix/react";
import { NavMenu } from "@shopify/app-bridge-react";
import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
import { Links, Meta, Scripts, ScrollRestoration } from "@remix-run/react";
import { authenticate } from "../shopify.server";
export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
export async function loader({ request }: any) {
await authenticate.admin(request);
return { apiKey: process.env.SHOPIFY_API_KEY || "" };
}
export default function App() {
const { apiKey } = useLoaderData<typeof loader>();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<AppProvider isEmbeddedApp apiKey={apiKey}>
<NavMenu>
<a href="/app" rel="home">Dashboard</a>
<a href="/app/leads">GLIE Leads</a>
<a href="/app/shipments">Shipments</a>
<a href="/app/shipments/new">Create Shipment</a>
<a href="/app/vip-drivers">VIP Drivers</a>
<a href="/app/drivers">Driver Fleet</a>
<a href="/app/gameplay">Gameplay Flow</a>
<a href="/app/driving-school">GY-HUG Driving School</a>
<a href="/app/payments">Payments (Non-Custody)</a>
</NavMenu>
<Outlet />
</AppProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export const ErrorBoundary = boundary.error((error: any) => (
<div style={{ padding: 20 }}>
<h1>Error</h1>
<pre>{error?.statusText || error?.message}</pre>
</div>
));
EOF
writefile "$ROOT/app/routes/_index.tsx" <<'EOF'
import { redirect } from "@remix-run/node";
export const loader = () => redirect("/app");
EOF
writefile "$ROOT/app/routes/auth.$.tsx" <<'EOF'
import { authenticate } from "../shopify.server";
export const loader = async ({ request }: any) => {
await authenticate.admin(request);
return null;
};
EOF
writefile "$ROOT/app/routes/app._index.tsx" <<'EOF'
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, Link } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, Button, IndexTable, Badge, InlineGrid } from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { connectToDatabase, collections } from "../lib/mongodb.server";
export async function loader({ request }: LoaderFunctionArgs) {
const { session } = await authenticate.admin(request);
const shop = session.shop;
const { db } = await connectToDatabase();
const [shipments, leads, stats] = await Promise.all([
db.collection(collections.SHIPMENTS).find({ shop }).sort({ createdAt: -1 }).limit(10).toArray(),
db.collection(collections.LEADS).find({ shop }).sort({ createdAt: -1 }).limit(10).toArray(),
(async () => ({
shipmentsTotal: await db.collection(collections.SHIPMENTS).countDocuments({ shop }),
pending: await db.collection(collections.SHIPMENTS).countDocuments({ shop, status: "pending" }),
leadsQualified: await db.collection(collections.LEADS).countDocuments({ shop, status: "QUALIFIED" }),
leadsFlagged: await db.collection(collections.LEADS).countDocuments({ shop, status: "FLAGGED_LEGAL_REVIEW" }),
}))(),
]);
return json({ shipments, leads, stats });
}
function StatCard({ label, value }: { label: string; value: number }) {
return (
<Card>
<BlockStack gap="100">
<Text as="p" variant="bodySm" tone="subdued">{label}</Text>
<Text as="p" variant="headingLg">{value}</Text>
</BlockStack>
</Card>
);
}
export default function Dashboard() {
const { shipments, leads, stats } = useLoaderData<typeof loader>();
return (
<Page title="Sharedrop.Trading — Operations Dashboard">
<Layout>
<Layout.Section>
<BlockStack gap="400">
<InlineGrid columns={4} gap="300">
<StatCard label="Shipments" value={stats.shipmentsTotal} />
<StatCard label="Pending" value={stats.pending} />
<StatCard label="Leads Qualified" value={stats.leadsQualified} />
<StatCard label="Legal Flags" value={stats.leadsFlagged} />
</InlineGrid>
);
}
EOF
writefile "$ROOT/app/routes/app.leads.tsx" <<'EOF'
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, useFetcher } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, IndexTable, Badge, Button, Banner, TextField, Select } from "@shopify/polaris";
import { useState } from "react";
import { authenticate } from "../shopify.server";
import { LeadEvaluator, persistLead, listLeads, auditLog } from "../lib/glie.server";
export async function loader({ request }: LoaderFunctionArgs) {
const { session } = await authenticate.admin(request);
const leads = await listLeads(session.shop);
return json({ leads });
}
export async function action({ request }: ActionFunctionArgs) {
const { session } = await authenticate.admin(request);
const form = await request.formData();
const raw = {
title: String(form.get("title") || ""),
source: String(form.get("source") || "Manual"),
category: String(form.get("category") || "freight"),
description: String(form.get("description") || ""),
value_gyd: Number(form.get("value_gyd") || 0),
};
const evaluator = new LeadEvaluator();
const evaluated = evaluator.evaluateLead(session.shop, raw);
await persistLead(evaluated);
await auditLog(session.shop, "lead.created", { lead_id: evaluated.lead_id });
return json({ lead: evaluated });
}
export default function LeadsPage() {
const { leads } = useLoaderData<typeof loader>();
const fetcher = useFetcher<typeof action>();
const [title, setTitle] = useState("");
const [source, setSource] = useState("Manual");
const [category, setCategory] = useState("freight procurement");
const [description, setDescription] = useState("");
const [value, setValue] = useState("0");
const submit = () => fetcher.submit({ title, source, category, description, value_gyd: value }, { method: "post" });
return (
<Page title="GLIE V7.0 — Lead Engine" subtitle="Dual-entity routing · 6-stream waterfall · legal firewall">
<Layout>
<Layout.Section>
<Banner tone="info">Non-PSP ABSOLUTE. Investment / securities / custody terms auto-escalate to Ms. Esther Osborne.</Banner>
</Layout.Section>
<Layout.Section>
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Evaluate New Lead</Text>
<TextField label="Title" value={title} onChange={setTitle} autoComplete="off" />
<Select label="Source" value={source} onChange={setSource} options={[
{ label: "Manual", value: "Manual" },
{ label: "Government Tender Board", value: "Government Tender Board" },
{ label: "NGO", value: "NGO" },
{ label: "Webhook", value: "Webhook" },
]} />
<Select label="Category" value={category} onChange={setCategory} options={[
{ label: "Freight Procurement", value: "freight procurement" },
{ label: "Cross-Border", value: "cross-border customs" },
{ label: "Commercial Tender", value: "tender procurement" },
{ label: "Grant / Social Impact", value: "grant social impact" },
{ label: "Investment Inquiry", value: "investment" },
]} />
<TextField label="Description" value={description} onChange={setDescription} multiline={4} autoComplete="off" />
<TextField label="Estimated Value (GYD)" type="number" value={value} onChange={setValue} autoComplete="off" />
<Button variant="primary" onClick={submit} loading={fetcher.state === "submitting"}>Evaluate Lead</Button>
);
}
EOF
writefile "$ROOT/app/routes/app.shipments.tsx" <<'EOF'
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, Link } from "@remix-run/react";
import { Page, Layout, Card, IndexTable, Badge, Button, Text, BlockStack } from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { connectToDatabase, collections } from "../lib/mongodb.server";
export async function loader({ request }: LoaderFunctionArgs) {
const { session } = await authenticate.admin(request);
const { db } = await connectToDatabase();
const shipments = await db.collection(collections.SHIPMENTS).find({ shop: session.shop }).sort({ createdAt: -1 }).limit(200).toArray();
return json({ shipments });
}
export default function Shipments() {
const { shipments } = useLoaderData<typeof loader>();
return (
<Page title="Shipments"
primaryAction={<Link to="/app/shipments/new"><Button variant="primary">Create Shipment</Button></Link>}>
<Layout>
<Layout.Section>
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">All Shipments</Text>
<IndexTable resourceName={{ singular: "shipment", plural: "shipments" }} itemCount={shipments.length}
headings={[{ title: "Client ID" }, { title: "Pickup" }, { title: "Delivery" }, { title: "Gender Pref" }, { title: "Status" }]}
selectable={false}>
{shipments.map((s: any, i) => (
<IndexTable.Row id={s._id} key={s._id} position={i}>
<IndexTable.Cell>{s.clientId}</IndexTable.Cell>
<IndexTable.Cell>{s.pickup?.location || "—"}</IndexTable.Cell>
<IndexTable.Cell>{s.delivery?.location || "—"}</IndexTable.Cell>
<IndexTable.Cell>{s.preferences?.driverGender || "any"}</IndexTable.Cell>
<IndexTable.Cell>
<Badge tone={s.status === "delivered" ? "success" : s.status === "in_transit" ? "info" : "attention"}>{s.status}</Badge>
</IndexTable.Cell>
</IndexTable.Row>
))}
</IndexTable>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/app.shipments.new.tsx" <<'EOF'
import { json, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
import { useActionData, useNavigate, useSubmit } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, TextField, Select, Button, RadioButton, Banner } from "@shopify/polaris";
import { useState } from "react";
import { authenticate } from "../shopify.server";
import { connectToDatabase, collections } from "../lib/mongodb.server";
import { v4 as uuidv4 } from "uuid";
export async function loader({ request }: LoaderFunctionArgs) {
await authenticate.admin(request);
return json({ ok: true });
}
export async function action({ request }: ActionFunctionArgs) {
const { session } = await authenticate.admin(request);
const form = await request.formData();
const { db } = await connectToDatabase();
const shipment = {
_id: uuidv4(), shop: session.shop,
clientId: shipment_${Date.now()}_${uuidv4()},
description: form.get("description"),
cargoType: form.get("cargoType"),
weight: Number(form.get("weight") || 0),
pickup: { location: form.get("pickupLocation"), date: form.get("pickupDate") },
delivery: { location: form.get("deliveryLocation"), date: form.get("deliveryDate") },
preferences: { driverGender: form.get("driverGender") || "any" },
status: "pending", syncStatus: "synced",
createdAt: new Date(), updatedAt: new Date(),
};
await db.collection(collections.SHIPMENTS).insertOne(shipment);
return json({ success: true, shipment });
}
export default function NewShipment() {
const actionData = useActionData<typeof action>();
const submit = useSubmit();
const navigate = useNavigate();
const [gender, setGender] = useState("any");
if (actionData?.success) navigate("/app/shipments");
return (
<Page title="Create Shipment" backAction={{ content: "Back", onAction: () => navigate("/app") }}>
<Layout>
<Layout.Section>
<form method="post" onSubmit={(e) => { e.preventDefault(); submit(e.currentTarget); }}>
<BlockStack gap="400">
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Shipment Details</Text>
<TextField label="Description" name="description" autoComplete="off" required />
<Select label="Cargo Type" name="cargoType" options={[
{ label: "General Goods", value: "general" },
{ label: "Construction Materials", value: "construction" },
{ label: "Agricultural Products", value: "agriculture" },
{ label: "Medical Supplies", value: "medical" },
]} />
<TextField label="Weight (kg)" name="weight" type="number" autoComplete="off" />
</BlockStack>
</Card>
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Pickup & Delivery</Text>
<TextField label="Pickup Location" name="pickupLocation" autoComplete="off" required />
<TextField label="Pickup Date" name="pickupDate" type="date" autoComplete="off" />
<TextField label="Delivery Location" name="deliveryLocation" autoComplete="off" required />
<TextField label="Delivery Date" name="deliveryDate" type="date" autoComplete="off" />
</BlockStack>
</Card>
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Safety Preferences</Text>
<Banner tone="info">Select preferred driver gender. Verified against KYC during onboarding.</Banner>
<RadioButton label="No preference" name="g" checked={gender === "any"} onChange={() => setGender("any")} />
<RadioButton label="Female drivers only" name="g" checked={gender === "female"} onChange={() => setGender("female")} />
<RadioButton label="Male drivers only" name="g" checked={gender === "male"} onChange={() => setGender("male")} />
<input type="hidden" name="driverGender" value={gender} />
</BlockStack>
</Card>
<Button variant="primary" submit>Create Shipment</Button>
</BlockStack>
</form>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/app.vip-drivers.tsx" <<'EOF'
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, useFetcher } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, Button, ResourceList, ResourceItem, Badge, Banner, Select, TextField } from "@shopify/polaris";
import { useState } from "react";
import { authenticate } from "../shopify.server";
import { connectToDatabase, collections } from "../lib/mongodb.server";
import { sendPushNotification } from "../lib/notifications.server";
import { v4 as uuidv4 } from "uuid";
export async function loader({ request }: LoaderFunctionArgs) {
const { session } = await authenticate.admin(request);
const { db } = await connectToDatabase();
const drivers = await db.collection(collections.DRIVERS).find({ shop: session.shop, status: "active" }).sort({ "ratings.average": -1 }).limit(25).toArray();
return json({ drivers });
}
export async function action({ request }: ActionFunctionArgs) {
const { session } = await authenticate.admin(request);
const { driverId, shipmentId, message } = await request.json();
const { db } = await connectToDatabase();
const driver = await db.collection(collections.DRIVERS).findOne({ _id: driverId, shop: session.shop });
if (!driver) return json({ error: "Driver not found" }, { status: 404 });
const contact = {
_id: uuidv4(), shop: session.shop, driverId, shipmentId, message,
status: "pending", createdAt: new Date(),
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
};
await db.collection(collections.VIP_CONTACTS).insertOne(contact);
const notification = await sendPushNotification({
userId: driver.userId || driver._id,
title: "🚀 VIP Shipment Request",
body: VIP client requested you for shipment ${shipmentId},
data: { type: "vip_request", contactId: contact._id, shipmentId },
persistUntilDelivered: true, expiresAt: contact.expiresAt,
});
return json({ contact, notification });
}
export default function VIPDrivers() {
const { drivers } = useLoaderData<typeof loader>();
const fetcher = useFetcher();
const [selected, setSelected] = useState<any>(null);
const [message, setMessage] = useState("");
const [vehicle, setVehicle] = useState("all");
const [gender, setGender] = useState("any");
const filtered = drivers.filter((d: any) => {
if (vehicle !== "all" && d.vehicleType !== vehicle) return false;
if (gender !== "any" && d.gender !== gender) return false;
return true;
});
const send = () => {
if (!selected) return;
fetcher.submit(JSON.stringify({ driverId: selected._id, shipmentId: "demo", message }),
{ method: "post", encType: "application/json" });
};
return (
<Page title="VIP Driver Contact" subtitle="Reach drivers even when they're offline">
<Layout>
<Layout.Section>
<Banner tone="info">Requests are queued and delivered when the driver reconnects. Non-PSP: no funds held.</Banner>
</Layout.Section>
<Layout.Section>
<Card>
<BlockStack gap="300">
<div style={{ display: "flex", gap: 12 }}>
<Select label="Vehicle" value={vehicle} onChange={setVehicle} options={[
{ label: "All", value: "all" }, { label: "Car", value: "car" },
{ label: "Motorcycle", value: "motorcycle" }, { label: "Truck", value: "truck" },
]} />
<Select label="Gender" value={gender} onChange={setGender} options={[
{ label: "Any", value: "any" }, { label: "Female", value: "female" }, { label: "Male", value: "male" },
]} />
</div>
<ResourceList
resourceName={{ singular: "driver", plural: "drivers" }}
items={filtered.map((d: any) => ({
id: d._id, name: d.name, rating: d.ratings?.average || 0,
trips: d.completedTrips || 0, vehicleType: d.vehicleType, status: d.status,
}))}
renderItem={(item) => (
<ResourceItem id={item.id} onClick={() => setSelected(item)} accessibilityLabel={Select ${item.name}}>
<BlockStack gap="100">
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<Text as="span" fontWeight="semibold">{item.name}</Text>
<Badge tone={item.status === "active" ? "success" : "attention"}>{item.status}</Badge>
</div>
<Text as="p" variant="bodySm" tone="subdued">
⭐ {item.rating.toFixed(1)} · {item.trips} trips · {item.vehicleType}
</Text>
</BlockStack>
</ResourceItem>
)}
/>
{selected && (
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Message to {selected.name}</Text>
<TextField label="Personal message" value={message} onChange={setMessage} multiline={3} autoComplete="off" />
<Button variant="primary" onClick={send} loading={fetcher.state === "submitting"}>Send VIP Request</Button>
</BlockStack>
)}
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/app.drivers.tsx" <<'EOF'
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, IndexTable, Badge } from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { connectToDatabase, collections } from "../lib/mongodb.server";
export async function loader({ request }: LoaderFunctionArgs) {
const { session } = await authenticate.admin(request);
const { db } = await connectToDatabase();
const drivers = await db.collection(collections.DRIVERS).find({ shop: session.shop }).sort({ createdAt: -1 }).toArray();
return json({ drivers });
}
export default function Drivers() {
const { drivers } = useLoaderData<typeof loader>();
return (
<Page title="Driver Fleet">
<Layout>
<Layout.Section>
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">All Drivers</Text>
<IndexTable resourceName={{ singular: "driver", plural: "drivers" }} itemCount={drivers.length}
headings={[{ title: "Name" }, { title: "Vehicle" }, { title: "Rating" }, { title: "Trips" }, { title: "Status" }]}
selectable={false}>
{drivers.map((d: any, i) => (
<IndexTable.Row id={d._id} key={d._id} position={i}>
<IndexTable.Cell>{d.name}</IndexTable.Cell>
<IndexTable.Cell>{d.vehicleType || "—"}</IndexTable.Cell>
<IndexTable.Cell>⭐ {(d.ratings?.average || 0).toFixed(1)}</IndexTable.Cell>
<IndexTable.Cell>{d.completedTrips || 0}</IndexTable.Cell>
<IndexTable.Cell>
<Badge tone={d.status === "active" ? "success" : "attention"}>{d.status}</Badge>
</IndexTable.Cell>
</IndexTable.Row>
))}
</IndexTable>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/app.gameplay.tsx" <<'EOF'
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, InlineGrid, Badge } from "@shopify/polaris";
import { authenticate } from "../shopify.server";
const FLOW = [
{ n: 1, title: "Player Signs Up", desc: "Profile stored via Sharedrop.Trading API" },
{ n: 2, title: "Country Selected", desc: "Rules JSON loaded: Guyana / Brazil" },
{ n: 3, title: "Simulation Engine Runs", desc: "Written + Practical Tests · GTTI Standards" },
{ n: 4, title: "Live Chat + Sharedrop FM", desc: "Music + real-time comms" },
{ n: 5, title: "Ride Booking + Cargo Logistics",desc: "85% Driver Share" },
{ n: 6, title: "QR Verification", desc: "WhatsApp / Sekai roleplay" },
{ n: 7, title: "AI Debugging", desc: "Keeps gameplay error-free" },
{ n: 8, title: "Progress → Real Onboarding", desc: "Feeds into driver pipeline · Fleet expansion" },
];
export async function loader({ request }: LoaderFunctionArgs) {
await authenticate.admin(request);
return json({ flow: FLOW });
}
export default function Gameplay() {
const { flow } = useLoaderData<typeof loader>();
return (
<Page title="Gameplay Flow — 8 Steps to Real Earnings" subtitle="Branded · Luxury · Scalable">
<Layout>
<Layout.Section>
<InlineGrid columns={4} gap="300">
{flow.map((s) => (
<Card key={s.n}>
<BlockStack gap="100">
<Badge tone="info">STEP {s.n}</Badge>
<Text as="p" fontWeight="semibold">{s.title}</Text>
<Text as="p" variant="bodySm" tone="subdued">{s.desc}</Text>
</BlockStack>
</Card>
))}
</InlineGrid>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/app.driving-school.tsx" <<'EOF'
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, useFetcher } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, Button, InlineGrid, Badge, Banner, RadioButton } from "@shopify/polaris";
import { useState } from "react";
import { authenticate } from "../shopify.server";
import { GYHUG_MODULES, GTTI_SAMPLE_QUESTIONS, startDrivingSession, submitTheoryTest } from "../lib/driving-school.server";
export async function loader({ request }: LoaderFunctionArgs) {
await authenticate.admin(request);
return json({ modules: GYHUG_MODULES, questions: GTTI_SAMPLE_QUESTIONS });
}
export async function action({ request }: ActionFunctionArgs) {
const { session } = await authenticate.admin(request);
const form = await request.formData();
const intent = String(form.get("intent"));
if (intent === "start") {
const country = (String(form.get("country")) || "GY") as "GY" | "BR";
const s = await startDrivingSession(session.shop, "player-demo", country);
return json({ session: s });
}
if (intent === "submit") {
const sessionId = String(form.get("sessionId"));
const answers: Record<string, string> = {};
for (const [k, v] of form.entries()) if (k.startsWith("q_")) answers[k.replace("q_", "")] = String(v);
const result = await submitTheoryTest(sessionId, answers);
return json({ result });
}
return json({ error: "Unknown intent" }, { status: 400 });
}
export default function DrivingSchool() {
const { modules, questions } = useLoaderData<typeof loader>();
const fetcher = useFetcher<any>();
const [answers, setAnswers] = useState<Record<string, string>>({});
return (
<Page title="GY-HUG Driving School Simulation" subtitle="Certified by GTTI Standards · Superapp Module V1.0">
<Layout>
<Layout.Section>
<Banner tone="info">9 core modules. Offline-first. Works in Region 9 without cellular.</Banner>
</Layout.Section>
<Layout.Section>
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Module Map</Text>
<InlineGrid columns={3} gap="300">
{Object.entries(modules).map(([key, m]: any) => (
<Card key={key}>
<BlockStack gap="100">
<Text as="p" fontWeight="semibold">{m.label}</Text>
<Badge tone="success">weight {m.weight}%</Badge>
</BlockStack>
</Card>
))}
</InlineGrid>
</BlockStack>
</Card>
</Layout.Section>
<Layout.Section>
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Theory Test — GTTI Standards</Text>
{questions.map((q, idx) => (
<BlockStack key={q.id} gap="100">
<Text as="p" fontWeight="semibold">Q{idx + 1}. {q.question}</Text>
{Object.entries(q.options).map(([k, v]) => (
<RadioButton key={k} label={${k}) ${v}} name={q_${q.id}}
checked={answers[q.id] === k} onChange={() => setAnswers({ ...answers, [q.id]: k })} />
))}
</BlockStack>
))}
<Button variant="primary" onClick={() =>
fetcher.submit(
{ intent: "submit", sessionId: "demo",
...Object.fromEntries(Object.entries(answers).map(([k, v]) => [q_${k}, v])) },
{ method: "post" }
)}>Submit Test</Button>
{fetcher.data?.result && (
<Banner tone={fetcher.data.result.passed ? "success" : "critical"}
title={Score: ${fetcher.data.result.score}% · ${fetcher.data.result.passed ? "PASSED" : "FAILED"}}>
{fetcher.data.result.feedback}
</Banner>
)}
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/app.payments.tsx" <<'EOF'
import { json, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { useLoaderData, useFetcher } from "@remix-run/react";
import { Page, Layout, Card, Text, BlockStack, Button, Banner, Badge, InlineGrid } from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { PLATFORM_ACCESS_FEES, createP2PHandoff, assertNonCustody } from "../lib/payments.server";
const PIX_CHAVE = "71086287240";
export async function loader({ request }: LoaderFunctionArgs) {
await authenticate.admin(request);
return json({ fees: PLATFORM_ACCESS_FEES, pixChave: PIX_CHAVE });
}
export async function action({ request }: ActionFunctionArgs) {
const { session } = await authenticate.admin(request);
const form = await request.formData();
const intent = String(form.get("intent"));
try {
if (intent === "handoff") {
const amount = Number(form.get("amount") || 0);
const currency = (String(form.get("currency")) || "GYD") as "GYD" | "BRL";
assertNonCustody(create handoff amount=${amount} currency=${currency});
const link = await createP2PHandoff(session.shop, String(form.get("orderId")), amount, currency);
return json({ link });
}
} catch (e: any) {
return json({ error: e.message }, { status: 400 });
}
return json({ error: "Unknown intent" }, { status: 400 });
}
export default function Payments() {
const { fees, pixChave } = useLoaderData<typeof loader>();
const fetcher = useFetcher<any>();
return (
<Page title="Payments — Day 1 Non-Custody Model"
subtitle="V7.1 · P2P Direct · No Wallet · No Escrow · PCI-Compliant · Non-PSP">
<Layout>
<Layout.Section>
<Banner tone="warning" title="V7.1 Legal Boundary — Absolute">
SDT Ltd does not hold, store, or control any rider or driver funds. All payments flow
Rider → Licensed Partner (Be!, MMG, Stripe, PIX) → Driver directly. Platform Access Fees
are invoiced separately by SDT Ltd (software license only).
</Banner>
</Layout.Section>
<Layout.Section>
<InlineGrid columns={2} gap="300">
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">P2P Payment Handoff</Text>
<Text as="p" variant="bodySm">Generate a deep link to a licensed PSP. SDT never touches funds.</Text>
<Button variant="primary" onClick={() =>
fetcher.submit({ intent: "handoff", orderId: "demo", amount: "45000", currency: "GYD" }, { method: "post" })}>
Create GYD Handoff
</Button>
<Button onClick={() =>
fetcher.submit({ intent: "handoff", orderId: "demo", amount: "320", currency: "BRL" }, { method: "post" })}>
Create BRL Handoff (PIX)
</Button>
{fetcher.data?.link && (
<Banner tone="success" title="Handoff created">
Provider: {fetcher.data.link.provider} · Non-custody: true
</Banner>
)}
{fetcher.data?.error && (
<Banner tone="critical" title="Blocked by V7.1 firewall">{fetcher.data.error}</Banner>
)}
</BlockStack>
</Card>
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">Platform Access Fees</Text>
<Text as="p" variant="bodySm">Software license · Invoiced by SDT Ltd</Text>
{Object.values(fees).map((f: any) => (
<div key={f.tier} style={{ display: "flex", justifyContent: "space-between" }}>
<Text as="span" fontWeight="semibold">{f.tier.toUpperCase()}</Text>
<Text as="span">{f.amountGyd === 0 ? "FREE / Custom" : GYD ${f.amountGyd.toLocaleString()}/mo}</Text>
</div>
))}
</BlockStack>
</Card>
</InlineGrid>
</Layout.Section>
<Layout.Section>
<Card>
<BlockStack gap="200">
<Text as="h2" variant="headingMd">Brazil Instant Payments</Text>
<Badge tone="success">PIX · Instant · Non-Custodial</Badge>
<Text as="p">PIX Chave: <strong>{pixChave}</strong></Text>
<Text as="p" variant="bodySm" tone="subdued">
Aceitamos PIX · Payment flows directly to driver's nominated account.
SDT Brazil Ltda corporate account for Platform Access Fees only.
</Text>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
EOF
writefile "$ROOT/app/routes/webhooks.tsx" <<'EOF'
import { type ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import { connectToDatabase, collections } from "../lib/mongodb.server";
import { v4 as uuidv4 } from "uuid";
export async function action({ request }: ActionFunctionArgs) {
const { topic, shop, admin, payload } = await authenticate.webhook(request);
if (!admin) return new Response();
const { db } = await connectToDatabase();
switch (topic) {
case "ORDERS_PAID": {
const order: any = payload;
await db.collection(collections.SHIPMENTS).insertOne({
_id: uuidv4(), shop, orderId: String(order.id), orderName: order.name,
customer: { email: order.email, name: ${order.customer?.first_name || ""} ${order.customer?.last_name || ""}.trim() },
pickup: { location: "Lethem Hub", date: new Date() },
delivery: { location: order.shipping_address ? ${order.shipping_address.address1}, ${order.shipping_address.city} : "Customer Address", date: new Date() },
status: "pending", syncStatus: "synced",
clientId: shopify_${order.id},
preferences: { driverGender: "any" },
createdAt: new Date(), updatedAt: new Date(),
});
break;
}
case "FULFILLMENTS_CREATE":
case "FULFILLMENTS_UPDATE": {
const f: any = payload;
await db.collection(collections.SHIPMENTS).updateOne(
{ shop, orderId: String(f.order_id) },
{ $set: { status: f.status === "success" ? "in_transit" : f.status,
trackingNumber: f.tracking_number, trackingCompany: f.tracking_company, updatedAt: new Date() } });
break;
}
case "ORDERS_FULFILLED": {
const order: any = payload;
await db.collection(collections.SHIPMENTS).updateOne(
{ shop, orderId: String(order.id) },
{ $set: { status: "delivered", updatedAt: new Date() } });
break;
}
case "CUSTOMERS_CREATE": {
const c: any = payload;
await db.collection("customers").updateOne(
{ shop, customerId: String(c.id) },
{ $set: { shop, customerId: String(c.id),
emailHash: c.email ? Buffer.from(c.email).toString("base64") : null,
region: c.default_address?.province || null, updatedAt: new Date() } },
{ upsert: true });
break;
}
}
return new Response();
}
EOF
============================================================================
SERVICE WORKER + PUBLIC
============================================================================
writefile "$ROOT/public/service-worker.js" <<'EOF'
const CACHE_NAME = "sharedrop-admin-v7";
const API_PREFIX = "/api";
const PRECACHE = ["/", "/app", "/app/leads", "/app/shipments", "/app/vip-drivers", "/app/driving-school", "/app/payments"];
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((c) => c.addAll(PRECACHE)).then(() => self.skipWaiting()));
});
self.addEventListener("activate", (event) => {
event.waitUntil(caches.keys().then((names) =>
Promise.all(names.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n)))
).then(() => self.clients.claim()));
});
self.addEventListener("fetch", (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== "GET") { event.respondWith(fetch(request)); return; }
if (url.pathname.startsWith(API_PREFIX)) { event.respondWith(networkFirst(request)); return; }
event.respondWith(cacheFirst(request));
});
async function networkFirst(request) {
const cache = await caches.open(CACHE_NAME);
try {
const res = await fetch(request);
if (res.ok) cache.put(request, res.clone());
return res;
} catch (err) {
const cached = await cache.match(request);
if (cached) return cached;
throw err;
}
}
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
try {
const res = await fetch(request);
if (res.ok) { const cache = await caches.open(CACHE_NAME); cache.put(request, res.clone()); }
return res;
} catch (err) {
if (request.mode === "navigate") return caches.match("/app");
throw err;
}
}
self.addEventListener("sync", (event) => {
if (event.tag === "sync-requests") {
event.waitUntil((async () => {
const clients = await self.clients.matchAll();
clients.forEach((c) => c.postMessage({ type: "PERFORM_SYNC" }));
})());
}
});
EOF
writefile "$ROOT/public/offline.html" <<'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Sharedrop — Offline</title>
<style>
body { font-family: -apple-system, sans-serif; background: #0b1b3d; color: #fff;
display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; text-align: center; }
.card { max-width: 480px; padding: 40px; }
h1 { color: #d4af37; margin-bottom: 8px; }
p { opacity: 0.85; line-height: 1.6; }
</style>
</head>
<body>
<div class="card">
<h1>Offline</h1>
<p>Sharedrop.Trading is offline-first. Your changes will sync automatically when the connection is restored.</p>
<p><a href="/app" style="color:#d4af37">Retry</a></p>
</div>
</body>
</html>
EOF
============================================================================
CHECKOUT EXTENSION
============================================================================
writefile "$ROOT/extensions/delivery-date/shopify.extension.toml" <<'EOF'
api_version = "2026-04"
[[extensions]]
name = "Sharedrop Delivery Date"
handle = "delivery-date"
type = "ui_extension"
[[extensions.targeting]]
module = "./src/Checkout.tsx"
target = "purchase.checkout.delivery-address.render-after"
[extensions.capabilities]
api_access = true
network_access = true
EOF
writefile "$ROOT/extensions/delivery-date/src/Checkout.tsx" <<'EOF'
import {
reactExtension, useApplyMetafieldsChange, useShippingAddress, useCartLines,
BlockStack, Text, DateField, Banner,
} from "@shopify/ui-extensions-react/checkout";
import { useState } from "react";
export default reactExtension("purchase.checkout.delivery-address.render-after", () => <Extension />);
function Extension() {
const [date, setDate] = useState<string>("");
const applyMetafield = useApplyMetafieldsChange();
const addr = useShippingAddress();
const lines = useCartLines();
const onChange = (value: string) => {
setDate(value);
applyMetafield({
namespace: "sharedrop", key: "preferred_delivery_date",
value, type: "single_line_text_field",
});
};
return (
<BlockStack spacing="tight">
<Banner status="info">
<Text>Sharedrop coordinates delivery. Choose your preferred date.</Text>
</Banner>
<DateField label="Preferred delivery date" value={date} onChange={onChange} />
{addr?.city && (
<Text size="small" appearance="subdued">Delivering to {addr.city}, {addr.countryCode}</Text>
)}
<Text size="small" appearance="subdued">{lines.length} item(s) in cart</Text>
</BlockStack>
);
}
EOF
============================================================================
DOCS
============================================================================
writefile "$ROOT/docs/AGENT_INSTRUCTIONS.md" <<'EOF'
Agent Instructions — Locked Decisions V7.1
1. Maps API
Leaflet + OpenStreetMap (free). Fallback: Mapbox for vector tiles.
2. Payments (V7.1 Non-Custody ABSOLUTE)
· Mock initially. Swap to licensed PSP when keys provided.
· NO wallet. NO escrow. NO custody. NO pooled funds.
· P2P: Rider → Licensed Partner → Driver bank account.
· Platform Access Fee: invoiced separately by SDT Ltd.
· PIX Chave: 71086287240
· Every payment record MUST call assertNonCustody().
3. Real-time
Socket.io. Namespaces: /rides, /chat, /alerts.
Fallback: polling 15s for offline regions.
4. Build Order
1. Phase 1: Job Board + Profiles + Ratings + KYC
2. Phase 2: Live Map + Matching + Offline sync
3. GY-HUG Driving School (9 modules)
4. Gameplay Flow (8 steps)
5. Payments V7.1 UI
6. Trading + Ratings
7. Admin Dashboard
5. DO NOT BUILD
· Wallet, escrow, custody, pooled funds
· Securities/investment intake
· Any feature holding rider/driver money
EOF
writefile "$ROOT/docs/LEGAL_FIREWALL.md" <<'EOF'
V7.1 Legal Firewall — ABSOLUTE
Non-Custody Model
· SDT Ltd is NOT a PSP (Handbook Part IV 16)
· No wallet, escrow, custody, or pooled funds
· All payments via licensed external PSPs
Forbidden Terms (auto-escalate)
investment, securities, equity sale, shares offering, public offering, ipo,
custody, wallet, escrow account, hold funds
Escalation Target
eosborne.legal@gmail.com (Ms. Esther Osborne)
Enforcement
Every payment record calls assertNonCustody() from app/lib/payments.server.ts
EOF
writefile "$ROOT/docs/DEPLOY.md" <<'EOF'
Deployment Runbook
```bash
# 1. Install
npm install
npx prisma generate
npx prisma db push
# 2. Env
cp .env.example .env
# Fill SHOPIFY_API_KEY, SHOPIFY_API_SECRET, MONGODB_URI
# 3. Local dev
shopify app dev
# 4. Deploy to Fly.io
fly launch --no-deploy
fly secrets set SHOPIFY_API_KEY=... SHOPIFY_API_SECRET=... \
SHOPIFY_APP_URL=https://s...content-available-to-author-only...y.dev \
MONGODB_URI="mongodb+srv://..." \
LEGAL_COUNSEL_EMAIL=eosborne.legal@gmail.com \
NON_PSP_MODE=true \
PIX_CHAVE=71086287240
fly deploy
# 5. Push config to Shopify
shopify app deploy
```
EOF
============================================================================
ZIPPING
============================================================================
echo ""
echo "📦 Packaging $ROOT..."
zip -r "$ZIP" "$ROOT" -x "/node_modules/" ".sqlite" > /dev/null
echo ""
echo "✅ Created: $ZIP"
echo ""
echo "📂 Files included:"
find "$ROOT" -type f | sort | sed "s|^| |"
echo ""
echo "📊 Size: $(du -h "$ZIP" | cut -f1)"
echo ""
echo "🚀 To use:"
echo " unzip $ZIP"
echo " cd $ROOT"
echo " npm install"
echo " npx prisma generate && npx prisma db push"
echo " cp .env.example .env # fill values"
echo " shopify app dev"
```
---
## HOW TO RUN
```bash
# 1. Save the script as build_sharedrop_v7_1.sh
chmod +x build_sharedrop_v7_1.sh
# 2. Run it
./build_sharedrop_v7_1.sh
# 3. Zip appears in current directory
ls -la sharedrop-trading-v7.1.zip
```
---
WHAT'S IN THE ZIP
```
sharedrop-trading-v7.1/
├── README.md
├── .env.example
├── .gitignore
├── package.json
├── tsconfig.json
├── shopify.app.toml
├── Dockerfile
├── fly.toml
├── prisma/
│ └── schema.prisma
├── app/
│ ├── shopify.server.ts
│ ├── db.server.ts
│ ├── root.tsx
│ ├── lib/
│ │ ├── mongodb.server.ts
│ │ ├── glie.server.ts ← GLIE V7.0 engine
│ │ ├── notifications.server.ts
│ │ ├── fulfillment.server.ts
│ │ ├── payments.server.ts ← V7.1 Non-Custody
│ │ └── driving-school.server.ts ← GY-HUG module
│ └── routes/
│ ├── app.tsx ← NavMenu (9 items)
│ ├── _index.tsx
│ ├── auth.$.tsx
│ ├── app._index.tsx ← Dashboard
│ ├── app.leads.tsx ← GLIE lead engine
│ ├── app.shipments.tsx
│ ├── app.shipments.new.tsx ← Safety preferences
│ ├── app.vip-drivers.tsx ← Offline contact
│ ├── app.drivers.tsx
│ ├── app.gameplay.tsx ← 8-step flow
│ ├── app.driving-school.tsx ← 9 GTTI modules
│ ├── app.payments.tsx ← Non-custody UI
│ └── webhooks.tsx
├── public/
│ ├── service-worker.js
│ └── offline.html
├── extensions/delivery-date/
│ ├── shopify.extension.toml
│ └── src/Checkout.tsx
└── docs/
├── AGENT_INSTRUCTIONS.md
├── LEGAL_FIREWALL.md
└── DEPLOY.md
```
---
VERIFICATION CHECKLIST AFTER UNZIP
```bash
[ ] unzip sharedrop-trading-v7.1.zip
[ ] cd sharedrop-trading-v7.1
[ ] npm install # succeeds
[ ] npx prisma generate # succeeds
[ ] grep -r "assertNonCustody" app/ # finds payments.server.ts + app.payments.tsx
[ ] grep -r "71086287240" app/ # finds payments.server.ts + app.payments.tsx
[ ] grep -r "GY-HUG" app/routes/ # finds driving-school + gameplay
[ ] ls app/routes/ # shows 9+ routes
```
Everything from all previous responses is bundled: GLIE V7.0, VIP drivers, safety preferences, GY-HUG driving school, gameplay flow, V7.1 non-custody payments, service worker, checkout extension, and deployment configs. Run the script and you have your zip.