Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Networking With Open Transport / Part 1 - Open Transport Essentials
Chapter 7 - Option Management


Using Options

This section explains how you use endpoint functions to set and retrieve option values and how you use Open Transport utility functions to construct an options buffer and parse through an options buffer.

Determining Which Function to Use to Negotiate Options

You can negotiate options using the OTOptionManagement function or using any one of the endpoint functions used to transfer data or establish a connection. The basic distinction between setting option values using the OTOptionManagement function and using any of the other endpoint functions is that options negotiated with the OTOptionManagement function affect all functions called by an endpoint, whereas options negotiated using any other function affect only the connection, transaction, or datagram for which they are set. For more detailed information about these differences, see "XTI Option Summary".

Obtaining the Maximum Size of an Options Buffer

Different types of endpoints support different numbers of options. For example, an ATP endpoint might support more options than a DDP endpoint and might need a larger buffer to hold the options. When you call the OTOptionManagement function to change option values, the function returns in the ret parameter a pointer to the buffer containing the negotiated option values. You must have allocated the buffer used to store these options before calling the function. Likewise, when you call the OTListen, OTRcvUData, OTRcvURequest, or OTRcvConnect functions, you can allocate a buffer in which current option values are to be placed when these functions return. In either case, you must specify the size of the buffer, and the buffer must be large enough to hold all of the endpoint's options. Otherwise, the function fails with a kOTBufferOverflow result. You can obtain the maximum size of a buffer used to store options for your endpoint by examining the options field of the TEndpointInfo structure for the endpoint. You can get a pointer to this structure when you open the endpoint, when you bind the endpoint, or when you call the OTGetEndpointInfo function.

Setting Option Values

You can use the OTOptionManagement, OTAccept, OTSndUData, OTSndURequest, and OTConnect functions to set option values. Setting option values results in a negotiation process between you (the client application) and the endpoint provider or, in the case of association-related options, between local and remote clients and their endpoint providers. Appendix D describes the rules that govern an option negotiation that you have initiated using the OTOptionManagement, OTConnect, OTSndUData, or OTSndURequest functions. The section "Retrieving Values for Connection-Oriented Endpoints" describes the negotiation rules that hold when you use the OTOptionManagement or OTAccept functions to respond to a negotiation. This section describes ways in which you can build the options buffer used to specify the options you want to change.

Specifying Option Values

No matter which function you use to set option values, you must allocate a buffer that contains the option value or values you want to change. The options in this buffer are described by TOption structures; the format of this structure is illustrated in Figure 7-1 . You can concatenate several structures in the buffer, as shown by Figure 7-2 , so long as each structure begins on a long-word boundary. The buffer itself is described by a TNetbuf structure that specifies the location of the buffer and its size.

You can create a buffer that contains the option values you want to set in one of two ways: manually or by using the OTCreateOptions function. If you construct the buffer manually, you must do the following:

  1. Allocate the buffer.

  2. Create a TOption structure for each option you want to change.

  3. Initialize each field of the TOption structure except for the status field.

  4. Place the TOption structures in the buffer, making sure that each begins on a long-word boundary. This enables Open Transport to parse the buffer.

To have Open Transport create a buffer for you, you must call the OTCreateOptions function and pass it a string containing one or more option values. This method saves time and trouble, but you can only use it if all the options in the buffer are for the same level and that level is the same as the top-level protocol for the endpoint provider. That is to say, you could not use this method to construct a buffer that contains DDP-level options for an ATP endpoint. In addition, this method is only guaranteed to work if you are building an options buffer for the OTOptionManagement function.

Listing 7-1 shows how you construct an options buffer by using the OTCreateOptions function. The code initializes a string array, myStr, to hold option values. It then creates a TOptMgmt structure, which would later be passed to the OTOptionManagement function to request the option values specified in the string. Finally, it calls the OTCreateOptions function to create the options buffer. The OTCreateOptions function creates the TOption structures and places them in the buffer, making sure that the structures are properly aligned.

Listing 7-1 Constructing an options buffer using the OTCreateOptions function

char* myStr = "BaudRate = 9650 DataBits = 8 Parity = 0 
                     StopBits = 10";
