Add assembler client

This commit is contained in:
John Bintz 2023-04-02 16:24:33 -04:00
parent 7c24814352
commit 24255230c3
6 changed files with 575 additions and 0 deletions

View File

@ -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`. 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. 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.

22
client_in_asm/README.md Normal file
View File

@ -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`

BIN
client_in_asm/client-asm Normal file

Binary file not shown.

BIN
client_in_asm/client-c Normal file

Binary file not shown.

400
client_in_asm/client.asm Normal file
View File

@ -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 <ip address> <port> <message...>",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

145
client_in_asm/client.c Normal file
View File

@ -0,0 +1,145 @@
#include <stdio.h>
#include <strings.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <exec/memory.h>
#include <proto/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
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 <ip address> <port> <message...>\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 <ip address> <port> <message...>\n");
teardown();
return 1;
}
if (-1 == (StrToLong(ReadBuffer, &Port))) {
printf("client <ip address> <port> <message...>\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;
}