Exemplo n.º 1
0
def handle_set_led(server, body, source):
    """
    Sends SET_LED message to the destination that corresponds with the source
    address in the cache.
    """
    logging.debug('SET_LED')

    if 'state' not in body:
        msg = 'missing field: "state" required'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    state = body['state']

    if not isinstance(state, int) or state < 0 or state > 2:
        msg = 'state must be an int in range [0,2]'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    car_addr = server.get_destination(source)
    if car_addr == None:
        msg = 'invalid destination'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    server.send(json.dumps(body).encode('utf-8'), car_addr)
Exemplo n.º 2
0
    def receive_forever(self):
        """
        Start an infinite loop that will indefinitely block on a receive until
        a new message comes in. If the message is JSON-formatted and has a
        "type" key, then the corresponding handler will be run.
        """

        while True:
            body, addr = self.receive()
            if body == None and addr == None:
                # There was an error with the received message
                continue

            if body['type'] not in self.handlers:
                # Provided type is not a registered handler
                logging.debug('Invalid message type', body)
                self.send(Error.json(Error.BAD_REQ, 'invalid message type'),
                          addr)
                continue

            try:
                self.handlers[body['type']](self, body, addr)
            except CloseServer:
                self.close()
                return
            except Exception as e:
                self.send(Error.json(Error.SERVER_ERR, str(e)), addr)
Exemplo n.º 3
0
def handle_connect_car(server, body, source):
    logging.debug('CONNECT CAR')

    if 'car_id' not in body:
        logging.debug('missing field: car_id')
        server.send(Error.json(Error.BAD_REQ, 'missing field: car_id'), source)
        return

    car_id = body['car_id']

    if not isinstance(car_id, int):
        msg = '"car_id" must be an integer'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    with server.get_db() as (dbconnect, cursor):
        cursor.execute('select * from cars where (id=?)', (car_id, ))
        entry = cursor.fetchone()

        request_ip = source[0]
        if entry is None:
            msg = 'car does not exist'
            logging.debug(msg)
            server.send(Error.json(Error.BAD_REQ, msg), source)
        elif entry[2] != request_ip:
            msg = 'IP address does not match car ID'
            logging.debug(msg)
            server.send(Error.json(Error.BAD_REQ, msg), source)
        else:
            cursor.execute('update cars set isOn=1 where (id=?)', (car_id, ))
            dbconnect.commit()
            data = '{"type": %d}' % MsgType.ACK
            server.send(data.encode('utf-8'), source)
Exemplo n.º 4
0
def handle_link(server, body, source):
    logging.debug('LINK')

    if 'car_id' not in body or 'user_id' not in body:
        msg = 'missing field: "car_id", "user_id" required'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    car_id = body['car_id']
    user_id = body['user_id']

    if not isinstance(car_id, int) or not isinstance(user_id, int):
        msg = '"car_id" and "user_id" must be integers'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    with server.get_db() as (_, cursor):
        cursor.execute('select * from cars where id=? and userID=?',
                       (car_id, user_id))
        entry = cursor.fetchone()

    if entry == None:
        msg = 'car does not exist'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
    elif not entry[3]:  # Check the isOn column
        msg = 'car is not available'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
    else:
        server.add_route(source, (entry[2], CAR_PORT))
        data = '{"type": %d}' % MsgType.ACK
        server.send(data.encode('utf-8'), source)
Exemplo n.º 5
0
def handle_set_led(server, body, addr):
    """
    Passes the new LED state to the Arduino via the serial port.

    Arguments:
    server -- instance of Server class
    body -- JSON body of UDP packet received
    addr -- source destination of received UDP packet
    """
    logging.debug('SET LED')
    if 'state' not in body:
        msg = 'missing "state" field'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    state = body['state']
    if not isinstance(state, int) or state < 0 or state > 2:
        msg = '"state" must integer in range [0, 2]'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    SerialMsg.write_8_bit(server.serial, SerialMsg.LED, state)

    server.send(json.dumps({'type': MsgType.ACK}).encode('utf-8'), addr)
