![]() |
![]() |
![]() |
![]() |
![]() |
The Adaptation Framework
Introduction to the Adaptation Framework
The Adaptation Framework, technically referred to as "ACM", is a collection of the functions that are needed to write a full-fledged adaptation implemented by plug-in libraries that can be used "as is". Currently, the following plug-in modules are included in the TTCN Suite distribution:
This means that if the target system uses TCP/IP to communicate, the adaptation writer can simply use the Adaptation Framework right from the box to get the IUT connected to generated code with minimal amount of work.
Note that the Adaptation Framework is provided as a standardized way to implement the GCI interface. This means that the Adaptation Framework does not replace GCI, but rather complements it. To build the adaptation, simply implement the needed GCI functions by calling the counterpart framework functions that use the compiled-in implementations for the low level code; in this distribution this would be the TCP/IP protocol for communication and a standard system time timer implementation. On the other hand, this means that one can continue to implement adaptations without using the framework altogether, the old way, and old adaptations will continue to work as before.
Examples of usage
The included adaptation residing under the ACM/ subdirectory contains an implementation of the GCI layer by framework functions and is instructive in displaying the way to work with framework functions.
Function reference
Communication data types
Defined constant values
The address type specifier used with the TCP/IP implementation of the Adaptation Framework for the GciAddress type.
More values will be defined in the file acm.h once more plug-in packages are available. The user can also specify new values for his/her own communication modules.
Communication role of the executable test suite
typedef enum{ACMClient,ACMServer} ACMConnectionType;The enumerated type ACMConnectionType is used when calling
ACMConnect() to select whether the generated code should act as a server or client to the IUT.Communication primitives
These functions are used to implement the GCI communication functions.
Initializing the communication and timer package
GciStatus ACMInit(const GciTime* max_timeout,
GciTimeUnit time_tick_unit)Must be called after GCIInit() but prior to any use of the ACM communication or timer primitives. The first argument, max_timeout defines the maximum amount of time the system will wait in blocked state for any timer. Setting this to a low value enables the possibility to write a polling snapshot function. A defined constant exists by the name of ACM_ONE_YEAR_IN_MINUTES that can be used as a default "big enough" number argument.
The second argument, time_tick_unit, depicts the unit of time (in GCI time units) used internally in the run-time system. Since all internal timer data is converted to and from this unit, it is important to set this to a value that encompasses approximately the value range of the time-out values used in the test suite. This parameter also restricts the maximum time that is available for a time-out value, as depicted below.
Resetting the Run-time System
GciStatus ACMReset()
This function cancels all timers and resets all timer queues and PCO buffers. It is typically called before starting a new test case to ensure that all old data is removed and to put the run-time system into an initial mode.
Registering the GciTimeout Function
GciStatus ACMRegisterTimeoutHandler(ACMTimeoutHandler
timeouthandler)This function needs to be supplied with a pointer to the GciTimeout() function before using the ACMSnapshot() function, since ACMSnapshot() calls the timeout function automatically for every timer that has expired.
Registering the GciReceive Function
GciStatus ACMRegisterReceiveHandler(GciReceiveHandler
receive_callback)This function needs to be supplied with a pointer to the GciReceive() function before using the ACMSnapshot() function, since ACMSnapshot() calls the receive function automatically for every data packet that has arrived.
Registering the Active Decode Function
GciStatus ACMRegisterDefaultDecodeHandler(ACMDecodeHandler
decode_handler)The function ACMSnapshot() calls GciReceive() automatically for every data packet that are ready to be received. In order to do this, however, a decode function needs to be registered that can decode the incoming data from its transfer syntax to the internal GCI value representation. This is easily done by the above call. Note that this opens the possibility to switch decoding during runtime, since any decode function that fulfills the requirements are eligible to be registered as the default decoder at any time. See Encoding, and in Particular Decoding within ACM and GCI for details on how to construct the decode function(s).
Registering a Specific Decode Function for a PCO
GciStatus ACMRegisterDecodeHandler(ACMDecodeHandler
decode_handler,
GciPCOID pco_id)This function is similar to the previous function, but it enables a certain decode function to be connected with a certain PCO. This means that the function ACMSnapshot() will be able to use different decoding mechanisms depending on which PCO the message was received from. See Encoding, and in Particular Decoding within ACM and GCI for details on how to construct the decode functions.
Connecting to a Communication Port
GciStatus ACMConnect( GciPCOID pco_id,
GciAddress* address,
ACMConnectionType type,
unsigned int buffer_size)This function maps the PCO identifier value internally to an external communication port and makes the connection. This makes it possible to use the PCO identifier in all subsequent calls, which makes the code quite clear.
In addition, we need to supply what type of role the ETS will have in the communication with the IUT - as a server (ACMServer) or as a client (ACMClient).
The buffer_size argument defines the largest possible buffer (in bytes) that can be received in one chunk. This should usually be set to the largest buffer needed to encode a value in the current value representation.
Disconnecting a Communication Port
void ACMDisconnect(GciPCOID pco_id)
This function disconnects a previously opened communication link.
Sending a Message
GciStatus ACMSend(GciPCOID pco_id, GciBuffer* buffer)
This function sends an encoded buffer on the designated PCO. When sending, the function will block until the entire buffer have been sent, or an error has occurred. This is simply because it is preferred to see the send operation as an atomic operation without possible race conditions.
Receiving a Message
GciStatus ACMReceive(GciPcoID pco_id, GciBuffer* buffer)
This function tries to receive a message from the designated PCO. If successful, the received encoded byte stream is inserted to the provided buffer, which is allocated inside the function.
Retrieving the Position of the First PCO with Received Data
GciPosition ACMGetReceivedPCOPos()
This function returns the position of the first PCO to have data ready for receiving. The return value is simply meant to be used as an iterator argument to the ACMGetNextReceivedPCO() function. If the returned value is zero, there are at this time no PCO that has received data.
Retrieving the position of the next PCO with received data
GciPCOID ACMGetNextReceivedPCO(GciPosition* position)
This function returns the numeric identifier of the PCO that has data ready to receive and updates the position variable to point to the next PCO with data to receive. If the position is zero, no further PCO has data to receive.
A simple loop that checks all PCO's for receive can be written in a similar manner to the timer iteration described below in A Loop That Fetches All Expired Timers and Prints Their ID Numbers.
Timer Primitives
Timer handling can be implemented in various ways. The package makes it easy for the adaptation writer to use timers, by encapsulating functionality that used to be implemented in a fairly standard way in GciSnapshot() implementations over and over again. Instead of using cryptic timer structures the user can now use the GCI identifier for the needed timer in a number of timer access functions. Time itself is fetched from the system clock, normalized to zero at the time of starting the execution.
Starting a Timer
GciStatus ACMStartTimer(GciTimerID timer_id,
GciTime timeout)This function starts the designated timer with the given expiration time. If the timer has not yet been created, this call will create it. If the timer was running or expired it is simply restarted.
Cancelling a Timer
GciStatus ACMCancelTimer(GciTimerID timer_id)
This function transfers the designated timer from the active list to the list of stopped timers. This call needs to be performed once a timer has been noted as expired (via GciTimeout() in GciSnapshot() for instance)
Cancelling All Timers
GciStatus ACMCancelAllTimers()
This function cancels all timers, even if they are already expired.
Reading the Value of a Timer
GciStatus ACMReadTimer( GciTimerID timer_id,
GciTime* timer_before_timeout)This function places the current time of a given timer in the provided GciTime object.
Checking the Current Status of a Timer
GciStatus ACMTimerStatus(GciTimerID timer_id, GciTimerStatus* timer_status)
This function places the current status of the given timer in the status variable. Please refer to the declaration of GciTimerStatus for the various states of a timer.
Retrieving the Key to the First Timer That Has Expired
GciPosition ACMGetTimedOutPos()
This function retrieves the position to the first timer that has expired from a chronological list of expired timers. The return value is simply meant to be used as an iterator argument to the ACMGetNextTimedOut() function. If the returned value is zero, there are no currently expired timers.
Getting the Next Expired Timer
GciTimerID ACMGetNextTimedOut(GciPosition* position)
Given a position index (the index to the first timer is fetched with the ACMGetTimedOutPos() function), this function returns an expired timer and updates the position variable to point to the next timer in the list of expired timers. If the position value is equal to zero after this call, no more expired timers exist in the list.
Example 296 A Loop That Fetches All Expired Timers and Prints Their ID Numbers
. . .GciPosition pos = ACMGetTimedOutPos()while (pos != NULL) {printf("Timer %d has expired!\n", ACMGetNextTimedOut(&pos) );}Time Left Before the Next Timer Expires
GciStatus ACMTimeLeft(GciTime *time_before_timeout)
This function retrieves the actual time left before the next timer is due to expire. If one or more timers have already expired, the function returns zero as the time left. If no timers exist, or all timers have been stopped, the function returns GciNotOk.
Timer and PCO Handling with a Single Call
GciStatus ACMSnapshot()
For the hardened writer of many adaptations, this function offers a relief. Calling this function will wrap most GciSnapshot functionality into one call. Briefly, what the function performs internally is the following:
- Check all PCO/CP channels for received data.
- For each queue that contains fresh data, receive, decode it and call GciReceive() with the result.
- For each expired timer, call GciTimeout() with the expired timer's ID number and move the timer to the list of stopped timers.
- Return status of the operations.
In effect, the simplest snapshot implementation can now look like this:
Example 297 The Simplest Snapshot in the World!
GciStatus GciSnapshot(){return ACMSnapshot();}Somewhere in the code, before calling ACMSnapshot() for the first time, it is also necessary to register three functions with ACM:
This is due to the fact that ACMSnapshot() will possibly try to call these functions depending on the internal state. A good place to register these functions is close to the ACMInit() call:
..ACMInit(max_timeout, ... );..ACMRegisterTimeoutHandler(&GciTimeout);ACMRegisterReceiveHandler(&GciReceive);ACMRegisterDefaultDecodeHandler(&DecoderFunction);Waiting for the Next Event
GciStatus ACMWaitForEvent()
If more functionality is needed in the snapshot function, a mechanism for waiting for the correct amount of time is needed. This function sleeps until the next timer is due, or until data arrives on a PCO, whichever comes first. The function then returns with the status GciOk, at which point the snapshot function can simply poll the input queues for data, and/or take the now arrived timeout. If no timers are running, the function returns after the maximum time to wait as defined in ACMInit( ... ). If this time is set to a small amount, there is a polling snapshot in effect.
Example 298 : The Same Snapshot but with Written Out Code
GciStatus GciSnapshot(){GciPosition expired_pos;GciTimerID expired_timer;GciPosition received_pos;GciPCOID received_pco;GciBuffer buffer;GciValue* value;/* Sleep until either a timer has expired* or data is received. For a polling* snapshot, remove this. */if (ACMWaitForEvent() != GciOk) {return GciNotOk;}/* First, check all timers. */if (ACMSynchronizeTimers() != GciOk) {return GciNotOk;}expired_pos = ACMGetTimedOutPos();while(expired_pos != NULL) {expired_timer = ACMGetNextTimedOut(&expired_pos);GciTimeout(expired_timer);if (ACMCancelTimer(expired_timer) != GciOk) {return GciNotOk;}}/* Next, take care of the PCO's */buffer.buffer =
(char *)malloc(sizeof(char) * MAX_ENCODING_BUFFER+1);buffer.current_length = 0;buffer.max_length = MAX_ENCODING_BUFFER;received_pos = ACMGetReceivedPCOPos();while(received_pos != NULL) {received_pco =
ACMGetNextReceivedPCO(&received_pos);if (ACMReceive(received_pco, &buffer) != GciOk)
{return GciNotOk;}value = GeneralDecode(&buffer);if (value != NULL) {if (GciReceive(received_pco, value)!= GciOk) {return GciNotOk;}}else {fprintf(stderr,"Transfer syntax error on PCO %s\n",GciGetPCOName(received_pco) );return GciNotOk;}}return GciOk;}Retrieving the Current System Time
GciStatus ACMCurrentTime(GciTime* current_time)
This function retrieves the time of the system clock in the instance of the function call.
Error Handling
The error handling of the Adaptation Framework will be straightforward to those familiar with the err_no paradigm of C. Every time an error occurs, the function sets an internal error code and returns
GciNotOk. The error code can then be retrieved either as an enumerated value, or as a descriptive string.The error codes can be found in the file acm.h.
Setting the Error Code
ACMSetLastError(ACM_Error_Number errornumber)
Sets the error number to the given value.
Retrieving the Error Code of the Last ACM Error
ACM_Error_Number ACMGetLastError()
Returns the last error code as an enumerated value.
Retrieving the Error String of the Last ACM Error
const char* ACMGetLastErrorMessage()
http://www.ibm.com/rational |
![]() |
![]() |
![]() |
![]() |