diff --git a/auto/gui.files.go b/auto/gui.files.go index c8b4d9a4b..67b52e0a3 100644 --- a/auto/gui.files.go +++ b/auto/gui.files.go @@ -18,7 +18,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["angular.min.js"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ffec3d6b73db3892dffd2b30bacc887264ca4e66e7f6e238531927b3ebcdcb15277357e7f154512224714c910a1f7154b6fffb750320892749c54976b7ea54bbb104341a8d46a3d1dd68602613729cae3759b45816c43b1e9107fb073f927f0497e994fc92660b122421498b25cdc82c4d8a2c9a96459ae53e791ac784b5ca4946739a7da4a1bf03d8dee794a473522ca39ce46999cd28340c29819f8bf423cd121a92e906d0925727eff6f26213531247339a40bb621914640655538aa8e669099d47099453f2f2e4f8f9ebb3e7641ec5d4dfd999ecfe99c751529069965e41f78f48919574cc888c929256bfd77199e3fff96fb23b81968b389d0631b9f788cc833807a0205994719089df08b4332ce14b0ee39d15c3c39d9d8f4146f24d328361250b7254b5f0576958c6d41bd675c33139bf181db20665164f0340734486c02286a786f381d079b4f0e62514446942bc7bcba2589f66e9c728a4d9885cef10f828857e48e7411917b9ff29cfe67fa70194bd0e56ac83ffd93b3e7bfbebdebbf49226d05147dbe334bd8c68d55669793bd2c92cb2348e69e60dcfaad2e3228b61a012edf92c5d0323599715edc88275463f3e0b0aec66ffb02e5dd0e2cd0b28c229694a914741567006b399004ad84018762425a1acc31c00ae6f0fb54a60a759beda9c3cc3310e95d2042412919c5f58909c243850853c51bfced2229da5f1f112e61fe4b8265382a15906ebc3c49d539a3cc73a931a10221a9ba467749d5a468ac559014c0d5c75a7c0f4885ed9b998501a3e6db8c8aaf033cc56c34764f88cc2ccca8561948972e2c1f7915c8b628295c82fb9bc48cbd9122bdeaf43987c5175ab937132731091d115a80a2b1d66554544985e25711a84564282bca059945fd6a4b03fa063ce6089278b1c34ce3ccd2899a6699c931896079414d0469dc2a260c030b9acfc3a0a01f7cb0890278008960488d52c131c21a7425c0887204fc310643ca73900169b35e8a861413f15c3dbb184ed55f0e98c26e18be93a97f0bd298b458a2be32d2ea697d12a026dfd22fa65928f1a6449b99ad24c45f7161004c9490243f918c467124a5e43aa2ae275a3122bd08e4d546e81f034c802d02cf15bfaa184b52f8f17b84060cca01092900dbb01694108adf8d2d4b887d87e857d83f05acec416f6c9939bce82f869929cc2ba9230b262f22c02c1803d6d4344750b6d15a2e749308d69e8c6d5a041615491fc8ded5c562cbcaa1f9a33d4b3bff08d53165a2c264db9b3fdfbd3e4d4a4809710ac6c6dfcd66cfa344993cd2a2d73301f029c20a6c7f8766a4174a1aab445199dd997a65871329bde9f742dc771b513719b41255ee518627b5a82790246c72c607be17b85779d084f833cbf4ab3b01da9042510af9b92566adfbd9457291a677f7ff7eef40c6cab8c40471a7fdb903d3d3d794137f2a49d9e105e225004ebe8127e6b93545b09b0eb9f95b319287e1a7a2349eb4773e27dc76c02b9549ae028890a6f74a85679c3ff4868013cb864dbea7084fb68107bc325d83b430ddab4389a1da922a1b13fb624a369d84e04c2e6cbb2c09daa1dd2660aa944df9accfd3500156772d63dac8c166596b8d8619f1017d7afa7c1ec32ccd235c801f407920b7201d2304d832c1486f5ad6352da06581b367318c712816ba353a68d199e3e20f42aa3fb3e194ef20d2cf415d098a3dce5b9646d835d12e8835305f4d026021c239081ed259247dceef3ecd409e4d504c9ed9aef6fa67fc2f6e903cf724fb600473eacd5e7c16c29218f421d3dce182e4370bd64abf23c0a2fc8d111989ce04ad17904ded7506f8a1fb0845e53d836c89c16b325ba68cc1e32e0ec7c665dfd8cd41e0da18026e8f1bd7f7b729caed669027a0c09ee3b0b1ac7a561684cb73191fd2614bd38136b0bf113187b0e247de941e0b460bd2ff09327c6b8aa2a178a6fccf6add9ef9a86babc7bc2acebc13e66c909ecbdaed1b94c52f487d01505e7efca1b8d1588027d390f61f66a97754426e4607f7f5f858c42b1ad551fc9c385f6eac070938539c010868be56cef6312b20cf2375709f80c6b9a151b36758e29aa621c96a5609414609cdab160a74cfea204ac6520ff55502cfd55f0c9db1f93bf925dce4b067192fcb22968fe2e2dc0c2dcb3f8e3061432af081d1251e34dcba2ab6b7002faf4ad80393bbf25604fcd96c4a32ed61a4cd9ef3b040be0ad43caf1638d69e89b4ad762e0b186deeb408f50d8bbbb3ddcd1430ae0181465ae6cbe58ac5b1ab6fd07e13a77206e8880799a5c26681bc9b680d283891a260a7c4d5863df61174ecc6745ba5e43cf56cc2cb857f0353c444d6ae986d537ad2b70f6f77cffc22fd2f7d041760c53035bff7d5e01f332cd8bcc3b181daae310cd8fc8000305609a0dc8cd0d694a4fc2980ef4b1f0eafb504dbc414325c63f4e6936037d0fae939898fb64f0fd68601dad60893420cb8c1fc7e05f7cfd098f9279fa15663b442f3ffb6293adccd83082b971f62c16e2d065543768723ef14e4ceb2c5a05e0c6db3059d8a7cda12a145f7d22619f748dd8c25e1e7f67fa9ae1dfef875599c2f5ac80510104ec165639c185c5bb98d800241a0e75beb2dd681ea760cf433776b58811648b5ac4e2e3b9e26921b5a8e601ceb2798906fe6bf87bf2ec42153a04b419fa58eea3951753d62d32115861dbd46ad5ba2645caec9f614f6bb9569d5c5089878b45ef1976a1ef473ac616ad3364f12936fc5a1b5b388b91e17f0fbea697dbf253b8b2db306d05d65eeee696a9acff65d965eac75e3c33956127cfdcba11c78ab1bf6fcc314118639a083db6e9f69f5be65b62f93f6d089a1268dda7dc43f98d764e0312a352ca044d3adf73902920847b6da3ef6bb2278e60effdcddd77c73cc3761b62c70677d4e1e20856e8d3b0e34cf9741330c405952352486fd56dd2b09270ac878abf81431478fd98268b6209261739708cb8b60f5a062ab081adec1406711edd290dc25a11b53d4d94c1c065a1d4f30cdd3b1acb202d635425a6b2fec1a7fdc96e446092c46bdbc0f5294eb82468cd3c6dc224bef4658a5772978b351ab5b2a88b3f7d98d39b33348c0ae94cc7ce99c9044cb54b4a0282f168b451a0eda6aeaeb8b55abf59571e7695b781809e72eaef0b20d9e9d611f8f5d915868b6cd54f617b5b8369439e80556bc384873bad640080dc108f0c041ffac6dad9c98d9dad79f011760f91295131d590b6192e2af28fb337affd9c1d8f47738d4a89426c90ae0b768ebf642929f923723d845e0a50807bef4026d901fc7a1d8bd3acc99f799a0c6f6ff5e8c63acd8d581ff405630382c6ac135ba4c38872b8e286806bc29205b60989da334358c04e2ed30c134b88df3213bd053c2f33f8275d519e7e34e3e927ea9cd52929df55c2453f94419cdba57c6c0af788dcdc18c3e79f769420b16355c235bd2468d3d98ba9674b3abb4498921dfb66d5b12f59069892411342c57a4b33124639fb6ed8a16debf4871faccbb85ea78f2deea7344df646ccccddb7dab02c9e7b178250716c4dcede816e193b395de53091ab288e09a6e1a06338a5b50481108a23438dd1e6b4fbfc8099456314011015ee81989954eac9ac7d104f41896c80e62b52a7e2c4983c116f762c7da812efd0ba92fc1fb62071ab6d43e29d14f83cef40d8fe8acd69429d15999f83ca2cbce118157fb09694d527c992fbe4838a5e8129c0348d8d8a46e92b478f3bed9bcc00cfa507ae9830930fa7de528eafd589759c976f7b7cecd83104e6a1b9f55a53fb64457b7615e1d1c0159dae5115d5cb04964cc24e8477cc95a049b126ee3cff51a0016b0c491e6a53d4b2aaccc5a32333dc6815374ce9bb6845d3b2f09aadde5c9157e069a4573e2e2404f1a55eaaaf5a3763a6fe1cd2d69a23c9a964ff6adba1488bf82c91b2cb829469d1c368b0a5666c2b94b73dc5ce327eb477adee9ee6fe5468cb2c03eb4ab4b8e7d34fa03242ef1aa6a66a6490825d00fb9e7f02fd6265a3027646e3393f13d53c6fc5f136c7db10e6d7f955a0cbc851ed1cd5c5fe9f6994807a2396e58ac0cf819234f3ef811c9f668c68c5e3c129abd8f6054ce3c874b715c6436f51f8414a33ca97e9d5d08e2b083b90d966f15ae617e6b36e926005f4dff698496d8959a7d20ef3ed191dd29816b493d9468f46a2946c6a692ce9ce74b2f1a15f08c5eaff56eb43b284e4a5c06b7b680a06a9d321295a9651801b0bcf2a50f284eca7bdac8e1d556ba8b58a1e83bdc380f5419b13605a29168fa9556aaa40c9317a8b619ad031890e77b614aa3aa864199501556b453df203bc8d8319f5266402b40ca1bbba64af2a514e8c2b23cee8a1560a360d7a57231199646a069eb5c2521a48441eab4b820703a1e2fe7ddb91800c7b1e5dc8015b95452d3e57d5b819f1a1012a28379d06fc4c331a5cf6382ae0b93780c9b174f888d765bef4cc5dd5a142fc1c3c58af8ad20719fddc95de7759b0bb59152e97ed24c7c39daaadb766c3adbf33c05105295b976b04f68aaedd3e43f2128d8d203c878668263d4f0fe4c1bb85c83262f043dbe7a15250d2ac37b3a72f7e217846c857029302abb9ddaac44c1ff4277b8a06cf0c3265c315f6a53e3a184d4e637dc7a9533a66310db2e7552252ab95245f9c52e83c57a916c7147be4e08291d5e920b076134689c38e9b671198d5f1c69cd6bcc864529b6d797be16d22aead229c33231afeadb79259ad4cc7fa290ad68d462dc178c0e3f0ecd7691f81a9e0d44c65a76b83f739b6736d440b25e2e2746da436202e314b2ea8e6ecdab49f657061fb1819d686f1d3d5d579a55f2ecc6b069ad16a41250e0ad19a6f028476001f43ea4c890df208cf608d44311ba90c12ef40493dd9f64f67db1794aea1c5fd76daf04ad72af72f01d8267d5dd8dbeb6f6ec85f3ed7ad4501ede9e760b75fc0cf0127d010fb4e79bf5664ea1148efe7bb7fdf7ec868acb48e995ff35da71d26ba428e61a20b04567131a06a33ebfcc2ac5317b06413986bb8d6f0fcb41c9d2f2b169b516cef2ea9b4c511efcb6e874883e006c135d7328f04212db9f48d99cf3c6e3bbd5a626a0d63d117a611c061158d628e61581d01729cc3b109c1b506c098ad593daa13c48019682a85a8174c845ae2b5ca219d1b9691f668f042d570b60426ad9d0dbde94470afbc6ac32551fc72391228f7392ef38809ba6b6bdedee15e82fb926bebb99210c3ae05abc3b5cc9c7b2ce61ce8a63ce2514cdfdaa6d132633c29a16034728461393ae693590c6756c9a2908331719c75f0596cd7e4bdd4d697095709a152a4c5b26b2af94adf485c68c1af882a7c9aa9a282366b0d95054998aef80d7cefe1fe983c7ce088cd2651e14a58b01ff78ba4af6daf4654b7b46c77233a7adcf2ae61c534fb7d4209423c60c1320e4cc7bb83289141b12551f55b1a4ea25c47961d879a2270a504fe9d811703d7ebc6cfb5c1db222f36e0ea590dfcfb2a587beed5e144c012783dbd5a36edd5d1cb67f58e7c83c9849cb4a4412434626ff004159e041322e82cc6ec2e7303046c57945c05498187fb417ec99ed02973c000bf573c696ab64ca319f5c92f6581d0608e0d0bd6c6860e7304ca05a25991b0c4154b705546418ce78ce57a4cf214b1c00f44cdde97215740b41519105360e4803d1444c93ccac0e5fc1801e37df2df4b9a889780389628c74b7639b51386ef13d5f8007485cf7840f3049f0fcac812fec949b048c7489de0840d0f7b5f02cf3f778c5adcdf1889bf2185b82ed259b9027a7c4e6113dcf57e7e04fffbe3c6df3dfc3ddf1d358de0d7ef47f08f77fec7e1c5eec8dfbd37baf903fe9dc0be73ef6060b9abc6b68b0681f3c29a4a0a1037681a1de135a1e6ce23de0a3a5c059ff640c858d5c3fddd073fee3efc49cf65c18f3327bb22eebec492c7722f7b84e3dc65a7c47604f8c16db3bcdb0df29ad82e13707ba5b95daa98fdb8b7254dac071d5c0d6c4b82f23c50af1b7e5ca7bc7fdbe9a9ba951acf87c265f432c816a8657001e2b28cf1372c6e5a79ab9c3e2276da6e2bc322298a6965986b4c27de6d34723ad597220c8f8b5f539e10e1bc8b55ed672ce1e4651a84b6040afe0603427c8ecbee92b7c10471f2dbe403fb6d7246ed96e2c8096db325b4c1da43677ab678fd8895c24e7c1b4e774ce6cfb8c57c447edcffaf9f0eb5ba28035738cdd01a3df8e9e15f7fd4dc6186d1ff350e1639f901f0735cf79b76a311f3df2d158e6b7dfc212bc373343b132815fc6ea4fd3036c471a45dc4f2e7b33adcdcfa6a0fea4b3de8a71f56c1b2cfa2b0fd42a2355c3f10ef3454183e4f509d3ab3b6e83a044f7e02064c9deaad2b4f45c413a430438aa9f9ba7fc938f582319956e448c9fb014ba3e7f75b9bf47d0c0a0b80a91540bf2221d03c16e023db8cc95a4e1489664f4433c1821d092b8f7e31bcc639ae8697b76b30f3a64feaa6c01d89372c58e0e04de0370b15bbb60a6e7bef0d8227320293067409e20a2fbb5e231d22c49628867210b43acfaaa08d2043084b750bc6ec9039df2bb9c7588aa368091f2bb9b398c7495698c43192471d735f4862a94a45ac52017b66b4c2bc6e90e431494a8598305a44458eaf3ecdaa60214e093ef066dcdd15e8f72b5ad81f8e801cc9d76ad9d7385df02fc194753dc2771bea9a83fd2aa6833d13f58d08a011cc4c8e591d1a2753e1b11c65886902b285ad7638d9534e1bfb7373431e8a1d82dd6a280beed20e06634c3a3eabceccae96f8969cc7ebab73c7c704bef075d63084b7aa88e79460b2472ac86194f839be7fea3d90b48fe8fafe91c0204060e49c0be03e8b0adefb98e0c880250a4d75544c590b0244493f611c6b1efd1407c0c324801681fad46735df029bf42651b22e8b3161f7f32d42c1aaa1d75fa34f34f46a91535a353a577b85b422681a25017bdaae2f3dba5e64854c706bdde9dafcf689f3163fc7f204b4fc831fc9aefcc708ff33c8c99105f4d0d66b3b971e8cd0871b92bf45db90d687a63b10f3aa1f31ad54dca1fb17f6eee5abfc19be235cc903b61976c9d98ae2cbbfff4272c6de3990feb4705307bd8b9c6d43591f92ee2266bd686925e20ebd5f7e0521038f903d1afa3932a6106fbd4de9e83488af824dfe9a3f50fa2de4dbfa30884c7e17c5b365995c9e3cfbc2c4da4895af08b3cb1b41c68c03ce657681d99bf8d707e39f6e270bfd64870177a215450c5884c1f7869d93c624e514a4ec9fce049e6d3a39ff7df2fbef17132b0fd08ce2e3ab2ca323f2d0211c9200580564e0fbfe049d3c8e905b4132f2bd072371803719745b0f60ebe099dfbf27179db7efbb98c8d09c2b5c3bb8e85c75310d92bb2927db7b840cb489977f2f92a15dd484cc4f8b3e526f98b2401dcf6769a34b2683bfdfff880c8fa5ac03d1b9f80f04d4c50558e7f92c2e43a386b9f67a6e82b82f07b88fc457a98b5b092d5d4177053e8ffb980b0ffaf147037c18784092c51e0b6b1c0dd470e4b9c0e983673778f278c25a3ea91e2f6f635399441f4a765c2d31a98d471f4a688c6f562f5e2125d228e228b97cd4e0606c18131aafc624288a0c7cc25991c5ba5062997f0fa42da759ee9749be8ce6d205347c98feb720b6a7bbb017b2fa1c90571f7ece05eab18c433cbd626e0470db0acc2903cefe865051b1d1b8c592700e8da6523c4d3e74af0772d1421c46c0119c87bf7312c4190dc2cd6791c7a2c06efaba698872c2117e51ee182542c66afea86d6e8dacd4567166138ae1a228fc7f79ee9e31955ded026da7806523a3ee67c1f87a745fe0468bce18d14b6557fd71fe74ef7f1fecfde7c5f55f1edcde9b381f3fbdcbc83b47df0bb96b2132ec5f6f81fc1f000000ffff010000fffff551e466aa670000") + bs, _ = hex.DecodeString("1f8b080000096e8800ffec3cfd73dbb692bffbaf40756945d93265277dbd77519c4eeaa4eff9e5cb1327bd9b71dd194a8424d614a9f023b6cef6ff7ebb0048e293a2e224f7deccd3b4b1042c168bc562b1bb58603422c7e96a9d45f34541bce301797870f823f94770994ec82f693627411292b458d08c4cd3a4c8a24959a459ee9367714c58ab9c6434a7d9271afa3b80ed434e493a23c522ca499e96d99442c39012f8394f3fd12ca12199ac012d797df27e3f2fd631257134a509b42b164141a65035a1886a9696d07994403925af4e8e5fbc397b4166514cfd9d9dd1ee9f791c25059964e91574ff98145949878cc8282969f57b159739fecf7f93dd11b49cc7e92488c983c76416c4390005c9bc8c834cfc46a09d7e095f7218efb4e88f77763e0519c9d7c9148695ccc951d5c25fa6611953af5fd7f587e4fc6230660dca2c9e0480e688f481450c4f0de703a1b368eecd4a2888d284780f1645b13acdd24f5148b301b9d921f0510afd90ce82322e72ff3acf667fa70194bd0996ac83ffd93f3e7bf7ebfefbf49226d0d186b6c7697a19d1aaadd2f26ea0935964691cd3cceb9f55a5c74516c34025daf369ba0246b22e2bda9105ab8c7e7a1e14d8cdc1b82e9dd3e2ed4b28c229694a914741567006b399004ad84018762425a1acc31c006eeec65a25b0d32c5fae4f9ee318fb4a6902128948ce2f2c484e121ca8429ea85f6569914ed3f87801f30f725c9329c1d02c83f561e2ce294d5e609d490d08118d4dd233ba4a2d23c5e2ac00a606aeba53607a44afec5c4c280d9f355c6455f8e967cbfe63d27f4e6166e5c230ca4439f1e0fb40ae4531c14ae4975c5ea4e57481151f56214cbea8bad3c938993a88c8e8125485950eb3aa22224caf92380d422b21415ed02cca2f6b52d81fd03167b0c493790e1a679666944cd234ce490ccb034a0a68a34e61513060985c567e138580fb5504c81340044b02c46a9a098e9053212e844390676108329ed31c008bf50a7454bfa0d745ff6e28617b1d5c9fd1247c3959e512beb765314f7165bcc3c5f42a5a46a0ad5f46bf8cf241832c2997139aa9e8de018220394960289f82f84c42c96b485545bccda8c40ab46313955b203c0db200344bfc8e7e2c61edcbe3052e10183328842464c36e405a10422bbe3435ee21b65f61df20bc9633b1857df2e4a6d3207e9624a7b0ae248cac983c8f4030604f5b1351dd425b85e845124c621aba713568501855247f633b97150bafea86e60cf5ec2f7ce39485168b4953ee6cffe134393529e025042b5b1bbf339b3e4bd264bd4ccb1ccc87002788e931be9d5a105da82a6d5e4667f6a529569ccca60f279b96e3b0da89b8cda012af720cb13d2bc13c01a3631ab0bdf083c2bb8d084f833cbf4ab3b01da9042510af9a92566adfbf9257291a677f7ffffef40c6cab8c40471a7fdb903d3b3d7949d7f2a49d9e105e225004abe8127e6b93545b09b0eb9f95d329287e1a7a0349eb4733e27dc76c02b9549ae028890a6f3056abbcfe7f24b4001e5cb26db53fc07d3488bdfe02ec9dbe066d5a1ccd8e5491d0d81f5b92d1346c270261f34559e04ed50e69338554a2ef4ce6fe1a808a3339eb1e56468b324b5cecb04f888beb3793607a1966e90ae400fa03c905b9006998a441160ac3face31296d03ac0d9b198c6381c0b5d129d3c60c4f1f107a95d1bd47faa37c0d0b7d0934e62877792e59db609704fae054011ddb4480630432b0bd44f280db7d9e9d3a81bc9a20b95df3fdede44fd83e7de059eec916e0c087b5fa22982e24e451a8a3c719c36508ae976c559e47e105393a0293135c293a8bc0fbeaeb4df10396d01b0adb0699d162ba40178dd943069c9dcfacab9f91daa33e14d0043dbe0fef4e8ed3e52a4d408f21c15d6741e3b8340c8de93626b2df84a21767626d217e0463cf81a42f3d089c16acf7057ef2d4185755e542f18dd9be35fb5dd350976f9e30eb7ab08f5972023baf6b742e9314fd217445c1f9bbf2064305a2405fce4398fdda651d9011393c38385021a3506c6bd547f270a1bd3a30dc64610e3084e16239dbfb98842c82fced55023ec38a66c59a4d9d638aaa1887652918250518a7762cd82993bf28016b19c87f1d140b7f195c7b0743f257b2cb79c9204e925fd605cddfa7055898fb167fdc8042e615a143226abc69596cea1a9c802e7d2b60ceceef08d853d305f1a88bb506530eba0ec10278e79072fc58631afaa6b26931f05843e775a04728ecdddd8d77f49002380645992b9b2f16eb96866dff41b88d3b103744c03c4d2e13b48d645b40e9c1440d1305be26acb1efb00b27e6b3225dada0672b6616dc2bf81aeea326b574c3ea9bd61538fb7b7e70e117e907e8203b86a981ad7f8f57c0bc4cf222f30e0763751ca2f911e961a0004cb31eb9bd254de94918d39e3e165ebd07d5c4eb355462fce3946653d0f7e03a8989d923bdef073deb68054ba4015966fc3806ffe2eb4f7894ccd2af30db217af9d9179b6c65c6fa11cc8db367b110fb2ea3ba4193f38977625a65d1320037de86c9c23e6d0e55a1f8ea1309fba46bc416f6f2f83bd3d70cff4137acca14aea6058c0a2060b7b0ca092e2cdec5c80620d130d6f9ca76a3599c823d0fddd8d52246902d6a118b8f678aa785d4a29a0738cbe6251af86fe0efc9f30b55e810d066e863b98f565e4c59b7c84460856d53ab55eb8a1429b37ffa1dade55a757241251e2e16bd67d885be1fe8185bb44e9fc5a7d8f06b6d6ce12c4686ff35f89a5e6ecb4fe1ca6ec3b425587bb99b5ba6b2fea76597a91f3bf1cc54861b79e6d68d38568cfd7d638e09c218d344e8b14db7ffdc32df12cbffdf86a02981d67dca3d94dfe8c6694062544a99a049e77b0e32058470af6df47d4df6c411ecbdbfb9fbde30cfb0dd86d8b1c11d75b8388225fa34ec38533edd040c7141e58814d25b759b34ac241ceb58f137708802af1fd3645e2cc0e422878e11d7f641cb400536b0959dc220cea3374a83b056446d4713a5d7735928f53c43f78ec63248cb185589a9ac7ff0697fb21b119824f1c636707d8a132e095a334f9b30892f5d99e295dce5628d06ad2cdac49f2ecce9cc191a468574a663e7cc6804a6da252501c17834da28d0765d5757dc5aaedeae2a0fbbcadb40404f39f5f70590ec74eb08fcfaec0ac345b6ea67b0bdadc0b4214fc1aab561c2c39d563200406e884706820f5d63edece4c6ced63cf804bb87c894a8986a48db141715f9c7d9db377ece8ec7a39946a54421364857053bc75fb09494fc31b9e9432f0528c0fdf72093ec007eb58ac569d6e8cf3c4dfa77777a746395e646ac0ffa82b1014143d6892dd26144395c7143c03562c902db8444ed99212c60279769868925c46f9989ce029e9719fc932e294f3f9af2f41375ceea9494ef2ae1a21fcb20ceed523e34857b406e6f8de1f34f3b4a90d8a12ae19a5e12b4e9ecc5d4b3059d5e224cc98e7db3ead8972c024cc9a009a162bda51909a39c7d37ecd0b675fac30fd6655cafd32716f7539a267b2366e61e586d5816cfbd0f41a838b62667ff50b78c9d9cae7298c85514c704d370d0319cd05a824008c591a1c66873da7d7ec0cca2318a00880af740cc4c2af564d63e8867a044d640f315a95371624c9e88d73b963e548977685d49fec72d48dc6adb907827053ecf3b10b6bf62739a506745e6e7a0320baf3f44c51fac2465752d5972d73ea8e82598024cd3d8a86894be72f4b8d3bec9f4f05cbae78a0933f970ea2de5f85a9d58c779f9b6c7c78e1d4360ee9b5baf35b54f56b46757111e0d5cd1c90a5551bd4c60c924ec4478c75c099a146be2cef31f051ab0c690e4be36452dabca5c3c3a32c38d5671c394be8f96342d0bafd9eacd1579059e467ae5e34242105feaa5faaa753364eacf216dad39929c4af6afb61d8ab488cf1229bb2c4899161d8c065b6ac6b64279d751ec2ce3477bd7eaee69ee4f85b6cc32b0ae448b073ebd0695117a37303555238314ec02d8f7e21af48b958d0ad8198d67fc4c54f3bc15c7db1c6f43985fe757812e2347b5735417fb7fa65102ea8d58962b02bf004ad2cc7f00727c9a31a2158f07a7ac62db17308d23d3dd56180fbd45e14729cd285fa4577d3bae20dc80cc368b3732bf309f759d044ba0ffaec34c6a4bcc3a9576986fcfe890c6b4a01b996df468244ac9a696c692cd994e363e740ba158fddf6a7d489690bc14786d074dc120753a2445cb320a7063e159054a9e90fdb497d5b1a36a0db556d161b0f718b03e6873024c2bc5e231b54a4d152839466f314c133a24d178674ba1aa834a96511950b556d4233fc0db3898526f4446404b1fbaab4bf6ab92227d955e5527c6951167f4502b059b06bdaf91884c323503cf5a61290d24224fd425c1838150b1b7673b129061cfa30b3960abb2a8c5e7aa1a37231e1ba08272d369c0cf24a3c16587a3029e7b03981c4b878f7855e60bcfdc551d2ac4cfc183f5aa287d90d1cf5de95d9705bb9b55e172d94e723cdca9da3a6b36dcfa370638aa2065eb728dc05ed1b5db67485ea2b11184676c8866d2f1f4401ebc5b882c23063fb47d1e2a0525cd7a337bfae2178267847c253029b09adbad4accf4417fb2a368f0cc2053365c615feaa383d1e434d6779c364ac734a641f6a24a446ab592e48b530a9de72ad5e298629f1c5e30b2363a08acdd8851e2b0e36659046675bc36a7352f3299d4665bde5e789b886bab08e7cc88867febad645a2bd3a17e8a827583414b301ef0383cfb55da45602a383553d9e9dae07d8eed5c1bd14289b8385d1ba90d884bcc920baa39bb31ed67195cd83e4686b561fc6ceaeabcd22f17e63503cd68b5a012078568cd3701423b808f2175a6c47a798467b046a2988d54068977a0a49e6cfba7b3ed4b4a57d062af9d36bcd2b5ccfd4b00b649df26ecedf5b7b7e42f9febd6a28076f473b0db2fe0e780136888fd4679bf5164ea3148efe7bb7fdf7ec868acb48e995ff35da51b4c74851cc3441708ace26240d566d6f98559a72e60c92630d770ade1f969393a5f562c36a3d8de5d52698b23de97dd0e9106c10d821bae651e0b425a72e91b339f79dc767ab5c4d41ac6a22f4c2380c32a1ac51c43bf3a02e438fb4313826b0d80315bb37a5427880133d0540a512f9808b5c46b95433a372c23edd0e0a5aae16c094c5a3b1b7ad389e05e79d5864ba2f8e5722450ee735ce6111374d7d6bcbdc3bd00f725d7d6732521865d0b56876b9939f758cc39d04d79c4a398beb54da365c6785242c160e008c37274cc27b318ceac9245217b43e238ebe0b3d8aec93ba9ad2f13ae1242a5488b65d754f295be91b8d0825f1155f8345545056dd61a2a0b92305df21bf8dea3832179f4d0119b4da2c295b0603fee17495fdb5e8da86e69d9ee466ce871cbbb8615d3ecf7092508f18005cb38301def0d44890c8a2d89aadfd27012e53ab2dc70a82902574ae0df19783170bd69fc5c1bbc2df26203ae9ed5c0bfaf8395e75e1d4e042c81d7d3ab65d35e1dbd7c56efc837188dc8494b1a444223f6064f50e1493021824e63ccee323740c07645c955901478b81fe497ec099d32070cf07bc993a6a68b349a529ffc5216080de658bf606d6ce83047a09c239a25094b5cb104576514c478ce58ae86244f110bfc40d4ec7d197205445b91013105460ed8434194cca20c5cce4f1130de27ffbda089780988638972bc6497533b61f83e518d0f4097f88c07344ff0f9a08c2ce09f9c04f37488d4094ed8f0b0f725f0fc73c7a8c5fd8d91f81b5288eb229d964ba0c7e71436c15defe7c7f0df1fb7feeef8f77c77d034825fbf1fc13fdef91fe38bdd81bffb6070fb07fc3b827de7c161cf72578d6d170d02e78535951420aed7343ac26b42cd9d47bc15345e06d7fb2064acead1c1eec31f771ffda4e7b2e0c799935d11b727b1e489dccb3ee13877d929b11d017e70db2cef7783bc26769309b8bdd2dc2e55cc7edcdb9226d6810eae06b62541791ea8d30d3fae533ebcdbe8a9ba951acf87c265f42ac8e6a8657001e2b28cf1372c6e5a79ab9c3e2276dacd5686455214d3ca30d7984ebcdf68e474aa2f45181e17bfa13c21c27917abdacf58c2c9ab34086d0914fc0d0684f81c97dd256fbd11e2e4b7c97bf6dbe48cda2dc59113da664b6883b587cef46cf1fa112b859df8369cee98cc9e738bf988fc78f05f3f8db5ba28035738cdd01a3dfce9d15f7fd4dc6186d1ff350ee639f901f0735c7b4dbbc180f9ef960ac7b53efe9095e1399a9d09940a7e37d26e181be238d24dc4f2e7b336b8b9f5d51ed4977ad04f3fac82659f4561fb85446bb8be27de69a8307c9ea03a75666dd16d103cf909183075aab7ae3c15114f90c20c29a6e6ebfe25e3d40b866452912325ef072c8d9edf6f6dd2f731282c00265600fd8a8440f344800f6c33266b3951249a3d15cd040b7624ac3cfac5f01ae7b81a5edeaec1cc9b3ead9b027724deb06081833781df2c54ecda2ab8edbd37089eca084c1ad025882bbcec7a8d7488105ba218ca41d0f23cab8236820c212cd52d18b343e67c2fe51e63298ea2257c2ce5ce621e27596212c7401e75cc7d2189a52a15b14a05ec99d112f3ba41928724291562c2681e1539befa34ad82853825f8c09b717757a03fa868617f380272245fab655fe374cebf0413d6f500df6da86b0e0faa980ef64cd4372280463033396675689c4c85c7729421a609c816b6dae1644f386deccfed2d7924760876aba12cb84bdbeb0d31e9f8ac3a33bb5ae05b721eafafce1d9f10f8c2d759c310deaa229e5382c91ea9208751e2e7f8fea9f750d23ea2ebbd23814180c0c83917c07d1615bcf721c191014b149aeaa898b2160488927ec238d63cfa290e80fb49002d02f5a9cf6abe0536e94da264551643c2eee75b84825543afbf46d734f46a91535a353a577b85b422681225017bdaae2b3dba5e64854c706bdde9dafc0e88f3163fc7f214b4fcc31fc9aefcc708ff33c8d19105746cebb59d4b0f07e8c3f5c9dfa26d48eb42d33d8879dd8d98562aeed1fd4b7bf7f255fe0cdf11aee401dbf437c9d992e2cbbfff4472c6de3990feb4705307bd8f9c6d43591792ee23669d686925e21ebd5f7e0521038f903d1afa3932a6106fbd4de9e83488af8275fe863f50fa2de4dbfa30884c7e27369d028bbf30b93662e54bc2ecfa469031f340f099a55a8ece7f1ffdfefbc5483fdae1b06043b02fb55970441e3938238ddeca9d9eeffb23f47038426e02c8c8f71f0ec4e9d5a8b779eb848d1e0fbcfe35b9e8bc7abe89890ccdb9c2b5c38b4dbcc29cb0e47e2bd3f6181f036d82c5df8b4c601735217352a24fd4eba72c4ac59339dae892c9e08fd73f26fd63e9c85d742e5ec7af8b0b304df3695c86460df36bf5837971590c701f89af521777125aba84ee0a7c1bf609171e74628f7af82a6e8f24f37de6d31ff5d458dcb9c0e9835bd37bfa64c45a3ead5eee6e635399441f4b76562b31a98d471f4b688c0f36cf5f2325d228e228b97cdce0606c18121a2f8724288a0c1ca26991c5ba506299ff00a42da759ee9749be8866d2ed2b7c95fdb720b6e77ab0e7a1ba9c0e571f7ec803eab18c433cba61363470db0acc2903cefe865051b1d6b8c53250c646532998249f38d703b968210ec3bf08ce63bf3909e28c06e1fab3c8632150377d9b698872c2117e51ee182542c66afea86dee8c94cc567166138ab19228fcb73c6f9e31955ded026da780a5e2a2ee6791e87a745fe03a87ce18d18bcf5e45f1467f9c07fbfffb70ff3f2f6efef2f0eec1c8f9f2e77d46be71f49d90bb1622c3fef516c8ff010000ffff010000ffffb3da8f3aa7660000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["app.js"] = bs @@ -63,7 +63,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["favicon.png"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ffec3d6977dc3872dffd2be0dec958de886cf918ef46963a9125cfae32e3e35976369b79933c3489eec68824680294dc2b6b7f7baa0abc8f6eaa25d9da9df960ab490085425da82a1cdcbb7ff4e6f0fd5fdfbe640b1306937b7bf71de7dea18a97899c2f0cdb3a7cc81eef3c7acafe939faa297ba19239e391cf94598884792a32899ca64625da650741c0a8956689d0223913be7bef83164ccd985948cdb44a134f402b5f30789cab339144c267d325c064af8edf3bda2c03c102e98908da990537cc83a2a9b8375329742b237829d88fc7872f5f9fbc64331908f79ee300da883d0b7834df1f8968c4a2b9c3e3787fa49791077d47737a45f8aa2010c9fee8242f39344930625ec0b5de1f61a540f1d3118214dc9fdc636c2f1486336fc1132dccfe283533e78fa3b260614cec888fa93cdb1ffdb7f3e1c0395461cc8d9c066244141211b43a7eb92ffcb9a8b48b7828f64767529cc72a3195aae7d2378b7d5f9c01191c7ad886814b2379e0688f0762ff91bbd302e40bed25323652451558ad6a3c350b95b46a04323a05ae05403128365e6a98f410d22211b3fdd18c9fe1a31b032127f7b081912610938288ec33bbb84026bf06e6be869eb61e5e5eee8d6dada2030b6caa94d126e1f1d8d37a5c3cb9a18c5c7833caf04051d00b218cc5d08a8659c63008233e196c4c258c4d95bf6417f493b198fb3e20e44c95312adc657fd8893f3dcfca66306867c643192c77d9e8cf223813467a9cbd16a9186db3e2c5363b4880dadb4cf3483b20ca7266415cdea33f69f0af8bef8a1e439ecc65e41815efb247ee7722acd5751159275491d23107e9bfe8c2e5958802b5cd5ea9887bf0f750455a81406eb3d121e88c04557b2dce01c3024ca30b0ec286d2eda3def81343a26b92098a4ebb14e9d55f3a0386f4971690fd95903b4b0bc87e4185a94a7c9158da452a6a8c2b5073d520f32edb795ee774e50d8171be2b191e2b2d51237651a64027cf9a1d486d9c4839d3148c8229baa2d724700e0a9c45add1100616a46154b4f1a58e030ebc9411c8ba70a681f24e733c40b4ad26833ce6f251080ed94c109db260cabdd37982160f7b5109a03f9f6e3d7ef26c9b3d7eba83ff3d7af8bc4ec184fb32d5bbec4965f0397d1ec59fd8d3f27d4ec8c7f0fa71fefab2392e10b3c8f539188e869c076206d8ee94825e1bdea39df235493e0fe41ce98f837cbe9e5639817343dca62ff282dd97215a4d1e990e4d8039a26876be904638a433d8f41c0c4dcd1a9c0b4bfda73b3b9d90fc9661b1e37f0c66651d16beab431e044e8d8abd08658dff2314bee46c2be49f329afee1d91fe24f0f0b00995ec1141b839900919ed83755ed2b2a3336fe3dc83e4cb4867156d85a06339941b32ddcb9bb5bd465bf07a2246065a630b1b2780194d6a0540c06a1ce19b26a9a087eaa711e0e14344f449c28681380088e35cc90309b9f4bb3a842b4f204fec1efc7c5eb0611122053ce95cb421cf7c6a48230138fed547c6f8f4648f30fb829ecbd8a415512860e00be8bf859318ff3332cb17fd0bce43f7d31e36900d32df80182eac939a749d3ce257bbe2c80e01cc961d0495686b3106845bd0f679a8043349aecc9709e97a0dd1a319d78388d39f8e43c7afc479a3d999ddd474f1ec3dc4ab2677f8f27ac984cf7486c72600be98301753ec16c57efdf4e2ca911d0fb672016144d3a67610237c96ae4234983ca3872ea547e92c21603a7193c6fe0272af6d579542985729e4defbf1b35eb01fde773f4865011b2872a9497be34df46531d3fdf9b16b4075982eef7c653f8c76b1d9598171d84224a6bd810be930227ebfd815f790a1e90efbf13b1da7a08d0abe49c07cb78815e0e2b7e390b1f396b09f7ad0801c303df67d81ce615952c11b5bd31f434b86be4cca0ae61f8e7c292a0d13d82e8edb8208d3c933e4aee15d003c3634eac61d08370f4d4bc815fdefc8a8491c3e9f231c1f8a1d9ed02ecd3f1d1ad50452f52834236083b359bb550b3cdaf481130ef862766a0a8cca0faa2d1f13b0ba1abdfbd711a5434bb525a96ec8d8158e4798fc1225803db631b6d7db4c8dfcb441b30ade7db4c45c1924130711e31396391f084d63c593e67195eec9c27114e4199f5cec003052490e23ec09fc9f9718446b1302800b834c535640227f4c1c656ed55a51c88220246ff3b59b775dbd5acebe08443b5f6164fea2514d28c26f9285e0be10b1f66a827938262fd6071fe6adaa978f27e8141318e374d682e620bae21e815e085f1330c90211c8b14cce01eb8b01c0cbe5b4e162c4c91e4193a3859679528528ec4791db4bb378eeba2b0166974db2b7360560d703280a90dc8ec43c1a7a98198dd448e0ee94f36e7b218bcec6c5eb91d49b7683410adca49207832939f461dbcaabfa83d561eb29f2d8917f334007f0304b421cf35c9cde161c37212a170836da19382bee5c31242a7943f5b25e40e860df188491f099bf520855e2dee996a5c5c6093432cd8c25feef111f80fc42b7814dc5898e8fde1df1f016de45a2fb91baa54ab07357b34ab518d3c8b9ae300740878ac737f22064f011319bfab8d377746f0250cec1b0951e827184c1b7cc3ab5be706304b25f74826c223f67d66943379cbcde2f2721df8520758c5b1b3304f0c376995f47597ad2aacbc454db440754e34453c634e2e1a55aad41991139815946ef536c4b2352b36e3966e5ed8d8a9da84351209d93304323246dfb7030ac2b1b142571996267d4558b8586f890c6fba3e1585464f0480ace8c1af39f199af9d49d6f111a5cefc5ee4c7fdd85f7f60369873542ca2c600bfa792eb8cacd0996b0d30f71142f003839f3292fdeccae88c07b24f1e068f3ff30f1c2de74d02bc4c12b5e9f8bb91fd9a8cf65408615333c040bf6291a848fe8ddc856b70bbc39a7dada1ce03356d860c7f82773ca8c57237c1d83981fd5e0642c3c4c08373bed4afd3702a92cb4b260d74bdcd7a1abd581a6a349511471579f1f508b65061935e3f2aef16c81520d42b528bdadc216279814a7d0763bc40f166bee00db8ee6a46eefa4614ebafcf7bec60041189a5e884edd40359758ee14aa192b993d4c19712c860b660930eaeb43c95ca1088b2eb46b1bf0fc398ece4fdeeb0177d6e5101f7ab8902e6b11b02f08a6b889eaeab38bdf51b5424fabd038ffb0d84dfa3c95f855e47ac0680fb0d08afd5dda5760a5395f04e4553e78ee7914a047b2b92506a0db398be7d9adb3eb14bbd39d96b40ee32e529d5eff0a0e53dd82580bf48b3d8707e20c89a12b448928ddd0628e90b00a088d64b3ac2aa56c444afbbe3b7eef085f7e440e244866012473d69d772c003b220e09a7b32683aa700a5c7daf622e5f3682e9251a7f160df7ecb86ce2cb8912391bee89859d62b71dc356b6600d9e10251d49d03eb568faeb07783644f355583b9e94a9286b87f63599a08800f4bcf144b57957c0cb63e9ccd3125f353bef0b3f5f0e7bb9394b1c3cbb331f8e4209e43d65eaa3e28b81b51bea6958d994cc335f320253e6b322040e0df9220ed24482b137bf08a7d3032b85ef4a897e032852efcb91b0ebee1fa5437467af8f6c3cd8dd48b5398f43dd09886bb0d8f1144d1090f761f5d5efecb1d8d718eb2d7ec1d3762434a402791f09094faa70746191e3cc02cc9344619080588b97779894f5b3d758f230a3edee363556c1e7e6da2754e701fe25b21984acd708a41687af324cb7d894cb201e5832852e0a98b373fb0fbfb2c05fb359351afc91a4c5cdcb2b55049334797f7c64e7003ea66d9bab54e7bbe9a8cfb7aa8a14e3d5cdf1cad1afd68f286763b0d8d0c5a9d34bcb5fb3d9dcc66437af99ad6b49939ff2f91e8cd6de8996d7d9703854da302f2e67277e7ba81c1b5dde5412b88842dad20966e5a9fc74a9bc8718cfa2e2d237678acd75d3f6ceee5e980d1e9df6eba988810b2f47bd559bea5c5c426897e5b4cac150e491bcaa899363cf07d18dc66892b2b0008a1c6fe7fc685a61e49ff55b8c9d9805db4a0764d71adefd76cf26b769a9bb418e23b37dbdc8a0b7dc7fc2b1c3340b8b67efde668dd445eb2fc697393e3fa96337c45db1474f7be4a816576e3961b88686e1636977bf73658be56467ae25a1b2bab6e270c1c5dcecaf891cdb431fee202debaef65284083c11714bba33fef86e12ec4739797bbf9f679f0d0668914911f2cadcb8a8d88d4a411b7bfa9b2e2ec75efafac0a35ed74b482306cdf72d3ff78f3c397dd4e5993e962737171b4035fbfa0137dc34e7ce45b4eb3474052f8d991c0aaab7caf2de8ad231f7d0725ea67232aa627ab424e5d666ff08ce8ee1886a83d95265ab8c5b9543712663c9a9ca4319e246263f6bd4ad2b0bd697b50171afa984bb348a72e385d638f07e1625c74354e04304ce36ac38f20e5dab077f6c586bdad189007f0e72a598e7de5a5e8fc65476c8eaa8fb73348a9758a437c91769d42b8811e8059f61cf361c7398cd5fbe76931096241959c5a4b842b6ce038e4e25c4454b60ee96fa1ffb6ea8cfba2436ea9d0f1250fd4bcd382db0ad9a9df1e2b6eeba06506cde3019d19c3fff3cc534dc1174febcdbac2eeb566477c82c290a4a1bee1ae06e6b0f0c0ecdc56b3338ba73d66a66b6cad69622fae77556eabd702ec20eea79f0a860efd36037661c282ceae7316270a5c96908ebbb12548043b06da26c03a567a8c6e0df83b702c9700fbdb850802599c43b498c783ad65cd6cdaade4b4ff9d0e0374ca535254b80bd224a399ba05596a9e0d68903ea7c017111ea959497397bd2533cb162af06f9af7f9f1a21ecee78797ee02dff324f9cdb3be7aeaaace919c3a782743208cf822dc2f4fef40f7643cdc9b62f7f1510fa3a5ff7183a9c20273829be1f62d70b671daafd698b6651c832b67e44c7af6e0d4b7a1cff5e279359d5a6e8ba86754afcdff4a9573506a86ffa14bdeb8f0811e716d17097471112e81879f99b748a353bbd3bfe93ae341e27c5324542eb86a61d8c3f20c2a39e0a184d38843206acf1a7f4cc66df8a3f11506d911950c3ae8554420944a07af1037de6540071deb0ad55973c3f161a0d0356d062157d51892128cf17bbdad3c0170e7d4074f212cd4f9fee83ea20896e5e527885c5bb378ae59e5d9e0aa6037400d8284998c0e501be8c84c256176d41e7f8eb25b6850395f124f56acfb60837ca71445b6f4f6e201985787bce7077877400ec88d2827e77e931dadc0fd6b1da5be4cccb2bd8cb317f0a908f0f6038b1ca8ddc4da972370efb1acd54246311ecbcc16635b742d078a2a9caf9354d4998697dd48d4b019a3ca3d373476da880716204d70910a11cb52907859cec754e2a64b1ab5839d481f3489d0eb5c6ac953413d92b0c296510ab585c34a6b46bdc6396c707e626bc0ba9677aa1b623b5867d9faf973175be384460118e261dac8720ebd77b2998cdc767b9314abdd243522519fb0ec00391830abd8ca16db6bae109ccb4e90029a6ebfc239067f025045372e81b9d892b479d77fe8762538f3a1f5e8f3e42f0b7017ec0523105be0a95dec749b9d0a1123aea1846ee93eac3d114ee836ad88f4139eec09601819b40739a8a1ae7113a551caed5ffdeb79bf8619a4806e217c2b94adce1260029e64066ca7018f4efbf11ad43f89c46b12f9f528902800ae392abe129a8e55074a9d5af571d9b1c1d39c69e01341d9778fe9e22feea114e12221f0c7c31547e21b9eec00af12caac5c44b4694b6fdba050b704662aa85126323d636fe412bb33c7dd6672a555e3a1c86c1aba45abad5a1c00ea0b3aefb73ffab30af3ed34992143505d666c90dde2765b67b77deab41427f61681481b982791e69965ce95d80b526df9830b81cd13ee3745406e576431b77490ffbc0215fd25904d7a4416709070bdc3efb2e878dec5ba7d48eab2d34de95de07a62922bd2fd25d94e5cbee54c8b18d4c080fa74dc4153ce0f32dec53c66be6bb7401f93272b1b66e4c91b82e58e4542de034f8dc21491c728db0852b8b4370a8a1cfc008eef8d11d617f0848b85a6cab92b7e26b2eb4deaccaf98ab6f8ad3ab574fd59f00fcae64fd5df3dc4b8cba3d10b4e0f9cc782282d9680de69494b4974ae0bd095592831640b03ff84e19985dd3e606df230271fdf8034f76ac8f3fb0d63f43fc513de6762351481fc09b8b45f0cccada58a4ba30da55deb540da556f484c5322e4e2cf464cd351da13d350df9509cc56c7ab6b6a171574ce60d4368b700a2ae1a4549fd3b2e51cdd306c2df6e6774ef4843f6be6b177f6545125d64923f931c5ed5fa05b503de6e88345fba3f1fffec49dbf1d38ffb3e3fc9bf37feecf178fb69f3dbdfc66dc3bf1d13887852754b579eeafc1892244e9282b439413bc2e046862335760c3667669811537982c5df62a73eaf1bd0616a05f6f6f85b3ce0e6d53ec759f87206b5d684b4ab23fe4252755e928420b5beb06baab460c7da2dc85c8d0b861382699d05c159122d8b2b7be54d9b8f5ec69192d90cb138077f2b03b60d8cea3050a1090cbf86f0fd39d1310ddbd31fdaac18b82e58a71b79c20fbb27b03cdf54c125e75b3ca28d9f22b98256c50334cf8e22aa68900d48dd3dfc747a579ca0d90adb7a1092aae2f292dd117b02b96987d96c59696b6059fd17bae9b933c29405727a0470f288bc4657f9160514098bd44906f2f674c9a32301668c05d864a602450b5120b5b41fd7b21a636b792e28e5aae73e5c06ba4d1b6d9cac571278285b9227abdb12adba1af352b15616ce9738cd41a6e5a86abd890dd5e1b7918cf863b189d23a842c4c3f353f5a94f267bb58f4aad065aa52920f5284e799d40f5da337b334277df2b54bfdba2f5a99b3daf8c99ba388188ce43299f252a4451c693c52c04d717d5a34cf58169c60bf79a15f2c49bcdb95112e93c531f8d27f70aa5d3268f4fb3b9ba1d9fae18c89d6766ed9202d6be6be10b3014730359776c2a8de56f967f65e7984ac5941eceb7687d3246ba8cbe4510b1ef0fded39703ac35eac81eacc0ea2adc692c296874fc1051bc1d8152587de9a36647358e54cfd1ac394453c7e44698af212a461da20e693b74be177ad43aa3dd7d80e52665e184b0b1175d2242a883748184fdde44c5d06342f60a8cee7ef94f628db5c4bd10a847d9c6705a8160a458e59b2f66973181e423e770eb2de670c31857b32c2274d538cf1c08579bfc75ee77d8dbeaacce03bbd1fff3d1f1b60920fab448b933f1863c647445d6d273882b6d1bfe2044dce74c576b0c74a7cb26a309fe9fb374a5b1a9bad31500760b51e57985e36ca399d532678114be5a2821647f74dbb17995865d5e74b5bcb18c68cfe0c3640e52564a24882a2ecb6de32c643f41b389075bed76a50fdb92812672056265689a156264791381731b5760dd7a34ffaa528b13ad6272c3701b1c384791c871ded0d7dec0d96e2e00946be17d4bb23578edf5d97272e95aa5c529a99667c32d81141da19b282b4188a4696b2ec8fd9cd241d1284bee60e0b5241be9e1f641f032f16b266782f84a840db9f11610a6414c162c8bd69826b210eef552e44b2f806457eed7f38415f1f96d0104278d6b2e780cfeaec16d2e78e49f00e8db919a15dfb9d58eae2588f2730637bcf6f025f300d5a821ffee0b18ab8211bd9e697e9985ade8a270e2eaf403cc913dc059b45562e79d072b7d58eba65c5ce48df1165cba0b277ba60f9961766885c79b392c284f0d40ab5c94b22a3e67f71264ce8a09e337b13de459c2fb79b56fd2ebf1f6c56e9dd49c2a15aca6589fd7dfa2d92abaf4c7061b0c7d9348e04b055a3de23e4fe5c9ed497c67590ca89dabc4ff07d5873f7d38fe35eac25586bd6144bc4ad4782c4fc5720d11daa3dd2abe22b3e6689e199fed387f741e3d75a02707bad2e3274fbe1b4d3e683ea7e36c0f574bdab05def28c06d5aa2968c9c51cf36d1ac83ab7f58a4e66b0a73f0f6f807b1dcb2dd8337f42711898477b9366b79b5716eff6bef37bad2679cfe611deaabbaa624e3147a25a6c73d4dbffa299a8dcf4c1dd057fa0e22152d4395ea6cb4ef68b4200bff7e3d27b67adcc9eea6f592658c39b6b44a55fccc2fae86f85c064bda4e5b0d7c13ee9dd2864260791c70834aa2b7a92dd3f26fd96e591ec745520340cc8ac5cc04bba30bd140cd11b04dedfb78feb2588c891315125eb4099712c1199bf89ccbca51acfa41acc6f0f87c9e88392d8ae2b6520cd0bc6c1d299d829e41b8cdcf608c74a7106ecbe68d03c48866eb98f78a4234bd7db85d49edca948625d9db44e04786eb9f5caa14b07de04b8a4bc7d973556e3a742e061a5452446507e57d0af6fd1132ea33fb45dbcbd9f2af1042fb2f6017ab37f2e55fdf8337b1f9f06e339b48777a6f6a12cb9bfb8a78ddc35bfa0622d36913f17ef06b1f8ea2af97d9d5b0deb3e858e54e19c5c107882b1f5f004b0454bb761cdf7ba158768158f37ab146eba41a9ccc302cc9c98b1a154978cb411ff1e5011d20df9ab56ec62befe929bf2e3b40862e2e10ea31ee45fa89ff8c2146e56ae5a23f5bd6bee0077b25a2a2333da34dfc364ab1bff1ea230e769f5e77b7aeeefbb6b798b4d1cfcccacc3d81b9802ea6a11ee8a976133021deeca779f550eb56a15fd5f147fc69bf086f0f878209c24fd4d157d67fb1b93f2a9d342bfef23115c9d279eceeb84fd6d72e3fe6fe4bf35bee2bdbc11cdfa80063a5eba140474d08d1c7ff030000ffff010000ffffa2543bf29a800000") + bs, _ = hex.DecodeString("1f8b080000096e8800ffec3d6b77dbb692dff32b10dd6ee2dc3529e7d1dcbb8eac5dc74e5b6f9bc78993ed767bba7b20129250930443807654c7f7b7efcc806f91122ddb896fdb0f894502180ce68599c183a3bb87af0fdefdf4e6059b9b3018df19dd759c3b072a5e247236376cebe0017bb4f3f009fb4f7ea226ecb94a668c473e53662e12e6a9c82472921a956897ed0701a3569a25428be454f8ee9df75a303565662e35d32a4d3c01ad7cc1e071a64e4512099f4d160093bd3c7ae768b308040ba427226867e6dc300f8a26e2ce54a5d0ad8ce0a5603f1c1dbc7875fc824d6520dc3b8e036823f62ce0d16c6f20a2018b660e8fe3bd815e441ef41dcde815e1ab8240247b83e3bce4c024c1807901d77a6f809502c54f060852707f7c87b151280c67de9c275a98bd416aa6cedf0765c1dc98d8111f5279ba37f86fe7fdbe73a0c2981b3909c4802824226875f4624ff833516917f150ec0d4ea5388b55622a55cfa46fe67bbe38053238f4b00d039746f2c0d11e0fc4de43776709902fb497c8d8481555602d55e3a999ab64a94620a313e05a00148362e3a586490f21cd1331dd1b4cf9293eba3110727c071b186902312e88c83eb1f37364f22b60ee2be869ebc1c5c568686b151d586013a58c36098f879ed6c3e2c90d65e4c29b4186078a829e0b612c865634cc22864118f1d160632a616ca2fc053ba79f8cc5dcf7012167a28c51e12efbdb4efcf159563685413b531eca60b1cb06df89e05418e971f64aa462b0cd8a17db6c3f016a6f33cd23ed8028cba905717187fea4c1bfcebf2e7a0c793293916354bccb1eba5f8bb056d745649d50454ac71ca4ffbc0d9797220ad4367ba922eec1df0315690502b9cd0607a0331254ed9538030c0b308d2e38081b4ab78f7ae38f0d89ae49c6283acba548afeed22930a4bbb480ecaf84dc5a5a40f60b2a4c54e28bc4d22e5251635c819aa9069977d9ceb33aa72b6f088cf375c9f05869891ab18b32053a79daec406ae344ca99a460144cd115bd26817350e02c6a8d8630b0200da3a28d2f751c70e0a58c40d6853309947792e301a26d3519e431978f4270c86682e8940513ee9dcc12b478d88b4a00fdd964ebd1e3a7dbecd1931dfcefe18367750a26dc97a9de658f2b83cfe9f330fec89e94ef73423e82d78ff2d717cd71819845aecfc17034e43c1053c076a714f4daf01eee94af49f2792067487f1ce4b3f5b4ca099c1be265fa222fd85d19a2d5e49169d10498238a6667736984433a834dcfc0d0d4acc199b0d47fb2b3d30ac95f322c76fc8fc0acacc3c27775c883c0a951b113a1acf17f84c2979c6d85fc6346d3bf3dfd5bfcf1410120d32b986263301320d263fba6aa7d4565c6867f05d98789d630ce0a5bcb60263368b6853b73778bbaecaf409404accc04265616cf81d21a948ac120d41943564d12c14f34cec38182e6898813056d0210c1a186191266f33369e65588569ec03ff8ebb078dd20420264cab9725188e368482a0833f1d04ec5774634429a7fc04d61ef540caa92307400f05dc44f8b799c9f6289fd83e625ffe98b294f03986ec10f10544fce384d9a762e19f9b2008273248741275919ce42a015f53e9c49020ed1603c92e12c2f41bb35603af1701a73f0c979f8e8ef347b323bbb0f1e3f82b99564cffe1e8e5931998e486c726073e98301753ec26c57efdf4e2ca911d0fb272016148d5b67610237ce6ae4234983ca3872ea547e92c21603a7193c6fe0272af6d559542985729e4def7f1934eb01fd6733f4865011b2872a9417be34f7a2898e9f8d2605ed4196a0fbd17002ff78ada312f3a2835044690d1bc2775ce064bd3ff02b4fc003f2fdb722565b0f007a959cb36011cfd1cb61c52f67ee23672de1ee891030dcf77d86cd615e51c902511b0da1a7de5d23677a750dc33f1396048dee114467c70569e4a9f451722f811e181e736c0d83ee85a3a7660dfcf2e697248cec4f970f09c60fcd6ee7609f8e0e6f842a7a9e1a14b25ed8a9e9740935dbfc921401f36e78627a8aca14aacf1b1dbfb510dafa1d0dd3a0a2d995d2b26434046291e73d048b600d6c876db4f5d1227f23136dc0b49e6d3315050b06c1c459c4e49445c2135af364f18c6578b1339e44380565d63b030f1490408abb007f2a6747111ac5c2a000e0d214d790099cd0071b5bb5579572208a0818fdef64ddd66d57b3ae83130ed51acd1fd74b28a4198cf351bc12c2173ecc508fc705c5bac1e2fcd5b453f1f8dd1c83621c6f9ad05cc4e65c43d02bc00be3a718204338162998c13d706139187cb79c2c589822c9337470b2ce2a51a41c89b33a6877348ceba2b0166974db2b7360560d703280a90dc8ec43c1a7898198dd448e0ee94f36e7b218bcec6c5eb91949b7683410adca49207832951f072dbcaabfa83d561eb29f4b122f666900fe060868439e6b929bc3c386e52442e106db4227057dcb07258456297fba4ac81d0c1be201933e1236eb410abd5adc33d5383fc7260758b085bfdca343f01f8857f028b8b130d1fbc3bf3f00dac8b54e723754a9560f6a766856a31a791635c701e810f058e7fe440c9e022632fe521b6fee8ce04b18d85712a2d08f309865f00daf6e9d1bc02c95dc4399088fd8f78951cee40d37f38b8b75e04b1d6015c7cec23c36dca455d2d75db6aab0f2256aa205aa73a229e2197372d1a852a5ce889cc0aca0f4526f7d2c5bb362336e69e7858d9daa4d582391903d43202363f47d5ba0201c1b2bb4956169d2558485f3f596c8f0a6eb535168f44400c88a1efc9a139ff9da99641d1d52eaccef447ed88dfdd50766833947c5226a0cf01b2ab9cac80a9db9d200731f21043f30f83923d92fae8c4e7920bbe4a1f7f833ffc0d172d624c08b24519b8ebf1dd92fc9684f85103635030cf42be6898ae46fe42e5c81db2dd6ec4b0d7516a8493364f816def1a016cb5d07636704f61b19080d13030fcef842bf4ac389482e2e9834d0f536eb68f47c61a8d144461c55e4f99723d85c854d7afda0bc1b205780502f492d6a738b88e5052af51d8cf102c59bf982d7e0baab29b9eb1b51acbb3eefb08311442496a263b6530f64d519862b854ae64e520b5f4a20bdd9824d5ab8b2e4a9548640945d378abd3d18c67827ef77873def728b0ab85f4c14308fdd1080975c43f47455c5e9acdfa022d1ef2d78dcaf21fc1e8c7f127a1db11a00ee3620bc52b797da294c55c23b114d9d3b9a452a11ec8d4842a935cc62fae6696efbc42ef5e664af01b9cd94a754bfc38325efc12e01fc28cd7cc3f981206b4ad0224936761ba0a42b0080225a2f6909ab9622267add1ebfb5872fbc2307122732049338e848bb9603ee910501d7dc9341d33905281dd6b613299f4733910c5a8d07bb778ff59d59702347227dd132b3ac57e2b86dd6cc00b28339a2a85b07d6ae1e6d61ef06c99e6aaa0673d395240d71ffdab2341100ef979e2996ae2af9186c7d309d614ae6e77ce167ebc12fb727296387976763f0c9413cfbacbd547d507037a27c4d2b1b3399862be6414a7cd6644080c07f26419693204b99d8fd97ecbd91c1d5a247bd00972974e1cfed70f00dd727ba31d28337efaf6fa45e9cc2a4ef81c634dc6d788c208a4e78b0fbf0e2e25f6e698c7398bd666fb9111b52023a898487a4d43fdf37caf0e03e66492631ca402840ccbd8b0b7cdaeaa87b1451f0f10e1fab62f3e04b13ad75827b1fdf08c1546afa530c42d3eb2759ee4b64920d28ef4791024f5dbcfe9edddd6329d8afa98c3a4d566fe2e296adb94a9a39babc37768c1b5037cbd6ad75daf3d564dcd7430d75eae1fae660d5e807e3d7b4dba96f64b0d449c35bbbdbd1c974daa7972f694d9b99f3ff1289dedc869edad6b73950d8342a206f2e7777ae1a185cd95deeb58248d8d20a62e9a67579acb4891cc7a86fd332628bc77ad5f5c3e65e9e1618adfeeda68b8908214bbf579de51b5a4c6c92e8cfc5c45a619fb4a18c9a69c37ddf87c16d96b8b20280106aecff3d2e347548fa1fc24dce06eca205b56b8a6b7dbf66933fb2d3dca4451fdfb9d9e6465ce85be65fe19801c295f5eb4f47eb3af292e54f9b9b1cd6b79ce12bdaa6a0dbf7550a2cb31bb7dc40443333b7b9dcdbb7c1f29532d21357da5859753b61e0e87256c68f6ca68df1e7e7f0d67d2743011a0cbea0d81d7cb71b86bb10cf5d5cece6dbe7c1439b2652447eb0b02e2b3622529346dcfca6ca8ab3d7bebfb22ad4b4d3d10a42bf7dcb4dffe3f5f79f773b654da68bcdc5c5d10e7cfd9c4ef4f53bf1916f39cd1e0149e1674702abaef29d65415f3af2d17550a27e36a2627ab22ae4d465f606cf88ee0e6188da5369a2855b9c4b7523618683f1711ae349223664dfa8240d97376df7ea42431f3369e6e9c405a76be8f1209c0f8bae86890086695c6df801a45c1bf6d6bed8b0b71503f200fe4c258ba1afbc149dbfec88cd61f5f1660629b54e7188cfd3b65308d7d00330cb9e633e683987b17aff3c2d26412ca892136b8970850d1c875c9c8b88cad621fd2df4df569d725fb4c82d153abee4819ab55a705b213bf5db61c56d1db4cca0793ca03363f87f9e79aa29f8fc49bd595bd8bdd6ec888f50189234d437dcd5c01c141e989ddb6a7666fea4c3ccb48d6d699a18c5f5aeca6df55a801dc4fdf413c1d0a1df66c02e4c58d0d975cee24481cb12d27137b60089604740db0458c74a8fd1ad017f0b8ee50260df9b8b2090c539448b79dcdb5ad6cca6dd4a4efbdfe93040ab3c254585db204d329aaa1b90a5e6d98006e9730a7c16e1919a953477d91b32b36cae02ffba799f1f2feae07c7e78e936f03d4f925f3febaba7aeea1cc9a983773204c288cfc2fdf2f40e744fc6c3bd2e761f1d76305afa1f36982a2c3027b81e6edf00671ba7fd6a8d695bc611b872464ea5670f4edd0b7daee7cfaae9d4725b443da37a65fe57aa9c815233fc0f5df2c6850ff4886bbb48a0f3f37061f7f7371d663c3e9c6f85842a052f6d4b7b449e412507fc92701271083fed09e30fc930873a185e62402d1148af435d45b4416973f00071935d06b4d711ae509d3637171f040addd066c07159ed2089c078bed3b3ca83fd5ba72a78e260aecef606771145b0222f3e4294ba3463e75a549e03ae0a7103542f4898b56801b5813e4c551266c7eaf1e720bb710615f105f164c51a0f36c8774551144b6fcfef832975c853be8ff704e480dc88f26fee57d9310adcabd652eacbc42c96976c46019f88006f3ab0c881b28dad2d3904571ecb965ac828c62398d9c2eb125dcb81a2e2e66b221525a6e165b70f35ecc3a072a70d8d9d36dd81dea7092e48216259ba112fc6f9904adc6049a376b013e98326117aadcb2a79daa7431256d82d4a972ee1d062b9a8af388708ee4d6c8d55db024e75cb6b0bc32c333f7d6a63669c10ee80171e978d2cbfd03f27fbc8c831b77745b1da5d510312f031cb8e8883d9b2eaac6cb1bdc80ac1b9ec18c7ade97e2b9c45f027005574a71218892d49db73fd076e5b0a331f5a87168f7f9c834360af1081e801cfe562a7dbec448818710d25744b375e8d4438a6fbb222d24a78b2677c6164d01eb85f435de33649a394dbbdbed7f17e0d3348eddc42e456a8589d25c0043cab0cd84e021e9d74e3d5ab7f12895724e8eb512051005c73547c25341d9c0e943ab14ae3b22383e735d3c02782b2af1fd1d55edc4329c26540e08f876b8ac4373cbb017e239459b988685b96deb6619f5e129889a04699c8748cbd912d6ccf0db71bc795b68c8722b364e8f8acb6657100a8cfe944dfdee03b15e61b6632f385a0da8c572f6bc5edc6cd76abd46a298eed3d019136303b22cd337b9c2bb117a4daf20797fa9a67d8af8b80dcaeb962f6683fff79092afa0b209bf4882ce016e18a86df66c7f1448b75f190d465a79bd2bbc0f5d82497a4fb0bb29db840cb991631a88101f569b965a69c1564bc8b99ca7c5f6e813ea6475636ccc8933704cb1d8b847c069e1a8549208f513e11a47061ef0c1439f81e1c1f0d11d667f07f8ba5a4cac92a7e2ab20b4ceaccaf98abaf8af3a9974fc61f03fcb674fc6df3d74b8cdafd0eb4e0f9cc782c82e9600de69476b4d746e0cd08559283164038dffbd618985dd3e616de430271f5a803cf6eac8f3ab0d6ef21eaa81e64bb96d8a30be0f54520782a656d04525dfa6c2b6f5b026dabd72792291172f16723926929ed8864a8efca0466abe3e534b5ab085a67306a9bc53505957052aacf69d9828d6e18b625f6e6b74a74043d6be6b1b7f6dc5025c24923f921c50d5ea05b503de6e883457b83e1fffecc9ddff69dffd971fecdf93ff797f387db4f9f5c7c35ec9cf8689cfdc213aada3cd9d7e04411a2b4949521ca315e080234b1b929b06153bb78c08a3b4a162e7b9939f5f85e030bd0afb7f7be596787362276bacf7d90b52eb42525d91ff29293aa7414a185ad750ddd5523862e516e43a46fdcd01f934c682e8b48116cd97b5daa6cdc7afaa48c16c8e509c03b79d01e306ce7d1020508c865fc37c284e618447734a45f357851b05831ee2527c8be6cdf227335938497d9ac324ab6fc1266091bd40c13beb88c69220075e3f48fe161699e720364eb6d68828a0b4a4a4bf419ec8a25669765b1a5a56dc167f49eebe6244f0ad0e508e8d103ca2271d98f122c0a08b39708f2ede594495306c6020db8cb50098c04aa5662612ba8ff28c4d4e65652dc33cb75ae1c785134da365bb938d044b0305744af3756653bf4b566a5228c4bfa1c23b5fa9b96fe2ad6673fd7461ec6d3fe0e46eb08aa10f178fc447dec92c94eeda352ab8156690a481d8a535e1850bdd8ccde7dd0def70ad56fb7685dea664f2463a62e4e20a2f350caa7890a5194f1ec300bc1f545f528537d609af14abd66853cf166736e94443acbd447e3d9bc42e9b4c9e3d36cae5e8e4f570ce4d633b3760d015bbe4de1333014730359776c228de56f967f6567984ac5941eceb7687d3246ba8cbe3610b16ff6dfd1b701ac356ac91eacc0ea32dc692c246874fc1051bcff8052585de9a36647358e544fcaac392653c7e45a98af212a461da20e69c373bedb79b0740abbfd88ca75cac2316163afb244845007e98a08fb45898aa1c784ec2518ddfef277628db5c4dd0ea847d9d66f5a8160a458e59bcf66973181e423e770732de670c318d7b02c22749938cf1c08579bfc75ee77d8fbe8acce03bbd1fff3d1f1b60920fa7848b9f7f09a3c647445d6d2b38f2b6d1b7e2f44dce54c576bf474a7cb268331fe9fb374a5b1a9bad31500769350e57985e36ca399d532678114be5a2821647f78d3b17995866d5e74b5bcb18c684fd9c3640e52564a24882a2ecb6de32c643f32b389075bed76a50fbb24034de40ac4cad0342bc4c8f23a02e7655c8175ebd1fc49a516275ac5e486e14637708e2291e3bca1afbd81b3dd5c002857c0bb96646bf096d767cbc9a56d9516a7a45a9e0d37fd5174846ea2ac042192a6ad9920f773424741a32cb98381d7826ca4871b04c1cbc4ef959c0ae22b1136e4c69b4398063159b0285a639ac842b8d34991cfbd00925daa5fcf1356c4e7cf05109c34aeb8e0d1fbcb0537b9e0915ff2dfb5e7342bbe75ab1d6d4b10e5070bae79ede173e601aa5143fe6517305605233a3dd3fcba0a5bd145e1c4d5e9fb9823bb8fb3e852899d77eeaff461ad9b727e9e37c67b6ee9b69bec993e5586d9a1151e6fe6b0a03c3500ad7251caaaf89cdd3c90392b268c5fc7f6186709ef97d5be49a7c7db15bbb55273a254b09a625d5eff12cd56d1a53b36d860e89b44029f2bd0ea10f7592a8f6f4ee25bcb6240ed4c25fe3fa93e7cfbfee88fa80b9719f68611f12a51e3b13c118b3544581eed56f19d983587efccf074c7f9bbf3f089033d39d0951e3e7efcf560fc5ef3191d587bb05ad2faed6b47015ea6256ac9c019746c13cd3ab8fca7436abea630fb6f8ebe178b2ddb3d7843df8a4824bccdb559cbab8d73fb5f7abfd1a53ed4f44feb505fd6352519a7d02b311dee69fac5cfc96c7c2a6a9fbec3b71fa96811aa5467a37d4ba30559f8f7ab39b1d5034d7637ad972c62ccb1a555aae2877c7135c4e73258d076da6ae09b70ef84361402cbe3801b5412bd4d6d9996bf65bb65791c17490d00312d163313ec8eae3c033547c036b5efe309cb6231264e544878d1265c4a04676ce2332e2b87adea47ad1ac3e3b3592266b4288adb4a3140f3b275a474027a06e1363f8531d2ad41b82d9b378e08239a4b07b95714a2e9edc2ed526a57a6342cc9de24023f235cffa852a580ed015f525c3ace9eab72d3a27331d0a092222a3b286f4cb0ef0f91519fd8afda5ebf967f6710da7f06bb58bd732fffbe1ebc89cdfbb79bd944bab57b539358decd57c4eb1edec3d71399569b8837805ff948147d9fccae86759e36c72ab7ca28f63e225cf9bc025822a0da95e3f8ce2bc3b22bc29a1788355a27d5e0648a61494e5ed4a848c25b0efa882ff7e988f8d674e9eebbf2269ef2fbb13d64e8fc1ca11ee15ea49ff92f1862542e4f2efab365cb57f860af445474a6a7b489df4629f6375e6ec4c1eed3ebf6d6d57ddff69e9265f433b332758f612ea0ab67a8077aaaddf54b8837fb695e2eb4746fd01fead023feb4df7cb70741c104e147e8e83beabfdadc1f958e9b157ffd908a64e13c7277dcc7eb6b979f6bffb5f9b5f695ed608e6f5480b1d20550a0a32684e8e3ff010000ffff010000ffff2e7f74417c800000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["index.html"] = bs diff --git a/cid/cid.go b/cid/cid.go index a47a1240a..22642b97c 100644 --- a/cid/cid.go +++ b/cid/cid.go @@ -5,27 +5,32 @@ // Package cid provides a manager for mappings between node ID:s and connection ID:s. package cid -import "sync" +import ( + "sync" + + "github.com/calmh/syncthing/protocol" +) type Map struct { sync.Mutex - toCid map[string]uint - toName []string + toCid map[protocol.NodeID]uint + toName []protocol.NodeID } var ( - LocalName = "" - LocalID uint = 0 + LocalNodeID = protocol.NodeID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + LocalID uint = 0 + emptyNodeID protocol.NodeID ) func NewMap() *Map { return &Map{ - toCid: map[string]uint{"": 0}, - toName: []string{""}, + toCid: map[protocol.NodeID]uint{LocalNodeID: LocalID}, + toName: []protocol.NodeID{LocalNodeID}, } } -func (m *Map) Get(name string) uint { +func (m *Map) Get(name protocol.NodeID) uint { m.Lock() defer m.Unlock() @@ -36,7 +41,7 @@ func (m *Map) Get(name string) uint { // Find a free slot to get a new ID for i, n := range m.toName { - if n == "" { + if n == emptyNodeID { m.toName[i] = name m.toCid[name] = uint(i) return uint(i) @@ -50,19 +55,19 @@ func (m *Map) Get(name string) uint { return cid } -func (m *Map) Name(cid uint) string { +func (m *Map) Name(cid uint) protocol.NodeID { m.Lock() defer m.Unlock() return m.toName[cid] } -func (m *Map) Names() []string { +func (m *Map) Names() []protocol.NodeID { m.Lock() - var names []string + var names []protocol.NodeID for _, name := range m.toName { - if name != "" { + if name != emptyNodeID { names = append(names, name) } } @@ -71,11 +76,11 @@ func (m *Map) Names() []string { return names } -func (m *Map) Clear(name string) { +func (m *Map) Clear(name protocol.NodeID) { m.Lock() cid, ok := m.toCid[name] if ok { - m.toName[cid] = "" + m.toName[cid] = emptyNodeID delete(m.toCid, name) } m.Unlock() diff --git a/cid/cid_test.go b/cid/cid_test.go index 81a26dccd..9be7e1662 100644 --- a/cid/cid_test.go +++ b/cid/cid_test.go @@ -4,28 +4,35 @@ package cid -import "testing" +import ( + "testing" + + "github.com/calmh/syncthing/protocol" +) func TestGet(t *testing.T) { m := NewMap() - if i := m.Get("foo"); i != 1 { + fooID := protocol.NewNodeID([]byte("foo")) + barID := protocol.NewNodeID([]byte("bar")) + + if i := m.Get(fooID); i != 1 { t.Errorf("Unexpected id %d != 1", i) } - if i := m.Get("bar"); i != 2 { + if i := m.Get(barID); i != 2 { t.Errorf("Unexpected id %d != 2", i) } - if i := m.Get("foo"); i != 1 { + if i := m.Get(fooID); i != 1 { t.Errorf("Unexpected id %d != 1", i) } - if i := m.Get("bar"); i != 2 { + if i := m.Get(barID); i != 2 { t.Errorf("Unexpected id %d != 2", i) } if LocalID != 0 { t.Error("LocalID should be 0") } - if i := m.Get(LocalName); i != LocalID { - t.Errorf("Unexpected id %d != %c", i, LocalID) + if i := m.Get(LocalNodeID); i != LocalID { + t.Errorf("Unexpected id %d != %d", i, LocalID) } } diff --git a/cmd/stcli/main.go b/cmd/stcli/main.go index 161efae84..ee6d19bc3 100644 --- a/cmd/stcli/main.go +++ b/cmd/stcli/main.go @@ -46,12 +46,12 @@ func connect(target string) { log.Fatal(err) } - myID := string(certID(cert.Certificate[0])) + myID := protocol.NewNodeID(cert.Certificate[0]) tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, - ServerName: myID, + ServerName: myID.String(), ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, @@ -63,7 +63,7 @@ func connect(target string) { log.Fatal(err) } - remoteID := certID(conn.ConnectionState().PeerCertificates[0].Raw) + remoteID := protocol.NewNodeID(conn.ConnectionState().PeerCertificates[0].Raw) pc = protocol.NewConnection(remoteID, conn, conn, Model{}) @@ -82,7 +82,7 @@ func prtIndex(files []protocol.FileInfo) { } } -func (m Model) Index(nodeID string, repo string, files []protocol.FileInfo) { +func (m Model) Index(nodeID protocol.NodeID, repo string, files []protocol.FileInfo) { log.Printf("Received index for repo %q", repo) if cmd == "idx" { prtIndex(files) @@ -121,7 +121,7 @@ func getFile(f protocol.FileInfo) { fd.Close() } -func (m Model) IndexUpdate(nodeID string, repo string, files []protocol.FileInfo) { +func (m Model) IndexUpdate(nodeID protocol.NodeID, repo string, files []protocol.FileInfo) { log.Printf("Received index update for repo %q", repo) if cmd == "idx" { prtIndex(files) @@ -131,16 +131,16 @@ func (m Model) IndexUpdate(nodeID string, repo string, files []protocol.FileInfo } } -func (m Model) ClusterConfig(nodeID string, config protocol.ClusterConfigMessage) { +func (m Model) ClusterConfig(nodeID protocol.NodeID, config protocol.ClusterConfigMessage) { log.Println("Received cluster config") log.Printf("%#v", config) } -func (m Model) Request(nodeID, repo string, name string, offset int64, size int) ([]byte, error) { +func (m Model) Request(nodeID protocol.NodeID, repo string, name string, offset int64, size int) ([]byte, error) { log.Println("Received request") return nil, io.EOF } -func (m Model) Close(nodeID string, err error) { +func (m Model) Close(nodeID protocol.NodeID, err error) { log.Println("Received close") } diff --git a/cmd/stcli/tls.go b/cmd/stcli/tls.go index 1d89f8589..2be011238 100644 --- a/cmd/stcli/tls.go +++ b/cmd/stcli/tls.go @@ -5,20 +5,12 @@ package main import ( - "crypto/sha256" "crypto/tls" - "encoding/base32" "path/filepath" - "strings" ) func loadCert(dir string) (tls.Certificate, error) { - return tls.LoadX509KeyPair(filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")) -} - -func certID(bs []byte) string { - hf := sha256.New() - hf.Write(bs) - id := hf.Sum(nil) - return strings.Trim(base32.StdEncoding.EncodeToString(id), "=") + cf := filepath.Join(dir, "cert.pem") + kf := filepath.Join(dir, "key.pem") + return tls.LoadX509KeyPair(cf, kf) } diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 5d70ed580..d000ff161 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -327,7 +327,7 @@ func restGetSystem(w http.ResponseWriter) { runtime.ReadMemStats(&m) res := make(map[string]interface{}) - res["myID"] = myID + res["myID"] = myID.String() res["goroutines"] = runtime.NumGoroutine() res["alloc"] = m.Alloc res["sys"] = m.Sys diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index dfbb4957d..90ed07226 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -61,7 +61,7 @@ func init() { var ( cfg config.Configuration - myID string + myID protocol.NodeID confDir string logFlags int = log.Ltime rateBucket *ratelimit.Bucket @@ -181,8 +181,8 @@ func main() { l.FatalErr(err) } - myID = certID(cert.Certificate[0]) - l.SetPrefix(fmt.Sprintf("[%s] ", myID[:5])) + myID = protocol.NewNodeID(cert.Certificate[0]) + l.SetPrefix(fmt.Sprintf("[%s] ", myID.String()[:5])) l.Infoln(LongVersion) l.Infoln("My ID:", myID) @@ -263,7 +263,7 @@ func main() { tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, NextProtos: []string{"bep/1.0"}, - ServerName: myID, + ServerName: myID.String(), ClientAuth: tls.RequestClientCert, SessionTicketsDisabled: true, InsecureSkipVerify: true, @@ -567,7 +567,7 @@ func saveConfig() { saveConfigCh <- struct{}{} } -func listenConnect(myID string, m *model.Model, tlsCfg *tls.Config) { +func listenConnect(myID protocol.NodeID, m *model.Model, tlsCfg *tls.Config) { var conns = make(chan *tls.Conn) // Listen @@ -673,7 +673,7 @@ next: conn.Close() continue } - remoteID := certID(certs[0].Raw) + remoteID := protocol.NewNodeID(certs[0].Raw) if remoteID == myID { l.Infof("Connected to myself (%s) - should not happen", remoteID) diff --git a/cmd/syncthing/tls.go b/cmd/syncthing/tls.go index 51231cf13..e9f989616 100644 --- a/cmd/syncthing/tls.go +++ b/cmd/syncthing/tls.go @@ -11,14 +11,12 @@ import ( "crypto/tls" "crypto/x509" "crypto/x509/pkix" - "encoding/base32" "encoding/binary" "encoding/pem" "math/big" mr "math/rand" "os" "path/filepath" - "strings" "time" ) @@ -28,14 +26,9 @@ const ( ) func loadCert(dir string, prefix string) (tls.Certificate, error) { - return tls.LoadX509KeyPair(filepath.Join(dir, prefix+"cert.pem"), filepath.Join(dir, prefix+"key.pem")) -} - -func certID(bs []byte) string { - hf := sha256.New() - hf.Write(bs) - id := hf.Sum(nil) - return strings.Trim(base32.StdEncoding.EncodeToString(id), "=") + cf := filepath.Join(dir, prefix+"cert.pem") + kf := filepath.Join(dir, prefix+"key.pem") + return tls.LoadX509KeyPair(cf, kf) } func certSeed(bs []byte) int64 { diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go index cee7196aa..46bd9a3e3 100644 --- a/cmd/syncthing/usage_report.go +++ b/cmd/syncthing/usage_report.go @@ -23,7 +23,7 @@ var stopUsageReportingCh = make(chan struct{}) func reportData(m *model.Model) map[string]interface{} { res := make(map[string]interface{}) - res["uniqueID"] = strings.ToLower(certID([]byte(myID)))[:6] + res["uniqueID"] = strings.ToLower(myID.String()[:6]) res["version"] = Version res["longVersion"] = LongVersion res["platform"] = runtime.GOOS + "-" + runtime.GOARCH diff --git a/config/config.go b/config/config.go index 77c86d478..710201654 100644 --- a/config/config.go +++ b/config/config.go @@ -14,10 +14,10 @@ import ( "regexp" "sort" "strconv" - "strings" "code.google.com/p/go.crypto/bcrypt" "github.com/calmh/syncthing/logger" + "github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/scanner" ) @@ -69,7 +69,7 @@ type RepositoryConfiguration struct { Versioning VersioningConfiguration `xml:"versioning"` SyncOrderPatterns []SyncOrderPattern `xml:"syncorder>pattern"` - nodeIDs []string + nodeIDs []protocol.NodeID } type VersioningConfiguration struct { @@ -113,7 +113,7 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl return nil } -func (r *RepositoryConfiguration) NodeIDs() []string { +func (r *RepositoryConfiguration) NodeIDs() []protocol.NodeID { if r.nodeIDs == nil { for _, n := range r.Nodes { r.nodeIDs = append(r.nodeIDs, n.NodeID) @@ -138,9 +138,9 @@ func (r RepositoryConfiguration) FileRanker() func(scanner.File) int { } type NodeConfiguration struct { - NodeID string `xml:"id,attr"` - Name string `xml:"name,attr,omitempty"` - Addresses []string `xml:"address,omitempty"` + NodeID protocol.NodeID `xml:"id,attr"` + Name string `xml:"name,attr,omitempty"` + Addresses []string `xml:"address,omitempty"` } type OptionsConfiguration struct { @@ -174,8 +174,8 @@ type GUIConfiguration struct { APIKey string `xml:"apikey,omitempty"` } -func (cfg *Configuration) NodeMap() map[string]NodeConfiguration { - m := make(map[string]NodeConfiguration, len(cfg.Nodes)) +func (cfg *Configuration) NodeMap() map[protocol.NodeID]NodeConfiguration { + m := make(map[protocol.NodeID]NodeConfiguration, len(cfg.Nodes)) for _, n := range cfg.Nodes { m[n.NodeID] = n } @@ -276,7 +276,7 @@ func uniqueStrings(ss []string) []string { return us } -func Load(rd io.Reader, myID string) (Configuration, error) { +func Load(rd io.Reader, myID protocol.NodeID) (Configuration, error) { var cfg Configuration setDefaults(&cfg) @@ -297,15 +297,6 @@ func Load(rd io.Reader, myID string) (Configuration, error) { cfg.Repositories = []RepositoryConfiguration{} } - // Sanitize node IDs - for i := range cfg.Nodes { - node := &cfg.Nodes[i] - // Strip spaces and dashes - node.NodeID = strings.Replace(node.NodeID, "-", "", -1) - node.NodeID = strings.Replace(node.NodeID, " ", "", -1) - node.NodeID = strings.ToUpper(node.NodeID) - } - // Check for missing, bad or duplicate repository ID:s var seenRepos = map[string]*RepositoryConfiguration{} var uniqueCounter int @@ -321,13 +312,6 @@ func Load(rd io.Reader, myID string) (Configuration, error) { repo.ID = "default" } - for i := range repo.Nodes { - node := &repo.Nodes[i] - // Strip spaces and dashes - node.NodeID = strings.Replace(node.NodeID, "-", "", -1) - node.NodeID = strings.Replace(node.NodeID, " ", "", -1) - } - if seen, ok := seenRepos[repo.ID]; ok { l.Warnf("Multiple repositories with ID %q; disabling", repo.ID) @@ -390,8 +374,9 @@ func convertV1V2(cfg *Configuration) { for i, repo := range cfg.Repositories { cfg.Repositories[i].ReadOnly = cfg.Options.Deprecated_ReadOnly for j, node := range repo.Nodes { - if _, ok := nodes[node.NodeID]; !ok { - nodes[node.NodeID] = node + id := node.NodeID.String() + if _, ok := nodes[id]; !ok { + nodes[id] = node } cfg.Repositories[i].Nodes[j] = NodeConfiguration{NodeID: node.NodeID} } @@ -416,7 +401,7 @@ func convertV1V2(cfg *Configuration) { type NodeConfigurationList []NodeConfiguration func (l NodeConfigurationList) Less(a, b int) bool { - return l[a].NodeID < l[b].NodeID + return l[a].NodeID.Compare(l[b].NodeID) == -1 } func (l NodeConfigurationList) Swap(a, b int) { l[a], l[b] = l[b], l[a] @@ -425,10 +410,10 @@ func (l NodeConfigurationList) Len() int { return len(l) } -func ensureNodePresent(nodes []NodeConfiguration, myID string) []NodeConfiguration { +func ensureNodePresent(nodes []NodeConfiguration, myID protocol.NodeID) []NodeConfiguration { var myIDExists bool for _, node := range nodes { - if node.NodeID == myID { + if node.NodeID.Equals(myID) { myIDExists = true break } diff --git a/config/config_test.go b/config/config_test.go index 58f364a2d..cacb45fe7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -12,9 +12,19 @@ import ( "testing" "github.com/calmh/syncthing/files" + "github.com/calmh/syncthing/protocol" "github.com/calmh/syncthing/scanner" ) +var node1, node2, node3, node4 protocol.NodeID + +func init() { + node1, _ = protocol.NodeIDFromString("AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ") + node2, _ = protocol.NodeIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY") + node3, _ = protocol.NodeIDFromString("LGFPDIT-7SKNNJL-VJZA4FC-7QNCRKA-CE753K7-2BW5QDK-2FOZ7FR-FEP57QJ") + node4, _ = protocol.NodeIDFromString("P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2") +} + func TestDefaultValues(t *testing.T) { expected := OptionsConfiguration{ ListenAddress: []string{"0.0.0.0:22000"}, @@ -31,7 +41,7 @@ func TestDefaultValues(t *testing.T) { UPnPEnabled: true, } - cfg, err := Load(bytes.NewReader(nil), "nodeID") + cfg, err := Load(bytes.NewReader(nil), node1) if err != io.EOF { t.Error(err) } @@ -45,10 +55,10 @@ func TestNodeConfig(t *testing.T) { v1data := []byte(` - +
a
- +
b
@@ -61,20 +71,20 @@ func TestNodeConfig(t *testing.T) { v2data := []byte(` - - + + - +
a
- +
b
`) for i, data := range [][]byte{v1data, v2data} { - cfg, err := Load(bytes.NewReader(data), "NODE1") + cfg, err := Load(bytes.NewReader(data), node1) if err != nil { t.Error(err) } @@ -83,23 +93,23 @@ func TestNodeConfig(t *testing.T) { { ID: "test", Directory: "~/Sync", - Nodes: []NodeConfiguration{{NodeID: "NODE1"}, {NodeID: "NODE2"}}, + Nodes: []NodeConfiguration{{NodeID: node1}, {NodeID: node4}}, ReadOnly: true, }, } expectedNodes := []NodeConfiguration{ { - NodeID: "NODE1", + NodeID: node1, Name: "node one", Addresses: []string{"a"}, }, { - NodeID: "NODE2", + NodeID: node4, Name: "node two", Addresses: []string{"b"}, }, } - expectedNodeIDs := []string{"NODE1", "NODE2"} + expectedNodeIDs := []protocol.NodeID{node1, node4} if cfg.Version != 2 { t.Errorf("%d: Incorrect version %d != 2", i, cfg.Version) @@ -118,18 +128,13 @@ func TestNodeConfig(t *testing.T) { func TestNoListenAddress(t *testing.T) { data := []byte(` - - -
dynamic
-
-
`) - cfg, err := Load(bytes.NewReader(data), "nodeID") + cfg, err := Load(bytes.NewReader(data), node1) if err != nil { t.Error(err) } @@ -142,11 +147,6 @@ func TestNoListenAddress(t *testing.T) { func TestOverriddenValues(t *testing.T) { data := []byte(` - - -
dynamic
-
-
:23000 false @@ -180,7 +180,7 @@ func TestOverriddenValues(t *testing.T) { UPnPEnabled: false, } - cfg, err := Load(bytes.NewReader(data), "nodeID") + cfg, err := Load(bytes.NewReader(data), node1) if err != nil { t.Error(err) } @@ -193,13 +193,13 @@ func TestOverriddenValues(t *testing.T) { func TestNodeAddresses(t *testing.T) { data := []byte(` - -
dynamic
-
- +
- + + + +
dynamic
`) @@ -207,25 +207,25 @@ func TestNodeAddresses(t *testing.T) { name, _ := os.Hostname() expected := []NodeConfiguration{ { - NodeID: "N1", + NodeID: node1, Addresses: []string{"dynamic"}, }, { - NodeID: "N2", + NodeID: node2, Addresses: []string{"dynamic"}, }, { - NodeID: "N3", + NodeID: node3, Addresses: []string{"dynamic"}, }, { - NodeID: "N4", + NodeID: node4, Name: name, // Set when auto created Addresses: []string{"dynamic"}, }, } - cfg, err := Load(bytes.NewReader(data), "N4") + cfg, err := Load(bytes.NewReader(data), node4) if err != nil { t.Error(err) } @@ -235,86 +235,32 @@ func TestNodeAddresses(t *testing.T) { } } -func TestStripNodeIs(t *testing.T) { - data := []byte(` - - -
dynamic
-
- -
-
- -
-
- - - - - -
-`) - - expected := []NodeConfiguration{ - { - NodeID: "AAAABBBBCCCC", - Addresses: []string{"dynamic"}, - }, - { - NodeID: "AAAABBBBDDDD", - Addresses: []string{"dynamic"}, - }, - { - NodeID: "AAAABBBBEEEE", - Addresses: []string{"dynamic"}, - }, - } - - cfg, err := Load(bytes.NewReader(data), "n4") - if err != nil { - t.Error(err) - } - - for i := range expected { - if !reflect.DeepEqual(cfg.Nodes[i], expected[i]) { - t.Errorf("Nodes[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Nodes[i]) - } - if cfg.Repositories[0].Nodes[i].NodeID != expected[i].NodeID { - t.Errorf("Repo nodes[%d] differ;\n E: %#v\n A: %#v", i, expected[i].NodeID, cfg.Repositories[0].Nodes[i].NodeID) - } - } -} - func TestSyncOrders(t *testing.T) { data := []byte(` - -
dynamic
-
-
`) expected := []SyncOrderPattern{ { - Pattern: "\\.jpg$", - Priority: 1, + Pattern: "\\.jpg$", + Priority: 1, }, } - cfg, err := Load(bytes.NewReader(data), "n4") + cfg, err := Load(bytes.NewReader(data), node1) if err != nil { t.Error(err) } for i := range expected { if !reflect.DeepEqual(cfg.Repositories[0].SyncOrderPatterns[i], expected[i]) { - t.Errorf("Nodes[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Repositories[0].SyncOrderPatterns[i]) + t.Errorf("Patterns[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Repositories[0].SyncOrderPatterns[i]) } } } @@ -361,9 +307,9 @@ func TestFileSorter(t *testing.T) { if !reflect.DeepEqual(f, expected) { t.Errorf( "\n\nexpected:\n" + - formatFiles(expected) + "\n" + - "got:\n" + - formatFiles(f) + "\n\n", + formatFiles(expected) + "\n" + + "got:\n" + + formatFiles(f) + "\n\n", ) } } diff --git a/discover/cmd/discosrv/main.go b/discover/cmd/discosrv/main.go index 344b5959c..56cb6faa0 100644 --- a/discover/cmd/discosrv/main.go +++ b/discover/cmd/discosrv/main.go @@ -17,6 +17,7 @@ import ( "time" "github.com/calmh/syncthing/discover" + "github.com/calmh/syncthing/protocol" "github.com/golang/groupcache/lru" "github.com/juju/ratelimit" ) @@ -32,7 +33,7 @@ type address struct { } var ( - nodes = make(map[string]node) + nodes = make(map[protocol.NodeID]node) lock sync.Mutex queries = 0 announces = 0 @@ -182,8 +183,16 @@ func handleAnnounceV2(addr *net.UDPAddr, buf []byte) { updated: time.Now(), } + var id protocol.NodeID + if len(pkt.This.ID) == 32 { + // Raw node ID + copy(id[:], pkt.This.ID) + } else { + id.UnmarshalText(pkt.This.ID) + } + lock.Lock() - nodes[pkt.This.ID] = node + nodes[id] = node lock.Unlock() } @@ -199,8 +208,16 @@ func handleQueryV2(conn *net.UDPConn, addr *net.UDPAddr, buf []byte) { log.Printf("<- %v %#v", addr, pkt) } + var id protocol.NodeID + if len(pkt.NodeID) == 32 { + // Raw node ID + copy(id[:], pkt.NodeID) + } else { + id.UnmarshalText(pkt.NodeID) + } + lock.Lock() - node, ok := nodes[pkt.NodeID] + node, ok := nodes[id] queries++ lock.Unlock() diff --git a/discover/discover.go b/discover/discover.go index 4eb82d388..0b23cc31e 100644 --- a/discover/discover.go +++ b/discover/discover.go @@ -5,6 +5,7 @@ package discover import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -14,15 +15,16 @@ import ( "time" "github.com/calmh/syncthing/beacon" + "github.com/calmh/syncthing/protocol" ) type Discoverer struct { - myID string + myID protocol.NodeID listenAddrs []string localBcastIntv time.Duration globalBcastIntv time.Duration beacon *beacon.Beacon - registry map[string][]string + registry map[protocol.NodeID][]string registryLock sync.RWMutex extServer string extPort uint16 @@ -41,7 +43,7 @@ var ( // When we hit this many errors in succession, we stop. const maxErrors = 30 -func NewDiscoverer(id string, addresses []string, localPort int) (*Discoverer, error) { +func NewDiscoverer(id protocol.NodeID, addresses []string, localPort int) (*Discoverer, error) { b, err := beacon.New(localPort) if err != nil { return nil, err @@ -52,7 +54,7 @@ func NewDiscoverer(id string, addresses []string, localPort int) (*Discoverer, e localBcastIntv: 30 * time.Second, globalBcastIntv: 1800 * time.Second, beacon: b, - registry: make(map[string][]string), + registry: make(map[protocol.NodeID][]string), } go disc.recvAnnouncements() @@ -78,7 +80,7 @@ func (d *Discoverer) ExtAnnounceOK() bool { return d.extAnnounceOK } -func (d *Discoverer) Lookup(node string) []string { +func (d *Discoverer) Lookup(node protocol.NodeID) []string { d.registryLock.Lock() addr, ok := d.registry[node] d.registryLock.Unlock() @@ -94,15 +96,17 @@ func (d *Discoverer) Lookup(node string) []string { func (d *Discoverer) Hint(node string, addrs []string) { resAddrs := resolveAddrs(addrs) + var id protocol.NodeID + id.UnmarshalText([]byte(node)) d.registerNode(nil, Node{ - ID: node, Addresses: resAddrs, + ID: id[:], }) } -func (d *Discoverer) All() map[string][]string { +func (d *Discoverer) All() map[protocol.NodeID][]string { d.registryLock.RLock() - nodes := make(map[string][]string, len(d.registry)) + nodes := make(map[protocol.NodeID][]string, len(d.registry)) for node, addrs := range d.registry { addrsCopy := make([]string, len(addrs)) copy(addrsCopy, addrs) @@ -132,7 +136,7 @@ func (d *Discoverer) announcementPkt() []byte { } var pkt = AnnounceV2{ Magic: AnnouncementMagicV2, - This: Node{d.myID, addrs}, + This: Node{d.myID[:], addrs}, } return pkt.MarshalXDR() } @@ -142,7 +146,7 @@ func (d *Discoverer) sendLocalAnnouncements() { var pkt = AnnounceV2{ Magic: AnnouncementMagicV2, - This: Node{d.myID, addrs}, + This: Node{d.myID[:], addrs}, } for { @@ -153,7 +157,7 @@ func (d *Discoverer) sendLocalAnnouncements() { break } - anode := Node{node, resolveAddrs(addrs)} + anode := Node{node[:], resolveAddrs(addrs)} pkt.Extra = append(pkt.Extra, anode) } d.registryLock.RUnlock() @@ -184,7 +188,7 @@ func (d *Discoverer) sendExternalAnnouncements() { if d.extPort != 0 { var pkt = AnnounceV2{ Magic: AnnouncementMagicV2, - This: Node{d.myID, []Address{{Port: d.extPort}}}, + This: Node{d.myID[:], []Address{{Port: d.extPort}}}, } buf = pkt.MarshalXDR() } else { @@ -246,11 +250,11 @@ func (d *Discoverer) recvAnnouncements() { } var newNode bool - if pkt.This.ID != d.myID { + if bytes.Compare(pkt.This.ID, d.myID[:]) != 0 { n := d.registerNode(addr, pkt.This) newNode = newNode || n for _, node := range pkt.Extra { - if node.ID != d.myID { + if bytes.Compare(node.ID, d.myID[:]) != 0 { n := d.registerNode(nil, node) newNode = newNode || n } @@ -287,14 +291,16 @@ func (d *Discoverer) registerNode(addr net.Addr, node Node) bool { if debug { l.Debugf("discover: register: %s -> %#v", node.ID, addrs) } + var id protocol.NodeID + copy(id[:], node.ID) d.registryLock.Lock() - _, seen := d.registry[node.ID] - d.registry[node.ID] = addrs + _, seen := d.registry[id] + d.registry[id] = addrs d.registryLock.Unlock() return !seen } -func (d *Discoverer) externalLookup(node string) []string { +func (d *Discoverer) externalLookup(node protocol.NodeID) []string { extIP, err := net.ResolveUDPAddr("udp", d.extServer) if err != nil { if debug { @@ -320,7 +326,7 @@ func (d *Discoverer) externalLookup(node string) []string { return nil } - buf := QueryV2{QueryMagicV2, node}.MarshalXDR() + buf := QueryV2{QueryMagicV2, node[:]}.MarshalXDR() _, err = conn.Write(buf) if err != nil { if debug { diff --git a/discover/packets.go b/discover/packets.go index cec218484..a8074ce9f 100644 --- a/discover/packets.go +++ b/discover/packets.go @@ -11,7 +11,7 @@ const ( type QueryV2 struct { Magic uint32 - NodeID string // max:64 + NodeID []byte // max:32 } type AnnounceV2 struct { @@ -21,7 +21,7 @@ type AnnounceV2 struct { } type Node struct { - ID string // max:64 + ID []byte // max:32 Addresses []Address // max:16 } diff --git a/discover/packets_xdr.go b/discover/packets_xdr.go index fd87a78e9..b2e5e2e46 100644 --- a/discover/packets_xdr.go +++ b/discover/packets_xdr.go @@ -1,7 +1,3 @@ -// Copyright (C) 2014 Jakob Borg and other contributors. All rights reserved. -// Use of this source code is governed by an MIT-style license that can be -// found in the LICENSE file. - package discover import ( @@ -25,10 +21,10 @@ func (o QueryV2) MarshalXDR() []byte { func (o QueryV2) encodeXDR(xw *xdr.Writer) (int, error) { xw.WriteUint32(o.Magic) - if len(o.NodeID) > 64 { + if len(o.NodeID) > 32 { return xw.Tot(), xdr.ErrElementSizeExceeded } - xw.WriteString(o.NodeID) + xw.WriteBytes(o.NodeID) return xw.Tot(), xw.Error() } @@ -45,7 +41,7 @@ func (o *QueryV2) UnmarshalXDR(bs []byte) error { func (o *QueryV2) decodeXDR(xr *xdr.Reader) error { o.Magic = xr.ReadUint32() - o.NodeID = xr.ReadStringMax(64) + o.NodeID = xr.ReadBytesMax(32) return xr.Error() } @@ -112,10 +108,10 @@ func (o Node) MarshalXDR() []byte { } func (o Node) encodeXDR(xw *xdr.Writer) (int, error) { - if len(o.ID) > 64 { + if len(o.ID) > 32 { return xw.Tot(), xdr.ErrElementSizeExceeded } - xw.WriteString(o.ID) + xw.WriteBytes(o.ID) if len(o.Addresses) > 16 { return xw.Tot(), xdr.ErrElementSizeExceeded } @@ -138,7 +134,7 @@ func (o *Node) UnmarshalXDR(bs []byte) error { } func (o *Node) decodeXDR(xr *xdr.Reader) error { - o.ID = xr.ReadStringMax(64) + o.ID = xr.ReadBytesMax(32) _AddressesSize := int(xr.ReadUint32()) if _AddressesSize > 16 { return xdr.ErrElementSizeExceeded diff --git a/gui/app.js b/gui/app.js index 776f5518a..b9f0fe0a9 100644 --- a/gui/app.js +++ b/gui/app.js @@ -410,7 +410,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) { $('#editNode').modal('hide'); nodeCfg = $scope.currentNode; - nodeCfg.NodeID = nodeCfg.NodeID.replace(/ /g, '').replace(/-/g, '').toUpperCase().trim(); + nodeCfg.NodeID = nodeCfg.NodeID.replace(/ /g, '').replace(/-/g, '').toLowerCase().trim(); nodeCfg.Addresses = nodeCfg.AddressesStr.split(',').map(function (x) { return x.trim(); }); done = false; @@ -711,7 +711,7 @@ function randomString(len, bits) newStr = Math.random().toString(bits).slice(2); outStr += newStr.slice(0, Math.min(newStr.length, (len - outStr.length))); } - return outStr.toUpperCase(); + return outStr.toLowerCase(); } syncthing.filter('natural', function () { @@ -777,17 +777,6 @@ syncthing.filter('alwaysNumber', function () { }; }); -syncthing.filter('chunkID', function () { - return function (input) { - if (input === undefined) - return ""; - var parts = input.match(/.{1,6}/g); - if (!parts) - return ""; - return parts.join('-'); - }; -}); - syncthing.filter('shortPath', function () { return function (input) { if (input === undefined) @@ -860,8 +849,8 @@ syncthing.directive('validNodeid', function() { // we shouldn't validate ctrl.$setValidity('validNodeid', true); } else { - var cleaned = viewValue.replace(/ /g, '').replace(/-/g, '').toUpperCase().trim(); - if (cleaned.match(/^[A-Z2-7]{52}$/)) { + var cleaned = viewValue.replace(/ /g, '').replace(/-/g, '').toLowerCase().trim(); + if (cleaned.match(/^[a-z2-7]{52}$/)) { ctrl.$setValidity('validNodeid', true); } else { ctrl.$setValidity('validNodeid', false); diff --git a/gui/index.html b/gui/index.html index 4004d5a19..809229feb 100644 --- a/gui/index.html +++ b/gui/index.html @@ -418,8 +418,8 @@ found in the LICENSE file.