/*++
*******************************************************************************
* Copyright (c) 1990-97 Digi International Inc., All Rights Reserved				*
* 																										*
* This software contains proprietary and confidential information of Digi		*
* International Inc.  By accepting transfer of this copy, Recipient agrees		*
* to retain this software in confidence, to prevent disclosure to others,		*
* and to make no use of this software other than that for which it was			*
* delivered.  This is an unpublished copyrighted work of Digi International	*
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly	*
* prohibited.																						*
* 																										*
* Restricted Rights Legend																		*
*																										*
* Use, duplication, or disclosure by the Government is subject to					*
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in			*
* Technical Data and Computer Software clause at DFARS 252.227-7031 or			*
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -				*
* Restricted Rights at 48 CFR 52.227-19, as applicable.								*
*																										*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343				*
*																										*
*******************************************************************************

Module Name:

   DsAsync.c

Abstract:

   This module is a kernelmode library for interfacing to Digi STREAMS service
   providers. It encapsulates NT and STREAMS-like system calls to relieve the client
   driver of managing these tasks. It is built as a library and linked with the client
   driver.

	This source file includes all APIs which complete asynchronously, i.e. return
	STATUS_PENDING, later calling a completion routine when the operation has completed.

Author:

   Michael Zarns

--*/

#include <ntddk.h>

#include "DsApi.h"
#include "DsIoctl.h"
#include "digilib.h"
#include "DsDef.h"

///////////////////////////////////////////////////////////////////////////////
//
// Typedefs
//
// The following DSL_xxx structures are wrappers around the corresponding 
// structures passed down to a DStreams Service Provider on Async requests.  It 
// contains infomation needed until the request completes.  A pointer to this 
// structure is passed as the Context parameter of IoSetCompletion routine and is 
// returned at completion callback time via the Context value.
//

typedef struct DSL_OPEN 
{
	DSL_OBJECT_TYPE Type;
	PDSL_STREAM pStream;
	PDSL_OPEN_COMPLETION_ROUTINE Complete;
	DS_OPEN Open;
} DSL_OPEN, *PDSL_OPEN;


typedef struct DSL_CLOSE 
{
	DSL_OBJECT_TYPE Type;
	PDSL_STREAM pStream;
	PDSL_CLOSE_COMPLETION_ROUTINE Complete;
	DS_CLOSE Close;
} DSL_CLOSE, *PDSL_CLOSE;


typedef struct DSL_PUTMSG 
{
	DSL_OBJECT_TYPE Type;
	PDSL_STREAM pStream;
	PVOID ClientContext;
	PDSL_PUTMSG_COMPLETION_ROUTINE Complete;
	DS_PUTMSG PutMsg;
} DSL_PUTMSG, *PDSL_PUTMSG;


typedef struct DSL_GETMSG 
{
	DSL_OBJECT_TYPE Type;
	PDSL_STREAM pStream;
	PVOID ClientContext;
	PDSL_GETMSG_COMPLETION_ROUTINE Complete;
	DS_GETMSG GetMsg;
} DSL_GETMSG, *PDSL_GETMSG;


///////////////////////////////////////////////////////////////////////////////
//
// Internal Routines
//
// All internal routines are named with a lowercase "ds" prefix.
// 

STATIC INLINE NTSTATUS
dsAsyncIoRequest(
	LONG Ioctl,
	PDEVICE_OBJECT pDeviceObject,
	PVOID pDataBuffer,
	ULONG DataBufferLength,
	PIO_COMPLETION_ROUTINE CompletionRoutine,
	PVOID CompletionContext )
