Fixed gpylint warnings.

TBR=noamsml
NOTRY=true

Review URL: https://codereview.chromium.org/292103002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271596 0039d316-1c4b-4281-b951-d872f2087c98
parent 938f37f7
...@@ -3,18 +3,20 @@ ...@@ -3,18 +3,20 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
# This prototype has tons of flaws, not the least of which being that it """Prototype of cloud device with support of local API.
# occasionally will block while waiting for commands to finish. However, this is
# a quick sketch. This prototype has tons of flaws, not the least of which being that it
# Script requires following components: occasionally will block while waiting for commands to finish. However, this is
# sudo apt-get install python-tornado a quick sketch.
# sudo apt-get install python-pip Script requires following components:
# sudo pip install google-api-python-client sudo apt-get install python-tornado
sudo apt-get install python-pip
sudo pip install google-api-python-client
"""
import atexit import atexit
import base64 import base64
import datetime import datetime
import httplib2
import json import json
import os import os
import subprocess import subprocess
...@@ -23,6 +25,7 @@ import traceback ...@@ -23,6 +25,7 @@ import traceback
from apiclient.discovery import build_from_document from apiclient.discovery import build_from_document
from apiclient.errors import HttpError from apiclient.errors import HttpError
import httplib2
from oauth2client.client import AccessTokenRefreshError from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import OAuth2WebServerFlow from oauth2client.client import OAuth2WebServerFlow
from oauth2client.file import Storage from oauth2client.file import Storage
...@@ -33,6 +36,7 @@ _OAUTH_SCOPE = 'https://www.googleapis.com/auth/clouddevices' ...@@ -33,6 +36,7 @@ _OAUTH_SCOPE = 'https://www.googleapis.com/auth/clouddevices'
_API_CLIENT_FILE = 'config.json' _API_CLIENT_FILE = 'config.json'
_API_DISCOVERY_FILE = 'discovery.json' _API_DISCOVERY_FILE = 'discovery.json'
_DEVICE_STATE_FILE = 'device_state.json'
DEVICE_DRAFT = { DEVICE_DRAFT = {
'systemName': 'LEDFlasher', 'systemName': 'LEDFlasher',
...@@ -45,7 +49,7 @@ DEVICE_DRAFT = { ...@@ -45,7 +49,7 @@ DEVICE_DRAFT = {
'base': { 'base': {
'vendorCommands': [{ 'vendorCommands': [{
'name': 'flashLED', 'name': 'flashLED',
'parameter' : [{ 'parameter': [{
'name': 'times', 'name': 'times',
'type': 'string' 'type': 'string'
}] }]
...@@ -76,47 +80,65 @@ network={ ...@@ -76,47 +80,65 @@ network={
led_path = '/sys/class/leds/ath9k_htc-phy0/' led_path = '/sys/class/leds/ath9k_htc-phy0/'
class DeviceUnregisteredError(Exception): class DeviceUnregisteredError(Exception):
pass pass
def ignore_errors(func): def ignore_errors(func):
def inner(*args, **kwargs): def inner(*args, **kwargs):
try: try:
func(*args, **kwargs) func(*args, **kwargs)
except: except Exception: # pylint: disable=broad-except
print 'Got error in unsafe function:' print 'Got error in unsafe function:'
traceback.print_exc() traceback.print_exc()
return inner return inner
class CommandWrapperReal(object): class CommandWrapperReal(object):
"""Command wrapper that executs shell commands."""
def __init__(self, cmd): def __init__(self, cmd):
if type(cmd) == str: if type(cmd) == str:
cmd = cmd.split() cmd = cmd.split()
self.cmd = cmd self.cmd = cmd
self.process = None self.process = None
def start(self): def start(self):
if self.process: if self.process:
end() self.end()
self.process = subprocess.Popen(self.cmd) self.process = subprocess.Popen(self.cmd)
def wait(self): def wait(self):
self.process.wait() self.process.wait()
def end(self): def end(self):
if self.process: if self.process:
self.process.terminate() self.process.terminate()
class CommandWrapperFake(object): class CommandWrapperFake(object):
"""Command wrapper that just prints shell commands."""
def __init__(self, cmd): def __init__(self, cmd):
self.cmd = cmd self.cmd = cmd
def start(self): def start(self):
print 'Start: ', self.cmd print 'Start: ', self.cmd
def wait(self): def wait(self):
print 'Wait: ', self.cmd print 'Wait: ', self.cmd
def end(self): def end(self):
print 'End: ', self.cmd print 'End: ', self.cmd
class CloudCommandHandlerFake(object): class CloudCommandHandlerFake(object):
"""Prints devices commands without execution."""
def __init__(self, ioloop): def __init__(self, ioloop):
pass pass
def handle_command(self, command_name, args): def handle_command(self, command_name, args):
if command_name == 'flashLED': if command_name == 'flashLED':
times = 1 times = 1
...@@ -124,9 +146,13 @@ class CloudCommandHandlerFake(object): ...@@ -124,9 +146,13 @@ class CloudCommandHandlerFake(object):
times = int(args['times']) times = int(args['times'])
print 'Flashing LED %d times' % times print 'Flashing LED %d times' % times
class CloudCommandHandlerReal(object): class CloudCommandHandlerReal(object):
"""Executes device commands."""
def __init__(self, ioloop): def __init__(self, ioloop):
self.ioloop = ioloop self.ioloop = ioloop
def handle_command(self, command_name, args): def handle_command(self, command_name, args):
if command_name == 'flashLED': if command_name == 'flashLED':
times = 1 times = 1
...@@ -134,10 +160,13 @@ class CloudCommandHandlerReal(object): ...@@ -134,10 +160,13 @@ class CloudCommandHandlerReal(object):
times = int(args['times']) times = int(args['times'])
print 'Really flashing LED %d times' % times print 'Really flashing LED %d times' % times
self.flash_led(times) self.flash_led(times)
@ignore_errors @ignore_errors
def flash_led(self, times): def flash_led(self, times):
self.set_led(times*2, True) self.set_led(times*2, True)
def set_led(self, times, value): def set_led(self, times, value):
"""Set led value."""
if not times: if not times:
return return
...@@ -150,45 +179,58 @@ class CloudCommandHandlerReal(object): ...@@ -150,45 +179,58 @@ class CloudCommandHandlerReal(object):
file_trigger.close() file_trigger.close()
self.ioloop.add_timeout(datetime.timedelta(milliseconds = 500), self.ioloop.add_timeout(datetime.timedelta(milliseconds=500),
lambda: self.set_led(times - 1, not value)) lambda: self.set_led(times - 1, not value))
class WifiHandler(object): class WifiHandler(object):
class Delegate: """Base class for wifi handlers."""
# Token is optional, and all delegates should support it being None
def on_wifi_connected(self, token): class Delegate(object):
def on_wifi_connected(self, unused_token):
"""Token is optional, and all delegates should support it being None."""
raise Exception('Unhandled condition: WiFi connected') raise Exception('Unhandled condition: WiFi connected')
def __init__(self, ioloop, state, delegate): def __init__(self, ioloop, state, delegate):
self.ioloop = ioloop self.ioloop = ioloop
self.state = state self.state = state
self.delegate = delegate self.delegate = delegate
def start(self): def start(self):
raise Exception('Start not implemented!') raise Exception('Start not implemented!')
def get_ssid(self): def get_ssid(self):
raise Exception('Get SSID not implemented!') raise Exception('Get SSID not implemented!')
# Note that by using CommandWrapperFake, you can run WifiHandlerReal on fake
# devices for testing the wifi-specific logic
class WifiHandlerReal(WifiHandler): class WifiHandlerReal(WifiHandler):
"""Real wifi handler.
Note that by using CommandWrapperFake, you can run WifiHandlerReal on fake
devices for testing the wifi-specific logic.
"""
def __init__(self, ioloop, state, delegate): def __init__(self, ioloop, state, delegate):
super(WifiHandlerReal, self).__init__(ioloop, state, delegate) super(WifiHandlerReal, self).__init__(ioloop, state, delegate)
self.hostapd = CommandWrapper(hostapd_cmd) self.command_wrapper = CommandWrapperReal
self.wpa_supplicant = CommandWrapper(wpa_supplicant_cmd) self.hostapd = self.CommandWrapper(hostapd_cmd)
self.dhcpd = CommandWrapper(dhcpd_cmd) self.wpa_supplicant = self.CommandWrapper(wpa_supplicant_cmd)
self.dhcpd = self.CommandWrapper(dhcpd_cmd)
def start(self): def start(self):
if self.state.has_wifi(): if self.state.has_wifi():
self.switch_to_wifi(self.state.ssid(), self.switch_to_wifi(self.state.ssid(), self.state.password(), None)
self.state.password(),
None)
else: else:
self.start_hostapd() self.start_hostapd()
def start_hostapd(self): def start_hostapd(self):
self.hostapd.start() self.hostapd.start()
time.sleep(3) time.sleep(3)
run_command(ifconfig_cmd) self.run_command(ifconfig_cmd)
self.dhcpd.start() self.dhcpd.start()
def switch_to_wifi(self, ssid, passwd, token): def switch_to_wifi(self, ssid, passwd, token):
try: try:
wpa_config = open(wpa_supplicant_conf, 'w') wpa_config = open(wpa_supplicant_conf, 'w')
...@@ -197,59 +239,53 @@ class WifiHandlerReal(WifiHandler): ...@@ -197,59 +239,53 @@ class WifiHandlerReal(WifiHandler):
self.hostapd.end() self.hostapd.end()
self.dhcpd.end() self.dhcpd.end()
self.wpa_supplicant.start() self.wpa_supplicant.start()
run_command(dhclient_release) self.run_command(dhclient_release)
run_command(dhclient_renew) self.run_command(dhclient_renew)
self.state.set_wifi(ssid,passwd) self.state.set_wifi(ssid, passwd)
self.delegate.on_wifi_connected(token) self.delegate.on_wifi_connected(token)
except DeviceUnregisteredError: except DeviceUnregisteredError:
self.state.reset() self.state.reset()
self.wpa_supplicant.end() self.wpa_supplicant.end()
self.start_hostapd() self.start_hostapd()
def stop(self): def stop(self):
self.hostapd.end() self.hostapd.end()
self.wpa_supplicant.end() self.wpa_supplicant.end()
self.dhcpd.end() self.dhcpd.end()
def get_ssid(self): def get_ssid(self):
return self.state.get_ssid() return self.state.get_ssid()
def run_command(self, cmd):
wrapper = self.command_wrapper(cmd)
wrapper.start()
wrapper.wait()
class WifiHandlerPassthrough(WifiHandler): class WifiHandlerPassthrough(WifiHandler):
"""Passthrough wifi handler."""
def __init__(self, ioloop, state, delegate): def __init__(self, ioloop, state, delegate):
super(WifiHandlerPassthrough, self).__init__(ioloop, state, delegate) super(WifiHandlerPassthrough, self).__init__(ioloop, state, delegate)
def start(self): def start(self):
self.delegate.on_wifi_connected(None) self.delegate.on_wifi_connected(None)
def switch_to_wifi(self, ssid, passwd, token):
def switch_to_wifi(self, unused_ssid, unused_passwd, unused_token):
raise Exception('Should not be reached') raise Exception('Should not be reached')
def get_ssid(self): def get_ssid(self):
return 'dummy' return 'dummy'
def setup_fake(): class State(object):
print 'Called setup' """Device state."""
def setup_real():
file_trigger = open(os.path.join(led_path, 'trigger'), 'w')
file_trigger.write('none')
file_trigger.close()
if os.path.exists('on_real_device'):
CommandWrapper = CommandWrapperReal
CommandWrapperMDns = CommandWrapperReal
CloudCommandHandler = CloudCommandHandlerReal
WifiHandler = WifiHandlerReal
setup_real()
else:
CommandWrapper = CommandWrapperFake
CommandWrapperMDns = CommandWrapperReal
CloudCommandHandler = CloudCommandHandlerFake
WifiHandler = WifiHandlerPassthrough
setup_fake()
class State:
def __init__(self): def __init__(self):
self.oauth_storage_ = Storage('oauth_creds') self.oauth_storage_ = Storage('oauth_creds')
self.clear() self.clear()
def clear(self): def clear(self):
self.credentials_ = None self.credentials_ = None
self.has_credentials_ = False self.has_credentials_ = False
...@@ -257,25 +293,30 @@ class State: ...@@ -257,25 +293,30 @@ class State:
self.ssid_ = '' self.ssid_ = ''
self.password_ = '' self.password_ = ''
self.device_id_ = '' self.device_id_ = ''
def reset(self): def reset(self):
self.clear() self.clear()
self.dump() self.dump()
def dump(self): def dump(self):
"""Saves device state to file."""
json_obj = { json_obj = {
'has_credentials': self.has_credentials_, 'has_credentials': self.has_credentials_,
'has_wifi': self.has_wifi_, 'has_wifi': self.has_wifi_,
'ssid': self.ssid_, 'ssid': self.ssid_,
'password': self.password_, 'password': self.password_,
'device_id': self.device_id_ } 'device_id': self.device_id_
statefile = open('device_state.json', 'w') }
statefile = open(_DEVICE_STATE_FILE, 'w')
json.dump(json_obj, statefile) json.dump(json_obj, statefile)
statefile.close() statefile.close()
if self.has_credentials_: if self.has_credentials_:
self.oauth_storage_.put(self.credentials_) self.oauth_storage_.put(self.credentials_)
def load(self): def load(self):
if os.path.exists('device_state.json'): if os.path.exists(_DEVICE_STATE_FILE):
statefile = open('device_state.json', 'r') statefile = open(_DEVICE_STATE_FILE, 'r')
json_obj = json.load(statefile) json_obj = json.load(statefile)
statefile.close() statefile.close()
...@@ -287,85 +328,102 @@ class State: ...@@ -287,85 +328,102 @@ class State:
if self.has_credentials_: if self.has_credentials_:
self.credentials_ = self.oauth_storage_.get() self.credentials_ = self.oauth_storage_.get()
def set_credentials(self, credentials, device_id): def set_credentials(self, credentials, device_id):
self.device_id_ = device_id self.device_id_ = device_id
self.credentials_ = credentials self.credentials_ = credentials
self.has_credentials_ = True self.has_credentials_ = True
self.dump() self.dump()
def set_wifi(self, ssid, password): def set_wifi(self, ssid, password):
self.ssid_ = ssid self.ssid_ = ssid
self.password_ = password self.password_ = password
self.has_wifi_ = True self.has_wifi_ = True
self.dump() self.dump()
def has_wifi(self): def has_wifi(self):
return self.has_wifi_ return self.has_wifi_
def has_credentials(self): def has_credentials(self):
return self.has_credentials_ return self.has_credentials_
def credentials(self): def credentials(self):
return self.credentials_ return self.credentials_
def ssid(self): def ssid(self):
return self.ssid_ return self.ssid_
def password(self): def password(self):
return self.password_ return self.password_
def device_id(self): def device_id(self):
return self.device_id_ return self.device_id_
def run_command(cmd):
wrapper = CommandWrapper(cmd)
wrapper.start()
wrapper.wait()
class MDnsWrapper: class MDnsWrapper(object):
def __init__(self): """Handles mDNS requests to device."""
def __init__(self, command_wrapper):
self.command_wrapper = command_wrapper
self.avahi_wrapper = None self.avahi_wrapper = None
self.setup_name = None self.setup_name = None
self.device_id = '' self.device_id = ''
self.started = False self.started = False
def start(self): def start(self):
self.started = True self.started = True
self.run_command() self.run_command()
def get_command(self): def get_command(self):
cmd = ['avahi-publish', '-s', 'Raspberry Pi' , '_privet._tcp', '8080', cmd = ['avahi-publish', '-s', 'Raspberry Pi', '_privet._tcp', '8080',
'txtvers=2', 'type=wifi', 'ty=Raspberry Pi', 'txtvers=2', 'type=wifi', 'ty=Raspberry Pi', 'id=' + self.device_id]
'id=' + self.device_id]
if self.setup_name: if self.setup_name:
cmd.append('setup=' + self.setup_name) cmd.append('setup=' + self.setup_name)
return cmd return cmd
def run_command(self): def run_command(self):
if self.avahi_wrapper: if self.avahi_wrapper:
self.avahi_wrapper.end() self.avahi_wrapper.end()
self.avahi_wrapper.wait() self.avahi_wrapper.wait()
self.avahi_wrapper = CommandWrapperMDns(self.get_command()) self.avahi_wrapper = self.command_wrapper(self.get_command())
self.avahi_wrapper.start() self.avahi_wrapper.start()
def set_id(self, device_id): def set_id(self, device_id):
self.device_id = device_id self.device_id = device_id
if self.started: if self.started:
self.run_command() self.run_command()
def set_setup_name(self, setup_name): def set_setup_name(self, setup_name):
self.setup_name = setup_name self.setup_name = setup_name
if self.started: if self.started:
self.run_command() self.run_command()
class CloudDevice:
class Delegate: class CloudDevice(object):
"""Handles device registration and commands."""
class Delegate(object):
def on_device_started(self): def on_device_started(self):
raise Exception('Not implemented: Device started') raise Exception('Not implemented: Device started')
def on_device_stopped(self): def on_device_stopped(self):
raise Exception('Not implemented: Device stopped') raise Exception('Not implemented: Device stopped')
def __init__(self, ioloop, state, delegate):
def __init__(self, ioloop, state, command_wrapper, delegate):
self.state = state self.state = state
self.http = httplib2.Http() self.http = httplib2.Http()
if not os.path.isfile(_API_CLIENT_FILE): if not os.path.isfile(_API_CLIENT_FILE):
credentials = { credentials = {
'oauth_client_id' : '', 'oauth_client_id': '',
'oauth_secret' : '', 'oauth_secret': '',
'api_key' : '' 'api_key': ''
} }
credentials_f = open(_API_CLIENT_FILE + '.samlpe', 'w') credentials_f = open(_API_CLIENT_FILE + '.samlpe', 'w')
credentials_f.write(json.dumps(credentials)); credentials_f.write(json.dumps(credentials))
credentials_f.close() credentials_f.close()
raise Exception('Missing ' + _API_CLIENT_FILE); raise Exception('Missing ' + _API_CLIENT_FILE)
credentials_f = open(_API_CLIENT_FILE) credentials_f = open(_API_CLIENT_FILE)
credentials = json.load(credentials_f) credentials = json.load(credentials_f)
...@@ -377,21 +435,23 @@ class CloudDevice: ...@@ -377,21 +435,23 @@ class CloudDevice:
if not os.path.isfile(_API_DISCOVERY_FILE): if not os.path.isfile(_API_DISCOVERY_FILE):
raise Exception('Download https://developers.google.com/' raise Exception('Download https://developers.google.com/'
'cloud-devices/v1/discovery.json'); 'cloud-devices/v1/discovery.json')
f = open('discovery.json') f = open(_API_DISCOVERY_FILE)
discovery = f.read() discovery = f.read()
f.close() f.close()
self.gcd = build_from_document( self.gcd = build_from_document(discovery, developerKey=self.api_key,
discovery, developerKey=self.api_key, http=self.http) http=self.http)
self.ioloop = ioloop self.ioloop = ioloop
self.active = True self.active = True
self.device_id = None self.device_id = None
self.credentials = None self.credentials = None
self.delegate = delegate self.delegate = delegate
self.command_handler = CloudCommandHandler(ioloop) self.command_handler = command_wrapper(ioloop)
def try_start(self, token): # Token may be null
def try_start(self, token):
"""Tries start or register device."""
if self.state.has_credentials(): if self.state.has_credentials():
self.credentials = self.state.credentials() self.credentials = self.state.credentials()
self.device_id = self.state.device_id() self.device_id = self.state.device_id()
...@@ -401,33 +461,36 @@ class CloudDevice: ...@@ -401,33 +461,36 @@ class CloudDevice:
else: else:
print 'Device not registered and has no credentials.' print 'Device not registered and has no credentials.'
print 'Waiting for registration.' print 'Waiting for registration.'
def register(self, token): def register(self, token):
"""Register device."""
resource = { resource = {
'deviceDraft': DEVICE_DRAFT, 'deviceDraft': DEVICE_DRAFT,
'oauthClientId': self.oauth_client_id 'oauthClientId': self.oauth_client_id
} }
self.gcd.registrationTickets().patch(registrationTicketId=token, self.gcd.registrationTickets().patch(registration_ticke_id=token,
body=resource).execute() body=resource).execute()
finalTicket = self.gcd.registrationTickets().finalize( final_ticket = self.gcd.registrationTickets().finalize(
registrationTicketId=token).execute() registration_ticke_id=token).execute()
authorization_code = finalTicket['robotAccountAuthorizationCode'] authorization_code = final_ticket['robotAccountAuthorizationCode']
flow = OAuth2WebServerFlow( flow = OAuth2WebServerFlow(self.oauth_client_id, self.oauth_secret,
self.oauth_client_id, self.oauth_secret, _OAUTH_SCOPE, _OAUTH_SCOPE, redirect_uri='oob')
redirect_uri='oob')
self.credentials = flow.step2_exchange(authorization_code) self.credentials = flow.step2_exchange(authorization_code)
self.device_id = finalTicket['deviceDraft']['id'] self.device_id = final_ticket['deviceDraft']['id']
self.state.set_credentials(self.credentials, self.device_id) self.state.set_credentials(self.credentials, self.device_id)
print 'Registered with device_id ', self.device_id print 'Registered with device_id ', self.device_id
self.run_device() self.run_device()
def run_device(self): def run_device(self):
"""Runs device."""
self.credentials.authorize(self.http) self.credentials.authorize(self.http)
try: try:
dev=self.gcd.devices().get(deviceId=self.device_id).execute() self.gcd.devices().get(deviceId=self.device_id).execute()
except HttpError, e: except HttpError, e:
# Pretty good indication the device was deleted # Pretty good indication the device was deleted
if e.resp.status == 404: if e.resp.status == 404:
...@@ -437,53 +500,52 @@ class CloudDevice: ...@@ -437,53 +500,52 @@ class CloudDevice:
self.check_commands() self.check_commands()
self.delegate.on_device_started() self.delegate.on_device_started()
def check_commands(self): def check_commands(self):
"""Checks device commands."""
if not self.active: if not self.active:
return return
print 'Checking commands...' print 'Checking commands...'
commands = self.gcd.commands().list(deviceId=self.device_id, commands = self.gcd.commands().list(deviceId=self.device_id,
state='queued').execute() state='queued').execute()
if 'commands' in commands: if 'commands' in commands:
print 'Found ', len(commands['commands']), ' commands' print 'Found ', len(commands['commands']), ' commands'
args = {} vendor_command_name = None
vendorCommandName = None
for command in commands['commands']: for command in commands['commands']:
try: try:
if command['name'].startswith('base._'): if command['name'].startswith('base._'):
vendorCommandName = command['name'][ vendor_command_name = command['name'][len('base._'):]
len('base._'):]
if 'parameters' in command: if 'parameters' in command:
parameters = command['parameters'] parameters = command['parameters']
else: else:
parameters = {} parameters = {}
else: else:
vendorCommandName = None vendor_command_name = None
except KeyError: except KeyError:
print 'Could not parse vendor command ', print 'Could not parse vendor command ',
print repr(command) print repr(command)
vendorCommandName = None vendor_command_name = None
if vendorCommandName: if vendor_command_name:
self.command_handler.handle_command( self.command_handler.handle_command(vendor_command_name, parameters)
vendorCommandName,
parameters)
self.gcd.commands().patch(commandId = command['id'], self.gcd.commands().patch(commandId=command['id'],
body={'state': 'done'}).execute() body={'state': 'done'}).execute()
else: else:
print 'Found no commands' print 'Found no commands'
self.ioloop.add_timeout(datetime.timedelta(milliseconds=1000), self.ioloop.add_timeout(datetime.timedelta(milliseconds=1000),
self.check_commands) self.check_commands)
def stop(self): def stop(self):
self.active = False self.active = False
def get_device_id(self): def get_device_id(self):
return self.device_id return self.device_id
def get_only(f): def get_only(f):
def inner(self, request, response_func, *args): def inner(self, request, response_func, *args):
if request.method != 'GET': if request.method != 'GET':
...@@ -491,13 +553,15 @@ def get_only(f): ...@@ -491,13 +553,15 @@ def get_only(f):
return f(self, request, response_func, *args) return f(self, request, response_func, *args)
return inner return inner
def post_only(f): def post_only(f):
def inner(self, request, response_func, *args): def inner(self, request, response_func, *args):
if request.method != 'POST': # if request.method != 'POST':
return False # return False
return f(self, request, response_func, *args) return f(self, request, response_func, *args)
return inner return inner
def wifi_provisioning(f): def wifi_provisioning(f):
def inner(self, request, response_func, *args): def inner(self, request, response_func, *args):
if self.on_wifi: if self.on_wifi:
...@@ -505,6 +569,7 @@ def wifi_provisioning(f): ...@@ -505,6 +569,7 @@ def wifi_provisioning(f):
return f(self, request, response_func, *args) return f(self, request, response_func, *args)
return inner return inner
def post_provisioning(f): def post_provisioning(f):
def inner(self, request, response_func, *args): def inner(self, request, response_func, *args):
if not self.on_wifi: if not self.on_wifi:
...@@ -512,76 +577,92 @@ def post_provisioning(f): ...@@ -512,76 +577,92 @@ def post_provisioning(f):
return f(self, request, response_func, *args) return f(self, request, response_func, *args)
return inner return inner
def extract_encryption_params(f): def extract_encryption_params(f):
"""Extracts privet encription header and pass as parameter into function."""
def inner(self, request, response_func, *args): def inner(self, request, response_func, *args):
"""Extracts privet encription header."""
try: try:
client_id = request.headers['X-Privet-Client-ID'] client_id = request.headers['X-Privet-Client-ID']
if 'X-Privet-Encrypted' in request.headers: if 'X-Privet-Encrypted' in request.headers:
encrypted = (request.headers['X-Privet-Encrypted'].lower() encrypted = (request.headers['X-Privet-Encrypted'].lower() == 'true')
== 'true')
else: else:
encrypted = False encrypted = False
except (KeyError, TypeError): except (KeyError, TypeError):
print 'Missing client parameters in headers' print 'Missing client parameters in headers'
response_func(400, { 'error': 'missing_client_parameters' }) response_func(400, {'error': 'missing_client_parameters'})
return True return True
return f(self, request, response_func, client_id, encrypted, *args) return f(self, request, response_func, client_id, encrypted, *args)
return inner return inner
def merge_dictionary(a, b):
result = {}
for k in a:
result[k] = a[k]
for k in b:
result[k] = b[k]
return result
class InvalidStepError(Exception): class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
"""Handles HTTP requests."""
class InvalidStepError(Exception):
pass pass
class InvalidPackageError(Exception): class InvalidPackageError(Exception):
pass pass
class EncryptionError(Exception): class EncryptionError(Exception):
pass pass
class CancelableClosure: class CancelableClosure(object):
"""Allows to cancel callbacks."""
def __init__(self, function): def __init__(self, function):
self.function = function self.function = function
def __call__(self): def __call__(self):
if self.function: if self.function:
return self.function return self.function
return None return None
def cancel(self): def cancel(self):
self.function = None self.function = None
class DummySession(object):
"""Handles sessions."""
class DummySession:
def __init__(self, client_id): def __init__(self, client_id):
self.client_id = client_id self.client_id = client_id
self.key = None self.key = None
def do_step(self, step, package): def do_step(self, step, package):
if step != 0: if step != 0:
raise InvalidStepError() raise self.InvalidStepError()
self.key = package self.key = package
return self.key return self.key
def decrypt(self, cyphertext): def decrypt(self, cyphertext):
return json.loads(cyphertext[len(self.key):]) return json.loads(cyphertext[len(self.key):])
def encrypt(self, plain_data): def encrypt(self, plain_data):
return self.key + json.dumps(plain_data) return self.key + json.dumps(plain_data)
def get_client_id(self): def get_client_id(self):
return client_id return self.client_id
def get_stype(self): def get_stype(self):
return 'dummy' return 'dummy'
class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
def __init__(self, ioloop, state): def __init__(self, ioloop, state):
self.cloud_device = CloudDevice(ioloop, state, self) if os.path.exists('on_real_device'):
self.wifi_handler = WifiHandler(ioloop, state, self) mdns_wrappers = CommandWrapperReal
self.mdns_wrapper = MDnsWrapper() cloud_wrapper = CloudCommandHandlerReal
wifi_handler = WifiHandlerReal
self.setup_real()
else:
mdns_wrappers = CommandWrapperReal
cloud_wrapper = CloudCommandHandlerFake
wifi_handler = WifiHandlerPassthrough
self.setup_fake()
self.cloud_device = CloudDevice(ioloop, state, cloud_wrapper, self)
self.wifi_handler = wifi_handler(ioloop, state, self)
self.mdns_wrapper = MDnsWrapper(mdns_wrappers)
self.on_wifi = False self.on_wifi = False
self.registered = False self.registered = False
self.in_session = False self.in_session = False
...@@ -593,78 +674,84 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): ...@@ -593,78 +674,84 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
'/privet/v2/session/handshake': self.do_session_handshake, '/privet/v2/session/handshake': self.do_session_handshake,
'/privet/v2/session/cancel': self.do_session_cancel, '/privet/v2/session/cancel': self.do_session_cancel,
'/privet/v2/session/api': self.do_session_api, '/privet/v2/session/api': self.do_session_api,
'/privet/v2/setup/start': self.get_insecure_api_handler( '/privet/v2/setup/start':
self.do_secure_setup), self.get_insecure_api_handler(self.do_secure_setup_start),
'/privet/v2/setup/status': self.get_insecure_api_handler( '/privet/v2/setup/cancel':
self.do_secure_status), self.get_insecure_api_handler(self.do_secure_setup_cancel),
'/privet/v2/setup/status':
self.get_insecure_api_handler(self.do_secure_status),
} }
self.current_session = None self.current_session = None
self.session_cancel_callback = None self.session_cancel_callback = None
self.session_handlers = { self.session_handlers = {
'dummy' : DummySession 'dummy': self.DummySession
} }
self.secure_handlers = { self.secure_handlers = {
'/privet/v2/setup/start' : self.do_secure_setup, '/privet/v2/setup/start': self.do_secure_setup_start,
'/privet/v2/setup/status' : self.do_secure_status '/privet/v2/setup/cancel': self.do_secure_setup_cancel,
'/privet/v2/setup/status': self.do_secure_status
} }
@staticmethod
def setup_fake():
print 'Called setup'
@staticmethod
def setup_real():
file_trigger = open(os.path.join(led_path, 'trigger'), 'w')
file_trigger.write('none')
file_trigger.close()
def start(self): def start(self):
self.wifi_handler.start() self.wifi_handler.start()
self.mdns_wrapper.set_setup_name('RaspberryPi.camera.privet') self.mdns_wrapper.set_setup_name('RaspberryPi.camera.privet')
self.mdns_wrapper.start() self.mdns_wrapper.start()
@get_only @get_only
def do_ping(self, request, response_func): def do_ping(self, unused_request, response_func):
response_func(200, '{ "pong": true }') response_func(200, '{ "pong": true }')
return True return True
@get_only @get_only
def do_public_info(self, request, response_func): def do_public_info(self, request, unused_response_func):
info = merge_dictionary( info = self.get_common_info().items() + {
self.get_common_info(), 'stype': self.session_handlers.keys()}.items()
{ self.real_send_response(request, 200, json.dumps(info))
'stype' : self.session_handlers.keys()
})
self.real_send_response(
request, 200, json.dumps(info))
@post_provisioning @post_provisioning
@get_only @get_only
def do_info(self, request, response_func): def do_info(self, request, unused_response_func):
specific_info = { specific_info = {'x-privet-token': 'sample'}
'x-privet-token': 'sample', info = self.get_common_info().items() + specific_info.items()
} self.real_send_response(request, 200, json.dumps(info))
info = merge_dictionary(
self.get_common_info(),
specific_info
)
self.real_send_response(
request, 200, json.dumps(info))
@post_only @post_only
@wifi_provisioning @wifi_provisioning
def do_wifi_switch(self, request, response_func): def do_wifi_switch(self, request, response_func):
"""Handles /deprecated/wifi/switch requests."""
data = json.loads(request.body) data = json.loads(request.body)
try: try:
ssid = data['ssid'] ssid = data['ssid']
passw = data['passw'] passw = data['passw']
except KeyError: except KeyError:
print 'Malformed content: ' + repr(data) print 'Malformed content: ' + repr(data)
self.real_send_response( self.real_send_response(request, 400, {'error': 'invalid_params'})
request, 400, { 'error': 'invalid_params' })
traceback.print_exc() traceback.print_exc()
return True return True
response_func(200, { 'ssid': ssid } ) response_func(200, {'ssid': ssid})
self.wifi_handler.switch_to_wifi(ssid, passw, None) self.wifi_handler.switch_to_wifi(ssid, passw, None)
# TODO: Return to normal wifi after timeout (cancelable) # TODO(noamsml): Return to normal wifi after timeout (cancelable)
return True return True
@extract_encryption_params @extract_encryption_params
@post_only @post_only
@wifi_provisioning @wifi_provisioning
def do_session_handshake(self, request, response_func, client_id, def do_session_handshake(self, request, unused_response_func, client_id,
encrypted): unused_encrypted):
"""Handles /privet/v2/session/handshake requests."""
data = json.loads(request.body) data = json.loads(request.body)
try: try:
stype = data['stype'] stype = data['stype']
...@@ -673,77 +760,70 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): ...@@ -673,77 +760,70 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
except (KeyError, TypeError): except (KeyError, TypeError):
traceback.print_exc() traceback.print_exc()
print 'Malformed content: ' + repr(data) print 'Malformed content: ' + repr(data)
self.real_send_response( self.real_send_response(request, 400, {'error': 'invalid_params'})
request, 400, { 'error': 'invalid_params' })
return True return True
if self.current_session: if self.current_session:
if client_id != self.current_session.get_client_id(): if client_id != self.current_session.get_client_id():
self.real_send_response( self.real_send_response(request, 500, {'error': 'in_session'})
request, 500, { 'error': 'in_session' })
return True return True
if stype != self.current_session.get_stype(): if stype != self.current_session.get_stype():
self.real_send_response( self.real_send_response(request, 500, {'error': 'invalid_stype'})
request, 500, { 'error': 'invalid_stype' })
return True return True
else: else:
if stype not in self.session_handlers: if stype not in self.session_handlers:
self.real_send_response( self.real_send_response(request, 500, {'error': 'invalid_stype'})
request, 500, { 'error': 'invalid_stype' })
return True return True
self.current_session = self.session_handlers[stype](client_id) self.current_session = self.session_handlers[stype](client_id)
try: try:
output_package = self.current_session.do_step(step, package) output_package = self.current_session.do_step(step, package)
except InvalidStepError: except self.InvalidStepError:
self.real_send_response( self.real_send_response(request, 500, {'error': 'invalid_step'})
request, 500, { 'error': 'invalid_step' })
return True return True
except InvalidPackageError: except self.InvalidPackageError:
self.real_send_response( self.real_send_response(request, 500, {'error': 'invalid_step'})
request, 500, { 'error': 'invalid_step' })
return True return True
return_obj = { return_obj = {
'stype' : stype, 'stype': stype,
'step' : step, 'step': step,
'package': base64.b64encode(output_package)} 'package': base64.b64encode(output_package)
}
self.real_send_response( self.real_send_response(request, 200, json.dumps(return_obj))
request, 200, json.dumps(return_obj))
self.post_session_cancel() self.post_session_cancel()
return True return True
@extract_encryption_params @extract_encryption_params
@post_only @post_only
@wifi_provisioning @wifi_provisioning
def do_session_cancel(self, request, response_func, client_id, def do_session_cancel(self, request, unused_response_func, client_id,
encrypted): unused_encrypted):
if client_id == self.current_session.client_id: if client_id == self.current_session.client_id:
self.current_session = None self.current_session = None
if self.session_cancel_callback: if self.session_cancel_callback:
self.session_cancel_callback.cancel() self.session_cancel_callback.cancel()
else: else:
self.real_send_response( self.real_send_response(request, 400, {'error': 'invalid_client_id'})
request, 400, { 'error': 'invalid_client_id' })
return True return True
@extract_encryption_params @extract_encryption_params
@post_only @post_only
@wifi_provisioning @wifi_provisioning
def do_session_api(self, request, response_func, client_id, encrypted): def do_session_api(self, request, response_func, client_id, encrypted):
"""Handles /privet/v2/session/api requests."""
if not encrypted: if not encrypted:
response_func(400, { 'error': 'encryption_required' }) response_func(400, {'error': 'encryption_required'})
return True return True
if (not self.current_session or if not self.current_session or client_id != self.current_session.client_id:
client_id != self.current_session.client_id): response_func(405, {'error': 'invalid_client_id'})
response_func(405, { 'error': 'invalid_client_id' })
return True return True
try: try:
decrypted = self.current_session.decrypt(request.body) decrypted = self.current_session.decrypt(request.body)
except EncryptionError: except self.EncryptionError:
response_func(415, { 'error': 'decryption_failed' }) response_func(415, {'error': 'decryption_failed'})
return True return True
def encrypted_response_func(code, data): def encrypted_response_func(code, data):
...@@ -752,53 +832,44 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): ...@@ -752,53 +832,44 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
else: else:
self.encrypted_send_response(request, code, { self.encrypted_send_response(request, code, {
'api': decrypted['api'], 'api': decrypted['api'],
'response': data}) 'response': data
})
if ('api' not in decrypted or 'request' not in decrypted if ('api' not in decrypted or 'request' not in decrypted or
or type(decrypted['request']) != dict): type(decrypted['request']) != dict):
print 'Invalid params in API stage' print 'Invalid params in API stage'
encrypted_response_func(400, { 'error': 'invalid_params' }) encrypted_response_func(400, {'error': 'invalid_params'})
return True return True
if decrypted['api'] in self.secure_handlers: if decrypted['api'] in self.secure_handlers:
self.secure_handlers[decrypted['api']](request, self.secure_handlers[decrypted['api']](request,
encrypted_response_func, encrypted_response_func,
decrypted['request']) decrypted['request'])
else: else:
encrypted_response_func(400, { 'error': 'unknown_api' }) encrypted_response_func(400, {'error': 'unknown_api'})
self.post_session_cancel() self.post_session_cancel()
return True return True
def get_insecure_api_handler(self, handler): def get_insecure_api_handler(self, handler):
return lambda request, response_func: self.insecure_api_handler( def inner(request, func):
request, response_func, handler) return self.insecure_api_handler(request, func, handler)
return inner
@post_only @post_only
def insecure_api_handler(self, request, response_func, handler): def insecure_api_handler(self, request, response_func, handler):
real_params = json.loads(request.body) real_params = json.loads(request.body) if request.body else {}
handler(request, response_func, real_params) handler(request, response_func, real_params)
return True return True
def do_secure_setup(self, request, response_func, params):
setup_handlers = {
'start': self.do_setup_start,
'cancel': self.do_setup_cancel }
if not 'action' in params:
response_func(400, { 'error': 'invalid_params' })
return
if params['action'] not in setup: def do_secure_status(self, unused_request, response_func, unused_params):
response_func(400, { 'error': 'invalid_action' }) """Handles /privet/v2/setup/status requests."""
return
setup[params['action']](request, response_func, params)
def do_secure_status(self, request, response_func, params):
setup = { setup = {
'registration' : { 'registration': {
'required' : True 'required': True
}, },
'wifi' : { 'wifi': {
'required' : True 'required': True
} }
} }
if self.on_wifi: if self.on_wifi:
...@@ -811,9 +882,11 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): ...@@ -811,9 +882,11 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
setup['registration']['status'] = 'complete' setup['registration']['status'] = 'complete'
setup['registration']['id'] = self.cloud_device.get_device_id() setup['registration']['id'] = self.cloud_device.get_device_id()
else: else:
specific_info['setup']['registration'] = 'available' setup['registration']['status'] = 'available'
response_func(200, setup)
def do_setup_start(self, request, response_func, params): def do_secure_setup_start(self, unused_request, response_func, params):
"""Handles /privet/v2/setup/start requests."""
has_wifi = False has_wifi = False
token = None token = None
...@@ -827,18 +900,21 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): ...@@ -827,18 +900,21 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
token = params['registration']['ticketID'] token = params['registration']['ticketID']
except KeyError: except KeyError:
print 'Invalid params in bootstrap stage' print 'Invalid params in bootstrap stage'
response_func(400, { 'error': 'invalid_params' }) response_func(400, {'error': 'invalid_params'})
return return
response_func(200, { 'ssid' : ssid })
if has_wifi: if has_wifi:
self.wifi_handler.switch_to_wifi(ssid, passw, token) self.wifi_handler.switch_to_wifi(ssid, passw, token)
elif token: elif token:
self.cloud_device.register(token) self.cloud_device.register(token)
else: else:
response_func(400, { 'error': 'invalid_params' }) response_func(400, {'error': 'invalid_params'})
def do_setup_cancel(self, request, response_func, params): return
self.do_secure_status(unused_request, response_func, params)
def do_secure_setup_cancel(self, request, response_func, params):
pass pass
def handle_request(self, request): def handle_request(self, request):
def response_func(code, data): def response_func(code, data):
self.real_send_response(request, code, json.dumps(data)) self.real_send_response(request, code, json.dumps(data))
...@@ -848,60 +924,72 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate): ...@@ -848,60 +924,72 @@ class WebRequestHandler(WifiHandler.Delegate, CloudDevice.Delegate):
handled = self.handlers[request.path](request, response_func) handled = self.handlers[request.path](request, response_func)
if not handled: if not handled:
self.real_send_response(request, 404, self.real_send_response(request, 404, {'error': 'Not found'})
{ 'error': 'Not found' })
def encrypted_send_response(self, request, code, json): def encrypted_send_response(self, request, code, data):
self.real_send_response(request, code, self.real_send_response(request, code,
self.current_session.encrypt(json)) self.current_session.encrypt(data))
def real_send_response(self, request, code, data): def real_send_response(self, request, code, data):
request.write('HTTP/1.1 %d Maybe OK\n' % code) request.write('HTTP/1.1 %d Maybe OK\n' % code)
request.write('Content-Type: application/json\n') request.write('Content-Type: application/json\n')
request.write('Content-Length: %d\n' % len(data)) request.write('Content-Length: %d\n' % len(data))
write_data = '\n%s' % data write_data = '\n%s' % data
request.write(str(write_data)); request.write(str(write_data))
request.finish() request.finish()
def device_state(self): def device_state(self):
return 'idle' return 'idle'
def get_common_info(self): def get_common_info(self):
return { 'version' : '2.0', return {
'name' : 'Sample Device', 'version': '2.0',
'device_state' : self.device_state() } 'name': 'Sample Device',
'device_state': self.device_state()
}
def post_session_cancel(self): def post_session_cancel(self):
if self.session_cancel_callback: if self.session_cancel_callback:
self.session_cancel_callback.cancel() self.session_cancel_callback.cancel()
self.session_cancel_callback = CancelableClosure(self.session_cancel) self.session_cancel_callback = self.CancelableClosure(self.session_cancel)
self.ioloop.add_timeout(datetime.timedelta(minutes=2), self.ioloop.add_timeout(datetime.timedelta(minutes=2),
self.session_cancel_callback) self.session_cancel_callback)
def session_cancel(self): def session_cancel(self):
self.current_session = None self.current_session = None
# WifiHandler.Delegate implementation # WifiHandler.Delegate implementation
def on_wifi_connected(self, token): def on_wifi_connected(self, token):
self.mdns_wrapper.set_setup_name(None) self.mdns_wrapper.set_setup_name(None)
self.cloud_device.try_start(token) self.cloud_device.try_start(token)
self.on_wifi = True self.on_wifi = True
def on_device_started(self): def on_device_started(self):
self.mdns_wrapper.set_id(self.cloud_device.get_device_id()) self.mdns_wrapper.set_id(self.cloud_device.get_device_id())
def on_device_stopped(self): def on_device_stopped(self):
pass pass
def stop(self): def stop(self):
self.wifi_handler.stop() self.wifi_handler.stop()
self.cloud_device.stop() self.cloud_device.stop()
state = State()
state.load()
ioloop = IOLoop.instance()
def main():
state = State()
state.load()
handler = WebRequestHandler(ioloop, state) ioloop = IOLoop.instance()
handler.start()
def logic_stop(): handler = WebRequestHandler(ioloop, state)
handler.start()
def logic_stop():
handler.stop() handler.stop()
atexit.register(logic_stop)
server = HTTPServer(handler.handle_request)
server.listen(8080)
atexit.register(logic_stop) ioloop.start()
server = HTTPServer(handler.handle_request) if __name__ == '__main__':
server.listen(8080) main()
ioloop.start()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment