Chapter 5. Programming Examples

This chapter contains the following sections:

To perform any of the operations described in this section, the application must open a stream to the X.25 PLP driver. Once the stream has been opened, it can be used for initiating, listening for, or accepting a connection. There is a one-to-one mapping between X.25 virtual circuits and PLP driver streams. Once a connection has been established on a stream, the stream cannot be used other than for passing data and protocol messages for that connection.

Such a stream is opened on /dev/x25, the major device, as follows:

if ((x25_fd = open("/dev/x25", O_RDWR))  <  0)
{
        perror("Opening Stream");
        exit(1);
}

Using the NLI Conversion Module

IRIS SX.25 provides an “NLI Conversion Module” which allows older NLI applications to run, without modification, over a new version of the NLI. This situation might arise in porting an application from another platform that supports the older NLI version. The module ensures binary compatibility between applications that utilize an older version of the NLI than that supported by the current release of IRIS SX.25. Newly developed applications for IRIS SX.25 have no need for the conversion module.

The IRIS SX.25 conversion module provided for this release, s_nli3, converts version 3 applications to the future versions of the NLI. An ioctl command, N_getnliversion, is used to get the current version of the NLI. If the NLI applications are older than the current network, then the appropriate conversion module can be directly pushed onto the protocol stack. An example is given below.

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stream.h> 
#include <stropts.h>
#include <sys/snet/uint.h>
#include <sys/snet/x25_proto.h>
#include <sys/snet/ll_proto.h>
#include <sys/snet/x25_control.h>

#define OPENFLAGS O_RDWR

int              fd;
struct nliformat versioninfo;
struct strioctl  nioctl;
/*
    Open a stream to the device using the flags
    passed  to  the  open routine.
*/
if ((fd = open("/dev/x25", OPENFLAGS)) < 0)
{
    perror("failed to open stream");
    return(-1);
}
/*
    Send an ioctl  message requesting the current NLI
    version number.
    If the ioctl fails, then return -1 to fail the open.
    If the ioctl succeeds, then compare the returned version
    number with the library version number. There are 3
    possible cases:
        i) if a value less than the library NLI version
           number is returned then an attempt has been made
           to run an application over an X.25 driver which
           does not support multiple NLI versions.
       ii) if a value equal to the library NLI version number
           is returned then no further action is required.
      iii) otherwise a version conversion is required, so
           push on a module that converts between the
           library NLI version and the X.25 driver version.
*/
versioninfo.version = 0;
nioctl.ic_cmd = N_getnliversion;
nioctl.ic_timout = 0;
nioctl.ic_len = sizeof(struct nliformat);
nioctl.ic_dp = (char *)&versioninfo;
if (ioctl(fd, I_STR, &nioctl) < 0)
{
    perror("N_getnliversion ioctl failed");
    return(-1);
}
if (versioninfo.version < NLI_VERSION)
{
   fprintf(stderr,"X.25 driver is older than application\n”);
   return(-1);
}
else
if (versioninfo.version > NLI_VERSION)
{
    if (ioctl(fd, I_PUSH, "s_nli3") < 0)
    {
        perror("Failed to push conversion module");
        return(-1);
    }
}
/*
    If neither of the above cases is TRUE then there is no
    need to  PUSH the module as the application has the same
    NLI version as the network
*/
return(fd);
}

Also provided is an NLI library, which allows application software to have access to the X.25 PLP driver without having detailed knowledge of the operation of the network providers services. The n_open routine ensures that the correct conversion module is pushed onto the protocol stack.

The n_open routine is used as follows:

if ((x25_fd = n_open("/dev/x25", O_RDWR, NULL)) < 0)
{
    perror("Opening Stream");
    exit(1);
} 

The first two parameters are the same as for any STREAMS open routine, namely the device name and the open flags. The third is the service argument, which is used to return the service characteristics of the network provider. This argument should be of the type:

(struct n_info *)

This service facility is currently unsupported. Setting the parameter to NULL means the parameter is ignored. If the conversion fails, –1 is returned. Any application using the n_open routine should “link in” the appropriate NLI library for the release of the NLI they are using.

Opening a Connection

To establish a connection on an open stream, an application must do the following:

  1. Allocate a Connect Request structure.

  2. Supply the Connect Request with the quality of service and facilities parameters.

  3. Set the called (and optionally calling) addresses.

  4. Pass the Connect Request down to the X.25 driver.

  5. Wait for the connect confirmation or rejection.

The following sections describe the procedures for opening a connection for a CONS call and for a non-CONS call, respectively.

CONS Calls

The following example opens a connection for a CONS call:

#define FALSE   0
#define TRUE    1

#include <memory.h> 
#include <sys/snet/x25_proto.h>

