Generic IO

Generic IO is the name given to VFX Forth's entire input/output architecture. This system allows for a "device-driver" to be written to a standard format such that the drivers are all interchangeable within the Forth System. As noted later you will see that all standard Forth I/O words (such as EMIT) are passed through Generic I/O. )

Under VFX each thread has it's own current input and output stream and can be accessed via the standard Forth IO words and a general purpose wordset which acts upon current thread devices. (See Later) In addition Generic IO also supports a wordset which can use a nominated device directly. This second wordset follows the same naming convention as the current-thread wordset.

Format of a GENIO Driver

An instance of a Generic Driver is described by the following structure, the address of such a structure is called a SID (structure-identifier).


  CELL     Device Handle (interpretation depends on device),
  CELL     Pointer to function Table (see below),
  ????     Device Private Data.

The function table is a list of execution tokens for words to perform various standard actions. Each action word will receive the SID to operate upon at the top of the data stack.

To be a "Generic Device" the vector table must hold valid entries for:


Index      Name            Description
  0        OPEN            Open/Initialise a device.
  1        CLOSE           Close a device.
  2        READ            Read to a block of memory.
  3        WRITE           Write a specified block of memory.
  4        KEY             Perform an action equivalent to Forths
                           KEY definition, (i.e. a blocking character
                           read.)
  5        KEY?            Perform an action equivalent to Forths
                           KEY?, (i.e. any-input-pending?)
  6        EKEY            Supports EKEY
  7        EKEY?           Supports EKEY?
  8        ACCEPT          Added to support FORTH definition of the
                           same name. Read a character stream into
                           a memory buffer.
  9        EMIT            Write a single character to a stream.
 10        EMIT?           Check that EMIT can work.
 11        TYPE            As with Forths TYPE. Write a string of
                           characters to a device.
 12        CR              Perform nearest equivalent action of
                           "carriage return" on the device.
 13        LR              As with CR except for "line-feed"
 14        FF              As with CR except for "form-feed"
 15        BS              As with CR except for "backspace"
 16        BELL            Where applicable to the device emit an
                           audible beep.
 17        SETPOS          Set current position. May reflect screen
                           cursor/file position etc.
 18        GETPOS          Read current position.
 19        IOCTL           Perform a special function. Each device
                           may or may not support various IOCTL
                           codes. The currently assigned function
                           codes used by MPE are documented later.
 20        FLUSHOP         Flush any pending output for device.
 21        RFU/READEX      As READ with additional return of count.
                           Not available on all devices.

Devices that require additional functions may add these at the end of the table. If additional functions are added the first three must be as below. It is valid for these to perform no action except to return a zero ior.


 22        initialise device    addr len sid -- ior
 23        terminate device     sid -- ior
 24        configure device     sid -- ior ; produces a dialog

Current Thread Device Access

The following definitions act upon the nominated input or output stream for the calling thread. Definitions declared with IPFUNC act upon the current input stream and definitions declared with OPFUNC act upon the current output stream.

struct gen-sid  \ -- len
Define the generic I/O structure known as a SID. This structure does not include any private data.

  cell  field   gen-handle
  cell  field   gen-vector
  0     field gen-private
end-struct

OpenFnid    OPFunc open-gen     \ addr len attribs -- handle/sid ior
Perform the Generic IO "OPEN" action for current output device.

CloseFnid   OPFunc close-gen    \ -- ior
Perform the Generic IO "CLOSE" action for current output device.

ReadFnid    IPFunc read-gen     \ addr len -- ior
Perform the Generic IO "READ" action for current input device.

WriteFnid   OPFunc write-gen    \ addr len -- ior
Perform the Generic IO "WRITE" action for current output device.

KeyFnid     IPFunc key-gen      \ -- char
Perform the Generic IO "KEY" action for current input device. This operation is identical to the Forth word KEY.

Key?Fnid    IPFunc key?-gen     \ -- flag
Perform the Generic IO "KEY?" action for current input device. This operation is identical to the Forth word KEY?.

EKeyFnid    IPFunc ekey-gen     \ -- echar
Perform the Generic IO "EKEY" action for current input device. This operation is identical to the Forth word EKEY.

EKey?Fnid   IPFunc ekey?-gen    \ -- flag
Perform the Generic IO "EKEY?" action for current input device. This operation is identical to the Forth word EKEY?.

