Skip to content

Instantly share code, notes, and snippets.

@nicholastay
Last active May 23, 2022 11:11
Show Gist options
  • Save nicholastay/167441180a99ad90d237c80d790fbffb to your computer and use it in GitHub Desktop.
Save nicholastay/167441180a99ad90d237c80d790fbffb to your computer and use it in GitHub Desktop.
misc porth stuff test

porth-stuff

Just some random programs in porth because I saw Tsoding's videos/stream and I thought it looked cool.

Licence: MIT

# Ignore all without extensions
*
!*.*
*.asm
*.o
// A really bad http server example in Porth
// It does not comply to anything. (yet?)
const SERVER_PORT 8080 end
include "../porth/std/std.porth"
const AF_INET 2 end
const SOCK_STREAM 1 end
const SOL_SOCKET 1 end
const SO_REUSEADDR 2 end
const O_DIRECTORY 65536 end
const S_IFMT 61440 end // 00170000
const S_IFREG 32768 end // 0x0100000
const S_IFDIR 16384 end // 0x0040000
inline proc S_ISREG int -- bool in S_IFMT and S_IFREG = end
inline proc S_ISDIR int -- bool in S_IFMT and S_IFDIR = end
const sizeof(sockaddr) 16 end
const sizeof(linux_dirent) 1024 end
proc main in
memory socket sizeof(int) end
memory sockaddr sizeof(sockaddr) end
memory connection sizeof(int) end
memory dirent_buf sizeof(linux_dirent) end
memory stat_buf sizeof(stat) end
const client_read_buf_size 1024 end
memory client_read_buf client_read_buf_size end
const file_buf_size 1024 end
memory file_buf file_buf_size end
// Create socket
// socket(AF_INET, SOCK_STREAM, 0)
0 SOCK_STREAM AF_INET SYS_socket syscall3
dup 0 < if
"ERROR: Could not create socket, errcode " eputs
dup print
1 exit
end
socket !64
"INFO: Got socket.\n" eputs
// We make the socket's address reusable for better debugging
// setsockopt(socket, level, option_name, *option_value, option_len)
// Load '1' into the temp buffer area
memory ONE 8 end
1 ONE !8
8 ONE SO_REUSEADDR SOL_SOCKET socket @64 SYS_setsockopt syscall5 drop
// Now we make the 'sockaddr' struct to pass into bind()
// struct sockaddr_in {
// sa_family_t sin_family; /* address family: AF_INET */
// in_port_t sin_port; /* port in network byte order */
// struct in_addr sin_addr; /* internet address */
// };
//
// struct in_addr {
// uint32_t s_addr; /* address in network byte order */
// };
// Keep in mind little endian
// sin_family 16 bit unsigned int
AF_INET sockaddr !8
0 sockaddr 1 ptr+ !8
// sin_port 16 bit (network byte order)
// e.g. port 3000 - 00001011 10111000 = 11 184
// we can shift and truncate into the 1byte memory locations
SERVER_PORT 8 shr sockaddr 2 ptr+ !8
SERVER_PORT sockaddr 3 ptr+ !8
// sin_addr 32 bit (in_addr.s_addr) listen on 0.0.0.0
0 sockaddr 4 ptr+ !32
// Actually bind
// bind(socket_fd, &sockaddr, size)
16 sockaddr socket @64 SYS_bind syscall3
dup 0 < if
"ERROR: Could not bind, errcode " eputs
dup print
1 exit
end drop
"INFO: Bound socket on all addresses to port " eputs
SERVER_PORT print
// Listen to the socket now that it's bound (queue 2)
2 socket @64 SYS_listen syscall2
dup 0 < if
"ERROR: Could not listen, errcode " eputs
dup print
1 exit
end drop
"INFO: Listening.\n" eputs
// Accept any connections that come our way and serve them our HTTP :^)
//while 1 cast(bool) do
while true do
0 0 socket @64 SYS_accept syscall3
dup 0 < if
"WARN: Failed to accept a connection, errcode " eputs
print
else
"INFO: Accepted a connection.\n" eputs
// Accepted connection gives us another fd to write back to them
// Keep another one on the stack to close later
dup connection !64
// TODO: Handle writes failing with a debug message (when we can break loops?)
// Read the TCP contents
client_read_buf_size client_read_buf connection @64 read
dup 0 < if
"WARN: Error reading from socket but going to continue! errcode " eputs
print
else
drop
"\n\nDEBUG: The client said...\n----------\n" eputs
client_read_buf dup cstrlen swap eputs
"\n----------\n\n" eputs
// Read "GET", return error otherwise
// FIXME: This is a hacky way of just reading and comparing as null terminated, but it works I guess (but it destroys the original read buffer)
0 client_read_buf 3 ptr+ !8
client_read_buf "GET"c cstreq if
"It was a GET\n" eputs
// TODO: Skip over "GET " and then get the path until a " "
// FIXME: Another hack. We go until the " " (ASCII 32) and add a null terminator there and then we can use it as a C string.
// The string will be at 'client_read_buf 4 +'
client_read_buf 4 ptr+
while dup @8 dup 32 != swap 0 != land do // keep going until space or end of string
1 ptr+
end 0 swap !8
// FIXME: Another weird thing we can do. Set 'client_read_buf 3 +' as '.' (ASCII 46), then we can use that whole C string as the path to read locally.
46 client_read_buf 3 ptr+ !8
"Trying to stat '" eputs
client_read_buf 3 ptr+ dup cstrlen swap eputs
"'...\n" eputs
// stat() the path to test if dir/file/unknown
stat_buf client_read_buf 3 ptr+ stat
0 < if
"Couldn't stat.\n" eputs
"HTTP/1.0 404 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>File not found.</body></html>" connection @64 fputs
else
// Determine if file or folder with st_mode
// struct stat {
// dev_t st_dev; /* ID of device containing file */
// ino_t st_ino; /* Inode number */
// mode_t st_mode; /* File type and mode */
// ...
// }
// mode_t will (probably) be 16 bytes in, and is 4 bytes long - read it as such (little endian)
// TODO: Turn this into a macro to read these various lengths/at least more elegant?
stat_buf stat.st_mode @32
// TODO: If file, read file and send back
// TODO: Determine Content-Type to send back nicely
// TODO: If dir, read that dir and send back
// TODO: Else, return error
dup S_ISREG if
// Just open and serve the file
0 client_read_buf 3 ptr+ SYS_open syscall2
dup 0 < if
"HTTP/1.0 500 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>Could not open the file.</body></html>" connection @64 fputs
"WARN: Could not open file! errcode " eputs
print
else
"HTTP/1.0 200 OK\nContent-Type: " connection @64 fputs
// TODO: Handle types properly
// Naive .html ending check
client_read_buf 3 ptr+ cstrlen
dup 7 > swap
5 - client_read_buf 3 ptr+ swap ptr+ ".html"c cstreq
land if
"HTML found\n" eputs
"text/html" connection @64 fputs
else
"Not HTML, serving as plain text\n" eputs
"text/plain" connection @64 fputs
end
"\n\n" connection @64 fputs
// Read in the file (fd is on the stack, save a copy)
// (also, read returns the bytes read)
// TODO: fix this awkward read twice syntax
dup file_buf_size swap file_buf swap read
while dup 0 > do
file_buf connection @64 fputs
dup file_buf_size swap file_buf swap read
end drop
// Close the file as we're done
close drop
end
else dup S_ISDIR if*
// Enumerate folder contents
// Open directory, then read its contents
O_DIRECTORY client_read_buf 3 ptr+ SYS_open syscall2
dup 0 < if
"HTTP/1.0 500 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>Could not open the directory.</body></html>" connection @64 fputs
"WARN: Could not open directory! errcode " eputs
print
else
"HTTP/1.0 200 OK\nContent-Type: text/html\n\n<html><head><title>Directory listing</title></head><body><h1>Directory listing for " connection @64 fputs
client_read_buf 3 ptr+ dup cstrlen swap connection @64 fputs
"</h1><ul>" connection @64 fputs
"INFO: Reading directory...\n" eputs
// Do the content reading of the directory stream
// (keep pointer to opendir with dup)
// getdents(fd, buf, buf_size)
dup sizeof(linux_dirent) swap dirent_buf swap SYS_getdents syscall3
while dup 0 > do
// Enumerate the contents as per returned, keep the offset on the stack
// Initial as 0
0 while over over > do
// We have a 'Linux dirent'
//
// struct linux_dirent {
// unsigned long d_ino;
// off_t d_off;
// unsigned short d_reclen;
// char d_name[];
// };
//
// So to jump to the filename, we jump 18 bytes (8+8+2)
// TODO: sizeof() - will we ever run into any issues with this assumption?
// Main pointer to linux_dirent struct
dup dirent_buf swap ptr+
// Add in offset and jump to filename
"<li><a href=\"./" connection @64 fputs
dup 18 ptr+ dup cstrlen swap connection @64 fputs
"\">" connection @64 fputs
dup 18 ptr+ dup cstrlen swap connection @64 fputs
"</a></li>" connection @64 fputs
// Jump by the offset 'd_reclen'
// ... it is a unsigned short, so we'll need to deal with it as such...
// (Remember little endian)
16 ptr+ @16 +
end drop drop // drop twice to get rid of the extra duped getdents return value as well
// TODO: Fix this awkward syntax
dup sizeof(linux_dirent) swap dirent_buf swap SYS_getdents syscall3
end drop
"INFO: Closing directory...\n" eputs
close drop
"</ul><i>powered by porth-nothttpd</i></body></html>\n" connection @64 fputs
"INFO: Replied to connection with directory listing.\n" eputs
end
else
"Not a regular file or directory.\n" eputs
"HTTP/1.0 404 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>File not found.</body></html>" connection @64 fputs
end drop
end
else
"It was NOT a GET\n" eputs
"HTTP/1.0 400 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>The server does not accept non-GET requests.</body></html>" connection @64 fputs
end
end
"INFO: Closing connection.\n" eputs
close drop
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment