1#!/usr/bin/env python3
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import importlib
18import logging
19
20MOBLY_CONTROLLER_CONFIG_NAME = "Sniffer"
21ACTS_CONTROLLER_REFERENCE_NAME = "sniffers"
22
23
24def create(configs):
25    """Initializes the sniffer structures based on the JSON configuration. The
26    expected keys are:
27
28    Type: A first-level type of sniffer. Planned to be 'local' for sniffers
29        running on the local machine, or 'remote' for sniffers running
30        remotely.
31    SubType: The specific sniffer type to be used.
32    Interface: The WLAN interface used to configure the sniffer.
33    BaseConfigs: A dictionary specifying baseline configurations of the
34        sniffer. Configurations can be overridden when starting a capture.
35        The keys must be one of the Sniffer.CONFIG_KEY_* values.
36    """
37    objs = []
38    for c in configs:
39        sniffer_type = c["Type"]
40        sniffer_subtype = c["SubType"]
41        interface = c["Interface"]
42        base_configs = c["BaseConfigs"]
43        module_name = "acts.controllers.sniffer_lib.{}.{}".format(
44            sniffer_type, sniffer_subtype)
45        module = importlib.import_module(module_name)
46        objs.append(
47            module.Sniffer(interface,
48                           logging.getLogger(),
49                           base_configs=base_configs))
50    return objs
51
52
53def destroy(objs):
54    """Destroys the sniffers and terminates any ongoing capture sessions.
55    """
56    for sniffer in objs:
57        try:
58            sniffer.stop_capture()
59        except SnifferError:
60            pass
61
62
63class SnifferError(Exception):
64    """This is the Exception class defined for all errors generated by
65    Sniffer-related modules.
66    """
67
68
69class InvalidDataError(Exception):
70    """This exception is thrown when invalid configuration data is passed
71    to a method.
72    """
73
74
75class ExecutionError(SnifferError):
76    """This exception is thrown when trying to configure the capture device
77    or when trying to execute the capture operation.
78
79    When this exception is seen, it is possible that the sniffer module is run
80    without sudo (for local sniffers) or keys are out-of-date (for remote
81    sniffers).
82    """
83
84
85class InvalidOperationError(SnifferError):
86    """Certain methods may only be accessed when the instance upon which they
87    are invoked is in a certain state. This indicates that the object is not
88    in the correct state for a method to be called.
89    """
90
91
92class Sniffer(object):
93    """This class defines an object representing a sniffer.
94
95    The object defines the generic behavior of sniffers - irrespective of how
96    they are implemented, or where they are located: on the local machine or on
97    the remote machine.
98    """
99
100    CONFIG_KEY_CHANNEL = "channel"
101
102    def __init__(self, interface, logger, base_configs=None):
103        """The constructor for the Sniffer. It constructs a sniffer and
104        configures it to be ready for capture.
105
106        Args:
107            interface: A string specifying the interface used to configure the
108                sniffer.
109            logger: ACTS logger object.
110            base_configs: A dictionary containing baseline configurations of the
111                sniffer. These can be overridden when staring a capture. The
112                keys are specified by Sniffer.CONFIG_KEY_*.
113
114        Returns:
115            self: A configured sniffer.
116
117        Raises:
118            InvalidDataError: if the config_path is invalid.
119            NoPermissionError: if an error occurs while configuring the
120                sniffer.
121        """
122        raise NotImplementedError("Base class should not be called directly!")
123
124    def get_descriptor(self):
125        """This function returns a string describing the sniffer. The specific
126        string (and its format) is up to each derived sniffer type.
127
128        Returns:
129            A string describing the sniffer.
130        """
131        raise NotImplementedError("Base class should not be called directly!")
132
133    def get_type(self):
134        """This function returns the type of the sniffer.
135
136        Returns:
137            The type (string) of the sniffer. Corresponds to the 'Type' key of
138            the sniffer configuration.
139        """
140        raise NotImplementedError("Base class should not be called directly!")
141
142    def get_subtype(self):
143        """This function returns the sub-type of the sniffer.
144
145        Returns:
146            The sub-type (string) of the sniffer. Corresponds to the 'SubType'
147            key of the sniffer configuration.
148        """
149        raise NotImplementedError("Base class should not be called directly!")
150
151    def get_interface(self):
152        """This function returns The interface used to configure the sniffer,
153        e.g. 'wlan0'.
154
155        Returns:
156            The interface (string) used to configure the sniffer. Corresponds to
157            the 'Interface' key of the sniffer configuration.
158        """
159        raise NotImplementedError("Base class should not be called directly!")
160
161    def get_capture_file(self):
162        """The sniffer places a capture in the logger directory. This function
163        enables the caller to obtain the path of that capture.
164
165        Returns:
166            The full path of the current or last capture.
167        """
168        raise NotImplementedError("Base class should not be called directly!")
169
170    def start_capture(self,
171                      override_configs=None,
172                      additional_args=None,
173                      duration=None,
174                      packet_count=None):
175        """This function starts a capture which is saved to the specified file
176        path.
177
178        Depending on the type/subtype and configuration of the sniffer the
179        capture may terminate on its own or may require an explicit call to the
180        stop_capture() function.
181
182        This is a non-blocking function so a terminating function must be
183        called - either explicitly or implicitly:
184        - Explicitly: call either stop_capture() or wait_for_capture()
185        - Implicitly: use with a with clause. The wait_for_capture() function
186                      will be called if a duration is specified (i.e. is not
187                      None), otherwise a stop_capture() will be called.
188
189        The capture is saved to a file in the log path of the logger. Use
190        the get_capture_file() to get the full path to the current or most
191        recent capture.
192
193        Args:
194            override_configs: A dictionary which is combined with the
195                base_configs ("BaseConfigs" in the sniffer configuration). The
196                keys (specified by Sniffer.CONFIG_KEY_*) determine the
197                configuration of the sniffer for this specific capture.
198            additional_args: A string specifying additional raw
199                command-line arguments to pass to the underlying sniffer. The
200                interpretation of these flags is sniffer-dependent.
201            duration: An integer specifying the number of seconds over which to
202                capture packets. The sniffer will be terminated after this
203                duration. Used in implicit mode when using a 'with' clause. In
204                explicit control cases may have to be performed using a
205                sleep+stop or as the timeout argument to the wait function.
206            packet_count: An integer specifying the number of packets to capture
207                before terminating. Should be used with duration to guarantee
208                that capture terminates at some point (even if did not capture
209                the specified number of packets).
210
211        Returns:
212            An ActiveCaptureContext process which can be used with a 'with'
213            clause.
214
215        Raises:
216            InvalidDataError: for invalid configurations
217            NoPermissionError: if an error occurs while configuring and running
218                the sniffer.
219        """
220        raise NotImplementedError("Base class should not be called directly!")
221
222    def stop_capture(self):
223        """This function stops a capture and guarantees that the capture is
224        saved to the capture file configured during the start_capture() method.
225        Depending on the type of the sniffer the file may previously contain
226        partial results (e.g. for a local sniffer) or may not exist until the
227        stop_capture() method is executed (e.g. for a remote sniffer).
228
229        Depending on the type/subtype and configuration of the sniffer the
230        capture may terminate on its own without requiring a call to this
231        function. In such a case it is still necessary to call either this
232        function or the wait_for_capture() function to make sure that the
233        capture file is moved to the correct location.
234
235        Raises:
236            NoPermissionError: No permission when trying to stop a capture
237                and save the capture file.
238        """
239        raise NotImplementedError("Base class should not be called directly!")
240
241    def wait_for_capture(self, timeout=None):
242        """This function waits for a capture to terminate and guarantees that
243        the capture is saved to the capture file configured during the
244        start_capture() method. Depending on the type of the sniffer the file
245        may previously contain partial results (e.g. for a local sniffer) or
246        may not exist until the stop_capture() method is executed (e.g. for a
247        remote sniffer).
248
249        Depending on the type/subtype and configuration of the sniffer the
250        capture may terminate on its own without requiring a call to this
251        function. In such a case it is still necessary to call either this
252        function or the stop_capture() function to make sure that the capture
253        file is moved to the correct location.
254
255        Args:
256            timeout: An integer specifying the number of seconds to wait for
257                the capture to terminate on its own. On expiration of the
258                timeout the sniffer is stopped explicitly using the
259                stop_capture() function.
260
261        Raises:
262            NoPermissionError: No permission when trying to stop a capture and
263                save the capture file.
264        """
265        raise NotImplementedError("Base class should not be called directly!")
266
267
268class ActiveCaptureContext(object):
269    """This class defines an object representing an active sniffer capture.
270
271    The object is returned by a Sniffer.start_capture() command and terminates
272    the capture when the 'with' clause exits. It is syntactic sugar for
273    try/finally.
274    """
275
276    _sniffer = None
277    _timeout = None
278
279    def __init__(self, sniffer, timeout=None):
280        self._sniffer = sniffer
281        self._timeout = timeout
282
283    def __enter__(self):
284        pass
285
286    def __exit__(self, type, value, traceback):
287        if self._sniffer is not None:
288            if self._timeout is None:
289                self._sniffer.stop_capture()
290            else:
291                self._sniffer.wait_for_capture(self._timeout)
292        self._sniffer = None
293