The COMSTACK subsystem provides a transparent interface to different types of transport stacks for the exchange of BER-encoded data. At present, the RFC1729 method (BER over TCP/IP), and Peter Furniss' XTImOSI stack are supported, but others may be added in time. The philosophy of the module is to provide a simple interface by hiding unused options and facilities of the underlying libraries. This is always done at the risk of losing generality, and it may prove that the interface will need extension later on.
The interface is implemented in such a fashion that only the sub-layers constructed to the transport methods that you wish to use in your application are linked in.
You will note that even though simplicity was a goal in the design, the interface is still orders of magnitudes more complex than the transport systems found in many other packages. One reason is that the interface needs to support the somewhat different requirements of the different lower-layer communications stacks; another important reason is that the interface seeks to provide a more or less industrial-strength approach to asynchronous event-handling. When no function is allowed to block, things get more complex - particularly on the server side. We urge you to have a look at the demonstration client and server provided with the package. They are meant to be easily readable and instructive, while still being at least moderately useful.
COMSTACK cs_create(CS_TYPE type, int blocking, int protocol);
Creates an instance of the protocol stack - a communications endpoint.
The
type
parameter determines the mode of communication. At present, the
values
tcpip_type
and
mosi_type
are recognized. The function returns a null-pointer if a system error
occurs. The blocking
parameter should be one if you wish the
association to operate in blocking mode, zero otherwise. The
protocol
field should be one of PROTO_SR
or PROTO_Z3950
.
int cs_close(COMSTACK handle);
Closes the connection (as elegantly as the lower layers will permit),
and releases the resouces pointed to by the
handle
parameter. The
handle
should not be referenced again after this call.
NOTE: We really need a soft disconnect, don't we?
int cs_put(COMSTACK handle, char *buf, int len);
Sends
buf
down the wire. In blocking mode, this function will return only when a
full buffer has been written, or an error has occurred. In nonblocking
mode, it's possible that the function will be unable to send the full
buffer at once, which will be indicated by a return value of 1. The
function will keep track of the number of octets already written; you
should call it repeatedly with the same values of buf
and
len
, until the buffer has been transmitted. When a full buffer
has been sent, the function will return 0 for success. -1 indicates
an error condition (see below).
int cs_get(COMSTACK handle, char **buf, int *size);
Receives a PDU from the peer. Returns the number of bytes
read. In nonblocking mode, it is possible that not all of the packet can be
read at once. In this case, the function returns 1. To simplify the
interface, the function is
responsible for managing the size of the buffer. It will be reallocated
if necessary to contain large packages, and will sometimes be moved
around internally by the subsystem when partial packages are read. Before
calling
cs_get
for the fist time, the buffer can be initialized to the null pointer,
and the length should also be set to 0 - cs_get will perform a
malloc
(2)
on the buffer for you. When a full buffer has been read, the size of
the package is returned (which will always be greater than 1). -1
indicates an error condition.
See also the
cs_more()
function below.
int cs_more(COMSTACK handle);
The cs_more()
function should be used in conjunction with
cs_get
and
select
(2).
The cs_get()
function will sometimes (notably in the TCP/IP mode) read
more than a single protocol package off the network. When this
happens, the extra package is stored by the subsystem. After calling
cs_get()
, and before waiting for more input,
You should always call
cs_more()
to check if there's a full protocol package already read. If
cs_more()
returns 1,
cs_get()
can be used to immediately fetch the new package. For the
mOSI
subsystem, the function should always return 0, but if you want your
stuff to be protocol independent, you should use it.
NOTE: The cs_more()
function is required because the
RFC1729-method
does not provide a way of separating individual PDUs, short of
partially decoding the BER. Some other implementations will carefully
nibble at the packet by calling
read
(2)
several times. This was felt to be too inefficient (or at least
clumsy) - hence the call for this extra function.
int cs_look(COMSTACK handle);
This function is useful when you're operating in nonblocking
mode. Call it when
select
(2)
tells you there's something happening on the line. It returns one of
the following values:
No event is pending. The data found on the line was not a complete package.
A response to your connect request has been received. Call
cs_rcvconnect
to process the event and to finalize the connection establishment.
The other side has closed the connection (or maybe
sent a disconnect
request - but do we care? Maybe later). Call
cs_close
To close your end of the association as well.
A connect request has been received. Call
cs_listen
to process the event.
There's data to be found on the line. Call
cs_get
to get it.
NOTE:
You should be aware that even if
cs_look()
tells you that there's an event event pending, the corresponding
function may still return and tell you there was nothing to be found.
This means that only part of a package was available for reading. The
same event will show up again, when more data has arrived.
int cs_fileno(COMSTACK h);
Returns the file descriptor of the association. Use this when
file-level operations on the endpoint are required (select
(2)
operations, specifically).
int cs_connect(COMSTACK handle, void *address);
Initiate a connection with the target at address
(more on
addresses below). The function will return 0 on success, and 1 if
the operation does not complete immediately (this will only
happen on a nonblocking endpoint). In this case, use
cs_rcvconnect
to complete the operation, when select
(2)
reports input pending on the association.
int cs_rcvconnect(COMSTACK handle);
Complete a connect operation initiated by cs_connect()
. It will
return 0 on success; 1 if the operation has not yet completed (in this
case, call the function again later); -1 if an error has occured.
To establish a server under the inetd
server, you can use
COMSTACK cs_createbysocket(int socket, CS_TYPE type, int blocking,
int protocol);
The socket parameter is an established socket (when your
application is invoked from inetd
, the socket will typically be
0. The following parameters are identical to the ones for
cs_create
.
int cs_bind(COMSTACK handle, void *address, int mode)
Binds a local address to the endpoint. Read about addresses below. The
mode
parameter should be either CS_CLIENT
or CS_SERVER
.
int cs_listen(COMSTACK handle, char *addr, int *addrlen);
Call this to process incoming events on an endpoint that has been bound in listening mode. It will return 0 to indicate that the connect request has been received, 1 to signal a partial reception, and -1 to indicate an error condition.
COMSTACK cs_accept(COMSTACK handle);
This finalises the server-side association establishment, after
cs_listen has completed successfully. It returns a new connection
endpoint, which represents the new association. The application will
typically wish to fork off a process to handle the association at this
point, and continue listen for new connections on the old handle
.
You can use the call
char *cs_addrstr(COMSTACK);
on an established connection to retrieve the hostname of the remote host.
NOTE: You may need to use this function with some care if your name server service is slow or unreliable
The low-level format of the addresses are different depending on the mode of communication you have chosen. A function is provided by each of the lower layers to map a user-friendly string-form address to the binary form required by the lower layers.
struct sockaddr_in *tcpip_strtoaddr(char *str);
struct netbuf *mosi_strtoaddr(char *str);
The format for TCP/IP addresses is straightforward:
<host> [ ':' <portnum> ]
The hostname
can be either a domain name or an IP address. The
port number, if omitted, defaults to 210.
For OSI, the format is
[ <t-selector> '/' ] <host> [ ':' <port> ]
The transport selector is given as an even number of hex digits.
You'll note that the address format for the OSI mode are just a subset of full presentation addresses. We use presentation addresses because xtimosi doesn't, in itself, allow access to the X.500 Directory service. We use a limited form, because we haven't yet come across an implementation that used more of the elements of a full p-address. It is a fairly simple matter to add the rest of the elements to the address format as needed, however: Xtimosi does support the full P-address structure.
In both transport modes, the special hostname "@" is mapped to any local address (the manifest constant INADDR_ANY). It is used to establish local listening endpoints in the server role.
When a connection has been established, you can use
char cs_addrstr(COMSTACK h);
to retrieve the host name of the peer system. The function returns a pointer to a static area, which is overwritten on the next call to the function.
NOTE: We have left the issue of X.500 name-to-address mapping open, for the moment. It would be a simple matter to provide a table-based mapping, if desired. Alternately, we could use the X.500 client-function that is provided with the ISODE (although this would defeat some of the purpose of using ThinOSI in the first place. We have been told that it should be within the realm of the possible to implement a lightweight implementation of the necessary X.500 client capabilities on top of ThinOSI. This would be the ideal solution, we feel. On the other hand, it still remains to be seen just what role the Directory will play in a world populated by ThinOSI and other pragmatic solutions.
All functions return -1 if an error occurs. Typically, the functions
will return 0 on success, but the data exchange functions
(cs_get
, cs_put
, cs_more
)
follow special rules. Consult their descriptions.
When a function (including the data exchange functions) reports an
error condition,
use the function
cs_errno()
to determine the cause of the
problem. The function
void cs_perror(COMSTACK handle char *message);
works like
perror
(2)
and prints the
message
argument, along with a system message, to
stderr
.
Use the character array
extern const char *cs_errlist[];
to get hold of the message, if you want to process it differently. The function
const char *cs_stackerr(COMSTACK handle);
Returns an error message from the lower layer, if one has been provided.
Although you will have to download Peter Furniss' XTI/mOSI implementation for yourself, we've tried to make the integration as simple as possible.
The latest version of xtimosi will generally be under
ftp://pluto.ulcc.ac.uk/ulcc/thinosi/xtimosi/
When you have downloaded and unpacked the archive, it will (we assume)
have created a directory called xtimosi
. We suggest that you place
this directory in the same directory where you unpacked the
YAZ
distribution. This way, you shouldn't have to fiddle with the
makefiles of YAZ
beyond uncommenting a few lines.
Go to xtimosi/src
, and type "make libmosi.a
".
This should generally
create the library, ready to use.
CAVEAT
The currently available release of xtimosi has some inherent problems that make it disfunction on certain platforms - eg. the Digital OSF/1 workstations. It is supposedly primarily a compiler problem, and we hope to see a release that is generally portable. While we can't guarantee that it can be brought to work on your platform, we'll be happy to talk to you about problems that you might see, and relay information to the author of the software. There are some signs that the gcc compiler is more likely to produce a fully functional library, but this hasn't been verified (we think that the problem is limited to the use of hexadecimal escape-codes used in strings, which are silently ignored by some compilers).
A problem has been encountered in the communication with
ISODE-based applications. If the ISODE presentation-user calls
PReadRequest()
with a timeout value different from OK
or NOTOK
,
he will get an immediate TIMEOUT abort when receiving large (>2041
bytes, which is the SPDU-size that the ISODE likes to work with) packages
from an xtimosi-based implementation (probably most
other implementations as well, in fact). It seems to be a flaw in the
ISODE API, and the workaround (for ISODE users) is to either not
use an explicit timeout (switching to either blocking or
nonblocking mode), or to check that the timer really has expired
before closing the connection.
The next step in the installation is to modify the makefile in the toplevel YAZ directory. The place to change is in the top of the file, and is clearly marked with a comment.
Now run make
in the YAZ toplevel directory (do a
"make clean
" first, if the
system has been previously made without OSI support). Use the YAZ
ztest
and client demo programs to verify that OSI communication works OK. Then,
you can go ahead and try to talk to other implementations.
NOTE: Our interoperability experience is limited to version 7 of the Nordic SR-Nett package, which has had several protocol errors fixed from the earlier releases. If you have problems or successes in interoperating with other implementations, we'd be glad to hear about it, or to help you make things work, as our resources allow.
If you write your own applications based on YAZ, and you wish to
include OSI support, the procedure is equally simple. You should
include the xmosi.h
header file in addition to comstack.h
.
xmosi.h
will define the manifest constant mosi_type
, which you should pass
to the cs_create()
function. In
addition, you should use the function mosi_strtoaddr()
rather than
tcpip_strtoaddr()
when you need to prepare an address.
When you link your application, you should include (after the
libyaz.a
library) the libmosi.a
library, and the librfc.a
library
provided with
YAZ (for OSI transport).
As always, it can be very useful, if not essential, to have a look at the example applications to see how things are done.
Xtimosi requires an implementation of the OSI transport service under
the X/OPEN XTI API. We provide an implementation of the RFC1006
encapsulation of OSI/TP0 in TCP/IP (through the Berkeley Sockets API),
as an independent part of YAZ (it's found under the rfc1006
directory). If you have access to an OSI transport provider under XTI,
you should be able to make that work too, although it may require
tinkering with the mosi_strtoaddr()
function.
To simplify the implementation, we use Peter Furniss' alternative (PRF) option format for the Control of the presentation negotiation phase. This format is enabled by default when you compile xtimosi.
The current version of YAZ does not support presentation-layer negotiation of response record formats. The primary reason is that we have had access to no other SR or Z39.50 implementations over OSI that used this method. Secondarily, we believe that the EXPLAIN facility is a superior mechanism for relaying target capabilities in this respect. This is not to say that we have no intentions of supporting presentation context negotiation - we have just hitherto given it a lower priority than other aspects of the protocol.
One thing is certain: The addition of this capability to YAZ should
have only a minimal impact on existing applications, and on the
interface to the software in general. Most likely, we will add an extra
layer of interface to the processing of EXPLAIN records, which will
convert back and forth between oident
records (see section
Object Identifiers) and direct or indirect
references, given the current association setup. Implementations based
on any of the higher-level interfaces will most likely not have to be
changed at all.
#include <comstack.h>
#include <tcpip.h> /* this is for TCP/IP support */
#include <xmosi.h> /* and this is for mOSI support */
COMSTACK cs_create(CS_TYPE type, int blocking, int protocol);
COMSTACK cs_createbysocket(int s, CS_TYPE type, int blocking,
int protocol);
int cs_bind(COMSTACK handle, int mode);
int cs_connect(COMSTACK handle, void *address);
int cs_rcvconnect(COMSTACK handle);
int cs_listen(COMSTACK handle);
COMSTACK cs_accept(COMSTACK handle);
int cs_put(COMSTACK handle, char *buf, int len);
int cs_get(COMSTACK handle, char **buf, int *size);
int cs_more(COMSTACK handle);
int cs_close(COMSTACK handle);
int cs_look(COMSTACK handle);
struct sockaddr_in *tcpip_strtoaddr(char *str);
struct netbuf *mosi_strtoaddr(char *str);
extern int cs_errno;
void cs_perror(COMSTACK handle char *message);
const char *cs_stackerr(COMSTACK handle);
extern const char *cs_errlist[];