UInt8 buffer[512];
TOptMgmt cmd;
cmd.opt.len = 0;
cmd.opt.maxlen = sizeof(buffer);
cmd.opt.buf = buffer;
cmd.flags = T_NEGOTIATE
err = OTCreateOptions("SerialA", &myStr, &cmd.opt)
In this case, the initial value of cmd.opt.len, which is 0, tells the OTCreateOptions function at what offset it should begin to append option information in the buffer. When the function returns, this field specifies the actual length of the buffer.

Setting Default Values

To set all of an endpoint's options to their default values, call the OTOptionManagement function, specifying T_NEGOTIATE for the flags field and allocating a buffer containing only one option named T_ALLOPT. Doing this saves you the trouble of constructing a TOption structure for every option the endpoint supports. However, there is no guarantee that the provider can honor your request simply because you request default values. Therefore, you must allocate a buffer that is large enough to hold the option values returned in the ret parameter.

Retrieving Option Values

This section describes how you can retrieve information about options, including obtaining current and default option values for an endpoint and obtaining current option values related to a connection, transaction, or datagram.

When retrieving option values, you must allocate a buffer that is large enough to contain the options when the function returns. The section "Obtaining the Maximum Size of an Options Buffer" explains how you do this.

Obtaining Current and Default Values

To obtain some of an endpoint's default or current option values, you call the OTOptionManagement function. You specify T_DEFAULT or T_CURRENT for the flags field of the req parameter, and you use the option.buf field to specify the option names in which you are interested. When the function returns, it places TOption structures, describing the default or current option values, in the buffer referenced by the opt.buf field of the ret parameter.

If you are interested in obtaining all of an endpoint's default or current values, you can use the following methods:

Using T_ALLOPT for the option name allows you to construct an input buffer that contains only one option. Remember, however, that you must allocate an output buffer that is large enough to hold all of an endpoint's option values when the function returns.

Parsing an Options Buffer

If you use the OTOptionManagement function to set, verify, or retrieve values, the function returns in the ret parameter a pointer to a buffer containing option information. You can use the OTCreateOptionString function to parse this buffer and create a string that lists all options and their current values.

The code fragment shown in Listing 7-2 calls the OTOptionManagement function to retrieve the option values currently effective for an endpoint. On return, the OTOptionManagement function stores these in the cmd structure. Next, the code calls the OTCreateOptionString function. The first input parameter, "SerialA", specifies the name of the protocol. The next input parameter, opts, is a pointer to the buffer containing the option values returned by the OTOptionManagement function. The expression cmd.opt.buf + cmd.opt.len, which provides the next input parameter, specifies the length of the buffer. Using this information, the OTCreateOptionString function returns a string containing each option name and its respective value. The final parameter to the OTCreateOptionString function specifies the length of the string.

Listing 7-2 Using the OTCreateOptionString function to parse through a buffer

TOptMgmt    cmd;
UINt8       myBuffer[512];
char        myString[256];

cmd.opt.len = sizeof(TOption);
cmd.opt.maxlen = sizeof(myBuffer);
cmd.opt.buf = myBuffer;
((TOption*) buffer)->len = sizeof(TOption);
((TOption*) buffer)->level = COM_SERIAL;
((TOption*) buffer)->name = T_ALLOPT;
((TOption*) buffer)->status = 0;
cmd.flags = T_CURRENT;

OTOptionManagement(theEndpt, &cmd, &cmd);

TOption* opts = (TOption*)cmd.opt.buf;
err = OTCreateOptionString("SerialA", &opts, 
      cmd.opt.buf + cmd.opt.len, string, sizeof(string));
printf("Options = \"%s\"", string);
Note
The OTCreateOptionString function is supplied solely as a debugging aid. You should not include the function in a production version of your application because there is no provision made for localizing string information.

Verifying Option Values

In addition to obtaining default or current values and negotiating new values, you can use the OTOptionManagement function to verify whether an endpoint supports one or more options. To do this, you construct a buffer containing TOption structures describing the options you are interested in and pass this buffer in the req parameter to the OTOptionManagement function, specifying T_CHECK for the action flag. When the function returns, you can examine the status field of the TOption structures for the options passed back to you in the ret parameter to determine whether the specified options are supported.

Sample Code: Getting and Setting Options

