From: quzar <qu...@us...> - 2024-09-30 23:11:06
|
This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "A pseudo Operating System for the Dreamcast.". The branch, master has been updated via 495e77fd60d5b09a1ad52a26cd4a7e73cd0d9d51 (commit) via 91f5e7a55233f7b3daa01db68a169f20faa586ad (commit) from f9786ba4a0532de4ccf53635319993c9d643e97a (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 495e77fd60d5b09a1ad52a26cd4a7e73cd0d9d51 Author: Andy Barajas <and...@gm...> Date: Mon Sep 30 16:00:34 2024 -0700 Network speedtest (#671) Add bba/lan network speed test example. commit 91f5e7a55233f7b3daa01db68a169f20faa586ad Author: Paul Cercueil <pa...@cr...> Date: Mon Sep 30 12:01:56 2024 +0200 CMake: Use kos-cc as the assembler (#782) CMake does detect the GNU assembler correctly, but does not seem to know how to use it properly; it passes flags it shouldn't (e.g. -with-sysroot or -isystem), or flags with the incorrect arguments. This is because on most (all?) GNU platforms, CMake is configured to use GCC itself as the assembler. In that case, it passes the correct (and different) flags which makes it able to compile assembler files. Fix compilation of assembler files in CMake by using kos-cc as the assembler. Signed-off-by: Paul Cercueil <pa...@cr...> ----------------------------------------------------------------------- Summary of changes: examples/dreamcast/network/Makefile | 3 + .../{2ndmix => network/speedtest}/Makefile | 13 +- .../dreamcast/network/speedtest/handle_request.c | 307 +++++++++++++++++++++ .../dreamcast/network/speedtest/romdisk/index.html | 118 ++++++++ examples/dreamcast/network/speedtest/server.c | 71 +++++ examples/dreamcast/network/speedtest/speedtest.c | 47 ++++ examples/dreamcast/network/speedtest/speedtest.h | 29 ++ utils/cmake/dreamcast.toolchain.cmake | 2 +- 8 files changed, 582 insertions(+), 8 deletions(-) copy examples/dreamcast/{2ndmix => network/speedtest}/Makefile (61%) create mode 100644 examples/dreamcast/network/speedtest/handle_request.c create mode 100644 examples/dreamcast/network/speedtest/romdisk/index.html create mode 100644 examples/dreamcast/network/speedtest/server.c create mode 100644 examples/dreamcast/network/speedtest/speedtest.c create mode 100644 examples/dreamcast/network/speedtest/speedtest.h diff --git a/examples/dreamcast/network/Makefile b/examples/dreamcast/network/Makefile index b9627dcb..7d200aa3 100644 --- a/examples/dreamcast/network/Makefile +++ b/examples/dreamcast/network/Makefile @@ -13,6 +13,7 @@ all: $(KOS_MAKE) -C httpd $(KOS_MAKE) -C isp-settings $(KOS_MAKE) -C ntp + $(KOS_MAKE) -C speedtest clean: $(KOS_MAKE) -C basic clean @@ -23,6 +24,7 @@ clean: $(KOS_MAKE) -C httpd clean $(KOS_MAKE) -C isp-settings clean $(KOS_MAKE) -C ntp clean + $(KOS_MAKE) -C speedtest clean dist: $(KOS_MAKE) -C basic dist @@ -33,3 +35,4 @@ dist: $(KOS_MAKE) -C httpd dist $(KOS_MAKE) -C isp-settings dist $(KOS_MAKE) -C ntp dist + $(KOS_MAKE) -C speedtest dist diff --git a/examples/dreamcast/2ndmix/Makefile b/examples/dreamcast/network/speedtest/Makefile similarity index 61% copy from examples/dreamcast/2ndmix/Makefile copy to examples/dreamcast/network/speedtest/Makefile index 0e3b214d..9fa00bee 100644 --- a/examples/dreamcast/2ndmix/Makefile +++ b/examples/dreamcast/network/speedtest/Makefile @@ -1,11 +1,11 @@ -# KallistiOS ##version## # -# 2ndmix/Makefile -# Copyright (C)2003 Megan Potter +# KallistiOS network/speedtest example +# +# Copyright (C) 2024 Andress Barajas # -TARGET = 2ndmix.elf -OBJS = 2ndmix.o romdisk.o +TARGET = speedtest.elf +OBJS = speedtest.o server.o handle_request.o romdisk.o KOS_ROMDISK_DIR = romdisk all: rm-elf $(TARGET) @@ -22,9 +22,8 @@ $(TARGET): $(OBJS) kos-cc -o $(TARGET) $(OBJS) run: $(TARGET) - $(KOS_LOADER) $(TARGET) + $(KOS_LOADER) $(TARGET) -n dist: $(TARGET) -rm -f $(OBJS) romdisk.img $(KOS_STRIP) $(TARGET) - diff --git a/examples/dreamcast/network/speedtest/handle_request.c b/examples/dreamcast/network/speedtest/handle_request.c new file mode 100644 index 00000000..b64c5638 --- /dev/null +++ b/examples/dreamcast/network/speedtest/handle_request.c @@ -0,0 +1,307 @@ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "speedtest.h" + +/* + This file defines the handle_request function, which processes HTTP requests + received by the speed test server. It parses the request, determines the HTTP + method (GET or POST), and routes the request to the appropriate handler based + on the requested path. It supports GET requests to serve files such as + index.html and handles download and upload test endpoints to measure network + speed. + + Key paths: + - "/": serves the speed test HTML page (index.html). + - "/download-test?size=": initiates a download test of the requested size. + - "/upload-test": handles data uploads during an upload test. + */ + + +bool exact_match(const char *path, const char *pattern); +bool prefix_match(const char *path, const char *pattern); + +int send_ok(http_state_t *h, const char *ct); +void send_code(int socket, uint16_t code, const char *message); + +#define BUFSIZE 1024 +#define REQUEST_LINE_SIZE 160 +#define HEADER_BUF_SIZE 512 + +void *handle_request(void *p) { + char request_line[REQUEST_LINE_SIZE] = {0}; + char *buf_ptr = request_line; + char *path_end; + size_t total_bytes = 0; + http_state_t *hr = (http_state_t *)p; + + /* Read the max we expect the request line to be */ + total_bytes = recv(hr->socket, request_line, REQUEST_LINE_SIZE-1, MSG_NONE); + if(total_bytes <= 0) { + send_code(hr->socket, 400, "Bad Request."); + goto process_request_out; + } + request_line[total_bytes] = '\0'; + + /* If first byte is 'G' we have GET */ + if(*buf_ptr == 'G') { + hr->method = METHOD_GET; + buf_ptr += 4; /* Move to the start of path */ + } else if(*buf_ptr == 'P' && *(buf_ptr+1) == 'O') { + hr->method = METHOD_POST; + buf_ptr += 5; /* Move to the start of path */ + } else { + send_code(hr->socket, 501, "Method not implemented."); + goto process_request_out; + } + + /* Find the end of path and put \0. */ + path_end = strchr(buf_ptr, ' '); + if(!path_end) { + send_code(hr->socket, 414, "Request-URI Too Long."); + goto process_request_out; + } + *path_end = '\0'; /* Replace space with terminator */ + hr->path = buf_ptr; + buf_ptr = path_end + 1; + + printf("%s\n", hr->path); + + if(hr->method == METHOD_POST) { + char *content_length_key = "Content-Length: "; + char *cl_key_ptr = content_length_key; + + char *body_start_key = "\r\n\r\n"; + char *bs_key_ptr = body_start_key; + + char buf[HEADER_BUF_SIZE] = {0}; + + char *found; + char *buf_start = request_line; + bool got_content_length = false; + size_t bytes_left = total_bytes - (buf_ptr - buf_start); + + printf("RL: %s\n", buf_ptr); + + /* Look for Content-Length header(optional) and start of the body */ + while(true) { + /* Iterate over each byte trying to match */ + while(bytes_left > 0) { + /* Look for optional Content-Length header */ + if(!got_content_length) { +find_length: + if(*buf_ptr == *cl_key_ptr) { + while(bytes_left-- && *cl_key_ptr != '\0' && *buf_ptr++ == *cl_key_ptr++); + if(*cl_key_ptr == '\0') { /* We found Content-Length key */ + /* Get the count */ + hr->rem_content_length = atoi(buf_ptr); + /* Check that we grabbed the full number */ + found = strstr(buf_ptr, "\r\n"); + if(found) { + buf_ptr = found; + got_content_length = true; + + goto find_body; + } + } + else if(bytes_left > 0) { /* Start over */ + cl_key_ptr = content_length_key; + + /* Rewind back one char */ + buf_ptr--; + bytes_left++; + /* Fall through to find_body */ + } + else /* bytes_left == 0 */ + goto refresh_buffer; + } + } +find_body: + /* Look for body start */ + if(*buf_ptr == *bs_key_ptr) { + while(bytes_left-- && *bs_key_ptr != '\0' && *buf_ptr++ == *bs_key_ptr++); + if(*bs_key_ptr == '\0') { /* We found body start key */ + hr->body = buf_ptr; + /* How much of the body do we already have? */ + hr->read_content_length = total_bytes - (hr->body - buf_start); + if(got_content_length) + hr->rem_content_length -= hr->read_content_length; + + goto done_parsing; + } + else if(bytes_left > 0) /* Start over */ { + bs_key_ptr = body_start_key; + + if(!got_content_length) { + /* Rewind back one char */ + buf_ptr--; + bytes_left++; + goto find_length; + } + } + else /* bytes_left == 0 */ + goto refresh_buffer; + } + + /* Should only hit here if neither matched at the starting character */ + buf_ptr++; + bytes_left--; + } + +refresh_buffer: + /* We get here when we are done searching through current buffer */ + total_bytes = recv(hr->socket, buf, HEADER_BUF_SIZE-1, MSG_NONE); + if(total_bytes <= 0) { + send_code(hr->socket, 400, "Bad Request."); + goto process_request_out; + } + buf[total_bytes] = '\0'; + buf_start = buf; + buf_ptr = buf; + bytes_left = total_bytes; + + /* If we have already found Content-Length, skip searching it again */ + if(got_content_length) + goto find_body; + } + } + +done_parsing: + char response_buf[BUFSIZE]; + if(hr->method == METHOD_GET) { + file_t file = -1; + uint32_t offset; + int cnt; + int wrote; + + /* index.html */ + if(exact_match(hr->path, "") || exact_match(hr->path, "/")) { + file = fs_open("/rd/index.html", O_RDONLY); + + send_ok(hr, "text/html"); + while((cnt = fs_read(file, response_buf, BUFSIZE)) != 0) { + offset = 0; + + while(cnt > 0) { + wrote = send(hr->socket, response_buf + offset, cnt, MSG_NONE); + + if(wrote <= 0) + break; + + cnt -= wrote; + offset += wrote; + } + } + + fs_close(file); + goto process_request_out; + } + else if(prefix_match(hr->path, "/download-test")) { + char *size_str; + size_t size; + + /* Extract size parameter from path */ + size_str = strstr(hr->path, "size="); + if(size_str) { + size = strtoul(size_str + 5, NULL, 10); + if(size <= 0 || size > 16*1024*1024) { + /* Send ERROR Code with reason */ + send_code(hr->socket, 400, "GET download: 'size' is out of range (1 - 16*1024*1024)"); + } + + send_ok(hr, "application/octet-stream"); + uintptr_t data = 0x8000000; + offset = 0; + while(size > 0) { + wrote = send(hr->socket, (uint8_t *)data + offset, size, MSG_NONE); + + if(wrote <= 0) + break; + + size -= wrote; + offset += wrote; + } + + goto process_request_out; + } else { + /* Send ERROR Code with reason */ + send_code(hr->socket, 400, "GET download: Missing required params (size)"); + } + } + } + else { /* POST */ + if(exact_match(hr->path, "/upload-test")) { + while(hr->rem_content_length) { + total_bytes = recv(hr->socket, response_buf, HEADER_BUF_SIZE - 1, MSG_NONE); + + hr->rem_content_length -= total_bytes; + } + send_code(hr->socket, 204, ""); + + goto process_request_out; + } + } + + /* If no handler was found */ + send_code(hr->socket, 404, "Invalid request or file not found."); + +process_request_out: + /* Clean up our http state and all associated allocated memory */ + close(hr->socket); + free(hr); + + /* We're now done with this thread. */ + return NULL; +} + +bool exact_match(const char *path, const char *pattern) { + if (strlen(path) != strlen(pattern)) + return false; + + return strcmp(path, pattern) == 0; +} + +bool prefix_match(const char *path, const char *pattern) { + return strncmp(path, pattern, strlen(pattern)) == 0; +} + +int send_ok(http_state_t *h, const char *ct) { + char buffer[512]; + + sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-type: %s\r\nConnection: close\r\n\r\n", ct); + send(h->socket, buffer, strlen(buffer), MSG_NONE); + + return 0; +} + +void send_code(int socket, uint16_t code, const char *message) { + /* Send HTTP response header */ + char *buf; + size_t buf_size; + + /* Calculate size of message */ + buf_size = snprintf(NULL, 0, + "HTTP/1.1 %d %s\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n" + "%s", + code, message, strlen(message), message); + + /* Allocate buf on the stack */ + buf = (char *)alloca(buf_size + 1); /* Null terminator */ + + buf_size = snprintf(buf, buf_size + 1, + "HTTP/1.1 %d %s\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %zu\r\n" + "Connection: close\r\n" + "\r\n" + "%s", + code, message, strlen(message), message); + + send(socket, buf, buf_size, MSG_NONE); +} diff --git a/examples/dreamcast/network/speedtest/romdisk/index.html b/examples/dreamcast/network/speedtest/romdisk/index.html new file mode 100644 index 00000000..288f6667 --- /dev/null +++ b/examples/dreamcast/network/speedtest/romdisk/index.html @@ -0,0 +1,118 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Dreamcast Network Speed Test</title> + <style> + body { + font-family: Arial, sans-serif; + background-color: #1d1e21; + color: white; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + } + .speed-test-container { + width: 300px; + text-align: center; + background-color: #2a2b2e; + padding: 20px; + border-radius: 10px; + } + .speed-text { + font-size: 24px; + font-weight: bold; + margin: 10px 0; + } + .test-type { + font-size: 14px; + margin-top: 10px; + opacity: 0.7; + } + button { + margin-top: 20px; + padding: 10px 20px; + font-size: 16px; + background-color: #007bff; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + } + </style> +</head> +<body> + <div class="speed-test-container"> + <h2>Dreamcast Network Speed Test</h2> + <div class="speed-text" id="download-speed">Download: -- MB/s</div> + <div class="speed-text" id="upload-speed">Upload: -- KB/s</div> + <div class="test-type" id="test-status">Ready</div> + <button onclick="startTest()">Start Test</button> + </div> + + <script> + const downloadUrl = '/download-test?size=1048576'; // 1 MB + const uploadUrl = '/upload-test'; + const numRuns = 5; + + async function measureDownloadSpeed() { + const startTime = Date.now(); + const response = await fetch(downloadUrl); + const data = await response.blob(); + const endTime = Date.now(); + const duration = (endTime - startTime) / 1000; // time in seconds + const fileSize = data.size / (1024 * 1024); // size in MB + const speed = fileSize / duration; // speed in MB/s + return speed; + } + + async function measureUploadSpeed() { + const data = new ArrayBuffer(10 * 1024); // 10 KB + const startTime = Date.now(); + await fetch(uploadUrl, { method: 'POST', body: data }); + const endTime = Date.now(); + const duration = (endTime - startTime) / 1000; // time in seconds + const fileSize = data.byteLength / 1024; // size in KB + const speed = fileSize / duration; // speed in KB/s + return speed; + } + + async function runTests() { + let downloadTotal = 0; + let uploadTotal = 0; + + for (let i = 0; i < numRuns; i++) { + document.getElementById('test-status').textContent = `Download test ${i + 1}/${numRuns}`; + const downloadSpeed = await measureDownloadSpeed(); + downloadTotal += downloadSpeed; + console.log(`Download test ${i + 1}: ${downloadSpeed.toFixed(2)} MB/s`); + } + + const avgDownload = (downloadTotal / numRuns).toFixed(2); + document.getElementById('download-speed').textContent = `Download: ${avgDownload} MB/s`; + + for (let i = 0; i < numRuns; i++) { + document.getElementById('test-status').textContent = `Upload test ${i + 1}/${numRuns}`; + const uploadSpeed = await measureUploadSpeed(); + uploadTotal += uploadSpeed; + console.log(`Upload test ${i + 1}: ${uploadSpeed.toFixed(2)} KB/s`); + } + + const avgUpload = (uploadTotal / numRuns).toFixed(2); + document.getElementById('upload-speed').textContent = `Upload: ${avgUpload} KB/s`; + + document.getElementById('test-status').textContent = 'Test complete'; + } + + async function startTest() { + document.getElementById('download-speed').textContent = 'Download: -- MB/s'; + document.getElementById('upload-speed').textContent = 'Upload: -- KB/s'; + document.getElementById('test-status').textContent = 'Starting tests...'; + await runTests(); + } + </script> +</body> ...<truncated>... hooks/post-receive -- A pseudo Operating System for the Dreamcast. |