diff --git a/README.md b/README.md index 813a406..1b719d0 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,11 @@ This is the code used in the [23 Year Old Code](https://youtu.be/l4GNHJfYOUU) vi prints out what is received, like a very basic `netcat`. This is the code used in the [Simple Server in C for the Commodore Amiga](https://youtu.be/i6_PQUsayN4) video. + +## Client in Assembler + +`client_in_asm` uses AsmPro and the AmiTCK SDK to build a simple client that +connects to a specified host and port and sends some data, like the other end +of a very basic `netcat`. + +This is the code used in the [Building a simple Amiga network client...in assembler?!?](https://youtu.be/HbMPBbF9-RE) video. diff --git a/client_in_asm/README.md b/client_in_asm/README.md new file mode 100644 index 0000000..a6da716 --- /dev/null +++ b/client_in_asm/README.md @@ -0,0 +1,22 @@ +# Amiga BSD Socket Client in Assembler and C + +The versions are identical, except that the C version uses `printf` instead +of `PutStr()`. + +## Assembler Version + +Built using AsmPro. You'll have to adjust the `INCDIR` locations and build +the LVOs for the `bsdsocket` and `dos` libraries. You can get the socket +FD file from the AmiTCP SDK. + +``` +fd2pragma fd/dos.fd to include_i/ special 20 +fd2pragma fd/socket.fd to include_i/ special 20 +``` + +## C Version + +Built using SAS/C 6.58 and the +[AmiTCP 4.3 SDK](http://aminet.net/package/comm/tcp/AmiTCP-SDK-4.3). + +`sc link client.c` diff --git a/client_in_asm/client-asm b/client_in_asm/client-asm new file mode 100644 index 0000000..23f4a2f Binary files /dev/null and b/client_in_asm/client-asm differ diff --git a/client_in_asm/client-c b/client_in_asm/client-c new file mode 100644 index 0000000..8c51b30 Binary files /dev/null and b/client_in_asm/client-c differ diff --git a/client_in_asm/client.asm b/client_in_asm/client.asm new file mode 100644 index 0000000..7afdfcb --- /dev/null +++ b/client_in_asm/client.asm @@ -0,0 +1,400 @@ +FUNC_CNT SET -30 +FUNCDEF MACRO +_LVO\1 EQU FUNC_CNT +FUNC_CNT SET FUNC_CNT-6 + ENDM + + INCDIR "AsmPro:Include/include_i/" + INCLUDE "exec/exec_lib.i" + INCLUDE "dos/dos_lvo.i" + INCLUDE "exec/libraries.i" + INCLUDE "exec/memory.i" + INCLUDE "dos/dos.i" + INCLUDE "socket_lvo.i" + +PrintString MACRO + MOVE.L \1,D1 + MOVE.L DOSBase,A6 + CALLLIB _LVOPutStr ; dos/PutStr + ENDM + +OpenLibrary MACRO + MOVEQ \2,D0 + MOVE.L \1,A1 + MOVEA.L 4,A6 + CALLLIB _LVOOpenLibrary ; exec/OpenLibrary + ENDM + +ExitProgram MACRO + MOVEQ \1,D0 + RTS + ENDM + +DoCloseDOSLibrary MACRO + MOVEA.L DOSBase,A1 + MOVEA.L 4,A6 + CALLLIB _LVOCloseLibrary ; exec/CloseLibrary + ENDM + +DoCloseBSDSocketLibrary MACRO + MOVEA.L BSDSocketBase,A1 + MOVEA.L 4,A6 + CALLLIB _LVOCloseLibrary ; exec/CloseLibrary + ENDM + +DeallocateBuffer MACRO + MOVE.L ReadBuffer,A1 + MOVE.L #READ_BUFFER_SIZE,D0 + MOVEA.L 4,A6 + CALLLIB _LVOFreeMem ; exec/FreeMem + ENDM + +DoCloseSocket MACRO + MOVE.L SocketID,D0 + MOVE.L BSDSocketBase,A6 + CALLLIB _LVOCloseSocket ; bsdsocket/CloseSocket + ENDM + +; our user message has a limit of this many characters +READ_BUFFER_SIZE EQU 1024 + +; bsdsocket library stuff +; ported from the various C include headers +SOCK_STREAM EQU 1 +PF_INET EQU 2 +AF_INET EQU PF_INET +IPPROTO_TCP EQU 6 + +len_sockaddr_in EQU 16 +sockaddr_in_sin_len EQU 0 +sockaddr_in_sin_family EQU 1 +sockaddr_in_sin_port EQU 2 +sockaddr_in_sin_addr EQU 4 + +Start: + OpenLibrary #DOSLibrary,#36 + MOVE.L D0,DOSBase + TST.L D0 + BNE OpenBSDSocketLibrary + + ; if this fails, we have bigger problems + MOVE.L #20,ExitCode + BRA Teardown + +OpenBSDSocketLibrary: + OpenLibrary #BSDSocketLibrary,#4 + MOVE.L D0,BSDSocketBase + TST.L D0 + BNE ReserveBuffer + + PrintString #UnableToOpenBSDSocketLibrary + + ; be sure to unwind everything we've opened so far + ; i tried using this with codewatcher and i think there's + ; something up with trying to read command line argument + ; when run under codewatcher. + MOVE.L #1,ExitCode + BRA Teardown + +ReserveBuffer: + MOVE.L #READ_BUFFER_SIZE,D0 + ; we don't care where the memory comes from + ; nor should we! + MOVE.L #MEMF_CLEAR,D1 + MOVEA.L 4,A6 + CALLLIB _LVOAllocMem ; dos/AllocMem + MOVE.L D0,ReadBuffer + TST.L D0 + BNE ReadIPAddress + + PrintString #UnableToAllocateBuffer + + MOVE.L #1,ExitCode + BRA Teardown + +ReadIPAddress: + MOVE.L ReadBuffer,D1 + MOVE.L #READ_BUFFER_SIZE,D2 + MOVEQ #0,D3 + MOVE.L DOSBase,A6 + CALLLIB _LVOReadItem ; dos/ReadItem + + ; fail on an error or a missing parameter + CMP.L #ITEM_ERROR,D0 + BEQ NoIPAddress + CMP.L #ITEM_NOTHING,D0 + BEQ NoIPAddress + + BRA ParseIPAddress + +NoIPAddress: + PrintString #UnableToReadArguments + + MOVE.L #1,ExitCode + BRA Teardown + +ParseIPAddress: + MOVE.L ReadBuffer,A0 + MOVE.L BSDSocketBase,A6 + CALLLIB _LVOinet_addr ; bsdsocket/inet_addr + MOVE.L D0,IPAddress + + TST.L D0 + BNE ReadPort + + PrintString #UnableToParseIPAddress + + MOVE.L #1,ExitCode + BRA Teardown + +ReadPort: + MOVE.L ReadBuffer,D1 + MOVE.L #READ_BUFFER_SIZE,D2 + MOVEQ #0,D3 + MOVE.L DOSBase,A6 + CALLLIB _LVOReadItem ; dos/ReadItem + + ; fail on an error or a missing parameter + CMP.L #ITEM_ERROR,D0 + BEQ NoPort + CMP.L #ITEM_NOTHING,D0 + BEQ NoPort + + BRA ParsePort + +NoPort: + PrintString #UnableToReadArguments + + MOVE.L #1,ExitCode + BRA Teardown + + +ParsePort: + ; the Amiga library way to do atol() + MOVE.L ReadBuffer,D1 + MOVE.L #PortLong,D2 + MOVE.L DOSBase,A6 + CALLLIB _LVOStrToLong ; dos/StrToLong + + CMP.L #-1,D0 + BNE _ParsePort_Continue + + PrintString #UnableToParsePort + + MOVE.L #1,ExitCode + BRA Teardown + +_ParsePort_Continue: + MOVE.L PortLong,D0 + + ; we could just crop the provided port at a word boundary, + ; but let's be nice and tell the user they entered in a bad + ; port number. + CMP.L #65536,D0 + BLT ReadRemainingCommandLine + + PrintString #PortOutOfRange + + MOVE.L #1,ExitCode + BRA Teardown + +ReadRemainingCommandLine: + ; lop off the actual port number + MOVE.W D0,Port + + ; this is the Amiga equivalent of stdin? + MOVE.L DOSBase,A6 + CALLLIB _LVOInput ; dos/Input + MOVE.L D0,Stdin + + MOVE.L ReadBuffer,FGetCLocation + MOVEQ #0,D2 + +_ReadRemainingCommandLine_Loop: + ; loop until fgetc returns -1 or 10 + MOVE.L Stdin,D1 + MOVE.L DOSBase,A6 + CALLLIB _LVOFGetC ; dos/FGetC + + MOVE.L FGetCLocation,A0 + CMP.L #-1,D0 ; end on EOF... + BEQ _ReadRemainingCommandLine_Finish + CMP.L #10,D0 ; or a 10, from you physically hitting enter in the CLI + BEQ _ReadRemainingCommandLine_Finish + + MOVE.B D0,(A0)+ + ADDQ #1,D2 + MOVE.L A0,FGetCLocation + BRA _ReadRemainingCommandLine_Loop + +_ReadRemainingCommandLine_Finish: + ; add the chr(10) back, and end the string with a null + MOVE.B #10,(A0)+ + MOVE.B #0,(A0)+ + + ; we need to know how many characters total we read + ADDQ #2,D2 + MOVE.L D2,ReadBufferLength + +CreateSocket: + MOVE.L #PF_INET,D0 + MOVE.L #SOCK_STREAM,D1 + MOVE.L #IPPROTO_TCP,D2 + MOVE.L BSDSocketBase,A6 + CALLLIB _LVOsocket ; exec/socket + MOVE.L D0,SocketID + +PrintIPAddress: + ; exec/RawDoFmt is basically sprintf. + LEA IPAddrString,A0 + + ; load up the printf arguments + LEA DoFmtBuffer,A1 + MOVE.L IPAddress,(A1)+ + MOVE.W Port,(A1)+ + MOVE.L SocketID,(A1)+ + LEA DoFmtBuffer,A1 + + LEA _StuffChar,A2 + LEA PrintBuffer,A3 + + MOVE.L 4,A6 + CALLLIB _LVORawDoFmt ; exec/RawDoFmt + + PrintString #PrintBuffer + + MOVE.L SocketID,D0 + CMP.L #-1,D0 + BNE ConnectSocket + + PrintString #UnableToOpenSocket + + MOVE.L #1,ExitCode + BRA Teardown + +ConnectSocket: + ; populate sockaddr_in + LEA ConnectSockAddrIn,A0 + MOVE.B #AF_INET,sockaddr_in_sin_family(A0) + MOVE.W Port,sockaddr_in_sin_port(A0) + MOVE.L IPAddress,sockaddr_in_sin_addr(A0) + + MOVE.L SocketID,D0 + MOVE.L #len_sockaddr_in,D1 + MOVE.L BSDSocketBase,A6 + CALLLIB _LVOconnect ; bsdsocket/connect + + ; a socket is connected if the response is 0 + TST.L D0 + BEQ SendData + + PrintString #UnableToConnectSocket + + MOVE.L #1,ExitCode + BRA Teardown + +SendData: + MOVE.L SocketID,D0 + MOVE.L ReadBuffer,A0 + MOVE.L ReadBufferLength,D1 + MOVEQ #0,D2 + MOVE.L BSDSocketBase,A6 + CALLLIB _LVOsend ; bsdsocket/send + + CMP.L #-1,D0 + BNE CloseSocket + + PrintString #UnableToSendData + + MOVE.L #1,ExitCode + BRA Teardown + +CloseSocket: + MOVE.L #0,ExitCode + +; check all of the things we might have allocated and +; tear them down if we did, then return the appropriate exit code. +Teardown: + MOVE.L SocketID,D0 + CMP.L #-1,D0 + BEQ _Teardown_DeallocateBuffer + + DoCloseSocket +_Teardown_DeallocateBuffer: + MOVE.L ReadBuffer,D0 + BEQ _Teardown_CloseBSDSocketLibrary + + DeallocateBuffer +_Teardown_CloseBSDSocketLibrary: + MOVE.L BSDSocketBase,D0 + BEQ _Teardown_CloseDOSLibrary + + DoCloseBSDSocketLibrary +_Teardown_CloseDOSLibrary: + MOVE.L DOSBase,D0 + BEQ _Teardown_Exit + + DoCloseDOSLibrary +_Teardown_Exit: + MOVEQ #0,D0 + MOVE.L ExitCode,D0 + RTS + +; RawDoFmt helper +_StuffChar: + MOVE.B D0,(a3)+ + RTS + + +; libraries we open +DOSBase DCB.L 1,0 +BSDSocketBase DCB.L 1,0 + +; allocated memory +ReadBuffer DCB.L 1,0 +ReadBufferLength DS.L 1 + +; return code for the program +ExitCode DS.L 1 + +; reading command line args +Stdin DS.L 1 + +; user parameters +IPAddress DS.L 1 +Port DS.W 1 +PortLong DS.L 1 + +; message to send +FGetCChar DS.L 1 +FGetCLocation DS.L 1 + +; establishing a connection +SocketID DCB.L 1,-1 +ConnectSockAddrIn DCB.B 16,0 + +; place to build a string for printing via RawDoFmt +PrintBuffer DS.B 100 + +; stack for accepting RawDoFmt arguments as values +DoFmtBuffer DS.B 32 + +DOSLibrary DC.B "dos.library",0 +BSDSocketLibrary DC.B "bsdsocket.library",0 + +;FakeIP DC.B "1.2.3.4",0 +;FakePort DC.B "1234",0 + +; user messages +UnableToOpenBSDSocketLibrary DC.B "Unable to open bsdsocket.library. Have you started a TCP stack?",10,0 +UnableToAllocateBuffer DC.B "Unable to allocate buffer",10,0 +UnableToReadArguments DC.B "client ",10,0 +UnableToParseIPAddress DC.B "Unable to parse IP Address",10,0 +UnableToOpenSocket DC.B "Unable to open socket",10,0 +UnableToConnectSocket DC.B "Unable to connect socket",10,0 +UnableToSendData DC.B "Unable to send data",10,0 +UnableToParsePort DC.B "Unable to parse port",10,0 +PortOutOfRange DC.B "Port out of range (0-65535)",10,0 +;Here DC.B "Here",10,0 +IPAddrString DC.B "IP address is %8lx, port is %d, socket is %ld",10,0 +;FGetCResult DC.B "%ld %ld %ld",10,0 diff --git a/client_in_asm/client.c b/client_in_asm/client.c new file mode 100644 index 0000000..e64bebe --- /dev/null +++ b/client_in_asm/client.c @@ -0,0 +1,145 @@ +#include +#include + +#include +#include + +#include + +#include +#include +#include + +struct Library *SocketBase; +char *ReadBuffer; +int SocketID = -1; + +#define READ_BUFFER_SIZE (1024) + +void teardown() { + if (SocketBase && SocketID != -1) { + CloseSocket(SocketID); + } + + if (ReadBuffer) { + FreeMem(ReadBuffer, READ_BUFFER_SIZE); + } + + if (SocketBase) { + CloseLibrary(SocketBase); + } +} + +int main(void) { + int ReadItemResult; + ULONG IPAddress; + long Port; + char CurrentChar; + int ReadIndex; + BPTR Stdin; + + struct sockaddr_in ConnectSockaddrIn; + + if (!(SocketBase = OpenLibrary("bsdsocket.library", 4))) { + printf("Unable to open bsdsocket.library. Have you started a TCP stack?\n"); + return 1; + } + + if (!(ReadBuffer = AllocMem(READ_BUFFER_SIZE, MEMF_CLEAR))) { + printf("Unable to allocate buffer\n"); + teardown(); + return 1; + } + + // yes we could use argc/argv but we're mimicking what's in the + // original assembler code + + ReadItemResult = ReadItem(ReadBuffer, READ_BUFFER_SIZE, NULL); + if ( + ReadItemResult == ITEM_ERROR || + ReadItemResult == ITEM_NOTHING + ) { + printf("client \n"); + teardown(); + return 1; + } + + if (!(IPAddress = inet_addr(ReadBuffer))) { + printf("Unable to parse IP Address\n"); + teardown(); + return 1; + } + + ReadItemResult = ReadItem(ReadBuffer, READ_BUFFER_SIZE, NULL); + if ( + ReadItemResult == ITEM_ERROR || + ReadItemResult == ITEM_NOTHING + ) { + printf("client \n"); + teardown(); + return 1; + } + + if (-1 == (StrToLong(ReadBuffer, &Port))) { + printf("client \n"); + teardown(); + return 1; + } + + if (Port > 65535) { + printf("Port out of range (0-65535)\n"); + teardown(); + return 1; + } + + Stdin = Input(); + + Port = (short)Port; + CurrentChar = FGetC(Stdin); + ReadIndex = 0; + + while (CurrentChar != -1 && CurrentChar != 10) { + ReadBuffer[ReadIndex++] = CurrentChar; + printf("%d\n",CurrentChar); + CurrentChar = FGetC(Stdin); + } + + ReadBuffer[ReadIndex++] = 10; + ReadBuffer[ReadIndex++] = 0; + + SocketID = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + + // we'll skip on using RawDoFmt here + printf( + "IP address is %8x, port is %d, socket is %d\n", + IPAddress, + Port, + SocketID + ); + + if (SocketID == -1) { + printf("Unable to open socket\n"); + teardown(); + return 1; + } + + ConnectSockaddrIn.sin_family = AF_INET; + ConnectSockaddrIn.sin_port = Port; + ConnectSockaddrIn.sin_addr.s_addr = IPAddress; + + if (connect(SocketID, (struct sockaddr *)&ConnectSockaddrIn, sizeof(struct sockaddr_in)) == -1) { + printf("Unable to connect socket\n"); + teardown(); + return 1; + } + + if (send(SocketID, ReadBuffer, ReadIndex, 0) == -1) { + printf("Unable to send data\n"); + teardown(); + return 1; + } + + teardown(); + return 0; + +}