/* * SelectableBare.cpp * * Created on: Feb 2019 * Author: wentzelc */ // Standard C/C++ Libraries #include #include #include #include #include #include #include #include #include #include #include #include // redA Libraries #include "ApplicationCore.h" #include "SelectableCore.h" //--------------------------------------------------------------------------- // Global Vars extern char * ProcessName; extern CApplication * Application; //--------------------------------------------------------------------------- CSelectableBare::CSelectableBare( const char * pName, const char * pType ) : CFunctionCore( pName, pType ) { // Quick access Selector = Application->Selector; } //--------------------------------------------------------------------------- CSelectableBare::~CSelectableBare() { THandle * NextHandle = NULL; // Destroy File Handles while (FirstHandle) { // Close handle if open if ((FirstHandle->State == csOpen) || (FirstHandle->State == csWaitingtoOpen)) { Close( FirstHandle, false ); } NextHandle = FirstHandle->Next; DestroyHandle( FirstHandle ); FirstHandle = NextHandle; } } //--------------------------------------------------------------------------- CDataMember * CSelectableBare::GetHandleAddress( THandle * Handle, const char * HandleRef ) { CDataMember * AddressDef = NULL; char NamePath[100]; char * Address; // Handle renamed? sprintf( NamePath, "Application/Addresses/Rename/%s", HandleRef ); if (!(Address = (char*)Application->Config->GetChStr( NamePath, NULL, false ))) { Address = (char*)HandleRef; } // Get address def if ((AddressDef = Application->AddressList->GetChild( Address ))) { if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Use address '%s' ('%s')", ProcessName, Name, Handle->Name, Address, HandleRef ); } return AddressDef; } //--------------------------------------------------------------------------- THandle * CSelectableBare::CreateHandle( const char * HandleName, bool CreateChannel ) { THandle ** Handle = NULL; // Find Handle by Name or get end of list Handle = &FirstHandle; while ( *Handle && strcmp( HandleName, (*Handle)->Name )) Handle = &((*Handle)->Next); // Create if necessary if (!*Handle) { // Create File handle at end of list *Handle = new THandle; // Set name if (HandleName) { (*Handle)->Name = (char*)malloc( strlen(HandleName)+1 ); strcpy( (*Handle)->Name, HandleName ); } // Set File Descriptor (*Handle)->Function = this; (*Handle)->FD = -1; // Log event if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Created", ProcessName, Name, HandleName ); } // Create Matching Channel if (CreateChannel) { (*Handle)->Channel = AddChannel( HandleName, CH_off ); } return *Handle; } //--------------------------------------------------------------------------- bool CSelectableBare::RemoveHandle( THandle * Handle ) { THandle ** HandlePtr = NULL; // Validate if (!Handle || (Handle->State == csOpen) || (Handle->State == csWaitingtoOpen)) { return false; } // Find in List HandlePtr = &FirstHandle; while (*HandlePtr && (*HandlePtr != Handle)) { HandlePtr = &((*HandlePtr)->Next); } // Remove from list if found if (*HandlePtr) { *HandlePtr = (*HandlePtr)->Next; } // Log event if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Removed", ProcessName, Name, Handle->Name ); // Destroy Child handle DestroyHandle( Handle ); return true; } //--------------------------------------------------------------------------- bool CSelectableBare::DestroyHandle( THandle * Handle ) { // Validate Handle if (!Handle) return false; // Clear parameters if (Handle->Name) free( Handle->Name ); if (Handle->Path) free( Handle->Path ); // Destroy Pointer delete Handle; return true; } //--------------------------------------------------------------------------- bool CSelectableBare::HandleState( THandle * Handle, EConnectState State ) { EChannelState ChannelState = CH_off; // Validate if (!Handle || (Handle->State == State)) return false; // Execute call back for state if (Handle->StateCallback[ (int)State ]) (Handle->StateCallback[ (int)State ])( this, Handle, State ); // Change state Handle->State = State; // Update Channel if (Handle->Channel) { if ((Handle->State == csPreparing) || (Handle->State == csPrepared) || (Handle->State == csWaitingtoOpen)) ChannelState = CH_wait; else if ((Handle->State == csOpen) || (Handle->State == csDataWaiting)) ChannelState = CH_ready; else if (Handle->AutoManage) ChannelState = CH_standby; else ChannelState = CH_off; if (Handle->Channel->InState != ChannelState) SetChannelInState( Handle->Channel, ChannelState ); } return true; } //--------------------------------------------------------------------------- bool CSelectableBare::SetCallback( THandle * Handle, EConnectState pState, FHandleCallback pCallback ) { // Validate if (!Handle) { return false; } // Set callback Handle->StateCallback[ (int)pState ] = pCallback; return true; } //--------------------------------------------------------------------------- bool CSelectableBare::SetAutoManage( THandle * Handle, bool AutoManage, bool Persistent, int ReopenDelay, int CloseTimeout ) { // Validate if (!Handle) { return false; } // Set params Handle->AutoManage = AutoManage; Handle->Persistent = Persistent; Handle->ReopenDelay = ReopenDelay; Handle->CloseTimeout = CloseTimeout; return true; } //--------------------------------------------------------------------------- bool CSelectableBare::SetInBuffer( THandle * Handle, int InBufSize, int InTimeout, const char * InMarker, int InMarkerLen ) { // Validate if (!Handle) { return false; } // Input Buffer if (Handle->InBuffer) { delete Handle->InBuffer; Handle->InBuffer = NULL; } if (InBufSize) { Handle->InBuffer = (CRollingBuffer*) new CRollingBuffer( InBufSize ); } // Set Input Timeout Handle->InTimeout = InTimeout; // Set Input Markers if (InMarkerLen && InMarker) { Handle->InMarkerLen = InMarkerLen; Handle->InMarker = (char *)malloc( InMarkerLen+1 ); memcpy( Handle->InMarker, InMarker, InMarkerLen ); Handle->InMarker[InMarkerLen] = 0; } return true; } //--------------------------------------------------------------------------- bool CSelectableBare::SetOutBuffer( THandle * Handle, int OutBufSize ) { // Validate if (!Handle) { return false; } // Output Buffer if (Handle->OutBuffer) { delete Handle->OutBuffer; Handle->OutBuffer = NULL; } if (OutBufSize) { Handle->OutBuffer = (CRollingBuffer*) new CRollingBuffer( OutBufSize ); } return true; } //--------------------------------------------------------------------------- bool CSelectableBare::ProcessInputBuffer( THandle * Handle, bool Force ) { int Pos = 0; int Len = 0; char * Data = NULL; // Check if buffered data if (!Handle || !Handle->InBuffer || !Handle->InBuffer->Len()) { return false; } // Check if forced processed if (Force || (!Handle->InTimeout && !Handle->InMarkerLen)) { // Show Packet Len = Handle->InBuffer->Peek( &Data ); if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s/%s: Handle '%s' - IN-T:", ProcessName, Name, Handle->Name ); // Write buffer to Outputs if ((Handle->Type == ctTCPremote) || (Handle->Type == ctUNIXremote)) { Output( Handle->Parent->Channel, NULL, true, Data, Len ); } else { Output( Handle->Channel, NULL, true, Data, Len ); } // Clear processed bytes from buffer Handle->InBuffer->Clear( Len ); } else { // Search for end of packet marker while (Handle->InBuffer->FindStr( Handle->InMarker, Handle->InMarkerLen, Pos )) { // Show Packet Len = Handle->InBuffer->Peek( &Data, 0, Pos+Handle->InMarkerLen ); if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s/%s: Handle '%s' - IN-M:", ProcessName, Name, Handle->Name ); // Write buffer to Outputs if ((Handle->Type == ctTCPremote) || (Handle->Type == ctUNIXremote)) { Output( Handle->Parent->Channel, NULL, true, Data, Len ); } else { Output( Handle->Channel, NULL, true, Data, Len ); } // Clear processed bytes from buffer Handle->InBuffer->Clear( Len ); } } return true; } //--------------------------------------------------------------------------- int CSelectableBare::Open( THandle * Handle ) { THandle * NewHandle = NULL; // Validate if (!Handle || (Handle->Type == ctNone)) { return -1; } else if (Handle->State == csOpen) { return Handle->FD; } // Check if port exits if (access( Handle->Path, F_OK ) != 0) { // Log event if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Path not found [%s]", ProcessName, Name, Handle->Name, Handle->Path ); return -1; } // Open Port Handle->FD = open( Handle->Path, O_RDWR ); if (Handle->FD == -1) { // Log event if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Could not open Path [%s]", ProcessName, Name, Handle->Name, Handle->Path ); return -1; } // Log Event if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Path opened [%s]", ProcessName, Name, Handle->Name, Handle->Path ); // Add to Select Lists if (Selector) { Selector->Add( Handle->FD, true, false, Handle, this ); } // Set timer (for re-open or auto-close) SetStartTime( &Handle->LastAction ); // Set state HandleState( Handle, csOpen ); return (NewHandle)? NewHandle->FD : -1; }; //--------------------------------------------------------------------------- // Delete socket bool CSelectableBare::Close( THandle * Handle, bool QuickReopen ) { bool Fail; // Validate if (!Handle || (Handle->FD == -1)) return false; // Close Handle Fail = (close( Handle->FD ))? true : false; // Remove from Select List if (!Fail && Selector) { if (Handle->Type != ctUDPremote) { Selector->Remove( Handle->FD, true, true ); } } // Reset FD Handle->FD = ((Fail)? Handle->FD : -1); // Start timer (for re-open) if (QuickReopen) ClearStartTime( &(Handle->LastAction) ); else SetStartTime( &(Handle->LastAction) ); // Show action if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - %s", ProcessName, Name, Handle->Name, ((Fail)? "failed" : "closed") ); // Set State HandleState( Handle, ((Fail)? csFailed : csClosed) ); return true; } //--------------------------------------------------------------------------- // Device Interface bool CSelectableBare::Read( THandle * Handle ) { int BytesRead = 0; int BytesWaiting = -1; // Validate if (!Handle || (Handle->State == csNone) || (Handle->State == csFailed) || (Handle->State == csClosed)) { return false; } // Log Read Event if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Read Event", ProcessName, Name, Handle->Name ); // Check if socket ready (non-block open in progress) if (Handle->State == csWaitingtoOpen) { Open( Handle ); // Reset Timer (for auto-close) SetStartTime( &(Handle->LastAction) ); return true; } // Check if anything to read ioctl( Handle->FD, FIONREAD, &BytesWaiting ); // Error on port if (BytesWaiting < 0) { if (Log) Log->Message( LogLevel, dlMedium, "%s/%s: Handle '%s' - Data waiting error (%s)", ProcessName, Name, Handle->Name, strerror(errno) ); // Close Handle Close( Handle, false ); return false; } // Validate if (Handle->State != csOpen) { return false; } // Read File directly into buffer if (Handle->InBuffer) { errno = 0; BytesRead = Handle->InBuffer->ReadFromFD( Handle->FD, BytesWaiting ); // Report failure if (errno) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Error reading data [%d/%d] (%s)", ProcessName, Name, Handle->Name, -BytesRead, BytesWaiting, strerror(errno) ); } else if (BytesRead < BytesWaiting) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Incomplete data read [%d/%d]", ProcessName, Name, Handle->Name, BytesRead, BytesWaiting ); } if (BytesRead != 0) { // Process Buffer ProcessInputBuffer( Handle, false ); } } // Reset timer SetStartTime( &(Handle->InStart) ); return (bool)BytesRead; } //--------------------------------------------------------------------------- bool CSelectableBare::Write( THandle * Handle ) { int Len = 0; char * Data = NULL; int BytesWritten = 0; // Validate if (!Handle) return false; // Is Handle open? if ((Handle->State == csNone) || (Handle->State == csFailed) || (Handle->State == csClosed)) { // May it be opened? if (!Handle->AutoManage) { // Must be opened manually return false; } else if (Timeout( Handle->LastAction, Handle->ReopenDelay )) { // Attempt to re-open port Open( Handle ); } } // Log Ready for Write Event if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Write Event", ProcessName, Name, Handle->Name ); if (Handle->State == csWaitingtoOpen) { // Complete socket open process Open( Handle ); // Reset Timer (for auto-close) SetStartTime( &(Handle->LastAction) ); // Remove from set for select write if (Selector) { Selector->Remove( Handle->FD, false, true ); } return true; } else if (Handle->State == csOpen) { if (Handle->OutBuffer) { // Write directly to handle / socket errno = 0; BytesWritten = Handle->OutBuffer->WriteToFD( Handle->FD ); if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s/%s: Handle '%s' - OUT:", ProcessName, Name, Handle->Name ); // Report failure if (errno) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Error sending data [%d/%d] (%s)", ProcessName, Name, Handle->Name, -BytesWritten, Len, strerror(errno) ); } else if (BytesWritten < Len) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Incomplete data sent [%d/%d]", ProcessName, Name, Handle->Name, BytesWritten, Len ); } if (BytesWritten != 0) { // Update Buffer Handle->OutBuffer->Clear( (BytesWritten > 0)? BytesWritten : -BytesWritten ); // negative value reported if error occurred // Check if Buffer empty if (!Handle->OutBuffer->Len()) { // Remove from Select Write list if (Selector) { Selector->Remove( Handle->FD, false, true ); } } // Reset timeout SetStartTime( &(Handle->LastAction) ); } } else { // No Output buffer to write from, so remove from Write list if (Selector) { Selector->Remove( Handle->FD, false, true ); } } return true; } return false; } //--------------------------------------------------------------------------- int CSelectableBare::ReadFromFD( int FD, char * Data, int MaxLen ) { int BytesRead = 0; int TotalRead = 0; int DataRemain = MaxLen; bool Error = false; // Check if buffer created if ((FD == -1) || (MaxLen < 1)) { return 0; } // Read Data into buffer while (DataRemain) { // Read from file descriptor BytesRead = read( FD, &Data[TotalRead], DataRemain ); if ((BytesRead < 0)) { Error = true; errno = (!BytesRead)? 0 : errno; // No error if no bytes written break; } // Update Data Pointers TotalRead += BytesRead; DataRemain -= BytesRead; if (DataRemain) { usleep( 500 ); } } return (Error)? -TotalRead : TotalRead; // Report negative total on error } //--------------------------------------------------------------------------- int CSelectableBare::WriteToFD( int FD, const char * Data, int Len, bool Force ) { int BytesWritten = 0; int TotalWritten = 0; int DataRemain = (Len != -1)? Len : (Data)? strlen(Data) : 0; bool Error = false; // Check if buffer created if ((FD == -1) || !DataRemain) { return 0; } // Read Data into buffer while (DataRemain) { // Read from file descriptor BytesWritten = write( FD, &Data[TotalWritten], DataRemain ); if ((BytesWritten <= 0) && (!Force || (errno != EAGAIN))) { Error = true; errno = (!BytesWritten)? 0 : errno; // No error if no bytes written break; } // Update Data Pointers TotalWritten += BytesWritten; DataRemain -= BytesWritten; if (DataRemain) { usleep( 500 ); } } return (Error)? -TotalWritten : TotalWritten; // Report negative total on error } //--------------------------------------------------------------------------- int CSelectableBare::Input( const char * ChannelName, const char * SourceRef, const char * Data, int Len ) { TChannel * Channel = NULL; THandle * Handle = NULL; int HandleCount = 0; int TempWritten = 0; int BytesWritten = 0; // Validate if (!ChannelName || !Data) { return 0; } // Get Channel if (!(Channel = GetChannel( ChannelName ))) { // Channel not found if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Channel '%s'->'%s' - Input rejected, Channel not found", ProcessName, Name, ((SourceRef && *SourceRef)? SourceRef : "(Any)"), ChannelName ); return 0; } else if (Channel->InState == CH_off) { // Channel disabled if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Channel '%s'->'%s' - Input rejected, Channel off", ProcessName, Name, ((SourceRef && *SourceRef)? SourceRef : "(Any)"), ChannelName ); return 0; } // Log event if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s/%s: Channel '%s'->'%s' - IN:", ProcessName, Name, ((SourceRef && *SourceRef)? SourceRef : "(Any)"), ChannelName ); // Find Linked handle Handle = FirstHandle; while( Handle ) { if (Handle->Channel && !strcasecmp( ChannelName, Handle->Channel->Name )) { // Input to Handle TempWritten = OutputHandle( Handle, Data, Len ); BytesWritten = (TempWritten > BytesWritten)? TempWritten : BytesWritten; HandleCount++; } Handle = Handle->Next; } if (!HandleCount) { // Handle not found if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Channel '%s'->'%s' - Input rejected, No Handles found", ProcessName, Name, ((SourceRef && *SourceRef)? SourceRef : "(Any)"), ChannelName ); return 0; } return BytesWritten; } //--------------------------------------------------------------------------- int CSelectableBare::OutputHandle( THandle * Handle, const char * Data, int Len ) { int BytesWritten = 0; int DataLen = (Len != -1)? Len : (Data)? strlen(Data) : 0; if ((Handle->State != csOpen)) { // Check if auto-managed handle if (!Handle->AutoManage) { // Handle is not open or auto-managed if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Input rejected, Handle not Open (not auto-managed)", ProcessName, Name, Handle->Name ); return 0; } else if (Timeout( Handle->LastAction, Handle->ReopenDelay )) { // Complete opening process Open( Handle ); // Check if Handle is open if ((Handle->State == csPreparing) || (Handle->State == csPrepared)) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Input rejected, Preparing (auto-managed) Handle", ProcessName, Name, Handle->Name ); return 0; } else if (Handle->State == csWaitingtoOpen) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Input rejected, Waiting to open (auto-managed) Handle", ProcessName, Name, Handle->Name ); return 0; } else if (Handle->State != csOpen) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Input rejected, (auto-managed) Handle failed to Open", ProcessName, Name, Handle->Name ); return 0; } } else { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Input rejected, Retry (auto-managed) Handle re-open in %d ms", ProcessName, Name, Handle->Name, TimeLeft( Handle->LastAction, Handle->ReopenDelay) ); return 0; } } // Check packet length if (Len == -1) { Len = strlen( Data ); } // Decide where to put data if (Handle->OutBuffer) { // Write to buffer BytesWritten = Handle->OutBuffer->Push( true, Data, Len ); // Add to select write list if (BytesWritten && Selector) { Selector->Add( Handle->FD, false, true, Handle, this ); } } else { // Write directly to handle / socket errno = 0; BytesWritten = WriteToFD( Handle->FD, Data, Len, true ); if (Log) Log->Output( LogLevel, dlHigh, LogOutput, Data, Len, "%s/%s: Handle '%s' - OUT:", ProcessName, Name, Handle->Name ); // Report failure if (errno) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Error sending data [%d/%d] (%s)", ProcessName, Name, Handle->Name, -BytesWritten, DataLen, strerror(errno) ); } else if (BytesWritten < DataLen) { if (Log) Log->Message( LogLevel, dlHigh, "%s/%s: Handle '%s' - Incomplete data sent [%d/%d]", ProcessName, Name, Handle->Name, BytesWritten, DataLen ); } if (BytesWritten != 0) { // Reset timeout SetStartTime( &(Handle->LastAction) ); } } return BytesWritten; } //--------------------------------------------------------------------------- bool CSelectableBare::Process() { THandle * Handle = NULL; // Check all handles Handle = FirstHandle; while (Handle) { // Auto manage handles if (Handle->State == csPrepared) { // Proceed to open Open( Handle ); } else if ((Handle->State != csOpen) && Handle->AutoManage && Handle->Persistent) { // Try to re-open port after delay if (Timeout( Handle->LastAction, Handle->ReopenDelay )) { Open( Handle ); } } else if (Handle->Channel && (Handle->Channel->InState == CH_off) && Handle->AutoManage) { // Set channel to standby SetChannelInState( Handle->Channel, CH_standby ); } // Check Input buffers if (Handle->InBuffer && (Handle->InBuffer->Len() > 0)) { // Check duration since last PortIn if (Timeout( Handle->InStart, Handle->InTimeout )) { // Process Input ProcessInputBuffer( Handle, true ); // Reset timer ClearStartTime( &(Handle->InStart) ); } } // Check for auto close (but not on servers) if ((Handle->State == csOpen) && Handle->AutoManage && !Handle->Persistent) { // Close port after timeout if (Timeout( Handle->LastAction, Handle->CloseTimeout )) { Close( Handle, true ); } } Handle = Handle->Next; } return true; } //---------------------------------------------------------------------------