text: """Broker application module."""
  
  import os
  import os.path
  import logging
  import json
  import datetime
  import asyncio
  
  from collections import defaultdict
  
  import tornado
  import tornado.platform.asyncio
  from tornado.options import options
  from tornado import web
  
  from aiocoap import GET
  
  from coap import CoapServer, coap_request, COAP_METHOD
  
  logger = logging.getLogger("otaserver")
  
  
  def _path_from_publish_id(publish_id):
      _path = publish_id.replace('/', '_').replace('\\', '_')
      return _path
  
  
  def _get_versions_from_path(path):
      files = os.listdir(path)
      versions = defaultdict(dict)
      for file in files:
          version = file.split('.')[-2]
          if version == 'latest':
              continue
          if version == 'riot':
              version = file.split('.')[-3]
          if 'riot.suit' in file:
              versions[version]['manifest'] = file
          if 'slot0' in file:
              versions[version]['slot0'] = file
          if 'slot1' in file:
              versions[version]['slot1'] = file
      return versions
  
  
  def _get_applications(path):
      applications = []
      for d in os.listdir(path):
          board, name = d.split('_', 1)
          applications.append(
              { 'id': d,
                'name': name,
                'board': board,
                'count':int((len(os.listdir(os.path.join(path, d))) - 1) / 3),
                'versions': _get_versions_from_path(os.path.join(path, d))
              })
      return applications
  
  
  class OTAServerMainHandler(web.RequestHandler):
      """Web application handler for web page."""
  
      def get(self):
          logger.debug("Rendering SUIT updates web page")
          applications = _get_applications(options.upload_path)
          self.render("otaserver.html",
                      favicon=os.path.join("assets", "favicon.ico"),
                      title="SUIT Update Server",
                      applications=applications,
                      host=options.http_host,
                      port=options.http_port,
                      root_url=options.root_url)
  
  
  class OTAServerRemoveHandler(tornado.web.RequestHandler):
      """Handler for removing an existing version."""
  
      async def post(self):
          """Handle request for removing an existing version."""
          request = json.loads(self.request.body.decode())
          logger.debug("Removing version %s in application %s",
                       request['version'], request['publish_id'])
          for publish_id in os.listdir(options.upload_path):
              if publish_id == request['publish_id']:
                  for filename in os.listdir(
                          os.path.join(options.upload_path, publish_id)):
                      if str(request['version']) not in filename:
                          continue
                      logger.debug("Removing file %s", filename)
                      file = os.path.join(
                          options.upload_path, publish_id, filename)
                      if os.path.exists(file):
                          os.remove(file)
  
  
  class OTAServerCoapUrlHandler(web.RequestHandler):
      """Web application handler for getting the CoAP url."""
  
      async def get(self):
          _coap_uri = 'coap://{}:{}'.format(options.coap_host, options.coap_port)
          _publish_id = self.request.path.split('/')[-1]
          _coap_url = '{}/{}'.format(_coap_uri, _publish_id)
          logger.debug("Sending CoAP server url: %s", _coap_url)
          self.write(_coap_url)
  
  
  class OTAServerNotify(tornado.web.RequestHandler):
      """Handler for notifying an update to a list of devices."""
  
      async def post(self):
          """Handle notification of an available update."""
          version = self.request.body_arguments['version'][0].decode()
          publish_id = self.request.body_arguments['publish_id'][0].decode()
          publish_path = _path_from_publish_id(publish_id)
  
          _store_path = os.path.join(self.application.upload_path, publish_id)
          base_filename = os.listdir(_store_path)[0].split('-')[0]
  
          manifest_url = os.path.join(
              publish_path,
              '{}-riot.suit_signed.{}.bin'.format(base_filename, version))
  
          devices_urls = self.request.body_arguments['urls'][0].decode()
          logger.debug('Notifying devices %s of an update of %s',
                       devices_urls, publish_id)
  
          for url in devices_urls.split(','):
              logger.debug('Notifying an update to %s', url)
              payload = '{}://{}:{}/{}'.format(COAP_METHOD, options.coap_host,
                                               options.coap_port, manifest_url)
              logger.debug('Manifest url is %s', payload)
              notify_url = '{}/{}'.format(url, options.notify_url)
              logger.debug('Send update notification at %s', url)
              await coap_request(notify_url, payload=payload.encode())
  
  
  class OTAServerPublishHandler(tornado.web.RequestHandler):
      """Handler for storing published firmwares."""
  
      def _store(self, store_url, data):
          _store_path = os.path.join(self.application.upload_path, store_url)
          if not os.path.exists(_store_path):
              os.makedirs(_store_path)
  
          # Store each data in separate files
          for name, content in data.items():
              _path = os.path.join(_store_path, name)
              logger.debug('Storing file %s', _path)
              with open(_path, 'wb') as f:
                  f.write(content)
              # Hack to determine if the file is a manifest and copy as latest
              _path_split = _path.split('.')
              if 'suit' == _path_split[-3] or 'suit_signed' == _path_split[-3]:
                  _path_split[-2] = 'latest'
              _path = '.'.join(_path_split)
              with open(_path, 'wb') as f:
                  f.write(content)
  
      async def post(self):
          """Handle publication of an update."""
          # Verify the request contains the required files
          files = self.request.files
          msg = None
          if len(files) == 0:
              msg = "No file found in request"
          if msg is not None:
              self.set_status(400, msg)
              self.finish(msg)
              return
  
          # Load the content of the files from the request
          update_data = {}
          for file in files:
              filename = os.path.basename(file)
              update_data[filename] = files[file][0]['body']
  
          # Get publish identifier
          publish_id = self.request.body_arguments['publish_id'][0].decode()
          # Cleanup the path
          store_path = _path_from_publish_id(publish_id)
          logger.debug('Storing %s update', publish_id)
  
          # Store the data and create the corresponding CoAP resources
          self._store(store_path, update_data)
          if options.with_coap_server:
              self.application.coap_server.add_resources(store_path)
  
  
  class OTAServerApplication(web.Application):
      """Tornado based web application providing the OTA server."""
  
      def __init__(self):
          if options.debug:
              logger.setLevel(logging.DEBUG)
  
          handlers = [
              (r"/", OTAServerMainHandler),
              (r"/publish", OTAServerPublishHandler),
              (r"/remove", OTAServerRemoveHandler),
              (r"/notify", OTAServerNotify),
              (r"/coap/url/.*", OTAServerCoapUrlHandler),
          ]
  
          if options.root_url:
              for i, handler in enumerate(handlers):
                  handlers[i] = (
                      handler[0].replace(
                          '/', '/{}/'.format(options.root_url), 1),
                      handler[1])
  
          settings = dict(debug=True,
                          static_path=options.static_path,
                          template_path=options.static_path,)
  
          self.upload_path = options.upload_path
          if options.with_coap_server:
              self.coap_server = CoapServer(self.upload_path,
                                            port=options.coap_port)
  
          super().__init__(handlers, **settings)
          logger.info('Application started, listening on port {}'
                      .format(options.http_port))
  