struct xaddrf called =
    { 0, 0, {14, { 0x23,  0x42,  0x31, 0x56, 0x56, 0x56,
    0x56 }}, 0};
    /* Subnetwork "A" (filled in later), no flags,
       DTE = "23423156565656", null NSAP */

struct xcallf   conreq;

/* Convert sn_id to internal format */
called.sn_id = snidtox25("A");   /* snidtox25  only  fails
                                    if a NULL string is
                                    passed to it */
conreq.xl_type = XL_CTL;
conreq.xl_command = N_CI;
conreq.CONS_call = TRUE;         /* This is a CONS call */
conreq.negotiate_qos = TRUE;     /* Negotiate requested */

memset(&conreq.qos, 0, sizeof(struct qosformat));
conreq.qos.reqexpedited = TRUE;  /* Expedited requested */
conreq.qos.xtras.locpacket = 8;  /* 256  bytes  */ conreq.qos.xtras.rempacket = 8;  /* 256 bytes */
memcpy(&conreq.calledaddr, &called, sizeof(struct xaddrf));
memset(&conreq.callingaddr, 0, sizeof(struct xaddrf));


Note: When negotiate_qos is true (nonzero), setting the fields to zero means that the connection uses defaults for QOS and facilities. If required, these can be set to different values, but it is recommended that the whole QOS structure be zeroed first as shown. This is preferable to setting each field individually, as it allows for any future additions to this structure. Setting the calling address to null leaves the network to fill in this value.

The message is then sent on the stream using the putmsg system call, with any Call User Data (CUD) being passed in the data part of the message:

#define CUDFLEN  4

struct strbuf ctlblk, datblk;
char          cudf[CUDFLEN] = { 1, 0, 0, 0 };

ctlblk.len = sizeof(struct  xcallf);
ctlblk.buf = (char *) &conreq;
datblk.len = CUDFLEN;
datblk.buf = cudf;

if (putmsg(x25_fd, &ctlblk, &datblk, 0) < 0 )
{
    perror("Call putmsg");
    exit(1);
}

