N: Theory of Operation - FujiNetWIFI/fujinet-firmware GitHub Wiki

The N: Device is somewhat different from the other devices implemented by FujiNet, in that multiple network protocols are supported by the filespec passed into the explicit OPEN command. Because of this, a theory of operation must be discussed for the N: device.

Opening a Network Connection

To open a network connection, a Network Open command must be issued. For CIO, this maps directly to a CIO OPEN command. This command is translated to a Network Open ('O' aka $4E) command by the CIO handler into SIO.

CIO: N: OPEN

Aux1 and aux2 are literally passed through as daux1 and daux2 to the SIO Command $4E Network Open command.

Reading and Writing (aux1)

Since this is primarily intended to be used by CIO, AUX1 works as expected, with bit 2 specifying read, and bit 3 specifying write. If both bits are set, then reading and writing is allowed on the network socket.

Aux1 is handled by the sioNetwork base class, and should set appropriate flags to check if reading and writing are allowed for a device.

Alternate Options (aux2)

The definition of aux2 is entirely protocol specific, and is passed through verbatim to the protocol handler.

The Buffer

SIO Command $4E Network Open explicitly wants a 256 byte buffer containing the Devicespec to open. It must be null terminated, but can (and most likely will) contain an EOL ($9B) character.

Types of Network Filespecs that can be opened:

The network filespec is of one of two possible forms, for client connections which do not open listening sockets, and server connections, which do not connect to a specific host, but open a listening socket. Not all protocols support both connection methods, and any attempt to open an unsupported filespec to a protocol should return an SIO error, which can have its status returned by a SIO STATUS command (and pulled from the fourth byte of DVSTAT)

Client (non-listening connections)

Nx:PROTOCOL:PATH:PORTNUM

Where:

  • x specifies the device #. Either N:, N1:, N2:, N3:, N4:, N5:, N6:, N7: or N8:, N: implies N1:
  • PROTOCOL is a protocol identifier, examples of protocol identifiers are given below.
  • PATH is passed directly to the protocol handler and may be up to 232 bytes long, and must not contain a a colon. If one is required, then the protocol handler must provide a way to escape the colon character.
  • PORTNUM is a number between 1 to 65535 specifying the port number intended to open.

Examples

  • To open a TCP connection to BBS.FOZZTEXX.COM port 23: N:TCP:FOZZTEXX.COM:23
  • To open a UDP connection to 192.168.1.8 port 2000: N:UDP:192.168.1.8:2000
  • To open an HTTP connection to WWW.GOOGLE.COM: N:HTTP://WWW.GOOGLE.COM:80

Server (listening connections)

Nx:PROTOCOL:PORT

Where:

  • x specifies the device #. Either N:, N1:, N2:, N3:, N4:, N5:, N6:, N7: or N8:, N: implies N1:
  • PROTOCOL is a protocol identifier, examples of protocol identifiers are given below.
  • PORTNUM is a number between 1 to 65535 specifying the port number intended to open.

Rationale for difference

Because FujiNet network interfaces aren't planned to be multi-homed, I do not see a rationale to specify a path (which would ostensibly specify a network interface to listen upon.)

Examples

  • To listen for a TCP connection on port 2000: N:TCP:2000
  • To listen for UDP packets on port 2000 N:UDP:2000

Tying Open All Together

So, from BASIC, the command:

OPEN #1,12,0,"N:TCP:192.168.1.8:2000"

Would be interpreted by the CIO handler as a READ/WRITE channel, and would emit the following SIO request:

char* pathspec[256]="N:TCP:192.168.1.8:2000"

OS.dcb.ddevic=0x71;
OS.dcb.dunit=1;
OS.dcb.dcomnd='O';
OS.dcb.dstats=0x80;
OS.dcb.dbuf=&pathspec;
OS.dcb.dbyt=256;
OS.dcb.dtimlo=0x0F; // 16 second timeout
OS.dcb.daux1=12;
OS.dcb.daux2=0;
siov();

