VMSify.c

/*
    File:       VMSify.c
 
    Contains:   Stream module that does nothing.
 
    Written by: Quinn "The Eskimo!"
 
    Copyright:  © 1997 by Apple Computer, Inc., all rights reserved.
 
    Change History (most recent first):
 
    You may incorporate this sample code into your applications without
    restriction, though the sample code has been provided "AS IS" and the
    responsibility for its operation is 100% yours.  However, what you are
    not permitted to do is to redistribute the source as "DSC Sample Code"
    after having made changes. If you're going to re-distribute the source,
    we require that you make it clear in the source that the code was
    descended from Apple Sample Code, but that you've made changes.
*/
 
/*
    A Note About States
    -------------------
    Throughout the source, you'll see comments like:
    
        sta_X + Y -> sta_Z
    
    These comments describe state transitions in the TPI state machine,
    which is documented in Figure 9 of the TPI specification:
    
        <http://developer.apple.com/macos/opentransport/docs/dev/tpi.pdf>
    
    The basic idea is that, when you start in state X and get event Y,
    you transition to state Z.
    
    This module has to track the state of the provider to ensure that it
    only turns on the VMSify'ing functionality when the provider is being
    used for outgoing streams connected to the telnet port.
*/
 
/////////////////////////////////////////////////////////////////////
// The OT debugging macros in <OTDebug.h> require this variable to
// be set.
 
#ifndef qDebug
#define qDebug  0
#endif
 
/////////////////////////////////////////////////////////////////////
// Pick up all the standard OT module stuff.
 
#include <OpenTransportKernel.h>
#include <OpenTransportProviders.h>
 
/////////////////////////////////////////////////////////////////////
// OpenTptMiscUtilsPPC.o is not exporting OTDebugStr currently
// (Universal Interfaces 3.3.1).  So we just define our own version
// here.
 
extern pascal void OTDebugStr(const char *msg)
{
    debugstr(msg);
}
 
/////////////////////////////////////////////////////////////////////
// Some simple routines we use in our various assertions.
 
static Boolean IsReadQ(queue_t* q)
    // Returns true if q is the read queue of a queue pair.
{
    return ((q->q_flag & QREADR) != 0);
}
 
static Boolean IsWriteQ(queue_t* q)
    // Returns true if q is the write queue of a queue pair.
{
    return ((q->q_flag & QREADR) == 0);
}
 
/////////////////////////////////////////////////////////////////////
// Per-Stream information
 
enum {
    kTelnetPort = 23
};
 
// This structure is used to hold the per-stream data for the module.
// While module's can use normal global variables to store real globals,
// they must maintain their own per-stream data structures.  I use
// mi_open_comm to allocate this data structure when the stream is
// opened.  mi_open_comm stores the address of this data structure in the
// read and write queue's q_ptr field, so the rest of the code
// can get to it by calling the GetPerStreamData function.
 
enum {
    kVMSifyPerStreamDataMagic = 'VMS!'
};
 
struct PerStreamData
{
    OSType              magic;              // kVMSifyPerStreamDataMagic = 'VMS!' for debugging
    // Your per-stream data structures go here.
    InetPort            lastConnReqPort;    // outgoing connection requests setup this
    Boolean             vmsify;             // if we made a successful outgoing connection to port kTelnetPort, this is true
};
typedef struct PerStreamData PerStreamData, *PerStreamDataPtr;
 
static PerStreamDataPtr GetPerStreamData(queue_t* readOrWriteQ)
    // You can pass both the read or the write queue to this routine
    // because mi_open_comm sets up both q_ptr's to point to the
    // queue local data.
    //
    // Note that, in order to avoid the overhead of a function call,
    // you would normally use inline code (or a macro)
    // to get your per-stream data instead of using a separate function.
    // However I think the separate function makes things clearer.
    // I also acts as a central bottleneck for my debugging code.
    //
    // Environment: any standard STREAMS entry point
{
    PerStreamDataPtr streamData;
    
    streamData = (PerStreamDataPtr) readOrWriteQ->q_ptr;
 
    OTAssert("GetPerStreamData: what streamData", streamData != nil);
    OTAssert("GetPerStreamData: Bad magic", streamData->magic == kVMSifyPerStreamDataMagic);
    
    return streamData;
}
 