At this stage, the application should wait for a response to the call request. The response may be either a Connect Confirmation or a Disconnect Indication (rejection) message. (The second #define below may be shown wrapped; it must be on one line in the source file.)

#define    DBUFSIZ    128
#define CBUFSIZ MAX(sizeof(struct xccnff),sizeof(struct xdiscf))

int              getflags = 0;
S_X25_HDR        *ind_msg;
char             ctlbuf[CBUFSIZ], datbuf[DBUFSIZ];

struct xccnff    *ccnf;
struct qosformat qos;

ctlblk.maxlen = CBUFSIZ;
ctlblk.buf    = ctlbuf;
datblk.maxlen = DBUFSIZ;
datblk.buf    = datbuf;

for(;;)
{
    if (getmsg(x25_fd, &ctlblk, &datblk, &getflags) < 0)
    {
        perror("Getmsg fail");
        exit(1);
    }
    ind_msg = (S_X25_HDR *) ctlbuf;
    if (ind_msg->xl_type != XL_CTL)
        continue;
    switch (ind_msg->xl_command)
    {
        case N_CC:
            /* ........ Process the Connect Confirmation */
            ccnf = ((struct xccnff *) ind_msg;
            if (ccnf -> negotiate_qos)
            {
                bcopy (&qos, ccuf->qos,
                    sizeof (struct qosformat));
                if (qos->reqexpedited)
                    printf("Request Expedited set\n");
                else
                    printf("Request Expedited not set\n");
            }
            else
            {
            /* indicated values have been accepted */
            }
            return;
        case  N_DI:
            perror("Connection rejected");
            exit(1);
        default:
            continue;
    }
}

In the example, getmsg is used to retrieve the next message from the stream head. This is done in a loop, until the application receives either a Connect Confirmation message, indicating successful completion, or a Disconnect Indication, showing that the connect attempt was rejected.


Note: The facility and QOS values indicated in the Connect Confirmation are those that are used for the duration of the connection.

It is possible to abort the connect request before a response is received. The application can do this by sending a Disconnect Request message (see the section “Closing a Connection” in this chapter). If this is done, the application should read and discard all messages from the stream until it receives the disconnect acknowledgment (described in the section “Disconnect Request/Indication” in this chapter).

After a rejection or connect abort the stream remains open, and can be used, for example, to make further connection attempts.

Non-CONS Calls

The following example opens a connection for a non-CONS call:

#define FALSE   0
#define TRUE    1

#include <memory.h>
#include <sys/snet/x25_proto.h>

struct xaddrf   called =
    { 0, 0, { 14, { 0x23, 0x42, 0x31, 0x56, 0x56, 0x56,
    0x56  }}, 0 };
    /* Subnetwork "A" (filled in later), no flags,
       DTE = "23423156565656", null NSAP */
struct xcallf   conreq;

/* Convert sn_id to internal format */
called.sn_id = snidtox25("A");
conreq.xl_type = XL_CTL;
conreq.xl_command = N_CI;
conreq.CONS_call = FALSE;      /* This is not a CONS call */
conreq.negotiate_qos = FALSE;  /* Just  use  default  */

memset(&conreq.qos, 0, sizeof(struct qosformat));
memcpy(&conreq.calledaddr, &called, sizeof(struct xaddrf));
memset(&conreq.callingaddr, 0, sizeof(struct xaddrf));


Note: When negotiate_qos is true (nonzero), setting the fields to zero means that the connection uses defaults for QOS and Facilities. If required, these can be set to different values (see the sections “Quality of Service and X.25 Facilities” in Chapter 2 and “Connect Request/Indication” in Chapter 4 for more details). However, it is recommended that the whole QOS structure be zeroed first, as shown. This is preferable to setting each field individually, as it allows for any future additions to this structure. Setting the calling address to null leaves the network to fill this value in.

The message is sent on the stream using the putmsg system call, with any CUD being passed in the data part of the message:

#define CUDFLEN  4

struct strbuf   ctlblk, datblk;
char            cudf[CUDFLEN] = { 1, 0, 0, 0 };

ctlblk.len = sizeof(struct  xcallf);
ctlblk.buf = (char *) &conreq;

datblk.len = CUDFLEN;
datblk.buf = cudf;

if (putmsg(x25_fd, &ctlblk, &datblk, 0) < 0 )
{
    perror("Call putmsg");
    exit(1);
}

Data Transfer

In the data transfer phase, access is given to:

  • The Q-bit—to support X.29-like services

  • The M-bit—to signal packet fragmentation

  • The D-bit—to request confirmation of data delivery

  • Expedited Data—to support X.29 and CONS

Normal and Q-bit data is sent and received using the Data (N_Data) message and may be acknowledged using the Data Acknowledgment (N_DAck) message. Expedited Data uses the Expedited Data (N_EData) message, and is acknowledged using the Expedited Data Acknowledgment (N_EAck) message.

The following sections show examples of code for data transfer.

Sending Data

Once a connection has been successfully opened on a stream, sending a data packet is straightforward:

#define  DBUFSIZ  128

struct xdataf data;
char          datbuf[DBUFSIZ];
int           retval;

/* Copy data into datbuf[] here */
data.xl_type = XL_DAT;
data.xl_command = N_Data;
data.More = data.setQbit = data.setDbit = FALSE;

ctlblk.len = sizeof(struct xdataf);
ctlblk.buf = (char *) &data;
datblk.len = DBUFSIZ;
datblk.buf = datbuf;

retval = putmsg(x25_fd, &ctlblk, &datblk, 0);

Normally, the call to putmsg is blocked if there are flow control conditions in the connection, which lead to either a full queue at the stream head, or a lack of STREAMS resources. Blocking due to a full queue can be avoided if the stream is opened with the option O_NDELAY flagged. In this case, putmsg returns immediately, and the failure is signalled by a return value (retval) of EAGAIN.

This procedure allows the application to carry out other processing (for example, receiving data) before trying again. The best method to use depends on the nature of the application.

Receiving Data

In the same way, data reception is straightforward. When data is received with the D-bit set, action may be required by the application. When the initial Connect Request is sent, it may request that data confirmation be at the application-to-application level. If application-to-application confirmation is agreed upon, then on receiving a packet with the D-bit set, the application must acknowledge the packet by sending a Data Acknowledgment message.

This example prints out incoming data as a string, if the Q-bit is not set:

S_X25_HDR      *hdrptr;
struct xdataf  *dat_msg;
struct xdatacf *dack;

for (;;)
{
    if (getmsg(x25_fd, &ctlblk, &datblk, &getflags) < 0)
    {
        perror("Getmsg fail");
        exit(1);
    }
    hdrptr = (S_X25_HDR *) ctlbuf;
    if (hdrptr->xl_type == XL_CTL)
    {
        /* Deal with protocol message as required - 
           see below */
    }
    if (hdrptr->xl_type == XL_DAT)
    {
        dat_msg = (struct xdataf *) ctlbuf;
        switch (dat_msg->xl_command)
        {
            case N_Data:
                if (dat_msg->More)
                    printf("M-bit set\n");
                if (dat_msg->setQbit)
                    printf("Q-bit set\n");
                else
                {
                    if (dat_msg- >setDbit)
                        printf("D-bit set\n");
                    for (i =  1;i<datblk.len;  i++)
                        printf("%c", datbuf[i]);
                    /* If application to application Dbit
                       confirmation was negotiated
                       at call setup time, send an N_DAck */
                    if (app_to_app && dat_msg->setDbit)
                    {
                        dack = (struct xdatacf  *)
                            malloc(sizeof(struct xdatacf));
                        bzero((char *)dack, sizeof(struct
                            xdatacf));
                        dack->xl_command = N_DAck;
                        dack->xl_type = XL_DAT;
                        ctlblk->len = sizeof(struct xdatacf);
                        ctlblk->buf = (char *)dack;
                        datblk->len = 0;
                        datblk->buf = (char *)0;
                        putmsg(x25_fd, &ctlblk, &datblk,
                            &getflags);
                    }
                }
                break;
            case N_EData:
                printf("***Expedited data received\n");
                /* Must deal with */
                break;
            case N_DAck:
               printf("***Data Acknowledgement received\n");
               break;
            default:
                break;
        }
    }
}

Expedited Data

The above example allows for the possibility of receiving Expedited Data messages (which are carried in X.25 interrupt packets). These must be dealt with appropriately. Since only one Expedited Data packet can be outstanding in the connection at any time, its sender is prevented from sending any further such messages until the receiver has acknowledged it. It does this by sending an Expedited Data Acknowledgment message.

This is sent in much the same way as an ordinary Data packet, but with no data part. If the application does not need to use the Expedited Data capability, then other appropriate responses to receiving an Expedited Data message are to reset or to close the connection (see the sections “Resets” and “Closing a Connection” in this chapter).

When sending Expedited Data, the application must wait for an acknowledgment before requesting further expedited transmissions.

#include        <sys/snet/x25_proto.h>
#define         EXPLEN  4

struct xedataf exp;
char   expdata[]= {1, 2, 3, 4};

exp.xl_type = XL_CTL;
exp.xl_command  = N_Edata;
ctlblk.len      = sizeof (struct xedataf);
ctlblk.buf      = (char *) &exp;
datblk.len      = EXPLEN;
datblk.buf      = expdata;

if (putmsg(x25_fd, &ctlblk, &datblk, 0) < 0)
{
    error("Exp putmsg");
    exit(1);
}
for (;;)
{
    if (getmsg(x25_fd, &ctlblk, &datblk, &getflags) < 0)
    {
        perror("Getmsg fail");
        exit(1);
    }
    hdrptr = (S_X25_HDR *) ctlbuf;
    if (hdrptr->xl_type == XL_CTL)
    {
        /* Deal with protocol message as required  */
    }
    if (hdrptr->xl_type == XL_DAT)
    {
        dat_msg = (struct xdataf *) ctlbuf;
        switch (dat_msg->xl_command)
        {
            case N_Data:
                /* process more data */
                break;
            case N_EData:
                printf("***Expedited data received \n");
                /* Must deal with */
                .... send N_EAck ....
                break;
            case N_EAck:
                /* Expedited data received */
                /* Further  N_Edata  can  now be sent */
                break;
            default:
                break;
        }
    }
}

Resets

These can be dealt with in a way similar to the way interrupts are dealt with, except that there is no data passed with a Reset Request. When a Reset Request is issued, the application must wait for the acknowledgment, as for an Expedited Data request. However, until this is received, the only action that can be taken is to issue a Disconnect Request.

The diagnostic field in a Reset Request should be filled in with the reason for issuing the reset. Standard values for this are defined in the include file <sys/snet/x25_proto.h>, although the application can set any value. See Appendix B, “Error Codes,” for more details.

When a Reset Indication is received, there are only two valid actions that may be taken:

  • Send a Reset Confirmation message to acknowledge the reset.

  • Send a Disconnect Request. In this situation, pending data is flushed from the queue.

Reset Indications can be dealt with as part of the general processing of incoming messages—see the Disconnect handling example below.

#include        <sys/snet/x25_proto.h>

struct xrstf    rst;
S_X25_HDR       *hdrptr;

rst.xl_type     = XL_CTL;
rst.xl_command  = N_RI;
rst.cause       = 0;
rst.diag        = NU_RESYNC;
ctlblk.len      = sizeof (struct rstf);
ctlblk.buf      = (char *) &rst;

if (putmsg(x25_fd, &ctlblk, 0, 0) < 0)
{
    perror(" putnmsg");
    exit(1);
}
for (;;)
{
    if (getmsg(x25_fd, &ctlblk, &datblk, &getflags) < 0)
    {
        perror("Getmsg fail");
        exit(1);
    }
    hdrptr = (S_X25_HDR *) ctlbuf;
    if (hdrptr->xl_type  ==  XL_CTL)
    {
        continue;
    }
    switch (hdrptr->xl_command)
    {
        case N_RC:
            /* Reset complete */
            /* Enter data transfer */
            break;
        default:
            break;
    }
}

Control messages like resets and interrupts take higher priority than normal data messages, both internally in the PLP driver and across the network.

However, it is important to note that the NLI does not use the mechanism for priority processing of STREAMS messages (by setting the RS_HIPRI flag in putmsg). There are two reasons for this:

  • The stream head can hold only one incoming priority message (the first). This is inappropriate in certain situations where several of these messages may follow each other in quick succession. For example, a Reset may be followed immediately by a Disconnect.

  • An outgoing priority message would overtake any data that is queued, waiting to be sent. It is possible that data could then be sent after the priority message (for example, a reset), which would lead to an NLI protocol violation.

Closing a Connection

This section covers remote and local disconnects.

Remote Disconnect

If, during a connection, the remote end initiates a disconnect, then a Disconnect Indication message is received at the NLI (or possibly an Abort Indication message—see the section “Abort Indication” in Chapter 4). The application need not acknowledge this message since, after sending a Disconnect Indication, the X.25 driver silently discards all messages received except for connect and accept messages. These are the only meaningful X.25 messages on the stream after disconnection.

The receiver of a Disconnect Indication should ensure that enough room is available in the getmsg call to receive all parameters and, when present, up to 128 bytes of clear user data.

Handling such a disconnect event would normally be part of the general processing of incoming messages.

The example below could be combined with the code from the data transfer example shown above.

struct xdiscf *dis_msg;

if (hdrptr->xl_type == XL_CTL)
{
    switch (hdrptr->xl_command)
    {
        /* Other  events/indications  dealt  with
           here - e.g. Reset Indication (N_RI) */
        case N_DI:
            dis_msg = (struct xdiscf *) hdrptr;
            printf("Remote disconnect,
                cause = %x, diagnostic = %x \n",
                dis_msg->cause, dis_msg->diag);
            /* Any other processing needed here -
               e.g. change connection state */
            return;
        case N_Abort:
            printf("***Connection\n");
            /* etc. */
            return;
        default:
            break;
    }
}


Note: It is guaranteed that no X.25 interface messages are sent to the application once a disconnect message has been passed up to it, wherever the message came from. That is, it can be a Disconnect Indication or the “response” described in the section “Local Disconnect” in this chapter).

Although at this stage the stream is idle, it is in an open state and remains so until some user action. This could be to close the stream, or to initiate a new Listen or Connect Request on it.

Local Disconnect

To initiate a disconnect on a connection, the application should send a Disconnect Request message on the stream. Unless this is being used to reject an incoming call (see the section “Handling the Connect Indication” in this chapter), the X.25 driver signals that it has observed the message. It does this by sending a Disconnect Confirmation upstream when it receives the Disconnect Request. In this way, the upper components can be certain that no messages will follow the Disconnect Request.

In the case of rejection, the connection identifier supplied on the Connect Indication must be returned in the Disconnect Indication message. The Disconnect Request (reject) is not acknowledged in this case.

As in the case of a remote disconnection, once the response has been received the stream becomes idle, and remains in this state until the application sends out another control message. This may be to close the stream, or to initiate a new Listen or Connect Request on it. The application should, however, not send any of these messages until it receives the Disconnect Indication.

As described in the section “Disconnect Request/Indication” in Chapter 4, a disconnect collision may occur. If this happens, no Disconnect Confirmation is sent.

/* Coded and sent disconnect request, process response */
struct xdiscf *dis_ind;
struct xdcnff *dis_cnf;
struct extraformat *xqos = (struct extraformat *)0;

if (hdrptr->xl_type == XL_CTL)
{
    switch (hdrptr->xl_command)
    {
        /* Disconnect Collision */
        case N_DI:
            dis_ind = (struct xdiscf*) hdrptr;
            xqos = &dis_ind->indicatedqos.xtras;
            break;
        /* Disconnect Confirmation */
        case N_DC:
            dis_cnf = (struct xdcnff*)hdrptr;
            xqos = &dis_cnf->indicatedqos.xtras;
            break;
        default:
            return;
    }
    if (xqos)
    {
        /* Print  any  charging  information  returned */
        if (xqos->chg_cd_len)
        {
            /* Print out Call Duration from chg_cd_field */
        }
        if (xqos->chg_mu_len)
        {
            /* Print out Monetary Unit from chg_mu_field */
        }
        if (xqos->chg_sc_len)
        {
            /* Print out Segment Count from chg_sc_field */
        }
    }
}

Listening

For more information on listening, see Chapter 3, “Listens.”

Listening for Incoming Connections

Before an incoming call can be received from the X.25 driver, there must be at least one listener. Moreover, as mentioned in the section “Priority” in Chapter 3, listening for incoming connections may be a privileged operation—that is, the stream must have been opened by a process with superuser privilege.

To listen for an incoming connection, the application does the following:

  1. Sends a Listen Request message carrying the called address list that the application is interested in to the X.25 driver (see Chapter 3, “Listens”). After this, the application waits for the response to the Listen Request.

  2. When the Listen Response is received (and the l_result flag indicates success), wait for Connect Indication messages from the X.25 driver. If the l_result flag indicates failure, the application can decide either to close the stream or to try again later.

  3. When a Connect Indication is passed up, the application can decide whether to accept on this or a different stream.

  4. At this point, the facilities and QOS are negotiated if required. A Connect Confirmation message carrying the appropriate connection identifier is passed down on the stream on which the connection is being accepted.

Constructing the Listen Message

As described in Chapter 3, “Listens,” the listen message has two parts. The construction of the control part of the message is straightforward:

struct xlistenf     lisreq;

lisreq.xl_type = XL_CTL;
lisreq.xl_command = N_XListen;
lisreq.lmax = 1;

In this example, lmax has the value of 1, indicating that only one Connect Indication is to be handled at a time.

The data part of the message should be filled with the sequence of bytes that specifies the CUD string and address(es) which are to be listened for. The simplest case for this would be to set “Don't Care” values for both the CUD and address:

int lislen;
char        lisbuf[MAXLIS];

lisbuf[0] = X25_DONTCARE; /* l_cumode */
lisbuf[1] = X25_DONTCARE; /* l_mode   */
lislen = 2;

Alternatively, to set the CUD to match exactly the (X.29) value defined in the array cudf[] earlier (0x01000000), and the NSAP to match any sequence starting “0x80”, “0x00”, the following would be used:

lislen = 0;

lisbuf[lislen++] = X25_IDENTITY;          /* l_cumode   */
lisbuf[lislen++] = CUDFLEN;               /* l_culength */
memcpy(&(lisbuf[lislen]), cudf, CUDFLEN); /* l_cubytes  */
lislen += CUDFLEN;
lisbuf[lislen++] = X25_STARTSWITH;  /* l_mode   */
lisbuf[lislen++] = X25_NSAP;        /* l_type   */
lisbuf[lislen++] = 4;               /* l_length */
lisbuf[lislen++] = 0x80;            /* l_add    */
lisbuf[lislen++] = 0x00;

Or, to accept any CUD field, with a DTE of “2342315656565”:

#define     MY_DTE_LEN      13
#define     MY_DTE_OCTETS   7

char my_dte[MY_DTE_OCTETS] =
    {0x23,0x42,0x31,0x56,0x56,0x56,0x50};

lislen = 0;
lisbuf[lislen++] = X25_DONTCARE;    /* l_cumode */
lisbuf[lislen++] = X25_IDENTITY;    /* l_mode   */
lisbuf[lislen++] = X25_DTE;         /* l_type   */
lisbuf[lislen++] = MY_DTE_LEN;      /* l_length */     memcpy(&(lisbuf[lislen]), my_dte, MY_DTE_OCTETS);  /* l_add*/
lislen += MY_DTE_OCTETS;


Note: The l_add field uses packed hexadecimal digits and the l_length value is actually the number of semi-octets, whereas the l_culength field specifies the length of the l_cubytes field in octets.

Next, send the Listen Request down the open stream:

ctlblk.len = sizeof(struct xlistenf);
ctlblk.buf = (char *) &lisreq;
datblk.len = lislen;
datblk.buf = lisbuf;

if (putmsg(x25_fd, &ctlblk, &datblk, 0) < 0)
{
    perror("Listen putmsg failure");
    return -1;
}

Finally, wait for the Listen Response—the result flag indicates success or failure (the second #define below may be shown wrapped; it must be on one line in the source file):

#define    DBUFSIZ    128
#define    CBUFSIZ    MAX(sizeof(struct
xccnff),sizeof(struct xdiscf))

