Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / tools / usb_gadget / server.py
blobe5aa8b8e5d46d71aa80e9e76133bced15233b16d
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
3 # found in the LICENSE file.
5 """WSGI application to manage a USB gadget.
6 """
8 import datetime
9 import hashlib
10 import re
11 import subprocess
12 import sys
13 import time
14 import urllib2
16 from tornado import httpserver
17 from tornado import ioloop
18 from tornado import web
20 import default_gadget
22 VERSION_PATTERN = re.compile(r'.*usb_gadget-([a-z0-9]{32})\.zip')
24 address = None
25 chip = None
26 claimed_by = None
27 default = default_gadget.DefaultGadget()
28 gadget = None
29 hardware = None
30 interface = None
31 port = None
34 def SwitchGadget(new_gadget):
35 if chip.IsConfigured():
36 chip.Destroy()
38 global gadget
39 gadget = new_gadget
40 gadget.AddStringDescriptor(3, address)
41 chip.Create(gadget)
44 class VersionHandler(web.RequestHandler):
46 def get(self):
47 version = 'unpackaged'
48 for path in sys.path:
49 match = VERSION_PATTERN.match(path)
50 if match:
51 version = match.group(1)
52 break
54 self.write(version)
57 class UpdateHandler(web.RequestHandler):
59 def post(self):
60 fileinfo = self.request.files['file'][0]
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
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
75 filename = 'usb_gadget-{}.zip'.format(md5sum)
76 with open(filename, 'wb') as f:
77 f.write(content)
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])
86 print 'Reloading with version {}...'.format(md5sum)
88 global http_server
89 if chip.IsConfigured():
90 chip.Destroy()
91 http_server.stop()
93 child = subprocess.Popen(args, close_fds=True)
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)
102 http_server = httpserver.HTTPServer(app)
103 http_server.listen(port)
104 SwitchGadget(gadget)
105 return
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)
119 class ClaimHandler(web.RequestHandler):
121 def post(self):
122 global claimed_by
124 if claimed_by is None:
125 claimed_by = self.get_argument('session_id')
126 else:
127 self.write('Device is already claimed by "{}".'.format(claimed_by))
128 self.set_status(403)
131 class UnclaimHandler(web.RequestHandler):
133 def post(self):
134 global claimed_by
135 claimed_by = None
136 if gadget != default:
137 SwitchGadget(default)
140 class UnconfigureHandler(web.RequestHandler):
142 def post(self):
143 SwitchGadget(default)
146 class DisconnectHandler(web.RequestHandler):
148 def post(self):
149 if chip.IsConfigured():
150 chip.Destroy()
153 class ReconnectHandler(web.RequestHandler):
155 def post(self):
156 if not chip.IsConfigured():
157 chip.Create(gadget)
160 app = web.Application([
161 (r'/version', VersionHandler),
162 (r'/update', UpdateHandler),
163 (r'/claim', ClaimHandler),
164 (r'/unclaim', UnclaimHandler),
165 (r'/unconfigure', UnconfigureHandler),
166 (r'/disconnect', DisconnectHandler),
167 (r'/reconnect', ReconnectHandler),
170 http_server = httpserver.HTTPServer(app)