15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved. 25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)# found in the LICENSE file. 45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)"""WSGI application to manage a USB gadget. 65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)""" 75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import datetime 95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import hashlib 105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import re 115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import subprocess 125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import sys 135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import time 145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import urllib2 155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from tornado import httpserver 175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from tornado import ioloop 185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from tornado import web 195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)import default_gadget 215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)VERSION_PATTERN = re.compile(r'.*usb_gadget-([a-z0-9]{32})\.zip') 235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)address = None 255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)chip = None 265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)claimed_by = None 275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)default = default_gadget.DefaultGadget() 285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)gadget = None 295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)hardware = None 305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)interface = None 315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)port = None 325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)def SwitchGadget(new_gadget): 355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if chip.IsConfigured(): 365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) chip.Destroy() 375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) global gadget 395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) gadget = new_gadget 405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) gadget.AddStringDescriptor(3, address) 415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) chip.Create(gadget) 425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class VersionHandler(web.RequestHandler): 455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def get(self): 475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) version = 'unpackaged' 485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) for path in sys.path: 495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) match = VERSION_PATTERN.match(path) 505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if match: 515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) version = match.group(1) 525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) break 535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.write(version) 555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class UpdateHandler(web.RequestHandler): 585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def post(self): 605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) fileinfo = self.request.files['file'][0] 615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) match = VERSION_PATTERN.match(fileinfo['filename']) 635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if match is None: 645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.write('Filename must contain MD5 hash.') 655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.set_status(400) 665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return 675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) content = fileinfo['body'] 695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) md5sum = hashlib.md5(content).hexdigest() 705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if md5sum != match.group(1): 715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.write('File hash does not match.') 725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.set_status(400) 735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return 745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) filename = 'usb_gadget-{}.zip'.format(md5sum) 765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) with open(filename, 'wb') as f: 775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) f.write(content) 785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) args = ['/usr/bin/python', filename, 805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) '--interface', interface, 815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) '--port', str(port), 825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) '--hardware', hardware] 835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if claimed_by is not None: 845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) args.extend(['--start-claimed', claimed_by]) 855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) print 'Reloading with version {}...'.format(md5sum) 875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) global http_server 895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if chip.IsConfigured(): 905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) chip.Destroy() 915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) http_server.stop() 925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) child = subprocess.Popen(args, close_fds=True) 945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) while True: 965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) child.poll() 975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if child.returncode is not None: 985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.write('New package exited with error {}.' 995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) .format(child.returncode)) 1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.set_status(500) 1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) http_server = httpserver.HTTPServer(app) 1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) http_server.listen(port) 1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) SwitchGadget(gadget) 1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return 1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) try: 1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) f = urllib2.urlopen('http://{}/version'.format(address)) 1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if f.getcode() == 200: 1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) # Update complete, wait 1 second to make sure buffers are flushed. 1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) io_loop = ioloop.IOLoop.instance() 1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) io_loop.add_timeout(datetime.timedelta(seconds=1), io_loop.stop) 1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return 1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) except urllib2.URLError: 1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) pass 1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) time.sleep(0.1) 1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class ClaimHandler(web.RequestHandler): 1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def post(self): 1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) global claimed_by 1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if claimed_by is None: 1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) claimed_by = self.get_argument('session_id') 1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) else: 1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.write('Device is already claimed by "{}".'.format(claimed_by)) 1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) self.set_status(403) 1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class UnclaimHandler(web.RequestHandler): 1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def post(self): 1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) global claimed_by 1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) claimed_by = None 1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if gadget != default: 1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) SwitchGadget(default) 1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class UnconfigureHandler(web.RequestHandler): 1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def post(self): 1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) SwitchGadget(default) 1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class DisconnectHandler(web.RequestHandler): 1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def post(self): 1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if chip.IsConfigured(): 1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) chip.Destroy() 1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class ReconnectHandler(web.RequestHandler): 1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) def post(self): 1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if not chip.IsConfigured(): 1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) chip.Create(gadget) 1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)app = web.Application([ 1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/version', VersionHandler), 1625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/update', UpdateHandler), 1635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/claim', ClaimHandler), 1645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/unclaim', UnclaimHandler), 1655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/unconfigure', UnconfigureHandler), 1665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/disconnect', DisconnectHandler), 1675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) (r'/reconnect', ReconnectHandler), 1685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)]) 1695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)http_server = httpserver.HTTPServer(app) 171