struct xlistenf *lis_msg;

ctlblk.maxlen = CBUFSIZ; /* See 4.1 above for declarations */
ctlblk.buf  =  ctlbuf;
datblk.maxlen  =  DBUFSIZ;
datblk.buf  = datbuf;

for (;;)
{
    if (getmsg(x25_fd, &ctlblk, &datblk, &getflags) < 0)
    {
        perror("Listen getmsg failure");
        return -1;
    }
    lis_msg = (struct xlistenf *) ctlbuf;
    if ((lis_msg->xl_type  ==  XL_CTL) &&
        (lis_msg->xl_command == N_XListen))
        if (lis_msg->l_result != 0)
        {
            printf ("Listen command failed\n");
            return -1;
        }
        else
        {
            printf("Listen command succeeded\n");
            return 0;
        }
}

Cancelling a Listen Request can be done in the same way, except that no data is passed with the request—it simply cancels all successful Listen Requests that have been made on that stream.

Handling the Connect Indication

Once the listening application has received a Listen Response indicating success, it should wait for incoming Connect Indications.

When a Connect Indication message arrives, the application should inspect its parameters—address, CUD, facilities, quality of service, and so on, then decide whether to accept or reject the connection.

Acceptance

If accepting, it can do so either on the stream the indication arrived on, or on some other stream. This other stream can be one that is already open and free, or it can be newly opened.