AcceptFnid  IPFUnc accept-gen   \ addr len -- len'
Perform the Generic IO "ACCEPT" action for current input device. This operation is identical to the Forth word ACCEPT.

EmitFnid    OPFunc emit-gen     \ char --
Perform the Generic IO "EMIT" action for current output device. This operation is identical to the Forth word EMIT.

Emit?Fnid   OPFunc emit?-gen    \ -- flag
Perform the Generic IO "EMIT?" action for current output device. This operation is identical to the Forth word EMIT?.

TypeFnid    OPFunc type-gen     \ addr len --
Perform the Generic IO "TYPE" action for current output device. This operation is identical to the Forth word TYPE.

CRFnid      OPFunc cr-gen       \ --
Perform the Generic IO "CR" action for current output device. This operation is identical to the Forth word CR.

LFFnid      OPFunc lf-gen       \ --
Perform the Generic IO "LF" action for current output device.

FFFnid      OPFunc ff-gen       \ --
Perform the Generic IO "FF" action for current output device. This operation is identical to the Forth word PAGE.

BSFnid      OPFunc bs-gen       \ --
Perform the Generic IO "BS" action for current output device.

BellFnid    OPFunc bell-gen     \ --
Perform the Generic IO "BELL" action for current output device.

SetposFnid  OPFunc setpos-gen   \ d mode -- ior ; x y mode -- ior
Perform the Generic IO "SETPOS" action for current output device.

GetposFnid  OPFunc getpos-gen   \ mode -- d ior ; mode -- x y ior
Perform the Generic IO "GETPOS" action for current output device.

IoctlFnid   OPFunc ioctl-gen    \ addr len fn# -- ior
Perform the Generic IO "IOCTL" action for current output device.

FlushOPFnid OPFunc FlushOP-gen  \ -- ior
Perform the Generic IO "FLUSH" action for current output device.

ReadExFnid  IPFunc ReadEx-gen   \ addr len -- #read ior
Perform the Generic IO "READEX" action for current input device.

IO based on a Nominated Device

Generic IO allows you to perform an action on any device without changing the thread's current Input or Output Channel. All the definitions listed as xxx-GEN also have an equivalent definition called xxx-GIO which has as top of stack an additional parameter which is the SID of the nominated device.

OpenFnid   GIOFunc open-gio     \ addr len attribs sid -- handle/sid ior
CloseFnid  GIOFunc close-gio    \ sid -- ior
ReadFnid   GIOFunc read-gio     \ addr len sid -- ior
WriteFnid  GIOFunc write-gio    \ addr len sid -- ior
KeyFnid    GIOFunc key-gio      \ sid -- char
Key?Fnid   GIOFunc key?-gio     \ sid -- flag
EKeyFnid   GIOFunc ekey-gio     \ sid -- echar
EKey?Fnid  GIOFunc ekey?-gio    \ sid -- flag
AcceptFnid GIOFunc accept-gio   \ addr len sid -- len'
EmitFnid   GIOFunc emit-gio     \ char sid --
Emit?Fnid  GIOFunc emit?-gio    \ -- flag
TypeFnid   GIOFunc type-gio     \ addr len sid --
CRFnid     GIOFunc cr-gio       \ sid --
LFFnid     GIOFunc lf-gio       \ sid --
FFFnid     GIOFunc ff-gio       \ sid --
BSFnid     GIOFunc bs-gio       \ sid --
BellFnid   GIOFunc bell-gio     \ sid --
SetposFnid GIOFunc setpos-gio   \ d mode sid -- ior ; x y mode sid -- ior
GetposFnid GIOFunc getpos-gio   \ mode sid -- d ior ; mode -- x y ior
IoctlFnid  GIOFunc ioctl-gio    \ addr len fn# sid -- ior
FlushOPFnid GIOFunc FlushOP-gio \ sid -- ior
ReadExFnid GIOFunc ReadEx-gio   \ addr len sid -- #read ior
RFUFnid    GIOFunc RFU-gio      \ sid --
InitFnid   GIOFunc init-gio     \ addr len sid -- ior
TermFnid   GIOFunc term-gio     \ sid -- ior
ConfigFnid GIOFunc config-gio   \ sid -- ior

Standard Forth words using GenericIO

The following standard Forth definitions are already vectored through their generic IO equivalents. The SID device handle used comes from the USER variables OP-HANDLE and IP-HANDLE.