// mi_open_comm and mi_close_comm (and also mi_detach and mi_close_detached)
// use this global to store the list of open streams to this module.
 
static char* gStreamList = nil;
 
/////////////////////////////////////////////////////////////////////
// Open routine
 
static SInt32 VMSifyOpen(queue_t* rdq, dev_t* dev, SInt32 flag, SInt32 sflag, cred_t* creds)
    // This routine is called by STREAMS when a new stream is connected to
    // our module.  The bulk of the work here is done by the Mentat helper
    // routine mi_open_comm.
    //
    // Environment: standard STREAMS entry point
{
    SInt32 err;
    PerStreamDataPtr streamData;
 
    OTAssert("VMSifyOpen: Not the read queue", IsReadQ(rdq) );
 
    // OTDebugBreak("VMSifyOpen");
    
    err = noErr;
    
    // If we already have per-stream data for this stream, the stream is being reopened.
    // In that case, we can just return.
    // Note that we can't call GetPerStreamData because it checks that streamData is not nil.
    
    if ( rdq->q_ptr != nil ) {
        goto done;
    }
 
    // Make sure we're being opened properly -- because we're a module we
    // require a "module" open.  Other possibilities are the value 0 (used
    // to open a specific minor device number (ie stream) on a device driver),
    // and CLONEOPEN (used to open a new stream to a device number where you
    // don't care what device number you get -- the typical behaviour for
    // networking (as opposed to serial) devices).
    
    if ( (err == noErr) && (sflag != MODOPEN) ) {
        err = ENXIO;
    }
    
    // Use the mi_open_comm routine to allocate our per-stream data.  Then
    // zero out the entire per-stream data record and fill out the fields
    // we're going to need.
    
    if (err == noErr) {
        err = mi_open_comm(&gStreamList, sizeof(PerStreamData), rdq, dev, flag, sflag, creds);
        if ( err == noErr ) {
            // Note that we can't call GetPerStreamData because the magic is not set up yet.
            streamData = (PerStreamDataPtr) rdq->q_ptr;
            
            OTMemzero(streamData, sizeof(PerStreamData));
            
            streamData->magic = kVMSifyPerStreamDataMagic;
            streamData->lastConnReqPort = 0;
            streamData->vmsify = false;
        }
    }
 
done:
    return err;
}
 
/////////////////////////////////////////////////////////////////////
// Close routine
 
static SInt32 VMSifyClose(queue_t* rdq, SInt32 flags, cred_t* credP)
    // This routine is called by STREAMS when a stream is being
    // disconnected from our module (ie closed).  The bulk of the work
    // is done by the magic Mentat helper routine mi_close_comm.
    //
    // Environment: standard STREAMS entry point
{
    #pragma unused(flags)
    #pragma unused(credP)
 
    OTAssert("VMSifyClose: Not the read queue", IsReadQ(rdq) );
 
    (void) mi_close_comm(&gStreamList, rdq);
 
    return 0;
}
 
/////////////////////////////////////////////////////////////////////
 
enum {
    kNoPrimitive = -1
};
 
static long GetPrimitive(mblk_t* mp)
    // GetPrimitive gets the TPI/DLPI primitive out of a message block.
    // It returns kNoPrimitive if the message block is of the wrong
    // type or there is no primitive.
    //
    // Environment: any standard STREAMS entry point
{
    if ((mp->b_datap->db_type == M_PROTO || mp->b_datap->db_type == M_PCPROTO) && MBLK_SIZE(mp) >= sizeof(long) ) {
        return ( (union T_primitives*) mp->b_rptr)->type;
    } else {
        return kNoPrimitive;
    }
}
 