Whatever method is used for the accept, the identifier conn_id in the Connect Indication message must be copied into the accept message for matching by the X.25 driver. If this identifier in the accept message does not match, a Disconnect Request is sent to the accepting application. This causes the resource to hang on the stream on which the incoming call was sent, since the connection is never accepted.

Rejection

The call can be rejected by sending a Disconnect Request message down the stream on which the Connect Indication arrived. A Connect Indication cannot be rejected on a different stream. Again, the connection identifier must be quoted in the message for matching, since there may be several Connect Indications passed to the listening application. If there is no match for the rejection, the message is silently discarded.

The rejecting listener can request one of two actions in response to the disconnect:

  • Request immediate disconnect. Set the reason field to NU_PERMANENT (0xF5).

  • Search for further matching listeners. Set the reason field to any value except 0xF5.

The following code example shows how to reject an incoming call:

struct xcallf *conind;
struct xdiscf disc_msg;

/* Use getmsg to receive the Connect Indication,
   use conind to point to it */
disc_msg.xl_type = XL_CTL;
disc_msg.xl_command = N_DI;
disc_msg.conind = conind->conind;
disc_msg.cause = cause;    /* cause to be returned */
disc_msg.diag = diag;      /* diagnostic to be returned */

if (disc_immed)            /* no more searches */
    disc_msg.reason = NU_PERMANENT;    /* 0xF5 */