ACCEPT KEY KEY? EKEY EKEY? EMIT EMIT? TYPE CR

Also affected are any I/O words within the ANS Core wordset that use these primitives, such as:


. .S SEE U. ) $. F. DUMP EXPECT QUERY

Miscellaneous I/O Words

The following IO words are defined along with Generic IO and will use the standard Generic IO vectors.

: page          \ --
Performs a FORM-FEED operation. The effect of this differs from device to device. Binary devices will simply output the #12 character, screen devices will clear the screen and printer devices will move on to the next page.

: cls           \ --
An alias for PAGE which reads more clearly when using a screen based device.

: at-xy         \ x y --
The ANS cursor relocation definition. Attempt to move the cursor to relative position X Y. The actual translation of this varies from device to device since it is implemented with the SETPOS generic IO vector.

: SetIO         \ sid --
Set the given device as the current I/O device.

: [IO           \ -- ; R: -- ip-handle op-handle
Used inside a colon definition only to preserve the the current I/O devices before switching them temporarily. Usually used in the form:


[io  SomeDev SetIO
  ...
io]

: IO]           \ -- ; R: ip-handle op-handle --
Used inside a colon definition only to restore the the current I/O devices after switching them temporarily. See [IO for more details.

create szSID    \ -- addr ; used as a property string
Within a winproc controlling a device, it is often useful to be able to reference the SID of the device. This is best done by using the SetProp Windows call to set a property for the Window - see the STUDIO directories for examples. MPE code uses the property string "SID" for this, and szSID is the address of the zero terminated property string.

Supplied Devices

The following Generic IO device implementations can all be found in the supplied source library folder Lib/Genio.

Memory Buffer Device

This Generic IO Device uses blocks of memory for input and/or output. The source code is in LIB\GENIO\Buffer.fth. This is not a circular buffer system. After a buffer has been used, the read and write pointers are not reset. You must reset the buffer pointers using the IOCTL functions.

As of VFX Forth build 2380, this code has been overhauled. If this causes you problems, the file BUFFER.old.FTH contains the previous (but now unsupported) version. The major changes are:

  1. More error checking.
  2. The KEY operation (and hence ACCEPT blocks if no data is available.
  3. The KEY? operation returns the number of unread bytes.
  4. The EMIT? operation returns the number of unwritten bytes in the buffer..
  5. The close operation is protected if the device is already closed.
  6. The GENIO ReadEx function is implemented.
  7. The Textbuff-sid GENIO structure has been documented.
  8. IOCTL functions have been added. See later for details.

Note that the ACCEPT operation does not echo. It is designed for extracting lines from saved input.

In order to create a device use the TEXTBUFF: definition given later. TEXTBUFF: is compatible with ProForth 2.

When opening a memory device the parameters to OPEN-GEN have the following meaning:

ADDR

Address of memory to use as buffer or ignored if dynamic allocation is required.

LEN

The maximum length of the memory image.

ATTRIBS

When zero the ADDR parameter is ignored and LEN bytes of memory are allocated from the heap.

struct textbuff-sid     \ -- len
Defines the length of a SID for a text buffer device.

  gen-sid +             \ handle=buffer, reuse field names of GEN-SID
  int tb-len            \ length of buffer
  int tb-attribs        \ attributes
  int tb-wr             \ address to write to, set by SETPOS, WRITE
  int tb-rd             \ address to read from set by SETPOS, READ
end-struct

: ff-tb         \ sid -- ; page/cls on display devices
This word is run by PAGE, CLS and FF-GEN. It resets (empties) the buffer.

: setpos-tb     \ x y mode sid -- ior
This word is run by SETPOS-GEN. Mode controls the x and y input values as follows.

0

x = #bytes written, y is ignored

-1

x=col, y=line for next character to be written

-2

x = #bytes read, y is ignored

-3

x=col, y=line for next character to be read

: getpos-tb     \ mode sid -- x y ior
This word is run by GETPOS-GEN. Mode controls the x and y return values as follows.

0

x = #bytes written, y = 0

-1

x,y for next character to be written

-2

x = #bytes read, y = 0

-3

x,y for next character to be read

-4

x = addr, y = len of unread data

-5

x = base address, y = size of data area

: ioctl-tb      \ addr len fn sid -- ior
This word is run by IOCTL-GEN and IOCTL-GIO. Fn controls the meaning of addr, len and the ior return value as follows:

0

Get buffer address: addr=0, len=0, ior=addr.

-1

Set write pointer: addr=0, len=offset, ior=0.

-2

Get write pointer: addr=0, len=0, ior=offset.

-3

Set read pointer: addr=0, len=offset, ior=0.

-4

Get read pointer: addr=0, len=0, ior=offset.

Device Creation

: initTextBuffSid       \ addr --
Initialise a previously allocated SID for a text buffer to the default values.

: textbuff:     \ "name" -- ; Exec: -- sid
Create a memory buffer called name.

: SizedTextBuff \ size -- sid|0
Allocates and opens a SID with a buffer of size size bytes, and returns the SID on success or 0 on failure.

: AllocTextBuff \ -- sid|0
Allocates and opens a SID with a default 16kb text buffer and returns the SID on success or 0 on failure.

: FreeTextBuff  \ sid --
Closes the SID and frees memory allocated by AllocTextBuff or SizedTextBuff.

File Device

This Generic IO Device operates on a disk file for input and/or output. The source code can be found in Lib\Genio\file.fth. Neither input nor output are buffered, so that this device should not be used when speed is required. A buffered version is available in Lib\Genio\FileBuff.fth.

In order to create a device use the FILEDEV: definition given later. FILEDEV: is compatible with ProForth 2.

When opening a file device the parameters to OPEN-GEN have the following meaning:


   ADDR            Address of string for filename.
   LEN             Length of string at ADDR.
   ATTRIBS         Open flags. These match the ANS r/o r/w etc.

The ReadEx function is now implemented.

Device Creation

struct /FileDev \ -- len
Returns the size of the sid structure for a file device.

: initFileDev   \ sid --
Initialise the sid for a file device. Mostly used when the structure has been allocated from the heap.

: filedev:      \ "name" -- ; Exec: -- sid
Create a File based Generic IO device in the dictionary.

NULL Device

This Generic IO Device is used as a bit bucket for unwanted output. When used as input KEY? is always false and and read will never return.

In order to create a device use the NULLDEV: definition given later.

When opening a null device the parameters to OPEN-GEN have no meaning.

Device Creation

: nulldev:        \ "name" -- ; Exec: -- sid
Create a NULL Generic IO device in the dictionary.

Serial Device

This Generic IO Device operates on a serial port for input and/or output.

In order to create a device use the SERDEV: definition given later.

When opening a serial device the parameters to OPEN-GEN and open-gio have the following meaning:


   ADDR            Address of configuration string.
   LEN             Length of string at ADDR.
   ATTRIBS         file fam, usually R/W.

The configuration string takes the form:


   /dev/ttyS0 9600 baud  no parity  8 data  1 stop

for Serial Port 0 at 9600 baud, 8 data bits, no parity, 1 stop bit. Only the device name is mandatory. Words and IOCTL functions are available to modify the port setting later. Split baud rates are not supported - you will have to set these yourself. If only the device name is given, the line will be set to 115200 baud, N81 in raw mode. Additional configuration comands are documented later in this section, e.g. for setting the DTR and RTS lines. The configuration string is processed with BASE set to DECIMAL. USB serial devices are discussed at the end of this section.

A good description of Mac OS X serial ports is at:

  http://pbxbook.com/other/mac-tty.html

Serial primitives

struct /serial-sid      \ -- len
Defines the SID of a serial device.

struct /termios \ -- size
A structure corresponding to the termios structure used by tcgetattr and tcsetattr. 32 bit specific.

  4 field termios.c_iflag       \ input mode flags
  4 field termios.c_oflag       \ output mode flags
  4 field termios.c_cflag       \ control mode flags
  4 field termios.c_lflag       \ local mode flags
  1 field termios.c_line        \ line discipline
  NCCS field termios.c_cc       \ control characters
  3 field termios.padding       \ C aligns everything to 32-bits
  4 field termios.c_ispeed      \ input speed
  4 field termios.c_ospeed      \ output speed
end-struct

: setBaud       \ hertz fildes -- ior ; 0=success
Set the baud rate for an opened file descriptor.

: setParity     \ char fildes -- ior ; 0=success
Set the parity for an opened file descriptor. The character must be one of N,E,O.

: setData       \ u fildes -- ior ; 0=success
Set the data size for an opened file descriptor. The data size u must be one of 5,6,7,8.

: setStop       \ u fildes -- ior ; 0=success
Set the number of stop bits for an opened file descriptor. The value of *i{u} must be one of 1 or 2.

: setDTR        \ flag fildes -- ior ; 0=success
Set DTR inactive if flag is zero, otherwise set it active.

: setRTS        \ flag fildes -- ior ; 0=success
Set RTS inactive if flag is zero, otherwise set it active.

: setUnix       \ sid --
Set the line to have Unix line handling.

: setDOS        \ sid --
Set the line to have Windows/DOS line handling.

' setBaud SerCfg: baud          \ sid ior baud -- sid ior'
Used in the configuration string to set the baud rate, e.g.

  9600 baud

' setData SerCfg: data          \ sid ior u -- sid ior'
Used in the configuration string to set the number of data bits, e.g.

  8 data

' setParity SerCfg: parity      \ sid ior u -- sid ior'
Used in the configuration string to set parity, where u is one of the characters N, E, or O. Constants are defined, e.g.

  no parity
  even parity
  odd parity

' setStop SerCfg: stop          \ sid ior u -- sid ior'
Used in the configuration string to set the number of stop bits, e.g.

  1 stop
  2 stop

: 8n1           \ sid ior -- sid ior'
Used in the configuration string to set the most common case, 8 data bits, no parity, 1 stop bit, e.g.

  8n1

' setDTR SerCfg: DTR            \ sid ior flag -- sid ior'
Used in the configuration string to set the DTR line, where flag is non-zero for active and zero for inactive.

  1 DTR

' setRTS SerCfg: RTS            \ sid ior flag -- sid ior'
Used in the configuration string to set the RTS line, where flag is non-zero for active and zero for inactive.

  1 RTS

: Unix          \ sid ior -- sid ior
Used in the configuration string. Set the serial line to use LF as the line terminator sequence. CR characters will be ignored by ACCEPT.

: DOS           \ sid ior -- sid ior
Used in the configuration string. Set the serial line to use CR/LF as the line terminator sequence. This can also be used for Macs before OS X, but LF characters will be ignored by ACCEPT.

: open-Ser    \ addr len attribs sid -- sid ior
The string caddr/len is split into two. The space delimited left hand side is used as the device, e.g. "/dev/ttyS4" which is opened in raw mode. A default set up of 115200 baud, n81 and Unix line handling is applied, and then the right hand side of the string is parsed. Only the words documented as available in the serial configuration string may be used.

: ioctl-ser     \ addr len fn sid -- ior
The serial ioctl functions provide control over the serial line outputs and Unix/DOS mode handling. Where parameters are shown as ??, their value is ignored.


\ ?? ?? #50 sid -- ior ; Unix mode, LF
\ ?? ?? #51 sid -- ior ; DOS/Windows mode, CR/LF
\ ?? ?? #52 sid -- ior ; Mac mode, CR
\ ?? ?? #53 sid -- ior ; native mode, LF for Unices
\ caddr len #55 sid -- ior ; set string for CR.
\ linechar ignchar #56 sid -- ior ; set input chars for ACCEPT
\ ?? flag #60 sid -- ior ; set DTR, nz=active
\ ?? flag #61 sid -- ior ; set RTS, nz=active

Device Creation

: initSerDev    \ sid --
Initialise the sid for a serial device.

: serdev:        \ "name" -- ; Exec: -- sid
Create a Serial Port based Generic IO device in the dictionary.

 serdev: <name>

OS X serial devices

When using USB serial devices, the name used varies according to the function. The names are:

  /dev/tty.usb??????
  /dev/cu.usb?????

You can list them at a Terminal prompt with

  ls /dev/tty*
  ls /dev/cu*

The /dev/tty* devices are for modems waiting for a call into the OS X machine. It is assumed that the DCD line is active. Hence these are of little use for the three wire connections (RX, TX, Gnd) typically used to connect to embedded systems.

The /dev/cu* devices are much better suited for connecting out (calling up) to other systems.

Mac serial terminal emulators

The ones listed here are just ones recommended by others.

screen - on every Mac. For hardcore Unix buffs.

  screen /dev/cu.usbserial 19200
  http://hints.macworld.com/article.php?story=20061109133825654

Coolterm - GUI app.

  http://freeware.the-meiers.org

goSerial - GUI app.

  http://www.furrysoft.de/?page=goserial

The most widely used equivalent to Windows' HyperTerm appears to be minicom. It isn't pretty, but it works and is easy to use.

  http://pbxbook.com/other/mac-tty.html#minicom

XTERM Device

The XTERM Generic IO Device controls an xterm or equivalent device. Facilities are provided for cursor positioning, setting the foreground and background colours, line editing and line history. Cursor positioning uses ANSI escape sequences. Any terminal emulator which supports these sequences, e.g. in ANSI or VT100 mode, should work with this code. A good introduction to ANSI escape sequences may be found at http://en.wikipedia.org/wiki/ANSI_escape_code.

In order to create a device use the XTERM: definition given later. When opening a device the parameters to OPEN-GEN are unused. For compatibility with future versions please set them to -1, e.g.

  -1 -1 -1 <sid> open-gio

The IOCTL function has the following action

You can set the text foreground and background colours:

  <fcolour> <bcolour> #10 <sid> IOCTL-GIO drop

where colour is a colour in the XTERM format. If a colour is set to -1 the existing colour is left unchanged. For XTERMs and VT100/220 compatible terminals, the following colours are standard.


0 constant Black
1 constant Red
2 constant Green
3 constant Yellow
4 constant Blue
5 constant Magenta
6 constant Cyan
7 constant White
: +bright  \ color -- color'
\ Convert a colour into its bright version.
  #60 +
;

Similarly, terminal positioning control uses ANSI (VT100 and VT220) sequences. If you are connecting using Telnet or other remote access techniques (or even a real terminal), set it to ANSI, VT100 or VT220 compatibility mode.

Line editing is performed using the cursor keys, BS (<- or ^H) to delete before the cursor, and the DELETE keys to delete after the cursor. You can also use ^W and ^R for cursor movement. You can recall lines using the up (previous) and down (next) cursor keys. Lines can be edited after recall. You can also use ^E and ^D instead of the up and down keys. Note that Linux implementations are not consistent in the codes returned by keys such as BS and DELETE.

Unlike other devices, an XTERM uses three handles that correspond to stdin, stdout and stderr. By default these are handles 0, 1 and 2 respectively. If you wish to use different handles, you are responsible for their management. Use these handles by setting them into the /xterm-sid structure below. By default, the open operation uses the preset handles, which are not closed by the close operation.

The source code can be found in the file Lib/Osx32/Genio/xterm.fth, which is compiled during the second stage build. If you change this file, perform a second stage build when you wish to commit to using the changed file.

struct /xterm-sid       \ -- len
Defines the SID of an xterm console device.

  gen-sid +                     \ reuse field names of GEN-SID
  int xs.hIn                    \ input handle
  int xs.hOut                   \ output handle
  int xs.hErr                   \ error handle
  int xs.flags                  \ control flags
                                \ bit 0 - QUIT control
                                \ bit 1 - rfu
                                \ bit 2 - 1=maintain history, 0=none
                                \ bit 3 - 1=history in system ini file
  int xs.hiBuff                 \ address of 64k history buffer
  int xs.hiIndex                \ current history line#
  int xs.hiLowIndex             \ lowest history line#
  int xs.hiSection              \ pointer to INI file section name
end-struct

: initXtermSid  \ addr --
Initialise a /xterm structure at addr.

: xterm:        \ -- ; -- sid ; XTERM: <name>
Create a new terminal device.

xterm: xconsole \ -- addr
VFX Forth console.

: init-xcon     \ --
Set up to use the xconsole device. Performed at start up and compilation.

: term-xcon     \ --
Shut down the xconsole device. Performed at shut down.

Sockets

This Generic IO Device operates on Mac OS X socket for input or output. General socket programming words are made available in the Forth vocabulary.

In order to create a device use the SOCKDEV: definition given later.

When opening a socket device the parameters to OPEN-GEN have the following meaning:


   ADDR            Address of configuration data structure
   LEN             connection name zstring
   ATTRIBS         0=socket, 1=connect, 2=listen.

Sockets API

Many of the BSD socket functions are defined. Note that the accept function is accessed by SACCEPT to avoid a name clash with the ANS word ACCEPT.

AliasedExtern: saccept int OSCALL accept( int, void *, int *);
Because the sockets accept function has a name clash with the Forth word ACCEPT it is made available as SACCEPT.

Network order (big-endian) operations

TCP/IP protocols usually send data in what is called network order, which just means most-significant byte first. In memory, numbers are thus stored in big-endian form. The following words provide memory operations for this. These functions have to be capable of fetching 32 bit cells from 16 bit aligned addresses, not just from 32 bit aligned addresses.

: w@(n)         \ addr -- u16
Network order 16 bit fetch.

: w!(n)         \ u16 addr --
Network order 16 bit store.

: @(n)          \ addr -- u32
Network order 32 bit fetch.

: !(n)          \ u32 addr --
Network order 32 bit store.

: w,(n)         \ w --
Network order W,

: ,(n)          \ x --
Network order version of , (comma).

General socket functions in Forth

These words are available in the FORTH vocabulary for general socket programming.

max_path buffer: IPname \ -- addr
Holds the local computer's name as a zero terminated string.

2 cells buffer: IPaddress       \ -- addr
Holds the local computer's IP address as a four byte IPv4 number in network order. A value of 0 indicates that the address is unknown.

: findLinkIP    \ caddr len addr --
Find the IP address assigned to the given link, e.g. eth0, and place the link address at addr.

: findCurrIP    \ addr --
Place the IP address of the current network at addr. This word assumes that only one link is active at a time. The assumption is usually true.

#256 buffer: NetIF$     \ -- addr
Holds the name of the default network device, usually en0. This is a counted string.

: InitOSXSockets        \ --
Initialise sockets; called by the cold chain and during compilation.

: ?sockerr      \ serr -- ior
If serr is -1, the actual errno value is returned, otherwise zero is returned.

: writesock     \ c-addr u hsock -- len ior
Write the buffer to a socket, returning the length actually written and 0 on success, otherwise returning SOCKET_ERROR and the Linux error code.

: readsock      \ c-addr u hsock -- len ior
Read into a buffer from a socket, returning the length actually read and 0 on success, otherwise returning SOCKET_ERROR and the Linux error code.

: pollsock      \ hsock -- #bytes|-1
Poll a socket and return the number of bytes available to be read.

: sockReadLen   \ caddr len hsock -- ior
Read len bytes of input from a socket to the buffer at caddr, returning ior=0 if all bytes have been read. This is a blocking function which will not return until len bytes have been read or an error occurs.

: bindTo        \ hs af port ipaddr -- res
A non-BSD function that binds a socket to the given set of address family (af, usually AF_INET), port (port) and IP address (ipaddr). The returned result (res) is 0 for success, otherwise -1. See BIND.

: (Connect)     \ caddr u port# socket -- socket ior
Attempt to connect to a server. The socket has already been created in the appropriate mode. Caddr/u describes the server address either as a name or an IPaddress string and port# is the requested port. If u is zero, caddr is treated as a 32 bit number representing an IPv4 address. On success, the socket and zero are returned, otherwise SOCKET_ERROR and the Linux error code are returned.

: TCPConnect    \ c-addr u port# -- socket ior
Attempt to create a TCP socket and connect to a server. */i{Caddr/u} describes the server address either as a name or an IPaddress string and port# is the requested port. If u is zero, caddr is treated as a 32 bit number representing an IPv4 address. On success, the socket and zero are returned, otherwise SOCKET_ERROR and the Linux error code are returned.

: UDPConnect    \ c-addr u port# -- socket ior
Attempt to create a TCP socket and connect to a server. */i{Caddr/u} describes the server address either as a name or an IPaddress string and port# is the requested port. If u is zero, caddr is treated as a 32 bit number representing an IPv4 address. On success, the socket and zero are returned, otherwise SOCKET_ERROR and the Linux error code are returned.

Socket device

A socket device is created by SOCKETDEV: <name>.


SocketDev: SDsid  \ -- addr

When opening a socket device the parameters to OPEN-GEN have the following meaning:


   ADDR            Address of an /SDopen data structure.
   LEN             Address of Windows IP address zstring
                   If 0, /SDopen contains the IP adress.
   ATTRIBS         mode: 0=socket, 1=connect,

The following constants define the modes used to open socket:

  SD_SOCKET SD_CONNECT SD_LISTEN

struct /SDopen  \ -- len
The structure required for opening a socket Generic I/O device. Not all fields are used by all modes. The */fo{/SDopen} structure is defined as follows:

  int SDO.af                \ address family, usually AF_INET
  int SDO.type              \ socket type, e.g. SOCK_STREAM
  int SDO.protocol          \ IPPROTO_TCP ...
  sockaddr_in field SDO.sa  \ SOCKADDR_IN structure
end-struct

The SDO.af field is AF_INET for all TCP/IP operations. The SDO.TYPE field is SOCK_STREAM for TCP or SOCK_DGRAM for UDP. The SDO.protocol field is IPPROTO_TCP for TCP or IPPROTO_UDP for UDP. The SDO.sa field is a SOCKADDR or SOCKADDR_IN structure (same sizes), defined as follows:


struct sockaddr_in      \ -- len
  2 field sin_family        \ address family, usually AF_INET
  2 field sin_port          \ port ; in network order
  4 field sin_addr          \ IP address ; in network order
  8 field sin_reserved      \ RFU
end-struct

Note that only the first field is stored in native (little-endian for Intel i32) order. The other fields contain data in network (big-endian) form.

To open a socket, fill in a /SDopen structure, and call OPEN-GEN. The following example connects to a server.


SocketDev: SDsid  \ -- addr ; device

create zserver$   \ -- z$addr ; server name
  z", www.mpeforth.com"

create MySDopen   \ -- addr
  AF_INET ,       \ internet family
  SOCK_STREAM ,   \ connection type
  IPPROTO_TCP ,   \ TCP protocol
  AF_INET w,      \ server family, start of SOCKADDR_IN
  #80 w,(n)       \ server port
  #0 ,(n)         \ server IP address if known
  8 allot&erase   \ reserved
...
MySDopen zserver$ SD_connect SDsid open-gio

This will return the sid again and a result code (0=success).

The socket can then be used as the current I/O device.


: UseSDsid        \ --
  SDsid dup op-handle !  ip-handle !
;

struct /socket-sid      \ -- len
Defines the SID of a socket device.

: sd-flush      \ sid -- ior
Output to the socket is buffered to avoid running out of Linux buffers. Call FLUSHOP-GEN ( -- ior ) or KEY? to transmit the buffered output.

: sd-close      \ sid -- ior
The close function flushes pending output, closes the event object if used, performs shutdown with how=1, and closes the socket.

: sd-type       \ caddr len sid --
Buffered output.

: sd-write      \ caddr len sid -- ior
Buffered output.

: sd-emit       \ char sid --
Buffered output.

: sd-cr         \ sid --
Buffered output.

: sd-key?       \ sid -- #bytes|-1
The KEY? primitive for a socket returns the number of bytes available. If an error occurs, -1 is returned and KEY returns CR (ASCII code 13) so that KEY and ACCEPT do not block. Use the IOCTL function if you want to test for a specific error return code. Any buffered output is sent first.

: sd-key        \ sid -- char
If an error occurs, CR (ASCII code 13) is returned. Any buffered output is sent first.

: sd-ioctl      ( addr len fn# sid -- ior )
The IOCTL primitive for a socket is used to get or set socket status. The following functions are supported by IOCTL-GEN for sockets.

  addr 0 #10 sid -- ior

Place the number of bytes available to be read by recv at addr.

  0 0 #11 sid -- ior

Set the socket to notify when closed. N.B. This is not currently implemented

  0 0 #12 sid -- ior

Ior is returned non-zero if the socket has been closed. Ior is is returned false (zero) if the socket is still open or notification has not been requested. The socket must be open.

  state FD_xxx #20 sid -- ior

Set the created socket to notify on the FD_xxx flags. If state is zero the socket is set/restored to blocking mode otherwise it is set to blocking mode. N.B. This is not currently implemented

  state FD_xxx #21 sid -- flags

Flags contains FD_xxx bits which indicate what events have occurred from the set rquested by the call above. Flags is is returned false (zero) if no events have been reported or notification has not been requested. The socket must be open. N.B. This is not currently implemented

  0 0 #22 sid -- ior

Reset any notifications returned by function 21 above. Ior is zero for success or the Linux error code. N.B. This is not currently implemented

  0 0 #23 sid -- ior

Stop notification. Ior is zero for success or the Linux error code. N.B. This is not currently implemented

  0 flags #30 sid -- 0  ; set the device flags
  0 0     #31 sid -- flags ; get the device flags

The device flags control how some operations behave. Flags is a set of bits as follows:


Bit 0 - set to stop echoing during ACCEPT.

Device Creation

: InitSD        \ addr --
Initialise the data required for a socket device at addr.

: SocketDev:    \ "name" -- ; Exec: -- sid
Create a new socket device called name in the dictionary.