Exemplo n.º 6
0
def handle_move(server, body, addr):
    """
    Passes the joystick coordinates to the Arduino via the serial port.

    Arguments:
    server -- instance of Server class
    body -- JSON body of UDP packet received
    addr -- source destination of received UDP packet
    """
    logging.debug('MOVE')
    if 'x' not in body or 'y' not in body:
        msg = 'body must include "x" and "y" fields'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    x = body['x']
    y = body['y']

    if not isinstance(x, int) or not isinstance(y, int) or \
            x < 0 or x > 1023 or y < 0 or y > 1023:
        msg = '"x" and "y" values must be within the range [0, 1023]'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    SerialMsg.write_16_bit(server.serial, SerialMsg.MOVE, body['x'], body['y'])
Exemplo n.º 7
0
def handle_login(server, body, source):
    logging.debug('LOGIN')
    '''
    1. Compare salted-and-hashed passwords
    2. If success: get car list from database and send to the app
       If failure: send failed login message
    '''

    # Check data is valid. if not, send an error packet
    if 'name' not in body or 'password' not in body:
        message = 'missing field: "name", "password" required'
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Get JSON data
    name = body['name']
    password = body['password']

    if not isinstance(name, str) or not isinstance(password, str):
        msg = '"name" and "password" must be strings'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    # Get user from db. Send an error if user doesn't exist.
    with server.get_db() as (dbconnect, cursor):
        cursor.execute("select * from users where name=(?)", [name])
        entry = cursor.fetchone()

    if entry is None:
        message = "User does not exist"
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Get salt as bytes
    salt = entry[2]
    b_salt = b64decode(salt.encode('utf-8'))
    # Get salted password string from database
    salted_password = entry[3]
    # Salt the login password
    new_password = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'),
                                       b_salt, 100000)
    str_new_password = b64encode(new_password).decode('utf-8')

    # Compare two passwords as strings
    if str_new_password == salted_password:
        # Send Confirmation to App
        logging.debug("User login successful")
        user_id = entry[0]
        ackJSON = {"type": MsgType.ACK, "user_id": user_id}
        _send_JSON(server, source, ackJSON)
    else:
        message = "Password is incorrect"
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
Exemplo n.º 8
0
def handle_register_user(server, body, source):
    logging.debug('REGISTER USER')
    '''
    1. Store username, salt, and salted-and-hashed-password in 'users' table
    2. Send a confirmation (ACK) back to the app
    '''

    # Check data is valid. if not, send an error packet
    if 'name' not in body or 'password' not in body:
        message = 'missing field: "name", "password" required'
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Get JSON data
    name = body['name']
    password = body['password']

    if not isinstance(name, str) or not isinstance(password, str):
        msg = '"name" and "password" must be strings'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    # Salt password
    salt = os.urandom(32)
    password = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt,
                                   100000)

    # Create user in db. Send an error if user already exists.
    with server.get_db() as (dbconnect, cursor):
        cursor.execute("select * from users where name=(?)", [name])
        entry = cursor.fetchone()
        if entry is None:
            cursor.execute("insert into users (name,salt,password) values (?,?,?)",\
            (name,b64encode(salt).decode('utf-8'), b64encode(password).decode('utf-8')))
            dbconnect.commit()
        else:
            message = "User already exists"
            logging.debug(message)
            server.send(Error.json(Error.BAD_REQ, message), source)
            return

        user_id = cursor.lastrowid

    # Send Confirmation to App
    logging.debug("User registration successful")
    ackJSON = {
        "type": MsgType.ACK,
        "user_id": user_id,
    }
    _send_JSON(server, source, ackJSON)
Exemplo n.º 9
0
def handle_get_cars(server, body, source):
    logging.debug('GET CARS')
    '''
    1. Get list of cars from databse
    2. If successful: send list of cars to app
    '''

    # Check data is valid
    if 'user_id' not in body:
        message = 'missing field: "user_id" required'
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Get JSON data
    user_id = body["user_id"]

    if not isinstance(user_id, int):
        msg = '"user_id" must be an integer'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    # Create cars list
    cars = []
    with server.get_db() as (_, cursor):
        cursor.execute("select * from cars where userID=(?)", [user_id])
        entry = cursor.fetchall()
        # Return error if no cars are under userID
        if entry is None:
            message = "User has no registered cars"
            logging.debug(message)
            server.send(Error.json(Error.BAD_REQ, message), source)
            return
        else:
            # Add car_name and car_id to list for each car returned
            for row in entry:
                cars.append({
                    "id": row[0],
                    "name": row[1],
                    "is_on": bool(row[3])
                })

            carsJSON = {"type": MsgType.ACK, "cars": cars}
            _send_JSON(server, source, carsJSON)