/* Send Rejection down stream with putmsg */


Note: The application must not accept a connection on a listening stream that is capable of handling more than one Connect Indication at one time if there could subsequently be other Connect Indications to be handled on that stream. For example, suppose the application issues a Listen Request to handle three Connect Indications at one time. A Connect Indication is received and sent to the application on the listen stream. The application must not accept this connection on the listen stream because there could be two more Connect Indications that could be sent subsequently.


Negotiation of QOS Parameters

The Connect Indication message passed contains X.25 facility values, and CONS QOS parameters, if appropriate. The application may want to negotiate these values. This is done by setting the negotiate_qos flag in the Connect Response message. The values received should then be copied into the response, and those facilities and/or parameters (and any related flags) for which a different value is desired should then be altered (see the section “Quality of Service and X.25 Facilities” in Chapter 2). It is recommended that the whole QOS structure be copied from the indication to the response. This is preferable to copying each field individually, as it allows for any future additions to this structure.

An example of negotiation is shown below. Here all the values are copied as indicated, except the packet size, which is negotiated down to 256 if it is flagged as negotiable, and is greater than 256:

struct xcallf   *conind;
struct xccnff   conresp;

/* Do a getmsg etc to receive the Connect Indication,
   assign conind to point to it.*/
conresp.xl_type = XL_CTL;
conresp.xl_command = N_CC;
conresp.conn_id = conind->conn_id; /* Connection identifier*/
conresp.CONS_call = TRUE           /* This is a CONS call */

