/* Version: V1.0 */ /* Name: cx.c */ /* Location: sys$examples: */ /**************************************************************************/ /* */ /* XTI OSI Transport Class 4 Client Example Ported from OSF/1 */ /* please note the VMS specific comments. */ /* */ /* This example client connects to server program sx. The user is then */ /* prompted for commands (printed when the program is started) and these */ /* are processed. The usage for cx is "cx [target [condata]]" where */ /* target is the name of the host where the server is running (default is */ /* "localhost") and condata is the optional user data to be sent in the */ /* connect request (default is no data). */ /* */ /* The mapping of target node names to nsaps is done in a (/etc/nsaps). */ /* Each line in this file consists of a name/nsap pair. For example: */ /* localhost/41454187150041080021 */ /* To obtain the nsap of the local node use the "nodename -n" (oSF) */ /* command. The nsaps corresponding to the use of OSI Transport end */ /* in "21". */ /* */ /* All data (normal,expedited,connect,disconnect) is assumed to be */ /* printable ascii characters. For sending normal & expedited data, two */ /* characters have special meaning. '/' splits the ascii string into */ /* sections to be sent with multiple calls to t_snd. '!' causes normal */ /* data to be sent as expedited data and vice-versa. Thus, for example, */ /* the command "N123/456/789" will result in 3 calls to t_snd, the first */ /* sending "123", the second "456", and the third "789". The command */ /* "N123/!abc/456" also results in 3 calls to t_snd. The first and third */ /* are sent as normal data, and the second ("abc") is sent as expedited */ /* data. */ /* */ /* Notes: All output is done to stderr (because t_error outputs to stderr */ /* and we don't want to have to worry about interleaving output between */ /* stdin and stderr). No checking is done for success on malloc calls. */ /* No attempt is made to free malloced memory that is no longer needed. */ /* */ /* Note: */ /* */ /* For VMS the nsap file is called nsap.dat and should be located in your */ /* current directory. Since addressing and options are specific to the */ /* provider, the file vms_osi.h is used to map the specific addressing */ /* from OSF to VMS. This example set was ported from OSF/1 to VMS. */ /* */ /* Under VMS the "F" options, create a fresh endpoint will case the */ /* program to exit. Under OSF there would be a fork. */ /* */ /* OSF provides helper routines for XTI OSI addressing, similar routines */ /* are found in the VMS xti example helper library xtiutil.c. These */ /* routines are just examples and not intended to demonstrate proper */ /* porting methods. */ /* */ /**************************************************************************/ #include #include #include #ifdef vms #include "vms_osi.h" #else #include #include #include #endif /**************************************************************************/ /* Figure out where to go for connection oriented service. */ /**************************************************************************/ #ifdef ultrix #define COTS "cots" #elif __osf__ #define COTS "/dev/streams/xtiso/cots" #endif /* *** PNM( PTY_OSI) macro defined in vms_osi.h to access OSI provider */ #ifdef vms #define COTS PNM( PTY_OSI ) #endif extern xti_osimakeaddr(); /**************************************************************************/ /* Local #defines */ /**************************************************************************/ #define SNDTSAP "sendtsap" #define RCVTSAP "recvtsap" /* *** For VMS the macro OSIADDRLEN is defined in vms_osi.h */ #ifndef vms #define OSIADDRLEN(a) ((a)->osi_length + sizeof(struct sockaddr_osi)) #endif #ifdef ultrix #define T_UNINIT 0 #endif /**************************************************************************/ /* Forward function declarations. */ /**************************************************************************/ struct sockaddr_osi *subx_alloc_sosi(); /**************************************************************************/ /* Main */ /**************************************************************************/ main (argc, argv) int argc; char *argv[]; { int cfd; int len; int two; int connected; char cmd; char buff[256]; char target[256]; char *status; char *data; struct t_info t_open_info; data = &buff[1]; /* Check for where to connect to the server */ if (argc > 1) { strcpy(target,argv[1]); } else { strcpy(target,"localhost"); } /* Check for any connect data to be sent */ if (argc > 2) { strcpy(data,argv[2]); } else { *data = '\0'; } fflush(stdout); fflush(stderr); cfd = cx_create_transport_endpoint(&t_open_info); *data = '\0'; connected = cx_connect_to_server(cfd,data,target,&t_open_info); cx_print_message(); while (1) { /* Get next command */ fprintf (stderr,"cx> "); status = fgets(buff,sizeof(buff),stdin); if (status == NULL) { fprintf (stderr,"\n\n"); break; } else { cmd = toupper(buff[0]); } /* Get rid of any trailing newline */ len = strlen(buff); if (buff[len-1] == '\n') buff[len-1] = '\0'; /* Execute the command */ switch (cmd) { case 'N': { connected = cx_send_data(cfd,data,0,&two); if (connected) connected = cx_receive_data(cfd); if (connected && two) connected = cx_receive_data(cfd); break; } case 'E': { connected = cx_send_data(cfd,data,1,&two); if (connected) connected = cx_receive_data(cfd); if (connected && two) connected = cx_receive_data(cfd); break; } case 'I': { cx_getinfo(cfd); break; } case 'S': { cx_getstate(cfd); break; } case 'D': { cx_send_disconnect(cfd,data); connected = 0; break; } case 'C': { connected = cx_connect_to_server(cfd,data,target,&t_open_info); break; } case 'F': { connected = 0; cx_destroy_transport_endpoint(cfd); cfd = cx_create_transport_endpoint(&t_open_info); fprintf (stderr,"\n"); break; } case 'T': { strcpy(target,data); fprintf (stderr,"\nTarget node set to %s\n\n",target); break; } case 'Y': { cx_tsync(cfd); break; } default: { fprintf (stderr,"\nUnrecognized command. Valid commands:\n"); cx_print_commands (); fprintf (stderr,"\n"); break; } } } *data = '\0'; if (connected) cx_send_disconnect(cfd,data); cx_destroy_transport_endpoint(cfd); } /**************************************************************************/ /* Get a transport endpoint. */ /* */ /* NOTE: Addressing is XTI implementation dependent. In this case */ /* our XTI address is represented by sockaddr_osi structure. */ /* Note that this structure is variable length, with TSAP */ /* and NSAP dynamically constructed at the end of the */ /* structure. (OSF only) */ /**************************************************************************/ cx_create_transport_endpoint (infop) struct t_info *infop; { int sfd; int status; struct t_bind req; struct t_bind ret; struct t_optmgmt t_optm_req; struct t_optmgmt t_optm_ret; struct sockaddr_osi *client_sap; struct isoco_options isoco_opts; fprintf (stderr,"\nCreating transport endpoint ... "); /* Create a transport endpoint. */ sfd = t_open( COTS, O_RDWR, infop); if (sfd < 0) { cx_error("t_open",NULL); exit(1); } /* Set up our (the client) sap */ client_sap = subx_alloc_sosi(infop->addr); (void)xti_osimakeaddr(client_sap, OSIPROTO_COTS, /* Connection oriented transport */ strlen(SNDTSAP), /* Our service access point */ (unsigned char *) SNDTSAP, /* ditto */ 0, /* We don't care about network */ NULL, /* ditto */ NULL); /* ditto */ /* Fill in what address we want to be bound to the transport endpoint */ req.addr.len = OSIADDRLEN(client_sap); req.addr.buf = (char *)client_sap; req.qlen = 0; /* client won't do t_listen */ /* Set up the structure where we will find out what address actually */ /* got bound to the transport endpoint. */ ret.addr.maxlen = infop->addr; ret.addr.buf = (char *)client_sap; /* Try to do the bind */ status = t_bind(sfd, &req, &ret); if (status < 0) { cx_error("t_bind",NULL); exit(1); } /* Set our options with the transport provider. Note that we had */ /* to do the t_bind first to get the transport endpoint into the */ /* T_IDLE state before we could do t_optmgmt call. */ /* Get the default options for this transport endpoint */ t_optm_req.opt.len = 0; t_optm_req.flags = T_DEFAULT; t_optm_ret.opt.maxlen = sizeof(struct isoco_options); t_optm_ret.opt.buf = (char *)(&isoco_opts); status = t_optmgmt(sfd, &t_optm_req, &t_optm_ret); if (status < 0) { cx_error("t_optmgmt","T_DEFAULT"); exit(1); } /* Now that we've got the default options, we change those options */ /* that we specifically care about to the values we want, then we */ /* feed the option structure back to the transport provider. */ #ifndef vms isoco_opts.mngmt.dflt = T_NO; /* Don't ignore following params */ isoco_opts.mngmt.class = T_CLASS4; /* Preferred class is class 4 */ isoco_opts.mngmt.checksum = T_YES; /* We want checksums used */ isoco_opts.mngmt.ltpdu = 2048; /* Max TPDU length */ isoco_opts.expd = T_YES; /* We want expedited data support */ #else /* Parameters are values to vms supported options */ /* 1) class 2) expedited data 3) checksum 4) extended 5) flow ctrl */ /* */ /* Options 4 (extended format) and 5 ( flow control) are read only */ /* */ vms_set_options( &isoco_opts, T_CLASS4, T_YES, T_YES, 0, 0 ); #endif t_optm_req.opt.len = sizeof(struct isoco_options); t_optm_req.opt.buf = (char *)(&isoco_opts); t_optm_req.flags = T_NEGOTIATE; /* Note that we will be using the same options buffer to get the */ /* returned options as we use to pass in the desired options. */ t_optm_ret.opt.maxlen = sizeof(struct isoco_options); t_optm_ret.opt.buf = (char *)(&isoco_opts); status = t_optmgmt(sfd, &t_optm_req, &t_optm_ret); if (status < 0) { cx_error("t_optmgmt","T_NEGOTIATE"); exit(1); } /* If we got here our transport endpoint is now all ready */ /* for the connect attempt! */ fprintf (stderr,"Done.\n"); return(sfd); } /**************************************************************************/ /* Create a connection to the server. */ /**************************************************************************/ cx_connect_to_server (cfd,ptr,target,infop) int cfd; char *ptr; char *target; struct t_info *infop; { int len; int look; int status; struct nsap nsap; struct t_call sndcall; struct t_call rcvcall; struct sockaddr_osi *server_sap; fprintf(stderr,"\nAttempting to connect to Server %s ... ",target); /* Set up the server's sap */ status = subx_getnsap(target, &nsap); if (status < 0) return(0); server_sap = subx_alloc_sosi(infop->addr); (void)xti_osimakeaddr(server_sap, OSIPROTO_COTS, /* Connection oriented transport */ strlen(RCVTSAP), /* Server's service access point */ (unsigned char *) RCVTSAP, /* ditto */ OSIPROTO_CLNS, /* Connectionless network service */ nsap.nsap_length, /* Server's network access point */ nsap.nsap_addr); /* ditto */ /* Fill in the sndcall structure (this specifies who we want */ /* to connect to). */ len = strlen(ptr); sndcall.addr.len = OSIADDRLEN(server_sap); sndcall.addr.buf = (char *)server_sap; sndcall.opt.len = 0; sndcall.opt.buf = NULL; sndcall.udata.len = len; sndcall.udata.buf = ptr; /* Fill in the rcvcall structure (upon succesfull completion */ /* of the t_connect, this will tell us who we actually */ /* connected to). Note that in the sndcall structure (above) */ /* we filled in the len fields (because we were supplying the */ /* data). With the rcvcall structure we fill in the maxlen */ /* fields. Thats because the buffers are empty and the */ /* t_connect call will fill them in. */ rcvcall.addr.maxlen = infop->addr; rcvcall.addr.buf = (char *)subx_alloc_sosi(infop->addr); rcvcall.opt.maxlen = sizeof(struct isoco_options); rcvcall.opt.buf = (char *)malloc(sizeof(struct isoco_options)); rcvcall.udata.maxlen = infop->connect; rcvcall.udata.buf = (char *)malloc(infop->connect); /* Now go and try the connect! */ status = t_connect(cfd, &sndcall, &rcvcall); if ((status < 0) && (t_errno == TLOOK)) { fprintf(stderr,"\n"); look = subx_tlook(cfd,"t_connect",T_DISCONNECT,T_ORDREL); fprintf(stderr,"\n"); /* Need to consume event */ if (look == T_DISCONNECT) t_rcvdis(cfd,NULL); else if (look == T_ORDREL) t_rcvrel(cfd); return(0); } else if (status < 0) { cx_error("t_connect",NULL); return(0); } /* If here we had a successfull connect */ fprintf(stderr,"Connected.\n\n"); /* Print out info returned in the rcvcall parameter. Note that */ /* for purposes of this example program we are assuming that any */ /* user data returned will be ascii. */ fprintf (stderr,"Length of server's address ......... = %d\n",rcvcall.addr.len); fprintf (stderr,"Length of protocol specific options = %d\n",rcvcall.opt.len); fprintf (stderr,"Length of returned user data ....... = %d\n",rcvcall.udata.len); if (rcvcall.udata.len > 0) { fprintf (stderr,"Connect data = \"%s\"\n", rcvcall.udata.buf); } fprintf (stderr,"\n"); return(1); } /**************************************************************************/ /* Transmit data (normal or expedited) */ /**************************************************************************/ cx_send_data (cfd, ptr, eflag, two_p) int cfd; char *ptr; int eflag; int *two_p; { int cc; int tmp; int look; int flag; int last; int more; int multi; int eflag2; int nbytes; int e_segs; int n_segs; char *tp; char *type; char *term; /* See how many segments of each type of data are to be sent */ e_segs = 0; n_segs = 1; for (tp=ptr; *tp; tp++) { if (*tp == '/') { if (*(tp+1) == '!') { e_segs++; } else { n_segs++; } } } if (eflag) { tmp = e_segs; e_segs = n_segs; n_segs = tmp; } /* Indicate to the caller whether he will have to do 1 t_rcv or 2 */ if (e_segs && n_segs) { *two_p = 1; } else { *two_p = 0; } /* Is this data is to be done with multiple sends? */ multi = ((n_segs > 0) || ( e_segs > 0)) ? 1 : 0; /* Send the data */ while (1) { /* See if this is the last segment */ term = (char *)strchr(ptr,'/'); if (term == NULL) { last = 1; } else { last = 0; *term = '\0'; } /* See if we should invert the data type for this segment */ if (*ptr == '!') { ptr++; eflag2 = !eflag; } else { eflag2 = eflag; } /* See if this is the last data segment of the appropriate type */ if (eflag2) { more = (e_segs > 1) ? 1 : 0; e_segs--; } else { more = (n_segs > 1) ? 1 : 0; n_segs--; } /* Set the appropriate bits in the flag word */ flag = 0; if (eflag2) flag |= T_EXPEDITED; if (more) flag |= T_MORE; type = (eflag2) ? ("expedited") : ("normal"); /* Get the number of bytes to send on this t_snd */ nbytes = strlen(ptr); fprintf (stderr,"\nAttempting to send %d bytes of %s data ... ",nbytes,type); cc = t_snd(cfd, ptr, nbytes, flag); if ((cc <= 0) && (t_errno == TLOOK)) { fprintf(stderr,"\n"); look = subx_tlook(cfd,"t_snd",T_DISCONNECT,T_ORDREL); fprintf(stderr,"\n"); /* Need to consume event */ if (look == T_DISCONNECT) t_rcvdis(cfd,NULL); else if (look == T_ORDREL) t_rcvrel(cfd); return(0); } else if (cc < 0) { cx_error("t_snd",type); return(0); } else if ((cc == nbytes) && !multi) { fprintf (stderr,"Done.\n"); return(1); } else { fprintf(stderr,"%d bytes sent.\n", cc); if (last) { return(1); } else { ptr = term+1; } } } /* while */ } /**************************************************************************/ /* Receieve echoed data from the server. Note that we do not handle the */ /* case where T_MORE is set. We assume that each call to t_rcv will */ /* return an entire TPDU. */ /**************************************************************************/ cx_receive_data (cfd) int cfd; { int cc; int look; int flags; char buff[256]; fprintf (stderr,"\nAttempting to receive data ... "); cc = t_rcv(cfd,buff,(sizeof(buff)-1),&flags); if ((cc <= 0) && (t_errno == TLOOK)) { fprintf(stderr,"\n"); look = subx_tlook(cfd,"t_rcv",T_DISCONNECT,T_ORDREL); fprintf(stderr,"\n"); /* Need to consume event */ if (look == T_DISCONNECT) t_rcvdis(cfd,NULL); else if (look == T_ORDREL) t_rcvrel(cfd); return(0); } else if (cc < 0) { cx_error("t_rcv",NULL); return(0); } else if (flags & T_EXPEDITED) { fprintf (stderr,"Received %d bytes of expedited data\n",cc); buff[cc] = 0; fprintf (stderr,"Data = \"%s\"\n\n",buff); return(1); } else { buff[cc] = 0; fprintf (stderr,"Received %d bytes of normal data\n",cc); fprintf (stderr,"Data = \"%s\"\n\n",buff); return(1); } } /**************************************************************************/ /* Do a t_getinfo call and print the results. */ /**************************************************************************/ cx_getinfo (cfd) int cfd; { int status; struct t_info info; status = t_getinfo(cfd,&info); if (status == -1) { cx_error("t_getinfo",NULL); } else { fprintf(stderr,"\n"); subx_print_t_getinfo_struct(stderr,&info); fprintf(stderr,"\n"); } } /**************************************************************************/ /* Do a t_sync call on the transport endpoint. */ /**************************************************************************/ cx_tsync (cfd) int cfd; { int status; status = t_sync(cfd); if (status == -1) { cx_error("t_sync",NULL); } else { fprintf(stderr,"\nt_sync complete\n\n"); } } /**************************************************************************/ /* Print the current state of the transport endpoint. */ /**************************************************************************/ cx_getstate (cfd) int cfd; { int status; char *sp; char buff[64]; status = t_getstate(cfd); if (status == -1) { cx_error("t_getstate",NULL); } else { switch (status) { case T_UNINIT: sp="T_UNINIT (uninitialized)"; break; case T_UNBND: sp="T_UNBND (unbound)"; break; case T_IDLE: sp="T_IDLE (idle)"; break; case T_OUTCON: sp="T_OUTCON (outgoing connect pending"; break; case T_INCON: sp="T_INCON (incoming connect pending"; break; case T_DATAXFER: sp="T_DATAXFER (data transfer)"; break; case T_OUTREL: sp="T_OUTREL (outgoing orderly release)"; break; case T_INREL: sp="T_INREL (incoming orderly release)"; break; default: sp=buff; sprintf(buff,"Unknown state %d",status); } fprintf (stderr,"\nTransport endpoint state = %s\n\n",sp); } } /**************************************************************************/ /* Disconnect the connection. */ /**************************************************************************/ cx_send_disconnect (fd,ptr) int fd; char *ptr; { int len; int status; struct t_call call; len = strlen(ptr); memset(&call, '\0', sizeof(call)); call.udata.len = len; call.udata.buf = ptr; fprintf (stderr,"\nSending disconnect with %d bytes of disconnect data ... ",len); status = t_snddis(fd, &call); if (status < 0) { cx_error("t_snddis",NULL); } else { fprintf (stderr,"Done\n\n"); } } /**************************************************************************/ /* Unbind and close the transport endpoint. */ /**************************************************************************/ cx_destroy_transport_endpoint (cfd) int cfd; { (void) t_unbind(cfd); (void) t_close(cfd); } /**************************************************************************/ /* Print explanatory message on using this sample client. */ /**************************************************************************/ cx_print_message () { fprintf(stderr,"Commands to cx consist of single character, possibly\n"); fprintf(stderr,"followed by data. Exit by typing ^D. Legal commands:\n"); fprintf(stderr,"\n"); cx_print_commands(); fprintf(stderr,"\n"); } /**************************************************************************/ /* Print list of valid commands. */ /**************************************************************************/ cx_print_commands () { fprintf (stderr," N[data] send normal data\n"); fprintf (stderr," E[data] send expedited data\n"); fprintf (stderr," D[data] disconnect\n"); fprintf (stderr," C[data] connect to target system\n"); fprintf (stderr," S print current state of transport endpoint\n"); fprintf (stderr," I print protocol specific service information\n"); fprintf (stderr," F create a fresh transport endpoint\n"); fprintf (stderr," Y issue a t_sync on the trasnport endpoint\n"); fprintf (stderr," T[name] sets target server name\n"); } /**************************************************************************/ /* Call t_error, with appropriate pre & post processing. */ /**************************************************************************/ cx_error (s1,s2) char *s1; char *s2; { char buff[128]; if (s2 == NULL) { sprintf(buff,"\nERROR: %s",s1); } else { sprintf(buff,"\nERROR: %s (%s)",s1,s2); } t_error(buff); fprintf(stderr,"\n"); }