Skip to content

Commit

Permalink
Add virtual rockblock web server
Browse files Browse the repository at this point in the history
  • Loading branch information
samdai01 authored Sep 14, 2024
1 parent 796452f commit d44f9a7
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
2 changes: 2 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ if [ -d $NET_DIR ]; then
pushd $NET_DIR
./scripts/sailbot_db sailbot_db --clear
./scripts/sailbot_db sailbot_db --populate
python3 scripts/rockblock_web_server.py &
ROCKBLOCK_SERVER_PID=$!
popd
fi

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <curl/curl.h>
#include <gtest/gtest.h>

#include <array>
Expand All @@ -7,6 +8,7 @@
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <random>
#include <span>
#include <string>
Expand Down Expand Up @@ -67,6 +69,12 @@ class TestRemoteTransceiver : public ::testing::Test

TestRemoteTransceiver() { g_test_db.cleanDB(); }

static size_t WriteCallback(void * contents, size_t size, size_t nmemb, void * userp)
{
(static_cast<std::string *>(userp))->append(static_cast<char *>(contents), size * nmemb);
return size * nmemb;
}

~TestRemoteTransceiver() override {}
};

Expand Down Expand Up @@ -199,3 +207,36 @@ TEST_F(TestRemoteTransceiver, TestPostSensorsMult)
// Check that DB is updated properly for all requests
EXPECT_TRUE(g_test_db.verifyDBWrite(expected_sensors, expected_info));
}

TEST_F(TestRemoteTransceiver, rockblockWebServerExample)
{
CURL * curl;
CURLcode res;
std::string readBuffer;

curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();

if (curl != nullptr) {
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:8100/?data=B&imei=300434065264590&username=myuser");

curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);

curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);

res = curl_easy_perform(curl);

if (res != CURLE_OK) {
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
} else {
std::cout << "Response data: " << readBuffer << std::endl;
EXPECT_EQ("FAILED,11,No RockBLOCK with this IMEI found on your account", readBuffer);
}

curl_easy_cleanup(curl);
}

curl_global_cleanup();
}
13 changes: 13 additions & 0 deletions src/network_systems/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,16 @@ Wrapper for the [SailbotDB Utility DB tool](../lib/sailbot_db/src/main.cpp).
- Requires network_systems to be built
- Run with `--help` for full details on how to run
- Can clear, populate, and dump data from a DB

## Rockblock Web Server

This web server is a virtual implementation of the Rockblock Web Servers used to send MT messages. The specification for
the RockBlock Web Servers can be found
[here](https://docs.groundcontrol.com/iot/rockblock/web-services/sending-mt-message).

This virtual server follows this specification with one notable difference, it will return the specific error code that
is contained in the `data` section of the request sent to it.

So a request to this url: <http://localhost:8100/?data=B&imei=300434065264590&username=myuser> will return
`FAILED,11, No RockBLOCK with this IMEI found on your account` since `data=B` and B is hex for 11 which is the
corresponding error code as mentioned above.
102 changes: 102 additions & 0 deletions src/network_systems/scripts/rockblock_web_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import http.server
import logging
import random
import signal
import socketserver
import sys
from urllib import parse

# Define port and error code descriptions
PORT = 8100

error_codes = {
10: "Invalid login credentials",
11: "No RockBLOCK with this IMEI found on your account",
12: "RockBLOCK has no line rental",
13: "Your account has insufficient credit",
14: "Could not decode hex data",
15: "Data too long",
16: "No data",
99: "System Error",
}

# Set up logging
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)


# Custom HTTP request handler
class MyHandler(http.server.BaseHTTPRequestHandler):

def do_POST(self):
logger.debug("Received POST request.")
# Extract data parameter from the request
data = self.get_data()
logger.debug(f"Extracted data: {data}")

# Check if data is not empty
if data:
try:
# Decode the hex data
error_code = int(data, 16)
logger.debug(f"Decoded error code: {error_code}")
except ValueError:
# If decoding fails, return error code 99
logger.error("Failed to decode data. Using error code 99.")
error_code = 99
else:
# Default error code if data is empty
logger.warning("No data found. Using error code 99.")
error_code = 99

# Handle error codes and special case for error_code 0
if error_code == 0:
random_number = random.randint(1, 10000) # Generate a random positive integer
response = f'OK,"{random_number}"'
logger.info(f"Generated response: {response}")
elif error_code in error_codes:
response = f"FAILED,{error_code},{error_codes[error_code]}"
logger.info(f"Generated response: {response}")
else:
response = f"FAILED,99,{error_codes[99]}"
logger.info(f"Generated response: {response}")

# Send response status code
self.send_response(200)

# Send headers
self.send_header("Content-type", "text/plain")
self.end_headers()

# Write the response content
self.wfile.write(response.encode("utf-8"))

def get_data(self):
"""Extracts the data parameter from the URL query string (e.g., ?data=48656C6C6F)"""
query = parse.urlsplit(self.path).query
params = dict(parse.parse_qsl(query))
return params.get("data", "")


# Set up the server
def run_server():
with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
logger.info(f"Serving on port {PORT}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
logger.info("Server is shutting down...")


# Handle graceful shutdown
def signal_handler(signal, frame):
logger.info("Received exit signal, shutting down...")
sys.exit(0)


# Set up signal handling
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

if __name__ == "__main__":
run_server()

0 comments on commit d44f9a7

Please sign in to comment.