Developer Links
SUPPORT FORUMS  
SAMPLE CODE & TIDS  
TEST/DEVELOPMENT KIT INFO
 
DEVELOPER LABS  
NOVELL SUPERLAB  
SUPPORT DOCUMENTS  
STRATEGIC/EXECUTIVE ISSUE TRACKING  
SYSOP PROGRAM  
     
Related Links
 
 
 
 
 
Related Links  
 

 
DEVELOPER SUPPORT HOME  

NOVEMBER 1994 VOLUME 6 NUMBER 6


INDEX


FROM THE COCKPIT

Our lead article this month by Adam Jerome explores the use of the TLI transport interface. By using this technology, you can greatly simplify the development and debugging process of client/server software.

Novell is leading the industry in applying the client/server technology to solving big company challenges. This enterprise- wide computing is a critical component of extending the ability of all computing-power users to communicate and share with one another. As our partnership with AT&T grows, and more and more corporations, schools and smaller organizations, even individuals, begin to use the AT&T NetWare Connect Services, it is clear that a solid, reliable and flexible naming system must be in place across this global network. We are very excited about our Novell Directory Services technology and its ability to meet this need.

Though you may not see your applications operating in such a global network at present, rest assured that your customers will attempt to do just that. Now is the time to develop a strategy and implementation plan for NDS for your product.

As Novell prepares for the release of NetWare 4 version 4.1, our excitement builds about the possibilities of the NDS technology. We know that many of our current NetWare 3 customers are planning to migrate to this version of NetWare 4 very shortly after its release. You need to consider the best way to design your products that will operate in this environment.

Take advantage of our latest education courses listed in the Developer Education section on page 13 in this issue, which can guide you in the integration of NDS into your product. See you in class!

On another note, you're undoubtedly aware of the upturn in our use of the 1-800-RED-WORD telephone number. We have improved the ways that you can reach us. 1-800-RED-WORD is becoming the contact point for all of your development needs. Through this single telephone number you can learn about our latest SDK products, order them, register for our Developer Programs, and receive support and other information about Novell developer benefits. Please feel free to share your thoughts and ideas on how to make this even better via E-mail at devprog@novell.com.

Our Extended Networking Division has relocated from Monterey, California to our Provo, Utah headquarters. Our support team in Provo will now be handling the Embedded Systems Technology issues in addition to providing support to those of you developing ALMs.

I'd like to thank you for participating with us in the world-changing activity of network computing. With you, your products and technologies, and a solid partnership, we will satisfactorily provide for the needs of the network computing user well into the future.

Jared Blaser
Director of Developer Support


ARTICLES

Transport Level Interface__Part I

I wrote my first IPX communication program many years ago. It took me a while to get the hang of ECBs, ESRs, AES, IPX headers, immediate addresses, sockets and so on. Then as soon as I became somewhat fluent in IPX, I was required to write a session type application using SPX. Later I had to learn a bit about TCP/IP. Of course, when SPXII came out I was somehow expected to be an expert in that too! Now they expect me to learn how to talk to a Mac using the ADSP protocol? !!!STACK OVERFLOW!!!

Perhaps you need an NLM that can communicate to NetWare clients using IPX/SPX/SPXII, TCP/IP to communicate to a UnixWare workstation, and ADSP to communicate with a Macintosh all at the same time. That would be nice, but it would require an expert programmer in each of these areas to make sure everything is in spec. Let's get real here! Unless you are a professional protocol junkie, you probably don't have the resources to understand all network protocols__or at least to a depth where you feel safe using them.

Well, there is another way. Transport Level Interface (TLI) is used to isolate an application from specific network protocols. With TLI taking care of network communications, the application can focus on its actual objective.

TLI Origins

TLI is not a NetWare native; it came from the AT&T/UNIX environment. UNIX gurus designed TLI as the transport layer (layer four) of the OSI seven-layer cake, modeled after the industry-standard ISO Transport Definition (ISO 8072). TLI aspires to make network access as easy as file access. TLI functions use familiar STREAMS I/O mechanisms and implement functions similar to fopen(), fread(), fwrite(), fclose() and so on.

Most network protocols offer two levels of communication: Datagram and Session (see figure below). TLI provides a generic method to access both Datagram and Session network protocols.

Datagrams
Datagrams are generally used for small amounts of one-time data. Datagram communication assumes that the application has a method to make sure that data gets to the receiver. These methods can be complex, simple or nonexistent, depending on the needs of the application. Datagram communication protocols include IPX, DDP and UDP.
Sessions
Sessions are generally used to send larger amounts of data over an extent of time. Session communication informs the application when data gets lost or cannot be delivered. Session communication protocols include SPX/SPXII, ADSP and TCP.

Novell's implementation of TLI currently supports several protocols, depending on the OS platform. On DOS, Windows and OS/2 platforms, developers can select from IPX, SPX/SPXII and TCP. The NetWare NLM platform sports two additional AppleTalk protocols: DDP and ADSP.

Most network protocols use some method to resolve addresses. In other words, if an application wants to send some data to Brent, it must know Brent's network address. Novell's IPX/SPX protocols use the Service Advertisement Protocol (SAP) to do name-to-address resolution, since AppleTalk uses NBP. TLI does not offer address resolution. The application must know enough about the protocol running under TLI to figure out the address of the guy on the other side.

TLI Communications Model

If a tree falls in the forest and nobody is there to hear it, does it make a sound? Similarly, to have network communication there must be a sender, a transport medium and a receiver. With TLI, the sender and receiver are called endpoints. An application may have multiple endpoints open at the same time. The transport medium is called the underlying protocol.

To successfully initiate communication there must be a listener and a caller. The listener is sometimes referred to as a server application. Generally, the caller is referred to as the client application. After the client and server have established a communication connection, the titles of client and server are meaningless to TLI and the two are treated as equal peers.

The remainder of this article will walk through a sample TLI server application implemented as a multithreaded NLM using SPX as the underlying transport protocol. The December issue of Bullets will feature a walk-through of a sample TLI client application implemented as a DOS executable. The source of both these sample applications is available on Compuserve in the NDEVSUP forum, library 5, in a self-extracting file called XTLI1.EXE.

The TLI Server

The server application is called FILETX.C. In brief, it contains two parts: a core process and a child process. The core process performs the following:
  • Opens a TLI endpoint to listen for new connections.
  • Advertises itself on the network as a service provider.
  • Goes into a loop listening for connection requests, establishing a connection with the requesting client and handing off the connection to a new thread to service the connection.
  • Unloads smoothly and properly frees resources. The child process does the following:
  • Receives a TLI handle to an active connection with a client.
  • Opens a file and sends its contents to the client via the TLI connection.
  • Sends a disconnect request to the client when the file has been sent, to indicate that it is done sending data.
  • Waits for the client to send a disconnect message, then closes down the TLI connection and terminates the child thread.
