Decorator to mark a function as invoking a remote procedure call.
    When invoked in server mode, the function will be called; when
    invoked in client mode, an RPC will be initiated.

def remote(func):
    """
    Decorator to mark a function as invoking a remote procedure call.
    When invoked in server mode, the function will be called; when
    invoked in client mode, an RPC will be initiated.
    """

    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        if self.mode == 'server':
            # In server mode, call the function
            return func(self, *args, **kwargs)

        # Make sure we're connected
        if not self.conn:
            self.connect()

        # Call the remote function
        self.conn.send('CALL', func.__name__, args, kwargs)

        # Receive the response
        cmd, payload = self.conn.recv()
        if cmd == 'ERR':
            self.close()
            raise Exception("Catastrophic error from server: %s" %
                            payload[0])
        elif cmd == 'EXC':
            exc_type = utils.find_entrypoint(None, payload[0])
            raise exc_type(payload[1])
        elif cmd != 'RES':
            self.close()
            raise Exception("Invalid command response from server: %s" % cmd)

        return payload[0]

    # Mark it a callable
    wrapper._remote = True

    # Return the wrapped function
    return wrapper