Exemplo n.º 10
0
def handle_ack(server, body, source):
    """
    Sends ACK message to the destination that corresponds with the source
    address in the cache.
    """
    logging.debug('ACK')

    dest = server.get_destination(source)
    if dest == None:
        msg = 'invalid destination'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    server.send(json.dumps(body).encode('utf-8'), dest)
Exemplo n.º 11
0
def handle_movement(server, body, source):
    logging.debug('MOVEMENT')
    '''
    1. Get desination IP from cache (if not in cache, get from 'cars' table)
    2. Forward the message
    '''

    # Check data is valid
    if 'x' not in body or 'y' not in body:
        message = 'missing field: "x", "y" required'
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Get JSON data
    x = body['x']
    y = body['y']

    if not isinstance(x, int) or not isinstance(y, int) or \
            x < 0 or x > 1023 or y < 0 or y > 1023:
        msg = '"x" and "y" values must be within the range [0, 1023]'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    # Check cache for car ip address
    car_addr = server.get_destination(source)
    if car_addr is None:
        # Return bad request.
        message = 'car not connected'
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Send movement data to car
    _send_JSON(server, car_addr, body)
Exemplo n.º 12
0
    def _receive_forever(self):
        """
        Start an infinite loop that will indefinitely block on a receive until
        a new message comes in. If the message is JSON-formatted and has a
        "type" key, then the corresponding handler will be run in a new thread.
        """

        while True:
            data, addr = self.socket.recvfrom(self.BUFFER_SIZE)
            try:
                body = json.loads(data)
            except json.JSONDecodeError:
                logging.debug('Received invalid JSON')
                self.send(Error.json(Error.BAD_REQ, 'invalid JSON'), addr)
                continue
            if body['type'] in self.handlers:
                handler_thread = threading.Thread(
                    target=self.handlers[body['type']],
                    args=(self, body, addr))
                handler_thread.start()
            else:
                logging.debug('Invalid message type', body)
                self.send(Error.json(Error.BAD_REQ, 'invalid message type'),
                          addr)
Exemplo n.º 13
0
    def receive(self):
        """
        Receive a new message and return the data and source address. If the
        message is JSON-formatted, then it will return the parsed body and the
        source address. If not, it will send an error to the sender.
        """

        data, addr = self.socket.recvfrom(self.BUFFER_SIZE)
        try:
            body = json.loads(data)
        except json.JSONDecodeError:
            logging.debug('Received invalid JSON')
            self.send(Error.json(Error.BAD_REQ, 'invalid JSON'), addr)
            return None, None

        return body, addr
Exemplo n.º 14
0
def handle_register_car(server, body, source):
    logging.debug('REGISTER CAR')
    '''
    1. Add a row in the 'cars' table
    2. Send a confirmation (ACK) back to the car
    '''

    ip = source[0]

    # Check data is valid. if not, send an error packet
    if 'name' not in body or 'user_id' not in body:
        message = 'missing field: "name", "user_id" required'
        logging.debug(message)
        server.send(Error.json(Error.BAD_REQ, message), source)
        return

    # Get JSON data
    name = body['name']
    user_id = body['user_id']

    if not isinstance(name, str) or not isinstance(user_id, int):
        msg = '"name" must be a string and "user_id" must be an integer'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), source)
        return

    with server.get_db() as (dbconnect, cursor):
        # Check that the user exists in the database
        cursor.execute("select * from users where id=(?)", [user_id])
        entry = cursor.fetchone()
        # Send error packet
        if entry is None:
            message = "User is not registered"
            logging.debug(message)
            server.send(Error.json(Error.BAD_REQ, message), source)
            return

        # Check that the user does not already have car with that name
        cursor.execute("select * from cars where name=(?) and userID=(?)",
                       (name, user_id))
        entry = cursor.fetchone()
        # Send error if car already exists
        if entry is None:
            cursor.execute(
                "insert into cars (name,ip,userID,isOn) values(?,?,?,?)",
                (name, ip, user_id, 0))
            dbconnect.commit()
        else:
            message = "Car name already registered"
            logging.debug(message)
            server.send(Error.json(Error.BAD_REQ, message), source)
            return

        car_id = cursor.lastrowid

    with open(HAPROXY_CFG, 'r+') as cfg:
        content = cfg.read()

        cfg.seek(0)
        cfg.truncate()

        # Insert ACL based on car ID
        acl_regex = r'\1    acl url_car{0} path_beg /{0}\n'.format(car_id)
        content = re.sub(ACL_START_REGEX, acl_regex, content)

        # Insert rule based on ACL
        rule_regex = r'\1    use_backend car{0} if url_car{0}\n'.format(car_id)
        content = re.sub(RULE_START_REGEX, rule_regex, content)

        # Insert backend based on car IP
        backend_regex = \