Let's examine the elements of FILETX's initialization process . First, we need to know the name of the file that will be sent to the client. We will require that the user who loads FILETX.NLM must supply a file name as the second parameter. For example, the user who loads this NLM might specify SYS:SYSTEM\ AUTOEXEC.NCF as the file that will be sent to the client. A quick check is made to make sure that the specified file exists (see Figure 1).

Figure 1: The elements of FILETX's initialization process

664  sendFile = argv[2];
665  fp=fopen(sendFile,"r");
666  if(fp != NULL) fclose(fp);
667  else
668       {
669       printf("ERROR: Cannot open file: %s\n", sendFile);
670       exitingNLM=TRUE;
671       Uninit();
672       }

t_open()

Next, open a TLI endpoint that will listen for connection requests. It is important to realize that this endpoint will not be used to communicate data to a client; rather, its sole purpose is to listen for new incoming connection requests. t_open() requires three parameters: path, oflag and info (shown below).
687  fh=t_open(
688       /* I- path  */ "/dev/nspx",
689       /* I- oflag */ O_RDRW|O_NDELAY,
690       /* -O info  */ &tInfo
691       );
The path parameter is the name of a TLI compliant protocol device driver; it is not actually a path or file name at all. This application will use the SPX/SPXII protocol. Therefore it will specify the driver "/dev/nspx". Since SPXS.NLM supplies the "/dev/nspx" device driver, the error TBADNAME will be indicated if SPXS.NLM is not loaded prior to loading our application.

Now, how can I claim to be using SPXII when I didn't specify the device driver "/dev/nspx2"? Well, this is how it works: if SPXII is available on both endpoints (client and server), then SPXII will be used even if "/dev/nspx" was specified by t_open(). Likewise, if one or both endpoints do not support SPXII, the connection is downgraded to SPX even if "/dev/nspx2" was specified. So it doesn't matter. Either specify "/dev/nspx" or "/dev/nspx2".

There is a method for your application to find out which protocol (SPX or SPXII) is actually in use. t_getinfo() returns a tInfo structure which contains a field called servertype that will be set to T_COTS if SPX is in use and set to T_COTS_ORD if SPXII is in use.

The second t_open() parameter is labeled oflag, used to specify settings for flags O_NDELAY and O_RDRW. In the sample code, two flags have been OR-ed together. The O_RDRW flag specifies the access mode. O_NDELAY is discussed in the t_listen section of this article.

The third t_open() parameter is the address of a structure in which t_open() places protocol-specific information about the endpoint.

t_alloc()

Next, the application needs to create and initialize some TLI structures, but TLI structures are different for each protocol supported. We could figure out how big these structures are and then malloc() them and fill in default values, but that would be the hard way. The easiest way is to use t_alloc(), which performs all these functions. Using t_alloc also ensures that the program stays compatible with future changes to TLI libraries and underlying protocols (i.e., SPXII and a half?).

t_alloc() requires three parameters. The first parameter is the handle returned from t_open(), used to access the characteristics of the transport provider. The second parameter is the type of TLI structure to be allocated. The third parameter is used to indicate which substructures are needed. When the entire substructure should be allocated, use the parameter T_ALL. Otherwise, specific sub-substructure flags may be specified and OR-ed together. Like the malloc() function, t_alloc() returns a pointer to the block of allocated memory (see figure below).

711  tBind = (struct t_bind *)t_alloc(
712       /* I- fd          */ fh,
713       /* I- struct_type */ T_BIND,
714       /* I- fields      */ T_ALL
715       );

730  tCall = (struct t_call *)talloc(
731       /* I- fd          */ fh,
732       /* I- struct_type */ T_CALL,
733       /* I- fields      */ T_ADDR,
734       );

t_bind()

Our transport endpoint handle obtained from t_open() is of little use unless clients can get its attention. We must assign (or bind) an (IPX/SPX style) address to this endpoint.

Non-TLI applications generally call IPXOpenSocket() to obtain a dynamic socket assignment. Although an application may use the same method under TLI, it is generally easier to have TLI call IPXOpenSocket() automatically.

IPX/SPX programmers also realize another issue: the process that is listening for new connections had better be prepared to service more than one request at a time. Non-TLI applications of the IPX/SPX persuasion usually do this by registering multiple receive ECBs (also known as Packet Receive Buffers).

TLI offers a generic way to both assign dynamic IPX socket numbers and specify the number of ECBs that will be associ-ated with this client's connection request socket. All this takes place using the t_bind() function call, which takes three parameters: fd, req and rep.

756  tBind->qlen = 1;
757  tBind->addr.len = 0
758  bound=t_bind(
759       /* I- fd  */ fh,
760       /* I- req */ tBind,
761       /* -O rep */ tBind
762       );

(See figure above). The fd parameter is the TLI handle obtained from t_open().

The req parameter is a t_bind structure which contains an addr field. The addr field defines the address (IPX/SPX address in this case) that the server application will use to listen for new connection requests. The structure also contains a qlen field which specifies (in this case) the number of ECBs or packet receive buffers to open on this address to listen for new connections. Assigning qlen the value of one means that only one connection request may be serviced at a time.

Note: setting qlen to zero means that the endpoint may be used for communication to a known client but may not be used to listen for new connections. Setting the addr.len to zero tells TLI that the address should be dynamically allocated by the underlying protocol (IPX/SPX). This is similar to opening socket zero using IPXOpenSocket().

The rep parameter is also a t_bind structure which contains the actual address that was bound to the endpoint, actual qlen (ECBs in IPX/SPX), and so on. This information is needed for the next step when we will advertise the availability of our service on the network.

AdvertiseService()

Now we have to get our server application's network address advertised out on the network. IPX/SPX uses a protocol called Service Advertising Protocol (SAP) to do this. Basically, our server must broadcast a Service Advertising Packet (also SAP) once a minute which gives our server's name, type and address. The AdvertiseService() function is used to perform this service which requires the server's name, type and socket address as input (see Figure 2). AdvertiseService() returns a SAP handle that will be used to discontinue advertising when our server application is shut down. Figure 2: Advertising the server application's network address
784  sapHandle=AdvertiseService(
785       /* I- serverType    */ SERVER_TYPE,
786       /* I- serverName    */ ARGV[1],
787       /* I- serviceSocket */ *(WORD *)(((IPX_ADDR
*)tBind->addr.buf)->ipxa_socket)
788       );
Our server's name will be defined by the user from the LOAD command line. .

The server's type in the sample code is defined as 0x0275. This value was issued by Novell for use in Novell Developer Support's sample code. If you build such a client/server application for production use, you must obtain your own Server SAP Type by calling Novell Developer Support's Database Administrator at 1-801-429-3101.

