Skip to content

Commit c84e463

Browse files
BASIC socket server support
1 parent 3507e0f commit c84e463

File tree

5 files changed

+132
-11
lines changed

5 files changed

+132
-11
lines changed

include/basic/sockets.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,86 @@ void connect_statement(struct basic_ctx* ctx);
9292
*/
9393
void sockread_statement(struct basic_ctx* ctx);
9494

95+
/**
96+
* @brief Process the UDPWRITE statement in BASIC.
97+
*
98+
* Sends a single UDP datagram to the specified destination address and port, optionally binding a source port.
99+
* Arguments are parsed from the BASIC statement and validated; on error a diagnostic is raised to the tokenizer.
100+
*
101+
* @param ctx BASIC context.
102+
*/
95103
void udpwrite_statement(struct basic_ctx* ctx);
96104

105+
/**
106+
* @brief Process the UDPBIND statement in BASIC.
107+
*
108+
* Binds a UDP “daemon” handler for the given local port so that inbound datagrams are queued for subsequent reads.
109+
* The handler stores packets in a per-port FIFO owned by the BASIC runtime.
110+
*
111+
* @param ctx BASIC context.
112+
*/
97113
void udpbind_statement(struct basic_ctx* ctx);
98114

115+
/**
116+
* @brief Process the UDPUNBIND statement in BASIC.
117+
*
118+
* Unregisters a previously bound UDP handler for the given local port and stops queueing packets for that port.
119+
*
120+
* @param ctx BASIC context.
121+
*/
99122
void udpunbind_statement(struct basic_ctx* ctx);
100123

124+
/**
125+
* @brief Read the next queued UDP packet payload for a port.
126+
*
127+
* Implements UDPREAD$ in BASIC. Pops the oldest queued datagram for the specified local port and returns its
128+
* payload as a newly allocated string. Side effects: updates the BASIC context’s “last packet” metadata
129+
* (source IP/port, length).
130+
*
131+
* @param ctx BASIC context.
132+
* @return Pointer to a NUL-terminated payload string (owned by the BASIC GC), or an empty string if no packet
133+
* is available.
134+
*/
101135
char* basic_udpread(struct basic_ctx* ctx);
102136

137+
/**
138+
* @brief Return the source port of the last UDP packet read.
139+
*
140+
* Exposes the metadata captured by the most recent successful UDPREAD$ call.
141+
*
142+
* @param ctx BASIC context.
143+
* @return Source UDP port number of the last packet, or 0 if no packet has been read.
144+
*/
103145
int64_t basic_udplastsourceport(struct basic_ctx* ctx);
104146

147+
/**
148+
* @brief Return the source IP address of the last UDP packet read.
149+
*
150+
* Exposes the metadata captured by the most recent successful UDPREAD$ call.
151+
*
152+
* @param ctx BASIC context.
153+
* @return Source IPv4 address as a dotted-quad string, or an empty string if no packet has been read.
154+
*/
105155
char* basic_udplastip(struct basic_ctx* ctx);
156+
157+
/**
158+
* @brief Create a listening TCP socket from BASIC.
159+
*
160+
* Implements SOCKLISTEN. Binds a TCP listener to the specified address and port with the requested backlog and
161+
* returns a file descriptor for subsequent SOCKACCEPT calls.
162+
*
163+
* @param ctx BASIC context.
164+
* @return Non-negative file descriptor on success; −1 with an error raised into the tokenizer on failure.
165+
*/
166+
int64_t basic_socklisten(struct basic_ctx* ctx);
167+
168+
/**
169+
* @brief Accept an incoming TCP connection from BASIC (non-blocking).
170+
*
171+
* Implements SOCKACCEPT. If an established connection is queued, returns a new file descriptor; if no
172+
* connection is currently ready, returns −1 without blocking. Errors are reported to the tokenizer.
173+
*
174+
* @param ctx BASIC context.
175+
* @return File descriptor of the accepted connection, or −1 if no connection is ready or on error.
176+
*/
177+
int64_t basic_sockaccept(struct basic_ctx* ctx);

os/programs/ball.rrbasic

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ REPEAT
1616
GCOL RGB(0,255,0)
1717
CIRCLE X#, Y#, 50, TRUE
1818
IF X# > (GRAPHICS_WIDTH - 50) THEN DX# = -5
19-
IF X# < 50 THEN DX# = 5
19+
IF X# < 55 THEN DX# = 5
2020
IF Y# > (GRAPHICS_HEIGHT - 50) THEN DY# = -5
21-
IF Y# < 50 THEN DY# = 5
21+
IF Y# < 55 THEN DY# = 5
2222
FLIP
2323
UNTIL INKEY$ <> ""
2424
CLS

