Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Jamming Avoidance code #338

Merged
merged 13 commits into from
Dec 5, 2023
8 changes: 8 additions & 0 deletions common/tests/functional/test_jamming/add_syspath.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os
import sys

# Path to the jamming_detection directory
module_path = '/opt/mesh_com/modules/sc-mesh-secure-deployment/src/2_0/features/jamming'

# Append the path to the module directory to the system path
sys.path.append(module_path)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from unittest.mock import patch

import add_syspath
from channel_quality_estimator import ChannelQualityEstimator
from channel_quality_est import ChannelQualityEstimator


class TestCodeUnderTest:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def test_receive_message_unknown_action_id(self, mocker):
receive_thread.join()

# Assert that logger.error was called with the expected message
error_mock.assert_called_with("Error in received message: int is not allowed for map key when strict_map_key=True")
error_mock.assert_called_with('Error in received message: int is not allowed for map key')

# Assert that no state transition occurred
assert client.fsm.state == ClientState.IDLE
Expand Down
101 changes: 101 additions & 0 deletions common/tests/functional/test_jamming/test_jamming_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import pytest
import subprocess
from unittest import mock

import add_syspath
import util
from jamming_setup import is_interface_up, switch_frequency, check_client_osf_connectivity, check_osf_interface, start_jamming_scripts, start_client, start_server, validate_configuration, main
from options import Options


class TestCodeUnderTest:
"""
A suite of test cases for the code.
"""

# Mesh frequency is switched to starting frequency successfully
def test_switch_frequency_success(self, mocker):
# Create an Options object with debug set to True
args = Options()
args.starting_channel = 36
args.debug = True

# Mock the get_mesh_freq function to return a different frequency than the starting frequency
mocker.patch('util.get_mesh_freq', return_value=5180)
mocker.patch('util.map_channel_to_freq', return_value=5180)

# Mock the run_command function to not raise an exception
mocker.patch('util.switch_frequency', return_value=True)

# Run function under test, no exceptions should be returned
switch_frequency(args)

# IPv6 address of tun0 interface is retrieved successfully
def test_get_ipv6_address_success(self, mocker):
# Mock the get_ipv6_addr function to return a valid IPv6 address
mocker.patch('util.get_ipv6_addr', return_value='2001:db8::1')

# Call the function under test
ipv6_address = util.get_ipv6_addr(Options().osf_interface)

# Assert that the returned IPv6 address is correct
assert ipv6_address == '2001:db8::1'

# IPv6 client connectivity with remote server is successful
def test_check_client_osf_connectivity_success(self, mocker):
# Mock the subprocess.check_output function to not raise an exception
mocker.patch('subprocess.check_output')

# Call the function under test
check_client_osf_connectivity(Options())

# Assert that the subprocess.check_output function was called with the correct command
subprocess.check_output.assert_called_with(['ping6', '-c', '1', Options().jamming_osf_orchestrator], text=True)

# Jamming-related scripts are started successfully
def test_start_jamming_scripts_success(self, mocker):
args = Options()
args.jamming_osf_orchestrator = '2000:db1::2'

# Mock the run_command function to not raise an exception
mock_run_command = mocker.patch('util.run_command', side_effect=lambda command, error_message: (True, None))
mocker.patch('util.kill_process_by_pid', return_value=True)

# Call the function under test
start_jamming_scripts(args, '2001:db8::1')

# Assert that the run_command function was called with the correct command
mock_run_command.assert_called_with(["python", "jamming_client_fsm.py"], 'Failed to run jamming_client_fsm file')


# IPv6 connectivity with remote server fails
def test_check_client_osf_connectivity_failure(self, mocker):
# Mock the subprocess.check_output function to raise a subprocess.CalledProcessError
mocker.patch('subprocess.check_output', side_effect=subprocess.CalledProcessError(1, 'ping6'))

# Call the function under test and assert that it raises a SystemExit
with pytest.raises(SystemExit):
check_client_osf_connectivity(Options())

# is_interface_up function returns True if interface is up
def test_is_interface_up_returns_true_if_interface_is_up(self, mocker):
# Mock the netifaces.interfaces function to return a list of available interfaces
mocker.patch('netifaces.interfaces', return_value=['eth0', 'wlp1s0', 'tun0'])

# Call the function under test
result = is_interface_up('wlp1s0')

# Assert that the result is True
assert result is True

# Validate configuration fails when channels5 list includes invalid channels
def test_validate_configuration_fails_channels5_list_includes_invalid_channels(self, mocker):
args = Options()
args.channels5 = [36, 40, 44, 48, 149, 153, 157, 1600]
assert validate_configuration(args) == False

# Validate configuration succeeds when channels5 list includes valid channels
def test_validate_configuration_succeeds_channels5_list_includes_valid_channels(self, mocker):
args = Options()
args.channels5 = [36, 40, 44, 48, 149, 153, 157, 161]
assert validate_configuration(args) == True
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@ def test_options_object_created_successfully(self):
options = Options()
assert isinstance(options, Options)

# Validate configuration fails when mesh interface channel width is not set to 20 MHz
def test_validate_configuration_fails_mesh_interface_channel_width_not_set_to_20MHz(self, mocker):
options = Options()
options.validate_configuration = mocker.Mock(return_value=False)
assert options.validate_configuration() == False

# Validate configuration fails when channels5 list includes invalid channels
def test_validate_configuration_fails_channels5_list_includes_invalid_channels(self, mocker):
options = Options()
options.channels5 = [36, 40, 44, 48, 149, 153, 157, 162]
assert options.validate_configuration() == False