r'''\1backend car{0}
    mode http
    reqrep ^([^\ ]*\ /){0}[/]?(.*)     \\1\\2
    server car{0}_app {1}:8000 check\n\n'''.format(car_id, ip)
        content = re.sub(BACKEND_START_REGEX, backend_regex, content)

        cfg.write(content)

    subprocess.run(['systemctl', 'restart', 'haproxy'])

    # Send Confirmation to App
    logging.debug("Car registration successful")
    ackJSON = {"type": MsgType.ACK, "car_id": car_id}
    _send_JSON(server, source, ackJSON)
Exemplo n.º 15
0
def handle_connect_wifi(server, body, addr):
    """
    Connects to the specified network in the body.
    Raises an exception if connection fails.

    Arguments:
    server -- instance of Server class
    body -- JSON body of UDP packet received
    addr -- source destination of received UDP packet
    """
    logging.debug('WIFI CONN')
    if 'ssid' not in body or 'user_id' not in body or 'car_name' not in body:
        msg = 'body must include "ssid" and "user_id" and "car_name" fields'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    ssid = body['ssid']

    if not isinstance(ssid, str):
        msg = '"ssid" must be a string'
        logging.debug(msg)
        server.send(Error.json(Error.BAD_REQ, msg), addr)
        return

    if 'password' not in body:
        argList = ['./shell-scripts/connect-wifi.sh', ssid]
    else:
        password = body['password']
        if not isinstance(password, str):
            msg = '"password" must be a string'
            logging.debug(msg)
            server.send(Error.json(Error.BAD_REQ, msg), addr)
            return

        argList = ['./shell-scripts/connect-wifi.sh', ssid, password]

    try:
        process = subprocess.run(argList, check=True, capture_output=True,
                text=True)
    except subprocess.CalledProcessError as err:
        logging.debug("connect-wifi.sh exited with code {1:d}: {2}"\
                .format(ssid, err.returncode, err.output))
        raise Exception("Failed to connect to {0} network".format(ssid))
    except Exception as err:
        logging.debug("error running connect-wifi.sh: {0}".format(str(err)))
        raise Exception("Failed to connect to {0} network".format(ssid))

    logging.debug('wifi connection succeeded, sending ACK to app')

    # Respond to client with an ACK to confirm the connection was successful.
    # Retry if client sends the WIFI_CONN message within 10 seconds to indicate
    # that the ACK message was dropped.
    server.socket.settimeout(10)
    while True:
        server.send(json.dumps({'type': MsgType.ACK}).encode('utf-8'), addr)
        try:
            body, addr = server.receive()
        except socket.timeout:
            # WIFI_CONN message was not retried therefore the ACK was received
            # successfully
            logging.debug('ACK received by app')
            break

        logging.debug('retry ACK send')

    server.socket.settimeout(None)

    logging.debug('stopping access point')
    subprocess.run('./shell-scripts/stop-ap.sh')

    # Update metadata with user ID
    server.metadata.set_user_id(body['user_id'])
    server.metadata.set_car_name(body['car_name'])

    raise CloseServer