/////////////////////////////////////////////////////////////////////
// Write-side put routine
 
static InetPort ExtractPortFromDNSAddress(DNSAddress *dnsAddr, ByteCount DEST_length)
    // Parse the port number out of a DNSAddress structure.
    // Yetch!
{
    InetPort portNum;
    char *cursor;
    char *limit;
    
    portNum = 0;
    cursor = dnsAddr->fName;
    limit = ((char *) dnsAddr) + DEST_length;
    
    // Skip past all the stuff before the colon.
    
    while ( cursor < limit && *cursor != ':' ) {
        cursor += 1;
    }
    
    // If there's a colon, skip over it and then
    // extract digits from there.
    
    if (*cursor == ':') {
        cursor += 1;                // skip past ':'
        while (cursor < limit && *cursor >= '0' && *cursor <= '9') {
            portNum = (portNum * 10) + (*cursor - '0');
            cursor += 1;
        }
    }
    return portNum;
}
 
static SInt32 VMSifyWritePut(queue_t* q, mblk_t* mp)
    // This routine is called by STREAMS when it has a message for our
    // module from upstream.  Typically, this routine is a big case statement
    // that dispatches to our various message handling routines.
    //
    // Environment: standard STREAMS entry point
{
    PerStreamDataPtr streamData;
    T_conn_req *connReq;
    OTAddress *otAddr;
    InetAddress *inetAddr;
    
    OTAssert("VMSifyWritePut: Not the write queue", IsWriteQ(q) );
 
    // OTDebugBreak("VMSifyWritePut: Entered");
    
    streamData = GetPerStreamData(q);
 
    switch ( GetPrimitive(mp) ) {
        case T_CONN_REQ:
            OTAssert("VMSifyWritePut: We got connection request but we still think we're connected", streamData->lastConnReqPort == 0);
            OTAssert("VMSifyWritePut: We got connection request but we still think we're VMSifying", !streamData->vmsify);
            
            connReq = (T_conn_req *) mp->b_rptr;
            otAddr = (OTAddress *) (mp->b_rptr + connReq->DEST_offset);
            switch (otAddr->fAddressType) {
                case AF_DNS:
                    streamData->lastConnReqPort = ExtractPortFromDNSAddress( (DNSAddress *) otAddr, connReq->DEST_length);
                    break;
                case AF_INET:
                    OTAssert("VMSifyWritePut: Client passed bad address length, we're may make a bad decision, but TCP/IP will probably barf anyway", connReq->DEST_length == sizeof(InetAddress));
                    inetAddr = (InetAddress *) otAddr;
                    streamData->lastConnReqPort = inetAddr->fPort;
                    break;
                default:
                    OTDebugBreak("VMSifyWritePut: Bogus address format");
                    streamData->lastConnReqPort = 0;
                    break;
            }
            putnext(q, mp);
            break;
        default:
            putnext(q, mp);
            break;
    }
    
    return 0;
}
 
/////////////////////////////////////////////////////////////////////
// Read-side put routine
 