# Options object attributes are set correctly
def test_options_object_attributes_set_correctly(self):
Expand Down Expand Up @@ -57,14 +46,3 @@ def test_options_object_attributes_set_correctly(self):
assert options.periodic_target_freq_broadcast == 10
assert options.spectrum_data_expiry_time == 5

# Validate configuration succeeds when mesh interface channel width is set to 20 MHz
def test_validate_configuration_succeeds_mesh_interface_channel_width_set_to_20MHz(self, mocker):
options = Options()
options.validate_configuration = mocker.Mock(return_value=True)
assert options.validate_configuration() == True

# Validate configuration succeeds when channels5 list includes valid channels
def test_validate_configuration_succeeds_channels5_list_includes_valid_channels(self, mocker):
options = Options()
options.channels5 = [36, 40, 44, 48, 149, 153, 157, 161]
assert options.validate_configuration() == True
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ def test_resize_with_valid_input(self):
def test_normalize_with_valid_input(self):
# Set expected data
expected_data = {
'freq1': [5180], 'max_magnitude': [-3.00653], 'rssi': [-0.237583], 'relpwr_db': [4.563367],
'base_pwr_db': [-30.556816], 'avgpwr_db': [-9.808239], 'total_gain_db': [-4.744545], 'snr': [-1.930179],
'cnr': [-9.305355], 'pn': [-10.448828], 'ssi': [-2.702977], 'pd': [-26.652357], 'sinr': [-2.702977],
'sir': [3.455223], 'mr': [-2.898512], 'pr': [239.60759]
'freq1': [5180], 'max_magnitude': [-2.699096], 'rssi': [0.386689], 'relpwr_db': [5.723355],
'base_pwr_db': [-54.127222], 'avgpwr_db': [-7.553099], 'total_gain_db': [-8.580278], 'snr': [-4.467233],
'cnr': [-19.34198], 'pn': [-22.509996], 'ssi': [-4.161893], 'pd': [-33.130156], 'sinr': [-4.161893],
'sir': [7.230196], 'mr': [-2.481305], 'pr': [187.510565]
}
expected_result = pd.DataFrame(expected_data)

Expand Down
Empty file removed modules/__init__.py
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def _compute_channel_quality(self, probs: np.ndarray) -> np.ndarray:
:return: A 1D NumPy array of shape (n_channels,) representing the channel quality scores for each channel.
"""
# Weights for good states (communication, floor, inter_mid, inter_high)
good_weights = np.array([1.0, 0.6, 0.4])
good_weights = np.array([1.0, 0.5, 0.5])
# Compute good state probabilities
good_probs = probs[:, :3] # Get the probabilities for the first 3 states
# Compute a score based on the good state probabilities and their weights
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import socket
import sys
import threading
Expand Down Expand Up @@ -178,6 +179,8 @@ def __init__(self, node_id: str, host: str, port: int) -> None:
self.target_frequency: int = np.nan
self.best_freq: int = np.nan
self.freq_quality: dict = {}
self.num_retries = 0
self.max_retries = 3

# Time for periodic events' variables (in seconds)
self.time_last_scan: float = 0
Expand Down Expand Up @@ -370,21 +373,41 @@ def switch_frequency(self, trigger_event) -> None:
:param trigger_event: ClientEvent that triggered the execution of this function.
"""
try:
switch_frequency(str(self.target_frequency))
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(switch_frequency(str(self.target_frequency)))

# Validate outcome of switch frequency process
self.current_frequency = get_mesh_freq()

if self.current_frequency != self.target_frequency:
logger.info("Frequency switch unsuccessful")
# If maximum frequency switch retries not reached, try to switch again
if self.current_frequency != self.target_frequency and self.num_retries < self.max_retries:
logger.info(f"Frequency switch unsuccessful, retry {num_retries}")
self.num_retries += 1
self.fsm.trigger(ClientEvent.SWITCH_UNSUCCESSFUL)
else:

# If max frequency switch retries reached, and mesh frequency is NaN, mesh failed, exit jamming avoidance
elif self.num_retries == self.max_retries and np.isnan(self.current_frequency):
logger.info("Mesh failed... exiting jamming avoidance scheme")
kill_process_by_pid("jamming_setup.py")

# If max frequency switch retries reached, and mesh switched to a different frequency, continue scheme on current frequency
elif self.current_frequency != self.target_frequency and self.num_retries == self.max_retries:
logger.info("Switched to different channel... continue")
self.num_retries = 0
self.fsm.trigger(ClientEvent.SWITCH_SUCCESSFUL)

# Frequency switch successful
elif self.current_frequency == self.target_frequency:
logger.info("Frequency switch successful")
self.num_retries = 0
self.fsm.trigger(ClientEvent.SWITCHED)

except Exception as e:
logger.error(f"Switching frequency error occurred: {str(e)}")
self.fsm.trigger(ClientEvent.SWITCH_UNSUCCESSFUL)
finally:
loop.close()

def recovering_switch_error(self, trigger_event) -> None:
"""
Expand Down Expand Up @@ -443,7 +466,7 @@ def main():

host: str = args.jamming_osf_orchestrator
port: int = args.port
node_id: str = get_ipv6_addr('tun0')
node_id: str = get_ipv6_addr(args.osf_interface)

client = JammingDetectionClient(node_id, host, port)
client.start()
Expand Down
Loading
Loading