Files
epics-base/src/ca/nciu.cpp
Michael Abbott a0c0c55347 Client side support for automatic resizing arrays.
In this commit the channel access client is modified to allow a zero
size request in a caget or camonitor call.  This is passed through to
the corresponding CA_PROTO_READ_NOTIFY or CA_PROTO_EVENT__ADD message,
but only if the minor version of the protocol is >=13.

This commit also adds a new protocol test, CA_V413(), which detects a
server which claims to understand the new zero length request.

Finally, this commit prepares for a subtle change in the CA protocol.
A request for a zero length subscription or data request, which will
only be made for protocol version 4.13 and above, is interpreted as a
request for autosized data, where the intrinsic dynamic length of the
requested waveform should be returned.

With the synchronous ca_array_get() call there is no mechanism available
to pass back the size of the retrieved data if it is different from
the requested count, so the simplest thing is to ensure we don't
request autosizing data through this api.
2010-08-11 16:22:54 -05:00

629 lines
18 KiB
C++

/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
*
* L O S A L A M O S
* Los Alamos National Laboratory
* Los Alamos, New Mexico 87545
*
* Copyright, 1986, The Regents of the University of California.
*
* Author: Jeff Hill
*
*/
#include <new>
#include <string>
#include <stdexcept>
#define epicsAssertAuthor "Jeff Hill johill@lanl.gov"
#include "epicsAlgorithm.h"
#include "errlog.h"
#define epicsExportSharedSymbols
#include "iocinf.h"
#include "cac.h"
#include "osiWireFormat.h"
#include "udpiiu.h"
#include "virtualCircuit.h"
#include "cadef.h"
#include "db_access.h" // for INVALID_DB_REQ
#include "noopiiu.h"
nciu::nciu ( cac & cacIn, netiiu & iiuIn, cacChannelNotify & chanIn,
const char *pNameIn, cacChannel::priLev pri ) :
cacChannel ( chanIn ),
cacCtx ( cacIn ),
piiu ( & iiuIn ),
sid ( UINT_MAX ),
count ( 0 ),
retry ( 0u ),
nameLength ( 0u ),
typeCode ( USHRT_MAX ),
priority ( static_cast <ca_uint8_t> ( pri ) )
{
size_t nameLengthTmp = strlen ( pNameIn ) + 1;
// second constraint is imposed by size field in protocol header
if ( nameLengthTmp > MAX_UDP_SEND - sizeof ( caHdr ) || nameLengthTmp > USHRT_MAX ) {
throw cacChannel::badString ();
}
if ( pri > 0xff ) {
throw cacChannel::badPriority ();
}
this->nameLength = static_cast <unsigned short> ( nameLengthTmp );
this->pNameStr = new char [ this->nameLength ];
strcpy ( this->pNameStr, pNameIn );
}
nciu::~nciu ()
{
delete [] this->pNameStr;
}
// channels are created by the user, and only destroyed by the user
// using this routine
void nciu::destroy (
epicsGuard < epicsMutex > & guard )
{
while ( baseNMIU * pNetIO = this->eventq.first () ) {
bool success = this->cacCtx.destroyIO ( guard, pNetIO->getId (), *this );
assert ( success );
}
// if the claim reply has not returned yet then we will issue
// the clear channel request to the server when the claim reply
// arrives and there is no matching nciu in the client
if ( this->channelNode::isInstalledInServer ( guard ) ) {
this->getPIIU(guard)->clearChannelRequest (
guard, this->sid, this->id );
}
this->piiu->uninstallChan ( guard, *this );
this->cacCtx.destroyChannel ( guard, *this );
}
void * nciu::operator new ( size_t ) // X aCC 361
{
// The HPUX compiler seems to require this even though no code
// calls it directly
throw std::logic_error ( "why is the compiler calling private operator new" );
}
void nciu::operator delete ( void * )
{
// Visual C++ .net appears to require operator delete if
// placement operator delete is defined? I smell a ms rat
// because if I declare placement new and delete, but
// comment out the placement delete definition there are
// no undefined symbols.
errlogPrintf ( "%s:%d this compiler is confused about placement delete - memory was probably leaked",
__FILE__, __LINE__ );
}
void nciu::initiateConnect (
epicsGuard < epicsMutex > & guard )
{
this->cacCtx.initiateConnect ( guard, *this, this->piiu );
}
void nciu::connect ( unsigned nativeType,
unsigned nativeCount, unsigned sidIn,
epicsGuard < epicsMutex > & /* cbGuard */,
epicsGuard < epicsMutex > & guard )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
if ( ! dbf_type_is_valid ( nativeType ) ) {
throw std::logic_error ( "Ignored conn resp with bad native data type" );
}
this->typeCode = static_cast < unsigned short > ( nativeType );
this->count = nativeCount;
this->sid = sidIn;
/*
* if less than v4.1 then the server will never
* send access rights and there will always be access
*/
bool v41Ok = this->piiu->ca_v41_ok ( guard );
if ( ! v41Ok ) {
this->accessRightState.setReadPermit();
this->accessRightState.setWritePermit();
}
/*
* if less than v4.1 then the server will never
* send access rights and we know that there
* will always be access and also need to call
* their call back here
*/
if ( ! v41Ok ) {
this->notify().accessRightsNotify (
guard, this->accessRightState );
}
// channel uninstal routine grabs the callback lock so
// a channel will not be deleted while a call back is
// in progress
//
// the callback lock is also taken when a channel
// disconnects to prevent a race condition with the
// code below - ie we hold the callback lock here
// so a chanel cant be destroyed out from under us.
this->notify().connectNotify ( guard );
}
void nciu::unresponsiveCircuitNotify (
epicsGuard < epicsMutex > & cbGuard,
epicsGuard < epicsMutex > & guard )
{
ioid tmpId = this->getId ();
cac & caRefTmp = this->cacCtx;
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
this->cacCtx.disconnectAllIO ( cbGuard, guard,
*this, this->eventq );
this->notify().disconnectNotify ( guard );
// if they destroy the channel in their disconnect
// handler then we have to be very careful to not
// touch this object if it has been destroyed
nciu * pChan = caRefTmp.lookupChannel ( guard, tmpId );
if ( pChan ) {
caAccessRights noRights;
pChan->notify().accessRightsNotify ( guard, noRights );
// likewise, they might destroy the channel in their access rights
// handler so we have to be very careful to not touch this
// object from here on down
}
}
void nciu::setServerAddressUnknown ( netiiu & newiiu,
epicsGuard < epicsMutex > & guard )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
this->piiu = & newiiu;
this->retry = 0;
this->typeCode = USHRT_MAX;
this->count = 0u;
this->sid = UINT_MAX;
this->accessRightState.clrReadPermit();
this->accessRightState.clrWritePermit();
}
void nciu::accessRightsStateChange (
const caAccessRights & arIn, epicsGuard < epicsMutex > & /* cbGuard */,
epicsGuard < epicsMutex > & guard )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
this->accessRightState = arIn;
//
// the channel delete routine takes the call back lock so
// that this will not be called when the channel is being
// deleted.
//
this->notify().accessRightsNotify ( guard, this->accessRightState );
}
/*
* nciu::searchMsg ()
*/
bool nciu::searchMsg ( epicsGuard < epicsMutex > & guard )
{
bool success = this->piiu->searchMsg (
guard, this->getId (), this->pNameStr, this->nameLength );
if ( success ) {
if ( this->retry < UINT_MAX ) {
this->retry++;
}
}
return success;
}
const char *nciu::pName (
epicsGuard < epicsMutex > & guard ) const throw ()
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
return this->pNameStr;
}
unsigned nciu::getName (
epicsGuard < epicsMutex > &,
char * pBuf, unsigned bufLen ) const throw ()
{
if ( bufLen == 0u ) {
return 0u;
}
if ( this->nameLength < bufLen ) {
strcpy ( pBuf, this->pNameStr );
return this->nameLength;
}
else {
unsigned reducedSize = bufLen - 1u;
strncpy ( pBuf, this->pNameStr, bufLen );
pBuf[reducedSize] = '\0';
return reducedSize;
}
}
unsigned nciu::nameLen (
epicsGuard < epicsMutex > & guard ) const
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
return this->nameLength;
}
unsigned nciu::requestMessageBytesPending (
epicsGuard < epicsMutex > & guard )
{
return piiu->requestMessageBytesPending ( guard );
}
void nciu::flush (
epicsGuard < epicsMutex > & guard )
{
piiu->flush ( guard );
}
cacChannel::ioStatus nciu::read (
epicsGuard < epicsMutex > & guard,
unsigned type, arrayElementCount countIn,
cacReadNotify &notify, ioid *pId )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
if ( ! this->connected ( guard ) ) {
throw cacChannel::notConnected ();
}
if ( ! this->accessRightState.readPermit () ) {
throw cacChannel::noReadAccess ();
}
if ( countIn > this->count ) {
throw cacChannel::outOfBounds ();
}
//
// fail out if their arguments are invalid
//
if ( INVALID_DB_REQ ( type ) ) {
throw cacChannel::badType ();
}
netReadNotifyIO & io = this->cacCtx.readNotifyRequest (
guard, *this, *this, type, countIn, notify );
if ( pId ) {
*pId = io.getId ();
}
this->eventq.add ( io );
return cacChannel::iosAsynch;
}
void nciu::stringVerify ( const char *pStr, const unsigned count )
{
for ( unsigned i = 0; i < count; i++ ) {
unsigned int strsize = 0;
while ( pStr[strsize++] != '\0' ) {
if ( strsize >= MAX_STRING_SIZE ) {
throw badString();
}
}
pStr += MAX_STRING_SIZE;
}
}
void nciu::write (
epicsGuard < epicsMutex > & guard,
unsigned type, arrayElementCount countIn, const void * pValue )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
// make sure that they get this and not "no write access"
// if disconnected
if ( ! this->connected ( guard ) ) {
throw cacChannel::notConnected();
}
if ( ! this->accessRightState.writePermit() ) {
throw cacChannel::noWriteAccess();
}
if ( countIn > this->count || countIn == 0 ) {
throw cacChannel::outOfBounds();
}
if ( type == DBR_STRING ) {
nciu::stringVerify ( (char *) pValue, countIn );
}
this->piiu->writeRequest ( guard, *this, type, countIn, pValue );
}
cacChannel::ioStatus nciu::write (
epicsGuard < epicsMutex > & guard, unsigned type, arrayElementCount countIn,
const void * pValue, cacWriteNotify & notify, ioid * pId )
{
// make sure that they get this and not "no write access"
// if disconnected
if ( ! this->connected ( guard ) ) {
throw cacChannel::notConnected();
}
if ( ! this->accessRightState.writePermit() ) {
throw cacChannel::noWriteAccess();
}
if ( countIn > this->count || countIn == 0 ) {
throw cacChannel::outOfBounds();
}
if ( type == DBR_STRING ) {
nciu::stringVerify ( (char *) pValue, countIn );
}
netWriteNotifyIO & io = this->cacCtx.writeNotifyRequest (
guard, *this, *this, type, countIn, pValue, notify );
if ( pId ) {
*pId = io.getId ();
}
this->eventq.add ( io );
return cacChannel::iosAsynch;
}
void nciu::subscribe (
epicsGuard < epicsMutex > & guard, unsigned type,
arrayElementCount nElem, unsigned mask,
cacStateNotify & notify, ioid *pId )
{
netSubscription & io = this->cacCtx.subscriptionRequest (
guard, *this, *this, type, nElem, mask, notify,
this->channelNode::isInstalledInServer ( guard ) );
this->eventq.add ( io );
if ( pId ) {
*pId = io.getId ();
}
}
void nciu::ioCancel (
epicsGuard < epicsMutex > & guard, const ioid & idIn )
{
this->cacCtx.destroyIO ( guard, idIn, *this );
}
void nciu::ioShow (
epicsGuard < epicsMutex > & guard,
const ioid &idIn, unsigned level ) const
{
this->cacCtx.ioShow ( guard, idIn, level );
}
unsigned nciu::getHostName (
epicsGuard < epicsMutex > & guard,
char *pBuf, unsigned bufLength ) const throw ()
{
return this->piiu->getHostName (
guard, pBuf, bufLength );
}
const char * nciu::pHostName (
epicsGuard < epicsMutex > & guard ) const throw ()
{
return this->piiu->pHostName ( guard );
}
bool nciu::ca_v42_ok (
epicsGuard < epicsMutex > & guard ) const
{
return this->piiu->ca_v42_ok ( guard );
}
short nciu::nativeType (
epicsGuard < epicsMutex > & guard ) const
{
short type = TYPENOTCONN;
if ( this->connected ( guard ) ) {
if ( this->typeCode < SHRT_MAX ) {
type = static_cast <short> ( this->typeCode );
}
}
return type;
}
arrayElementCount nciu::nativeElementCount (
epicsGuard < epicsMutex > & guard ) const
{
arrayElementCount countOut = 0ul;
if ( this->connected ( guard ) ) {
countOut = this->count;
}
return countOut;
}
caAccessRights nciu::accessRights (
epicsGuard < epicsMutex > & guard ) const
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
return this->accessRightState;
}
unsigned nciu::searchAttempts (
epicsGuard < epicsMutex > & guard ) const
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
return this->retry;
}
double nciu::beaconPeriod (
epicsGuard < epicsMutex > & guard ) const
{
return this->cacCtx.beaconPeriod ( guard, *this );
}
double nciu::receiveWatchdogDelay (
epicsGuard < epicsMutex > & guard ) const
{
return this->piiu->receiveWatchdogDelay ( guard );
}
bool nciu::connected ( epicsGuard < epicsMutex > & guard ) const
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
return this->channelNode::isConnected ( guard );
}
void nciu::show ( unsigned level ) const
{
epicsGuard < epicsMutex > guard ( this->cacCtx.mutexRef() );
this->show ( guard, level );
}
void nciu::show (
epicsGuard < epicsMutex > & guard, unsigned level ) const
{
if ( this->connected ( guard ) ) {
char hostNameTmp [256];
this->getHostName ( guard, hostNameTmp, sizeof ( hostNameTmp ) );
::printf ( "Channel \"%s\", connected to server %s",
this->pNameStr, hostNameTmp );
if ( level > 1u ) {
int tmpTypeCode = static_cast < int > ( this->typeCode );
::printf ( ", native type %s, native element count %u",
dbf_type_to_text ( tmpTypeCode ), this->count );
::printf ( ", %sread access, %swrite access",
this->accessRightState.readPermit() ? "" : "no ",
this->accessRightState.writePermit() ? "" : "no ");
}
::printf ( "\n" );
}
else {
::printf ( "Channel \"%s\" is disconnected\n", this->pNameStr );
}
if ( level > 2u ) {
::printf ( "\tnetwork IO pointer = %p\n",
static_cast <void *> ( this->piiu ) );
::printf ( "\tserver identifier %u\n", this->sid );
::printf ( "\tsearch retry number=%u\n", this->retry );
::printf ( "\tname length=%u\n", this->nameLength );
}
}
void nciu::ioCompletionNotify (
epicsGuard < epicsMutex > &, class baseNMIU & io )
{
this->eventq.remove ( io );
}
void nciu::resubscribe ( epicsGuard < epicsMutex > & guard )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
tsDLIter < baseNMIU > pNetIO = this->eventq.firstIter ();
while ( pNetIO.valid () ) {
tsDLIter < baseNMIU > next = pNetIO;
next++;
class netSubscription * pSubscr = pNetIO->isSubscription ();
// Its normal for other types of IO to exist after the channel connects,
// but before all of the resubscription requests go out. We must ignore
// them here.
if ( pSubscr ) {
try {
pSubscr->subscribeIfRequired ( guard, *this );
}
catch ( ... ) {
errlogPrintf ( "CAC: failed to send subscription request "
"during channel connect\n" );
}
}
pNetIO = next;
}
}
void nciu::sendSubscriptionUpdateRequests ( epicsGuard < epicsMutex > & guard )
{
guard.assertIdenticalMutex ( this->cacCtx.mutexRef () );
tsDLIter < baseNMIU > pNetIO = this->eventq.firstIter ();
while ( pNetIO.valid () ) {
tsDLIter < baseNMIU > next = pNetIO;
next++;
try {
pNetIO->forceSubscriptionUpdate ( guard, *this );
}
catch ( ... ) {
errlogPrintf (
"CAC: failed to send subscription update request "
"during channel connect\n" );
}
pNetIO = next;
}
}
void nciu::disconnectAllIO (
epicsGuard < epicsMutex > & cbGuard,
epicsGuard < epicsMutex > & guard )
{
this->cacCtx.disconnectAllIO ( cbGuard, guard,
*this, this->eventq );
}
void nciu::serviceShutdownNotify (
epicsGuard < epicsMutex > & callbackControlGuard,
epicsGuard < epicsMutex > & mutualExclusionGuard )
{
this->setServerAddressUnknown ( noopIIU, mutualExclusionGuard );
this->notify().serviceShutdownNotify ( mutualExclusionGuard );
}
void channelNode::setRespPendingState (
epicsGuard < epicsMutex > &, unsigned index )
{
this->listMember =
static_cast < channelNode::channelState >
( channelNode::cs_searchRespPending0 + index );
if ( this->listMember > cs_searchRespPending17 ) {
throw std::runtime_error (
"resp search timer index out of bounds" );
}
}
void channelNode::setReqPendingState (
epicsGuard < epicsMutex > &, unsigned index )
{
this->listMember =
static_cast < channelNode::channelState >
( channelNode::cs_searchReqPending0 + index );
if ( this->listMember > cs_searchReqPending17 ) {
throw std::runtime_error (
"req search timer index out of bounds" );
}
}
unsigned channelNode::getMaxSearchTimerCount ()
{
return epicsMin (
cs_searchReqPending17 - cs_searchReqPending0,
cs_searchRespPending17 - cs_searchRespPending0 ) + 1u;
}
unsigned channelNode::getSearchTimerIndex (
epicsGuard < epicsMutex > & )
{
channelNode::channelState chanState = this->listMember;
unsigned index = 0u;
if ( chanState >= cs_searchReqPending0 &&
chanState <= cs_searchReqPending17 ) {
index = chanState - cs_searchReqPending0;
}
else if ( chanState >= cs_searchRespPending0 &&
chanState <= cs_searchRespPending17 ) {
index = chanState - cs_searchRespPending0;
}
else {
throw std::runtime_error (
"channel was expected to be in a search timer, but wasnt" );;
}
return index;
}