/*++

Description:

	This routine builds and sends as asynchronous DeviceIoControl request.

Parameters:

	Ioctl
		Specifies the IOCTL_DS_xxx to be called.

	pDeviceObject
		Points to the DeviceObject which is the target of this request.

	pInputBuffer
		Points to the data buffer to be passed into the request.  Assumes ioctl is
		METHOD_BUFFERED, and as such sets the SystemBuffer to point to DataBuffer.  
		Note: assumes inputbuffer == outputbuffer since there's only one buffer.

	DataBufferLength
		Length of data buffer

	CompletionRoutine
		This routine is called when the IRP request has completed.

	CompletionContext
		The Context value to be passed into the completion routine.  This should
		be a pointer to the DS_xxx_WRAPPER struct for this request.

Return Value:

	STATUS_PENDING,
	STATUS_INSUFFICIENT_RESOURCES

Comments:

	If an allocation failure occurs, STATUS_INSUFFICIENT_RESOURCES will be 
	returned and the completion routine will *NOT* be called.  Otherwise, 
	STATUS_PENDING will be returned and the completion routine will be called
	when the Irp completes.

--*/
{
	PIRP pIrp;
	NTSTATUS Status;
	PIO_STACK_LOCATION IrpSp;

	ASSERT( pDeviceObject );
	// If and only if buffers exist, lengths should be nonzero.
	ASSERT( ( pDataBuffer && DataBufferLength ) );

	pIrp = IoAllocateIrp( pDeviceObject->StackSize, FALSE );
	if ( !pIrp )
		// Failure!  CompletionRoutine will not be called.
		return( STATUS_INSUFFICIENT_RESOURCES );

	// Alloc'd successfully!  Init the Irp.
	pIrp->RequestorMode = KernelMode;
	IrpSp = IoGetNextIrpStackLocation( pIrp );
	IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
	IrpSp->Parameters.DeviceIoControl.IoControlCode = Ioctl;
	IrpSp->Parameters.DeviceIoControl.InputBufferLength = DataBufferLength;
	IrpSp->Parameters.DeviceIoControl.OutputBufferLength = DataBufferLength;
	pIrp->AssociatedIrp.SystemBuffer = pDataBuffer;
	IoSetCompletionRoutine( pIrp, CompletionRoutine, 
	                     CompletionContext, TRUE, TRUE, TRUE );

	// Send it!
	Status = IoCallDriver( pDeviceObject, pIrp );

	// Override return status.  Always return pending if the Irp was passed down.
	// This tells the caller his CompletionRoutine will be called.
	Status = STATUS_PENDING;
	return( Status );
}


///////////////////////////////////////////////////////////////////////////////
//
// The API Routines and their corresponding completion routines.  
//
// All API routines (exposed to the public, available for general use) are
// named with the mixedcase "Ds" prefix.  Their completion routines are
// internal, and therefore have the lowercase "ds" prefix.
//
// Note: the following routines only have sync versions and are *not* 
// implemented here:
// 	DsDeviceOpen, DsDeviceClose, DsIoctl,
//    DsPush, DsLink, and DsPurge.
//
STATIC NTSTATUS
dsOpenComplete(
	PDEVICE_OBJECT pDeviceObject, 
	PIRP pIrp, 
	PVOID Context )
/*++

Description:

	This is the completion routine for the IOCTL_DS_OPEN request sent by 
	DsOpenEx.  This routine is called by the IO subsystem to process the 
	completed IRP.  

Parameters:

	pDeviceObject
		The device object to which this request was sent.  Ignored.

	pIrp
		The request was passed in this Irp.

	Context
		Pointer to the DSL_OPEN struct alloc'd in DsOpenEx.

Return Value:

	STATUS_MORE_PROCESSING_REQUIRED to keep the I/O manager from further
	processing this Irp.

--*/
{
	const PDSL_OPEN pOpen = Context;
	PDSL_STREAM pStream;
	NTSTATUS IoStatus;
	ULONG ErrorCode;

	ASSERT( pOpen && pOpen->Type == DSL_TYPE_OPEN );

	pStream = pOpen->pStream;	
	ASSERT( pStream &&
	        pStream->Type == DSL_TYPE_STREAM && 
	        pStream->State == DSL_STATE_OPENING );
	
	pStream->hProviderStream = pOpen->Open.hStream;

	// Translate "STATUS_SUCCESS with Error" to "STATUS_UNSUCCESSFUL"
	if ( pIrp->IoStatus.Status == STATUS_SUCCESS && pOpen->Open.ErrorCode )
		IoStatus = STATUS_UNSUCCESSFUL;
	else
		IoStatus = pIrp->IoStatus.Status;
	ErrorCode = pOpen->Open.ErrorCode;
	
	IoSetCompletionRoutine( pIrp, NULL, NULL, FALSE, FALSE, FALSE );
	IoFreeIrp( pIrp );

	if ( NT_SUCCESS( IoStatus ) )
	{
		// Move the Stream State to READY.  This indicates the stream is open
		// and ready to accept other actions.  Normally we'd use the stream
		// lock to protect this state change but we don't need to here because 
		// nobody knows about the stream until we call this CompletionRoutine.
		pStream->State = DSL_STATE_READY;
		// Call the client driver's completion routine
		pOpen->Complete( pStream->hOwner, (HANDLE)pStream, ErrorCode, IoStatus );
		dsDereferenceStream( pStream, FALSE );
	}
	else
	{
		// Cleanup
		pOpen->Complete( pStream->hOwner, NULL, ErrorCode, IoStatus );
		dsDereferenceStream( pStream, TRUE );
	}
	DigiFreeMem( pOpen );
	return( STATUS_MORE_PROCESSING_REQUIRED );
}