memset(&conresp.responder, 0, sizeof(struct xaddrf));
                    /* Let network fill in responding addr */
conresp.negotiate_qos = TRUE;
memcpy(&conresp.rqos, &conind->qos,sizeof(struct qosformat));
if (conind->qos.xtras.pwoptions & NEGOT_PKT)
{
    if (conind->qos.xtras.rempacket > 8)
        conresp.rqos.xtras.rempacket = 8;   /* 256 = 28 */
    if (conind->qos.xtras.locpacket > 8)
        conresp.rqos.xtras.locpacket = 8; }

/* Set any other values to be negotiated here,
   then send the response down with a putmsg. */

Alternatively, the application may decide to accept (agree with) the indicated values, in which case the negotiate_qos flag is set to zero.

Reusing the Listen Stream

If a connection is never established on a listening stream (using a matching accept) then that stream remains listening on the address list supplied. On the other hand, once an established connection has been disconnected, the stream does not return to a listening state. Instead, it remains open in an idle state. If the application needs to listen again, then the listen message must be re-sent. Rejection does not alter the listening state of the stream.

PVC Operation

The following subsections describe the procedures necessary for an application to operate a PVC on the X.25 PLP driver.

Attaching a PVC

To attach a PVC on an open stream, an application must:

  1. Allocate a PVC Attach structure.

  2. Supply the structure with the appropriate reqackservice and reqnsdulimit parameters. These parameters are used for the duration of the connection.

  3. Set the appropriate subnetwork and Logical Channel Identifiers.

  4. Pass the attach request down to the X.25 driver.

  5. Wait for the attach accept or rejection.

