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