NTSTATUS
DsOpenEx( 
	IN HANDLE hDevice,
	IN PCSZ   pszStreamDeviceName,
	IN HANDLE hOwner,
	IN PVOID  pUserData OPTIONAL,
	IN ULONG  cbUserData,
	IN PDSL_OPEN_COMPLETION_ROUTINE OpenComplete )
/*++

Description:

	This routine opens a Stream via a DSTREAM API IOCTL_DS_OPEN request on
	behalf of the caller. This request completes asynchronously, returning
	a stream handle in the completion routine which is a required parameter 
	in other library routines.

Parameters:

	hDevice
		Handle for the DSTREAMS service provider device returned from DsOpenDevice.
		The open request is sent to this device.

	pszStreamDeviceName
		Pointer to a null terminated ASCII string containing the name of the STREAMS
		device to open, of the form "X250".

	hOwner
		Handle identifying the owner of this Stream. Opaque to the library. It is 
		passed back unaltered into all callback routines for this Stream.

	pUserData
		Optional user data to be passed in the data field of the DXB Open request.  Use
		is defined by the firmware entity being opened.  This data is copied into the 
		open request and may be freed immediately upon return from DsOpenEx.

	cbUserData
		Count of bytes of user data to pass to firmware.

	OpenComplete
		This routine will be called when the request has completed, but only if
		return STATUS_PENDING.

Return Value:

	STATUS_PENDING
	STATUS_INVALID_PARAMETER_X where X identifies the invalid paramter
	STATUS_INSUFFICIENT_RESOURCES

Comments:

	A Stream must be opened with DsOpen before calling DsClose, DsPutMsg,
	DsGetMsg, DsPush, DsLink, or DsIoctl. A Stream is closed by calling DsClose.
	The handle returned in phStream is used as a parameter to identify the Stream
	to operate on in these calls.

	The hOwner parameter is returned in the PutMsg and GetMsg completion
	routines. The client driver should set hOwner to something meaningful it
	can use in it's putmsg and getmsg completion routines. Typically, this
	would be some context such as pAdapter.

--*/
{
	const PDSL_DEVICE pDevice = hDevice;
	PDSL_STREAM pStream;
	NTSTATUS  Status;
	PDSL_OPEN pOpen;
	ULONG Length, i;
	KIRQL OldIrql;

	// validate input parameters
	if ( !pDevice || pDevice->Type != DSL_TYPE_DEVICE )
		return STATUS_INVALID_PARAMETER_1;
	if ( !pszStreamDeviceName )
		return STATUS_INVALID_PARAMETER_2;
	if ( (pUserData == NULL) ^ (cbUserData == 0) )
		return STATUS_INVALID_PARAMETER_4;
	if ( !OpenComplete )
		return STATUS_INVALID_PARAMETER_6;

	if ( pDevice->State != DSL_STATE_READY )
		return STATUS_DSAPI_DEVICESTATE;

	// Find length of input string
	for ( Length = 0; *(pszStreamDeviceName+Length); Length++ )
		; // Do nothing
	if ( !Length || Length >= DS_DEVICENAME_SIZE )	
		return STATUS_INVALID_PARAMETER_2;

	// Alloc the stream object
	pStream = DigiAllocMem( NonPagedPool, sizeof( DSL_STREAM ) );
	if ( !pStream )
		goto AllocStreamFailed;

	// Initialize stream object
	RtlZeroMemory( pStream, sizeof( DSL_STREAM ) );
	pStream->Type = DSL_TYPE_STREAM;
	pStream->State = DSL_STATE_OPENING;
	pStream->pDevice = pDevice;
	pStream->hOwner = hOwner;
	KeInitializeSpinLock( &pStream->Lock );

	// Put this new stream object on the stream list.
	KeAcquireSpinLock( &gStreamList.Lock, &OldIrql );
	InsertTailList( &gStreamList.Head, &pStream->ListEntry );
	KeReleaseSpinLock( &gStreamList.Lock, OldIrql );

	// Indicate a request is outstanding on this stream.  
	Status = dsReferenceStream( pStream, FALSE );
	ASSERT( Status == STATUS_SUCCESS );

	// Alloc and init the Open request struct
	pOpen = DigiAllocMem( NonPagedPool, sizeof *pOpen + cbUserData );
	if ( pOpen == NULL )
		goto AllocOpenFailed;

	RtlZeroMemory( pOpen, sizeof *pOpen );	// Don't bother to zero userdata
	pOpen->Type = DSL_TYPE_OPEN;
	pOpen->Complete = OpenComplete;
	pOpen->pStream = pStream;

	// Initialize DS_OPEN struct

	for ( i = 0; i < Length; i++ )
		pOpen->Open.DeviceName[i] = *(pszStreamDeviceName+i);

	// Setup user data block, if present
	if ( cbUserData )
	{
		pOpen->Open.pProviderData = pOpen->Open.pUserData = pOpen + sizeof *pOpen;
		pOpen->Open.cbUserData    = cbUserData;
		RtlCopyMemory( pOpen->Open.pProviderData, pUserData, cbUserData );
	}

	Status = dsAsyncIoRequest( IOCTL_DS_OPEN, pDevice->DeviceObject,
	                           &pOpen->Open, sizeof( DS_OPEN ),
	                           dsOpenComplete, pOpen );

	// dsAsyncIoRequest returns error indicating completionroutine won't be
	// called, or Pending - indicating completionroutine will be called.
	ASSERT( Status == STATUS_PENDING || Status == STATUS_INSUFFICIENT_RESOURCES );
	if ( !NT_ERROR( Status ) )
		return( STATUS_PENDING );

AllocOpenFailed:;
	// Remove the stream from streamlist and free it.
	dsDereferenceStream( pStream, TRUE );

AllocStreamFailed:;
	return( STATUS_INSUFFICIENT_RESOURCES );
}