The code listings discussed in this section furnish examples of how you can use the Open Transport API to get, set, and display the values of options.

Listing 7-3 shows a main function that calls a number of other functions (defined in subsequent listings) to set, get, and display option values.

Listing 7-3 Calling functions that get, set, and display options

#ifndef qDebug / *OT debugging macros need this var */
#define qDebug 1
#endif
#include <OpenTransport.h>

#include <OpenTptInternet.h> /* for TCP/IP */

#include <OpenTptSerial.h>/* for serial endpoints */

#include <OTDebug.h>/* Need OTDebugBreak and OTAssert macros */
#include <stdio.h>/* Standard C prototypes */

/* OTDebugStr is not defined in any OT header files, but it is
exported by the libraries, so we define the prototype here. */

extern pascal void OTDebugStr(const char* str);

void main(void) {
   OSStatus err;
   OSStatus junk;
   EndpointRef ep;
   UInt32 value;
   
   printf("HelloWorld!\n");
   
   err = InitOpenTransport();
   
   if (err == noErr) {
         ep = OTOpenEndpoint(OTCreateConfiguration(kTCPName), 0, nil,
                                             &err);
      if (err == noErr) {
         printf("\nGetting and Setting IP_REUSEADDR.\n");
         err = GetFourByteOption(ep, INET_IP, IP_REUSEADDR, &value);
         if (err == noErr) 
            printf("Default value = %d\n", value);
         if (err == noErr) 
            err = SetFourByteOption(ep, INET_IP, IP_REUSEADDR, true);
         if (err == noErr){
            err = GetFourByteOption(ep, INET_IP, IP_REUSEADDR,
                                          &value);
            if (err == noErr) 
               printf("New value = %d\n", value)
         }
         if (err == noErr) {
            printf("\nPrinting Options Piecemeal at Level
                                          INET_IP.\n");
            err = PrintAllOptionsAtLevel(ep, INET_IP);
         }
         if (err == noErr){
            printf("\nPrinting Formatted Options at Level
                                       COM_SERIAL.\n");
            err = PrintOptionsForConfiguration(kSerialName,
                                          COM_SERIAL);
         }
         if (err == noErr) {
            printf("\nBuilding Options for COM_SERIAL.\n");
            err = BuildAndPrintOptions(kSerialName, "BaudRate=9600,
                                 DataBits=7, StopBits=15");
         }
         junk = OTCloseProvider(ep);
         OTAssert("GetSetOptions: Closing the endpoint failed", 
                                    junk == noErr);
      }
      CloseOpenTransport();
   }
   if (err == noErr) 
      printf("Success.\n");
    else 
      printf("Failed with error %d.\n", err);
   printf("Done.  Press command-Q to Quit.\n");
}
This main function initializes Open Transport and then creates a TCP endpoint. Then, it calls the function GetFourByteOption to obtain the value of the IP_REUSEADDR option, which governs whether you can bind multiple endpoints to addresses with the same port number. Listing 7-4 contains the definition of the GetFourByteOption function.

Listing 7-4 Getting an option value

static OTResult GetFourByteOption(EndpointRef ep,
                           OTXTILevel level,
                           OTXTIName  name,
                           UInt32   *value)
{
   OTResult err;
   TOption  option;
   TOptMgmt request;
   TOptMgmt result;
   
   /* Set up the option buffer */
   option.len  = kOTFourByteOptionSize;
   option.level= level;
   option.name = name;
   option.status = 0;
   option.value[0] = 0;// Ignored because we're getting the value.

   /* Set up the request parameter for OTOptionManagement to point
    to the option buffer we just filled out */

   request.opt.buf= (UInt8 *) &option;
   request.opt.len= sizeof(option);
   request.flags= T_CURRENT;

   /* Set up the reply parameter for OTOptionManagement. */
   result.opt.buf  = (UInt8 *) &option;
   result.opt.maxlen = sizeof(option);
   
   err = OTOptionManagement(ep, &request, &result);

   if (err == noErr) {
      switch (option.status) 
      {
         case T_SUCCESS:
         case T_READONLY:
            *value = option.value[0];
            break;
         default:
            err = option.status;
            break;
      }
   }
            
   return (err);
}
The function GetFourByteOption gets the current option setting and assigns it to the location referenced by value. The endpoint is assumed to be in synchronous mode. If an error occurs, the function returns a negative result. If the option could not be read, a positive result (either T_FAILURE, T_PARTSUCCESS, or T_NOTSUPPORT) is returned.

Within the body of the function, the fields of the option buffer are set up to specify the option and value we want to get. The TOption structure is used to represent the option buffer. This structure is defined to allow easy construction of 4-byte options.

Next, the request parameter for the OTOptionManagement function is defined to reference the option buffer that was just initialized. The request.flags field is initialized to T_CURRENT, specifying that we want to get the current value of the option. The reply parameter for the OTOptionManagement function is then initialized. This is where the function stores the result of the negotiation. Finally, the OTOptionManagement function is called, and its result is checked to see that the option value was read successfully. Any status other than T_SUCCESS or T_READONLY is stored in the err variable.

The next function called by main is SetFourByteOption, shown in Listing 7-5. This routine sets an option and assigns it to the location referenced by value. The endpoint is assumed to be in synchronous mode. If an error occurs, the function returns a negative result. If the option could not be read, a positive result ( T_FAILURE, T_PARTSUCCESS, TREADONLY, or T_NOTSUPPORT) is returned.

Listing 7-5 Setting an option value

static OTResult SetFourByteOption(EndpointRef ep,
                           OTXTILevel level,
                           OTXTIName  name,
                           UInt32   value)
{
   OTResult err;
   TOption  option;
   TOptMgmt request;
   TOptMgmt result;
   
   /* Set up the option buffer to specify the option and value to
         set. */
   option.len  = kOTFourByteOptionSize;
   option.level= level;
   option.name = name;
   option.status = 0;
   option.value[0] = value;

   /* Set up request parameter for OTOptionManagement */
   request.opt.buf= (UInt8 *) &option;
   request.opt.len= sizeof(option);
   request.flags  = T_NEGOTIATE;

   /* Set up reply parameter for OTOptionManagement. */
   result.opt.buf  = (UInt8 *) &option;
   result.opt.maxlen  = sizeof(option);

   
   err = OTOptionManagement(ep, &request, &result);

   if (err == noErr) {
      if (option.status != T_SUCCESS) 
         err = option.status;
   }
            
   return (err);
}
The SetFourByteOption function is very similar in structure to the GetFourByteOption function, shown in the previous listing. Once again, we use a TOption structure to represent the option buffer and initialize its fields to specify the option and value we want to set. Next, we initialize the request parameter of the OTOptionManagement function to reference the option buffer we just initialized. The length field is set to the size of the option buffer and the flags field is set to T_NEGOTIATE to specify that we want to set the option value specified in the option buffer.

The reply parameter for the OTOptionManagement function is then set up. This is where the function will store the negotiated value of the option when it returns. Finally, the OTOptionManagement function is invoked and its result is checked to make sure the option was successfully negotiated.

After calling the SetFourByteOption function, main calls the GetFourByteOption function again to check the newly set value. The next three functions, PrintAllOptionsAtLevel, PrintOptionsForConfig, and BuildAndPrintOptions demonstrate various ways of displaying option values for an endpoint. Two of those functions call the function PrintOptionBuffer shown in Listing 7-6. This function calls the Open Transport function OTNextOption to parse through an options buffer and then uses printf statements to display the results.

Listing 7-6 Parsing an options buffer

static OSStatus PrintOptionBuffer(const TNetbuf *optionBuffer)
{
   OSStatus     err;
   TOption     *currentOption;

   currentOption = nil;
   do 
   {err = OTNextOption(optionBuffer->buf, optionBuffer->len,
                                    &currentOption);
      if (err == noErr && currentOption != nil) 
          printf("Level = $%08x, Name = $%08x, Data Length = %d,
                     Status = $%08x\n",
                     currentOption->level, 
                     currentOption->name, 
                     currentOption->len - kOTOptionHeaderSize,
                     currentOption->status);
   } while (err == noErr && currentOption != nil);
      return (err);
}
The PrintOptionBuffer function displays the level, name, size, and status for each option in the option buffer. The PrintAllOptionsAtLevel function, which main calls next, uses this function to display all the options for an endpoint that are set at a specified level.

Listing 7-7 Obtaining options for a specific level

static OSStatus PrintAllOptionsAtLevel(EndpointRef ep, OTXTILevel level)
{
   OSStatus    err;
   TEndpointInfoepInfo;
   TOptionHeaderrequestOption;
   void        *resultOptionBuffer;
   TOptMgmt    request;
   TOptMgmt    result;

   resultOptionBuffer = nil;

   /* Find max size of options of endpoint and allocate buffer */

   err = OTGetEndpointInfo(ep, &epInfo);
   if (err == noErr) {
      resultOptionBuffer = OTAllocMem(epInfo.options);
      if (resultOptionBuffer == nil) 
         err = kENOMEMErr;
      }
   
   /* Call OTOptionManagement to get current option values */
   if (err == noErr) {
      requestOption.len= kOTOptionHeaderSize;
      requestOption.level= level;
      requestOption.name= T_ALLOPT;
      requestOption.status = 0;

      request.opt.buf= (UInt8 *) &requestOption;
      request.opt.len= sizeof(requestOption);
      request.flags  = T_CURRENT;
      
      result.opt.buf = resultOptionBuffer;
      result.opt.maxlen = epInfo.options;
      
      err = OTOptionManagement(ep, &request, &result);
   }
   /* Print options to stdout. */
   
   if (err == noErr) {
      err = PrintOptionBuffer(&result.opt);
      printf("\n");
   }
   if (resultOptionBuffer != nil) 
      OTFreeMem(resultOptionBuffer);
   return (err);
}
The function PrintAllOptionsAtLevel takes two parameters, an endpoint reference and a level value. The function first calls OTGetEndpointInfo to determine the maximum size of options for the endpoint and then allocates a buffer to hold the options after they are read. Next, the OTOptionManagement function is called to get the current value (T_CURRENT) of all the options (T_ALLOPT) set for the endpoint. The option values that are returned are stored in the buffer referenced by the result parameter. A pointer to the result parameter is passed to the function PrintOptionsBuffer , which displays option values. After the values are displayed, the memory allocated for the result options buffer is freed.

The next function called by main is PrintOptionsForConfiguration, shown in Listing 7-8. This function gets all the options associated with a level of a provider, converts those options to a formatted string, and then displays that string. The function demonstrates one common use of the function OTCreateOptionString.

Listing 7-8 Using the OTCreateOptionString function

static OSStatus PrintOptionsForConfiguration(const char *configStr,
                                    OTXTILevel level)
{
   OSStatus    err;
   OSStatus    junk;
   EndpointRef ep;
   TEndpointInfoepInfo;
   TOptionHeaderrequestOption;
   void        *resultOptionBuffer;
   TOptMgmt    request;
   TOptMgmt    result;
   TOption     *resultOption;
   char        optionsString[1024];

   resultOptionBuffer = nil;
   ep             = kOTInvalidEndpointRef;
   
   /* Create an endpoint using the specified configuration. */
   ep = OTOpenEndpoint(OTCreateConfiguration(configStr), 0, &epInfo,
                                             &err);

   /* Allocate a buffer to store option info */
   
   if (err == noErr) {
      resultOptionBuffer = OTAllocMem(epInfo.options);
      if (resultOptionBuffer == nil) 
         err = kENOMEMErr;
      }

   /* Get the current value of all options at the specified level. */
   if (err == noErr) {
      requestOption.len = kOTOptionHeaderSize;
      requestOption.level= level;
      requestOption.name= T_ALLOPT;
      requestOption.status = 0;
      request.opt.buf   = (UInt8 *) &requestOption;
      request.opt.len   = sizeof(requestOption);
      request.flags     = T_CURRENT;
      
      result.opt.buf    = resultOptionBuffer;
      result.opt.maxlen = epInfo.options;
      
      err = OTOptionManagement(ep, &request, &result);
   }
   
   /* Convert the options bufferinto a formatted string, and display */
   if (err == noErr) 
   {
      resultOption = (TOption *) result.opt.buf;
      err = OTCreateOptionString(configStr, &resultOption,
            result.opt.buf + result.opt.len, optionsString, 1024);
      if (err == noErr) 
         printf("Formatted Options = "%s"\n\n", optionsString);
   }
   
   /* Clean up. */
   
   if (resultOptionBuffer != nil) 
      OTFreeMem(resultOptionBuffer);
   if (ep != kOTInvalidEndpointRef) {
      junk = OTCloseProvider(ep);
      OTAssert("PrintOptionsForConfiguration: Closing the endpoint
               failed", junk == noErr);
   }
   return (err);
}
The PrintOptionsForConfiguration function takes two parameters, a configuration string that describes a specific endpoint provider and a level for which we are interested in getting option information. The function first opens an endpoint using the configuration information passed in. Note that the address of a buffer to hold endpoint information (&epInfo) is also specified in the call. We need the endpoint information structure in order to determine the maximum size of the options buffer for the endpoint.

Next, the function allocates a buffer to hold option values passed back by the OTOptionManagement function. The value of T_ALLOPT for the name field and T_CURRENT for the flags field specify that we are interested in getting currently set values for all options set for the endpoint. After the buffer is allocated, the OTOptionManagement function is invoked. Finally, the OTCreateOptionString function is invoked; this function converts the option buffer passed back by the OTOptionManagement function into a formatted string, which is then displayed using a printf statement.

The last function called by main, BuildAndPrintOptions, accomplishes the reverse of the PrintOptionsForConfiguration function: it takes a configuration string and a set of formatted options, converts the options to their binary format (that is, an options buffer), and then displays the contents of that buffer. The BuildAndPrintOptions function is shown in Listing 7-9.

Listing 7-9 Building an options buffer from a configuration string

static OSStatus BuildAndPrintOptions(const char *configStr, 
                     const char *optionsString)
{  OSStatus     err;
   OSStatus     junk;
   void         *resultOptionBuffer;
   EndpointRef  ep;
   TEndpointInfo epInfo;
   TNetbuf      optionsNetbuf;
   
   resultOptionBuffer = nil;
   ep = kOTInvalidEndpointRef;
   
   /* Create an endpoint using the specified configuration. */
   
   ep = OTOpenEndpoint(OTCreateConfiguration(configStr), 0,
                                    &epInfo, &err);
   
   /* Allocate a buffer of the maximum option buffer size. */
   if (err == noErr) {
      resultOptionBuffer = OTAllocMem(epInfo.options);
      if (resultOptionBuffer == nil) 
         err = kENOMEMErr;
   }

   /* Parse formatted optionsString into the binary format */
   if (err == noErr) {
      optionsNetbuf.buf = resultOptionBuffer;
      optionsNetbuf.len = 0;
      optionsNetbuf.maxlen = epInfo.options;
      err = OTCreateOptions(configStr, (char **) &optionsString,
                                       &optionsNetbuf);
      if (err == noErr) {
         err = PrintOptionBuffer(&optionsNetbuf);
         printf("\n");
      }
   }
   /* Clean up. */
   if (resultOptionBuffer != nil)
      OTFreeMem(resultOptionBuffer);
   if (ep != kOTInvalidEndpointRef) {
      junk = OTCloseProvider(ep);
      OTAssert("BuildAndPrintOptions: Closing the endpoint failed",
                                       junk == noErr);
   }
   return (err);
}
The function BuildAndPrintOptions creates an endpoint using the specified configuration. When opening the endpoint, the address of an endpoint information structure (&epInfo) is passed in; the endpoint provider fills in this structure with information about the endpoint, including its maximum option buffer size. The function then allocates a buffer that is large enough to contain option information for the endpoint.

Next the function BuildAndPrintOptions calls the OTCreateOptions function to parse the formatted optionsString into the binary format (optionsNetbuf). Then the function calls PrintOptionBuffer to display the contents of the options buffer. Finally, the function frees memory allocated for the options buffer and returns.


Subtopics
Determining Which Function to Use to Negotiate Options
Obtaining the Maximum Size of an Options Buffer
Setting Option Values
Specifying Option Values
Setting Default Values
Retrieving Option Values
Obtaining Current and Default Values
Parsing an Options Buffer
Verifying Option Values
Sample Code: Getting and Setting Options

Previous Book Contents Book Index Next

© Apple Computer, Inc.
15 JAN 1998