server.py 3.8 KB

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