For example:

#include <sys/stropts.h>
#include <sys/snet/x25_proto.h>

struct pvcattf attach = {XL_CTL, N_PVC_ATTACH,1,0,0,0,0};
    /* Subnetwork "A" (filled in later), Logical Channel 1
       No request for Receipt Ack or nsdulimit */
struct strbuf ctlblk; 

/* Convert sn_id to internal format */
attach.sn_id = snidtox25("A");
ctlblk.len = sizeof(struct pvcattf);
ctlblk.buf = (char *) &attach;

The message is then sent on the stream using the putmsg system call:

if (putmsg(x25_fd, &ctlblk, 0, 0) < 0)
{
    perror("Attach putmsg");
    exit(1); 
}

At this stage, the application should wait for a response to the attach. The response may indicate either a successful attachment or a rejection.

#define    DBUFSIZ     128
#define    CBUFSIZ     sizeof(struct pvcattf)

int             getflags;
struct pvcattf  *ind_msg;
char            ctlbuf[CBUFSIZ], datbuf[DBUFSIZ];

ctlblk.maxlen = CBUFSIZ;
ctlblk.buf    = ctlbuf;
datblk.maxlen = DBUFSIZ;
datblk.buf    = datbuf;

for (;;)
{
    if (getmsg(x25_fd, &ctlblk, &datblk, &getflags) < 0)
    {
        perror("Getmsg fail");
        exit(1);
    }
    ind_msg = (struct pvcattf *) ctlbuf;
    if (ind_msg->xl_type != XL_CTL)
        continue;
    switch (ind_msg->xl_command)
    {
        case N_PVC_ATTACH:
            switch (ind_msg->result_code)
            {
                case PVC_SUCCESS:
                    /* ...... Process the attach */
                    return(1);
                case PVC_NOSUCHSUBNET:
                case PVC_CFGERROR:
                case PVC_PARERROR:
                case PVC_BUSY:
                    /*  ...... Process the reject */
                    return(0);
                default:
                    printf("Unknown PVC message\n");
                    exit(1);
            }
    }
}

In this example, getmsg is used to retrieve the next message from the stream head. This is done in a loop, until the attach is either confirmed successful or rejected. Although the processing of the attach is not shown here, it is recommended that the application send a Reset Request (see the section “Reset Request/Indication” in Chapter 4) and wait for the Reset Confirmation (see the section “Reset Response/Confirmation” in Chapter 4) before proceeding with the data transfer. The example given in the section “Resets” in this chapter shows the code used to send a Reset Request and handle the acknowledgment. This synchronizes the X.25 PLP drivers at each end of the PVC. The example does not illustrate all possible result_code cases.

It is possible to abort the Attach Request before a response is received. The application can do this by sending a PVC Detach message (see the section “Detaching a PVC” below). If this is done, the application should read and discard all messages from the stream until it receives the detach acknowledgment.

After a rejection or an attach abort the stream remains open and can be used, for example, to make further attach attempts.

PVC Data Transfer

The transfer of data over a permanent virtual circuit is exactly the same, to the application, as for virtual circuits. See the section “Data Transfer” in this chapter for a description of the procedures involved.

Detaching a PVC

The procedure used to detach a PVC differs for the remote and local cases, so these are described separately here.

Remote Detach

If, during a connection, the remote end initiates a detach, then a Reset Indication (see the section “Reset Request/Indication” in Chapter 4) message is received at NLI. The application should acknowledge this with a Reset Response (see the section “Reset Response/Confirmation” in Chapter 4).

Handling such an event would normally be part of the general processing of incoming messages.

After sending the Reset Response, the application is still attached to its PVC and remains so until it initiates a local detach.

Local Detach

To initiate a detach on a connection, the application should send a PVC Detach message on the stream. The X.25 driver signals that it has observed the message by sending a PVC Detach upstream. In this way, the upper component can be certain that no messages follow the PVC Detach.

For example:

struct pvcdetf detach = { XL_CTL, N_PVC_DETACH, 0 };

ctlblk.len = sizeof(struct pvcdetf);
ctlblk.buf = (char *) &detach;

if (putmsg(x25_fd, &ctlblk, 0, 0) < 0)
{
    perror("Detach putmsg");
    exit(1);
}

As is the case for a remote detach, the stream becomes idle once the response has been received. It enters an open state, in which it remains until the application commands otherwise. This could be to close the stream, or to initiate a new PVC Attach on it. The application should, however, wait until it receives the PVC Detach.