STATIC NTSTATUS
dsCloseComplete(
	PDEVICE_OBJECT pDeviceObject, 
	PIRP pIrp, 
	PVOID Context )
/*++

Description:

	This is the completion routine for the IOCTL_DS_CLOSE request sent by 
	DsCloseEx.  This routine is called by the IO subsystem to process the 
	completed IRP.  

Parameters:

	pDeviceObject
		The device object to which this request was sent.  Ignored.

	pIrp
		The request was passed in this Irp.

	Context
		Pointer to the DSL_CLOSE struct alloc'd in DsCloseEx.

Return Value:

	STATUS_MORE_PROCESSING_REQUIRED to keep the I/O manager from further
	processing this Irp.

--*/
{
	const PDSL_CLOSE pClose = Context;
	const NTSTATUS IoStatus = pIrp->IoStatus.Status;
	PDSL_STREAM pStream;

	ASSERT( pClose && pClose->Type == DSL_TYPE_CLOSE );

	pStream = pClose->pStream;
	ASSERT( pStream && pStream->Type == DSL_TYPE_STREAM && 
	        pStream->State == DSL_STATE_CLOSING );
	ASSERT( pStream->pDevice &&
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );
	
	IoSetCompletionRoutine( pIrp, NULL, NULL, FALSE, FALSE, FALSE );
	IoFreeIrp( pIrp );

	// Call the client driver's completion routine
	pClose->Complete( pStream->hOwner, IoStatus );
	DigiFreeMem( pClose );

	if ( !NT_SUCCESS( IoStatus ) )
	{
		// Request Failed!  Reset state to READY, and decrement reference count
		KIRQL OldIrql;
		KeAcquireSpinLock( &pStream->Lock, &OldIrql );
		pStream->State = DSL_STATE_READY;
		KeReleaseSpinLock( &pStream->Lock, OldIrql );
		dsDereferenceStream( pStream, FALSE );
	}
	else
	{
		// Close completed successfully.  Free resources alloc'd for this stream
		dsDereferenceStream( pStream, TRUE );
	}
	return( STATUS_MORE_PROCESSING_REQUIRED );
}


