Information Networking Institute Carnegie Mellon University Pittsburgh, Pennsylvania 15213
NbIDL: Secure, Object-oriented, Client-Server Middleware By Alex Somogyi*
[email protected] Thomas Wagner°
[email protected] Marvin Sirbu°
[email protected]
INI TR 1998-10
*Current address: Entersoft Systems Corporation, 1170 Route 22, Bridgewater, NJ 08807 °Current address: Carnegie Mellon University, Pittsburgh, Pennsylvania, 15213 Correspondence should be addressed to Marvin Sirbu
1 Introduction NbIDL provides an object-oriented interface to an object-aware client-server architecture. Built on top of The OpenGroup’s DCE RPC, it provides a reliable remote communication mechanism that provides for the passing of C++ objects, polymorphism, and C++-style exceptions. It uses a variation of Kerberos, modified to use asymmetric-key cryptography, to provide authentication and privacy. It can also be used with Transarc’s Encina to provide transactional RPC’s. This paper looks at the requirements of the NetBill electronic commerce system that motivated the development of NbIDL; describes the features of NbIDL in detail; provides a programming example; and compares NbIDL to other object-oriented communication architectures.
2 NetBill Software Architecture 2.1 Communication Software Architecture Requirements NetBill is an electronic commerce system designed to provide highly reliable and secure delivery of electronic goods with very low transaction costs. The NetBill system and the patented NetBill commerce protocol are described in [Sirb95, CTS95]. Multiple application components comprise the NetBill micro-payment system. A communication software architecture was required that could support the development of the protocol and a prototype implementation of the system. The following diagram outlines the major software components that interact to process NetBill transactions.
In the NetBill web-based implementation, the NetBill consumer component consists of a MoneyTool application. This application manages the NetBill protocol from a client's perspective. It interacts with a browser to retrieve and display goods that are sold using NetBill. The MoneyTool, or consumer wallet, performs authentication on behalf of the consumer, negotiates purchase transactions with merchants, sends purchase requests to NetBill via the merchant, and processes purchased goods upon delivery. NetBill merchant software is responsible for authenticating on behalf of the merchant, responding to price and purchase requests, and submitting transactions to the NetBill transaction processing system. The NetBill software components provide the core transaction capabilities of the micro-payment system. Server components exist for managing consumer and merchant private keys and certificates. The transaction processing components will accept purchase requests from merchant product servers, perform the necessary validation and transfer funds within the back-end accounting system. Additional functions exist such as consumer and merchant statement queries, transaction status queries, balance queries, and account creation and management. The sections below outline many of the design requirements for the software architecture.
2.1.1 Heterogeneous Client-Server Communication The NetBill protocol is inherently distributed in nature. Each component communicates with one or more other NetBill components and the interfaces between the components are mostly static. While the initial prototype was to be developed on Solaris, the software components of the NetBill system were intended to run on a variety of hardware and software platforms. The MoneyTool was intended to be NbIDL
2
the most portable of the NetBill software, with support for the major consumer platforms, most notably win32-based environments. The merchant product server was intended to run on various flavors of UNIX and NT. The NetBill transaction servers were to be built on Solaris, but other UNIX variants were considered for the cryptographic-intensive applications.
2.1.2 Object-Oriented Programming Support As mentioned previously, an object-oriented software architecture was desired. The programming language of choice at the time was C++. Support was needed for a distributed object framework wherein the value of objects could be passed across the communications channels, and C++ style exceptions could be passed across an interface. An object-oriented interface to client-server communications was to be implemented, to provide a simple interface to the RPC architecture for the client and server authors1.
2.1.3 Rapid Application Development A high-level communication abstraction was desired to promote rapid application development. The prototype implementation team had a limited number of programming resources. A good communication layer could not only increase team efficiency but it could ease the system maintenance burden as well.
2.1.4 Royalty-free Distribution The need for royalty-free software distribution was an important requirement. The consumer software was intended to be readily available for little or no cost. This requirement ruled out much of the existing middleware products at the time which all had associated run-time licensing fees.
2.1.5 Transaction support The NetBill back-end systems had requirements for server components to participate in distributed transactions. An example of such a distributed transaction would be the account creation process. When a consumer opens an account, a public/private key pair is generated along with database entries for her account. The separate operations of recording the key pair in the key repository and the account information in the database needed to be coordinated to safeguard the information in the event of a failure and maintain data integrity.
2.1.6 Incorporate PKDA Security Model The PKDA protocol was defined as an integral part of the NetBill transaction protocol. PKDA is a mechanism for performing authentication and data integrity/security in an insecure distributed environment. The protocol uses public key operations to enable a client and server to mutually authenticate without recourse to a centralized authentication server such as a Kerberos Key Distribution Center (KDC) or Ticket Granting Service (TGS). Initial authentication based on public key operations leads to the server issuing a Kerberos 5 session ticket to the client. Standard Kerberos operations can then be used to authenticate and secure subsequent communications between the client and server. In effect substituting a Certificate Authority for a KDC results in considerably greater scalability of PKDA compared with traditional Kerberos; NetBill was designed to handle millions of potential clients. PKDA also has several advantages over SSL, another security protocol that uses public key operations to create a session key. PKDA can be used over both UDP and TCP; it can be used to secure transactions which pass through a 1
It is important to note the NbIDL is not a system for Remote Objects, as JavaRMI and CORBA are. This is further discussed in section 5.1, Client-Server vs. Remote Object.
NbIDL
3
third party—for example a NetBill client communicating to the NetBill server via the merchant; and servers are not required to cache session keys for each client—session keys are cached by the client in the form of the session ticket. Refer to [SiCh97] for additional information. Integrated support for PKDA security was, therefore, an additional requirement.
2.1.7 Ease of Client Installation Consumers would need to download and install on their machines the client software. This task had to be kept as simple as possible. It could not require any special privileges beyond those of the average computer user; no Unix root or NT administrator privileges could be required. Installation was to remain as simple as possible so that little skill was required beyond being able to browse the web.
2.2 Design 2.2.1
Chosen Tools
The decision was made to base communication on the Open Group DCE RPC standard. There were several reasons for this choice. First, public domain DCE RPC source code was available which could be compiled and freely distributed for a variety of Unix platforms. We anticipated that many Unix vendors would eventually distribute DCE RPC libraries with the OS, eliminating the necessity to statically link these with the application.2 Further, Microsoft’s RPC is wire compatible with the DCE RPC, and is available on all Win95/NT platforms. The public domain version of DCE RPCs was acquired for the consumer and merchant software. Transarc DCE and Encina products were acquired as the basis for the NetBill transaction-processing environment. While DCE RPC source code had been put in the public domain, the same was not true for the Cell Directory Service, which provides a naming service for mapping DCE services into the appropriate IP address and port number. Accordingly NetBill clients find services through a combination of DNS name and well-known ports assigned to specific services. The use of well-known ports also eases the problem of persuading firewall managers at client sites to open the firewall for NetBill transactions. The low-level software architecture to support the distributed computing requirements was comprised of classes for managing PKDA operations, RogueWave tools.h++ and threads.h++ class libraries, publicdomain DCE RPC libraries, Transarc DCE, Transarc Encina, and custom classes representing entities and operations of the NetBill protocol. CORBA and Java were also considered as the basis for the NetBill distributed object infrastructure. At the time (1994), these architectures had certain limitations that failed to meet the NetBill system requirements. CORBA had promise, but it was still an immature technology. Also, there were concerns about the availability and robustness of ORBs on the consumer platforms. There could be no associated licensing costs for providing NetBill software to consumers. The level of ORB administration required by the consumer was also a concern. NetBill software setup and maintenance tasks had to be kept to a minimum and require no special administrative privileges. Java was certainly immature at the time and was not considered a viable option. Although Java possessed many desirable characteristics, such as "write once, run anywhere", there were concerns about performance and support for cryptography. Today, these concerns no longer apply due to the advent of just-in-time compilers and the vast improvements made to the Java language and API's. 2
HP and IBM currently distribute DCE runtime bundled with their respective Unix operating systems.
NbIDL
4
2.2.2 Architecture Overview Our basic goal was to enable distributed application developers to specify the interfaces between modules in a straightforward fashion that relieves the developer from all of the details of communications, argument marshalling and security. The standard solution to this problem is the use of an Interface Definition Language (IDL) and a compiler or pre-processor which automatically generates all of the necessary stub code at both client and server to facilitate distributed computation. An IDL compiler (NbIDL) was implemented to provide a remote interface abstraction that took into account the basic system requirements mentioned in the preceding sections.
The NetBill IDL Compiler is a code generator that was developed to blend together the underlying components of the software architecture into a simple programming paradigm. Given a specification of a communications interface-- the IDL file--a set of classes is created that, along with the NbIDL runtime library, manage the client-server object interaction. The client is provided with a client interface class that manages communication with a remote service implementation. A server interface class is also created that accepts remote client requests, interprets the request, dispatches the appropriate server method, packages the reply and returns the response to the client. In addition a DCE IDL file is created, and the results of running the DCE IDL compiler are merged with the above classes to generate the client and server stubs. The client and server classes contain methods that perform marshaling of all objects passed as parameters by serializing them as a platform independent byte stream; C data types are marshaled by the standard DCE IDL compiler mechanisms. They also contain methods to perform authentication and encryption as required. The client interface object is a stub with methods for each of the calls in the interface. The server interface object is a skeleton, having method declarations for each call, but no definitions. The server author supplies these definitions to perform the business logic of the server. A single server process can simultaneously support multiple interfaces, each having its own instance of the server interface class. . The NbIDL compiler may automatically generate additional hidden methods of these classes to support authentication, encryption, network time protocol, or bulk data transfer as necessary. The NetBill interface definition language remained close to DCE’s RPC IDL with the addition of some keywords to: 1) indicate which parameters are C++ objects; and 2) to support PKDA authentication and encryption.
2.2.2.1 Client To contact a server, the client first creates a binding object. When creating this object, the client specifies a DCE-style string binding, which is simply an indication of which server is to be contacted using what protocol. Any protocol which DCE supports can be used, but normally TCP or UDP is used and the server is referenced by a hostname or IP address and an optional port to be used instead of the default port for the given interface. NbIDL interfaces support no extra naming service beyond DNS, so something outside of the RPC mechanism, such as a URL, must provide the server’s address. The client also uses this object to specify the identity of the caller, and the desired level of authentication and encryption to be used Once a binding object is created, an instance of the desired interface object is created and the binding object is passed to it. The interface object uses the binding object to identify what server to contact, and updates the binding object with the server’s authentication. The client can then call the methods on the interface object; it in turn implements the methods by making RPC calls to the server named in the binding. The client can also ask the interface for the binding object to be returned and from that binding object learn the authenticated identity of the server.
NbIDL
5
2.2.2.2 Server The server author simply fills in the code for the defined callback methods, one for each call defined in the interface. The server code must then do some setup, including telling the RPC mechanism what identity to use for authenticated calls, register the various interfaces it wants to listen for, then wait. The standardized server shell will automatically receive calls and dispatch them as method calls on the appropriate server interface object.
3 NbIDL 3.1 Functional Overview The process of creating a distributed application using the NetBill architecture starts with defining the communication interfaces using the NbIDL interface definition language. Once the interfaces have been defined, the "nbidl" IDL compiler is used to generate the client and server interface classes. These classes act as "stubs" and "skeletons" that handle all data marshaling between the remote application components. Figure 1 illustrates the code generation process.
nbidl
nbidl
dce idl
client interface
server interface
idl
client code client rpc stub
server code server rpc stub
C++
C++
client
server
Figure 1: Code Generation Process
3.1.1 IDL Grammar The format of the nbidl grammar is similar to the grammar used in the Open Group DCE RPC system. For a definition of the DCE RPC IDL grammar refer to [OSF95]. NbIDL
6
3.1.1.1 DCE IDL Grammar Extensions
NbIDL provides several new keywords to control what authentication and encryption services the stub code should provide. Nbauthenticate will cause the client to perform authentication; nbencrypt will cause all information passed between client and server to be encrypted. Nbobject indicates that a particular identifier specifies a marshallable object. Nbtransaction as applied to an interface or a method indicates that the specified entity participates in a distributed transaction under the Encina transaction processing monitor (see discussion in 3.1.2 below). Lines can be passed unprocessed from NbIDL to DCE-IDL by prefacing them with the “@” symbol.
3.1.2 Transport modes for PD-DCE, DCE, Encina
One output of NbIDL is a DCE IDL specification, which is passed to the DCE IDL compiler for further expansion. The Encina transaction processing environment provides a Transaction Interface Definition Language (TIDL) and a TIDL compiler which generates all of the stub code necessary to enable the interface to participate in a distributed transaction under the Encina transaction monitor. Within the NetBill central service, processes are distributed among several different servers for availability and scalability. To ensure the atomicity of NetBill transactions, distributed processing within the NetBill central node is performed within the Encina framework. NbIDL can be directed to produce a TIDL file, as opposed to a DCE IDL where the interface requires transactional properties. The Encina TIDL compiler itself produces a DCE IDL file. Thus, there may be as many as three layers of automated code generation between an NbIDL specification and the production of the stub source code to implement the interface with all of the required properties.
3.2 Internals 3.2.1 Marshaling
In order to be able to pass objects as parameters across the interface it is necessary to marshal the objects in a machine independent manner. This is accomplished by using facilities provided by the RWTools.h++ library of base classes.
3.2.1.1 Objects marshaled using RW virtual streams
3.2.1.1.1
RWCollectable Objects
The RWTools.h++ library provides a base class, RWCollectable, which allows subclasses with proper virtual function redefinitions, to be easily used in a number of collections. It also provides for an easy method of saving these objects to disk as well as to “virtual streams” which can represent various ways of serializing an object, e.g. as ASCII text or binary data.
NbIDL
7
3.2.1.1.2
XDR virtual streams
Because NetBill must exist in a heterogeneous network, binary streams cannot be used to save data. Therefore, XDR streams are used to marshal objects into a machine independent format for saving to disk or shipping across the network. Because virtual streams are used, the underlying format could easily be changed by altering the code generator to use some other virtual stream without having to make any changes to the objects to be passed. Given the use of XDR streams to package objects, all C++ objects to be sent via an NbIDL interface must descend from the RWCollectable object and provide a reasonable set of redefinitions for the RWCollectable virtual functions. NbIDL generates stubs, which marshal all objects as byte arrays using these XDR streams. These byte arrays plus any other data types are specified in the NbIDL generated DCE IDL. The DCE IDL compiler then generates stubs for marshalling all of the data. In effect, the object contents are being marshaled twice, but DCE marshalling of byte arrays is minimal.
3.2.1.1.3
Argument passing
DCE provides for: IN arguments which are supplied by the client and not modified by the server; OUT arguments which are returned by the server and not modified by the client; and IN/OUT arguments which, after being passed to the server, may be modified by the server before being returned to the client. NbIDL provides a similar capability for objects. In addition to arguments being passed by value or as a pointer, NbIDL supports parameters that are references to C++ objects.
3.2.1.1.4
Polymorphic object parameters
Polymorphism is supported for remote method calls. For example the caller may not know the precise class that will be returned as an OUT argument. In this case the OUT argument is specified as a reference to a pointer to a base class. The server object implementation will create an instance of the appropriate sub class to be returned. The content will be sent to the client stub, which will allocate memory for a local instance of the appropriate subclass using the returned contents. On the client side it is the caller’s responsibility to deallocate this memory when it is no longer needed. On the server side, it is the responsibility of the server method implementation to indicate to the stub that the stub is free to reclaim the memory for the object. The stub will reclaim the memory when it has completed marshalling the OUT or IN/OUT parameters. Similarly, an IN argument may be a pointer or a reference to a base class. In this case the client can pass either a pointer or a reference to an appropriate subclass for this argument. The server stub will allocate the appropriate subclass and will pass it to the server method implementation. The server stub will reclaim the memory after the server method returns. DCE servers are typically stateless. If a server wants to preserve the information in an object passed as an IN or an IN/OUT argument, it must make a local copy of the object.
3.2.2 Security/PKDA Integration
The PKDA protocol is designed to provide mutual authentication between a client and server, leaving the client with a Kerberos session ticket, which can be used to authenticate and secure further communication between client and server. The first step is for the client to request the server’s public key certificate, which is returned as a response. In the next step, the client sends a request for a session ticket--containing the NbIDL
8
client’s certificate and a proposed session key--signed by the client and encrypted in the server’s public key. The server decrypts the request using its private key, verifies the signature using the client’s certificate, and returns a Kerberos style session ticket containing the proposed session key. Further communications can then be encrypted using the Kerberos ticket and session key, which also provides mutual authentication. Kerberos session security requires that the clocks of client and server be closely synchronized. A Simple Network Time Protocol [Mill96] request is piggybacked on the hidden certificate request method to enable clock synchronization with a trusted server.
Client
Server
remoteMethod()
remoteMethod() Client Object Stub
getCertificate() getTicket()
Server Object Stub
remoteMethod()
Figure 2: Initial PKDA Interaction
If the NbIDL authenticate and encrypt keywords are present in the interface definition, the NbIDL compiler will automatically generate two hidden methods between the client and server stubs. These methods include a request for certificate from client to server, and the PKDA ticket request. A set of security related classes implement public and private keys, certificates, Kerberos tickets, and include methods for digital signatures, encryption and decryption. NetBill uses the RSA public key encryption algorithm, and DES symmetric encryption. Certificates are RWCollectible objects and are serialized using XDR as opposed to using ASN.1 and the X.509 syntax.3 If only one way authentication is desired (e.g. server to client), the ticket requested may be unsigned, and the corresponding Kerberos session ticket is issued with a client name of “Anonymous”. This is useful to establish a capability for encrypting communication between client and server prior to a client acquiring its certificate. Once acquired, a Kerberos ticket is automatically cached by the security support classes. Subsequent method invocations can use a cached ticket and bypass the hidden method calls. If the ticket has expired the security support classes will automatically invoke the hidden methods to acquire a new ticket. If the server’s certificate has not expired, this subsequent ticket refresh can bypass the request for the server certificate. Similarly, servers will cache client certificates that have had their signatures successfully verified. Subsequent ticket requests can be approved without reverifying the signature on a cached certificate that has not yet expired.
3
Certificates are implented as a base class with provision for subclasses that implement either XDR serialization or ASN.1. Polymorphism allows either subclass to be used across the interface, though we have only implemented the XDR –based subclass.
NbIDL
9
3.2.2.1 Using the ticket If the Nbencrypt keyword was specified to NbIDL, the generated stub code will, after marshaling the parameters, encrypt the byte stream using the standard Kerberos libraries. The Kerberos ticket and onetime authenticator are added by the NbIDL compiler as hidden arguments to the method call so that they may be passed with the encrypted arguments. The server stub will use the Kerberos ticket and authenticator to extract the session key, decrypt the arguments, and then pass them to the server implementation. Kerberos is ideally suited for the stateless nature of the DCE servers, as the per client session key is passed to the server from the client in the form of the ticket, rather than being cached by the server.
3.2.2.2 Marshalling and encryption using NbIDL versus DCE As currently designed, NbIDL encryption is accomplished by encrypting the XDR-serialized objects one at a time. This has two consequences. If many different objects are passed between client and server, there are repeated calls to the encryption library, which is less efficient than encrypting a single stream and results in increased overhead. Second, C data types, which could have been passed directly to DCE for marshalling, must be included in an object in order to be encrypted. In practice, these limitations can be addressed by the application programmer by creating a single parameter object that contains all of the actual parameters or objects to be passed. A better design would have been to first concatenate all of the serialized objects before encryption. The recently released object-oriented DCE 1.2 takes a different approach. By requiring that all classes that are used as parameters be defined within the IDL, the IDL compiler can assume responsibility for generating code to marshal the classes prior to DCE encryption. Neither DCE 1.1 nor Microsoft RPC provide access to DCE marshaled objects for user provided encryption.
3.2.3 Pipes DCE uses pipes to pass large amounts of data over an interface—such as the digital goods being sold using NetBill. MSRPC, however, doesn’t fully support DCE pipes, so another mechanism was needed. For this, NbPipes was created. Due to time limitations, only OUT pipes, that is pipes that pass information from server to client, were implemented, though the mechanism could easily be extended to support pipes in the other direction. The interface author can specify that an argument in an RPC is a pipe. The corresponding method on the interface classes will then have an argument that is a reference to an NbPipe object. The server author repeatedly passes buffers to the NbPipe object which are then passed to the client. The client author must create a subclass of the NbPipe class, defining virtual methods that will be called to empty buffers as they arrive. The client software must create an instance of this NbPipe subclass and pass it to the proper method of the client interface object. It is important to note that the server is driving the sending of data. This matches the way pipes are implemented in DCE and means that eventually, the NbPipe interface could be augmented to take advantage of DCE pipes on platforms that support them. NbPipe uses repeated DCE RPC calls to give the appearance of a single call to both the client and server code. Every RPC call specified in the NbIDL file as containing an NbPipe results in three RPC calls in the corresponding DCE interface. This is illustrated in Figure 3.
NbIDL
10
Client
Program thread
Server
Server Callback
Server callback thread Give buffer
Return from Server Callback
RPC call Client code
Server code
NbIdl layer
NbIdl layer First DCE call
Empty buffer callback
Middle DCE call (repeated)
Final DCE call RPC threads Return from RPC call
Call across layers Return from call Callback Thread synchronization DCE RPC call Figure 3: NbPipe Interaction Diagram The client-author makes an NbIDL RPC call. The NbIDL layer then makes the first call to the server. This call tells the server that an NbPipe RPC call is starting and it contains all of the IN arguments for this call. The DCE layer spawns a thread (labeled RPC thread in Figure 3: NbPipe Interaction Diagram). The NbIDL generated code for this call spawns another thread, labeled “server callback thread” in the figure. In this thread, the server interface callback method that corresponds to the NbIDL RPC call made by the client is called. To the server author this looks like any other RPC call. Meanwhile, the NbIDL layer ends the DCE RPC call and exits the first RPC thread. The NbIDL layer on the client then calls back to the server to get a buffer full of data. The DCE RPC from the client causes another RPC thread to be spawned. This thread blocks, waiting for the server code to callback into the NbIDL layer to give it a buffer. When this happens, the RPC thread returns the buffer back to the client. The NbIDL layer on the client calls back into the client code to hand over the buffer, then repeats the RPC call to the server. This happens until the server gives an empty buffer to the NbIDL layer. It appears to the server author as if they are repeatedly sending data to the client in one thread of control, when in actuality they are waiting for the client to ask for more data.
NbIDL
11
At this point, the NbIDL layer makes a final RPC call to the server to get any OUT arguments, return values, and exceptions. This DCE call spawns another RPC thread, which waits to join with the server callback thread. The server code eventually returns from the callback method at which point the server callback thread exits. The RPC thread now packages up the data and returns to the client. To the client-author and server-author, it appears that there is only one thread of control, which includes callbacks with buffers of data from the server to the client. In actuality, multiple threads and RPC calls are utilized by the NbIDL layer.
3.2.4 Exception Propagation Exceptions are a powerful feature of C++ which simplify error handling and make code much more readable and less error prone. NbIDL provides support for exception propagation across the network. Exceptions thrown in the server method implementation must be objects descended from the NbPropagateException class, which is defined as a subclass of RWCollectable. Exceptions raised by the server implementation are caught by the server stub, marshaled and propagated across the network using the same method used for OUT object parameters, and then are reraised by the client stub to its caller. Client and server stubs translate DCE error codes into exceptions.
3.2.5 Interface Versioning The DCE RPC mechanism provides support for interface version numbers. If the client stub code detects a version number mismatch it will raise an appropriate exception that will be passed to the caller. NetBill uses this capability to notify consumers when they need to download an updated version of the client software to support new interface features.
4 Programming Example This section provides an example of how NbIDL is used in the development process. The following example illustrates how the various capabilities of the software architecture may be employed to develop a distributed application. The application is a sample file transfer utility that consists of a server daemon process that waits for a file transfer request from the client. The client makes a request for a distinct remote file and the file is transferred from the server to the client using the pipe data streaming capability of NbIDL.
4.1 Interface Definition The first step in writing a distributed application using NbIDL is to specify the interface definition. The following example shows the “nbidl” file for the grab file transfer utility. Note that the syntax is identical to the DCE IDL syntax except for a few additional keywords. /* * * grabif.nbidl * * Interface for NbPipes integration test * */ [ uuid(3b99a94a-c879-11d0-b510-8002ed10aa77), nbuuid(3ca158c4-c879-11d0-a009-8002ed10aa77), version(1.1), endpoint("ncadg_ip_udp:[23330]", "ncacn_ip_tcp:[23330]"), nbobject "rw/collstr.h", nbexception "grabexc.h"
NbIDL
12
] interface grabif { @ typedef unsigned long idl_ulong_t; [nbencrypt, throw "Grab_ServerError"] long grabRemote( [in,nbobject] RWCollectableString remoteFileNm, [in] idl_ulong_t bufSize, [nbpipe] NbPipe pipe, [out,ref,nbobject] RWCollectableString& fileStats, [out,ref] idl_ulong_t* fileSize ); }
Example 1: grab.nbidl
The attribute section for the interface defines the general characteristic such as the Universal Unique Identifier (UUID) for the interface. The UUID, interface version number and protocol endpoints are specified as they are in a typical DCE IDL definition. Additional NbIDL-specific interface attributes need to be specified for declaring classes that are processed by the interface. The nbobject keyword is used to indicate which class headers should be included for interface method arguments. The nbexception keyword indicates the header file for an exception class that may be thrown by one or more interface methods. The interface section of the interface definition file contains declarations of the support methods. Each method has an attribute section that defines what security attributes apply to the method. A throw clause is also acceptable that indicates what exceptions may be thrown by the remote method. Any exception class names specified in a method attribute section must be defined using the nbexception keyword in the interface attribute section. Each parameter in a method declaration may also contain an attribute section. The parameter attribute section identifies whether the argument is an IN or OUT parameter, whether it is an object or if the argument is a data pipe. In the example interface above, the “grabif” interface is defined to have a single method, grabRemote(). The method takes a collectable string object and a buffer size as IN arguments. An NbPipe is used to transfer the data. The out arguments consist of a collectable string object and a DCE unsigned long type. Method invocations are authenticated and encrypted, as indicated by the nbencrypt keyword, and the Grab_ServerError exception may be thrown. Note that a DCE IDL typedef is used to define a long typedef for the buffer size and file size arguments.
4.2 Propagatable Exception Exception classes that are to be propagated across an NbIDL interface need to be defined in a certain way in order for the propagation mechanism to work correctly. Each exception class must inherit from the NbPropagateException class, which, as noted earlier, is descended from RWCollectible. In addition, the NBDECLARE_PROPEXCEPTION macro must be called within the class declaration and the NBDEFINE_PROPEXCEPTION macro must be called within the class definition. Example 2 and Example 3 illustrate the definition of the Grab_ServerError exception class that may be thrown by the grabd server. #ifndef _GRABEXC_H #define _GRABEXC_H // // grabexc.h // // #include #include #define __GRAB_SERVERERROR 0x0401
NbIDL
13
class Grab_ServerError : public NbPropagateError { RWDECLARE_COLLECTABLE(Grab_ServerError) NBDECLARE_PROPEXCEPTION(Grab_ServerError) }; extern int grabexcPullMe; #ifdef NBIDL_CLIENT_CODE static void grabexcPullMeFunc() {grabexcPullMe=1;} #endif #endif
Example 2: grabexc.h
// // grabexc.cpp // // #include "grabexc.h" int grabexcPullMe; RWDEFINE_COLLECTABLE(Grab_ServerError,__GRAB_SERVERERROR); NBDEFINE_PROPEXCEPTION(Grab_ServerError);
Example 3: grabexc.cpp
4.3 Client The client component of the grab application accepts command line arguments for the server string binding and remote file name. Authentication is performed under a set client identity and the grabRemote() method is invoked to transfer the specified file from the grab server running on the remote host. Example 4 shows the client source for the grab application. // // // // // // // //
grab.cpp Copyright 1997, Carnegie Mellon University.
All Rights Reserved.
Sample NbPipe application client. Connect to server and transfer specified remote file to local file.
#include #include #include #include #include #include #include #include #include
"grabifC.h"
class GrabClientPipe : public NbClientPipe { public: RWFile file_; //Constructor for our use of pipe. In this case, we take the name
NbIDL
14
//of the file to save the pipe data in, and make sure that file is //empty. GrabClientPipe(const char* filenm="./grab.data") : file_(filenm) { file_.Erase(); } //Write the given buffer to the file void emptyBuf(NbBytes& buf) {file_.Write(buf.data(),buf.length());} }; main(int argc, char *argv[]) { if (argc < 4 || argc > 5) { cerr