• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import logging
18import socket
19import socketserver
20import threading
21
22from vts.runners.host import errors
23from vts.proto import AndroidSystemControlMessage_pb2 as SysMsg
24from vts.proto import ComponentSpecificationMessage_pb2 as CompSpecMsg
25from vts.utils.python.mirror import pb2py
26
27_functions = dict()  # Dictionary to hold function pointers
28
29
30class VtsCallbackServerError(errors.VtsError):
31    """Raised when an error occurs in VTS TCP server."""
32
33
34class CallbackRequestHandler(socketserver.StreamRequestHandler):
35    """The request handler class for our server."""
36
37    def handle(self):
38        """Receives requests from clients.
39
40        When a callback happens on the target side, a request message is posted
41        to the host side and is handled here. The message is parsed and the
42        appropriate callback function on the host side is called.
43        """
44        header = self.rfile.readline().strip()
45        try:
46            len = int(header)
47        except ValueError:
48            if header:
49                logging.exception("Unable to convert '%s' into an integer, which "
50                                  "is required for reading the next message." %
51                                  header)
52                raise
53            else:
54                logging.error('CallbackRequestHandler received empty message header. Skipping...')
55                return
56        # Read the request message.
57        received_data = self.rfile.read(len)
58        logging.debug("Received callback message: %s", received_data)
59        request_message = SysMsg.AndroidSystemCallbackRequestMessage()
60        request_message.ParseFromString(received_data)
61        logging.debug('Handling callback ID: %s', request_message.id)
62        response_message = SysMsg.AndroidSystemCallbackResponseMessage()
63        # Call the appropriate callback function and construct the response
64        # message.
65        if request_message.id in _functions:
66            callback_args = []
67            for arg in request_message.arg:
68                callback_args.append(pb2py.Convert(arg))
69            args = tuple(callback_args)
70            _functions[request_message.id](*args)
71            response_message.response_code = SysMsg.SUCCESS
72        else:
73            logging.error("Callback function ID %s is not registered!",
74                          request_message.id)
75            response_message.response_code = SysMsg.FAIL
76
77        # send the response back to client
78        message = response_message.SerializeToString()
79        # self.request is the TCP socket connected to the client
80        self.request.sendall(message)
81
82
83class CallbackServer(object):
84    """This class creates TCPServer in separate thread.
85
86    Attributes:
87        _server: an instance of socketserver.TCPServer.
88        _port: this variable maintains the port number used in creating
89               the server connection.
90        _ip: variable to hold the IP Address of the host.
91        _hostname: IP Address to which initial connection is made.
92    """
93
94    def __init__(self):
95        self._server = None
96        self._port = 0  # Port 0 means to select an arbitrary unused port
97        self._ip = ""  # Used to store the IP address for the server
98        self._hostname = "localhost"  # IP address to which initial connection is made
99
100    def RegisterCallback(self, func_id, callback_func):
101        """Registers a callback function.
102
103        Args:
104            func_id: The ID of the callback function.
105            callback_func: The function to register.
106
107        Raises:
108            CallbackServerError is raised if the func_id is already registered.
109        """
110        if func_id in _functions:
111            raise CallbackServerError(
112                "Function ID '%s' is already registered" % func_id)
113        _functions[func_id] = callback_func
114
115    def UnregisterCallback(self, func_id):
116        """Removes a callback function from the registry.
117
118        Args:
119            func_id: The ID of the callback function to remove.
120
121        Raises:
122            CallbackServerError is raised if the func_id is not registered.
123        """
124        try:
125            _functions.pop(func_id)
126        except KeyError:
127            raise CallbackServerError(
128                "Can't remove function ID '%s', which is not registered." %
129                func_id)
130
131    def Start(self, port=0):
132        """Starts the server.
133
134        Args:
135            port: integer, number of the port on which the server listens.
136                  Default is 0, which means auto-select a port available.
137
138        Returns:
139            IP Address, port number
140
141        Raises:
142            CallbackServerError is raised if the server fails to start.
143        """
144        try:
145            self._server = socketserver.TCPServer(
146                (self._hostname, port), CallbackRequestHandler)
147            self._ip, self._port = self._server.server_address
148
149            # Start a thread with the server.
150            # Each request will be handled in a child thread.
151            server_thread = threading.Thread(target=self._server.serve_forever)
152            server_thread.daemon = True
153            server_thread.start()
154            logging.info('TcpServer %s started (%s:%s)', server_thread.name,
155                         self._ip, self._port)
156            return self._ip, self._port
157        except (RuntimeError, IOError, socket.error) as e:
158            logging.exception(e)
159            raise CallbackServerError(
160                'Failed to start CallbackServer on (%s:%s).' %
161                (self._hostname, port))
162
163    def Stop(self):
164        """Stops the server.
165        """
166        self._server.shutdown()
167        self._server.server_close()
168
169    @property
170    def ip(self):
171        return self._ip
172
173    @property
174    def port(self):
175        return self._port
176