The SIO handler should then process the pathspec buffer passed in, removing EOL from its stream, and parsing the result. An example tokenizer/parser:

/**
 * Parse deviceSpecs of the format
 * Nx:PROTO:PATH:PORT or
 * Nx:PROTO:PORT
 */
bool sioNetwork::parse_deviceSpec(char *tmp)
{

    char *p;
    char i = 0;
    char d = 0;

    p = strtok(tmp, ":"); // Get Device spec

    if (p[0] != 'N')
        return false;
    else
        strcpy(deviceSpec.device, p);

    while (p != NULL)
    {
        i++;
        p = strtok(NULL, ":");
        switch (i)
        {
        case 1:
            strcpy(deviceSpec.protocol, p);
            break;
        case 2:
            for (d = 0; d < strlen(p); d++)
                if (!isdigit(p[d]))
                {
                    strcpy(deviceSpec.path, p);
                    break;
                }
            deviceSpec.port = atoi(p);
            return true;
        case 3:
            deviceSpec.port = atoi(p);
            return true;
            break;
        default:
            return false; // Too many parameters.
        }
    }
}

Once parsed, the protocol handler should be determined, and its open handler called. If the open handler fails, then an sio_error() should be emitted, the CIO handler should receive the resulting Error 144, which should cause the CIO handler to ask for a SIO STATUS command. The fourth byte of DVSTAT should contain the error number of what happened.

unsigned char err;

OS.dcb.ddevic=0x71;
OS.dcb.dunit=1;
OS.dcb.dcomnd='S';
OS.dcb.dstats=0x40;
OS.dcb.dbuf=&OS.dvstat;
OS.dcb.dbyt=4;
OS.dcb.dtimlo=0x0f;
OS.dcb.daux=0;
siov();

err=OS.dvstat[3];

Error codes I can think of:

  • Error 128 - No active connection
  • Error 165 - Invalid Device spec.
  • Error 138 - Timeout
  • Error 170 - Invalid path (e.g. unknown host)
  • Error 171 - Connection immediately closed (is there something more appropriate for this?)
  • Error 172 - Could not allocate buffers

Getting Status

Status can be queried to, for example, check for the number of bytes waiting in the receive buffer. So if an IOCB was opened using one of the protocol strings above:

STATUS #1,A

Can be used to get the status of an open channel.

This is translated into the following SIO command:

OS.dcb.ddevic=0x71;
OS.dcb.dunit=1;
OS.dcb.dcomnd='S';
OS.dcb.dstats=0x40;
OS.dcb.dbuf=&OS.dvstat;
OS.dcb.dbyt=4;
OS.dcb.dtimlo=0x0f;
OS.dcb.daux=0;
siov();

and the result of the fourth DVSTAT byte should be returned as both the return and error values to the CIO handler.

The rest of the status information can be retrieved by looking at DVSTAT, which will have the following structure:

Offset Description
0 The Low byte of # of bytes waiting in buffer
1 The High byte of # of bytes waiting in buffer
2 Connection Status (0 = not connected, 1 = connected)
3 Last error (1 on success, otherwise an error

Status and Interrupts

Because FujiNet has access to the INTERRUPT and PROCEED lines, we can code the CIO handler to be more intelligent about when to poll the FujiNet for new status information. The general mode of operation is:

  1. On CIO handler initialization, point OS.vinter to our interrupt handler
  2. Set the interrupt enable flag in PIA's PACTL
  3. When Interrupt goes LOW, our vector is called, which sets "dirty flag"
  4. If dirty flag is set, poll for new data, then reset interrupt enable to indicate it has been serviced.
  5. Repeat to step 2.

This would imply that there should be a digitalWrite to the INTERRUPT pin to pull LOW, when to pull back HIGH?

Reading and Writing

Reading and writing are complementary functions with corresponding reciprocity in their parameters.

Of note, Due to how SIO works, the number of requested bytes must be less than or equal to the number of bytes waiting in the buffer. For this reason, it is important that a Network Status command should be issued before any read or write, so that a usable value for # of bytes to retrieve is known.