OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """WSGI application to manage a USB gadget. | 5 """WSGI application to manage a USB gadget. |
6 """ | 6 """ |
7 | 7 |
| 8 import datetime |
| 9 import hashlib |
8 import re | 10 import re |
| 11 import subprocess |
9 import sys | 12 import sys |
| 13 import time |
| 14 import urllib2 |
10 | 15 |
11 from tornado import httpserver | 16 from tornado import httpserver |
| 17 from tornado import ioloop |
12 from tornado import web | 18 from tornado import web |
13 | 19 |
14 import default_gadget | 20 import default_gadget |
15 | 21 |
16 VERSION_PATTERN = re.compile(r'.*usb_gadget-([a-z0-9]{32})\.zip') | 22 VERSION_PATTERN = re.compile(r'.*usb_gadget-([a-z0-9]{32})\.zip') |
17 | 23 |
18 address = None | 24 address = None |
19 chip = None | 25 chip = None |
20 claimed_by = None | 26 claimed_by = None |
21 default = default_gadget.DefaultGadget() | 27 default = default_gadget.DefaultGadget() |
22 gadget = None | 28 gadget = None |
| 29 hardware = None |
| 30 interface = None |
| 31 port = None |
23 | 32 |
24 | 33 |
25 def SwitchGadget(new_gadget): | 34 def SwitchGadget(new_gadget): |
26 if chip.IsConfigured(): | 35 if chip.IsConfigured(): |
27 chip.Destroy() | 36 chip.Destroy() |
28 | 37 |
29 global gadget | 38 global gadget |
30 gadget = new_gadget | 39 gadget = new_gadget |
31 gadget.AddStringDescriptor(3, address) | 40 gadget.AddStringDescriptor(3, address) |
32 chip.Create(gadget) | 41 chip.Create(gadget) |
33 | 42 |
34 | 43 |
35 class VersionHandler(web.RequestHandler): | 44 class VersionHandler(web.RequestHandler): |
36 | 45 |
37 def get(self): | 46 def get(self): |
38 version = 'unpackaged' | 47 version = 'unpackaged' |
39 for path in sys.path: | 48 for path in sys.path: |
40 match = VERSION_PATTERN.match(path) | 49 match = VERSION_PATTERN.match(path) |
41 if match: | 50 if match: |
42 version = match.group(1) | 51 version = match.group(1) |
43 break | 52 break |
44 | 53 |
45 self.write(version) | 54 self.write(version) |
46 | 55 |
47 | 56 |
| 57 class UpdateHandler(web.RequestHandler): |
| 58 |
| 59 def post(self): |
| 60 fileinfo = self.request.files['file'][0] |
| 61 |
| 62 match = VERSION_PATTERN.match(fileinfo['filename']) |
| 63 if match is None: |
| 64 self.write('Filename must contain MD5 hash.') |
| 65 self.set_status(400) |
| 66 return |
| 67 |
| 68 content = fileinfo['body'] |
| 69 md5sum = hashlib.md5(content).hexdigest() |
| 70 if md5sum != match.group(1): |
| 71 self.write('File hash does not match.') |
| 72 self.set_status(400) |
| 73 return |
| 74 |
| 75 filename = 'usb_gadget-{}.zip'.format(md5sum) |
| 76 with open(filename, 'wb') as f: |
| 77 f.write(content) |
| 78 |
| 79 args = ['/usr/bin/python', filename, |
| 80 '--interface', interface, |
| 81 '--port', str(port), |
| 82 '--hardware', hardware] |
| 83 if claimed_by is not None: |
| 84 args.extend(['--start-claimed', claimed_by]) |
| 85 |
| 86 print 'Reloading with version {}...'.format(md5sum) |
| 87 |
| 88 global http_server |
| 89 if chip.IsConfigured(): |
| 90 chip.Destroy() |
| 91 http_server.stop() |
| 92 |
| 93 child = subprocess.Popen(args, close_fds=True) |
| 94 |
| 95 while True: |
| 96 child.poll() |
| 97 if child.returncode is not None: |
| 98 self.write('New package exited with error {}.' |
| 99 .format(child.returncode)) |
| 100 self.set_status(500) |
| 101 |
| 102 http_server = httpserver.HTTPServer(app) |
| 103 http_server.listen(port) |
| 104 SwitchGadget(gadget) |
| 105 return |
| 106 |
| 107 try: |
| 108 f = urllib2.urlopen('http://{}/version'.format(address)) |
| 109 if f.getcode() == 200: |
| 110 # Update complete, wait 1 second to make sure buffers are flushed. |
| 111 io_loop = ioloop.IOLoop.instance() |
| 112 io_loop.add_timeout(datetime.timedelta(seconds=1), io_loop.stop) |
| 113 return |
| 114 except urllib2.URLError: |
| 115 pass |
| 116 time.sleep(0.1) |
| 117 |
| 118 |
48 class ClaimHandler(web.RequestHandler): | 119 class ClaimHandler(web.RequestHandler): |
49 | 120 |
50 def post(self): | 121 def post(self): |
51 global claimed_by | 122 global claimed_by |
52 | 123 |
53 if claimed_by is None: | 124 if claimed_by is None: |
54 claimed_by = self.get_argument('session_id') | 125 claimed_by = self.get_argument('session_id') |
55 else: | 126 else: |
56 self.write('Device is already claimed by "{}".'.format(claimed_by)) | 127 self.write('Device is already claimed by "{}".'.format(claimed_by)) |
57 self.set_status(403) | 128 self.set_status(403) |
(...skipping 23 matching lines...) Expand all Loading... |
81 | 152 |
82 class ReconnectHandler(web.RequestHandler): | 153 class ReconnectHandler(web.RequestHandler): |
83 | 154 |
84 def post(self): | 155 def post(self): |
85 if not chip.IsConfigured(): | 156 if not chip.IsConfigured(): |
86 chip.Create(gadget) | 157 chip.Create(gadget) |
87 | 158 |
88 | 159 |
89 app = web.Application([ | 160 app = web.Application([ |
90 (r'/version', VersionHandler), | 161 (r'/version', VersionHandler), |
| 162 (r'/update', UpdateHandler), |
91 (r'/claim', ClaimHandler), | 163 (r'/claim', ClaimHandler), |
92 (r'/unclaim', UnclaimHandler), | 164 (r'/unclaim', UnclaimHandler), |
93 (r'/unconfigure', UnconfigureHandler), | 165 (r'/unconfigure', UnconfigureHandler), |
94 (r'/disconnect', DisconnectHandler), | 166 (r'/disconnect', DisconnectHandler), |
95 (r'/reconnect', ReconnectHandler), | 167 (r'/reconnect', ReconnectHandler), |
96 ]) | 168 ]) |
97 | 169 |
98 http_server = httpserver.HTTPServer(app) | 170 http_server = httpserver.HTTPServer(app) |
OLD | NEW |