Home | History | Annotate | Download | only in usb_gadget
      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.
      4 
      5 """WSGI application to manage a USB gadget.
      6 """
      7 
      8 import datetime
      9 import hashlib
     10 import re
     11 import subprocess
     12 import sys
     13 import time
     14 import urllib2
     15 
     16 from tornado import httpserver
     17 from tornado import ioloop
     18 from tornado import web
     19 
     20 import default_gadget
     21 
     22 VERSION_PATTERN = re.compile(r'.*usb_gadget-([a-z0-9]{32})\.zip')
     23 
     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
     32 
     33 
     34 def SwitchGadget(new_gadget):
     35   if chip.IsConfigured():
     36     chip.Destroy()
     37 
     38   global gadget
     39   gadget = new_gadget
     40   gadget.AddStringDescriptor(3, address)
     41   chip.Create(gadget)
     42 
     43 
     44 class VersionHandler(web.RequestHandler):
     45 
     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
     53 
     54     self.write(version)
     55 
     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 
    119 class ClaimHandler(web.RequestHandler):
    120 
    121   def post(self):
    122     global claimed_by
    123 
    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)
    129 
    130 
    131 class UnclaimHandler(web.RequestHandler):
    132 
    133   def post(self):
    134     global claimed_by
    135     claimed_by = None
    136     if gadget != default:
    137       SwitchGadget(default)
    138 
    139 
    140 class UnconfigureHandler(web.RequestHandler):
    141 
    142   def post(self):
    143     SwitchGadget(default)
    144 
    145 
    146 class DisconnectHandler(web.RequestHandler):
    147 
    148   def post(self):
    149     if chip.IsConfigured():
    150       chip.Destroy()
    151 
    152 
    153 class ReconnectHandler(web.RequestHandler):
    154 
    155   def post(self):
    156     if not chip.IsConfigured():
    157       chip.Create(gadget)
    158 
    159 
    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),
    168 ])
    169 
    170 http_server = httpserver.HTTPServer(app)
    171