static void VMSifyMessageChain(mblk_t **destPtr)
    // Given a pointer to a pointer to a chain of messages
    // (destPtr), replace the chain of messages with a
    // chain of buffers where the data has been uppercased.
    // This might involve copying the messages if the data
    // they reference is read only, hence we pass in the
    // address of the head of the chain, not the head
    // of the chain itself.
{
    mblk_t *source;
 
    OTAssert("VMSifyMessageChain: Bad dest pointer", destPtr != nil);
 
    source = *destPtr;
    
    while ( source != nil ) {
        mblk_t *next;
        mblk_t *tmp;
 
        OTAssert("VMSifyReadPut: Oh dear", *destPtr != nil);
        
        // Extract next here because we might have freed
        // source by the time we get to the end of the
        // routine.
        
        next = source->b_cont;
        
        // See whether the source buffer is writable.
        // If it is, setup tmp so that we munge it.
        // If it isn't, try to copy the buffer.  If
        // that works, setup tmp so that we munge
        // the copy, and free the original.  If it
        // fails, leave tmp set to nil so that we
        // just pass through the unmunged buffer,
        // which is not necessarily the best response
        // to an out of memory situation, but it
        // certainly is easy to implement (-:
        
        if (source->b_datap->db_ref == 1) {
            tmp = source;
        } else {
            tmp = copyb(source);
            if (tmp != nil) {
                freeb(source);
            }
        }
        
        // We we can write to the buffer, upper case
        // it and chain it on to the destination list.
        // If we can't write to it, just chain the
        // unmunged buffer on to the destination list.
        
        if (tmp != nil) {
            UInt8 *cursor;
            UInt8 ch;
            
            OTAssert("VMSifyMessageChain: Buffer is still not writeable", tmp->b_datap->db_ref == 1);
            
            for (cursor = tmp->b_rptr; cursor < tmp->b_wptr; cursor++) {
                ch = *cursor;
                if (ch >= 'a' && ch <= 'z') {
                    ch -= 0x20;
                    *cursor = ch;
                }
            }
            *destPtr = tmp;
        } else {
            *destPtr = source;
        }
        
        // Move to the next buffer on both the
        // source and destination chains.
        
        destPtr = &((*destPtr)->b_cont);
        source = next;
    }
}
 
static SInt32 VMSifyReadPut(queue_t* q, mblk_t* mp)
    // This routine is called by STREAMS when it has a message for our
    // module from downstream.  Typically, this routine is a big case statement
    // that dispatches to our various message handling routines.
    //
    // Environment: standard STREAMS entry point
{
    PerStreamDataPtr streamData;
    Boolean okAck;
    
    OTAssert("VMSifyReadPut: Not the read queue", IsReadQ(q) );
 
    // OTDebugBreak("VMSifyReadPut: Entered");
    
    streamData = GetPerStreamData(q);
 
    switch ( mp->b_datap->db_type ) {
        case M_DATA:
            if ( streamData->vmsify ) {
                VMSifyMessageChain(&mp);
            }
            putnext(q, mp);
            break;
        default:
            switch ( GetPrimitive(mp) ) {
                case T_OK_ACK:
                case T_ERROR_ACK:
                    okAck = (GetPrimitive(mp) == T_OK_ACK);
                    switch ( ((T_ok_ack *) mp->b_rptr)->CORRECT_prim ) {
                        case T_CONN_REQ:
                            if (okAck) {
                                // sta_5 + ok_ack1 -> sta_6
                                // Connection in place, let's start vmsifying if required.
                                streamData->vmsify = (streamData->lastConnReqPort == kTelnetPort);
                            } else {
                                // sta_5 + error_ack -> sta_3
                                // Connection attemp failed, leave vmsify false.
                                OTAssert("VMSifyReadPut: vmsify should be false but isn't", !streamData->vmsify);
                            }
                            streamData->lastConnReqPort = 0;
                            break;
                        case T_DISCON_REQ:
                            if (okAck) {
                                // sta_14 + ok_ack1 -> sta_3
                                // sta_15 + ok_ack1 -> sta_3
                                // sta_16 + ok_ack1 -> sta_3
                                // Disconnect successful, connection is gone, clear vmsify.
                                streamData->vmsify = false;
                            } else {
                                // sta_14 + error_ack -> sta_9
                                // sta_15 + error_ack -> sta_10
                                // sta_16 + error_ack -> sta_11
                                // Disconnect failed, connection is still in place,
                                // so is vmsify.
                            }
                            break;
                        default:
                            break;
                    } 
                    putnext(q, mp);
                    break;
                case T_ORDREL_IND:
                    // sta_10 + ordrel_ind -> sta_3
                    // sta_9  + ordrel_ind -> sta_11
                    streamData->vmsify = false;
                    putnext(q, mp);
                    break;
                case T_DISCON_IND:
                    // sta_6  + discon_ind1 -> sta_3
                    // sta_9  + discon_ind1 -> sta_3
                    // sta_10 + discon_ind1 -> sta_3
                    // sta_11 + discon_ind1 -> sta_3
                    // sta_7  + discon_ind1 -> sta_3
                    // sta_7  + discon_ind1 -> sta_7 ¥¥¥ÊThis is a weird one, probably need to analyse this more. ¥¥¥
                    streamData->vmsify = false;
                    putnext(q, mp);
                    break;
                case T_DATA_IND:
                    if ( streamData->vmsify ) {
                        VMSifyMessageChain(&mp->b_cont);
                    }
                    putnext(q, mp);
                    break;
                default:
                    putnext(q, mp);
                    break;
            }
            break;
    }
        
    return 0;
}
 