os/programs/servertest.rrbasic

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
server = SOCKLISTEN(NETINFO$("ip"), 2000, 5)
2+
PRINT "Socket server listening on port 2000"
3+
REPEAT
4+
client = SOCKACCEPT(server)
5+
IF client >= 0 THEN PROChandleConnection(client)
6+
UNTIL INKEY$ <> ""
7+
SOCKCLOSE server
8+
END
9+
10+
DEF PROChandleConnection(client)
11+
PRINT "Handling connection, HELLORLD!"
12+
SOCKWRITE client, "HELLORLD!" + CHR$(10) + CHR$(13)
13+
SLEEP 1 ' Give the write time to finish
14+
SOCKCLOSE client
15+
ENDPROC

src/basic/function.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ struct basic_int_fn builtin_int[] =
4949
{ basic_shl, "SHL" },
5050
{ basic_shiftkey, "SHIFTKEY" },
5151
{ basic_shr, "SHR" },
52+
{ basic_sockaccept, "SOCKACCEPT" },
53+
{ basic_socklisten, "SOCKLISTEN" },
5254
{ basic_sockstatus, "SOCKSTATUS" },
5355
{ basic_get_text_max_y, "TERMHEIGHT" },
5456
{ basic_get_text_max_x, "TERMWIDTH" },

src/basic/sockets.c

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,44 @@ void sockclose_statement(struct basic_ctx* ctx)
149149
}
150150
}
151151

152-
char* basic_insocket(struct basic_ctx* ctx)
153-
{
152+
int64_t basic_socklisten(struct basic_ctx* ctx) {
153+
PARAMS_START;
154+
PARAMS_GET_ITEM(BIP_STRING);
155+
const char* ip = strval;
156+
PARAMS_GET_ITEM(BIP_INT);
157+
int64_t port = intval;
158+
PARAMS_GET_ITEM(BIP_INT);
159+
int64_t backlog = intval;
160+
PARAMS_END("SOCKLISTEN",-1);
161+
if (port < 1 || port > UINT16_MAX - 1) {
162+
tokenizer_error_print(ctx, "Invalid port for LISTEN");
163+
return -1;
164+
}
165+
int rv = tcp_listen(str_to_ip(ip), port, backlog);
166+
if (rv < 0) {
167+
tokenizer_error_print(ctx, socket_error(rv));
168+
return -1;
169+
}
170+
return rv;
171+
}
172+
173+
int64_t basic_sockaccept(struct basic_ctx* ctx) {
174+
PARAMS_START;
175+
PARAMS_GET_ITEM(BIP_INT);
176+
int64_t server = intval;
177+
PARAMS_END("SOCKACCEPT",-1);
178+
int rv = tcp_accept(server);
179+
if (rv == TCP_ERROR_WOULD_BLOCK) {
180+
/* This is an expected, handled error */
181+
return -1;
182+
} else if (rv < 0) {
183+
tokenizer_error_print(ctx, socket_error(rv));
184+
return -1;
185+
}
186+
return rv;
187+
}
188+
189+
char* basic_insocket(struct basic_ctx* ctx) {
154190
uint8_t input[2] = { 0, 0 };
155191

156192
PARAMS_START;
@@ -222,8 +258,7 @@ char* basic_netinfo(struct basic_ctx* ctx)
222258
return gc_strdup(ctx, "0.0.0.0");
223259
}
224260

225-
char* basic_dns(struct basic_ctx* ctx)
226-
{
261+
char* basic_dns(struct basic_ctx* ctx) {
227262
PARAMS_START;
228263
PARAMS_GET_ITEM(BIP_STRING);
229264
PARAMS_END("DNS$","");
@@ -233,12 +268,9 @@ char* basic_dns(struct basic_ctx* ctx)
233268
return gc_strdup(ctx, ip);
234269
}
235270

236-
void sockwrite_statement(struct basic_ctx* ctx)
237-
{
238-
int fd = -1;
239-
271+
void sockwrite_statement(struct basic_ctx* ctx) {
240272
accept_or_return(SOCKWRITE, ctx);
241-
fd = basic_get_numeric_int_variable(tokenizer_variable_name(ctx), ctx);
273+
int fd = basic_get_numeric_int_variable(tokenizer_variable_name(ctx), ctx);
242274
accept_or_return(VARIABLE, ctx);
243275
accept_or_return(COMMA, ctx);
244276
const char* out = printable_syntax(ctx);

0 commit comments

Comments
 (0)