NTSTATUS
DsCloseEx(
	IN HANDLE hStream,
	IN PDSL_CLOSE_COMPLETION_ROUTINE CloseComplete )
/*++

Description:

	This routine closes a Stream via a DSTREAM API IOCTL_DS_CLOSE request on
	behalf of the caller.  This request completes asynchronously.

Parameters:

	hStream
		Handle received from DSOpen identifying the stream to close.

	Complete
		This routine will be called when the request has completed, but only if
		return STATUS_PENDING.

Return Values:

	STATUS_PENDING
	STATUS_INVALID_PARAMETER_X
	STATUS_INSUFFICIENT_RESOURCES

--*/
{
	const PDSL_STREAM pStream = hStream;
	PDSL_DEVICE pDevice;
	NTSTATUS Status;
	PDSL_CLOSE pClose;
	KIRQL OldIrql;

	// validate input parameters
	if ( !pStream )
		return ( STATUS_INVALID_PARAMETER_1 );
	if ( !CloseComplete )
		return( STATUS_INVALID_PARAMETER_2 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, TRUE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	pDevice = pStream->pDevice;
	ASSERT( pDevice && pDevice->Type == DSL_TYPE_DEVICE &&
	        pDevice->State == DSL_STATE_READY );

	// Alloc and init the Close request
	pClose = DigiAllocMem( NonPagedPool, sizeof( DSL_CLOSE ) );
	if ( !pClose ) 
		goto AllocCloseFailed;

	RtlZeroMemory( pClose, sizeof( DSL_CLOSE ) );
	pClose->Type = DSL_TYPE_CLOSE;
	pClose->pStream = pStream;
	pClose->Complete = CloseComplete;
	pClose->Close.hStream = pStream->hProviderStream;

	pStream->State = DSL_STATE_CLOSING;

	Status = dsAsyncIoRequest( IOCTL_DS_CLOSE, pDevice->DeviceObject,
	                           &pClose->Close, sizeof( DS_CLOSE ),
	                           dsCloseComplete, pClose );

	// dsAsyncIoRequest returns error indicating completionroutine won't be
	// called, or Pending - indicating completionroutine will be called.
	ASSERT( Status == STATUS_PENDING || Status == STATUS_INSUFFICIENT_RESOURCES );
	if ( !NT_ERROR( Status ) )
		return( STATUS_PENDING );

	// Allocation failure in dsAsyncIoRequest
	ASSERT( Status == STATUS_INSUFFICIENT_RESOURCES );
	KeAcquireSpinLock( &pStream->Lock, &OldIrql );
	pStream->State = DSL_STATE_READY;
	KeReleaseSpinLock( &pStream->Lock, OldIrql );

AllocCloseFailed:;
	dsDereferenceStream( pStream, FALSE );
	return( STATUS_INSUFFICIENT_RESOURCES );
}


STATIC NTSTATUS
dsPutMsgComplete(
	PDEVICE_OBJECT pDeviceObject, 
	PIRP pIrp, 
	PVOID Context )
/*++

Description:

	This is the completion routine for the IOCTL_DS_PUTMSG request sent by 
	DsPutMsgEx.  This routine is called by the IO subsystem to process the 
	completed IRP.  

Parameters:

	pDeviceObject
		The device object to which this request was sent.  Ignored.

	pIrp
		The request was passed in this Irp.

	Context
		Pointer to the DSL_PUTMSG struct alloc'd in DsPutMsgEx.

Return Value:

	STATUS_MORE_PROCESSING_REQUIRED to keep the I/O manager from further
	processing this Irp.

--*/
{
	const PDSL_PUTMSG pPutMsg = Context;
	const NTSTATUS IoStatus = pIrp->IoStatus.Status;
	PDSL_STREAM pStream;

	ASSERT( pPutMsg && pPutMsg->Type == DSL_TYPE_PUTMSG );

	pStream = pPutMsg->pStream;
	ASSERT( pPutMsg->pStream && pPutMsg->pStream->Type == DSL_TYPE_STREAM );
	ASSERT( pPutMsg->pStream->State == DSL_STATE_READY ||
	        pPutMsg->pStream->State == DSL_STATE_CLOSING ) ;
	ASSERT( pStream->pDevice && 
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
			  pStream->pDevice->State == DSL_STATE_READY );

	IoSetCompletionRoutine( pIrp, NULL, NULL, FALSE, FALSE, FALSE );
	IoFreeIrp( pIrp );

	// Call the client driver's completion routine
	pPutMsg->Complete( pStream->hOwner, pPutMsg->ClientContext, 
	                   pPutMsg->PutMsg.pControl,
	                   pPutMsg->PutMsg.cbControl,
	                   pPutMsg->PutMsg.pData,
	                   pPutMsg->PutMsg.cbData,
	                   IoStatus );

	DigiFreeMem( pPutMsg );
	dsDereferenceStream( pStream, FALSE );
	return( STATUS_MORE_PROCESSING_REQUIRED );
}


NTSTATUS
DsPutMsgEx(
	IN HANDLE hStream,
	IN PVOID pControlBuffer OPTIONAL,	// Optional
	IN ULONG ControlLength OPTIONAL,		// Must be zero if pControlBuffer is NULL
	IN PVOID pDataBuffer OPTIONAL,		// Optional.  
	IN ULONG DataLength OPTIONAL,			// Must be zero if pDataBuffer is NULL
	IN HANDLE Context,
	IN DS_PRIORITY Priority,
	IN PDSL_PUTMSG_COMPLETION_ROUTINE PutMsgComplete )
/*++

Description:

	This routine makes an asynchronous DSTREAM API IOCTL_DS_PUTMSG request on 
	behalf of the caller.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream on which to send the
		message.

	pControlBuffer
		Pointer to the control buffer.  This buffer is needed only while in this
		routine and may be freed immediately upon return.

	ControlLength
		Length of control buffer.

	pDataBuffer
		Pointer to the data buffer. This buffer is owned by the library until 
		either this routine returns STATUS_INSUFFICIENT_RESOURCES or the 
		completion routine is called.

	DataLength
		Length of data buffer.

	Context
		Client contents passed unaltered into the completion routine.

	Priority
		DS_PRIORITY_XXX level.  Must be LOW if data-only message.

	GetMsgComplete
		This routine will be called when the asynchronous request completes -
		but only if returning STATUS_PENDING.

Return Value:

	STATUS_PENDING
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_INVALID_PARAMETER_x

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS  Status;
	PDSL_PUTMSG pPutMsg;

	// Validate input parameters
	if ( pStream == NULL || pStream->Type != DSL_TYPE_STREAM )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( ( pControlBuffer == NULL ) ^ ( ControlLength == 0 ) )
		return( STATUS_INVALID_PARAMETER_2 );
	if ( ( pDataBuffer == NULL ) ^ ( DataLength == 0 ) )
		return( STATUS_INVALID_PARAMETER_4 );
	if ( pControlBuffer == NULL && Priority != DS_PRIORITY_LOW )
		return( STATUS_INVALID_PARAMETER_7 );
	if ( PutMsgComplete == NULL )
		return( STATUS_INVALID_PARAMETER_8 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice && 
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	// Alloc a block big enough to contain both the PutMsg request *AND* the 
	// PutMsg's control buffer.  
	pPutMsg = DigiAllocMem( NonPagedPool, sizeof( DSL_PUTMSG ) + ControlLength );
	if ( !pPutMsg )
		goto AllocPutMsgFailed;
	RtlZeroMemory( pPutMsg, sizeof( DSL_PUTMSG ) );
	if ( ControlLength != 0 )
		RtlCopyMemory( (PUCHAR)pPutMsg + sizeof( DSL_PUTMSG ), pControlBuffer, ControlLength );

	pPutMsg->Type = DSL_TYPE_PUTMSG;
	pPutMsg->pStream = pStream;
	pPutMsg->ClientContext = Context;
	pPutMsg->Complete = PutMsgComplete;

	pPutMsg->PutMsg.hStream = pStream->hProviderStream;
	pPutMsg->PutMsg.pControl = ControlLength == 0 ? NULL : (PVOID)( (PUCHAR)pPutMsg + sizeof( DSL_PUTMSG ) );
	pPutMsg->PutMsg.cbControl = ControlLength;
	pPutMsg->PutMsg.pData = DataLength ? pDataBuffer : NULL;
	pPutMsg->PutMsg.cbData = DataLength;
	pPutMsg->PutMsg.Priority = Priority;

	Status = dsAsyncIoRequest( IOCTL_DS_PUTMSG, 
	                           pStream->pDevice->DeviceObject,
	                           &pPutMsg->PutMsg, sizeof( DS_PUTMSG ),
	                           dsPutMsgComplete, pPutMsg );

	// dsAsyncIoRequest returns error indicating completionroutine won't be
	// called, or Pending - indicating completionroutine will be called.
	ASSERT( Status == STATUS_PENDING || Status == STATUS_INSUFFICIENT_RESOURCES );
	if ( !NT_ERROR( Status ) )
		return( STATUS_PENDING );

	// Failed, dsOpenComplete callback won't be called.  Cleanup.
	DigiFreeMem( pPutMsg );

AllocPutMsgFailed:	
	dsDereferenceStream( pStream, FALSE );
	return( STATUS_INSUFFICIENT_RESOURCES );
}


STATIC NTSTATUS
dsGetMsgComplete(
	PDEVICE_OBJECT pDeviceObject, 
	PIRP pIrp, 
	PVOID Context )
/*++

Description:

	This is the completion routine for the IOCTL_DS_GETMSG request sent by 
	DsGetMsgEx.  This routine is called by the IO subsystem to process the 
	completed IRP.  

Parameters:

	pDeviceObject
		The device object to which this request was sent.  Ignored.

	pIrp
		The request was passed in this Irp.

	Context
		Pointer to the DSL_GETMSG struct alloc'd in DsGetMsgEx.

Return Value:

	STATUS_MORE_PROCESSING_REQUIRED to keep the I/O manager from further
	processing this Irp.

--*/
{
	const PDSL_GETMSG pGetMsg = Context;
	const NTSTATUS IoStatus = pIrp->IoStatus.Status;
	PDSL_STREAM pStream;

	ASSERT( pGetMsg && pGetMsg->Type == DSL_TYPE_GETMSG );

	pStream = pGetMsg->pStream;
	ASSERT( pGetMsg->pStream && pGetMsg->pStream->Type == DSL_TYPE_STREAM );
	ASSERT( pGetMsg->pStream->State == DSL_STATE_READY ||
	        pGetMsg->pStream->State == DSL_STATE_CLOSING ) ;
	ASSERT( pStream->pDevice && 
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
			  pStream->pDevice->State == DSL_STATE_READY );

	IoSetCompletionRoutine( pIrp, NULL, NULL, FALSE, FALSE, FALSE );
	IoFreeIrp( pIrp );

	// Call the client driver's completion routine
	pGetMsg->Complete( pStream->hOwner, pGetMsg->ClientContext, 
	                   pGetMsg->GetMsg.cbControl ? pGetMsg->GetMsg.pControl : NULL, 
	                   pGetMsg->GetMsg.cbControl,
	                   pGetMsg->GetMsg.pData,
	                   pGetMsg->GetMsg.cbData,
	                   IoStatus );
	DigiFreeMem( pGetMsg );

	dsDereferenceStream( pStream, FALSE );
	return( STATUS_MORE_PROCESSING_REQUIRED );
}


NTSTATUS
DsGetMsgEx(
	IN HANDLE hStream,
	IN ULONG ControlMaxLength OPTIONAL,
	IN PVOID pDataBuffer OPTIONAL,
	IN ULONG DataMaxLength OPTIONAL,
	IN HANDLE Context,
	IN DS_PRIORITY Priority,
	IN PDSL_GETMSG_COMPLETION_ROUTINE GetMsgComplete )
/*++

Description:

	This routine makes an asynchronous DSTREAM API IOCTL_DS_GETMSG request on 
	behalf of the caller.  Althought both Data and Control portions are optional, one
	of the two must be present.

Parameters:

	hStream
		Handle returned from DsOpen identifying the Stream on which to send the
		request.

	ControlMaxLength
		Length of control buffer to alloc.  A control buffer of this size will
		be alloc'd, filled with incoming GetMsg control info and passed back in
		completion routine.  

	pDataBuffer
		Pointer to the data buffer. This buffer is owned by the library until 
		either this routine returns STATUS_INSUFFICIENT_RESOURCES or the 
		completion routine is called.

	DataMaxLength
		Length of data buffer.

	Context
		Client contents passed unaltered into the completion routine.

	Priority
		Specifies the minimum DS_PRIORITY_XXX level incoming message required to 
		complete this request.

	GetMsgComplete
		This routine will be called when the asynchronous request completes -
		but only if returning STATUS_PENDING.

Return Value:

	STATUS_PENDING
	STATUS_INSUFFICIENT_RESOURCES
	STATUS_INVALID_PARAMETER_x

--*/
{
	const PDSL_STREAM pStream = hStream;
	NTSTATUS  Status;
	PDSL_GETMSG pGetMsg;

	// Validate input parameters
	if ( pStream == NULL || pStream->Type != DSL_TYPE_STREAM )
		return( STATUS_INVALID_PARAMETER_1 );
	if ( ControlMaxLength == 0 && pDataBuffer == NULL )
		return( STATUS_INVALID_PARAMETER_2 );
	if ( ( pDataBuffer == NULL ) ^ ( DataMaxLength == 0 ) )
		return( STATUS_INVALID_PARAMETER_3 );
	if ( ControlMaxLength == 0 && Priority != DS_PRIORITY_LOW )
		return( STATUS_INVALID_PARAMETER_6 );
	if ( Priority > DS_PRIORITY_MAX )
		return( STATUS_INVALID_PARAMETER_6 );
	if ( GetMsgComplete == NULL )
		return( STATUS_INVALID_PARAMETER_7 );

	if ( !NT_SUCCESS( dsReferenceStream( pStream, FALSE ) ) )
		return( STATUS_DSAPI_STREAMSTATE );

	ASSERT( pStream->pDevice && 
	        pStream->pDevice->Type == DSL_TYPE_DEVICE &&
	        pStream->pDevice->State == DSL_STATE_READY );

	// Alloc a block big enough to contain both the GetMsg request *AND* the 
	// GetMsg's control buffer.  
	pGetMsg = DigiAllocMem( NonPagedPool, sizeof(DSL_GETMSG) + ControlMaxLength );
	if ( !pGetMsg )
		goto AllocGetMsgFailed;
	RtlZeroMemory( pGetMsg, sizeof( DSL_GETMSG ) + ControlMaxLength );

	pGetMsg->Type = DSL_TYPE_GETMSG;
	pGetMsg->pStream = pStream;
	pGetMsg->ClientContext = Context;
	pGetMsg->Complete = GetMsgComplete;

	pGetMsg->GetMsg.hStream = pStream->hProviderStream;
	pGetMsg->GetMsg.pControl = ControlMaxLength == 0 ? NULL : (PVOID)( (PUCHAR)pGetMsg + sizeof( DSL_GETMSG ) );
	pGetMsg->GetMsg.cbMaxControl = ControlMaxLength;
	pGetMsg->GetMsg.pData = pDataBuffer;
	pGetMsg->GetMsg.cbMaxData = DataMaxLength;
	pGetMsg->GetMsg.Priority = Priority;

	Status = dsAsyncIoRequest( IOCTL_DS_GETMSG, 
	                           pStream->pDevice->DeviceObject,
	                           &pGetMsg->GetMsg, sizeof( DS_GETMSG ),
	                           dsGetMsgComplete, pGetMsg );

	// dsAsyncIoRequest returns error indicating completionroutine won't be
	// called, or Pending - indicating completionroutine will be called.
	ASSERT( Status == STATUS_PENDING || Status == STATUS_INSUFFICIENT_RESOURCES );
	if ( !NT_ERROR( Status ) )
		return( STATUS_PENDING );

	// Failed, dsOpenComplete callback won't be called.  Cleanup.
	DigiFreeMem( pGetMsg );

AllocGetMsgFailed:	
	dsDereferenceStream( pStream, FALSE );
	return( STATUS_INSUFFICIENT_RESOURCES );
}
