|  |  |  | #!/usr/bin/env python3
 | 
					
						
							|  |  |  | import base64
 | 
					
						
							|  |  |  | import bz2
 | 
					
						
							|  |  |  | import hashlib
 | 
					
						
							|  |  |  | import io
 | 
					
						
							|  |  |  | import json
 | 
					
						
							|  |  |  | import os
 | 
					
						
							|  |  |  | import queue
 | 
					
						
							|  |  |  | import random
 | 
					
						
							|  |  |  | import select
 | 
					
						
							|  |  |  | import socket
 | 
					
						
							|  |  |  | import subprocess
 | 
					
						
							|  |  |  | import sys
 | 
					
						
							|  |  |  | import tempfile
 | 
					
						
							|  |  |  | import threading
 | 
					
						
							|  |  |  | import time
 | 
					
						
							|  |  |  | from collections import namedtuple
 | 
					
						
							|  |  |  | from datetime import datetime
 | 
					
						
							|  |  |  | from functools import partial
 | 
					
						
							|  |  |  | from typing import Any, Dict
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import requests
 | 
					
						
							|  |  |  | from jsonrpc import JSONRPCResponseManager, dispatcher
 | 
					
						
							|  |  |  | from websocket import (ABNF, WebSocketException, WebSocketTimeoutException,
 | 
					
						
							|  |  |  |                        create_connection)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import cereal.messaging as messaging
 | 
					
						
							|  |  |  | from cereal import log
 | 
					
						
							|  |  |  | from cereal.services import service_list
 | 
					
						
							|  |  |  | from common.api import Api
 | 
					
						
							|  |  |  | from common.basedir import PERSIST
 | 
					
						
							|  |  |  | from common.file_helpers import CallbackReader
 | 
					
						
							|  |  |  | from common.params import Params
 | 
					
						
							|  |  |  | from common.realtime import sec_since_boot, set_core_affinity
 | 
					
						
							|  |  |  | from system.hardware import HARDWARE, PC, AGNOS
 | 
					
						
							|  |  |  | from selfdrive.loggerd.config import ROOT
 | 
					
						
							|  |  |  | from selfdrive.loggerd.xattr_cache import getxattr, setxattr
 | 
					
						
							|  |  |  | from selfdrive.statsd import STATS_DIR
 | 
					
						
							|  |  |  | from system.swaglog import SWAGLOG_DIR, cloudlog
 | 
					
						
							|  |  |  | from system.version import get_commit, get_origin, get_short_branch, get_version
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai')
 | 
					
						
							|  |  |  | HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
 | 
					
						
							|  |  |  | LOCAL_PORT_WHITELIST = {8022}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | LOG_ATTR_NAME = 'user.upload'
 | 
					
						
							|  |  |  | LOG_ATTR_VALUE_MAX_UNIX_TIME = int.to_bytes(2147483647, 4, sys.byteorder)
 | 
					
						
							|  |  |  | RECONNECT_TIMEOUT_S = 70
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RETRY_DELAY = 10  # seconds
 | 
					
						
							|  |  |  | MAX_RETRY_COUNT = 30  # Try for at most 5 minutes if upload fails immediately
 | 
					
						
							|  |  |  | MAX_AGE = 31 * 24 * 3600  # seconds
 | 
					
						
							|  |  |  | WS_FRAME_SIZE = 4096
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | NetworkType = log.DeviceState.NetworkType
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | dispatcher["echo"] = lambda s: s
 | 
					
						
							|  |  |  | recv_queue: Any = queue.Queue()
 | 
					
						
							|  |  |  | send_queue: Any = queue.Queue()
 | 
					
						
							|  |  |  | upload_queue: Any = queue.Queue()
 | 
					
						
							|  |  |  | low_priority_send_queue: Any = queue.Queue()
 | 
					
						
							|  |  |  | log_recv_queue: Any = queue.Queue()
 | 
					
						
							|  |  |  | cancelled_uploads: Any = set()
 | 
					
						
							|  |  |  | UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id', 'retry_count', 'current', 'progress', 'allow_cellular'], defaults=(0, False, 0, False))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | cur_upload_items: Dict[int, Any] = {}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def strip_bz2_extension(fn):
 | 
					
						
							|  |  |  |   if fn.endswith('.bz2'):
 | 
					
						
							|  |  |  |     return fn[:-4]
 | 
					
						
							|  |  |  |   return fn
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class AbortTransferException(Exception):
 | 
					
						
							|  |  |  |   pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class UploadQueueCache():
 | 
					
						
							|  |  |  |   params = Params()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def initialize(upload_queue):
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue")
 | 
					
						
							|  |  |  |       if upload_queue_json is not None:
 | 
					
						
							|  |  |  |         for item in json.loads(upload_queue_json):
 | 
					
						
							|  |  |  |           upload_queue.put(UploadItem(**item))
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athena.UploadQueueCache.initialize.exception")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod
 | 
					
						
							|  |  |  |   def cache(upload_queue):
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       items = [i._asdict() for i in upload_queue.queue if i.id not in cancelled_uploads]
 | 
					
						
							|  |  |  |       UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items))
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athena.UploadQueueCache.cache.exception")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def handle_long_poll(ws):
 | 
					
						
							|  |  |  |   end_event = threading.Event()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   threads = [
 | 
					
						
							|  |  |  |     threading.Thread(target=ws_recv, args=(ws, end_event), name='ws_recv'),
 | 
					
						
							|  |  |  |     threading.Thread(target=ws_send, args=(ws, end_event), name='ws_send'),
 | 
					
						
							|  |  |  |     threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'),
 | 
					
						
							|  |  |  |     threading.Thread(target=log_handler, args=(end_event,), name='log_handler'),
 | 
					
						
							|  |  |  |     threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'),
 | 
					
						
							|  |  |  |   ] + [
 | 
					
						
							|  |  |  |     threading.Thread(target=jsonrpc_handler, args=(end_event,), name=f'worker_{x}')
 | 
					
						
							|  |  |  |     for x in range(HANDLER_THREADS)
 | 
					
						
							|  |  |  |   ]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for thread in threads:
 | 
					
						
							|  |  |  |     thread.start()
 | 
					
						
							|  |  |  |   try:
 | 
					
						
							|  |  |  |     while not end_event.is_set():
 | 
					
						
							|  |  |  |       time.sleep(0.1)
 | 
					
						
							|  |  |  |   except (KeyboardInterrupt, SystemExit):
 | 
					
						
							|  |  |  |     end_event.set()
 | 
					
						
							|  |  |  |     raise
 | 
					
						
							|  |  |  |   finally:
 | 
					
						
							|  |  |  |     for thread in threads:
 | 
					
						
							|  |  |  |       cloudlog.debug(f"athena.joining {thread.name}")
 | 
					
						
							|  |  |  |       thread.join()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def jsonrpc_handler(end_event):
 | 
					
						
							|  |  |  |   dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event)
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       data = recv_queue.get(timeout=1)
 | 
					
						
							|  |  |  |       if "method" in data:
 | 
					
						
							|  |  |  |         cloudlog.debug(f"athena.jsonrpc_handler.call_method {data}")
 | 
					
						
							|  |  |  |         response = JSONRPCResponseManager.handle(data, dispatcher)
 | 
					
						
							|  |  |  |         send_queue.put_nowait(response.json)
 | 
					
						
							|  |  |  |       elif "id" in data and ("result" in data or "error" in data):
 | 
					
						
							|  |  |  |         log_recv_queue.put_nowait(data)
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         raise Exception("not a valid request or response")
 | 
					
						
							|  |  |  |     except queue.Empty:
 | 
					
						
							|  |  |  |       pass
 | 
					
						
							|  |  |  |     except Exception as e:
 | 
					
						
							|  |  |  |       cloudlog.exception("athena jsonrpc handler failed")
 | 
					
						
							|  |  |  |       send_queue.put_nowait(json.dumps({"error": str(e)}))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = True) -> None:
 | 
					
						
							|  |  |  |   if cur_upload_items[tid].retry_count < MAX_RETRY_COUNT:
 | 
					
						
							|  |  |  |     item = cur_upload_items[tid]
 | 
					
						
							|  |  |  |     new_retry_count = item.retry_count + 1 if increase_count else item.retry_count
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item = item._replace(
 | 
					
						
							|  |  |  |       retry_count=new_retry_count,
 | 
					
						
							|  |  |  |       progress=0,
 | 
					
						
							|  |  |  |       current=False
 | 
					
						
							|  |  |  |     )
 | 
					
						
							|  |  |  |     upload_queue.put_nowait(item)
 | 
					
						
							|  |  |  |     UploadQueueCache.cache(upload_queue)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cur_upload_items[tid] = None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for _ in range(RETRY_DELAY):
 | 
					
						
							|  |  |  |       time.sleep(1)
 | 
					
						
							|  |  |  |       if end_event.is_set():
 | 
					
						
							|  |  |  |         break
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def upload_handler(end_event: threading.Event) -> None:
 | 
					
						
							|  |  |  |   sm = messaging.SubMaster(['deviceState'])
 | 
					
						
							|  |  |  |   tid = threading.get_ident()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     cur_upload_items[tid] = None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       cur_upload_items[tid] = upload_queue.get(timeout=1)._replace(current=True)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if cur_upload_items[tid].id in cancelled_uploads:
 | 
					
						
							|  |  |  |         cancelled_uploads.remove(cur_upload_items[tid].id)
 | 
					
						
							|  |  |  |         continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Remove item if too old
 | 
					
						
							|  |  |  |       age = datetime.now() - datetime.fromtimestamp(cur_upload_items[tid].created_at / 1000)
 | 
					
						
							|  |  |  |       if age.total_seconds() > MAX_AGE:
 | 
					
						
							|  |  |  |         cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True)
 | 
					
						
							|  |  |  |         continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Check if uploading over metered connection is allowed
 | 
					
						
							|  |  |  |       sm.update(0)
 | 
					
						
							|  |  |  |       metered = sm['deviceState'].networkMetered
 | 
					
						
							|  |  |  |       network_type = sm['deviceState'].networkType.raw
 | 
					
						
							|  |  |  |       if metered and (not cur_upload_items[tid].allow_cellular):
 | 
					
						
							|  |  |  |         retry_upload(tid, end_event, False)
 | 
					
						
							|  |  |  |         continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try:
 | 
					
						
							|  |  |  |         def cb(sz, cur):
 | 
					
						
							|  |  |  |           # Abort transfer if connection changed to metered after starting upload
 | 
					
						
							|  |  |  |           sm.update(0)
 | 
					
						
							|  |  |  |           metered = sm['deviceState'].networkMetered
 | 
					
						
							|  |  |  |           if metered and (not cur_upload_items[tid].allow_cellular):
 | 
					
						
							|  |  |  |             raise AbortTransferException
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         fn = cur_upload_items[tid].path
 | 
					
						
							|  |  |  |         try:
 | 
					
						
							|  |  |  |           sz = os.path.getsize(fn)
 | 
					
						
							|  |  |  |         except OSError:
 | 
					
						
							|  |  |  |           sz = -1
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=cur_upload_items[tid].retry_count)
 | 
					
						
							|  |  |  |         response = _do_upload(cur_upload_items[tid], cb)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if response.status_code not in (200, 201, 401, 403, 412):
 | 
					
						
							|  |  |  |           cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
 | 
					
						
							|  |  |  |           retry_upload(tid, end_event)
 | 
					
						
							|  |  |  |         else:
 | 
					
						
							|  |  |  |           cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type, metered=metered)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         UploadQueueCache.cache(upload_queue)
 | 
					
						
							|  |  |  |       except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError):
 | 
					
						
							|  |  |  |         cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type, metered=metered)
 | 
					
						
							|  |  |  |         retry_upload(tid, end_event)
 | 
					
						
							|  |  |  |       except AbortTransferException:
 | 
					
						
							|  |  |  |         cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type, metered=metered)
 | 
					
						
							|  |  |  |         retry_upload(tid, end_event, False)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     except queue.Empty:
 | 
					
						
							|  |  |  |       pass
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athena.upload_handler.exception")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _do_upload(upload_item, callback=None):
 | 
					
						
							|  |  |  |   path = upload_item.path
 | 
					
						
							|  |  |  |   compress = False
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # If file does not exist, but does exist without the .bz2 extension we will compress on the fly
 | 
					
						
							|  |  |  |   if not os.path.exists(path) and os.path.exists(strip_bz2_extension(path)):
 | 
					
						
							|  |  |  |     path = strip_bz2_extension(path)
 | 
					
						
							|  |  |  |     compress = True
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   with open(path, "rb") as f:
 | 
					
						
							|  |  |  |     if compress:
 | 
					
						
							|  |  |  |       cloudlog.event("athena.upload_handler.compress", fn=path, fn_orig=upload_item.path)
 | 
					
						
							|  |  |  |       data = bz2.compress(f.read())
 | 
					
						
							|  |  |  |       size = len(data)
 | 
					
						
							|  |  |  |       data = io.BytesIO(data)
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |       size = os.fstat(f.fileno()).st_size
 | 
					
						
							|  |  |  |       data = f
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if callback:
 | 
					
						
							|  |  |  |       data = CallbackReader(data, callback, size)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return requests.put(upload_item.url,
 | 
					
						
							|  |  |  |                         data=data,
 | 
					
						
							|  |  |  |                         headers={**upload_item.headers, 'Content-Length': str(size)},
 | 
					
						
							|  |  |  |                         timeout=30)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # security: user should be able to request any message from their car
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getMessage(service=None, timeout=1000):
 | 
					
						
							|  |  |  |   if service is None or service not in service_list:
 | 
					
						
							|  |  |  |     raise Exception("invalid service")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   socket = messaging.sub_sock(service, timeout=timeout)
 | 
					
						
							|  |  |  |   ret = messaging.recv_one(socket)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if ret is None:
 | 
					
						
							|  |  |  |     raise TimeoutError
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ret.to_dict()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getVersion() -> Dict[str, str]:
 | 
					
						
							|  |  |  |   return {
 | 
					
						
							|  |  |  |     "version": get_version(),
 | 
					
						
							|  |  |  |     "remote": get_origin(''),
 | 
					
						
							|  |  |  |     "branch": get_short_branch(''),
 | 
					
						
							|  |  |  |     "commit": get_commit(default=''),
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=None):
 | 
					
						
							|  |  |  |   destination = {
 | 
					
						
							|  |  |  |     "latitude": latitude,
 | 
					
						
							|  |  |  |     "longitude": longitude,
 | 
					
						
							|  |  |  |     "place_name": place_name,
 | 
					
						
							|  |  |  |     "place_details": place_details,
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   Params().put("NavDestination", json.dumps(destination))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return {"success": 1}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def scan_dir(path, prefix):
 | 
					
						
							|  |  |  |   files = list()
 | 
					
						
							|  |  |  |   # only walk directories that match the prefix
 | 
					
						
							|  |  |  |   # (glob and friends traverse entire dir tree)
 | 
					
						
							|  |  |  |   with os.scandir(path) as i:
 | 
					
						
							|  |  |  |     for e in i:
 | 
					
						
							|  |  |  |       rel_path = os.path.relpath(e.path, ROOT)
 | 
					
						
							|  |  |  |       if e.is_dir(follow_symlinks=False):
 | 
					
						
							|  |  |  |         # add trailing slash
 | 
					
						
							|  |  |  |         rel_path = os.path.join(rel_path, '')
 | 
					
						
							|  |  |  |         # if prefix is a partial dir name, current dir will start with prefix
 | 
					
						
							|  |  |  |         # if prefix is a partial file name, prefix with start with dir name
 | 
					
						
							|  |  |  |         if rel_path.startswith(prefix) or prefix.startswith(rel_path):
 | 
					
						
							|  |  |  |           files.extend(scan_dir(e.path, prefix))
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         if rel_path.startswith(prefix):
 | 
					
						
							|  |  |  |           files.append(rel_path)
 | 
					
						
							|  |  |  |   return files
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def listDataDirectory(prefix=''):
 | 
					
						
							|  |  |  |   return scan_dir(ROOT, prefix)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def reboot():
 | 
					
						
							|  |  |  |   sock = messaging.sub_sock("deviceState", timeout=1000)
 | 
					
						
							|  |  |  |   ret = messaging.recv_one(sock)
 | 
					
						
							|  |  |  |   if ret is None or ret.deviceState.started:
 | 
					
						
							|  |  |  |     raise Exception("Reboot unavailable")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def do_reboot():
 | 
					
						
							|  |  |  |     time.sleep(2)
 | 
					
						
							|  |  |  |     HARDWARE.reboot()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   threading.Thread(target=do_reboot).start()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return {"success": 1}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def uploadFileToUrl(fn, url, headers):
 | 
					
						
							|  |  |  |   return uploadFilesToUrls([{
 | 
					
						
							|  |  |  |     "fn": fn,
 | 
					
						
							|  |  |  |     "url": url,
 | 
					
						
							|  |  |  |     "headers": headers,
 | 
					
						
							|  |  |  |   }])
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def uploadFilesToUrls(files_data):
 | 
					
						
							|  |  |  |   items = []
 | 
					
						
							|  |  |  |   failed = []
 | 
					
						
							|  |  |  |   for file in files_data:
 | 
					
						
							|  |  |  |     fn = file.get('fn', '')
 | 
					
						
							|  |  |  |     if len(fn) == 0 or fn[0] == '/' or '..' in fn or 'url' not in file:
 | 
					
						
							|  |  |  |       failed.append(fn)
 | 
					
						
							|  |  |  |       continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     path = os.path.join(ROOT, fn)
 | 
					
						
							|  |  |  |     if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)):
 | 
					
						
							|  |  |  |       failed.append(fn)
 | 
					
						
							|  |  |  |       continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Skip item if already in queue
 | 
					
						
							|  |  |  |     url = file['url'].split('?')[0]
 | 
					
						
							|  |  |  |     if any(url == item['url'].split('?')[0] for item in listUploadQueue()):
 | 
					
						
							|  |  |  |       continue
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item = UploadItem(
 | 
					
						
							|  |  |  |       path=path,
 | 
					
						
							|  |  |  |       url=file['url'],
 | 
					
						
							|  |  |  |       headers=file.get('headers', {}),
 | 
					
						
							|  |  |  |       created_at=int(time.time() * 1000),
 | 
					
						
							|  |  |  |       id=None,
 | 
					
						
							|  |  |  |       allow_cellular=file.get('allow_cellular', False),
 | 
					
						
							|  |  |  |     )
 | 
					
						
							|  |  |  |     upload_id = hashlib.sha1(str(item).encode()).hexdigest()
 | 
					
						
							|  |  |  |     item = item._replace(id=upload_id)
 | 
					
						
							|  |  |  |     upload_queue.put_nowait(item)
 | 
					
						
							|  |  |  |     items.append(item._asdict())
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   UploadQueueCache.cache(upload_queue)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   resp = {"enqueued": len(items), "items": items}
 | 
					
						
							|  |  |  |   if failed:
 | 
					
						
							|  |  |  |     resp["failed"] = failed
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return resp
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def listUploadQueue():
 | 
					
						
							|  |  |  |   items = list(upload_queue.queue) + list(cur_upload_items.values())
 | 
					
						
							|  |  |  |   return [i._asdict() for i in items if (i is not None) and (i.id not in cancelled_uploads)]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def cancelUpload(upload_id):
 | 
					
						
							|  |  |  |   if not isinstance(upload_id, list):
 | 
					
						
							|  |  |  |     upload_id = [upload_id]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   uploading_ids = {item.id for item in list(upload_queue.queue)}
 | 
					
						
							|  |  |  |   cancelled_ids = uploading_ids.intersection(upload_id)
 | 
					
						
							|  |  |  |   if len(cancelled_ids) == 0:
 | 
					
						
							|  |  |  |     return 404
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cancelled_uploads.update(cancelled_ids)
 | 
					
						
							|  |  |  |   return {"success": 1}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def primeActivated(activated):
 | 
					
						
							|  |  |  |   return {"success": 1}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def setBandwithLimit(upload_speed_kbps, download_speed_kbps):
 | 
					
						
							|  |  |  |   if not AGNOS:
 | 
					
						
							|  |  |  |     return {"success": 0, "error": "only supported on AGNOS"}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   try:
 | 
					
						
							|  |  |  |     HARDWARE.set_bandwidth_limit(upload_speed_kbps, download_speed_kbps)
 | 
					
						
							|  |  |  |     return {"success": 1}
 | 
					
						
							|  |  |  |   except subprocess.CalledProcessError as e:
 | 
					
						
							|  |  |  |     return {"success": 0, "error": "failed to set limit", "stdout": e.stdout, "stderr": e.stderr}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def startLocalProxy(global_end_event, remote_ws_uri, local_port):
 | 
					
						
							|  |  |  |   try:
 | 
					
						
							|  |  |  |     if local_port not in LOCAL_PORT_WHITELIST:
 | 
					
						
							|  |  |  |       raise Exception("Requested local port not whitelisted")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cloudlog.debug("athena.startLocalProxy.starting")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     dongle_id = Params().get("DongleId").decode('utf8')
 | 
					
						
							|  |  |  |     identity_token = Api(dongle_id).get_token()
 | 
					
						
							|  |  |  |     ws = create_connection(remote_ws_uri,
 | 
					
						
							|  |  |  |                            cookie="jwt=" + identity_token,
 | 
					
						
							|  |  |  |                            enable_multithread=True)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ssock, csock = socket.socketpair()
 | 
					
						
							|  |  |  |     local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
					
						
							|  |  |  |     local_sock.connect(('127.0.0.1', local_port))
 | 
					
						
							|  |  |  |     local_sock.setblocking(False)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     proxy_end_event = threading.Event()
 | 
					
						
							|  |  |  |     threads = [
 | 
					
						
							|  |  |  |       threading.Thread(target=ws_proxy_recv, args=(ws, local_sock, ssock, proxy_end_event, global_end_event)),
 | 
					
						
							|  |  |  |       threading.Thread(target=ws_proxy_send, args=(ws, local_sock, csock, proxy_end_event))
 | 
					
						
							|  |  |  |     ]
 | 
					
						
							|  |  |  |     for thread in threads:
 | 
					
						
							|  |  |  |       thread.start()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cloudlog.debug("athena.startLocalProxy.started")
 | 
					
						
							|  |  |  |     return {"success": 1}
 | 
					
						
							|  |  |  |   except Exception as e:
 | 
					
						
							|  |  |  |     cloudlog.exception("athenad.startLocalProxy.exception")
 | 
					
						
							|  |  |  |     raise e
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getPublicKey():
 | 
					
						
							|  |  |  |   if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'):
 | 
					
						
							|  |  |  |     return None
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   with open(PERSIST + '/comma/id_rsa.pub') as f:
 | 
					
						
							|  |  |  |     return f.read()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getSshAuthorizedKeys():
 | 
					
						
							|  |  |  |   return Params().get("GithubSshKeys", encoding='utf8') or ''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getSimInfo():
 | 
					
						
							|  |  |  |   return HARDWARE.get_sim_info()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getNetworkType():
 | 
					
						
							|  |  |  |   return HARDWARE.get_network_type()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getNetworkMetered():
 | 
					
						
							|  |  |  |   network_type = HARDWARE.get_network_type()
 | 
					
						
							|  |  |  |   return HARDWARE.get_network_metered(network_type)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def getNetworks():
 | 
					
						
							|  |  |  |   return HARDWARE.get_networks()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @dispatcher.add_method
 | 
					
						
							|  |  |  | def takeSnapshot():
 | 
					
						
							|  |  |  |   from system.camerad.snapshot.snapshot import jpeg_write, snapshot
 | 
					
						
							|  |  |  |   ret = snapshot()
 | 
					
						
							|  |  |  |   if ret is not None:
 | 
					
						
							|  |  |  |     def b64jpeg(x):
 | 
					
						
							|  |  |  |       if x is not None:
 | 
					
						
							|  |  |  |         f = io.BytesIO()
 | 
					
						
							|  |  |  |         jpeg_write(f, x)
 | 
					
						
							|  |  |  |         return base64.b64encode(f.getvalue()).decode("utf-8")
 | 
					
						
							|  |  |  |       else:
 | 
					
						
							|  |  |  |         return None
 | 
					
						
							|  |  |  |     return {'jpegBack': b64jpeg(ret[0]),
 | 
					
						
							|  |  |  |             'jpegFront': b64jpeg(ret[1])}
 | 
					
						
							|  |  |  |   else:
 | 
					
						
							|  |  |  |     raise Exception("not available while camerad is started")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_logs_to_send_sorted():
 | 
					
						
							|  |  |  |   # TODO: scan once then use inotify to detect file creation/deletion
 | 
					
						
							|  |  |  |   curr_time = int(time.time())
 | 
					
						
							|  |  |  |   logs = []
 | 
					
						
							|  |  |  |   for log_entry in os.listdir(SWAGLOG_DIR):
 | 
					
						
							|  |  |  |     log_path = os.path.join(SWAGLOG_DIR, log_entry)
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       time_sent = int.from_bytes(getxattr(log_path, LOG_ATTR_NAME), sys.byteorder)
 | 
					
						
							|  |  |  |     except (ValueError, TypeError):
 | 
					
						
							|  |  |  |       time_sent = 0
 | 
					
						
							|  |  |  |     # assume send failed and we lost the response if sent more than one hour ago
 | 
					
						
							|  |  |  |     if not time_sent or curr_time - time_sent > 3600:
 | 
					
						
							|  |  |  |       logs.append(log_entry)
 | 
					
						
							|  |  |  |   # excluding most recent (active) log file
 | 
					
						
							|  |  |  |   return sorted(logs)[:-1]
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def log_handler(end_event):
 | 
					
						
							|  |  |  |   if PC:
 | 
					
						
							|  |  |  |     return
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   log_files = []
 | 
					
						
							|  |  |  |   last_scan = 0
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       curr_scan = sec_since_boot()
 | 
					
						
							|  |  |  |       if curr_scan - last_scan > 10:
 | 
					
						
							|  |  |  |         log_files = get_logs_to_send_sorted()
 | 
					
						
							|  |  |  |         last_scan = curr_scan
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # send one log
 | 
					
						
							|  |  |  |       curr_log = None
 | 
					
						
							|  |  |  |       if len(log_files) > 0:
 | 
					
						
							|  |  |  |         log_entry = log_files.pop() # newest log file
 | 
					
						
							|  |  |  |         cloudlog.debug(f"athena.log_handler.forward_request {log_entry}")
 | 
					
						
							|  |  |  |         try:
 | 
					
						
							|  |  |  |           curr_time = int(time.time())
 | 
					
						
							|  |  |  |           log_path = os.path.join(SWAGLOG_DIR, log_entry)
 | 
					
						
							|  |  |  |           setxattr(log_path, LOG_ATTR_NAME, int.to_bytes(curr_time, 4, sys.byteorder))
 | 
					
						
							|  |  |  |           with open(log_path) as f:
 | 
					
						
							|  |  |  |             jsonrpc = {
 | 
					
						
							|  |  |  |               "method": "forwardLogs",
 | 
					
						
							|  |  |  |               "params": {
 | 
					
						
							|  |  |  |                 "logs": f.read()
 | 
					
						
							|  |  |  |               },
 | 
					
						
							|  |  |  |               "jsonrpc": "2.0",
 | 
					
						
							|  |  |  |               "id": log_entry
 | 
					
						
							|  |  |  |             }
 | 
					
						
							|  |  |  |             low_priority_send_queue.put_nowait(json.dumps(jsonrpc))
 | 
					
						
							|  |  |  |             curr_log = log_entry
 | 
					
						
							|  |  |  |         except OSError:
 | 
					
						
							|  |  |  |           pass  # file could be deleted by log rotation
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # wait for response up to ~100 seconds
 | 
					
						
							|  |  |  |       # always read queue at least once to process any old responses that arrive
 | 
					
						
							|  |  |  |       for _ in range(100):
 | 
					
						
							|  |  |  |         if end_event.is_set():
 | 
					
						
							|  |  |  |           break
 | 
					
						
							|  |  |  |         try:
 | 
					
						
							|  |  |  |           log_resp = json.loads(log_recv_queue.get(timeout=1))
 | 
					
						
							|  |  |  |           log_entry = log_resp.get("id")
 | 
					
						
							|  |  |  |           log_success = "result" in log_resp and log_resp["result"].get("success")
 | 
					
						
							|  |  |  |           cloudlog.debug(f"athena.log_handler.forward_response {log_entry} {log_success}")
 | 
					
						
							|  |  |  |           if log_entry and log_success:
 | 
					
						
							|  |  |  |             log_path = os.path.join(SWAGLOG_DIR, log_entry)
 | 
					
						
							|  |  |  |             try:
 | 
					
						
							|  |  |  |               setxattr(log_path, LOG_ATTR_NAME, LOG_ATTR_VALUE_MAX_UNIX_TIME)
 | 
					
						
							|  |  |  |             except OSError:
 | 
					
						
							|  |  |  |               pass  # file could be deleted by log rotation
 | 
					
						
							|  |  |  |           if curr_log == log_entry:
 | 
					
						
							|  |  |  |             break
 | 
					
						
							|  |  |  |         except queue.Empty:
 | 
					
						
							|  |  |  |           if curr_log is None:
 | 
					
						
							|  |  |  |             break
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athena.log_handler.exception")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def stat_handler(end_event):
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     last_scan = 0
 | 
					
						
							|  |  |  |     curr_scan = sec_since_boot()
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       if curr_scan - last_scan > 10:
 | 
					
						
							|  |  |  |         stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR)))
 | 
					
						
							|  |  |  |         if len(stat_filenames) > 0:
 | 
					
						
							|  |  |  |           stat_path = os.path.join(STATS_DIR, stat_filenames[0])
 | 
					
						
							|  |  |  |           with open(stat_path) as f:
 | 
					
						
							|  |  |  |             jsonrpc = {
 | 
					
						
							|  |  |  |               "method": "storeStats",
 | 
					
						
							|  |  |  |               "params": {
 | 
					
						
							|  |  |  |                 "stats": f.read()
 | 
					
						
							|  |  |  |               },
 | 
					
						
							|  |  |  |               "jsonrpc": "2.0",
 | 
					
						
							|  |  |  |               "id": stat_filenames[0]
 | 
					
						
							|  |  |  |             }
 | 
					
						
							|  |  |  |             low_priority_send_queue.put_nowait(json.dumps(jsonrpc))
 | 
					
						
							|  |  |  |           os.remove(stat_path)
 | 
					
						
							|  |  |  |         last_scan = curr_scan
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athena.stat_handler.exception")
 | 
					
						
							|  |  |  |     time.sleep(0.1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event):
 | 
					
						
							|  |  |  |   while not (end_event.is_set() or global_end_event.is_set()):
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       data = ws.recv()
 | 
					
						
							|  |  |  |       local_sock.sendall(data)
 | 
					
						
							|  |  |  |     except WebSocketTimeoutException:
 | 
					
						
							|  |  |  |       pass
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athenad.ws_proxy_recv.exception")
 | 
					
						
							|  |  |  |       break
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cloudlog.debug("athena.ws_proxy_recv closing sockets")
 | 
					
						
							|  |  |  |   ssock.close()
 | 
					
						
							|  |  |  |   local_sock.close()
 | 
					
						
							|  |  |  |   cloudlog.debug("athena.ws_proxy_recv done closing sockets")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   end_event.set()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def ws_proxy_send(ws, local_sock, signal_sock, end_event):
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       r, _, _ = select.select((local_sock, signal_sock), (), ())
 | 
					
						
							|  |  |  |       if r:
 | 
					
						
							|  |  |  |         if r[0].fileno() == signal_sock.fileno():
 | 
					
						
							|  |  |  |           # got end signal from ws_proxy_recv
 | 
					
						
							|  |  |  |           end_event.set()
 | 
					
						
							|  |  |  |           break
 | 
					
						
							|  |  |  |         data = local_sock.recv(4096)
 | 
					
						
							|  |  |  |         if not data:
 | 
					
						
							|  |  |  |           # local_sock is dead
 | 
					
						
							|  |  |  |           end_event.set()
 | 
					
						
							|  |  |  |           break
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ws.send(data, ABNF.OPCODE_BINARY)
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athenad.ws_proxy_send.exception")
 | 
					
						
							|  |  |  |       end_event.set()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cloudlog.debug("athena.ws_proxy_send closing sockets")
 | 
					
						
							|  |  |  |   signal_sock.close()
 | 
					
						
							|  |  |  |   cloudlog.debug("athena.ws_proxy_send done closing sockets")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def ws_recv(ws, end_event):
 | 
					
						
							|  |  |  |   last_ping = int(sec_since_boot() * 1e9)
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       opcode, data = ws.recv_data(control_frame=True)
 | 
					
						
							|  |  |  |       if opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY):
 | 
					
						
							|  |  |  |         if opcode == ABNF.OPCODE_TEXT:
 | 
					
						
							|  |  |  |           data = data.decode("utf-8")
 | 
					
						
							|  |  |  |         recv_queue.put_nowait(data)
 | 
					
						
							|  |  |  |       elif opcode == ABNF.OPCODE_PING:
 | 
					
						
							|  |  |  |         last_ping = int(sec_since_boot() * 1e9)
 | 
					
						
							|  |  |  |         Params().put("LastAthenaPingTime", str(last_ping))
 | 
					
						
							|  |  |  |     except WebSocketTimeoutException:
 | 
					
						
							|  |  |  |       ns_since_last_ping = int(sec_since_boot() * 1e9) - last_ping
 | 
					
						
							|  |  |  |       if ns_since_last_ping > RECONNECT_TIMEOUT_S * 1e9:
 | 
					
						
							|  |  |  |         cloudlog.exception("athenad.ws_recv.timeout")
 | 
					
						
							|  |  |  |         end_event.set()
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athenad.ws_recv.exception")
 | 
					
						
							|  |  |  |       end_event.set()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def ws_send(ws, end_event):
 | 
					
						
							|  |  |  |   while not end_event.is_set():
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       try:
 | 
					
						
							|  |  |  |         data = send_queue.get_nowait()
 | 
					
						
							|  |  |  |       except queue.Empty:
 | 
					
						
							|  |  |  |         data = low_priority_send_queue.get(timeout=1)
 | 
					
						
							|  |  |  |       for i in range(0, len(data), WS_FRAME_SIZE):
 | 
					
						
							|  |  |  |         frame = data[i:i+WS_FRAME_SIZE]
 | 
					
						
							|  |  |  |         last = i + WS_FRAME_SIZE >= len(data)
 | 
					
						
							|  |  |  |         opcode = ABNF.OPCODE_TEXT if i == 0 else ABNF.OPCODE_CONT
 | 
					
						
							|  |  |  |         ws.send_frame(ABNF.create_frame(frame, opcode, last))
 | 
					
						
							|  |  |  |     except queue.Empty:
 | 
					
						
							|  |  |  |       pass
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athenad.ws_send.exception")
 | 
					
						
							|  |  |  |       end_event.set()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def backoff(retries):
 | 
					
						
							|  |  |  |   return random.randrange(0, min(128, int(2 ** retries)))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main():
 | 
					
						
							|  |  |  |   try:
 | 
					
						
							|  |  |  |     set_core_affinity([0, 1, 2, 3])
 | 
					
						
							|  |  |  |   except Exception:
 | 
					
						
							|  |  |  |     cloudlog.exception("failed to set core affinity")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   params = Params()
 | 
					
						
							|  |  |  |   dongle_id = params.get("DongleId", encoding='utf-8')
 | 
					
						
							|  |  |  |   UploadQueueCache.initialize(upload_queue)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id
 | 
					
						
							|  |  |  |   api = Api(dongle_id)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   conn_retries = 0
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       cloudlog.event("athenad.main.connecting_ws", ws_uri=ws_uri)
 | 
					
						
							|  |  |  |       ws = create_connection(ws_uri,
 | 
					
						
							|  |  |  |                              cookie="jwt=" + api.get_token(),
 | 
					
						
							|  |  |  |                              enable_multithread=True,
 | 
					
						
							|  |  |  |                              timeout=30.0)
 | 
					
						
							|  |  |  |       cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       conn_retries = 0
 | 
					
						
							|  |  |  |       cur_upload_items.clear()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       handle_long_poll(ws)
 | 
					
						
							|  |  |  |     except (KeyboardInterrupt, SystemExit):
 | 
					
						
							|  |  |  |       break
 | 
					
						
							|  |  |  |     except (ConnectionError, TimeoutError, WebSocketException):
 | 
					
						
							|  |  |  |       conn_retries += 1
 | 
					
						
							|  |  |  |       params.remove("LastAthenaPingTime")
 | 
					
						
							|  |  |  |     except socket.timeout:
 | 
					
						
							|  |  |  |       params.remove("LastAthenaPingTime")
 | 
					
						
							|  |  |  |     except Exception:
 | 
					
						
							|  |  |  |       cloudlog.exception("athenad.main.exception")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       conn_retries += 1
 | 
					
						
							|  |  |  |       params.remove("LastAthenaPingTime")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     time.sleep(backoff(conn_retries))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__":
 | 
					
						
							|  |  |  |   main()
 |