The server's address in the IPX/SPX environment is made up of a four-byte Network segment address, a six-byte Node address and a two-byte Socket address [Network:Node:Socket]. AdvertiseService() only requires the Socket address. It obtains the Network and Node addresses from internal sources. The Socket address for our application was allocated dynamically and was stored as output from the t_bind() function in the t_bind structure.

t_listen()

Now let's examine the first half of FILETX's core process loop (see Figure 3). Figure 3: FILETX's core process loop
411  while(!exitingNLM)
412       {
...
416       ThreadSwitch();
...
441       cCode=t_listen(
442            /* I- fd   */ fh,
443            /* -O call */ tCall
444            );
445       if(cCode == (-1))
446            {
447            switch(t_errno)
448                 {
...
454                 case TNODATA:
...
456                      continue;
...
468                 default:
469                      t_error("ERROR");
470                      exitingNLM=TRUE;
471                      continue;This loop will continue until the NLM is
unloaded.
This loop listens for and dispatches incoming client connection requests to a child process. To check for these requests, the t_listen() function is used. t_listen() can be a blocking or nonblocking function, depending on the O_NDELAY parameter passed into t_open(). This flag causes t_listen() and other TLI functions to be nonblocking. Non-blocking means that t_listen() will not wait for an incoming connection request before it returns. Instead it will return immediately with an indicator of whether or not there is a pending connection request. If there is no outstanding request, control is passed back to the top of the loop by the `C' continue statement. Since t_listen() is in a nonblocking mode, the ThreadSwitch() function in the top of the loop is very important. It allows other processes on the file server to get some CPU time. The O_NDELAY flag also affects other TLI functions similarly.

t_accept()

The second half of the loop is only executed when a client connection request has been made. The idea is to hand off the client to a child process so that our loop can get back to listening for new requests. The loop will accept the new connection using a new TLI endpoint, then pass the handle of this new endpoint to the child client service process.

The child process has a separate stack from our main() process so we can't use a TLI handle that is allocated from the main() stack. The memory for the new TLI handle must be allocated so that the handle can be passed to the child. Then the new endpoint can be opened. (See figure 4 on page 5). Figure 4: The second half of the core process loop


486       newFh=(int *)malloc(sizeof(int));
...
498       *newFh=t_open(
499            /* I- path  */ "/dev/nspx",
500            /* I- oflag */ O_RDRW,
501            /* -O info  */ NULL
502            );
...
522       newBindFlag=t_bind(
523            /* I- fd  */ *newFh,
524            /* I- req */ NULL,
535            /* -O ret */ NULL
536            );
...
538       ...t_accept(fh, *newFh, tCall)...
...
550       cCode=t_look(*newFh);
551       if(cCode == T_DISCONNECT)
...
576       cCode=BeginThread(childProcess, NULL, NULL, newFh);
...
624       }
Notice that this time O_NDELAY is not specified as an option flag with t_open() (which will cause the TLI functions to block when called with this new TLI handle). Then the new TLI endpoint handle is bound to a dynamically assigned address (dynamic socket) and the client connection request is accepted using the new handle. It is wise at this point to check for a disconnect request from the client just in case it has changed its mind. BeginThread is called to initiate a child thread, passing through the new TLI endpoint handle.

t_snd()

The child process will send the previously specified file to the client using the TLI endpoint handle it was given. It opens the file, reads a buffer full and then calls t_snd(). The t_snd() function needs the address and size of the buffer and also sports a flags parameter. (See Figure 5). Figure 5: Sending the file to the client with the TLI endpoint handle
196  fp=fopen(sendFile, "r");
197  if(fp != NULL)
198       {
199       while((bytes=fread(buf, 1, sizeof(buf), fp)))
200            {
...
204            cCode=t_snd(
205                 /* I- fd     */ *newFh,
206                 /* I- buf    */ buf,
207                 /* I- nbytes */ bytes,
208                 /* I- flags  */ feof(fp) ? 0 : T_MORE
209                 );
Each time t_snd() is called, the specified data is queued up to be sent, but may not be sent immediately. TLI may buffer up data from several t_snd()s and send one large packet rather than many small packets. The T_MORE flag tells TLI that there is more data to follow and suggests that TLI should not send a half-filled packet. It is important to understand that the T_MORE flag does not force TLI to do anything; rather, it is used by TLI to aid in its decisions in the packet building process. The child process specifies the T_MORE flag on all t_snd()s, until the end of the sendFile is encountered.

Application Termination

Although it would seem that we are finished here, t_close() cannot be called yet. As mentioned before, data is queued up and TLI may still be sending packets to the client. If t_close() is called in this state, TLI communication buffers would be freed, and all pending data lost.

Instead, the child process sends a connection disconnect request to the client indicating that the server process will not be sending any more data. This information is sent as TLI control information right behind the pending data. (See Figure 6). Figure 6: Sending a disconnect request

299  cCode=t_snddis(
300       /* I- fd   */ *newFh,
301       /* I- call */ NULL
302       );
...
322  do   {
323       ThreadSwitch();
324       cCode=t_rcvdis(
325            /* I- fd     */ *newFh,
326            /* -O discon */ NULL,
327            );
...
346       } while(cCode == TNODIS);
...
352  cCode=t_unbind(*newFh);
...
370  cCode-t_close(*newFh);
...
382  if(newFh != NULL) free(newFh);
...
389  return;
390  }
When the client receives this information, it will know there is no more data and that the server is waiting for it to finish receiving data. The client then sends a connection disconnect request back to the server. The server listens for the client's disconnect request by calling t_rcvdis() repeatedly until the request arrives. At that point the child process may close the connection by unbinding the endpoint from the address (IPX/SPX dynamic socket close) and then by calling t_close().

The method used to terminate the TLI connection in FILETX is called an abortive release.

There is another method of connection termination called orderly release. Instead of using t_snddis() and t_recdis(), the orderly release method uses the functions t_sndrel() and t_rcvrel(). An orderly release guarantees that no data will be lost in transit when the connection is terminated. Not all protocols support orderly release; for example, SPXII supports orderly release, but SPX does not. The t_getinfo() function returns a structure called t_info. The t_info structure contains a field called servtype. If the underlying protocol supports orderly release, servtype will contain the value T_COTS_ORD.

For more information about TLI, refer to the following:

  1. Novell NetWare Using NetWare Services for NLMs; Chapters 41_55 and Appendices A, B and C.
  2. Novell NetWare Programmer's Guide for C; Chapter 25.
  3. Programmer's Guide: Networking Interfaces; UNIX System V Release 4 .2 Documentation; UNIX Press. To order, call 1-515-284-6761. Adam Jerome is an engineer in Novell's Developer Support Group. He has been with Novell for four years. He describes himself as a "hick hacker" from Ferron, Utah. He likes to drive his dump truck to work.

    Static vs. Dynamic IPX Sockets

    IPX Socket Definition

    Network communication protocols use various addressing methods. The IPX/SPX protocols use a three part address: (1) a four-byte Network segment address, (2) a six-byte Node address, and (3) a two-byte Socket address.

    The network segment address applies to physical as well as logical network segments. An Ethernet segment is a physical segment. A NetWare 3.x or 4.x file server has a logical internal network segment. No two segments on internetwork may have the same address.

    The node address may be a hard-coded address burned into a LAN adapter, such as with Ethernet; on the adapter, such as with ArcNet; or set with software, such as the IBM Token Ring adapter's Locally Administered Address. The node address must be unique within the same network segment. Two node addresses may be the same if they are located on separate network segments.

    The socket address may be a static address that is issued by Novell Developer Support, or a dynamic address issued to the application at run-time by IPX. Several IPX applications may run on the same node. IPX uses the socket address to determine which application on the destination node will receive a packet. Only one application can open a specific socket address on a node at a time. If a second application requests to open a socket address already opened by another application on the same node, the open socket request will fail.

    Static Sockets

    When I lived in the Fiji Islands, I kept a post office box in various cities that I lived in to receive mail. Each city's post office has an exterior wall of post office boxes. My assigned post office box number in Lautoka was 275. When I moved to Tavua I was assigned box number 354. Post office boxes are very similar to IPX socket addresses.

    Static socket numbers are like owning a specific P.O. box number in every post office in the world. Although this would be neat, it is not usually necessary. In fact, the only time a static socket number is needed is for IPX LAN segment diagnostics applications where SAP and RIP protocols may not exist.

    Programmers are tempted to use static socket addresses for various reasons. Many times programmers erroneously assume that if they have a static socket, they can hard-code it into their application in order to make network communications easier. This is not true. In fact, static socket addresses are bad news for applications that will be loaded more than once on a network node.

    Another consideration is that broadcast packets are limited to the local network segment. This gives some programmers a real wake-up call when their application is ported from a simple one segment development network to a multisegment production customer environment. "Hey! Why doesn't your application work across an IPX router?" Developers then have to think fast and implement a method to broadcast their packet to multiple IPX segments individually.

    Because of the limitations of static sockets, Novell Developer Support is responsible for assigning static sockets to developers when necessary. A more viable option than static sockets is dynamic sockets. There is no additional overhead in using a dynamic socket as opposed to a static socket for advertised services.

    Dynamic Sockets

    When someone wants to use a P.O. box in Fiji, the local postmaster allocates one for his or her use. At that point it is up to P.O. box users to let people know their postal address. Dynamic sockets are allocated in a similar way. When a process wants to use a dynamic socket it requests one from IPX with a call to OpenSocket() with a value of 0x0000 in the socket number field. If there is a dynamic socket address available on the node, OpenSocket() returns the assigned address (a number between 0x4000 and 0x7FFF). Similar to a post office, the address returned may be different in any given machine depending on what sockets are available on that node. After a socket is allocated, it is up to the application service provider to let the network community know where they are. This is done by telling them the network, node and socket that they are listening on.

    NetWare provides the Service Advertising Protocol (SAP) and NetWare Directory Services (NDS) to advertise services. SAP provides an easy way to advertise a service and is similar to a junk-mail method of advertisement. Novell also offers NDS as a method of advertisement, which scales better than SAP and can provide more information about a service than just its location.

    Conclusion

    Novell Developer Support encourages developers to limit the use of static socket addresses. As an alternative, use dynamic sockets for communication points in conjunction with SAP and/or NDS.

    Mark Oberg is an engineer in Novell's Developer Support Group. He was previously with Novell National Accounts support group. He likes to ski and windsurf.

    Server Statistical APIs in NetWare 4.x

    Figure 7: Making a call to SSGetLANCustomCounters
    /************************************************************/** Description :
    This sample code  shows how to make a call to
    ** the API SSGetLANCustomCounters and obtain the information
    ** about the custom statistics for a LAN Board.
    ** Syntax :
    ** LONG   SSGetLANCustomCounters(
         LONG boardNumber,
         LONG startNumber,
         BYTE *buffer,
         LONG bufferLen);
    ** The structures GetCustomCountersInfoStructure and
    ** CustomCountersInfo that are returned in buffer are defined
    ** in NWSERVST.h
    ** The input and output parameters are explained in the
    ** Documentation "NLM Library Reference Volume II"
    /***********************************************************/
    
    /* INCLUDE HEADERS */
    /* ANSI */
    #include 
    
    /* OTHER */
    #include 
    
    /* NetWare Specific */
    #include 
    
    /* defines */
    #define BOARD_NUMBER 1
    
    /* FUNCTION PROTOTYPE */
    int SSGetLANCustomCounters_T();
    
    /*=========================================================*/
    
    /* MAIN */
    void main(void)
    {
         clrscr();
         SSGetLANCustomCounters_T();
    }
    
    /*===========================================================
    ** FUNCTION:   SSGetLANCustomCounters_T
    ** PURPOSE:    Returns Custom Statistics for a LAN Board
    *==========================================================*/
    int SSGetLANCustomCounters_T()
    {
         LONG boardNumber;
         LONG startNumber;
         BYTE buffer[SS_DEFAULT_BUFFER_SIZE];
         GetCustomCountersInfoStructure     *customCounters;
         CustomCountersInfo  *CInfo;
         BYTE *bPtr;
         WORD bufferLen;
         int  x,z,offset;
         LONG rtnVal;
    
         /* BEGIN CODE */
    
         boardNumber = BOARD_NUMBER;
         startNumber = 0;
         bufferLen = SS_DEFAULT_BUFFER_SIZE;
    
         rtnVal = SSGetLANCustomCounters(
                   boardNumber,
                   startNumber,
                   buffer,
                   bufferLen);
         if (!rtnVal)
         {
              customCounters = (GetCustomCountersInfoStructure *)
                             &buffer[0];
              printf("Board Number = %d\n", boardNumber);
              printf("Start Number = %d\n", startNumber);
              printf("Buffer Length = %d\n", bufferLen);
    
              printf("******* CUSTOM COUNTERS INFO *********\n");
              printf("CurrentServerTime = %d\n", customCounters->
                        currentServerTime);
              printf("VConsoleVersion = %X\n", customCounters->
                        vConsoleVersion);
              printf("VconsoleRevision = %X\n", customCounters->
                        vConsoleRevision);
              printf("More Flag = %d\n", customCounters->moreFlag);
              printf("Number Of Custom Counters = %d\n",
              customCounters->numberOfCustomCounters);
              printf("Start Of Custom Counters = %d\n",
                   customCounters->startOfCustomCounters);
              printf("***************************************\n");
    
              z = z;
              for (x = 0; offset = 0; x < customCounters->
                   numberOfCustomCounters; x++)
              {
                   /* point to the offset of the customCountersInfo
                        struct */
                   bPtr = (BYTE *) &customCounters->
                        startOfCustomCounters;
    
                   /* add all strings in the struct so far,
                   (variable length */
                   bPtr += offset;
    
                   /* now offset the correct number of
                   customCountersInfo structs */
                   CInfo = (CustomCountersINfo *) bPtr;
                   CInfo += x;
                   bPtr = &CInfo->stringStart;
    
                   printf("\nValue = %d\n", CInfo->value);
                   printf("String Length = %d\n",
                   CInfo->stringLength);         `
    
                   printf("StringStart = ");
                   for (z = 0; z < CInfo->stringLength; z++)
                   {
                        printf("%c", *bPtr++);
                   }
    
                   /* sub 1, since stringStart BYTE will be added at
                   `cPtr += x' */
                   offset += CInfo->stringLength - 1;
              } /* for */
         } /* if */
    
         printf("\nSSGetLANCustomCounters returned = %d\n", rtnVal);
         printf("===========================================\n");
    
         return 0;
    }
    
    These Server Statistical APIs can be used in a wide range of applications that provide the following:
    • Performance Analysis
    • Configuration of the File System
    • Detection of Hardware Failure
    • Detection of CPU Overload
    and many more features. The following list is a quick reference to "SSGet" APIs, documented in NLM Library Reference Volume II, pages 1264 to 1346.
    SSGetActiveConnListByType
    SSGetActiveLANBoardList
    SSGetActiveProtocolStacks
    SSGetCacheInfo
    SSGetCPUInfo
    SSGetDirCacheInfo
    SSGetFileServerInfo
    SSGetFileSystemInfo
    SSGetGarbageCollectionInfo
    SSGetIPXSPXInfo
    SSGetKnownNetworksInfo
    SSGetKnownServersInfo
    SSGetLANCommonCounters
    SSGetLANConfiguration
    SSGetLANCustomCounters
    SSGetLoadedMediaNumberList
    SSGetLSLInfo
    SSGetLSLLogicalBoardStats
    SSGetMediaManagerObjChildList
    SSGetMediaManagerObjInfo
    SSGetMediaManagerObjList
    SSGetMediaNameByNumber
    SSGetNetRouterInfo
    SSGetNetworksRoutersInfo
    SSGetNLMInfo
    SSGetNLMLoadedList
    SSGetNLMResourceTagList
    SSGetOSVersionInfo
    SSGetPacketBurstInfo
    SSGetProtocolConfiguration
    SSGetProtocolCustomInfo
    SSGetProtocolNumbersByLANBoard
    SSGetProtocolNumbersByMedia
    SSGetProtocolStatistics
    SSGetRouterAndSAPInfo
    SSGetServerInfo
    SSGetServerSourcesInfo
    SSGetUsersInfo
    SSGetVolumeSegmentList
    SSGetVolumeSwitchInfo
    
    Latha Rao is an engineer in Novell's Developer Support Group. She has worked for Novell for three-plus years. She came over from Novell Engineering. She likes to play tennis and run.

    DEVELOPER NEWS

    Searching for Beta Sites

    Novell Engineering will soon launch the NT Client Beta program. We would like interested developers to contact us if they want to be considered for this program. Following is a list of developer and Novell expectations for the NT SDK Beta program:

    We expect you to do the following:

    1. Join the PDP by calling 1-800-RED-WORD or returning the questionnaire printed in the September issue of Bullets which serves as your PDP registration.
    2. Install all the NT Client Beta files and begin testing within 48 hours of receiving the kit.
    3. Thoroughly read all README files before testing.
    4. Dedicate at least 10 hours per week working directly with the product.
    5. Submit biweekly electronic status reports, including any bugs you find.
    6. Submit suggestions for product improvement through NetWire, E-mail or Internet.
    7. Provide a short summary report at the end of the beta cycle. (The report should include overall impression of the product, outstanding issues and plans for future implementation).

    Novell will provide the following:

    1. Beta product and at least one update.
    2. Free NetWire support.
    3. Biweekly electronic reports to fill out.
    4. Private NetWire forum to discuss the product with Novell and other beta sites.
    5. NetWire status updates on the problems you report and the suggestions you make.
    6. A free copy of the released product if you meet the above expectations.
    If you can fulfill all of these activities, please download the form in NDEVSUP Library 1 entitled NTBETA and return it to the address specified in that form. Each approved beta site that provides us with feedback on each of the defined activities will receive the final released kit at no charge.

    Obviously, we are looking for beta sites that will work with us and provide us the feedback to make this Beta program a success. If you cannot fulfill all of the defined activities, then please do not respond. The Beta program size is limited. Not everyone who submits their information for consideration into the Beta program will be selected to participate.

    QueryServices() Timeout Value Under NetWare Client SDK 1.0e

    How long will QueryServices() wait before it times out and returns an error status?

    The QueryServices() function returns with a status 01. This status indicates that no server of the type passed into the function responded within the hard-coded timeout interval.

    The QueryServices() function uses the following means to determine how long it will wait for a response from the nearest server query:

    QueryServices() sends out a GetLocalTarget request (RIP broadcast) to the local network and uses the transport time value from the response to this broadcast as a variable for determining the QueryServices() timeout value. A problem with this approach occurs when the broadcast is destined for the local network.

    IPX satisfies the request internally and passes back a transport time of one tick (1/18 sec). The QueryServices() function multiplies this value by five for a total transport time of 5/18 of a second. If a router does not respond within this interval of time the QueryServices() function will time out and return a status 1 (v1.0d of the Client SDK) or a status 0 (v1.0e of the Client SDK). At the present time, there is no way to increase this timeout interval.

    Currently, the only way to circumvent this problem is to use IPX functions to build and transmit a custom SAP query packet. This will allow the use of a variable timeout value. The structure of the Service Query Packet is as follows:

    Offset    Content   Type Order
    0    IPX Header     BYTE[30]
    30   Query Type     WORD hi-lo
    32   Server Type    WORD hi-lo
    
    The Query type is set to one for General Service Query or three for Nearest Service Query. The custom Service Query packet is an IPX packet built according to this outline and sent in to the local network with the node address of 0xFFFFFFFFFFFF on socket 0x452. Post at least one IPX packet (IPXListenForPacket) to retrieve the first response that comes back.

    API Structures and Byte Alignment Under NetWare Client SDK 1.0e

    For proper results to be returned in the elements of a structure, single byte alignment may be required depending on the variable sizes in a structure. The first byte of a character array contains corrupted or unexpected data.

    The Microsoft compilers by default align structures on two-byte boundaries. This can result in the last byte of one element of a structure being moved to the first byte of a following structure. Borland compilers align structures on one-byte boundaries by default so this issue is not a problem with these compilers.

    Include the following Microsoft compiler directives above and below the structure in question to force single-byte alignment. An example is shown in Figure 8.

    This will align the bytes on one-byte boundaries and allow the proper data to appear in each element of the structure. Borland compilers pack structures on one-byte boundaries by default. The Borland IDE provides a way to change between WORD and BYTE alignment under the compiler options menu.

    NWServiceQueueJob2() Returns 89D5 Under NetWare Client SDK 1.0e

    NWServiceQueueJob2() returns 89D5 (ERR_NO_Q_JOB) with the Client SDK 1.0e libraries when there are jobs to be serviced.

    This is a problem with the Client SDK version 1.0e libraries. The current solution is to use the previous Client SDK libraries, version 1.0d. This problem will be corrected in future releases of the Client SDK.

    NWGetPreferredConnName() Parameters Under NetWare Client SDK 1.0e

    The parameters for the NWGetPreferredConnName() function are incorrectly documented.

    The NWGetPreferredConnName() function should not have a NWCONN_HANDLE as an input parameter. There are just two output parameters for NWGetPreferredConnName(), preferredName and preferredType.

    GetMachineStaticInfo() Returns 0xFE Under NetWare Client SDK 1.0e

    The diagnostic GNMA (Generic Network Management Responder) function GetMachineStaticInfo() returns error code 0xFE. The GNMA responder does not exist on the node to which the GetMachineStaticInfo() function is sent.

    Two checks must be made to ensure that the GNMA responder is present on the node to which the request is being made. First, the GNMA component must exist in the component list returned from the node that will be the partner for the diagnostic session. This is component nine. Second, if the GNMA component exists a call to GetGNMAInfo() must be made to determine the number of GNMA responders available on the node. A structure of type GNMAInfoStruct is passed as a parameter to GetGNMAInfo. If any responders exist then GNMAInfoStruct.GNMANumberOfResponders will be greater than zero. The responder for the call GetMachineStaticInfo() is the NMR.VLM (Network Management Responder) virtual loadable module provided with the Client DOS Requester software. If this module is loaded then GNMAInfoStruct.Type[0].ResponderType will equal one. The NMR.VLM is not loaded by default. Add the following line under the "NetWare DOS Requester" header: VLM=NMR.VLM. The function GetMachine-DynamicInfo() also relies on the NMR.VLM being loaded.

    How to Calculate the Available Bytes on a Volume from your NLM

    1. Call GetVolumeStatistics (File System Service)
      GetVolumeStatistics(
      fileServerID, /* the file Server ID */
      volumeNumber, /* the volume number you want info for */
      structSize, /* size of VOLUME_INFO */
      &returned VolumeStatistics);
      
    2. Available Number of Bytes on the Volume =
      (returnedVolumeStatistics.availableBlocks +
      returnedVolumeStatistics.purgableBlocks *
      returnedVolumeStatistics.sectorsPerBlock * 512.
      
    Refer to NLM Library Reference Volume I on page 565 for more information on this API.

    Reading Fields from LOGIN_CONTROL Property Under NetWare Client SDK 1.0e

    Following is a description of how to read information from the LOGIN_CONTROL property of a user object.

    The LOGIN_CONTROL structure is an 86 byte structure that contains account and password information. Figure 9 on page 11 is the structure of the LOGIN_CONTROL property and a sample program that demonstrates how to read information from this structure. Figure 9: The structure of the LOGIN_CONTROL property

    typedef struct
    {
         BYTE accountExpiresYear;
         BYTE accountExpiresMonth;
         BYTE accountExpiresDay;
         BYTE accountExpired;
         BYTE passwordExpiresYear;
         BYTE passwordExpiresMonth;
         BYTE passwordExpiresDay;
         BYTE passwordGraceLogins;
         WORD expirationInterval;
         BYTE graceReset;
         BYTE minimumPasswordLength;
         WORD maxConcurrentConnections;
         BYTE timeBitMap[42];
         BYTE lastLoginDate[6];
         BYTE restrictionFlags;
         BYTE filler;
         LONG maxDiskBlocks;
         WORD badLoginCount;
         LONG nextResetTime;
         BYTE badStationAddress[12];
    } LOGIN_CONTROL;
    
    /*****************************************************
    **File:  SCANLC.C
    **
    **Desc:  Program designed to read any value from the LOGIN_CONTROL
    **      Structure.
    **
    **   Usage: SCANLC   
    **   Where: object name -- bindery object name
    **          offset--offset in the LOGIN_CONTROL property
    **                    of the value you wish to read.
    **          length--number of bytes to read from the
    **                    specified offset.
    **
    **
    
    
    /*****************************************************
    **   Include headers, macros, function prototypes, etc.
    */
    
         /*-------------------------------------------------
         **   Macros
         */
         #define NWDOS
    
    
         /*-------------------------------------------------
         **   ANSI
         */
         #include 
         #include 
         #include 
    
         /*-------------------------------------------------
         **   NetWare
         */
         #include 
    
    /*****************************************************
    **   This function contains the entire program
    */
    
    int main(int argc, char *argv[])
    {
    NWCONN_HANDLE       connHandle;
    char           objectName[47+1];
    BYTE           propValue[128];
    NWCCODE             cCode;
    BYTE           desiredValue[128];
    int            i;
    int            offSet;
    int            length;
    int            count = 1;
    
    if (argc < 2)
    {
    printf("\nUsage: SCANLC   \n");
    exit(1);
    }
    
    offSet = atoi(argv[2]);
    length = atoi(argv[3]);
    strupr(argv[1]);
    strcpy(objectName, argv[1]);
    
    cCode = NWCallsInit(NULL,NULL);
    
    if(cCode)
    {
    printf("NWCallsInit returned: %04X\n",cCode);
    exit(1);
    }
    
    cCode = NWGetDefaultConnectionID(&connHandle);
    
    if(cCode)
    {
    printf("Call to NWGetDefaultConnectionID failed!! Code %04X\n", cCode);
    exit(1);
    }
    
         cCode = NWReadPropertyValue(
                        /* > Conn Handle  */ connHandle,
                        /* > object name  */ objectName,
                        /* > object type  */ OT_USER,
                        /* > property Name*/ "LOGIN_CONTROL",
                        /* > data set indx*/ 1,
                        /* < Data Buffer  */ propValue,
                        /* < More flag    */ NULL,
                        /* < Property Flgs*/ NULL
                        );
    
         if (cCode)
              {
              printf("\nNWReadPropertyValue returned: %04X", cCode);
              exit(1);
              }
    
         for (i=offSet; i< (offSet+length); ++i)
              {
              desiredValue[count] = (BYTE)propValue[i];
              count = count + 1;
              }
    
         /* Operate on the Desired Value as needed from this point */
    
         printf("\nHEX dump  :", desiredValue);
    
         for (i=1; i<=length; ++i)
              printf("%02X", desiredValue[i]);
    
    }
    
    

    CURRENT PATCHES FOR NOVELL DEVELOPMENT TOOLS

    Novell's NetWire Forum

    The latest NetWare drivers, example code for NetWare API development tools and OS/2 requester patches are available on Novell's NetWire forum on CompuServe. New information is first stored in NOVLIB library 1 and moved to library 7 after a period of 30 days.

    If you do not have access to CompuServe, request these files from Novell's Developer Support Group at 1-801-429-5588. When you call, be ready to provide a shipping address, disk preference (5.25", 3.5", HD or DD) and, if you prefer overnight delivery, your company's Federal Express account number. If you do not provide a Federal Express account number, the patch disk will be sent to you via regular mail.

    Novell Professional Developer's Program members can obtain updated NetWare API SDK components from Novell's NDEVSUP forum on CompuServe. For more information on Novell developer programs contact Novell at 1-801-429-5588.

    Novell Developer Relations' Automated Fax System

    A document describing available patches and other files and their location on CompuServe is available through Novell Developer Relations' Automated Fax System. This system can provide you other useful information as well.

    To use the Automated Fax System, call 1-800-RED-WORD (1-800-733-9673) from a touch-tone phone. Then choose the option for the Automated Fax System, select the documents you wish to receive and supply your fax number (the fax number to which you want your document(s) sent). Document #1 lists all other documents available through the Automated Fax System. Up to five documents can be requested per call.

    Note: previous issues made reference to document #7805 in this article. That document is in the process of being rewritten, but was unavailable previously.


    TELETIPS

    TeleTip #7
    To Share or Not to Share

    Well, we're in a whole new world now with our telephones. With NetWare Telephony Services, there are two points of view to the "world" as we "bit-heads" know it.

    First, there are the added services that are now available to our computers. To a computer user, we have another add-on. Yep, another peripheral and cable on the desk. I mean, why brag about adding control of an analog telephone? Isn't it a lot more fun to say "I have a P90 with 32 meg of RAM, 1.2 gig disk, digital sound card with midi interface, 2 meg PCI video graphics accelerator, .28 dpi non-interlaced monitor, 14.4 modem, 1 meg local bus caching scsi controller . . ."? You at least get a good, blank "stare of respect" from people when you tell them that. When you tell them you just added a telephone, they say "gee, that's nice."

    The other point of view is that of the telephone or switch box. From this perspective, we have just given control of the telephone or PBX to an entire network of users, each sitting at their own workstations, running their own software. They may not even have to touch the phone unless they want to use the handset!

    From this second point of view, a programmer can build an application specifically designed to take advantage of both the telephone network as well as the data network. This allows an application to leverage the strengths of both__communication and sharing information.

    When someone was on a call before and didn't know how to transfer the call to your extension, did you ever want to do it for them? Were you ever hung up on as someone tried to put you on hold and switch to another phone? Why can't you just do it from your phone?

    The Telephony Server keeps track of calls that are going on by way of a CallID. This CallID is allocated by the Telephony Server each time someone makes a call, or when a monitored device receives a call. As long as that call is active on some device, the CallID will be valid.

    An application can track calls being made from a device by checking the event structures that are returned to it. In the structures will appear the CallIDs that have been allocated by the Telephony Server during such events as making or conferencing calls. When another user (or even the same user) wants to control that call from another station, the application could use a communications protocol (such as TLI, IPX or SPX) to talk between the workstations and pass the CallID between them. As long as a valid CallID is being used, and the workstation controlling the call makes the valid CallID known, any application could use that CallID to control the call.

    This sharing of information between applications allows for more flexible phone control__control that was not easily available before. Taking advantage of the strengths of both the data and phone networks, application capabilities can reach a new high! Tip CallID's are considered to be global on the TServer. That is, they are not specific to any application, DLL or client machine. As long as a CallID is valid on one machine, it is valid from all workstations.

    TeleTip #8
    Can't We All Just Get Along?

    Wouldn't it be nice? Would I be considered living in a dream world to expect to have everyone just get along__to have no disagreements? It's beyond my control, right? Out of my hands? Well, while I may not be able to solve major world conflict, I can at least solve a disagreement between two header files associated with the Telephony Services SDK.

    The CSTADEFS.H file has the following three lines in it:

    typedef enum SDBLevel_t {
        NO_SDB_CHECKING = 1,
        ACS_ONLY = 2,
        ACS_AND_CSTA_CHECKING = 3
    } SDBLevel_t;
    
    When writing a PBX driver NLM to load on the TServer, a file called TDI.H is used, which has the following in it:
      #define TDI_CSTA_SECURITY    0
      #define TDI_LOGIN_SECURITY   1
      #define TDI_NO_SECURITY     -1
    
    Well, there is a subtle disagreement here. When a PBX driver requests one type of security, it may come across to the client Telephony Workstation as a different value. This discrepancy will be fixed in future versions of the TSAPI SDK (the PBX driver interface will remain as it is), but for now, you will need to make the changes yourself.

    The CSTADEFS.H file will need to be modified so that the current SDBLevel_t typedef (shown above) is changed to the following:

    typedef enum SDBLevel_t{
        NO_SDB_CHECKING = -1
        ACS_ONLY = 1
        ACS_AND_CSTA_CHECK = 0
    }SDBLevel_t;
    
    If you prefer not to modify the CSTADEFS.H file yourself, you may download the updated file from the Novell Developer Support BBS. This file is in the Telephony section of the BBS. The BBS number is 1-801-429-5836.

    What POWER!!! Look at the conflict you just resolved! Well, we may not live in a dream world, but we take what we can get, right? Tip There is a change that needs to be made to the CSTADEFS.H file in the TSAPI SDK. You can download an updated file from the Novell Developer Support BBS, or you can make the changes yourself. Both of these methods are described above.


    DEVELOPER EDUCATION

    Available Novell Developer Education Courses

    Novell Developer Education offers several courses for developers who use Novell's development tools.
    930  Developing NetWare Loadable Modules (NLMs)
    940  NetWare Programming: Basic Services
    941  Directory Services
    945  NetWare Programming: Protocol Support
    950  Visual AppBuilder
    954  ALM Development
    

    Novell Authorized Education Centers

    The following Novell Authorized Education Centers (NAECs) offer developer training:
    Alternative Computer
    with sites in Mt. Laurel, NJ; Albany, NY; Pittsburgh, Allentown and Bluebell, PA offers courses 930, 940 and 945. Contact Jamie Anewalt at 1-800-321-1154.
    C-Trec
    in Houston, TX offers courses 930, 940, 941 and 945, and plans to teach 950 and 954. Contact Conni Templin at 1-713-871-8411.
    Clarity
    located in San Jose, CA offers courses 930, 940 and 945, and plans to teach 941, 950 and 954. Contact Kirstin Hills or Cyd Simerville at 1-800-729-9995.
    Professional Computer Development Corp.
    with sites in Norcross, GA; Shaumburg and Mt. Prospect, IL; Livonia, MI; Edina, MN; Cincinnati and Maumee, OH; and Pittsburgh, PA offers course 930. Contact Larry Guyette at 1-800-322-7232.
    To obtain information on pricing, location, scheduling and course content for classes held in the U.S. call 1-800-233-3382 or 1-801-429-5508. For information on classes in all other locations, contact your local Novell office.

    NOVELL PROFESSIONAL DEVELOPER'S PROGRAM

    How to Become One of Our Developers

    Developing computing systems is a complex, time-consuming and expensive task. Novell understands this and is committed to its developer partners. The Novell Professional Developer's Program allows you, our developer, to access our broad base of 40 million NetWare users to develop and market your products.

    This program is designed for a wide range of developers who design and create commercially available solutions. This includes commercial developers, called Independent Software Vendors (ISVs), and vertical application developers who create applications for specialized markets such as manufacturing, medicine and law.

    Key Features

    Members of the Novell Professional Developer's Program receive the following benefits:
    Co-Marketing Support
    Members can participate in the Yes Program, which offers a variety of co-marketing opportunities as well as ongoing information about Novell's direction. The ultimate goal of the Yes Program is to make it easy for customers to identify and purchase products that are optimized for Novell platforms.
    Technical Support
    To obtain product information and technical support, phone 1-800-RED-WORD (1-800-733-9673) or 1-801-429-5281.
    Skills Transfer
    Members may participate in the Skills Transfer Workshops conducted by a Novell specialist. The workshops offer cutting-edge training on solution design, development and implementation. Members may also attend Novell Compass Special Interest Groups, where they have the chance to review products and provide feedback directly to Novell development teams.
    Project/Opportunity Services
    Members have access to Novell expertise through the developer forums on CompuServe. These forums address developer issues including technical information and support for Novell products and development tools.
    Information Services
    As a developer, you receive Novell Developer Notes, a publication targeted at network software developers. Other publications are also available: Bullets, a monthly technical journal; AppNotes (subscription only), a technical journal for network design; and the annual Developer Guide to Programs, Products, and Services. Members also get access to Novell's NDEVSUP CompuServe forum_an electronic meeting place where developers can exchange information with each other and with Novell staff members.
    No Program Fees
    Membership in this program is free of charge and open to all developers.
    If you would like more information about the PDP or would like to apply for membership, please phone 1-800-RED-WORD (1-800-733-9673) or 1-801-429-5281. If you would like to order Novell development tools, please call 1-800-RED-WORD. You may also contact the program administrator for general questions by E-mail at the following address: devprog@novell.com. You may also submit SDK enhancement requests by E-mail at the following address: devsdk@novell.com.

    CONTACTING NOVELL

    Developer Support

    Developers in the U.S. and Canada may contact Novell Developer Support via telephone, fax, BBS or electronic mail via CompuServe, MHS or the Internet.

    Voice

    For both presales and postsales support, call Novell Developer Support between 8:00 a.m. and 5:00 p.m. Mountain Time at 1-801-429-5588. Many calls to Novell Developer Support are passed to a software support engineer immediately. Calls to Novell Developer Support generally will be acknowledged or answered within four hours.

    Fax

    If you prefer, you may contact Novell Developer Support via fax at 1-801-429-2990. Faxed questions are acknowledged or answered within 24 hours.

    Developer BBS

    If you do not have access to either CompuServe or the Internet, send test cases to our BBS. Set your communication software to N-8-1 and call 1-801-429-5836.

    E-mail: CompuServe

    Post your messages in the appropriate section addressed to Novell at 76701,171. These messages will receive a response within 24 hours.

    E-mail: MHS

    You may direct your questions and comments to Novell Developer Support via Novell's Message Handling Service E-mail facility. Address your messages to devsup@novell. These messages will receive a response within 24 hours.

    E-mail: Internet (SMTP)

    An alternative method of contacting Novell Developer Support is through the Internet E-mail system. Send your messages to devsup@novell.com. These messages will receive a response within 24 hours.

    NetWire on CompuServe

    Novell's NetWire forum is open to all registered CompuServe subscribers. Through the NDEVSUP forum, professional developers writing applications with Novell development tools can gain access to information specific to Novell development. Support, patches, periodicals and product information, as well as information on all of the programs and services provided for Novell developers are accessible through this forum. Post your messages in the appropriate section addressed to Novell at 76701,171. Technical questions will be acknowledged or answered by Novell Developer Support within 24 hours.

    Novell Products and SDKs

    Novell products on the "Currently Shipping Developer Products" list (see page 15 of this issue), including the Red Box Products, are available to Novell Professional Developer's Program members. Call 1-800-RED-WORD (1-800-733-9673) or 1-303-894-4135 to order these products or to receive additional information. To order other Red Box Products not listed, contact your local Novell office or Novell Authorized Reseller.

    DeveloperNet Labs

    For information on DeveloperNet Labs development tools, education classes and product certification, in the U.S. and Canada call 1-800-453-1267 ext. 5544 or 1-801-429-5544, or call your local Novell office (see back cover of this issue).

    Novell Developer Relations

    Novell Professional Developers or those wishing to become members may contact Novell Developer Relations via telephone, fax, or electronic mail via Compuserve, MHS or the Internet.

    Voice

    For general information or questions on Novell Developer Relations programs, in the U.S. or Canada call 1-800-RED-WORD (1-800-733-9673). All others call 1-801-429-5281.

    Fax

    If you prefer, you may contact Novell Developer Relations via fax at 1-801-429-7207.

    NetWire on CompuServe

    Novell's NetWire forum is open to all registered Compuserve subscribers. Through the NDEVREL forum, Novell Professional Developer's Program-related issues or general questions may be posted. Through the NDEVINFO forum, customers who do not have products may post pre-sales product information questions on all Novell SDKs.

    E-mail: MHS or Internet (SMTP)

    You may direct your questions and comments to Novell Developer Relations via Novell's Message Handling Service E-mail facility. Address your messages to devprog@novell.com. Use the same address when going through the Internet E-mail system.