/////////////////////////////////////////////////////////////////////
// Static Declaration Structures
 
static struct module_info gModuleInfo =  
{
    9992,                       // Module Number, only useful for debugging
    "VMSify",                   // Name of module
    0,                          // Minimum data size
    INFPSZ,                     // Maximum data size
    16384,                      // Hi water mark for queue
    4096                        // Lo water mark for queue
};
 
static struct qinit gReadInit = 
{
    VMSifyReadPut,              // Put routine for "incoming" data
    nil,                        // Service routine for "incoming" data
    VMSifyOpen,                 // Our open routine
    VMSifyClose,                // Our close routine
    nil,                        // No admin routine
    &gModuleInfo                // Our module_info
};
 
static struct qinit gWriteInit =
{
    VMSifyWritePut,             // Put routine for client data
    nil,                        // Service routine for client data
    nil,                        // open  field only used in read-side structure
    nil,                        // close field only used in read-side structure
    nil,                        // admin field only used in read-side structure
    &gModuleInfo                // Our module_info
};
 
static struct streamtab theStreamTab = 
{
    &gReadInit,                 // Our read-side qinit structure
    &gWriteInit,                // Our write-side qinit structure
    0,                          // We are not a mux, so set this to nil
    0                           // We are not a mux, so set this to nil
};
 
/////////////////////////////////////////////////////////////////////
// Macintosh-specific Static Structures
 
static struct install_info theInstallInfo =
{
    &theStreamTab,          // Stream Tab pointer
    kOTModIsModule + kOTModUpperIsTPI + kOTModIsFilter,
                            // Tell OT that we are a driver, not a module
    SQLVL_MODULE,           // Synchronization level, module level for the moment
    0,                      // Shared writer list buddy
    0,                      // Open Transport use - always set to 0
    0                       // Flag - always set to 0
};
 
// Prototypes for the exported routines below.
 
extern Boolean InitStreamModule(void *portInfo);
extern void TerminateStreamModule(void);
extern install_info* GetOTInstallInfo();
 
#pragma export list InitStreamModule, TerminateStreamModule, GetOTInstallInfo
 
// Export entry point
 
extern Boolean InitStreamModule(void *portInfo)
    // Initialises the module before the first stream is opened.
    // Should return true if the module has started up correctly.
    //
    // Environment: Always called at SystemTask time.
{   
    #pragma unused(portInfo)
    Boolean result;
    
    OTDebugBreak("VMSify: InitStreamModule");
    
    result = true;
    
    return result;
}
 
extern void TerminateStreamModule(void)
    // Shuts down the module after the last stream has been
    // closed.
    //
    // Environment: Always called at SystemTask time.
{
    // It's an excellent idea to have the following in your code, just to make
    // sure you haven't left any streams open before you quit.  In theory, OT
    // should not call you until the last stream has been closed, but in practice
    // this can happen if you use mi_detach to half-close a stream.
    
    OTAssert("TerminateStreamModule: Streams are still active", gStreamList == nil);
}
 
extern install_info* GetOTInstallInfo()
    // Return pointer to install_info to STREAMS.
{
    return &theInstallInfo;
}