Miroslav Popovic - Communication Protocol Engineering - CRC Press (2006)
Miroslav Popovic - Communication Protocol Engineering - CRC Press (2006)
PROTOCOL
ENGINEERING
This book contains information obtained from authentic and highly regarded sources. Reprinted
material is quoted with permission, and sources are indicated. A wide variety of references are
listed. Reasonable efforts have been made to publish reliable data and information, but the author
and the publisher cannot assume responsibility for the validity of all materials or for the conse-
quences of their use.
No part of this book may be reprinted, reproduced, transmitted, or utilized in any form by any
electronic, mechanical, or other means, now known or hereafter invented, including photocopying,
microfilming, and recording, or in any information storage or retrieval system, without written
permission from the publishers.
For permission to photocopy or use material electronically from this work, please access www.
copyright.com (http://www.copyright.com/) or contact the Copyright Clearance Center, Inc. (CCC)
222 Rosewood Drive, Danvers, MA 01923, 978-750-8400. CCC is a not-for-profit organization that
provides licenses and registration for a variety of users. For organizations that have been granted a
photocopy license by the CCC, a separate system of payment has been arranged.
Trademark Notice: Product or corporate names may be trademarks or registered trademarks, and
are used only for identification and explanation without intent to infringe.
Popovic, Miroslav.
Communication protocol engineering / Miroslav Popovic.
p. cm.
Includes bibliographical references and index.
ISBN-13: 978-0-8493-9814-8 (alk. paper)
ISBN-10: 0-8493-9814-2 (alk. paper)
1. Computer network protocols. 2. Computer networks--Standards. I. Title.
TK5101.55.P67 2006
621.382’12--dc22 2005036572
Preface
I wrote this book as a textbook for postgraduate students, but it might also
be used by people in the industry to update specific knowledge in their life-
long learning processes. The book partly covers the actual postgraduate
course on computer communications and networks undertaken during the
first semester of studies for the M.Sc. degree in computer engineering. Since
nowadays we are witnessing the convergence of the Internet and public
telephone network, this book might also be useful to engineers with B.Sc.
degrees in telecommunications.
The prerequisite for this book is the knowledge of the first order logic
(predicate calculus), operating systems, and computer network fundamen-
tals. The reader should also be familiar with C++ and Java programming
languages.
My approach in writing this book was to provide all the details that the
reader may need. I assumed that nothing is obvious. However, if you the
reader find something obvious while reading the book, you are encouraged
to skip ahead. If something is not clear later on, you may always return to
what you skipped. Communication protocol engineering is a very interesting
combination of abstraction and practice that requires a lot of details. It starts
from a vision that gradually materializes in the real-world artifacts. This
happens through a typical engineering process. This book covers all aspects
of the communication protocol engineering, including requirements and
analysis, design, implementation, and test and verification.
Many people helped me in writing this book. My gratitude goes to all of
them. I thank my family for their continuous support, my niece Silvia Likavec
for her valuable text corrections, and B.J. Clark, Nora Konopka, and Helena
Redshaw, of Taylor & Francis, for their professional support. Special thanks
go to my colleagues from the University of Novi Sad, Prof. Vladimir Kovacevic
for giving his blessing to this book, Ph.D. student Ivan Velikic for the excellent
cooperation (in his M.Sc. thesis we actually developed the FSM Library, one
of the anchors of this book), Ph.D. student Ilija Basicevic (for helping me in
preparation of examples in Sections 3.10.5, 4.5.2, and 5.5.2), Sonja Vukobrat
(for helping me in preparation of the example in Section 3.7), Laslo Benarik
and Aleksander Stojicevic (for helping me in preparation of Chapter 6), Milan
Savic, Aleksander Stojicevic, and Cedomir Rebic (for helping me in prepara-
tion of examples in Sections 3.10.1 and 3.10.2), and Nenad Cetic (for helping
me in preparation of the example in Section 4.5.1). Thank you all!
Miroslav Popovic
Novi Sad
The Author
Miroslav Popovic, Ph.D. received all his degrees from the University of Novi
Sad. He defended his diploma thesis, “An Intelligent System Restart,” in
1984, his M.Sc. thesis, “An Efficient Virtual Machine System,” in 1988, and
his Ph.D. thesis, “A Contribution to Standardization of ISO OSI Presentation
Layer,” in 1990. He began working at the University of Novi Sad immediately
after graduating in 1984, and since then he has been teaching students
operating systems and intercomputer communications in various forms. The
research area he is mainly interested in today is engineering of computer-
based systems. He is a member of the program committee of the IEEE Annual
Conference on Engineering of Computer Based Systems. He is also a member
of IEEE (both Computer and Communications societies) and ACM. In the
last 20 years he has published approximately 100 papers and he has super-
vised many real-world projects for the industry.
Contents
Chapter 1 Introduction........................................................................ 1
1.1 The Notion of the Communication Protocol ............................................5
References ................................................................................................. 8
6.8.52 GetMsgToGroup..........................................................................362
6.8.53 GetNewMessage .........................................................................362
6.8.54 GetNewMsgInfoCoding.............................................................363
6.8.55 GetNewMsgInfoLength .............................................................363
6.8.56 GetNextParam .............................................................................363
6.8.57 GetNextParamByte .....................................................................364
6.8.58 GetNextParamDWord ................................................................364
6.8.59 GetNextParamWord ...................................................................365
6.8.60 GetObjectId ..................................................................................366
6.8.61 GetParam......................................................................................366
6.8.62 GetParamByte..............................................................................367
6.8.63 GetParamDWord.........................................................................367
6.8.64 GetParamWord ............................................................................368
6.8.65 GetProcedure ...............................................................................368
6.8.66 GetRightMbx ...............................................................................369
6.8.67 GetRightAutomata......................................................................369
6.8.68 GetRightGroup ............................................................................370
6.8.69 GetRightObjectId ........................................................................370
6.8.70 GetState.........................................................................................370
6.8.71 IsBufferSmall................................................................................370
6.8.72 Initialize ........................................................................................371
6.8.73 InitEventProc ...............................................................................371
6.8.74 InitTimerBlock .............................................................................372
6.8.75 InitUnexpectedEventProc ..........................................................373
6.8.76 IsTimerRunning...........................................................................373
6.8.77 NoFreeObjectProcedure .............................................................373
6.8.78 NoFreeInstances ..........................................................................374
6.8.79 ParseMessage...............................................................................374
6.8.80 PrepareNewMessage(uint8) ......................................................375
6.8.81 PrepareNewMessage(uint32, uint16, uint8) ...........................375
6.8.82 Process ..........................................................................................376
6.8.83 PurgeMailBox ..............................................................................376
6.8.84 RemoveParam .............................................................................377
6.8.85 Reset ..............................................................................................377
6.8.86 ResetTimer....................................................................................377
6.8.87 RestartTimer.................................................................................378
6.8.88 RetBuffer.......................................................................................378
6.8.89 ReturnMsg....................................................................................378
6.8.90 SetBitParamByteBasic.................................................................379
6.8.91 SetBitParamDWordBasic............................................................379
6.8.92 SetBitParamWordBasic ...............................................................380
6.8.93 SetCallId() ....................................................................................380
6.8.94 SetCallId(uint32) .........................................................................380
6.8.95 SetCallIdFromMsg ......................................................................381
6.8.96 SetDefaultFSMData ....................................................................381
6.8.97 SetDefaultHeader........................................................................381
6.8.98 SetGroup.......................................................................................382
6.8.99 SetInitialState ...............................................................................382
6.8.100 SetKernelObjects .........................................................................382
6.8.101 SetLeftMbx ...................................................................................383
6.8.102 SetLeftAutomata .........................................................................383
6.8.103 SetLeftObject ................................................................................383
6.8.104 SetLeftObjectId ............................................................................383
6.8.105 SetLogInterface............................................................................384
6.8.106 SendMessage(uint8)....................................................................384
6.8.107 SendMessage(uint8, uint8*).......................................................384
6.8.108 SetMessageFromData .................................................................385
6.8.109 SetMsgCallId(uint32)..................................................................385
6.8.110 SetMsgCallId(uint32, uint8*).....................................................385
6.8.111 SetMsgCode(uint16) ...................................................................386
6.8.112 SetMsgCode(uint16, uint8*) ......................................................386
6.8.113 SetMsgFromAutomata(uint8) ...................................................386
6.8.114 SetMsgFromAutomata(uint8, uint8*) ......................................387
6.8.115 SetMsgFromGroup(uint8)..........................................................387
6.8.116 SetMsgFromGroup(uint8, uint8*).............................................388
6.8.117 SetMsgInfoCoding(uint8) ..........................................................388
6.8.118 SetMsgInfoCoding(uint8, uint8*) .............................................388
6.8.119 SetMsgInfoLength(uint16).........................................................389
6.8.120 SetMsgInfoLength(uint16, uint8*)............................................389
6.8.121 SetMsgObjectNumberFrom(uint32) .........................................389
6.8.122 SetMsgObjectNumberFrom(uint32, uint8*)............................390
6.8.123 SetMsgObjectNumberTo(uint32) ..............................................390
6.8.124 SetMsgObjectNumberTo(uint32, uint8*) .................................390
6.8.125 SetMsgToAutomata(uint8).........................................................391
6.8.126 SetMsgToAutomata(uint8, uint8*) ...........................................391
6.8.127 SetMsgToGroup(uint8)...............................................................391
6.8.128 SetMsgToGroup(uint8, uint8*)..................................................392
6.8.129 SendMessageLeft ........................................................................392
6.8.130 SendMessageRight......................................................................392
6.8.131 SetNewMessage ..........................................................................393
6.8.132 SetObjectId ...................................................................................393
6.8.133 SetRightMbx ................................................................................393
6.8.134 SetRightAutomata.......................................................................394
6.8.135 SetRightObject .............................................................................394
6.8.136 SetRightObjectId .........................................................................394
6.8.137 SetState..........................................................................................394
6.8.138 StartTimer.....................................................................................395
6.8.139 StopTimer .....................................................................................395
6.8.140 SysClearLogFlag..........................................................................395
6.8.141 SysStartAll....................................................................................396
6.8.142 NetFSM.........................................................................................396
6.8.143 convertFSMToNetMessage ........................................................397
Dedication
1
Introduction
Originally, the term protocol was related to the customs and regulations
dealing with diplomatic formality, precedence, and etiquette. A protocol is
actually the original draft, minutes, or record from which a document, espe-
cially a treaty, is prepared, e.g., an agreement between states. Today, in the
context of computer networks, the term protocol is interpreted as a set of
rules governing the format of messages that are exchanged between com-
puters. Sometimes, especially if we want to be more specific, we use the term
communication protocol instead.
The title of this book, Communication Protocol Engineering, is used to empha-
size the process of developing communication protocols. Like other engi-
neering disciplines, communication protocol engineering typically
comprises the following phases (Figure 1.1):
The process as described in this book is ideally the union of the UML
(Unified Modeling Language)-driven unified development process (Booch
et al., 1998), Cleanroom engineering (formal system design verification and
statistical usage testing), and some elements of Agile programming (particu-
larly unit testing based on JUnit). Of course, each organization should adapt
and tune the process to its own needs and goals. For example, one organi-
zation may stick to the UML-driven unified development process, another
may prefer Cleanroom engineering, yet another may use the combination of
both, and so forth.
Because this book is written for the process in which all the existing state-
of-the-art methods and techniques in the area are applied, it is independent
of any particular engineering process; this is as far as we will go in discus-
sions on processes in this book. This book is not about managing processes.
Rather, this book is intended for engineers. It provides the knowledge that
Requirements
&
Analysis
Design
Implementation
Test
&
Verification
FIGURE 1.1
Typical communication protocol engineering phases.
• UML methodology
• ITU-T system specification and description methodology
• Agile unit testing methodology
• Cleanroom engineering methodology
Introduction 3
Agile unit testing methodology assumes writing the test cases before the
code. Today, it is supported by the following two open-source packages (both
are covered in this book):
The text of the book is organized as follows. At the end of this chapter, in
Section 1.1, we introduce the notion of the communication protocol and
related definitions.
Chapter 2 is devoted to the requirements and analysis phase of commu-
nication protocol engineering. The first part of that chapter introduces UML
use case and collaboration diagrams (Section 2.1 and Section 2.2, respec-
tively). The former is used for capturing both functional and nonfunctional
system requirements, whereas the latter is used for making system analysis
models. The second part of that chapter presents a real-world example —
requirements and analysis of an SIP (Session Initiation Protocol, RFC 3261)
Softphone. The example starts with the presentation of the domain-specific
information related to SIP, continues with the SIP Softphone requirements
model (in the form of the corresponding use case diagram), and ends with
the SIP Softphone analysis model (in the form of the corresponding collab-
oration diagram).
Introduction 5
automated theorem proving (Section 5.3). In this book, we use the theorem
prover Theo for this purpose.
The first part of Chapter 5 ends with the introduction of statistical usage
testing (Section 5.4) based on product operational profiles. The second part
of Chapter 5 consists of two real-world examples. The first example shows
the unit testing of the SIP INVITE client transaction based on the usage of
the CppUint, the library for unit testing C++ modules. The second example
demonstrates the integration testing of the SIP INVITE client transaction.
Chapter 6 is written as a programmer’s reference manual for the FSM
Library. The first part starts with the introduction of two main classes, FSM-
System and FiniteStateMachine (Section 6.2). Next, we introduce three main
groups of basic functions supported by the FSM Library: time, memory, and
message management functions (Sections 6.3, Sections 6.4, and Sections 6.5,
respectively). We then introduce two classes that support the communication
of FSMs over the TCP/IP Internet (Section 6.6), namely the classes FSMSys-
temWithTCP and NetFSM. The first part of Chapter 6 ends with the intro-
duction of global constants, types, and functions (Section 6.7).
The second part of Chapter 6 contains detailed descriptions of the indi-
vidual FSM Library Application Programming Interface (API) functions (Sec-
tion 6.8). The third part of Chapter 6 consists of two examples. The first is
a simple example with three automata (FSM) instances (Section 6.9), and the
second is a simple example with TCP/IP network-aware automata instances
(Section 6.10).
The message format completely defines the structure of the message, i.e., it
defines the set of fields that constitute the message by defining the width of
individual fields (most commonly in bits, bytes, or words), the applied
coding scheme (e.g., binary, ASCII, Unicode, ASN.1), and optionally legal
values (e.g., constants in binary or some symbolic form, value intervals).
Therefore, a message is a series of bits logically divided into various fields.
Typically, a message consists of a message header, which most commonly
comprises more subfields, and useful data referred to as a payload. The
payload contains data interpreted by the communicating program objects.
The message header contains data added for supervision and control pur-
poses in accordance with the established conventions.
The message-processing procedure (i.e., the process reaction) begins with
the message reception and is described as a series of primitive operations
that define the rules of the communication, which are the essential parts of
a protocol. Typical primitive operations include timer-start operations,
timer-stop operations, message-send operations, message-receive opera-
tions, and message-data processing operations (e.g., cyclic redundancy
checking of message data, calculating expected order number of the next
message to be received).
In terms of software implementation, message processing is performed
by a message processing routine. Depending on the selected working
Introduction 7
Let us forget for a moment the communication protocols and use the old
example of informal specification of a group of tasks to get a feeling about
the issues stated above. While leaving the house, the mother says to her
daughter:
Obviously, this specification does not say anything about the order of the
individual tasks. For example, the daughter may complete the tasks in any
order without interrupting the individual tasks (e.g., task order may be 1,
2, 3, or 1, 3, 2), or she may complete them in any order and switch between
them (e.g., she starts with task 1, then before completing it, she switches to
task 2, completes tasks 2 and 3, and at the end finishes task 1). An essential
question here is how to organize the task executions in time, i.e., how to
allocate time to them. Clearly, a need exists to limit task duration, i.e.,
to control the task execution time. What happens if the daughter gets
References
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Modeling Language User Guide,
Addison-Wesley, Reading, MA, 1998.
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Software Development Process,
Addison-Wesley, Reading, MA, 1998.
2
Requirements and Analysis
class diagrams is postponed until the next chapter. The next chapter deals
with the communication protocol design phase in which class diagrams are
essential to show the static relations among classes.
Further on, in accordance with the UML paradigm, the requirements
model should be transformed into the test model to facilitate the system
verification (the test model is actually the test suite needed for the system
verification). Essentially, the use cases should be translated into the corre-
sponding test cases described by test scripts of some kind. UML is not specific
in that respect. Of course, a few scripting languages are popular today, such
as TCL/TK, Perl, and Payton, but being general purpose languages, these
might be inappropriate for some of the projects.
To close this gap, we will introduce a domain-specific language known as
tree and tabular combined notation (TTCN). The TTCN tables are used for
specifying the test suites for communication protocols once the software
architecture is rather well known. Therefore, we will postpone the introduc-
tion to the TTCN language until the next chapter, which deals with the design
phase of communication protocol engineering.
A general problem when transforming use cases to test cases is that the
transformation is typically done manually, i.e., it is semiautomatic. Such an
approach is both time consuming and prone to error. However, the main
conceptual problem is the test coverage of the system behavior. In practice,
the number of possible scenarios and all possible combinations of message
parameters can be impossible to cover manually. Therefore, testing at least
the most frequently used system scenarios and message parameter combi-
nations should somehow be possible.
Clearly, more detailed UML models made during the system design phase
(e.g., statecharts, to be introduced in the next chapter) can be used later for
the automatic generation of test cases. However, the problem with this
approach is that if an error exists in the UML model, it will be propagated
into the test suite and the test suite will not be able to detect the error. A
well-known principle from mathematical logic is that negation of negation
leads to affirmation, so the bug will remain undiscovered. No matter how
large test suite we generate, it will not be able to detect the bug.
The former problem can be solved by the application of statistical usage
testing, also referred to as behavior testing. This paradigm is based on the
operational profile model of the system, which describes the statistics of the
system usage. It enables the practitioners to thoroughly test the system and
even estimate the system or software reliability. This practice is recognized
as a de facto standard by the industry (Broekman and Notenboom, 2003) and
it will be covered in detail in Chapter 5 (test and verification phase of
communication protocol engineering).
The latter problem can be solved by using one model as a source for the
software implementation generated with forward engineering and a com-
pletely different model for the system test suite generation. Also highly
desirable is that these two models are made by two separate individuals or
teams. For example, the well-known Cleanroom engineering paradigm is
conducted by three completely separate teams. The design team makes the
design and does its formal verification, the implementation team just does
the coding, and the test team makes the operational profile of the system
and conducts the statistical usage testing. Cleanroom engineering will be
described together with statistical usage testing in Chapter 5.
Before proceeding further to the introduction of the mainstream approach
to requirements and analysis, which is based on UML, worth mentioning is
that until recently, many opponents to this paradigm existed. Some ongoing
doubts still exist as to if this is the correct choice. For example, in his article,
“Use-Cases Are Not Requirements” (Meyer and Apfelbaum, 1999), Meyer
argues that a better approach to requirements and analysis is transforming
the functional requirements into the behavior model that takes the form of
a finite state machine (FSM). He sees use cases as just walks across the FSM
and claims it is possible to generate them automatically rather than writing
them manually.
According to the methodology proposed by Meyer, after creating the
behavior model, two parallel streams of activities are started. The first stream
covers the analysis, the design, and the implementation, and yields the
implementation. The second stream covers the operational profile and the
performance analysis, as well as the automatic test suite generation. These
two streams merge at the automated testing phase.
This approach is very similar to the one used in this book. A slight differ-
ence is that the latter promotes separation of concerns between design and
implementation, and promotes test teams, including the models they make,
very much like the Cleanroom engineering model does. Also, it gives more
credit to the UML use cases. If we go back to the original ideas of the UML
authors (Booch et al., 1998) and try to think of a single use case as a family
of closely related collaborations among the same set of objects, clearly a use
case really captures a part of the traditional list of functional requirements.
Use cases help us group simple and closely related functional requirements,
as will be illustrated by the examples in this chapter.
As already mentioned, use cases are the starting point of the software
development in the unified software development process (Booch et al.,
1998). The requirements model, essentially a set of use cases, is used to
develop all the models that correspond to the engineering phases of the
process, namely, the analysis model (result of the analysis phase), the design
and deployment models (results of the design phase), the implementation
model (result of the implementation phase), and the test model (result of the
test preparation phase). The focus of this chapter is on requirements and
analysis modeling.
The rest of the chapter is organized as follows: use case and collaboration
diagrams are introduced in the next two sections. The last section of this
chapter illustrates the requirements and analysis phases of communication
protocol engineering by presenting the case of the session initiation protocol
(SIP), RFC 3261 (Rosenberg et al., 2002). That last section is divided into three
subsections: SIP domain-specifics, the SIP requirements model, and the SIP
analysis model.
Use cases have another role in the analysis phase. The job of the analyst
is to realize the use cases by the corresponding collaborations between
objects. The analyst reads the descriptions of the use cases and uses domain-
specific knowledge to identify the individual objects (horizontal structuring)
and to establish a hierarchy among them (vertical structuring). This process
will be described in the next section.
Both actors and use cases are classifiers and, normally, they are connected
by associations. The association between the actor and the use case shows
the communication between the user and the part of the system modeled
Use Case
Actor
-Communicates
System
1 1
«uses» «extends»
FIGURE 2.1
The basic set of graphical symbols available for rendering use case diagrams.
by the use case. Using associations enables us to indicate explicitly the points
of connection between the users and the system.
Because both actors and use cases are classifiers, we can define general
actors and general use cases and then specialize them using the generaliza-
tion relationship. For example, we may specify the general actor Client and
its specializations SIP Client and H.323 Client (Figure 2.2). Or, we can specify
the general use case Make a connection and its specializations Make a local
connection and Make a long distance connection (Figure 2.3).
Furthermore, while capturing the individual use cases, it may become
obvious that a certain use case extends another use case or that a certain use
case includes some other use cases. In such circumstances, the requirements
engineer may structure the use cases using <<extends>> and <<includes>>
stereotyped relationships. Especially important things can be indicated by
using the sticky notes. Invariants, preconditions, and postconditions can be
specified by the corresponding constraints. In more complex use case dia-
grams, we may need to indicate the packages and the interfaces.
Use case diagrams are normally rendered using the appropriate graphical
tools, e.g., Microsoft® Visio. This tool provides the set of graphical symbols
that are placed on the working sheet by the drag-and-drop paradigm. The
basic set of graphical symbols is shown in Figure 2.1. The requirements
engineer must specify the properties for each instance of a symbol in the
drawing.
Five categories of actor properties are found: general information, table of
attributes, table of operations, table of constraints, and tagged values. The
general information includes name, full path, stereotype, visibility (private,
protected, or public), and the indicators for Root, Leaf, and Abstract types of
actors. The table of attributes includes columns for the attribute name, type,
visibility, multiplicity (1, *, 0..1, 0..*, 1..1, or 1..*), and its initial value. The
table of operations comprises columns for the operation name, return type,
visibility, scope (classifier or instance), and the indicator for the polymorphic
FIGURE 2.2
An example of the generalization and specialization of actors.
Make a connection
FIGURE 2.3
An example of structuring use cases.
Note (Constraint)
(Constraint) (OR)
Interface
Package
FIGURE 2.4
The additional graphical symbols available for rendering use case diagrams.
∗ ∗
∗
∗
User
Send e-mail Receive e-mail
«uses»
«uses»
«uses» «uses»
«uses»
«uses»
Use SMTP Use DNS Use POP3
«uses» «uses»
«uses»
Use TCP
«uses»
«uses»
Use IP
Use ARP
«uses»
«uses»
Use NIC
FIGURE 2.5
The use case diagram of the simple program for sending and receiving e-mails.
(named User). On the highest level of abstraction, this program has two main
use cases, Send e-mail and Receive e-mail.
Both of these highest-level use cases make use of the use cases Use DNS
(Domain Name System) and Use TCP (Transmission Control Protocol). The
DNS service provides the mapping of the e-mail server domain name into
its IP (Internet Protocol) address. The TCP provides reliable data delivery
service. Other than that, the use case Send e-mail uses the use case Use SMTP
(Simple Mail Transfer Protocol) and the use case Receive e-mail uses the use
case Use POP3 (Post Office Protocol, Version 3). Normally, an e-mail client
uses SMTP to send an e-mail message to the e-mail server. Similarly, a user
uses POP3 to read the e-mail messages from their mailbox.
The use case Use DNS uses the use case Use IP to send a DNS requests to
the DNS server and to receive DNS responses from it. The use case Use TCP
uses the use case Use IP to send and receive segments of data and control
information over the Internet. The use case Use IP uses the use case Use ARP
(Address Resolution Protocol) to map the IP address of the destination host
to its physical (e.g., Ethernet) address. Alternatively, the use case Use IP uses
the use case Use NIC (Network Interface Controller) to send and receive IP
datagrams over the Internet. Finally, the use case Use ARP uses the use case
Use NIC to send an ARP request to the ARP server and to receive an ARP
response from it.
This hierarchy of use cases actually follows the hierarchy of protocols in
the TCP/IP protocol stack. As already mentioned, the concept of layered
software architecture, which is traditionally explained by the ISO OSI, was
actually invented to enable the separation of functions and the correspond-
ing functional requests, which are referred to as use cases in UML.
After creating the skeleton of the use case model, the requirements engi-
neer must fill in the descriptions of the individual use cases. The descriptions
in this example are simplified for the sake of clarity. The description of the
use case Send e-mail in plain text is the following:
Precondition:
The user has issued the send mail command.
The use case Receive e-mail is identical to the use case Send e-mail with the
difference being that the former uses the use case Use POP3 instead of the
use case Use SMTP. The following description of the use case Use DNS is
rather simple (actually, this is the description of the behavior of the DNS
client):
The use case Use TCP is the active (initiator’s) side of the TCP. It is defined
as follows:
The use case Use SMTP is actually the client side of the SMTP (defined by
IETF RFC 821 and RFC 788) and can be described as follows (for simplicity,
only one exceptional flow of events is given):
The use case Use POP3 is the client side of the POP3 protocol, similar to
the use case Use SMTP. The use case Use IP is actually the IP protocol, which
is described as follows:
The use case Use ARP is an ARP client and the use case Use NIC is a
network card driver. The former is defined as follows:
The example above, especially the use cases Use TCP and Use SMTP, should
help the reader understand that a use case is a set of event sequences, not
just a single sequence. To keep use cases simple, separating the main and
the alternative flows of events is always desirable. Usually, we start by just
writing the main flow of events for each use case and later refine them by
adding the exceptional flow of events.
After this example, it should be clear that a use case captures the intended
behavior of the part of the system (subsystem, class, or interface). Of course,
after specifying the intended behavior, we must create a set of classes that
work together to implement that behavior. The means of modeling both
static and dynamic structures of the society of objects in UML are the col-
laboration diagrams.
1: Message1
2: Message2
Object : Class
Note (Constraint)
(Constraint) (OR)
FIGURE 2.6
The set of graphical symbols available for rendering collaboration diagrams.
table of attributes, the table of operations, the table of constraints and the
tagged values contain the same properties as the corresponding categories
for the use cases (see the previous section of this chapter).
The table of receptions has five columns, which contain the reception name,
signal name, visibility (private, protected, or public), polymorphic indicator
(false or true), and scope (classifier or instance). The table of template param-
eters includes the columns for the parameter name and its type. The list of
components is just a list of components that implement this class.
The links in collaboration diagrams have four categories of properties,
including general properties, table of messages, table of constraints, and
tagged values. The general properties are the link name, its full path, and
the table of link ends roles, which has two columns, the end name and its
stereotype (none, association, global, local, parameter, self). The table of link
messages has four columns, including the message name, its direction (for-
ward or backward), flow kind (procedure call, flat, or asynchronous), and
sequence expression. The table of constraints contains the same properties
as the corresponding category of object (and classifier) properties. The tagged
values are just the notes for the documentation. The notes and the constraints
have the same properties as in the use case diagrams (see the previous section
of this chapter).
Most frequently, we model sequential flow of control with collaboration
diagrams. In this case, a message sequence expression takes the simple form
of a message sequence number. However, collaboration diagrams allow
modeling of more complex flows, such as iteration and branching. Iteration
is modeled by prefixing the message sequence number with the iteration
expression
mailc : FSM
tcpc : FSM
ip : FSM
arpc :FSM
nic :FSM
FIGURE 2.7
The collaboration diagram of the simple program for sending and receiving e-mails.
• The object tcpc (abbreviation for a TCP Client, i.e., the side that
initiates the establishment of the TCP connection) is a realization of
the use case Use TCP.
• The object smtpc (abbreviation for an SMTP Client) is a realization
of the use case Use SMTP.
• The object pop3c (abbreviation for a POP3 Client) is a realization of
the use case Use POP3.
• The object ip is a realization of the use case Use IP.
• The object arpc (abbreviation for an ARP Client) is a realization of
the use case Use ARP.
• The object nic is a realization of the use case Use NIC.
Figure 2.7 shows general collaboration among the relevant objects, i.e., it
just shows the links between objects. Essentially, it shows the software archi-
tecture. We may think of it as a family of particular collaborations. For
example, the user of the program might select the use case Send e-mail and
this would lead to a particular collaboration, or the user might select the use
case Receive e-mail and that would lead to another particular collaboration.
Another important thing to notice and remember is that Figure 2.7 shows
only the objects of the system under development. In this case, it is a program
that runs on a computer connected to the Internet over its network interface
card. If we want the overall picture, we can also add the models of the
systems with which our system under development would normally com-
municate. By adding the models of these external systems, we are modeling
end-to-end collaborations.
The system under development communicates with external servers,
including the ARP server, the DNS server, and the e-mail server. If we assume
that all of these servers run on the same computer, the model of the external
environment of the system under development is rather simple (Figure 2.8).
The external objects are as follows:
tcps : FSM
dnss : FSM
ips : FSM
arps : FSM
nics : FSM
FIGURE 2.8
The collaboration diagram of the e-mail and DNS server.
message 220 READY FOR MAIL, is shown in Figure 2.9. The flow of events
is as follows:
1: The object mailc sends the signal sendMail(msg) to the object sender.
The signal parameter msg is the e-mail message itself.
2: The object sender sends the signal domainToIP(domain) to the object
dnsc. The signal parameter domain is the domain name of the e-mail
server.
3: The object dnsc sends the signal dnsReq(domain) to the object ip. The
signal dnsReq is actually the DNS service request message.
4: The object ip sends the signal data(dnsReq) to the object nic. The general
signal data is an IP datagram. Together with the parameter dnsReq,
it represents the datagram carrying the DNS service request
message.
5: The object nic sends the signal frame(dnsReq) to the object nics. The
general signal frame is a data frame from the underlying physical
network (e.g., Ethernet). The signal frame(dnsReq) is the data frame
carrying the datagram that encapsulates the DNS service request
message.
6: The object nics sends the signal data(dnsReq) to the object ips.
7: The object ips sends the signal dnsReq(domain) to the object dnss.
)
sg mailc : FSM
l(m
ai
n dM
se
1:
To add
IP r(i
(d p)
14: o openAck
24.1:
om
ain
pen(ip
smtps : FSM
)
,25)
25 2.5:
smtpc : FSM dnsc : FSM pop3c : FSM pop3s :FSM dnss : FSM
24
: m op
.
ail enA
(2
20 ck
31
(ip n)
)
:m
sp ai
ail
sR om
(22
)
tcpc : FSM tcps : FSM
0)
dn (d
ain)
eq( p(ip)
8: Req
24: seg(syn+ack)
20: seg(syn+ack)
dom
24.2.4: seg(ack)
s
24.2: seg(ack)
dn
15: seg(syn)
s
30: seg(220)
26: seg(220)
19: seg(syn)
3: d : dnsR
7:
nsR
12
24.2.3: data(ack)
9: data(dnsRsp)
6: data(dnsReq)
27: data(220)
29: data(220)
16: data(syn)
18: data(syn)
FIGURE 2.9
The overall real collaboration of the simple program for sending and receiving e-mails and its
environment.
8: The object dnss sends the signal dnsRsp(ip) to the object ips. The signal
dnsRsp is the DNS service response message and its parameter ip is
the IP address of the target e-mail server.
9: The object ips sends the signal data(dnsRsp) to the object nics.
10: The object nics sends the signal frame(dnsRsp) to the object nic.
11: The object nic sends the signal data(dnsRsp) to the object ip.
12: The object ip sends the signal dnsRsp(ip) to the object dnsc.
13: The object dnsc sends the signal ipaddr(ip) to the object sender.
14: The object sender sends the signal open(ip,25) to the object tcpc. The
signal open is an active open request to TCP (TCP should send the
SYN segment to initiate the TCP connection establishment proce-
dure). Its parameters, ip and 25, are the IP address of the target e-mail
sever and the well-known TCP port number reserved for the SMTP,
respectively.
15: The object tcpc sends the signal seg(syn) to the object ip. The general
signal seg is a TCP segment. The signal seg(syn) is a SYN (synchro-
nization) TCP segment (i.e., it has the SYN bit set in the code field).
16: The object ip sends the signal data(syn) to the object nic.
17: The object nic sends the signal frame(syn) to the object nics.
18: The object nics sends the signal data(syn) to the object ips.
19: The object ips sends the signal seg(syn) to the object tcps.
20: The object tcps sends the signal seg(syn+ack) to the object ips. The
signal seg(syn+ack) is a SYN+ACK (synchronization and acknowl-
edgment) TCP segment (i.e., it has both SYN and ACK bits set in
the code field).
21: The object ips sends the signal data(syn+ack) to the object nics. The
signal data(syn+ack) is the IP datagram that encapsulates the
SYN+ACK TCP segment.
22: The object nics sends the signal frame(syn+ack) to the object nic. The
signal frame(syn+ack) is the data frame carrying the IP datagram that
encapsulates the SYN+ACK TCP segment.
23: The object nic sends the signal data(syn+ack) to the object ip.
24: The object ip sends the signal seg(syn+ack) to the object tcpc. (The
event flow now forks into two parallel flows.)
24.1: The object tcpc sends the signal openAck to the object sender.
(The first flow begins here.)
24.1.1: The object sender sends the signal openAck to the object
smtpc (The first flow ends here.)
24.2: The object tcpc sends the signal seg(ack) to the object ip. (The
second flow begins here.)
24.2.1: The object ip sends the signal data(ack) to the object nic.
24.2.2: The object nic sends the signal frame(ack) to the object nics.
24.2.3: The object nics sends the signal data(ack) to the object ips.
24.2.4: The object ips sends the signal seg(ack) to the object tcps.
24.2.5: The object tcps sends the signal openAck to the object smtps.
25: The object smtps sends the signal mail(220) to the object tcps. The
general signal mail is the SMTP message. The particular signal
mail(220) is actually the message 220 READY FOR MAIL, where the
first three digits are mandatory and the rest of the message is a
human-readable comment. (Note: We have restarted the message
numbering here for brevity.)
26: The object tcps sends the signal seg(220) to the object ips.
27: The object ips sends the signal data(220) to the object nics.
28: The object nics sends the signal frame(220) to the object nic.
29: The object nic sends the signal data(220) to the object ip.
30: The object ip sends the signal seg(220) to the object tcpc.
31: The object tcpc sends the signal mail(220) to the object smtpc. (The
example ends here.)
What we have just described is the real collaboration between objects within
the system under development as well as with the relevant objects in its
surroundings. The real collaboration for any nontrivial system could be
rather complex. This behavior should be clear from the previous example,
where we intentionally stopped at the certain point of the event flow, which
was selected as a compromise between showing enough complexity and
maintaining clarity.
The complete list of events for the use case Send e-mail is much longer than
the one given above. For modeling the transfer of the rest of the SMTP
messages (12 of them), we would need additional 84 (12 × 7) UML events,
almost three times more than already in the list above. This complexity is
why we try to break the system down into its parts and analyze them in
detail later.
One important aspect of the simplification is the definition of the Appli-
cation Programming Interfaces (API). For example, we may define the API
between the sender and the hierarchically lower level objects (dnsc, smtpc,
and tcpc), or the API between tcpc and ip, and so on. Other important items
are the virtual collaborations that are governed by the peer-to-peer protocols.
Consider for example the virtual collaboration between dnsc and dnss (Figure
2.10). The corresponding flow comprises only two events, dnsReq(domain)
and dnsRsp(ip).
The virtual collaboration between tcpc and tcps is governed by the TCP. It
is slightly more complex and comprises the following flow of events (Figure
2.11):
1: The object tcpc sends the signal seg(syn) to the object tcps.
2: The object tcps sends the signal seg(syn+ack) to the object tcpc.
3: The object tcpc sends the signal seg(ack) to the object tcps.
4: The object tcpc sends the signal seg(data) to the object tcps. (Data
transmission phase)
1: dnsReq(domain)
2: dnsRsp(ip)
dnsc : FSM dnss : FSM
FIGURE 2.10
The virtual collaboration between the DNS client and the DNS server.
1: seg(syn)
2: seg(syn+ack)
3: seg(ack)
4: seg(data)
5: seg(fin)
6: seg(ack)
7: seg(fin+ack)
8: seg(ack)
tcpc : FSM tcps : FSM
FIGURE 2.11
The virtual collaboration between two TCP entities.
5: The object tcpc sends the signal seg(fin) to the object tcps.
6: The object tcps sends the signal seg(ack) to the object tcpc.
7: The object tcps sends the signal seg(fin+ack) to the object tcpc.
8: The object tcpc sends the signal seg(ack) to the object tcps.
Finally, the virtual collaboration between smtpc and smtps (in accordance
with SMTP) is of the same order of complexity (Figure 2.12; note that only
the first eight events are shown in the figure). The corresponding flow of
events is the following:
1: The object smtps sends the signal mail(220) to the object smtpc.
2: The object smtpc sends the signal mail(HELO) to the object smtps.
3: The object smtps sends the signal mail(250_OK) to the object smtpc.
4: The object smtpc sends the signal mail(MAIL_FROM:) to the object
smtps.
5: The object smtps sends the signal mail(250_OK) to the object smtpc.
6: The object smtpc sends the signal mail(RCPT_TO:) to the object smtps.
7: The object smtps sends the signal mail(250_OK) to the object smtpc.
8: The object smtpc sends the signal mail(DATA) to the object smtps.
9: The object smtps sends the signal mail(354_START_MAIL_INPUT) to
the object smtpc.
1: mail(220)
2: mail(HELO)
3: mail(250_OK)
4: mail(MAIL_FROM:)
5: mail(250_OK)
6: mail(RCPT_TO:)
7: mail(250_OK)
8: mail(DATA)
smtpc : FSM smtps : FSM
FIGURE 2.12
The virtual collaboration between the SMTP client and the SMTP server.
10: The object smtpc sends the signal mail(MAIL_BODY) to the object
smtps.
11: The object smtps sends the signal mail(250_OK) to the object smtpc.
12: The object smtpc sends the signal mail(QUIT) to the object smtps.
13: The object smtps sends the signal mail(221) to the object smtpc.
that persists for some time. A transaction is the collaboration between the
client and the server, which comprises all the messages from the first request
sent from the client to the server up to the final response sent from the server
to the client. The requests are processed automatically, meaning that either
all requested actions are conducted, if the request has been accepted, or none
of the actions are conducted, if the request has not been accepted.
Two main transaction types exist, referred to as invite (officially written
in capital letters, i.e., INVITE) and non-invite (or, more formally, non-
INVITE) transactions. An invite transaction is a three-way handshake com-
prising the request, the response, and the acknowledgment. In contrast, a
non-invite transaction is the two-way handshake starting with the request
and ending with the corresponding response.
Notice that the roles of the user agents (communication end points) are
not fixed, and they change on the transaction by transaction bases. The user
agent that creates a new request becomes a user agent client (UAC), whereas
the user agent that receives the request becomes the user agent server (UAS).
Another important detail is that a new transaction (either invite or non-
invite) may not be started while an invite transaction is in progress. Alter-
natively, a new invite transaction may be started while a non-invite trans-
action is in progress.
Besides user agents, the SIP standard defines three types of SIP servers,
namely, the proxy server (stateful or stateless), the registrar, and the redirect
server. A proxy server is the mediator that helps end points set up the session.
Officially, it is an intermediary entity that acts as both a server and a client
for the purpose of making requests on behalf of other clients. A registrar is
a server that supports the registration of the user agents by maintaining the
corresponding database for the domain it handles. This database is referred
to as a location service. These two types of servers are most frequently
collocated in the same physical machine. A redirect server can be viewed as
a proxy server with limited capabilities. It is only capable of directing the
client to contact an alternate set of Uniform Resource Identifications (URI).
Requests and responses between a server and a client are sent as SIP
messages. The SIP message comprises the start line, one or more header
fields, empty lines (carriage-return line-feed sequences, CRLF), and an
optional message body. The start line is different in requests and in responses.
In the former case, it is referred to as a request line, and in the latter as a
status line. The request line comprises the method name (six methods are
available in SIP: REGISTER, INVITE, ACK, CANCEL, BYE, and OPTIONS),
the request URI, and the SIP version (currently “SIP/2.0”). The status line
comprises the SIP version, the status code (a three-digit integer result code),
and the reason phrase (textual status description).
The SIP protocol stack comprises four layers. Starting from the top and
going down the hierarchy, these are the transaction user (TU) layer, the
transaction layer, the transport layer, and the syntax and encoding of SIP
messages. A transaction user is any SIP entity (client or server) except for
the stateless proxy. The transaction layer supports transactions, which are
the key component of SIP. The transport layer provides for the transfer of
SIP messages across the Internet. SIP may use three types of transport ser-
vices, including unreliable (UDP), reliable (TCP), and encrypted (Transport
Layer Security, TLS) transport service. Much of SIP message and header field
syntax is identical to HTTP/1.1. Although SIP is close to the HTTP philos-
ophy, it is not an extension of HTTP.
As mentioned above, the SIP standard specifies six methods, including
REGISTER for registering contact information, INVITE, ACK, and CANCEL
for setting up sessions, BYE for terminating sessions, and OPTIONS for
querying servers regarding their capabilities. Any INVITE after the initial
invite to the same destination is called re-INVITE and is used for modifying
the session and dialog parameters. The method INVITE starts the invite
transaction; all other methods start non-invite transactions. Interestingly
enough, six status code types are also found, depending on the value of
status code first digit, as follows:
1xx: Provisional (the request has been received and its processing has
been started)
2xx: Success (the request has been successfully processed)
3xx: Redirection (further action by the client is needed)
4xx: Client error (the request contains an error or it may not have been
fulfilled on this server)
5xx: Server error (the request is valid, but the server failed to fulfill it)
6xx: Global failure (the request cannot be fulfilled on any server)
p1 : Proxy p2 : Proxy
INVITE
INVITE
100 Trying INVITE
100 Trying
180 Ringing
180 Ringing
200 OK
200 OK
ACK
Media Session
BYE
200 OK
FIGURE 2.13
The example of SIP session setup (with SIP trapezoid).
At this point, ua2 indicates the incoming invite request to its user. The user
accepts the request and ua2 sends back the response 200 OK, which is
forwarded by the proxies p2 and p1 to ua1. The dialog between ua1 and ua2
is successfully established. Further on, ua1 sends the ACK request to ua2
directly (the end of the three-way handshake). The session is successfully
established at this point. The communicating user agents may now exchange
the media.
The session may be terminated by either ua1 or ua2. Suppose that ua2
wants to terminate the session. It sends the BYE request to ua1 directly, which
in its turn sends back the response 200 OK. The session is successfully closed.
This is an example of the non-invite transaction.
This simplified explanation hides one rather important aspect of the
invite three-way handshake, and that is the application of the offer-answer
procedure. This procedure is used by ua1 and ua2 to determine the session
parameters in accordance with SDP. The first offer must be carried either by
the invite request or by the response 200 OK. If the offer is carried by the
invite request (ua1 makes the first offer), the answer must be included in the
response 200 OK. If the offer is carried by the response 200 OK (ua2 makes
the first offer), the answer must be included in the ACK request (the last
message in the three-way handshake). The session is successfully established
only after the offer-answer procedure is successfully ended.
User
Use application
«uses»
Use transaction
user layer
«uses»
Use transaction
«uses» layer
«uses»
FIGURE 2.14
The use case diagram of the simple SIP softphone.
RFC 3261 but they are also not forbidden. These relations are introduced to
minimize the message paths at the expense of the increased relations com-
plexity.
To complete the requirements model, we need to describe the individual
use cases. The use case Use application is actually the main program that
interacts with the user and makes use of the SIP protocol stack and is out
of the scope of this book. The use case Use TU is responsible for dispatching
TU messages (coming from the application and the lower layers and going
to the user agent clients and servers and to the application), as well as for
dynamic creation of user agent clients and servers.
The use case Use UAC provides a set of procedures for the client side of
the transactions. The high-level description of these procedures follows:
User ∗
Use application
«uses»
Use TU
«uses» «uses»
«uses»
Use UAC Use UAS
«uses»
Use TLI
FIGURE 2.15
The detailed use case diagram of the simple SIP softphone.
The use case above includes only the main flow of events. A more detailed
version would also include the exceptional flow of events that would
describe the time management and the retransmissions of the unacknowl-
edged SIP messages. These are skipped here for brevity (in reality, we also
start from a very simple version of use cases and refine them later). The same
is true for all the other use cases given in this subsection.
The use case Use UAS provides the set of procedures for the server side
of the transactions. The high-level description of these procedures follows
(the implementation is rather simple and it takes the passive and goodwill
approach):
The use case Use TAL is responsible for dispatching TAL messages (coming
from TU, UAC, UAS, and TLI and going to the TAL transactions), as well
as for dynamic creation of TAL transactions. The use case Use INVITE CT is
an invite client transaction. Its description follows:
The use case Use INVITE ST is an invite server transaction. Its description
follows:
The use case Use TLI is responsible for dispatching transport messages. It
routes the requests from upper layers toward its remote peer in a forward
direction, and routes the responses received from its remote peer toward the
upper layers in a backward direction (non-ACK responses are sent to TAL,
whereas ACK responses are sent to TU). It may use UDP, TCP, or TLS for the
communication with its peers over the Internet. The description of this use case
follows:
Now that we have completed the use case diagram, we can proceed to the
next engineering phase. This phase is the analysis, whose main goal is the
definition of the software architecture.
• The instance of the class FSM named app realizes the use case Use
application.
• The instance of the class TUDisp named tud realizes the use case Use
TU.
• An unnamed instance of the class UAClient realizes the use case Use
UAC.
• An unnamed instance of the class UAServer realizes the use case Use
UAS.
• The instance of the class TALDisp named tald realizes the use case
Use TAL.
• An unnamed instance of the class InClientT realizes the use case Use
INVITE CT.
• An unnamed instance of the class NIClientT realizes the use case Use
non-INVITE CT.
• An unnamed instance of the class InServerT realizes the use case Use
INVITE ST.
• An unnamed instance of the class NIServerT realizes the use case
Use non-INVITE ST.
app :FSM
tud : TUDisp
: UAClient : UAServer
tald : TALDisp
tlid : TLIDisp
FIGURE 2.16
The general collaboration diagram of the simple SIP softphone.
• The instance of the class TLIDisp named tlid realizes the use case Use
TLI.
• The instance of the class FSM named udp realizes the use case Use
UDP.
• The instance of the class FSM named tcp realizes the use case Use
TCP.
• The instance of the class FSM named tls realizes the use case Use TLS.
The mapping given above translates the use case diagram shown in Figure
2.15 into the general collaboration diagram shown in Figure 2.16. This dia-
gram actually shows the software architecture, which defines the software
objects that constitute the software system or product and the associations
among them.
app :FSM
1: inviteReq(adr)
18: inviteRsp(adr)
r)
(ad
Req )
i n vite p(1XX
:
2 1: r s 0)
1 rsp(20 dr)
a
16: eRsp( tud : TUDisp
it
7 : inv
1
: UAClient : UAServer
3: r
eq(
IN
10 5: rsp
VIT
: rs ( 2
1
E)
p(1 00
XX )
X)
tald : TALDisp
14
:
4: 9: rs rsp(
req p( 20
(IN 1X 0)
13: rsp(200)
VI X)
8: rsp(1XX)
tlid : TLIDisp
6: req(INVITE)
12: rsp(200)
7: rsp(1XX)
FIGURE 2.17
The collaboration diagram showing the part of the SIP session setup.
The software architecture can be used for the further study of particular
object collaborations to check if the architecture is feasible and, if not, to
refine the use case or collaboration diagram. An example of a particular
collaboration is shown in Figure 2.17. This diagram shows the handling of
the invite request initiated by the softphone user. The flow of events is as
follows:
1: The object app sends the event inviteReq(adr) to the object tud.
2: The object tud sends the event inviteReq(adr) to an unnamed instance
of the class UAClient.
3: The unnamed instance of the class UAClient sends the event req(IN-
VITE) to the object tald.
Generally, req() and rsp() designate SIP requests and SIP responses in the
flow of events shown above. For example, req(INVITE) is the SIP invite
request, rsp(1xx) is the SIP provisional response, and rsp(200) is the SIP final
response.
References
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Modeling Language User Guide,
Addison-Wesley, Reading, MA, 1998.
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Software Development Process,
Addison-Wesley, Reading, MA, 1998.
Broekman, B. and Notenboom, E., Testing Embedded Software, Addison-Wesley,
London, 2003.
Meyer, S. and Apfelbaum, L., “Use Cases Are Not Requirements,” http://www.
geocities.com/model_based_testing/online_papers.htm, 1999.
Rosenberg, J. et al., “RFC 3261 – SIP: Session Initiation Protocol,” http://www.faqs.
org/rfcs/rfc3261.html, 2002.
3
Design
• System structure
• System behavior
The system structure defines the elements of the system and their associ-
ations. Sometimes it is referred to as the static structure because it defines
the static view of the system, i.e., a view without any respect to time. The
system behavior defines the outputs of the systems as functions of time or
their inputs. In the case of a family of communication protocols, which are
most frequently modeled as groups of finite state machines (automata), the
static structure defines the automata and the links between them whereas
the system behavior defines the state transitions for the individual automata
and the external messages.
Besides system synthesis, or system design, the communication protocol
design phase described in this book includes two additional designs, namely
deployment design and test design, which result in a deployment model
and a test model, respectively. The main goals of the deployment design are
identifying network nodes and configurations as well as identifying design
subsystems and interfaces. The deployment model is especially important
for the complex communication systems comprising many distributed
45
components. For less complex systems, it is not as important, and for very
simple systems it may not even be necessary.
Although the system design and deployment models make the complete
vision of the system, they do not specify how the system can be verified.
Therefore, the engineers conduct the test design by taking the requirements
and design models and creating a test model. The test model actually defines
the behavior of the testers, who emulate the environment of the system. As
already mentioned in the previous chapter, the test model is most frequently
referred to as a test suite, which comprises a set of test cases. Each test case
specifies a series of test input values (events and messages) to the system
and the corresponding output values (events and messages) that are
expected at the system output as the results of correct system reactions to
the given series.
To summarize, a communication protocol design is a process that takes
the requirements and analysis as its input and provides the following models
as its output:
The means of making these models today are UML diagrams or some
domain-specific languages, which are introduced in this chapter. The design
engineer starts from the analysis model, essentially a collaboration of
<<boundary>>, <<control>>, and <<entity>> classes, described in the corre-
sponding collaboration diagram. The development model is made by map-
ping each class from the analysis model to a set of new classes in the
development model. If the analysis model is well refined, this might even
be a one-to-one mapping or close to it. For example, the analysis model of
the SIP softphone given at the end of the previous chapter is detailed enough,
and the corresponding collaboration diagram is a good base for the refine-
ments that must be made during the system design phase.
The means of defining the static structure of the system in UML are class
diagrams and object diagrams. A class diagram shows the design classes
and the static relations (dependencies, associations, and generalizations)
among them without any respect to time. It shows important details about
classes, such as their members, fields and functions, and furthermore their
types, visibility, and so on. The object diagram is similar to the class diagram
except that it shows the system frozen at a certain moment of time. Typically,
the object diagram will show system objects (class instances) with the char-
acteristic and important values of certain field members.
The means of gathering and refining details about the system behavior are
the UML interaction diagrams. Two types of interaction diagrams are found,
namely collaboration diagrams (introduced in the previous chapter) and
sequence diagrams. Collaboration diagrams show the interaction organized
Design 47
• Application-specific layer
• Application-general layer (e.g., packages common for a set of appli-
cations)
• Middleware layer (e.g., Java VM and Java packages)
• System-software layer (e.g., TCP/IP protocol stack)
• Application layer
• Presentation layer
• Session layer
• Transport layer
• Network layer
• Data link layer
• Physical layer
• Application layer
• Transport layer
• Network layer
• Network interface layer
Design 49
corresponding vertical lines — and the messages they exchange, which are
rendered as horizontal arrows connecting the source and the destination
vertical lines.
Finally, this chapter covers the third domain-specific language, TTCN,
which is used for making test models more formal than in UML. In contrast
to the UML test model, which is rather descriptive and more like a general
framework, TTCN is a well-defined language for defining test suites. As
already mentioned, it originates from the ISO and has been traditionally
used for the conformance testing of communication protocols.
TTCN, much like the higher-level programming language, has built-in
types and allows a user to define new types (simple and structured), vari-
ables, constraints, and functions in specialized tables. The essence of the
TTCN test case specification is an indented tree of events that is filled in a
table, which specifies the behavior of the testers that run the test case and
the outcomes of the test case (pass, fail, or inconclusive).
The next sections describe the class diagrams, the object diagrams, the
sequence diagrams, the activity diagrams, the statechart diagrams, the
deployment diagrams, the SDL diagrams, the MSC charts, and the TTCN
tables. The chapter ends with a series of design examples.
Design 51
Class «interface»
Interface Interface
Object : Class
Package «subsystem»
Subsystem
∗ ∗
AssociationClass
1 ∗
∗ ∗
FIGURE 3.1
The basic set of graphical symbols available for rendering class diagrams.
example, we will render the FSM library as a package that is used by the
protocols that are the subjects of design and implementation.
We use packages and subsystems to manage complexity. Alternately, we
render class instances (objects) in class diagrams to manage ambiguity, espe-
cially when we want to explicitly show the dynamic type of an instance or
some other hidden details of the system. A special type of class diagrams
are object diagrams, which will be described in the next section of this
chapter.
Like use case and collaboration diagrams described in the previous chap-
ter, class diagrams are normally also rendered using some of the commer-
cially available graphical tools, e.g., Microsoft Visio. The same is true for
other UML diagrams described in this chapter. The basic set of graphical
symbols available for rendering class diagrams is shown in Figure 3.1. The
design engineer must specify properties for each instance of a symbol in the
drawing.
The most frequently used symbol in class diagrams is the class symbol.
Eight categories of class properties exist: the general information, the table
of attributes, the table of receptions, the table of parameters, the list of
components, the table of constraints, and the tagged values. The general
information includes the name, the full path, the stereotype (delegate, imple-
mentation class, metaclass, structure, type, union, or utility), the visibility
(private, protected, or public), and the indicators for the Root, Leaf, Abstract,
and Active types of classes. The table of attributes comprises columns for the
attribute name, the type, the visibility, the multiplicity (1, *, 0..1, 0..*, 1..1, or
1..*), and its initial value. The table of operations comprises columns for the
operation name, the return type, the visibility, the scope (classifier or
instance), and the indicator for the polymorphic operations. The table of
receptions includes columns for the reception name, the corresponding sig-
nal name, the visibility, the scope, and the indicator for the polymorphic
operations. The table of template parameters stores parameter names and
types. The list of components comprises names of the components that
implement this class. The table of constraints consists of four columns: the
constraint name, the stereotype (precondition, postcondition, or invariant),
the language type (OCL, text, pseudocode, or code), and the body of the
constraint. The tagged values include the notes for the documentation, the
location, the persistence, the responsibility, and the semantics.
Two graphical symbols are available for rendering interfaces. The first
shows just the name of the interface, whereas the second also shows the
available operations. Being the specialized classifier, the interface properties
are a subset of class properties. More precisely, the interface has four cate-
gories of properties: the general information, the table of operations, the
table of constraints, and the tagged values. Those properties are the same as
the corresponding class properties with a single exception. The interface is
passive in its nature, hence the general information might not include the
indicator of Active type.
The package has four categories of properties: the general information, the
table of events, the table of constraints, and the tagged values. The general
information includes the name, the full path, the stereotype (facade, frame-
work, stub, or system), the visibility (private, protected, or public), and the
indicators for the Root, Leaf, and Abstract types of packages. The table of
events stores the event names and the types.
The subsystem has four categories of properties: the general information,
the table of operations, the table of constraints, and the tagged values. The
general information includes the name, the full path, the visibility, and the
indicators for the Root, Leaf, Abstract, and Instantiable types of subsystems.
The object has four categories of properties: the general information, the
table of attributes, the table of constraints, and the tagged values. The general
information about the object includes the object name and the corresponding
class name. The tagged values are just documentation notes and the tag
persistent value.
The dependency relation has three categories of properties: the general
information, the table of constraints, and the tagged values. The general
information includes the name, the stereotype (becomes, call, copy, derived,
friend, import, instance, metaclass, power type, or send), and the description.
The tagged values are the notes for the documentation.
The generalization relation has three categories of properties: the general
information, the table of constraints, and the tagged values. The general
information comprises the name, the full path, the stereotype (extends, inher-
its, private, protected, subclass, subtype, or uses), and the discriminator. The
tagged values are documentation notes.
Design 53
1
1 1 Port
1
∗
Transport TCP UDP
∗
FIGURE 3.2
An example of a simple model of the TCP/IP protocol stack.
relations between the class Router and the classes that model the individual
layers.
The right side of Figure 3.2 shows some of the applications and protocols
available in the TCP/IP family of protocols. The electronic mail and World
Wide Web (WWW) applications — and their corresponding protocols — are
modeled by the class Email and WWW, respectively. These two applications
are the examples of particular applications, and this fact is modeled by the
generalization and specialization relations between the class that models a
generic application (Application) and the classes that model the particular
applications (Email and WWW). Similarly, TCP and UDP are particular trans-
port protocols (modeled by the classes TCP and UDP), and this is modeled
by the generalization and specialization relations between the class that
models a generic transport protocol and the class that model TCP and UDP.
Further down the hierarchy, the Internet network layer comprises the IP
and ICMP protocols (modeled as the classes IP and ICMP). This is modeled
by the composition relations between the classes that model the network
layer and the IP and ICMP protocols. At the bottom of the hierarchy, we
show that various types of interfaces exist, e.g., Ethernet and serial, by
generalization and specialization relations between the class Interface and
the classes Ethernet and Serial, which model these particular interfaces.
Design 55
Automata
1
1
∗
FIGURE 3.3
An example of a simple automata model.
of the class Transition to 0..* (because a state may have zero or more outgoing
and zero or more incoming state transitions). The navigability of these two
association relations is set such that the relation FromSourceState points from
the class State to the class Transition, whereas the relation ToDestinationState
points in the opposite direction.
The main problem with this model is ambiguity. The source and the des-
tination states may seem to be always the same (because both FromSource-
State and ToDestinationState association relations are connected to the same
class, namely the class State). However, source and destination states can be,
and most frequently are, different states. We will come back to this point
shortly, after introducing additional nodes and relations available for ren-
dering class diagrams, to resolve this problem in a less ambiguous way.
The key abstractions related to the transition are the condition that guards
the transition, the event that fires the transition, and the action that is taken
by the transition, which are modeled by the classes Condition, Event, and
Action. Each transition is characterized by these three optional elements, and
that is modeled by the composition relations between the class Transition
and the classes Condition, Event, and Action. The fact that these elements are
optional is modeled by setting the multiplicity to 0..1 from the side of the
corresponding classes.
Besides actions that are taken during the transitions, we can define state
bound actions, such as the action that is taken at the entrance to a certain
state, the action that is performed while the system is in a certain state, and
the action that is taken at the exit from a certain state (we will encounter
these and more in the UML statecharts later in this chapter). These action
types are modeled as the state operations entry(), do(), and exit(), which are
defined in the table of operations for the class State.
Until now, we were modeling a generic finite state machine. To make this
model useful for the implementation of a particular finite state machine, first
we need to define the concrete conditions, events, and actions. We do so
through the specialization of the base classes Condition, Event, and Action.
Figure 3.3 shows the examples of the particular condition, event, and action,
which are modeled by the classes ConditionX, EventY, and ActionZ, respec-
tively. Finally, to build the particular finite state machine, we need to instan-
tiate the classes.
This concludes the presentation of two simple examples of class diagrams.
To make this graphical language more expressive and to reduce the ambi-
guity of the class diagrams, the graphical tool provides the additional set of
graphical symbols, which are shown in Figure 3.4. The first of them is the
metaclass, whose instances are classes that are added to the class diagram.
We can resolve the problem of ambiguity in the previous example exactly
by using the metaclass instead of the class symbol because it is then clear
that the source and the destination state may both be the same state or two
completely different states. Again, as for the basic set of symbols, the addi-
tional symbols have similar categories of properties. The metaclass has the
Design 57
Parameter
«datatype» «utility»
DataType Utility ParamClass
«bind» «traces»
BoundElement
AssociationClass
∗ ∗
∗
∗ ∗
∗
FIGURE 3.4
The additional graphical symbols available for rendering class diagrams.
same properties as the class, with the exception that its stereotype (in general
information section) is fixed to metaclass.
Both the signal and the exception symbols have the same four categories
of properties, namely, the general information, the table of parameters, the
table of constraints, and the tagged values. The general information is the
same as for the interfaces (the name, the full path, the visibility, and the
indicators Root, Leaf, and Abstract). The table of parameters stores the infor-
mation about the parameters, which comprise the parameter name, the type,
the kind (in, out, or in-out), and the default value.
The data type has five categories of properties. These are the general
information, the table of enumeration literals, the table of operations, the
table of constraints, and the tagged values. The general information includes
the name, the full path, the stereotype (none or enumeration), the visibility,
and the indicators Root, Leaf, and Abstract. If the data type is an enumeration,
the table of enumeration literals holds the information about the literal
names and the corresponding values.
A utility is a special class, therefore it has the same properties as the class
with the exception that its stereotype is fixed to utility. Similarly, a parame-
terized class is a special class that has one or more unbound formal param-
eters, therefore it has the same categories of properties as the class. Related
to the parameterized class is a bind relation, that binds (connects) the des-
ignated arguments to the template formal parameters. It has four categories
of properties: the general information (just the name and the description),
the list of bound arguments, the table of constraints, and the tagged values.
The bound element adds the result of binding between the template param-
eters and their actual values. It has the same categories of properties as the
class.
The next three symbols are the traces, refines, and uses relations. We can
think of them as specialized dependency relations. The traces relation con-
nects two model elements from two different models. The refines relation
connects a more detailed model element to its previous version. The uses
relation indicates the dependency relationship between two model elements
where one requires another to fully operate. All these relations have the same
categories of information as the dependency relation, with the exception that
their stereotype is fixed.
The next four symbols are the note, the constraint note, the constraint
shown as arrow, and the OR constraint, which we have already encountered
in both use case and collaboration diagrams (described in the previous
chapter). The last three symbols are used to describe the relations among
more than two model elements. The first is the N-ary association, which
models the association among more than two classifiers. Its properties are
the same as for the binary association with the additional properties for each
association end (the name, the aggregation, the visibility, the multiplicity,
and the navigability indicator).
The second symbol is the N-ary association class, which models more
complex associations among more than two classifiers. Again, its properties
are the same as for the binary association class with additional properties
for each association end. The third and the last symbol is the N-ary object
link, which interconnects more than two objects. Its properties are the same
as the binary object link with additional properties for each end (the name
and the stereotype).
At the end of this section, we focus on the domain-specific class diagrams.
As already mentioned, the reader should assume and accept that somebody
has already prepared the infrastructure for the design and implementation
of communication protocols. There is no need to start modeling generic
Design 59
Automata
-Attributes
-StateTransition1() FSMLibrary
-StateTransition2()
-StateTransition3() «uses»
+Initialize()
+Start() 1 1
1 1
FiniteStateMachine FSMSystem
This is not the complete -NumOfStates -PostOffice
specification of the -NumOfTimers -Buffers
class FiniteStateMachine. -MaxNumOfProcPerState -Timer
Its just a snippet that -States #Automates
should give you an idea -ConnectionId #NumberOfMbx
of its complexity. -GroupId #NumberOfAutomates
-CallId -NumberOfObjects
-LeftMbx -FreeKernelMemory : bool
-LeftAutomate -SystemWorking : bool
-LeftGroup
#GetBuffer()
-LeftObjectId #GetMsg()
-RightMbx #GetMsgToAutomate()
-RightAutomate #GetMsgToGroup()
-RightGroup #GetMsgInfoLength()
-RightObjectId #GetMsgObjectNumberTo()
-State #SendToMbx()
#GetLeftMbx() +FSMSystem()
#GetLeftAutomate() +~FSMSystem()
#GetLeftGroup() +Add()
#GetLeftObjectId() +Delete()
#SetLeftMbx() +InitKernel()
#SetLeftAutomate() +Start()
#SetLeftObject() +StopSystem()
#SetLeftObjectId()
#Initialize()
#InitEventProc()
#InitUnexpectedEventProc() This is not the complete
+FiniteStateMachine() specification of the
+~FiniteStateMachine() class FSMSystem.
+Process()
FIGURE 3.5
A typical communication protocol class diagram.
automata every time we start a new project, but rather we do it once and
then use it on a number of projects. This practice is what in UML is called
providing generic design mechanisms.
In this book, we design and implement communication protocols based
on the FSM library. A typical class diagram is shown in Figure 3.5. The FSM
library is shown as the package FSMLibrary in the diagram and, on most
occasions, such representation would be sufficient. It actually comprises a
rather ramified hierarchy of C++ classes (we will go into more details in the
next chapter). The two most important classes are the FiniteStateMachine and
FSMSystem. The fact that the FSM library contains these classes is modeled
by the composition relations between the package FSMLibrary and the classes
Design 61
Object diagrams, like class diagrams, are used to show the static design
view of the system. As already mentioned in the previous chapter, the col-
laboration diagram is used to model the behavior of the system. It also shows
the architecture of the system; hence, we say that the collaboration diagram
is organized by the architecture. We can think of the object diagram as one
snapshot of the collaboration diagram. Imagine that time is frozen. Whatever
we can see in the collaboration diagram at that single moment of time is an
object diagram.
Later in this chapter, we will introduce the deployment diagrams, and we
will introduce the component diagrams in the next chapter. Both deployment
and component diagrams can contain only objects and their links. In such
cases, they are actually pure object diagrams.
Clearly, the graphical symbols available for rendering object diagrams are
the same as the symbols used for class diagrams (sometimes referred to as
a static structure). In practice, we use only a very limited subset of those
symbols, most frequently only two of them (object and object link).
The properties of these symbols are described in a previous section of this
chapter.
Usage of object diagrams can reduce the ambiguity of the static structure
twofold. First, by rendering instances of classifiers, we can better understand
the relations among them. For example, by rendering just the classes in the
TCP/IP protocol stack model, it may not be clear what the network really
looks like. Second, by showing the values of the key class attributes, we can
recognize reality more easily. For example, by showing the status of the
individual protocols, we can also comprehend their expectations from other
cooperating protocols.
These ideas are illustrated by the following two examples. The first is the
object diagram that shows the snapshot from a simple mail transfer protocol
(Figure 3.6). The second is the example of a simple finite state machine object
diagram (Figure 3.7).
Figure 3.6 shows the software running on two host computers that are
connected to two different local area networks, which are interconnected by
the router. The host computers clearly require full protocol stacks whereas
the router requires only the two lowest level layers (IP and network inter-
face). One host computer, shown on the left side of the figure, runs the SMTP
client on top of the TCP/IP protocol. The other host computer hosts the
SMTP server.
The first benefit of this object diagram is that it really makes clear which
layers are required by the hosts and which are required by the routers.
Graphically, we see the network, which was rather difficult to visualize just
by looking at the class diagram shown in Figure 3.3. Enough order is found
in this object diagram, too. More symbols are used than in the class diagram,
but only five per host and two per router. Of course, if we try to model a
large network there would be a flood of objects; therefore, we should always
try to restrict our modeling to a certain aspect of a system.
FIGURE 3.6
A snapshot from the simple mail transfer protocol (SMTP).
aut : Automata
currentState = S0
states = (s0, s1)
FIGURE 3.7
An example of a simple finite state machine (FSM) object diagram
.
Design 63
The second benefit is that we can peacefully study all the details of a certain
moment in the life of a protocol, in this case SMTP. It is like looking at the
photograph of a certain party. This one shows the moment when the SMTP
server has prepared the message 220 READY FOR MAIL and its intention
was to send it at the moment when the time has been frozen. We can imagine
what the sensation of looking at a series of such object diagrams would be,
like watching a replica of an important event in a game in slow motion. After
receiving the message 220 READY FOR MAIL, the SMTP client would pre-
pare the message HELO, and so fortth.
Besides current messages, other details are also important. For example,
we see in Figure 3.6 that the TCP port number 25 is opened from both sides,
and from there we can deduce that the SMTP client and server had to
establish the TCP connection in the first place before they could proceed any
further. Some details may seem obvious, for example that all Ethernet cards
and their drivers must be active, but they also help in making the complete
picture of the selected moment. In a series of object diagrams, the changes
of values of certain attributes, such as status, are the most interesting and
most informative part.
The second example of object diagrams is a simple finite state machine
object diagram, which is shown in Figure 3.7. A simple finite state machine
object, named aut, is an instance of the class Automata (Figure 3.4). It com-
prises a set of two state objects, namely s0 and s1, which are the instances
of the class State. Their identifications are S0 and S1, respectively. The current
state of the automata is the state with the identification S0.
The state object s0 contains a set of two transition objects, namely t00 and
t01, which are the instances of the class Transition (Figure 3.4). Similarly, the
state object s1 contains a set with one transition object, named t10. The
transition objects t00, t01, and t10 model the automata state transitions from
the state with the identification S0 to the state with the identification S0, or
more briefly from S0 to S0, next from S0 to S1, and last from S1 to S0,
respectively.
The attributes of the transition objects are the transition identification, the
condition that guards the transition, the event that fires the transition, the
action that is taken by the transition, and the next state identification. Their
identifiers are id, condition, event, action, and nextSate, respectively. id and
nextState would typically be strings or integers. condition, event, and action
are the instances of the class Condition, Event, and Action.
An important detail is that the values of these attributes are the instances
of classes that are specialized from the classes Condition, Event, and Action.
For example, the values of the attribute condition, namely con00, con01, and
con10, are the instances of the classes, e.g., Condition00, Condition01, and
Condition10, which are actually specializations of the class Condition. Such
modeling allows us to use polymorphism, the most powerful abstraction of
object-oriented design and programming.
• Object lifeline
• Focus of control
Design 65
• Flat
• Call
• Return
• Asynchronous
The flat message models the communication between the objects that
convey information, which should result in an action. The call message
Object : Class
Note (Constraint)
(Constraint) (OR)
FIGURE 3.8
The set of graphical symbols available for rendering sequence diagrams.
models a synchronous procedure call that should result in some action. The
return message models returns from the procedure, which convey the return
value that will cause an action. The asynchronous message models the asyn-
chronous communication between two objects, which also carries some
information that will trigger an action. The note, the constraint note, the
constraint, and the OR constraint are symbols that we have already encoun-
tered and explained in previous sections.
Next, we illustrate the use of sequence diagrams by four examples shown
in Figure 3.9, Figure 3.10, Figure 3.11, and Figure 3.12, which are semantically
equivalent to the collaboration diagrams shown in Figure 2.9, Figure 2.10,
Figure 2.11, and Figure 2.12, with one exception. Figure 3.9 and Figure 2.9
do relate to the same interaction, but they are not exactly semantically equiv-
alent because of two reasons. First, the former shows fewer objects than the
latter, mainly because of the limited diagram width. Second, the latter shows
only a part of the interaction shown by the former. Interestingly enough, this
seems to be a general rule. The sequence diagrams typically show fewer
objects but more messages than do collaboration diagrams.
The example shown in Figure 3.9 generally illustrates the same use case
Send e-mail as does the collaboration diagram shown in Figure 2.9. Figure
3.9 shows only the most important subset of objects but, at the same time,
it illustrates the interaction long enough to show the moment when the SMTP
client sends the SMTP message DATA toward the SMTP server. The
Design 67
1: sendMail(g)msg)
2: domainToIP(domain)
3: dnsReq(domain)
4: dnsRsp(ip)
5: ipadr(ip)
6: open(ip, 25)
7: seg(syn)
8: seg(syn+ack)
8.1: openAck
8.1.1: openAck 8.2: seg(ack)
8.2.1: openAck
9: mail(220)
10: seg(220)
11: mail(220)
12: mail(HELO)
13: seg(HELO)
14: mail(HELO)
15: mail(250_OK)
16: seg(250_OK)
17: mail(250_OK)
18: mail(MAIL_FROM:)
19: seg(MAIL_FROM:)
20: mail(MAIL_FROM:)
21: mail(250_OK)
22: seg(250_OK)
23: mail(250_OK)
24: mail(RCPT_TO:)
25: seg(RCPT_TO:)
26: mail(RCPT_TO:)
27: mail(250_OK)
28: seg(250_OK)
29: mail(250_OK)
30: mail(DATA)
FIGURE 3.9
A sequence diagram showing the interaction between a simple program for sending and
receiving e-mails and its environment.
dnsc dnss
1: dnsReq(domain)
2: dnsRsp(ip)
FIGURE 3.10
A sequence diagram showing the interaction between the DNS client and the DNS server.
tcpc tcps
1: seg(syn)
2: seg(syn+ack)
3: seg(ack)
4: seg(data)
5: seg(fin)
6: seg(ack)
7: seg(fin+ack)
8: seg(ack)
FIGURE 3.11
A sequence diagram showing the interaction between two TCP entities.
1: The object mailc (not shown in the diagram) sends the signal send-
Mail(msg) to the object sender.
2: The object sender sends the signal domainToIP(domain) to the object
dnsc.
3: The object dnsc sends the signal dnsReq(domain) to the object dnss.
4: The object dnss sends the signal dnsRsp(ip) to the object dnsc.
5: The object dnsc sends the signal ipadr(ip) to the object sender.
6: The object sender sends the signal open(ip,25) to the object tcpc.
7: The object tcpc sends the signal seg(syn) to the object tcps.
8: The object tcps sends the signal seg(syn+ack) to the object tcpc. (The
event flow now forks into two parallel flows.)
Design 69
smtpc smtps
1: mail(220)
2: mail(HELO)
3: mail(250_OK)
4: mail(MAIL_FROM:)
5: mail(250_OK)
6: mail(RCPT_TO:)
7: mail(250_OK)
8: mail(DATA)
9: mail(354_START_MAIL_INPUT)
10: mail(MAIL_BODY)
11: mail(250_OK)
12: mail(QUIT)
13: mail(221)
FIGURE 3.12
A sequence diagram showing the interaction between the SMTP client and the SMTP server.
8.1: The object tcpc sends the signal openAck to the object sender. (The
first flow begins here.)
8.1.1: The object sender sends the signal openAck to the object
smtpc. (The first flow ends here.)
8.2: The object tcpc sends the signal seg(ack) to the object tcps. (The
second flow begins here.)
8.2.1: The object tcps sends the signal openAck to the object smtps.
9: The object smtps sends the signal mail(220) to the object tcps. (Note:
We have restarted the message numbering here for brevity. We pro-
moted 8.2.2 to 9.)
10: The object tcps sends the signal seg(220) to the object tcpc.
11: The object tcpc sends the signal mail(220) to the object smtpc.
12: The object smtpc sends the signal mail(HELO) to the object tcpc.
13: The object tcpc sends the signal seg(HELO) to the object tcps.
14: The object tcps sends the signal mail(HELO) to the object smtps.
15: The object smtps sends the signal mail(250_OK) to the object tcps.
16: The object tcps sends the signal seg(250_OK) to the object tcpc.
17: The object tcpc sends the signal mail(250_OK) to the object smtpc.
18: The object smtpc sends the signal mail(MAIL_FROM:) to the object
tcpc.
19: The object tcpc sends the signal seg(MAIL_FROM:) to the object tcps.
20: The object tcps sends the signal mail(MAIL_FROM:) to the object
smtps.
21: The object smtps sends the signal mail(250_OK) to the object tcps.
22: The object tcps sends the signal seg(250_OK) to the object tcpc.
23: The object tcpc sends the signal mail(250_OK) to the object smtpc.
24: The object smtpc sends the signal mail(RCPT_TO:) to the object tcpc.
25: The object tcpc sends the signal seg(RCPT_TO:) to the object tcps.
26: The object tcps sends the signal mail(RCPT_TO:) to the object smtps.
27: The object smtps sends the signal mail(250_OK) to the object tcps.
28: The object tcps sends the signal seg(250_OK) to the object tcpc.
29: The object tcpc sends the signal mail(250_OK) to the object smtpc.
30: The object smtpc sends the signal mail(DATA) to the object tcpc.
Another practical detail about sequence diagrams is that not only their width
but also their height is limited. Because of this, we are normally forced to
break the flow of events at a certain point. In the previous example, it was
after the object smtpc has sent the signal mail(DATA) to the object tcpc.
Typically, we would continue that flow on another sequence diagram. Good
practice is to pick the breaking points logically, for example, at the beginning
or at the end of certain communication phases.
Also important is to emphasize that the sequence diagram in Figure 3.9
shows only main flows of events. It does not show what happens in the case
of errors. The error handling is typically shown in separate sequence dia-
grams. We can use packages to wrap together all the related sequence dia-
grams.
Figure 3.9 shows also that the real overall interaction can be fairly complex.
To deal with complexity, we can focus on the individual virtual interactions
instead. For example, the sequence diagram showing the interaction between
the DNS client and server is a trivial one (Figure 3.10). The flow of events
then reduces to only the following two events:
1: The object dnsc sends the signal dnsReq(domain) to the object dnss.
2: The object dnss sends the signal dnsRsp(ip) to the object dnsc.
Similarly, the virtual interaction between two TCP entities, modeled by the
objects tcpc and tcps, is governed by the TCP protocol. It is slightly more
complex and comprises the following flow of events (Figure 3.11):
Design 71
1: The object tcpc sends the signal seg(syn) to the object tcps.
2: The object tcps sends the signal seg(syn+ack) to the object tcpc.
3: The object tcpc sends the signal seg(ack) to the object tcps.
4: The object tcpc sends the signal seg(data) to the object tcps. (This is the
data transmission phase.)
5: The object tcpc sends the signal seg(fin) to the object tcps.
6: The object tcps sends the signal seg(ack) to the object tcpc.
7: The object tcps sends the signal seg(fin+ack) to the object tcpc.
8: The object tcpc sends the signal seg(ack) to the object tcps.
Finally, the virtual interaction between the SMTP client and server, modeled
by the objects smtpc and smtps, is of the same order of complexity (Figure
3.12). The interaction is governed by the SMTP protocol. The corresponding
flow of events is the following:
1: The object smtps sends the signal mail(220) to the object smtpc.
2: The object smtpc sends the signal mail(HELO) to the object smtps.
3: The object smtps sends the signal mail(250_OK) to the object smtpc.
4: The object smtpc sends the signal mail(MAIL_FROM:) to the object
smtps.
5: The object smtps sends the signal mail(250_OK) to the object smtpc.
6: The object smtpc sends the signal mail(RCPT_TO:) to the object smtps.
7: The object smtps sends the signal mail(250_OK) to the object smtpc.
8: The object smtpc sends the signal mail(DATA) to the object smtps.
9: The object smtps sends the signal mail(354_START_MAIL_INPUT) to
the object smtpc.
10: The object smtpc sends the signal mail(MAIL_BODY) to the object
smtps.
11: The object smtps sends the signal mail(250_OK) to the object smtpc.
12: The object smtpc sends the signal mail(QUIT) to the object smtps.
13: The object smtps sends the signal mail(221) to the object smtpc.
Design 73
what should be the order (flow) of the activities in the scope of a single object
or in the scope of a set of objects that are involved in the interaction. The
means to do this in UML are the activity diagrams, which are similar to Pert
network charts. The alternative means to specify the behavior of single
objects in UML are statecharts, which will be introduced in the next section.
An activity diagram is essentially a flowchart that shows the flow of control
from activity to activity. If we model the behavior of a single object, we
render the flow of control within that single object. The activity diagrams
are even more powerful and they allow us to model the behavior of a group
of objects by rendering the flow of control in that larger scope. Additionally,
we can model a single flow of control or more concurrent flows of control
within both a single object and a group of objects.
In the context of a single object, we typically partition its behavior into a
set of its operations and then model the flow of control of these operations
individually. Therefore, the most elementary level of modeling by using
activity diagrams is the level of the object’s operation. On the opposite side
of the scope scale, we can model the workflow of a group of cooperating
objects, and we will return to that point shortly.
The most elementary activity is an action state. It is defined as an atomic
(i.e., uninterruptible) program computation. Examples of action sates are the
following:
ActionState
State
FIGURE 3.13
The basic set of graphical symbols available for rendering activity diagrams.
Design 75
openPort(p)
sendData(seg)
closePort(p)
FIGURE 3.14
An example of a simple sequence of activity states.
startTimer(T1);
sendPacket(d);
a = waitAnswer();
[T1 expired]
restartTimer(T1);
[else]
stopTimer(T1);
[ELSE]
return false;
[a==ACK]
return true;
FIGURE 3.15
An example of a simple flow of activities with branching.
Design 77
i = 0;
no operation i = i + 1;
[i < n]
sendFragment(i);
[ELSE]
FIGURE 3.16
An example of a loop in an activity diagram.
openPort(p);
sendData(); receiveData();
closePort(p);
FIGURE 3.17
An example of a simple set of concurrent flows.
and waits for the answer. These two activities are modeled by the activity
state sendPacket(d) and a=waitAnswer(), respectively.
• The guards must not overlap — this makes the flow of control
unambiguous.
• The guards must cover all possibilities — this ensures that the flow
of control is not going to freeze.
Precisely these two features force us to make complete models and spec-
ifications of activities that describe the behavior of the system. When we
render interaction (collaboration and sequence) diagrams, no such enforce-
ments are present and, mainly because of that, they remain unfinished. Of
course, at the time when we render interaction diagrams, we really do not
want to make them final; rather, we want to check the most important aspects
and scenarios and to make our analysis more comprehensive and useful for
the finalization later. Therefore, when we start rendering the activity dia-
gram, we already have a good overall vision, but non-overlapping and
complete coverage features are the driving forces of the design finalization.
One safe way to provide both of these features is to use only the decisions
with two outgoing transitions and to guard one of them by the keyword
ELSE, as in the example in Figure 3.15. Special attention should be paid to
the decisions with more outgoing transitions, which are guarded by explicit
expressions (i.e., without the keyword ELSE). However, the price that we
may pay for safety is ambiguity. For example, if the operation in the previous
example returns the value false, it might do so because the correct not
acknowledge answer (NAK) has been received. However, the operation will
return the same value if any other message (including corrupted ACK or
NAK) has been received.
The example in Figure 3.16 illustrates the usage of loops in activity diagrams.
Imagine that the IP protocol must route a datagram over a physical network,
which has the Maximal Transfer Unit (MTU) smaller than the datagram size.
Normally, the IP protocol partitions the datagram into fragments (that fit MTU)
and routes the resulting fragments individually in such cases. The standard
means to model repetitive activities in activity diagrams are loops.
Design 79
The example in Figure 3.16 starts by setting the control variable i to the
value 0. It continues with no operation activity state, followed by the decision
that checks the loop continuation condition (i < n). If the condition is satisfied,
the flow enters the loop body (sendFragment(i)). The loop body is followed
by the activity state that updates the control variable (i = i + 1). The example
terminates when the loop continuation condition becomes false.
The example in Figure 3.17 shows the usage of concurrent control flows.
Imagine that we want to model a simple communication over the TCP
connection. First, we must establish the TCP connection by opening a par-
ticular TCP port. We model this by the activity state openPort(p). Once the
connection is established, the TCP protocol provides simultaneous transfer
of data in both directions (full-duplex). To model that, we need to fork a
single flow of control into two parallel (concurrent) flows of control. One of
them enters the activity state sendData, which models the activity of sending
the data to the remote site. The other control flow enters the activity state
receiveData, which models the activity of receiving the data from the remote
site.
These two activities logically evolve in parallel over time. On a multipro-
cessor system, they can be deployed on two different processors to maximize
the system throughput. In such a case, these two activities would be parallel
in reality, also. Alternately, single-processor systems create quasi-parallelism
using the time-sharing operating system. The activities are then not parallel
in reality, but they are still concurrent because they can compete for the
same resources. Additionally, the activities can communicate using signals.
Traditionally, such communicating sequential processes are referred to as
coroutines.
Although the model shown in Figure 3.17 is fairly simple, it may reflect a
realistic communication, such as a Telnet session. Imagine that the activity
state sendData is a composite state that reads the user keystrokes and sends
them to the Telnet server over the TCP connection, in a loop, until the end-
of-file key combination is detected. The activity state receiveData in this
scenario would be also a composite activity state, which receives the
responses from the Telnet server and displays them on the monitor, in a loop,
until the end-of-communication signal is detected (typically, it would be sent
when the end-of-file key combination is detected).
Once one of the parallel activities finishes, it proceeds to the control flow
join synchronization point where it waits for the other parallel activity to
finish. When both of the activities are finished, the corresponding parallel
control flow joins into a single control flow, which enters the activity state
closePort(p) and, after finishing that activity, it terminates.
As we have seen from the previous example, fork and join synchronization
points are rendered as either thick horizontal or vertical lines. It is important
to remember that they must be balanced. Similar to the subexpression —
which must begin with the opening parenthesis and end with the closing
one — each nesting level of the concurrent control flows must begin with
the fork symbol and end with the corresponding join symbol. Apart from
Swimlane
Object : Class
Note (Constraint)
(Constraint) (OR)
FIGURE 3.18
The additional graphical symbols available for rendering activity diagrams.
that, no restrictions are placed on the number of nesting levels, at least not
in theory. Of course, in practice we should not go beyond a manageable
number.
The set of additional symbols that are available for rendering activity
diagrams is shown in Figure 3.18. These are the object in state, the object
flow, and the swim lane symbols, as well as the symbols common for all
diagrams, namely, the note, the constraint note, the two-element constraint,
and the OR constraint.
The object flow transition enables us to show how the object state changes
in the activity diagrams. Typically, we render the objects showing the current
and the new states and we connect them by the object flow transition. The
objects themselves may be results of activity states and can be used by other
activity states. The object flow symbol has the same four categories of prop-
erties as the control flow symbol (described previously in this section).
The swim lane has no strict semantics. It is normally used to show indi-
vidual parties in the workflows. The swim lane is typically implemented as
a class or a set of classes. It is better suited for modeling business processes,
but it can also be used for modeling communication protocols. The swim
lane has three categories of properties: the general information (essentially,
its name), the table of constraints, and the tagged values.
The example in Figure 3.19 illustrates the usage of objects, data flow
transitions, and swim lanes, with the example of activities initiated by the
Domain Name System (DNS) client request for mapping a given domain
name onto the corresponding IP address. Figure 3.19 is a type of a workflow
conducted by the DNS client and server in their cooperative work of
translating a domain name into the IP address. The DNS client is repre-
sented by the first swim lane and the DNS server is represented by the
second. This activity diagram shows both the control flow among individ-
ual activity states and data flow, which are created by a series of objects
that are consumed and produced by the activity states of both DNS client
and server.
The given domain name is the input parameter of the DNS client operation
that translates the domain name into the corresponding IP address. This
operation starts by the activity state createDNSmsg(), which creates an empty
Design 81
m1 = createDNSmsg();
m1 : DNSmsg
domain = ?
ip = ?
m1.setDomain(D);
ip = map(domain);
m2.setIP(ip);
return m3;
m3 : DNSmsg
domain = D
ip = IP
FIGURE 3.19
The workflow between the DNS client and server with the message flow.
DNS message. This action is modeled by placing the object m1 that represents
the DNS message in the activity diagram and by connecting it to the activity
state createDNSmsg() with the arrow pointing toward the object m1. This
means that the object m1 is produced by the activity state createDNSmsg().
The fact that the message is empty is indicated by showing that the values
of both attributes domain and ip are unknown (the unknown value is denoted
by the question mark character, “?”).
Next, the activity state sets the attribute domain to the value of the input
parameter D, thus creating a new state of the object m1. This action is
modeled by placing a new copy of the object m1 in the activity diagram and
by adding two object flow arcs. The first connects the previous object copy
and the activity state m1.setDomain(D). The arrow points toward the activity
state, which means that the state consumes the object. The second object
flow arc connects the activity state and the new copy of the object m1, thus
implying that the activity state produces it.
The control flow then forks into two independent flows. One is conducted
by the DNS client and the other is conducted by the DNS server. The DNS
client continues by sending the DNS message, as a DNS request, to the DNS
server. The corresponding activity state creates a new object, named m2, and
places it in the second swim lane, because we assume that the DNS server
runs on a different machine, or at least in a different address space. The DNS
server, in its turn, receives the DNS message. A common mechanism for
copying the message from an internal operating system buffer to the buffer
that is located within the address space of the DNS server is modeled by
placing two different copies of the object m2.
The DNS server continues by translating the given domain name into the
corresponding IP address and by setting the attribute ip to the value IP, which
denotes the result of that translation. This fact is shown in the third copy of
the object m2. The DNS server proceeds by sending the completed DNS
message, which models the DNS response message, to the DNS client, which
in its turn receives it and creates the copy of the object m3 in its address
space. Finally, two independent control flows join together and the DNS
client returns the completed DNS message to its user, thus creating the final
copy of the object m3.
As this example shows, the models of the workflows are useful because
they show and specify the external behavior, i.e., the interface and protocol
between the objects in the form of the corresponding sequence of messages
exchanged by the objects, as well as the internal behavior of objects in the
form of the series of activity states visited by them. The first is created by
modeling the data and object flow, and the second is created by modeling
the control flow across the objects. Again, by taking care of the complete
coverage of possibilities, without any overlaps, we ensure that the model is
complete. (This was not the main goal of the last example, at least not to the
extent as in the previous one, but we should keep that in mind.)
Figure 3.20 shows the activity diagram for one real protocol, TCP, and it
follows the conventions introduced by the corresponding IETF RFC 793. The
user requests are written in capital letters. The user requests are OPEN,
SEND, and CLOSE. Two types of OPEN requests are used, namely active
OPEN and passive OPEN. The difference between the two is who is taking
the initiative in the connection establishment procedure.
The next convention is that the names of the events and actions are written
in lowercase letters, with the following abbreviations:
Design 83
CLOSED
[active OPEN]
create TCB delete TCB
[passive OPEN]
snd SYN
create TCB
LISTEN [CLOSE]
[rcv SYN]
SYN RCVD snd ACK
[CLOSE]
[CLOSE]
FIN WAIT 1
snd FIN
[rcv FIN]
snd ACK
LAST ACK
[rcv ACK of FIN]
CLOSING
FIN WAIT 2
[rcv ACK of FIN]
[rcv FIN] [rcv ACK of FIN]
[Timeout = 2MSL]
snd ACK TIME WAIT delete TCB CLOSED
FIGURE 3.20
A TCP activity diagram.
The TCP events are actually modeled as guard expressions whereas the
TCP activities are modeled as UML action states (relatively short and unin-
terruptible series of executable statements). Notice that we could model the
TCP activities either by action or by activity states because these activities
are essentially interruptible. However, because they can be implemented as
rather short routines — which do not involve reception of any signals —
modeling them as action states makes more sense than as activity states.
The TCP protocol spends most of the time in one of its stable states waiting
for a certain event to occur. The TCP stable states are modeled by the UML
activity states. While being in one of its stable states, the TCP protocol just
waits for an event (it does not execute any statements). The process that
executes the TCP protocol is blocked and it does not compete for the pro-
cessor execution time. Therefore, the activity corresponding to the stable
state is more than interruptible — it is blocked. Because such an abstraction
is missing in the UML activity diagrams, we are forced to model it with an
abstraction that is the most close to it, and that is the activity state. The model
of the TCP protocol shown in Figure 3.20 comprises the following activity
states (the names of the states are taken from the RFC 793):
Design 85
The activity diagram shown in Figure 3.20 is fully compliant with the
original TCP standard. Interested readers can refer to IETF RFC 793 for more
details.
The last example in this section shows a model of a simplified send e-mail
operation. The corresponding activity diagram (Figure 3.21) is a straightfor-
ward implementation of a typical SMTP scenario (client side), which has
already been introduced in this chapter (Figure 3.12) and in the previous
chapter (Figure 2.12). Although simplified, in a sense that it just follows the
successful path of the SMTP scenario, it is a complete specification of a
desired behavior because it covers all possibilities in a non-overlapping
manner.
Again, as in the previous example, the events associated with the reception
of the corresponding messages are modeled as guard expressions, while the
actions taken by the SMTP client are modeled by the corresponding action
states. Additional similarity with the previous example is that the SMTP
client, like the TCP protocol, spends most of the time in its stable states,
waiting for a message from the SMTP server. If the received message is the
one expected, the SMTP client sends the next message, prescribed by the
ideal SMTP scenario, and proceeds to the next stable state. If the received
message is not the one expected, the SMTP client returns the value false and
the operation terminates.
The e-mail is successfully sent if all of the prescribed messages between
the SMTP client and server are successfully exchanged. In this case, the send
e-mail operation returns the value true and terminates.
[ELSE] [ELSE]
return false return false
[ELSE] [ELSE]
return false return false
[ELSE] [ELSE]
return false return false
WAIT 250
[ELSE]
return false
[rcv 250]
snd DATA
FIGURE 3.21
A simple send e-mail operation activity diagram (SMTP client side).
Design 87
statechart diagrams are normally used for modeling the lifetime of a single
object (typically, an instance of a class) or a use case. The activity diagrams
emphasize the flow of the action and the activity states, whereas the state-
charts emphasize the event-ordered behavior of an object, which is especially
suitable for modeling reactive systems.
The common feature of both activity diagrams and statechart diagrams is
that they aim at making complete models of behavior, i.e., for use in the
design back-end. The driving forces for providing complete behavior spec-
ifications are the same, namely, the complete coverage of possibilities with-
out overlaps. The styles differ a bit. By unwritten rule, the decision symbols
are extensively used in activity diagrams and seldom in statechart diagrams.
Therefore, the coverage of possibilities is shown explicitly in activity dia-
grams and more implicitly in statechart diagrams.
That the activity diagrams and statechart diagrams are semantically equiv-
alent is also important to emphasize, i.e., we can use both of them for
modeling the same behavior on the comparable level of details. They merely
provide two different views of the same behavior. The activity diagrams are
better suited for modeling individual operations whereas the statechart dia-
grams are better for modeling the behavior of entire stateful objects, espe-
cially if the behavior is driven by events (messages).
Statecharts were originally invented for modeling state machines, which
makes them a perfect tool for modeling communication protocols because
the protocols are essentially state machines. According to the UML termi-
nology, a state machine is a sequence of states an object goes through in its
lifetime. A state is a situation during which an object satisfies a certain
condition, performs an activity, or waits for an event. An event is an occur-
rence of a stimulus that triggers the state transition. An action is an atomic
executable statement (computation). An activity is a non-atomic execution
composed of actions and other activities. A transition is a relation between
the source and the target states (these can be different states or the same
state) that specifies the actions to be taken when the given event occurs and
the given guard condition is satisfied.
The key abstractions in the context of state machines are the object state and
the state transition. We can think of the object state as a period of an object’s
lifetime (it can be just a moment characterized by a certain condition, a period
of a certain activity, or an interval of time in which the object waits for a certain
event). Alternately, we can think of the state transition as a rather short interval
of object’s lifetime, which is related to actions caused by a certain event and
which is defined by the following five attributes:
State
FIGURE 3.22
The basic set of symbols available for rendering statecharts.
• Create an object
• Destroy an object
• Call an operation on another object
• Call an operation on this object (local invocation)
• Send a signal (message) to another (or this) object
• Return a value
• Terminate execution
• Uninterrupted action (other unclassified types of actions)
• Signal event. This object has caught (received) the signal (message)
that was thrown (sent) by another (or this) object. In UML, we model
Design 89
The transition has four categories of properties. These are the general
information, the table of actions, the table of constraints, and the tagged
values (documentation notes). The general information comprises the name
and optionally the corresponding event and the guard expression. The table
of actions holds action names and their types. The decision has three cate-
gories of properties, namely, the general information (just the name), the
table of constraints, and the tagged values (same as the decision in activity
diagrams).
Simple examples that illustrate the usage of the basic set of graphical
symbols for rendering statechart diagrams seem to be appropriate at this
point. The following two examples, shown in Figure 3.23 and Figure 3.24,
are semantically equivalent to the simple examples of activity diagrams
shown in Figure 3.14 and Figure 3.15, respectively. The activity diagram
shown in Figure 3.14 illustrates a sequence of three activity states, namely,
openPort(p), sendData(seg), and closePort(p). Figure 3.23 shows three versions
of statechart diagrams that model the same behavior. These are the versions
A, B, and C.
Version A models the behavior by a sequence of three transient states,
namely, Opening, Sending, and Closing. By selecting appropriate names, we
can indicate what type of activity is taking place in each of the states. The
original activities openPort(p), sendData(seg), and closePort(p) are modeled as
internal transitions of the states Opening, Sending, and Closing, respectively.
Opening Initial
/openPort(p)
Sending Ready
/openPort(p),sendPacket(p),closePort(p)
/sendData(seg)
Closing Finished
/closePort(p)
FIGURE 3.23
An example of a simple state machine with a single path of evolution.
We could also use entry or exit actions instead of internal transitions. Alter-
nately, we could model this simple behavior by only one transient state with
three internal transitions. Generally, by compressing models we decrease
their clarity, and we should seek the compromise appropriate for the project
at hand. Of course, defining clarity is tricky because it is essentially a matter
of taste.
Version B is the model of the same behavior that employs another way of
modeling activities in the statechart diagrams, and that is by actions taken
by state transitions. This version of the model comprises three transient
states, namely, Initial, Ready, and Finished, which are connected by triggerless
transitions. Such transitions take place immediately after their source state
is left (finished). The original activities openPort(p), sendData(seg), and close-
Port(p) are modeled here by the actions of the corresponding state transitions.
Finally, version C is the most compressed form of the model with the
equivalent semantics. It comprises only one state transition, from the initial
to the final state, which conducts a series of actions, namely, openPort(p),
sendData(seg), and closePort(p). This extreme shows the power of statechart
diagrams. Generally, statecharts are more expressive than activity diagrams
when it comes to modeling state machines, therefore we can model the same
behavior in less space.
The activity diagram shown in Figure 3.15 is a model of a reliable packet
delivery operation, which starts the timer T1, sends a packet, and waits for
the answer from the remote site. If the timer T1 expires before the answer
is received, the packet is sent again. If the answer is ACK, the operation
returns the value true. Otherwise, it returns the value false. Figure 3.24 shows
Design 91
T1 expired/restartTimer(T1),sendPacket(d)
/startTimer(T1),sendPacket(d)
Waiting
answer received/stopTimer(T1)
[ELSE]/return false
Version A
[answer==ACK]/return true
after: T1/sendPacket(d)
/sendPacket(d)
Waiting
answer received
[ELSE]/return false
Version B.
[answer==ACK]/return true
FIGURE 3.24
An example of a simple state machine with alternative paths and loops of evolution.
two versions of statechart diagrams that are models of the same behavior,
namely, versions A and B.
Version A models the given behavior by explicit, rather than implicit, timer
management. The triggerless transition from the initial state to the state
Waiting starts the retransmission timer T1 and sends the packet by conduct-
ing the actions startTimer(T1) and sendPacket(d). The expiration of the timer
T1 is modeled here by the signal event T1 expired. The corresponding tran-
sition restarts the timer T1 and sends the packet again. The reception of the
answer from the remote site is modeled by the signal answer received. The
corresponding transition stops the timer T1 and leads to the decision with
two outgoing transitions. The first is taken if the answer is ACK; otherwise,
the second is taken. Those who prefer not to use decision symbols in their
statechart diagrams should delete it, as well as the previous transition, and
add the event answer received to both transitions that are leading to the final
state.
Design 93
complex in this respect. Besides the transitions between simple states, there
exist the transitions between simple states and composite states, as well as
the transitions from substates to external states. The transitions from external
states to substates of a composite state are not allowed. This asymmetrical
relation raises the following question: What happens with the flow of states
inside a composite state if a transition from that composite state to another
state is triggered?
The answer is that the information about the point of interruption inside
the composite state is lost by default. This means that the processing will be
restarted from the very beginning when that composite state is re-entered
once again later. This means that the composite state operates without con-
text saving, which is referred to as a history in the UML.
If we want the composite state to operate with the history — which means
it is able to restart from the point of interruption at its re-entrance — we can
use the special history state. The history state is a special type of an initial
state that is the target for the transitions from the external states. Once
activated, it restarts the operation at the point of interruption. The following
two types of history states are used:
The shallow history state ensures context-saving only on the first level of
nesting of composite states. Alternately, the deep history state provides
context-saving on the innermost state at any depth. If there are more nesting
levels, the shallow history remembers the outermost nested state and the
deep history remembers the innermost nested state.
Like activity diagrams, statechart diagrams also support modeling con-
currency. We model concurrent activities in statechart diagrams by using
concurrent sequences of substates inside a certain composite state. Typically,
each such sequence begins with the initial state and ends with the final state.
The transition, from the external state to this composite state, forks to con-
current substates, which at the end join in the transition from this composite
state to the external state. The usage of concurrent substates is advisable
only if the behavior of one of these concurrent flows is affected by the state
of another. Alternately, if the behavior of the concurrent flows is driven by
the signals (messages) they exchange, partitioning the object into more active
objects is preferable.
The set of additional symbols that are available for rendering statechart
diagrams is shown in Figure 3.25. These are the composite state, the shallow
history state, the deep history state, the fork or join synchronization point,
the note, the constraint note, the constraint and the OR constraint. These
symbols, like others, have their properties. The composite state has the same
categories of properties as a simple state and two additional indicators,
namely, Concurrent and Region, which determine whether the composite state
Composite State H H∗
Note (Constraint)
(Constraint) (OR)
FIGURE 3.25
The additional graphical symbols available for rendering statecharts.
is concurrent or not and if it is a region or not. Both shallow and deep history
states have the same three categories of properties. These are the name, the
table of constraints, and the tagged values. The rest of the symbols have
already been introduced.
Figure 3.26 shows the simple example of a statechart diagram that uses
the shallow history state. Imagine a simple state machine that starts from
the state Idle. The event sendCharacter(ch) triggers its transition to the com-
posite state Sending Segment, which starts with the shallow history state to
ensure context saving. Because this state comprises only simple states, the
application of the deep history state, instead of the shallow history state,
would have the same effect because only one level of nesting of composite
states is found.
sendCharacter(ch)
sendCharacter(ch)
Idle Sending Segment Buffering
Sending
Break
Idle
FIGURE 3.26
An example of a composite state that uses the shallow history state.
Design 95
The state machine remains in the substate Buffering while it is filling the
corresponding buffer with new incoming characters. This status means that
the state machine will wait for the additional event sendCharacter(ch) until
the buffer becomes full, when the state machine will proceed to the state
Sending. After it sends all the characters from the buffer, the state machine
leaves the compound state Sending Segment and triggerlessly transits to the
state Idle.
If the event break occurs while the state machine is in the compound state
Sending Segment, its context will be saved and the state machine will leave
it and move to the state Break. It will remain in this state until the event
continue occurs. Then the state machine will re-enter the compound state
Sending Segment, the context will be restored, and the state machine will
resume the processing from the point of interruption.
The example in Figure 3.27 shows a simplified DNS client and server
statechart diagrams. Both of them have just a single state. Being simple
enough, these diagrams make very clear how statechart diagrams are used
to make complete designs of communication protocols. Typically, a job per-
formed by the communication protocol is to receive a message, process it,
and send one or more messages as the result of this processing. Both DNS
client and server go along this simple scheme.
The DNS client starts from the initial state by receiving a call to map the
given domain name into the corresponding IP address. This action is mod-
eled by the call event map(d) in Figure 3.27. This event triggers the transition
of the DNS client from the initial state to the state Wait DNS Response. During
the course of this transition, the DNS client sends the signal (message)
DNSrequest(d), which causes the signal event receive DNSrequest(d) at the
DNS server side.
map(d)/send DNSrequest(d)
receive DNSresponse(d,ip)/return(ip)
receive DNSrequest(d)/send DNSresponse(d,ip)
FIGURE 3.27
A DNS client and server statechart diagrams.
The DNS client is simply blocked in the state Wait DNS Response while
waiting for the signal DNSresponse(d,ip). The signal event receive DNSre-
sponse(d,ip) triggers the DNS client transition to its final state. During this
transition, the DNS client extracts the IP address from the received signal
and returns it as its return value. This is modeled by the return action
return(ip).
The DNS server starts with the triggerless transition from its initial state
to the state Wait DNS Request, where it is blocked while waiting for the signal
DNSrequest(d). The signal event receive DNSrequest(d) causes the DNS server
to map the given domain name to the corresponding IP address, to create
the signal (message) DNSresponse(d,ip), and to send it to the DNS client. The
DNS server performs all these actions during the transition to the same state,
i.e., Wait DNS Request. This ensures that after servicing the current request,
the DNS server remains available for servicing the next DNS request.
The example in Figure 3.28 shows the statechart diagram for one real
protocol, namely TCP. It starts with the triggerless transition from the initial
state to the state CLOSED in which it awaits one of the two possible call
events. The call event passive OPEN causes TCP to create TCB (modeled with
the action create TCB) and to move to the state LISTEN. Alternately, the call
event active OPEN causes TCP to additionally send the signal SYN (TCP
segment with the bit SYN set in the header) to the remote TCP entity. This
is modeled with the actions create TCB and snd SYN.
TCP is blocked in the state LISTEN while waiting for one of the two
possible events. The signal event rcv SYN triggers it to send the signal SYN,
ACK (TCP segment with both bits SYN and ACK set) to the remote TCP
entity and to move to the state SYN RCVD. The call signal SEND causes
TCP to send the signal SYN to the remote TCP entity and to move to the
state SYN SENT.
While being blocked in the state SYN SENT, TCP can be triggered by one
of the three possible events. If the call event CLOSE occurs, TCP deletes TCB
(modeled with the action delete TCB) and returns to the initial state. If the
signal event rcv SYN occurs, TCP sends the signal ACK and moves to the
state SYN RCVD. If the signal event rcv SYN, ACK occurs, TCP sends the
signal ACK to the remote TCP entity and moves to the state ESTAB.
After reaching the state SYN RCVD, TCP can react to one of the two
possible events. If the call event CLOSE occurs, TCP sends the signal FIN to
the remote TCP entity and moves to the state FIN WAIT 1. If the signal event
rcv ACK of SYN, occurs, TCP moves to the state ESTAB.
Two events are recognizable in the state ESTAB. If the call event CLOSE
occurs, TCP sends the signal FIN to the remote TCP entity and moves to the
state FIN WAIT 1. If the signal event rcv FIN occurs, TCP sends the signal
ACK and moves to the state CLOSE WAIT.
In the state FIN WAIT 1, TCP may receive either FIN or ACK of FIN signals.
In the former case, it sends the signal ACK and moves to the state CLOSING,
whereas in the latter case it just moves to the state FIN WAIT 2, where it
waits for the signal FIN to send the signal ACK and move to the state TIME
Design 97
CLOSE/delete TCB
CLOSE/snd FIN
rcv ACK of FIN CLOSING
LAST ACK
FIGURE 3.28
A TCP statechart diagram.
WAIT. On the alternative path, TCP moves from the state CLOSING to the
state TIME WAIT after it receives the signal ACK of FIN.
Upon the entrance to the state TIME WAIT, a timer with the period 2MSL
is started. When this period expires, TCP deletes TCB and moves back to its
initial state CLOSED. After reaching the state CLOSE WAIT, TCP waits for
the call event CLOSE to send the signal FIN and move to the state LAST
ACK, and from there to the initial state CLOSED after it receives the signal
ACK of FIN.
The example in Figure 3.29 shows the statechart diagram of a simple send
e-mail operation (SMTP client side). It starts with the triggerless transition
from its initial state to the state WAIT 220 where it waits for the signal
WAIT 250 3
FIGURE 3.29
A simple send e-mail operation statechart diagram (SMTP client side).
(message) 220 from the SMTP server. When the signal event rcv 220 occurs,
the SMTP client sends the signal HELO to the SMTP server and moves to
the state WAIT 250 1. After receiving the signal 250, the SMTP client sends
the message MAIL FROM: to the SMTP server and moves to the state WAIT
250 2.
Next, two signals of 250 in succession cause the SMTP client first to send
the signal RCPT TO:, then to send the signal DATA to the SMTP server, and
finally to reach the state WAIT 354. Upon reception of the signal 354, the
SMTP client sends the body of the e-mail message and moves to the state
WAIT 250 4. After receiving the signal 250, it sends the signal QUIT to the
SMTP server and finally, after receiving the signal 250 again, it returns the
value true and moves to its final state.
The main problem in this oversimplified version of the SMTP client is that
it can block indefinitely while waiting for a signal from the SMTP server.
The first thing that would be added in a more realistic design is a time limit
on waiting for signals, which would be modeled with timers (keyword after:).
The reaction to the expiration of a timer could be as simple as returning the
Design 99
value false and moving to the final state, or it can include some type of a
recovery mechanism.
• Executables
• Libraries
• Tables
• Files
• Documents
• Personal computers
• Mainframes
• Embedded controllers
• Mobile or cellular phones
• Network routers
Design 101
∗ ∗ 1 ∗
(Constraint)
Note (Constraint)
(OR)
FIGURE 3.30
The basic set of symbols available for deployment diagrams.
values stores the name, the stereotype, the type, and the value for each
attribute. The component instance has the same categories of properties as
the node instance, with the exception that its general information differs and
it comprises the name of the component instance and the component name.
The deployment diagram in Figure 3.31 shows an example of a network
configuration comprised of three personal computers that are connected to
the Internet. A personal computer is modeled as the node PC. Individual
PCs are modeled as node instances, namely Machine1, Machine2, and
Machine3. Internet is modeled as the node instance, named Network, of the
node type named Internet. The real links that connect PCs to the Internet are
modeled with the association relations between the node instances Machine1,
Machine2, and Machine3, and the node instance Internet. The one-to-one
nature of these links is modeled by setting the multiplicities on both sides
of associations to 1.
This diagram is what the physical infrastructure of this example looks like.
The software components are deployed as follows: The e-mail client execut-
able is deployed to the first PC, the DNS server executable is deployed to
Network : Internet 1
1
FIGURE 3.31
An example of a network configuration.
the second PC, and the SMTP server is deployed to the third PC. We model
the e-mail client executable with the component EMailClient, which is ste-
reotyped as the <<executable>>, and its particular instance deployed to the
first PC with the component instance client.exe. Similarly, the DNS server
executable is modeled with the component DNSServer and its particular
instance deployed to the second PC with the component instance dnss.exe.
Finally, the SMTP server is modeled with the component SMTPServer and
its particular instance deployed to the third PC with the component instance
smtps.exe.
The deployment diagram in Figure 3.32 shows the example of subsystems
and interfaces. While thinking about the system shown in the previous
example (Figure 3.31), we can identify three application layer packages, two
system-software layer packages, and three interfaces. The application layer
packages are the packages EMailClient, SMTPServer, and DNSServer, whereas
the system-software packages are the packages TCP/IP and OS.
The package TCP/IP provides two service types through the interface
TCPport and IPint, respectively. The services provided through the former
interface are used by the package EMailClient and SMTPServer, whereas the
services provided through the latter interface are used by the package EMail-
Client and DNSServer. Similarly, the package OS provides services through
its interface OSapi. These services are used by the package TCP/IP.
Interested readers can find more information about the UML diagrams in
the original books by Booch, Rumbaugh, and Jacobson (Booch et al., 1998).
Design 103
Application layer
packages
TCPport
IPint
TCP/IP
System-software
layer packages
OSapi
OS
FIGURE 3.32
An example of subsystems and interfaces.
This section concludes the part of this chapter based on UML. The second
part of the chapter is based on domain-specific languages.
SDL creators have been facing the following dilemma. Traditionally, a finite
state machine (FSM) has been modeled by a state transition graph. Typically,
a state transition graph is graphically illustrated by circular symbols repre-
senting states and arrows representing state transitions. State labels are state
names whereas state transition labels indicate FSM input that causes the
corresponding state transition and FSM output produced by the same tran-
sition. An advantage of this type of FSM representation is that all the stable
FSM states are clearly indicated and can be easily noticed. Alternately, a
disadvantage of this type of FSM representation is that message-processing
procedures are not defined formally. Informally written state transition
labels, placed close to the corresponding arrows, indicate only the FSM input
causing the transition and the output that the FSM must produce. This
information is far from being sufficient for writing the software that imple-
ments the given FSM — it only provides some hints to programmers.
Another approach would be to use a flow chart, a traditional way of
specifying data-processing algorithms. An advantage of this type of FSM
representation is that message-processing procedures are clearly and pre-
cisely defined. A disadvantage is that stable FSM states are not clearly indi-
cated, therefore they can hardly be noticed. The FSM states can be marked
as certain points in a flow chart by using informal annotations, and that is
simply not comprehensible enough.
The creators of the SDL language have found a solution to this dilemma
by combining the above mentioned approaches, namely, the state transition
graph-based approach and the flowchart approach. This combination has
been cleverly made by simple extension of the set of graphical symbols
available for drawing flowcharts. The key new graphical symbols introduced
are the symbol corresponding to an FSM stable state and the symbols that
represent FSM inputs and outputs (input and output messages). We will
fully describe all the SDL graphical symbols later in this chapter.
The protocol designer uses SDL language to specify and describe the
corresponding automata instance by listing all its states and state transitions.
Although the number of states can be very large, this task is simplified by
the fact that in a given state, only a limited number of events can occur, and
this means that the automata instance can evolve from a given state only to
a limited number of new states. For example, consider a telephone call
automata instance waiting for the first digit to be dialed (the automata
instance enters this state immediately after the user has initiated an outgoing
call, i.e., after the so-called “hook-off” event). The telephone call automata
instance cannot evolve from this state to any other arbitrary state. More
precisely, in this state only the following three events are possible:
• The user ends the call (hook-on event), which causes the automata
instance to evolve to its initial idle state.
• The user dials a digit (digit event). This event triggers the state transi-
tion from the current state to the state of waiting for the second digit.
Design 105
• The user does nothing during a certain interval of time. This will
cause the expiration of the corresponding timer and a state transition
to the state in which the telephone line is blocked.
System
FIGURE 3.33
The structure of the communication software according to ITU-T.
• It is easy to learn.
• It is easy to extend the specification in case of the new requirements.
• In principle, it can support various methodologies for making the
system specifications.
Two forms of SDL language exist, graphical (SDL-GR) and program (SDL-
PR). The graphical form has been widely accepted for two reasons. First, it
is closer to human understanding because it is easier to understand and
Design 107
follow. Second, in principle, it does not require the support by special, and
frequently very expensive, software tools. Of course, a piece of paper and a
pencil is hardly sufficient for a professional work. At least a modern graph-
ical editor that supports the SDL set of graphical symbols is needed to enable
the making of decent specifications. In this book, we use Microsoft Visio for
that purpose.
The second SDL form, SDL-PR, is practically a higher-level programming
language of textual type (similar to C/C++ and Java programming lan-
guages). Clearly, this programming language is less synoptic and is harder
to follow than the graphical form. It is intended to be used mainly by the
accompanying software tools, such as Telelogic® Software Development
Tools (SDT). The goal of using such software tools is not just to make isolated
specification and description documents, but rather to make electronic spec-
ifications, essentially models of protocols. The software tools can then be
used to interpret the models and generate the corresponding program code.
In addition to the tools provided by Telelogic, other tools exist based on
this philosophy that is, as already mentioned, referred to as model integrated
computing (MIC). One of them is also already mentioned, GME.
The main SDL applications are the following:
The SDL language basics are as follows: SDL is based on a set of special
symbols and the rules for their application. The graphical form (SDL-GR) is
based on special graphical symbols whereas the program form (SDL-PR) is
based on a set of special keywords. Both SDL forms use the same set of
keywords specialized for data representation.
Later, we assume that a system consists of a number of protocols. Also,
we refer to a set of hierarchically organized protocols as a family of protocols
or a protocol stack. Typically, each protocol that is a part of the family
performs its well-defined task. The family of protocols conducts rather com-
plex tasks by cooperation of its members.
A system is described as a set of interconnected functional blocks. Chan-
nels are defined as communication links that are used for the interblock
communication and for the communication between the blocks and the
environment. Each block comprises a number of processes that communicate
by exchanging signals. A channel is typically implemented as a FIFO (First-
In-First-Out) queue that stores the signals (i.e., messages) to be transferred
system Daemongame
signal Newgame, Probe, Result, Endgame, Gameid, Win, Lose, Score(Integer);
channel Gameserver.in
from env to Game
Design 109
system Daemongame
signal
Newgame, Probe, Result,
Endgame, Gameid, Win, Lose,
Score(Integer)
Newgame, Gameid,
Probe, Win,
Game
Result, Lose,
Endgame Score
FIGURE 3.34
The structure of the system Daemongame.
Generally, any system SDL program specification starts with the keyword
system and ends with the keyword endsystem. This particular program defines
all the required signals (Newgame, Probe, Result, Endgame, Gameid, Win, Lose,
and Score), the input channel Gameserver.in, and the output channel
Gameserver.out.
In contrast with the graphical form, which is easy to understand, the
program form represents a lower-level specification, closer to the machine
and with more details. For example, in the graphical form a channel is simply
represented by an arrow pointing to or from the functional block. The chan-
nel declaration in the program form is much more detailed: It comprises the
channel name (e.g., Gameserver.in), its direction (e.g., from environment
toward the functional block Game), and a list of signals that must be trans-
ferred over the channel (e.g., Newgame, Probe, Result, and Endgame).
The next lower hierarchical level of detail describes a single functional
block of this simple system, namely, the block Game. Its specification is given
in both forms of SDL. The graphical form of the specification is given in
Figure 3.35. The program form of the specification is given immediately after
a short explanation of Figure 3.35.
Figure 3.35 shows that the block Game consists of two processes, namely
Monitor and Game. The processes are connected to the environment, and to
block Game
signal
Gameover(Pid)
Monitor(1,1)
R1
game
N w
e
Gameover
R
Pr 2 R3 id,
R4
Re obe, me
En sult Ga in,
dg , W se,
am Lo re
e o
Sc
Game(0,)
FIGURE 3.35
The structure of the functional block Game.
each other, by signaling paths. It also shows that the input channel
Gameserver.in consists of two signaling paths, the signaling path R1 (which
is used to carry Newgame signal) and the signaling path R2 (which is used
to carry the signals Probe, Result, and Endgame). The output channel
Gameserver.out comprises the single signaling path R3. A single internal sig-
naling path exists inside the block Game, the path R4, which is used to carry
the internal signal Gameover from the process Game to the process Monitor.
This new signal is declared in the upper left corner of the graphical specifi-
cation.
block Game;
signal Gameover(Pid);
connect Gameserver.in and R1, R2;
connect Gameserver.out and R3;
signalroute R1 from env to Monitor with Newgame;
signalroute R2 from env to Game with Probe,Result,Endgame;
signalroute R3 from Game to env with Gameid,Win,Lose,Score;
signalroute R4 from Game to Monitor with Gameover;
process Monitor(1,1) referenced;
process Game(0,) referenced;
endblock Game;
The specification given above starts with the keyword block and ends with
endblock. Inside the body of the definition of the block Game, we start with
the declaration of the internal signal Gameover by declaring its name, fol-
lowed by the list of its parameters enclosed in parenthesis. The signal
Gameover has a single parameter, the identification of a process (Pid) that is
sending this signal.
Design 111
Further on, we connect the channel Gameserver.in with the signaling paths
R1 and R2. We also connect the channel Gameserver.out with the signaling
path R3. We proceed with the declarations of signaling paths (keyword
signalroute). Each declaration indicates the signaling path name, its direction
(by using the keywords from and to), the names of the processes it connects
(note that env is the special process which represents the environment), and
a list of signals it carries (by using the keyword with). For example, the first
signal path declaration shown in SDL-PR above declares the signaling path
R1, which carries the signal Newgame from the process env (environment) to
the process Monitor.
We end the definition of the functional block Game by declaring the pro-
cesses it contains. A process in general is declared by the keyword process.
A process declaration indicates the name of the process followed by the
initial and maximal number of process instances that can appear in the
system. The maximal number of process instances is an optional parameter,
i.e., it can be omitted.
The process Monitor is declared as Monitor(1,1), which means that the block
Game should initially create one instance of this process and, at the same
time, it is also the maximal number of Monitor instances that can be created
in this block. Alternately, the process Game is declared as Game(0,), which
means that initially there are no Game instances, but also that the maximal
number of Game instances is not limited, i.e., in theory it is allowed to create
an infinite number of process Game instances inside the functional block
Game. Of course, in reality this number is always limited to the available
hardware resources.
In this particular example, we have declared two processes, Monitor and
Game, that operate inside the functional block Game. The process Monitor
handles the interaction with a player. It is a mediator between the player
and the process Game, which is essentially a model of the win-lose game.
Due to the fact that the process Monitor is trivial and actually insignificant
for this example, we will define only the process Game on the next hierar-
chically lower level of abstraction. On this level of detail, the process Game
is modeled as a finite state machine (automata instance).
As already mentioned, the creators of SDL-GR (graphical form of SDL)
have extended the basic set of traditional flow chart symbols with a set of
graphical symbols specialized for modeling finite state machines. The com-
plete set of graphical symbols available for describing processes in SDL-GR
is shown in Figure 3.36.
The meaning of the individual graphical symbols shown in Figure 3.36 is
as follows:
internal
state output
signal
internal external
input output
signal signal
external
input decision
signal
save
task
signal
FIGURE 3.36
The set of graphical symbols available in SDL-GR.
Design 113
Gameid
Result Endgame to
player
Score(count) even
Gameover
to (player)
player
Lose
odd to
player
Win even -
to
player
count = count
+1
FIGURE 3.37
The process Game specification in SDL-GR.
While the process Game is in its stable state even, it awaits one of two
possible events, the reception of the signal Probe or the expiration of the
timer labeled none. If the timer expires, the process Game receives the corre-
sponding signal none, and this causes the process to evolve into the next
stable state odd. If the process receives the signal Probe, it sends the signal
Lose to the player and updates the player’s score, which is stored in the
variable count, by adding one negative point. The process does not change
its stable state, i.e., it remains in its current state (which is denoted with the
character “–”), and that is the state even.
In its stable state odd, the process Game recognizes two same possible
events, the reception of the signal Probe or the expiration of the timer labeled
none. Actually, the timer none determines the time interval the process will
spend in either the even or odd state before switching to the other one. Hence,
if the timer none expires, the process evolves into the stable state even.
Alternatively, if the process receives the signal Probe, it sends the signal Win
to the player and updates the player’s score (value of the variable count) by
adding one positive point. The process remains in its current state (i.e., the
state odd).
The upper left corner of the graphical representation of the process Game
(Figure 3.37) shows one important example of simplifying SDL-GR dia-
grams. Because the reception of the input signals Result and Endgame is
possible in both even and odd states, a straightforward solution would be to
mechanically add these inputs and their processing to both states. The result
would be a diagram that is much more complex and harder to understand
and follow. A more elegant solution is to draw the description of the proc-
essing of the inputs Result and Endgame in both states as a separate drawing
in the diagram, as shown in Figure 3.37.
Generally, it is always useful to try to find identical processing of input
signals (state transitions) that repeat in a number of stable states and to
simplify the specification by drawing these parts separately in the diagram.
This type of a model reduction is really easy. We just draw an oval state
symbol and write a list of the states (the list comprises the state names
separated by commas) that share the common inputs inside the state symbol.
Then we can copy and paste common state transitions. At the end, we can
just remove the redundant state transitions. Of course, in the simple dia-
grams such as in the example at hand, we can see this in advance and draw
accordingly, as we did for the processing of the inputs Result and Endgame
in the states even and odd.
If the process Game receives the signal Result, which comes from the envi-
ronment, i.e., from the player, the process sends the signal Score(count) to the
environment (actually to the player) and it remains in its current state (even
or odd). Alternately, if the process Game receives the signal Endgame, it sends
the signal Gameover to the process Monitor and the game ends, i.e., the
functional block deletes the process Game.
Design 115
input none;
nextstate odd;
input Probe;
output Lose to player;
task count:=count-1;
nextstate -;
state odd;
input Probe;
output Win to player;
task count:=count+1;
nextstate -;
input none;
nextstate even;
state even,odd;
input Result;
output Score(count) to player;
nextstate -;
input Endgame;
output Gameover(player);
stop;
endprocess Game;
The definition of the process starts with the keyword process and it ends
with the keyword endprocess. As already mentioned, initially no instances of
the process Game are used, and the maximal number of its instances is
unlimited. The process declaration is followed by the construct fpar player
Pid, which defines the formal process parameter player that is assigned the
value Pid. At the beginning of the game, the run-time environment creates
an instance of the process, and assigns a unique Pid number to it.
Next, we declare the integer variable count (using the keyword Integer),
which contains the current total value of points that the player has scored
so far. After the label start, we define a series of statements that are executed
by the process at its startup. In this example, the process Game at its startup
sends the signal Gameid to the player and enters its initial stable state even
(next state of the process is defined by the keyword nextstate).
For each stable state (keyword state) of the process, we define all the
recognizable input signals (using the keyword input) and on the next level
of indentation, we define the corresponding state transition as a series of
statements that ends with the nextstate statement. For example, the recog-
nizable input signals in the stable state even are the signal none, which relates
to the expiration of the corresponding timer, and the signal Probe generated
by the player’s stroke of the pushbutton. In the case the timer none expires,
the process evolves to its next stable state odd. Alternatively, if the process
receives the signal Probe, it sends the signal Lose to the player (using the
keyword output), performs the task of decrementing the score by 1 (using
the keyword task), and remains in its current state (the statement nextstate -;).
The stable state odd is defined in a similar manner. The input signals
recognized by the process in its stable state odd are the signal Probe and the
expiration of the timer none. If the process receives the signal Probe, it sends
the signal Win to the player, increments the score by 1, and remains in its
current stable state odd. Alternatively, if the timer none expires, the process
evolves into its stable state even. Finally, we define the state transitions
initiated by the reception of the input signals Result and Endgame in either
the state even or odd.
Understanding the principals of SDL-PR helps in more easily understand-
ing the communications protocol software implementation in the state-of-
the-art, higher-level programming languages such as C/C++ or Java.
Although SDL-PR can resemble a pseudolanguage when compared to these
programming languages, in reality it is a specialized language of higher level
abstraction and it is feasible to construct a compiler for it. However, the
study of the compilers is out of the scope of this book. The primary goal of
this book in this respect is to provide an insight into the manual coding of
SDL graphical diagrams in some of the above mentioned programming
languages (C/C++ or Java).
The example under study can help in this respect. Obviously, two levels
of nesting are included in it. The first level of nesting corresponds to the
current stable state, in which the process is blocked while waiting for the
next input signal, i.e., start, even, and odd. The second level of nesting corre-
sponds to the type of input signal, i.e., Probe, none, Result, or Endgame.
The simplest method to implement this selection construction with two
levels of nesting in the C/C++ or Java programming language is to use
nested switch-case statements. The first switch-case statement is used to locate
the current state. Then in each case clause of the first switch-case statement,
another switch-case statement is used to locate the state transition statements
that correspond to the given input signal. This type of protocol implemen-
tation will be covered in detail in the next chapter.
Design 117
rl FE6 : D rl
rl
Where:
A, B, and D are the types of functional entities
FE1, FE2, FE3, FE4, FE5, and FE6 are the names of the functional entities
rk, rj, and rl are the relations between the functional entity types
FIGURE 3.38
A functional model of the telephone call processing system.
system CallProcessor
signallist
input, output
FIGURE 3.39
The hypothetical system CallProcessor.
block TelephoneLine
signal
hookOff, dialDigit, hookOn,
initiateOutgoingCall,
answerReceived
hookOff,
initiateOutgoingCall FE1 dialDigit,
hookOn
one of the three possible input signals (hookOff, dialDigit, and hookOn) from
the telephone user’s side. Alternately, it can send the output signal initiate-
OutgoingCall to the telephone exchange or it can receive the input signal
asnwerReceived from the exchange.
The process FE1 is specified in the graphical form of SDL, SDL-GR, in
Figure 3.41. This process resides in the telephone exchange and it commu-
nicates with the human that uses the telephone to establish a call, talk to the
called party, and release the call at the end of the conversation. In reality,
such a process must handle many scenarios, e.g., the user picks up the
receiver but does not dial the number, or stops after dialing an insufficient
number of digits.
The process specified in Figure 3.41 is rather simplified but it still captures
the most significant part of the telephone line functionality on the calling
party side. The telephone line in this context is a processor that hosts FE1,
together with the interfacing hardware that connects it to both the calling
party user’s telephone and switching unit of the telephone exchange. For
brevity, we refer to the former simply as a user and to the latter as a telephone
exchange, or just an exchange.
The process FE1 has four stable states, namely, IDLE, WAIT_DIGIT,
WAIT_ANSWER, and CONVERSATION. The evolution of the process starts
from the state IDLE. The single recognizable input signal in this state is the
signal hookOff. If the process FE1 receives the signal hookOff, it performs the
task prepareForDialing and moves to its next stable state WAIT_DIGIT. While
performing the task prepareForDialing, the process connects the free-to-dial
tone to the calling party user. This tone serves as the indication to the user
that they can start dialing the number of another user to which they wish
to talk.
Two recognizable input signals are used in the stable state WAIT_DIGIT,
i.e., the process can either receive the input signal hookOn or the input signal
Design 119
IDLE WAIT_ANSWER
answerRece-
hookOff ived hookOn
CONVERSATION IDLE
prepareFor-
Dialing.
hookOn
WAIT_DIGIT
IDLE
hookOn dialDigit
IDLE
initiateOutgo-
ingCall
WAIT_ANSWER
FIGURE 3.41
A simplified model of the Q.71 FE1 in SDL-GR.
telephone line entity at the called party translates it to the signal answerIn-
comingCall and sends it to the exchange at the called party side, which in
turn sends it to the exchange at the calling party side. Finally, the exchange
at the calling party side translates it to asnwerReceived and sends it to FE1.
In the final stable state CONVERSATION, only a single event is possible. The
process FE1 can receive the input signal hookOff, and if it does, that is the end
of the conversation phase of the call and the process will return to its initial
stable state IDLE. This closes the circle and the process is ready to process a
new call originating from the same telephone line. Clearly, an instance of the
process FE1 is assigned to each telephone line in the telephone exchange.
In this example, we described the process FE1 that is assigned to the calling
party telephone line without going into a detailed specification of the oper-
ations performed by the telephone exchanges and the called party telephone
line involved in the call. Obvious from this example should be that SDL
diagrams are self-documented formal specifications and that no need really
exists for any additional textual descriptions.
The SDL diagram shows the possible evolution paths of a process (a call
processing in the example above). It defines unambiguously all telephone stable
states, as well as all possible input signals for each state. The functional speci-
fication is based on the logical advance of a call, expressed in terms of telephony
events. This makes it completely independent of both hardware structure of
the hosting system and selected programming language and framework.
The SDL diagram is drawn based on the observations of a single telephone
call without thinking about other calls, which are processed simultaneously
(quasi-parallel by a single CPU or genuinely parallel by a multi-CPU system).
This approach greatly simplifies software design. Finally, the existing SDL
diagram can be easily extended by adding new states and input signals
without the need to start drawing a new diagram from the very beginning.
This possibility also enables the easy removal of revealed design errors.
Design 121
In the newly reached stable state, we select again one of the recognizable
events (the input signals that may be received in the stable state WAIT_DIGIT
are hookOff or dialDigit; let us assume that we have selected dialDigit) and
we follow the process evolution along the corresponding path (in the case
of the input signal dialDigit, the process moves to the state WAIT_ANSWER).
At the same time, as we mentally follow the evolution path of the process,
we draw on the paper, or even better in the corresponding graphical editor,
the messages that are exchanged between the process and its environment.
The messages are represented by the graphical arrow symbols that are
labeled by the message names. This is how we get the MSC charts.
Clearly, an MSC chart represents a single trace over the corresponding
path, through the SDL diagram, or some other form of specifying finite state
machines. We can see intuitively that for the real automata that we come
across in practical applications, a finite number of paths exist that cover the
SDL diagram. The set of the MSC charts that are obtained by visiting these
paths represents the specification that is in a logical sense equivalent to the
SDL diagram.
However, an obvious disadvantage of this type of a specification, in a form
of a set of the MSC charts, is that it is much less evident than the SDL
diagram. Therefore, when communication protocol designers refer to the
formal specification, they really assume the SDL diagram. This disadvantage
becomes obvious if instead of dealing with a single automaton, we try to
follow the evolution of a group of automata, which communicate between
themselves, as well as with the environment, e.g., the group of automata
defined in the above mentioned recommendation Q.71. The number of evo-
lution traces of such systems can be extraordinarily large.
Not only must we select the initial state of a single automata, we must do
it for all the automata from the group we want to analyze. Furthermore, in
the case of simple and loosely coupled automata, an increase in the number
of possible path combinations is not so high, but in the case of complex or
tightly coupled automata, it is clear that the number of evolutions of the
system can be huge.
The discussion above naturally raises the following questions: For what
purpose are the MSC charts useful? Do we need them at all? Practical
experience shows that making the MSC charts can be useful at the beginning
of the design process, when the designers talk rather freely about possible
communications scenarios. These scenarios of message exchange most fre-
quently represent the so-called main branches, i.e., main paths, through the
protocol. Typically, they go from the beginning (the initial state) to the end
(logically, the last state in the chain of states), e.g., from the state IDLE to the
state CONVERSATION in the previous example, without any errors or other
exceptional events. Later, after finishing the analysis of the main paths, the
paths of minor importance are analyzed. These are related to various less
frequent cases, such as handling timer expirations, error recovery proce-
dures, and so on.
All these scenarios, in the form of MSC charts, would be very useful in
the later stages. Actually, these charts will be used as individual test cases
during the implementation phase to partially check the functionality of the
individual software modules (this is the so-called unit testing). They are also
used during the final phase of the software verification as test cases for the
compliance testing. The goal of compliance testing is to check if the software
is compliant with the specification.
In most cases, the number of manually written MSC charts is finite and
not too large (on the order of a few hundred at most). Later, during the
testing and verification phase, automatically generating a much larger num-
ber of test cases would be normal (logically equivalent to MSC charts) to
check the system much more thoroughly. This testing most frequently takes
the form of statistical usage testing, and it enables quality engineers to
estimate the software reliability without any previous knowledge about the
system under examination.
As already mentioned, the MSC language — similar to the SDL language
— has both the graphical and program form. The graphical form of the MSC
language is more interesting than the program form for developing commu-
nications software. The next example illustrates the message exchange
among the functional entities FE1, FE2, FE3, FE4, and FE5, in the case of the
successful establishment and successful release of the ISDN connection
between two subscribers. From this example, MSC is obviously useful for
tracing the message exchange between more processes, which is not so easy
and clear by looking at the set of corresponding SDL diagrams.
We start drawing the MSC chart by placing the rectangle graphical symbols
that represent the communicating entities (i.e., processes) at the top of the
chart sheet. The names of the entities are used to label these rectangle
symbols. Next, we draw a vertical line from each rectangle symbol to the
bottom of the sheet. After that, we enter a series of messages exchanged by
the processes shown on the top of the chart. Each message (i.e., signal) is
represented by the arrow symbol labeled with the message name. The arrow
starts from the vertical line that represents the process sending the message
and ends at the vertical line that represents the process receiving the message.
The time advances in the direction from top to bottom of the sheet, i.e., the
messages that appear on the top of the chart are exchanged before the
messages that appear at the bottom of the chart.
An example of the MSC chart is shown in Figure 3.42. This example
illustrates the scenario of successful establishment and release of the ISDN
connection. The functional entities FE1 and FE5 are assigned to the calling
and called party user, respectively. Initially, the functional entity FE1
receives the signal SETUP_req from the environment (in reality, this signal
is generated by the signaling system DSS1). After receiving the signal
SETUP_req, FE1 translates it to the signal SETUP_req_ind and sends this new
signal to FE2. FE2 forwards this signal to FE3, FE3 forwards it to FE4, and
finally FE4 forwards it to FE5.
Design 123
SETUP_req
SETUP_req_ind
PROCEEDING_req_ind SETUP_req_ind
REPORT_ind SETUP_req_ind
SETUP_req_ind
REPORT_req_ind SETUP_ind
REPORT_req_ind
REPORT_req_ind
REPORT_req_ind
REPORT_ind
SETUP_resp
SETUP_resp_conf
SETUP_resp_conf
SETUP_resp_conf
SETUP_resp_conf
SETUP_conf CONNECT_req_ind
DISC_req
DISC_req_ind
RELEASE_req_ind
RELEASE_req_ind
RELEASE_req_ind DISC_req_ind
DISC_ind
RELEASE_resp_conf
DISC_resp
RELEASE_req_ind
RELEASE_resp_conf
RELEASE_resp_conf
RELEASE_resp_conf
FIGURE 3.42
An example of the MSC chart: Successful ISDN call establishment and release.
After receiving the signal SETUP_req_ind, the functional entity FE5 imme-
diately sends two signals, the signal SETUP_ind to its environment and the
signal REPORT_req_ind back to FE4. The latter signal is forwarded from FE4
to FE3, then from FE3 to FE2, and finally from FE2 to FE1. FE1 translates
this signal to REPORT_ind and sends the latter to its environment.
The acceptance of the call by the calling party is signaled to FE5 by the
signal SETUP_resp. FE5 translates this signal to the signal SETUP_resp_conf
and sends the latter over the chain of FEs back to FE1. FE1 in its turn
translates it to SETUP_conf and sends the latter to its environment. This is
the final step of the connection establishment procedure. The next commu-
nication phase is a conversation.
At the end of the conversation, the calling party user initiates the call
release procedure by sending the signal DISC_req to the functional entity
FE1, which in turn translates it to DISC_req_ind and sends the latter to FE2.
The functional entity FE2 translates this signal to the signal RELEASE_req_ind
and sends the latter to both FE1 and FE3. From there, we have two parallel
flows of messages. FE1 replies to the signal RELEASE_req_ind by the signal
RELEASE_req_conf. Alternately, FE3 forwards the signal RELEASE_req_ind
to FE4, which translates it to DISC_req_ind and sends the latter to FE5. FE5
indicates the reception of that signal by sending the signal DISC_ind to its
environment.
The environment answers with the signal DISC_resp, which is then trans-
lated to RELEASE_req_ind and sent to FE4. The functional entity FE4 trans-
lates that to the signal RELEASE_resp_conf and sends the latter to both FE3
and FE5. Finally, FE3 forwards that final signal to FE2. This is the final step
of the call release procedure.
This real-world example shows the main advantage of using MSC charts
— instead of speculatively analyzing the parallel work of five finite state
machines (FE1, FE2, FE3, FE4, and FE5) by looking at their SDL diagrams,
here on a single chart we see how the system evolves through the procedures
of call establishment and release. At this level of abstraction, we are not
interested in the individual work of the individual automata. We just follow
the interaction based on the message exchange between the automata in a
given group.
Design 125
PTC PTC
PTC PTC
PTC PTC
(N-1)-Service Provider
FIGURE 3.43
An illustration of the test configuration.
The test suite overview part is a table that contains an overall description
of the test suite. The purpose of this part of the TTCN specification is to
provide high-level information about the test suite to make it clear and more
readable.
The test suite declaration part contains declarations of the following spec-
ification elements:
Design 127
TABLE 3.1
An Example of Type Declarations in TTCN Language
Simple Type Definitions
Type Name Type Definition Type Encoding Comments
B_1 BITSTRING[1]
O_1 OCTETSTRING[1]
O_2 OCTETSTRING[2]
Detailed Comments:
with the row that names the table (in the example shown in Table 3.1, the
name of the table is Simple Type Definitions). Each next row of the type
declaration table is used to declare an individual data type additionally
introduced by the user. The last row of the type declaration table is reserved
for the detailed textual comments that the design engineer can provide to
improve the quality of the TTCN test suite specification.
Each individual data type is declared by writing the type name, type
definition, type encoding, and a textual comment in separate columns of the
type declaration table. The type encoding information and a textual comment
are optional. Table 3.1 declares three types, namely B_1 (a bit string com-
prising a single bit), O_1 (an octet string comprising a single octet), and O_2
(an octet string comprising two elements, i.e., octets).
The next example illustrates the ASP type definition. Table 3.2 defines ASP
type dl_data_ind. The table includes the information about its point of control
and observation type, namely DSAP, and about two abstract service primi-
tive parameters, v5dl_address and user_data. The type of the former parameter
is O_2 whereas the type of the latter is PDU. A single textual sentence is
included to improve the readability of this definition.
We proceed with an example of a PDU type efinition. Table 3.3 shows the
example of PDU type bcc_allocation_cpl definition. The point of control and
observation type for this PDU is DSAP. The PDU has three fields, namely,
TABLE 3.2
An Example of an ASP Type Definition
ASP Type Definition
ASP Name: dl_data_ind
PCO Type: DSAP
Comments:
Design 129
TABLE 3.3
An Example of a PDU Type Definition
PDU Type Definition
PDU Name: bcc_allocation_cpl
PCO Type: DSAP
Encoding Rule Name:
Encoding Variation:
Comments:
TABLE 3.4
An Example of Test Case Variables Declarations
Test Case Variables Declarations
Variable Name Type Value Comments
TestCaseV INTEGER 10
TestCaseV2 IA5String ”DefaultValue”
Detailed Comments:
TABLE 3.5
An Example of a Test Suite Operation Definition
Test Suite Operation Definition
Operation Name: TSO_GET_BCC_REF_NUM(bcc_message:dl_data_ind)
Result Type: O_2
Comments:
Description: The operation TSO_GET_BCC_REF_NUM returns the
value of the field reference_number, which is a part of the received
message (bcc_message).
to describe the messages exchanged over the PCOs. The constraints can be
parameterized to make them usable in various contexts. In each of the
contexts, real values are passed as constraint arguments.
The next example illustrates a constraint declaration. Table 3.6 declares the
PDU constraint Bcc_allocation_cpl. This constraint relates to the packet data
unit type bcc_allocation_cpl. This constraint concretely requires that three
PDU fields that are listed in the table (see the column “Field Name”) have
particular values (see the column “Field Value”), as specified in the table.
The field protocol_discriminator must contain the value TSC_V5_PD, the field
Bcc_reference_number must contain the value TSPX_BCC_REF_NUM, and the
field message_type must contain the value TSC_METY_ALLOCATION_CPL.
Of course, the constants TSC_V5_PD, TSPX_BCC_REF_NUM, and
TSC_METY_ALLOCATION_CPL must be declared earlier in the test suite
declaration part.
The last part of the TTCN test suite specification is the dynamic part, which
describes the individual test cases. The dynamic part comprises test cases,
test steps, and default behavior tables, with all the test events and test
verdicts. A test verdict describes the current IUT behavior. The dynamic part
TABLE 3.6
An Example of a PDU Constraint Declaration
PDU Constraint Declaration
Constraint Name: Bcc_allocation_cpl
PDU Type: bcc_allocation_cpl
Derivation Path:
Encoding Rule Name:
Encoding Variation:
Comments:
Design 131
is created in a hierarchical and nested manner. The building blocks are test
groups, test cases, test steps, and test events.
Starting from the basic toward more complex constructions, the following
hierarchical levels of constructions exist:
• Test event: the basic element that is used to build all other more
complex constructions.
• Test step: a sequence of test events.
• Test case: a sequence of test steps.
• Test group: a set of test cases.
• Test suite: a collection of individual test cases and groups of test
cases. Normally, the test suite is stored in a file system hierarchy.
The test suite itself corresponds to the top-level directory, the test
group corresponds to the subdirectory (so it is possible to have
groups of the groups of test cases), and the individual test case
corresponds to the file that contains the series of the test steps.
Test constructions are built as behavior trees that are placed into behavior
tables. This is where the name of the language comes from, tree and table
combined notation (TTCN). The behavior table contains the behavior tree
defined by writing test events in lines with different nesting (indentation)
levels. The lines on the same nesting level (i.e., with the same depth of
indentation) represent alternative test events. The line on the next nesting
level (i.e., with the next deeper indentation) executes after the line on the
previous nesting level. The line is successfully executed when the event
assigned to it has been processed.
This process means that the evolution of the behavior tree (table) begins
from the lines that start at the beginning of the corresponding rows of the
table, i.e., with the indentation of zero spaces. Of course, generally there may
be more such lines. One of them is arbitrarily selected and the execution
continues on the next level of nesting (indentation) where again more alter-
native lines may exist. One of them is selected randomly and this procedure
is repeated until the end of the tree, i.e., until its leaf is reached. The leaf of
the tree contains the test verdict.
An example of the successfully processed event is the reception of the
expected message. Once a line has been executed, the tester goes to the next
level of indentation. The tester is a program that executes the test case script
corresponding to the test case definition table. No return to the previous
level of indentation is allowed, except by using the GOTO construct. The
lines on the same level of indentation have equal probabilities.
The line in the behavior table can contain the following:
+STEP_CHECK_STATE
Another problem that can appear in practice is the need to repeat certain
test cases. A single test case can be repeated the given number of times by
using the repeat construct. For example, the next iteration ends when the
expression FLAG evaluates as true:
Design 133
L!HookOff
L?DialTone
L!Digits
L!HookOn
L?BusyTone
L!HookOn
L?NoTone
FIGURE 3.44
An example of the behavior tree for the outgoing telephone call.
TABLE 3.7
An Example of a Test Case Dynamic Behavior Specification
Test Case Dynamic Behavior
Test Case Name: Basic outgoing call to the conversation phase.
Group:
Purpose: To check whether the base outgoing call can be established.
Configuration:
Default:
Comments:
behavior tree is then placed in the behavior table to construct the test case
shown in Table 3.7.
The behavior tree starts with sending the hook-off signal to the IUT (the
line L!HookOff) because that line is a single line on that level of nesting
(indentation). On the next level of nesting, there are two equal native lines
(the line L?DialTone and the line L?NoTone). If the upper tester receives a
dial tone (the line L?DialTone), the execution of the test case may continue.
Alternatively, if the upper tester receives some other tone or no tone at all
(the line L?NoTone), that run of the test case is definitely not successful and
we reach the test verdict FAIL. If the upper tester receives a dial tone, it sends
the address of the called party (digits) to the IUT (the line L!Digits) and after
that, it waits for the IUT answer. On this level of indentation, there are again
two equal lines (the line L?RingTone and the line L?BusyTone).
If the upper tester receives a busy tone (the line L?BusyTone), that execution
of the test case is inconclusive (the test verdict is INCON). Alternately, if the
upper tester receives a ringing tone (the line L?RingTone), the upper tester
checks if the connection is successfully established (the line L?LineConnected),
e.g., by sending DTMF tones from the lower to the upper tester. If the upper
tester receives the LineConnected signal, that run of the test case is definitely
successful and we reach the test verdict PASS. In both cases, the upper tester
ends the test case by sending the hook-on signal to IUT (the line L!HookOn).
3.10 Examples
This section contains some examples that are related to the communication
protocol design. These should help the reader to consolidate their under-
standing of the concepts and techniques introduced so far.
3.10.1 Example 1
This example demonstrates the procedures for connection establishment
and release that are performed by two communicating processes, namely
TE1 and TE2. The processes TE1 and TE2 are specified by their statechart
diagrams shown in Figure 3.45 and Figure 3.46, respectively. The semanti-
cally equivalent SDL diagrams are shown in Figure 3.47 and Figure 3.48,
respectively.
The process TE1 has four stable states, labeled TE1_IDLE,
TE1_CONNECTING, TE1_CONNECTED, and TE1_DISCONNECTING.
While the process TE1 is in the state TE1_IDLE, it can receive only the
message CONNECT_req from the user and after receiving that message, the
process TE1 sends the message CONNECT_ind to the process TE2, and
evolves to its next stable state TE1_CONNECTING. In that state, the process
may receive one of two possible input messages, namely CONNECT_conf or
CONNECT_reject. In the former case, the process moves to the stable state
TE1_CONNECTED, whereas in the latter case, it evolves to its initial stable
state TE1_IDLE.
In its stable state TE1_CONNECTED, the process TE1 may receive the
message DISCONNECT_req from the user. In that case, it sends the message
DISCONNECT_ind to the process TE2 and evolves to the stable state
Design 135
rcv CONNECT_reject
TE1_IDLE
TE1_CONNECTING TE1_DISCONNECTING
TE1_CONNECTED
FIGURE 3.45
The statechart diagram of the process TE1.
TE2_IDLE
TE2_CONNECTED
FIGURE 3.46
The statechart diagram of the process TE2.
TE1_IDLE TE1_CONNECTING
CONNECT_req
CONNECT_conf CONNECT_reject
TE1_CONNECTING
TE1_CONNECTED TE1_DISCONNECTING
DISCONNECT_req DISCONNECT_conf
TE1_IDLE
DISCONNECT_ind
TE1_DISCONNECTING
FIGURE 3.47
The SDL diagram of the process TE1.
replies with the message CONNECT_reject and remains in its current state.
In the latter case, it replies with the message DISCONNECT_conf and goes
back to its initial state TE2_IDLE.
The scenario of a successful connection establishment and release is illus-
trated by the MSC chart shown in Figure 3.49. The top of the chart shows the
communicating entities, the human user, and the program processes TE1 and
Design 137
TE2_IDLE TE2_CONNECTED
TE2_CONNECTED - TE2_IDLE
FIGURE 3.48
The SDL diagram of the process TE2.
CONNECT_req
CONNECT_ind
CONNECT_conf
CONNECT_conf
DISCONNECT_req
DISCONNECT_ind
DISCONNECT_conf
DISCONNECT_conf
FIGURE 3.49
A successful connection establishment and release MSC.
TE2. The vertical lines are drawn from the rectangular graphical symbols down
to the bottom of the sheet. The time advances in the same direction.
The connection establishment procedure starts when the user sends the
message CONNECT_req to the process TE1 (this event is noted by the arrow
drawn from the vertical line labeled USER to the vertical line labeled TE1),
which in turn sends the message CONNECT_ind to the process TE2. The
process TE2, in its turn, replies with the message CONNECT_conf. Upon
recept of the message CONNECT_conf, the process TE1 forwards it to
the user. This completes the connection establishment procedure. The next
communication phase is normally used for the desired data transfer. Because
of that, it is most frequently referred to as a data transfer phase.
The connection release procedure starts when the user sends the message
DISCONNECT_req to the process TE1, which translates it to the message
DISCONNECT_ind and sends it to the process TE2, which in turn replies by
the message DISCONNECT_conf. Upon reception of the message
DISCONNECT_conf, the process TE1 forwards it to the user. This completes
the connection release procedure.
The tables shown represent a simple TTCN test suite specification for this
example. This simple test suite comprises Table 3.8 with simple type decla-
rations, Table 3.9 with the PDU type declarations, Table 3.10 with PDU
constraint declarations, and two tables (Table 3.11 and Table 3.12) with
dynamic behavior description, i.e., test cases.
The reader is encouraged to play more with this simple example. For
example, we can change the previous example so that before the existing
connection is established, the process User checks if the process TE1 is ready
for the communication. The MSC chart that specifies a new connection
establishment procedure is shown in Figure 3.50.
TABLE 3.8
The Example 1 Simple Type Declarations
Simple Type Declarations
Type Name Type Definition Type Encoding Comments
O_1 OCTETSTRING[1]
O_2 OCTETSTRING[2]
Detailed Comments:
TABLE 3.9
The Example 1 PDU Type Declarations
PDU Type Declaration
PDU Name: CONNECT_ind
PCO Type:
Encoding Rule Name:
Encoding Variation:
Comments: Example of PDU declaration
Design 139
TABLE 3.10
The Example 1 PDU Constraint Declarations
PDU Constraint Declaration
Constraint Name: CONNECT_ind
PDU Type: CONNECT_ind
Derivation Path:
Encoding Rule Name:
Encoding Variation:
Comments:
TABLE 3.11
The Example 1 Test Case 1
Test Case Dynamic Behavior
Test Case Name: Basic Connect TE2
Group:
Purpose: Check if a normal connection can be established
Configuration:
Default:
Comments:
3.10.2 Example 2
Figure 3.51 shows a hypothetical computer network with a star topology.
Three terminal nodes, N1, N2, and N3, are connected to one transit node
TN. The routing table residing in TN is shown in Figure 3.51 to the right of
TN. Terminal nodes generate messages for other terminal nodes in the net-
work. Depending on the value of the message parameter (1, 2, or 3), a transit
node delivers the message to its destination by sending it to the correspond-
ing port (A, B, or C).
The communication process that resides in the terminal node of the net-
work is specified by the statechart diagram shown in Figure 3.52. The process
that executes in the transit node is described by the statechart diagram shown
TABLE 3.12
The Example 1 Test Case 2
Test Case Dynamic Behavior
Test Case Name: Basic Disconnect TE2
Group:
Purpose: Check call disconnect
Configuration:
Default:
Comments:
READY_req
READY_conf
CONNECT_req
CONNECT_ind
CONNECT_conf
CONNECT_conf
DISCONNECT_req
DISCONNECT_ind
DISCONNECT_conf
DISCONNECT_conf
FIGURE 3.50
A new connection establishment procedure MSC.
Design 141
N2
Routing Table
B 1 A
2 B
3 C
TN
A C
N1 N3
FIGURE 3.51
A hypothetical star network with one transit and three terminal nodes.
rcv MSG
N123_IDLE
rcvMSG_req/snd rcvMSG_reject/snd
rcv MSG_conf/snd MSG_conf rcv MSG/snd MSG
MSG(dest) MSG_reject
N123_MSG_SENT
FIGURE 3.52
The statechart diagram of the process that runs in a terminal node of the network.
the user message MSG_req. The process returns to its initial state after the
reception of one of three possible messages, namely, MSG_conf, MSG, or
MSG_reject. The process that resides in the transit node of the network has
a single state, TN_IDLE. This process routes the input message toward its
destination.
Figure 3.56 shows the scenario of a successful message delivery. The node
N1 sends the correct message to the node N3 over the node TN. The user is
informed about the successful delivery by the message MSG_conf. Figure
3.57 shows the scenario of an unsuccessful message delivery. The node N1
has sent the message to the unknown destination, which has been rejected
from the node TN by the message MSG_reject.
TN_IDLE
FIGURE 3.53
The statechart diagram of the process that resides in the transit node of the network.
N123_IDLE N123_MSG_SENT
N123_IDLE
MSG(dest) MSG_conf MSG MSG_reject
N123_MSG_SENT
N123_IDLE
FIGURE 3.54
The SDL diagram of the process that runs in a terminal node of the network.
The next five tables constitute a simple TTCN test suite specification for
this example, as follows:
• Table 3.13 contains the PDU type declaration for the message
MSG_req.
• Table 3.14 contains the PDU constraint declaration MSG_req_
destination_addr_ok.
• Table 3.15 contains the PDU constraint declaration MSG_req_
destination_addr_not_ok.
Design 143
TN_IDLE
MSG(dest)
port = route(dest)
Is port
NO
valid?
YES
MSG MSG_reject
-
MSG_conf
FIGURE 3.55
The SDL diagram of the process that resides in the transit node of the network.
• Table 3.16 contains the test case that corresponds to the scenario of
sending a message to a known terminal node.
• Table 3.17 contains the test case that corresponds to the scenario of
sending a message to an unknown terminal node.
The reader is encouraged to play more with this example. One interesting
direction of generalization would be to consider a more complex network,
such as the one shown in Figure 3.58.
N1 TN N3
MSG_req
MSG(dest)
MSG_conf MSG
MSG_conf
FIGURE 3.56
A successful message delivery MSC.
N1 TN N3
MSG_req
MSG(dest)
MSG_reject
MSG_reject
FIGURE 3.57
An unsuccessful message delivery MSC.
TABLE 3.13
The Example 2 PDU Type Declaration MSG_req
PDU Type Declaration
PDU Name: MSG_req
PCO Type:
Encoding Rule Name:
Encoding Variation:
Comments: Example of PDU declaration
3.10.3 Example 3
This example illustrates reliable packet delivery based on the message
acknowledgment. Each communicating process expects the acknowledg-
ment of the message that it has previously sent. If the acknowledgment is
not received within the limited period of time, the corresponding timer will
Design 145
TABLE 3.14
The Example 2 PDU Constraint Declaration
MSG_req_destination_addr_ok
PDU Constraint Declaration
Constraint Name: MSG_req_destination_addr_ok
PDU Type: MSG_req
Derivation Path:
Encoding Rule Name:
Encoding Variation:
Comments:
TABLE 3.15
The Example 2 PDU Constraint Declaration
MSG_req_destination_addr_not_ok
PDU Constraint Declaration
Constraint Name: MSG_req_ destination_addr_not_ok
PDU Type: MSG_req
Derivation Path:
Encoding Rule Name:
Encoding Variation:
Comments:
expire and the process will assume that the message or its acknowledgment
have been lost and will retransmit the message once again.
The statechart diagram and the SDL diagram of the process are shown
in Figure 3.59 and Figure 3.60, respectively. The process has two stable
states, FSM_IDLE and FSM_MSG_SENT. In its initial state, the process
starts the timer T1, sends the message with the sequence number SN, and
evolves into its next stable state FSM_MSG_SENT. In that state, the process
either receives the acknowledgment, stops the timer T1, and returns to its
initial state, or the timer T1 expires and in its turn the process retransmits
the message.
In any state (FSM_IDLE or FSM_MSG_SENT), the process can receive a
message from its peer process. The process acknowledges the message if the
sequence number of the message is valid (in communication protocols, the
TABLE 3.16
The Example 2 Test Case 1
Test Case Dynamic Behavior
Test Case Name: Sending a message to a known terminal node
Group:
Purpose:
Configuration:
Default:
Comments:
TABLE 3.17
The Example 2 Test Case 2
Test Case Dynamic Behavior
Test Case Name: Sending a message to an unknown terminal node
Group:
Purpose:
Configuration:
Default:
Comments:
process would normally maintain the counter of the next expected message
in a sequence by incrementing its contents for each received message — a
validity check in this context would be to compare the sequence number in
the received message with the contents of this counter). If the sequence
number, RN, of the message is invalid, the process throws the message away.
Figure 3.61 illustrates two scenarios of the communication between two
peer processes. The MSC on the left in Figure 3.61 shows a successful mes-
sage delivery. The process FSM1 sends the message M1 to the process FSM2,
which in turn sends the acknowledgment ACK to the process FSM1.
The MSC on the right in Figure 3.61 shows a more complex scenario of
successful message retransmission after the unsuccessful first message
Design 147
Routing Table
N2
1 A
Routing Table 2 A
3 A
1 A
4 B
2 B
3 C
4 D B A
D TN2
B
TN1
A C
N1 N3 N4
FIGURE 3.58
The topology of a more complex hypothetical network.
rcv MSG(RN)
FSM_IDLE
[RN is ok] / snd ACK(RN)
rcv MSG(RN)
FSM_MSG_SENT
[RN is ok] / snd ACK(RN)
FIGURE 3.59
The statechart diagram of the communicating process that provides the reliable message deliv-
ery based on the re-transmission scheme.
delivery attempt. The process FSM1 sends the message M1, the process FSM2
receives it and sends its acknowledgment ACK, but it gets lost. The timer
T1 expires and the process FSM1 retransmits the message M1. The process
FSM2 receives it and sends its acknowledgment ACK, which is successfully
received by FSM1.
FSM_IDLE FSM_MSG_SENT
Start T1 ACK(SN) T1
FSM_MSG_SENT FSM_IDLE
MSG(SN)
FSM_IDLE,
-
FSM_MSG_SENT
MSG(RN)
Is RN
NO
OK?
YES
Destroy
ACK(RN) MSG(RN)
- -
FIGURE 3.60
The SDL diagram of the communicating process that provides the reliable message delivery
based on the re-transmission scheme.
Design 149
Start T1 Start T1
M1 M1
ACK ACK
Stop T1
T1 Expired
Start T1 M1
ACK
Stop T1
FIGURE 3.61
An example with two scenarios (with and without message retransmission).
3.10.4 Example 4
This example illustrates the sliding window concept, which provides a reli-
able and efficient transport service. Voluminous literature can be found that
addresses this topic (Halsall, 1988). The design shown here is based on Go-
back-N retransmission mechanism. It also supports the robust frame
acknowledgement procedure (one ACK may acknowledge more than one
frame).
The collaboration diagram in Figure 3.62 shows two distributed applica-
tions that communicate with the help of two communication objects, which
are deployed at the local and remote side. The application a1 sends the data
packed into messages (M) to the object p (primary), which in its turn encap-
sulates the messages into I (information) frames, together with its sequence
number V(s), and sends them to the object s (secondary). The object s checks
the frame I sequence number against the number it expects V(r), and if they
match, it accepts the frame I and acknowledges it by sending the message
ACK to the object p. If these numbers do not match, the object s rejects the
received I frame and sends the corresponding message NAK. We assume
that the numbers V(s) and V(r) are maintained in the variables vs and vr,
respectively. The object s delivers all the correctly received messages to the
remote application a2.
In this example, we are mainly interested in the communication protocol
between the primary and the secondary side of the communication link,
which is established by the corresponding communication processes, p and
s. The process p is modeled with the activity diagram shown in Figure 3.63
and Figure 3.64, whereas the process s is modeled with the activity diagram
shown in Figure 3.65.
I
M ACK/NAK M
a1 p s a2
FIGURE 3.62
The Example 4 collaboration diagram.
Assume that the variable rc holds the number of the I frames that were
sent by the process p but still not acknowledged by the process s. The activity
diagram in Figure 3.63 starts with the transition from the initial state to the
state IDLE. During this transition the variables vs and rc are reset. After
receiving a message M from the application a1, p checks if the send window
is full. If the send window is not full, p calls the procedure send(M) to
encapsulate M into I and sends it toward s. If the send window is full, p
adds M to the input queue (inputQueue). In both cases, it returns to the state
IDLE.
The procedure send(M) first creates the frame I and encapsulates the cur-
rent value of the variable vs and the message M in it by supplying them as
arguments of the corresponding constructor. It then adds the frame to the
retransmission queue (retransmissionQueue), allocates and starts a new timer
(T), adds the pair (T,I) to the map mapTtoI, adds the pair (I,T) to the map
mapItoT, increments vs and rc, and sends the frame I toward s. The map
mapTtoI is used to search for the frame I that corresponds to the given timer
T, whereas the map mapItoT is used to search for the timer T that corresponds
to the given frame I. Notice that the procedure send(M) assigns a timer to
each frame it sends. When the timer expires, p restarts the timer (restart-
Timer(T)), finds the corresponding frame by using the map mapTtoI, and
retransmits the frame toward s.
When p receives the message ACK from s, it provides the iterator on the
list retransmissionQueue and starts iterating through this list. For all the
frames whose sequence number is smaller than the sequence number in the
received ACK message, p finds the corresponding timer (by using the map
mapItoT), stops it, and removes both the pair (T,I) from the map mapTtoI and
the pair (I,T) from the map mapItoT.
Because some of the slots (or at least one of them) should be free after the
previous iteration, p provides the iterator on the list inputQueue and starts
iterating through it. It iterates while empty slots exist in the send window,
and while iterating, it removes the messages from the input queue and sends
them by calling the procedure send(M) as explained previously.
If the process p receives the message NAK, it performs the Go-back-N
retransmission procedure. Essentially, p scans the whole retransmission
queue and for each frame whose sequence number is greater than or equal
to the sequence number in the receive message ACK, it finds the correspond-
ing timer, restarts it, and retransmits the frame toward s.
The activity diagram shown in Figure 3.65 models the process s. It starts
with the triggerless transition from the initial state to the state IDLE. During
this transition, the variable vr is reset. After receiving the frame I, s checks
Design 151
Primary
(P) side
IDLE
message from the
rcv M application received
window
is full IDLE
send(M) definition
create I
I = new I(vs,M) frame
add I frame to
retransmission
retransmissionQueue.add(I) queue
allocate &
IDLE
start new
T = getTimer()
T expired timer
T expired
put new
restartTimer(T) mapTtoI.put(T,I) pair (T,I)
into mapTtoI
put new
I = map.get(T) mapItoT.add(I,T)
pair (I,T)
into mapItoT
/snd I get the corresponding
I frame & retransmit it
vs++, rc++
FIGURE 3.63
The Example 4 activity diagram, part I.
IDLE IDLE
robust ACK procedure Go-back-N
(one ACK may acknowledge retransmission
rcv ACK rcv NAK procedure
more I frames)
[else]
IDLE
[iter.hasNext()==true]
[iter.hasNext()==true] [else]
I = iter.next()
I = iter.next()
T = mapItoT.get(I)
iter.remove()
/snd I
[else]
[iter.hasNext()==true] restartTimer(T)
T = mapItoT(I)
[else]
M = iter.next() IDLE
mapItoT.remove(I)
iter.remove()
mapTtoI,remove(T)
send(M)
FIGURE 3.64
The Example 4 activity diagram, part II.
its sequence number equal to the value of the variable vr. If the values are
the same, s accepts the frame by incrementing vs, creating the message ACK,
and sending it to p. If the values are different, s rejects the frame by sending
the message NAK to p.
The next three figures show three typical scenarios. The sequence diagram
shown in Figure 3.66 illustrates a successful frame delivery scenario. The
frames I(0) and I(1) are sent through the window and are acknowledged
Design 153
Secondary
(S) side
/vr = 0
reset V(R), vr
IDLE
rcv I
[vr == I.N]
/snd ACK
IDLE
FIGURE 3.65
The Example 4 activity diagram, part III.
with ACK(1) and ACK(2), respectively. After some delay, I(2) is sent and it
is also successfully acknowledged with ACK(3).
The sequence diagram shown in Figure 3.67 illustrates the Go-back-N pro-
cedure. The process p starts by sending the frames I(0) and I(1). The frame
arrives at s side regularly but I(1) gets lost. This causes the mismatch of
sequence numbers at the secondary side when it successfully receives I(2),
because the value of the variable vr is 1 (which indicates that s is awaiting
I(1) instead of I(2)). Because the sequence number of the frame and the value
of the variable are not the same, s rejects the frame by sending the message
NAK(1). The process p in its turn retransmits both I(1) and I(2).
The sequence diagram shown in Figure 3.68 illustrates the frame retrans-
mission triggered by the retransmission timer. The process p starts again by
sending I(0) and I(1) in succession. The process s in its turn acknowledges
them by ACK(1) and ACK(2), respectively. The message ACK(1) arrives suc-
cessfully at the primary side, but the message ACK(2) gets lost. This causes
the corresponding timer to expire after a while. Triggered by that event, p
restarts the timer and retransmits the frame I(1). The second time both I(1)
p s
I(0)
I(1)
ACK(1)
ACK(2)
I(2)
ACK(3)
FIGURE 3.66
The Example 4 MSC diagram: Successful frame delivery.
and the corresponding ACK(2) are successfully transferred over the commu-
nication link. After receiving ACK(2), p stops the timer and removes I(1) from
the retransmission queue.
3.10.5 Example 5
In this example, we design the SIP INVITE client transaction in accordance
with RFC 3261, Section 17.11. First, let us return to the requirements and
analysis of a SIP Softphone, introduced as an example at the end of the
previous chapter. Briefly, in that example we have constructed the use case
diagram and have transformed it into the corresponding general collaboration
diagram. At the very end of that example, we have shown the one particular
collaboration related to the successful session establishment.
Now let us zoom in on the general collaboration diagram of a SIP Soft-
phone with the focus on the SIP INVITE client transaction and the surround-
ing objects with which it directly communicates. The resulting general
collaboration diagram is shown in Figure 3.69. The SIP INVITE client trans-
action is modeled as an unnamed object of the class InClientT because this
object is dynamically created upon user request. It collaborates with the
following three objects:
Design 155
p s
I(0)
I(1)
ACK(1)
I(2)
NAK(1)
I(1)
I(2)
ACK(2)
ACK(3)
FIGURE 3.67
The Example 4 MSC diagram: Go-back-N retransmission.
p s
I(0)
I(1)
ACK(1)
ACK(2)
T(1) expired
I(1)
ACK(2)
FIGURE 3.68
The Example 4 MSC diagram: I frame retransmission triggered by the retransmission timer.
tud : TUDisp
tlid : TLIDisp
FIGURE 3.69
The SIP INVITE client transaction collaboration diagram.
Design 157
tud : TUDisp
5:
8: rsp(
rsp 1X
(2 XX
00 )
)
tlid : TLIDisp
FIGURE 3.70
A successful session establishment collaboration diagram.
tud : TUDisp
5
8: : rs
rsp p(
(3 1XX
00 X
–6 )
99
)
tlid : TLIDisp
FIGURE 3.71
An unsuccessful session establishment collaboration diagram.
1: req(INVITE)
2: req(INVITE)
3: rsp(1XX)
4: rsp(1XX)
5: rsp(1XX)
6: rsp(200)
7: rsp(200)
8: rsp(200)
FIGURE 3.72
A successful session establishment sequence diagram.
Design 159
1: req(INVITE)
2: req(INVITE)
3: rsp(1XX)
4: rsp(1XX)
5: rsp(1XX)
6: rsp(300–699)
7: rsp(300–699)
8: rsp(300–699)
9 req(ACK)
FIGURE 3.73
An unsuccessful session establishment sequence diagram.
this procedure to repeat the maximum of seven times before the timer TB
expires. If the timer TB expires (or if a transport error is detected), the SIP
INVITE client transaction informs TU accordingly and moves to the state
Terminated, and from there to its final state.
Most frequently, a response to the request INVITE will be received before
the timer B expires. In such a case, the SIP INVITE client transaction stops
both timers and moves to the next state, which depends on the type of
response. If the provisional response rsp(1xx) is received, the SIP INVITE
client transaction forwards it to TU and moves to the state Proceeding. If the
successful final response rsp(2xx) is received, the SIP INVITE client transac-
tion forwards it to TU and moves to the state Terminated. If the unsuccessful
final response rsp(300-699) is received, the SIP INVITE client transaction
forwards it to TU and sends the signal (message) ACK to the remote site.
While being in the state Proceeding, the SIP INVITE client transaction
simply forwards all the preliminary responses rsp(1xx) to TU. Once it receives
the successful final response rsp(2xx), it forwards it also to TU and moves
to the state Terminated. If the SIP INVITE client transaction receives the
unsuccessful final response rsp(300-699) in the state Proceeding, it forwards
that response to TU, sends the signal req(ACK) to the remote site, and moves
to the state Completed.
At the entrance to the state Completed, the third timer, namely the timer D
(TD) is started. While being in the state Completed, the SIP INVITE client
transaction just confirms any unsuccessful final responses rsp(300-699) by
sending the SIP message ACK to the remote site. If the SIP INVITE client
Calling
rcv rsp(300–699)/snd
rsp(300–699), snd req(ACK) Proceeding
TB exp. or Trans.
rcv rsp(300–699)/snd rsp(300–699),snd req(ACK) Err/inform TU
Trans. Err/inform TU
TD exp.
Terminated
FIGURE 3.74
The statechart diagram of the SIP INVITE client transaction.
Design 161
Initial
Invite
Invite_T to
TPL
Transport
unreliable
Start Timer B
Tb = 64*T1
Calling
FIGURE 3.75
The SDL diagram of the SIP INVITE client transaction, part I.
Calling
Start Timer A Stop Timer A Stop Timer A Stop Timer A Stop Timer A
Stop Timer A
Ta = 2*Ta Stop Timer B Stop Timer B Stop Timer B Stop Timer B
Retransmit
300 – 699 to
INVITE to 1xx to TU 2xx to TU Inform TU
TU
TPL
ACK_T to
- Proceeding Terminated
TPL
reliable Transport
unreliable
Completed
FIGURE 3.76
The SDL diagram of the SIP INVITE client transaction, part II.
Design 163
Proceeding
Terminated -
ACK_T to
TPL
Transport
reliable unreliable
Completed
FIGURE 3.77
The SDL diagram of the SIP INVITE client transaction, part III.
Completed
ACK_T to
Stop Timer D
TPL
-
Inform TU
Terminated
FIGURE 3.78
The SDL diagram of the SIP INVITE client transaction, part IV.
References
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Modeling Language User Guide ,
Addison-Wesley, Reading, MA, 1998.
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Software Development Process ,
Addison-Wesley, Reading, MA, 1998.
Halsall, F., Data Communications, Computer Networks and OSI , Addison-Wesley, Read-
ing, MA, 1988.
4
Implementation
165
Implementation 167
the separate section. After that, we cover the concepts and most important
design and implementation details of the FSM library (its reference man-
ual is given in Chapter 6). We conclude this chapter with two implemen-
tation examples.
The differences between the components and the classes are the following:
• The former represent physical entities, whereas the latter are con-
ceptual abstractions, so they exist on different levels of abstraction.
• The former only have operations that are accessible through their
interfaces, whereas the latter may have both operations and
attributes.
Component Node
Package
Interface Note
FIGURE 4.1
The set of symbols available for rendering component diagrams.
Implementation 169
• Modeling APIs
• Modeling executables and libraries
• Modeling source code
TCPSockets
UDPSockets
IPInterface
tcpipstack.dll
FIGURE 4.2
An example of a simple API.
SIPInterface
sip.dll
TCPSockets
UDPSockets
IPInterface
tcpipstack.dll
FIGURE 4.3
An example of a simple API user.
• Technical issues
• Configuration management issues
• Reusability issues
Implementation 171
softphone.exe
SIPInterface
sip.dll
TCPSockets
UDPSockets
IPInterface
tcpipstack.dll
FIGURE 4.4
The model of a simple executable.
«file» «executable»
Main.dsw Main.exe
«file»
Constants.h
«file» «file»
AutomataA.h AutomataB.h
FIGURE 4.5
The model of a simple project.
of the header and source code files, except Constants.h, use the framework
FSMLibrary.
Implementation 173
A = (X, Y, S, t, o, S0 )
where
X = {X1, X2, …Xn} is a set of input signals (input alphabet)
Y = {Y1, Y2, …Ym} is a set of output signals (output alphabet)
S = {S1, S2, …Sk} is a set of states (state alphabet)
S0 is the initial state
t is the transition function, which maps the Cartesian product of SxX to S
o is an output function, which maps the Cartesian product of SxX to Y
B2 /1)
)
B
(0
2
/0
B12
S1 S2
(1/1)
B
(1 31 3
B2 /2)
/0
) (1 Legend:
Bij - i is the number of the current
state, j is the number of the next state
S3 (x, y) -x is an input signal, y is an
output signal
(0 33
)
B
/2
FIGURE 4.6
The counter by modulo 2 state transition graph.
TABLE 4.1
The Counter by Modulo 2 Transition Table
Next State//Output Signal Input Signal 0 Input Signal 1
State S1 1/0 2/1
State S2 2/1 3/2
State S3 3/2 1/0
C = (X, Y, S, t, o, S0)
where
X = {0, 1}
Y = {0, 1, 2}
S = {S1, S2, S3}
S0 = S1
0/1
S2
1/1 1/2
0/0 0/2
1/0
S1 S3
FIGURE 4.7
The counter by modulo 2 statechart diagram.
Implementation 175
The simplest but perhaps still the most frequently used FSM implemen-
tation is based on the structural or procedural approach. This implementa-
tion is made in the form of nested selection statements in higher-level
programming languages. In the programming languages C/C++ and Java,
we typically use switch-case statements for this purpose because the control
flow structures made with if and else-if statements are less readable.
Typically, the outermost switch-case statement selects a case that corre-
sponds to the automata current state. In the code paragraph that defines the
processing of the current state, normally we use the second, nested switch-
case statement, which selects the case that corresponds to the input signal.
The program paragraph that corresponds to that input signal effectively
performs the transition by creating the corresponding output signals and
evolving to the next state. This evolution is made simply by updating the
content of a variable that holds the identification of the current state (most
frequently, this is just the index of the state).
Actually, the structure of the resulting program code is very similar to the
program representation of SDL (SDL-PR), which was introduced in the pre-
vious chapter, and this fact is also mentioned there. Generally, communica-
tion protocol implementation based on nested switch-case statements looks
like the following:
switch(state) {
case STATE_1:
switch(message_code) {
case MESSAGE_CODE_1:
// processing of the message code 1 in the state 1
break;
case MESSAGE_CODE_2:
// processing of the message code 2 in the state 1
break;
case MESSAGE_CODE_3:
// processing of the message code 3 in the state 1
break;
...
default:
// processing of the unexpected message in the state 1
break;
}
case STATE_2:
switch(message_code) {
case MESSAGE_CODE_1:
// processing of the message code 1 in the state 2
break;
case MESSAGE_CODE_2:
// processing of the message code 2 in the state 2
break;
ase MESSAGE_CODE_3:
// processing of the message code 3 in the state 2
break;
...
default:
package automata;
import java.util.*;
import java.io.*;
public class Environment1 {
public static void main(String[] args) throws IOException {
char ch = '0';
Automata1 a1 = new Automata1();
System.out.println(“This is the example of counter by modulo 2.");
System.out.println(“Automata evolution has started...");
while(true) {
System.out.print(“Enter input signal (0/1 and <ENTER>):”);
ch = (char)System.in.read();
System.in.skip(2);
if(((ch!='0') && (ch!='1'))) break;
a1.processMsg(ch);
}
}
}
The demo program initially creates the object a1, an instance of the class
Automata1, which is the structural and procedural implementation of the
counter by modulo 2. After printing two welcome messages, it falls into an
infinite while loop in which it prompts the user for the input signal and reads
it. If the input signal is neither 0 nor 1, the demo program breaks the loop
and terminates. Otherwise, it performs one step of the automata evolution
by calling the procedure processMsg() of the object a1.
The Java code for the class Automata1 is the following:
package automata;
public class Automata1 {
private static final int S1 = 0;
private static final int S2 = 1;
private static final int S3 = 2;
private static final char M1 = '0';
Implementation 177
The implementation above starts with the definition of the symbolic con-
stants that correspond to the possible automata states, namely S1, S2, and
S3, and valid input signals M1 and M2 (input signals 0 and 1). Next, we
define the variable state that holds the current automata state and we set it
to the value S1 (the automata initial state).
The method processMsg starts with the switch-case statement that selects
the further execution path depending on the content of the variable state
(i.e., the current automata state). Three possible cases are found that are
defined by the corresponding case clauses. Each of these clauses contains a
further switch-case statement that distinguishes between two valid input
signals, namely M1 and M2. The nested case clause that corresponds to the
particular input signal prints the message, which corresponds to the output
signal, and updates the variable state, if the current state of the automata
changes.
This example demonstrates the main advantage of the structural or pro-
cedural approach, and that is simplicity, which yields greater performance
in terms of execution speed. Another advantage is that we can easily con-
struct a compiler or a code generator that generates such implementations
(a good example that justifies this claim is SDL-PR). The main disadvantage
of this approach is its bad scalability, which becomes evident in the case of
large-scale implementations, i.e., implementations of automata that have a
large number of states and state transitions.
The code size for such program implementations increases linearly with
the number of states and the number of state transitions. Another disadvan-
tage of this approach is that it is monolithic, which implies static regarding
the need to change the automata, either by adding new, or deleting the
existing states, or by adding or deleting state transitions.
In this type of implementation, the structure of the automata (its vertex
and arcs) is built into the machine code of the implementation (hard-coded).
We say that the input signal processing flow is governed by the structure of
the machine code. If we want to add or delete a state or a state transition,
we must change the program code, recompile it, and install the new version
on the target platform. Most frequently, the installation procedure requires
the system to be restarted at its end. Restarting the system means that
effectively it will not be operational for a certain short interval of time. The
problem is that some types of systems, such as nonstop systems, may not
tolerate restarts no matter how short the time interval is.
Some systems try to make restarts allowable by providing processor tan-
dem configurations. Typically in such a system, one of the processors con-
tinues the normal operation while the other is restarting after an update. In
that case, we have a synchronization problem, which of course can be solved
but it could be rather complex. Generally, system restarts are problematic
and should be handled with special care.
On the other end of the spectrum of FSM implementations, we have the
diametrical approach to FSM implementation in which the structure of the
automata is not defined by the program control flow but rather with the
corresponding data structure. The simple interpreter uses this data structure
to process the incoming events (messages), therefore it is referred to as an
event interpreter. The data structure implementations in assembler and C
programming language are built from lists and lookup tables.
Implementation 179
current state Li
...
Task n k
k
0
FIGURE 4.8
The event interpreter and the data structure that defines the FSM structure.
The automata evolution is driven by the incoming events. Each input event
triggers one step of the evolution. The event interpreter carries out the
evolution step by traversing the data structure to determine the current state
and the state transition that corresponds to the input event type. In contrast
to this common part of the message processing flow — which is directed by
the data structure — program parts that correspond to particular reaction
tasks are dedicated routines that perform specific functions, which cannot
be generalized.
Figure 4.8 illustrates the FSM implementation based on the event inter-
preter and the data structure that defines the FSM structure (essentially, the
state transition graph). New incoming events (messages) are added at the
end of the message queue (see the top left corner of Figure 4.8). The inter-
preter takes the messages from the head of the message queue and processes
them by using the data structure, which comprises:
The automata control table is assigned to automata to store its current state
and optionally some of its additional attributes. The automata state table is
a lookup table that maps the state index into the address of the corresponding
list of valid events in that state. The elements of this list contain the complete
information necessary and sufficient to perform the state transition from the
current state to the next state, which is determined by the event type. This
information is stored in the following fields:
• event ID: holds the event type to which this element corresponds
• task address: contains the pointer to the corresponding routine
(procedure)
• next state: stores the index of the next state
• next: contains the pointer to the next element in the list
The event interpreter processes the message through the following steps:
Implementation 181
always repeats the same routine. This is the same for all FSMs. Therefore,
this routine is universal in contrast to the implementation with nested switch-
case statements, which implement just one particular FSM. This characteristic
is especially important from the point of software maintenance. If we want
to change the FSM structure by adding or deleting states or state transitions,
we must update the data structure. There is no need to change the simple
interpreter routine at all.
The second characteristic of the event interpreter-based approach is that
it enables sharing of common tasks between more state transitions. In prin-
ciple, this is also possible in the nested switch-case-based approach by intro-
ducing common functions, which are called from the corresponding case
program clauses, but this is seldom used by their practitioners. In the event
interpreter-based approach, this possibility becomes more apparent and,
therefore, really used because tasks are already specified as procedures (sub-
routines) rather than case program clauses.
Because of task sharing, the number of tasks may generally be smaller
than the number of state transitions. We can also organize tasks hierarchi-
cally, such that higher-level tasks call their subordinate tasks. This makes it
possible to implement more complex tasks by using simple primitives. Such
organization has the following advantages:
package automata2;
import java.util.*;
import java.io.*;
class Task {
public int id;
public Task(int ident) {id=ident;}
public void processMsg() {System.out.println(id);}
}
class Branch {
private String msgcode;
class State {
private String stateid;
public Set setofbranches;
class AStructure {
private String automataid;
private Set setofstates;
class Automata {
protected AStructure structure;
protected String stateId;
protected State initial;
Implementation 183
}
}
Iterator iterS =
currentS.getSetOfBranches().iterator(); while(iterS.hasNext()) {
Branch eachB = (Branch)iterS.next();
if(eachB.getMsgCode().equals(msg)) {
Task t=eachB.getTask();
t.processMsg();
stateId=eachB.getNextStateId();
break;
}
}
}
}
The class Task models the task that is performed during the transition from
the current state to the next state. The task identification is stored in the class
field id. The user of the class Task specifies the particular task identification
as the parameter of the class constructor. The default message processing
function, named processMsg(), just prints the task identification to the stan-
dard output file.
The class Branch models the arc of the state transition graph. The attributes
of the state transition are the message code that triggers the state transition,
the task that is performed during the state transition, and the identification
of the next stable state. The corresponding fields are named msgcode, task,
and nextstateid, respectively. These fields are set by the class constructor. The
current content of these fields is returned by the functions getMsgCode(),
getTask(), and getNextStateId(), respectively.
The class State models a single FSM state. The state attributes are the state
identification and the set of the outgoing state transitions (the target state is
irrelevant; it can be this state or some other state). The corresponding class
fields are named id and branches, respectively. Their content is set by the class
constructor and returned by the functions getStateId() and getSetOfBranches(),
respectively.
The class AStructure models the FSM structure. Its attributes are the auto-
mata identification and the corresponding set of states. The corresponding
class fields are automataid and setofstates. The class constructor gets particular
values for these fields through its parameters. The functions getAutomataId()
and getSetOfStates() return the current values of these fields.
Finally, the class Automata models the complete FSM. Its attributes are the
FSM structure (essentially the set of sets of state transitions), the current state
identification, and the initial state identification. The corresponding class
fields are named structure, stateId, and initial, respectively. These fields are
set by the class constructor.
The function processMsg(String msg) is the event interpreter. The input
argument msg is the message, which triggered the state transition. The inter-
pretation starts with the iteration through the set of states to locate the object
that corresponds to the FSM current state (its identification is stored in the
class Automata2 {
public static void main(String[]args) throws IOException {
Automata a2 = makeAutomata();
char ch;
String msg;
System.out.println(“This is the example of counter by modulo 2.”);
System.out.println(“The automata evolution has started...”);
while(true) {
System.out.print(“Enter input signal (0/1 and <ENTER>): ”);
ch = (char)System.in.read();
System.in.skip(2);
if(((ch!='0') && (ch!='1'))) break;
Implementation 185
Automata Automata2
1 1
+processMsg()
1
1
AStructure
1
∗
State
1
∗
Branch
Task0
1
1
Task Task1
Task2
FIGURE 4.9
The static structure used in the second approach to the FSM implementation.
Implementation 187
State
S1 S2 S3
FIGURE 4.10
The counter by modulo 2 state class hierarchy.
• Use FSM input message (signal) and the lookup table (map), which
is associated with the FSM current state, to determine the corre-
sponding unstable state (state transition).
• Perform the application-specific task by calling the message proc-
essing function defined within the class that models the correspond-
ing unstable state.
• Move the FSM into its next stable state.
The class hierarchy for the counter by modulo 2 is defined with the following
Java module:
package automata;
import java.util.*;
class State {
public State msgToBranch(String msg) {return new State();}
public State processMsg() {return new State();}
}
Implementation 189
}
}
class B23 extends S2 {
public State processMsg() {
System.out.println(“Output: 2”);
return new S3();
}
}
public Automata3() {
state = new S1();
}
public void processMsg (char chmsg) {
String msg;
if(chmsg=='0') msg=“0”; else msg=“1”;
state = state.msgToBranch(msg);
state = state.processMsg();
}
}
The basic class State has two default functions, msgToBranch() and pro-
cessMsg(). Both functions return an instance of the class State. The fact that
the instance of the class derived from the class State is also considered to be
the instance of the class State enables the event interpreter to employ poly-
morphism. We will return to this point shortly.
The function msgToBranch() is responsible for mapping the FSM input
message into the corresponding state transition object. The input message
in this simple example is a one-character string (“0” or “1”). The function
can return any instance of the basic class State, but normally in this example
it should return the instance of the class B11, B12, B22, B23, B33, or B31.
The function processMsg() caries out the application-specific task for the
given input message. It returns the FSM next stable state. The idea is that
the FSM dynamically changes its behavior. The FSM is in a certain state,
either stable or unstable, at any point in time, but it is always represented
by a single object. That object is actually returned by one of these two
functions, which are called in the course of FSM evolution.
Next, we define the classes that model the FSM stable states, namely, S1, S2,
and S3. Each of these classes extends the basic class State and overrides the
default function msgToBranch() with the application-specific one. These partic-
ular functions actually delegate their responsibility to the function getBranch()
of the class Structure3 by passing their identification (“0,” “1,” and “2” for S1,
S2, and S3, respectively) and the input message to it. More precisely, these
simple functions just return the unstable state object that is provided by the
function getBranch() to their caller, and that is the event interpreter.
The stable state classes are followed by the classes that model the FSM
unstable states, namely, B11, B12, B22, B23, B33, and B31. Each of these classes
extends the corresponding stable state class and overrides the default func-
tion processMsg(), which it inherits from the basic class State, with the appli-
cation-specific one. These particular functions perform the application-
specific tasks and return the corresponding next stable state object (S1 for
B11 and B31, S2 for B12 and B22, and S3 for B23 and B33). The application-
specific tasks in this simple example are implemented as the corresponding
print statements to the standard output file.
The FSM is modeled with the class Automata3. This class has a single
attribute named state, which is set by the class constructor to the FSM initial
stable state, namely S1. Later during the FSM evolution it changes and can
become any FSM state, either stable or unstable.
The class Automata3 has a single function, named processMsg(), that is the
FSM event interpreter. This function performs one state transition in two
steps. In the first step, it calls the function msgToBranch() of the FSM current
stable state object. This effectively starts the state transition by moving the
FSM from its current stable state to the unstable state that corresponds to
the input message. In the second step, the event interpreter calls the function
processMsg() of the FSM unstable state, which performs the application-
specific task and returns the FSM next stable state object. This effectively
completes the state transition. Interestingly, the state class hierarchy in this
approach is completely application-specific whereas the event interpreter is
very simple and generic and therefore can be reused in implementations of
other FSMs.
The following utility classes support mapping of input messages to the
corresponding state transitions (unstable state objects):
package automata;
import java.util.*;
class MapContainer {
private String identification;
private Map map;
Implementation 191
The class MapContainer stores the map identification and the map itself in
the attributes identification and map, respectively. These attributes are set by
the class constructor. Their current content is available through the corre-
sponding get functions.
The class Structure3 contains a set of maps for all FSM stable states. This
set is established by the function setMaps() and searched by the function
getBranch(). The input parameters of the function getBranch() are the map
(i.e., stable state) identification and the input message. The function get-
Branch() iterates through the set of map containers, locates the one with the
given identification, uses the located map to get the state transition that
corresponds to the input message, and returns it to its caller.
An important feature of this approach is that it is based on Java sets and
maps, which makes it an ideal environment for making dynamically recon-
figurable FSMs as Java sets and maps can be dynamically updated. For
example, if we want to add a new state transition B21, it would be sufficient
to write, compile, and dynamically load a new class B21 that represents it
and to add the corresponding entry in the map that is associated to the FSM
stable state S2.
Because the current Java version does not support a map of maps, the
solution for mapping input events to the corresponding state transitions
presented here is based on the usage of a set of maps. Worth mentioning is
the fact that an environment with a map of maps would enable top perfor-
mance implementations based on two connected mappings. The key for the
first mapping would be the FSM current stable state whereas the key for the
second mapping would be the input message. The performance of such
implementations would be even better than the performance of the imple-
mentations based on nested switch-case statements.
Environment3
1 1 1 1
Automata3 Structure3
1 -state 1
1 ∗
State MapContainer
S1 S2 S3
FIGURE 4.11
The static structure used in the third approach to the FSM implementation.
The class Environment3 uses the previously defined classes and demon-
strates their usability. The corresponding Java code is the following (the
overall class architecture is shown in Figure 4.11):
package automata;
import java.util.*;
import java.io.*;
Implementation 193
The function main starts by creating the object a3, an instance of the counter
by modulo 2. It then creates all the necessary maps and map containers, the
set of maps named maps, the object st3, an instance of the class Structure3.
After this, it sets the set of maps by calling the function setMaps() and falls
into an infinite while loop in which it reads FSM input messages and calls
the event (message) interpreter until the user enters a signal that is neither
“0” nor “1.”
The keys for searching Java maps in this simple example are just simple
strings (“0” and “1”). This Java map is a rather powerful abstraction because
its key may be any class whose instances are comparable. This makes it
possible to model real communication protocol messages with such classes
and to build Java maps for them. Once we model the messages by the
corresponding objects, FSM objects can interact with them in an object-
oriented fashion.
If we want to provide a full object-oriented treatment of communication
protocol messages, we must provide the corresponding serialization func-
tions. Two types of these functions are actually used. The first type is used
for converting an object into a series of octets that can be transported over
the communication line. The second type performs the reverse operation by
converting the received series of octets into the corresponding object. If we
do not provide these serialization functions, we are forced to operate directly
on numbers and use switch-case and similar statements unpopular in the
object-oriented world.
Implementation 195
-state
Automata4 State
-state : State 1 1
+processMsg() +processMsg()
state.processMsg()
S1 S2 S3
FIGURE 4.12
The static structure used by the State design pattern.
At the end of this short overview of the State pattern, we illustrate its
applicability with the simple example — a State pattern-based implementa-
tion of the counter by modulo 2. The corresponding class diagram is shown
in Figure 4.12. The context in this example is the class Automata4. The
attribute state holds the current FSM state object. The key function pro-
cessMsg() delegates message processing to the current FSM state object by
calling its function processMsg().
The generic state class State defines a simple interface, which comprises a
single function, processMsg(). Generally, such a function would define the
default FSM behavior, which can then be overridden in the concrete substate
classes. In this simple example, as we will shortly see, no such a behavior
is allowed and therefore the corresponding operation is simply empty.
The concrete substate classes S1, S2, and S3 are derived from the generic
state class State. Each of these classes provides a state-specific behavior by
overriding the function processMsg() with its own particular definition. The
corresponding code in Java is the following:
package automata4;
import java.util.*;
class State {
public void processMsg(Automata4 a,char ch) {
}
}
The definition of the class Automat4 begins with the definition of the field
state, which is used to store the FSM current state object. The class constructor
sets this field to the FSM initial state object, which is an instance of the class
S1. The function setState() is used by the FSM concrete state objects to change
the FSM state (an example of distributed transit logic). The function pro-
cessMsg() simply calls the corresponding function on the FSM current state
object.
The class State defines a simple state interface with just one function —
processMsg( ) — which is empty because this example has no default behav-
ior. The class S1 is an example of a concrete substate class. It defines the S1-
specific FSM behavior by overriding the function processMsg() that it inherits
from the base class State. This function checks whether the input signal is 0
or 1, prints the corresponding output signal, and changes FSM state by
calling the function setState(). We made the context accessible by passing it
as a parameter to the function processMsg().
Implementation 197
The following Java code creates the working environment for this example
(given without the comments because similar code is already explained in
a previous section):
package automata4;
import java.util.*;
import java.io.*;
The reader may be puzzled by the fact that the list given above does not
include any message receiving functions. The FSM Library is specific in this
respect. The developer does not need to explicitly call a function that receives
a message (signal). Rather, the FSM execution platform (provided by the
class FSMSystem) routes all sent messages toward their destination automata,
locates the state transition function that corresponds to the message type
(determined by the content of the corresponding message header field), and
calls it as its subroutine. We will see shortly that the function that performs
the message routing and processing (named Start) is actually the event
interpreter.
Therefore, the FSM Library completely supports the message handling
style present in the design artifacts (statecharts, activity diagrams, and SDL
diagrams), which just name the input event (message) without taking care
of how that event is effectively recognized (received). The FSM Library
provides the class FSMSystem to support the straightforward implementa-
tions of design artifacts. Once provided with the class FSMSystem, the
Implementation 199
developers do not care how the message is received; they simply write the
C++ function that performs the state transition when the message is received.
Other FSM Library specifics are the following:
Implementation 201
handles the message header and a group that handles the message payload,
the FSM Library provides complete FSM implementation independence from
the message source and destination information location.
An additional enhancement related to the message destination provided
by the FSM Library is the support for sending messages to the left or to the
right FSM. The abstraction of the left and right FSM originally comes from
SDL. If the SDL symbol for sending a message points to the left, we say that
the message is sent to the left FSM. Similarly, if the symbol points to the
right, we say that the message is sent to the right FSM.
The internal class KernelAPI provides the functions SendMessageLeft and
SendMessageRight, which are inherited by the class FiniteStateMachine, to
support this abstraction. These two functions enable the direct coding of the
corresponding parts of SDL diagrams, and the resulting C++ code has a great
similarity with the original SDL diagrams. For example, consider the follow-
ing snippet of C++ code that corresponds to a state transition:
StopTimer(FE4_TIMER1);
DisconnectRingTone();
PrepareNewMessage(0x00,r2_SetupRespConf);
SendMessageLeft();
StartChargingIncoming();
Connect();
SetState(FE4_ACTIVE);
Implementation 203
the FSM Library, besides writing the initialization function and a couple of
simple auxiliary functions, is the encoding of state transitions by using the
set of primitives provided within the FSM Library application programming
interface (see Section 6.8). A good thing about these primitives is that they
provide mapping of SDL steps in almost a one-to-one manner. The names
of the primitives are almost self-documenting, at least after the short expe-
rience you get by using them. The code resembles the original design artifacts
(especially SDL diagrams). All these attributes helps any member of the
development team to read, understand, and continue the work that was
done by some other member of the development team, especially if they
have the design artifact at their disposal.
Also worth mentioning is that besides forward engineering, the FSM
Library helps backward engineering, too. This is especially true if the back-
ward engineering is done by hand. Using software tools for that purpose is
also possible if the development team strictly obeys certain coding guide-
lines. The key for the successful forward and backward engineering is the
well-defined API (see Section 6.8).
We demonstrate the usage of the FSM Library API by the examples at the
end of this chapter, as well as with the examples at the end of Chapter 6.
Implementation 205
• Maintaining the current state variable (the field member of this class)
• Maintaining the state transition table
• FSM evolution support by providing the address of the state tran-
sition function that corresponds to the incoming message type
• Message handling (message checking, parsing, and creation)
• Message exchange (the message send operation is explicit whereas
the message receive operation is implicit)
• Memory management (supports requesting and releasing buffers for
messages)
• Timer management (supports starting, stopping, restarting, and test-
ing timers)
void FSMSystem::Start(){
SystemWorking = true;
while(SystemWorking) {
Sleep(1);
for(uint8 i=0; i<NumberOfMbx; i++) {
uint8 *msg = GetMsg(i);
if(msg == NULL){
continue;
}
uint8 automataType = GetMsgToAutomata(msg);
if(((automataType > NumberOfAutomata) ||
(NumberOfObjects[automataType] == 0))){
// Error handling
DiscardMsg(msg);
continue;
}
The function Start initially sets its field member SystemWorking to the value
true and enters the loop, which is executed while SystemWorking has the
value true. Once this variable is set to the value false (this is exactly what the
API function StopSystem() does), the function Start exits the loop and termi-
nates. Because this function is the FSM event interpreter, once it stops the
whole system stops.
Inside the while loop, this function enters the nested for loop in which it
checks all mailboxes for messages. This for loop starts from the mailbox with
the identification (index) 0, thus making it the highest priority mailbox. As
it proceeds toward the identification NumberOfMbx, the priority of the cor-
responding mailboxes decreases.
Once it finds a message in the mailbox, it exits the nested for loop and
continues with determining the destination automata (FSM) type iden-
tification by calling the function GetMsgToAutomata(). If the identification is
invalid (greater than the configuration parameter NumberOfAutomata) or if
no instances of that type are found, the function discards the message by
calling the function DiscardMsg() and continues the main loop.
If the automata type identification is valid and at least one instance of that
type is found, the function Start determines the destination object iden-
tification by calling the function GetMsgObjectNumberTo(). If this iden-
tification is equal to UNKNOWN_AUTOMATA, the function Start tries to
allocate an object from the pool of objects of the given type by calling the
function Get() on the object of that type.
If at least one free object is found in the pool (actually an array of objects
of the given type), the function Get() will return the identification (array
index) of the first one and in its turn, the function Start will call its function
ProcessMsg(). Behind the scenes, the function ProcessMsg() locates the state
transition that corresponds to the message type, calls it as its subroutine,
Implementation 207
and continues the main loop. If no free objects are in the pool, the function
Start discards the message and continues the main loop.
Finally, if the message destination is a known object (its identification is
not equal to UNKNOWN_AUTOMATA), the function Start checks if its
identification is valid (not greater than the configuration parameter Num-
berOfObjects[automataType]). If the object identification is valid, the func-
tion Start calls object function processMsg() and continues the main loop.
FSMSystem
LogAutomata MessageInterface
LogFile LogTCP
FIGURE 4.13
The internal FSM Library static structure.
struct SState {
SState(uint16 maxNumOfProceduresPerState);
~SState();
bool StateValid; // if true, data are valid
unsigned short NumOfBranches; // number of branches in a state
// procedure for processing unexpected message
PROC_FUN_PTR UnexpectedEventProcPtr;
SBranch* PBranch; // pointer on data for each branch
};
struct SBranch {
uint16 EventCode; // message code
PROC_FUN_PTR ProcPtr; // message processing function
};
Implementation 209
The field EventCode contains the code of the event (message) that triggers
this state transition. The field ProcPtr contains the pointer to the C++ function
that performs the actions during this particular state transition.
Generally, an FSM can use a number of timers. Each timer is represented
with the instance of the structure TimerBlock:
struct TimerBlock {
TimerBlock(uint16 v, uint16 s) :
Count(v), SignalId(s), Valid(false), TimerBuffer(0){}
TimerBlock() :
Count(INVALID_32), SignalId(INVALID_16), Valid(false),
TimerBuffer(0) {};
uint32 Count; // in time slices
uint16 SignalId; // message code
bool Valid; // if true, data is valid
ptrBuff TimerBuffer; // Ptr to timer buffer
};
The field Count defines the timer duration, the field SignalId defines the
code of the message (signal) that is generated when the timer expires, the
field Valid is set if the timer is running, and the field TimerBuffer contains the
pointer to the buffer used by the timer expiration message.
The main private field members of the class FiniteStateMachine are the
following:
copy the content of this field into the object identification field of the message
header.
The field CallId carries another domain-specific name but it can be used
for various purposes in various applications. In contrast to the field Connec-
tionId whose uniqueness is limited to the scope of a single FSM type, the
value of the field CallId is unique in the scope of the whole system. Tradi-
tionally, it has been used to identify a single call, but generally it can be used
to identify any communication process of interest. Like the field ConnectionId,
this field is also copied by the message sending functions to the message
header automatically.
Finally, the field State is the FSM current state identification, which is the
value of the index of array defined in the field States. This field defines the
context of the FSM.
As already mentioned, the FSM Library supports the abstraction of the left
and right FSM. The message sending functions, namely SendLeftAutomata()
and SendRightAutomata() — originally defined in the class KernelAPI —
require the data about the left and right FSM. Relevant FiniteStateMachine
attributes are the following:
FiniteStateMachine(
uint16 numOfTimers = DEFAULT_TIMER_NO,
uint16 numOfState = DEFAULT_STATE_NO,
uint16 maxNumOfProceduresPerState = DEFAULT_PROCEDURE_NO_PER_STATE);
virtual void Initialize(void) = 0;
void InitEventProc(uint8 state, uint16 event, PROC_FUN_PTR fun);
void InitUnexpectedEventProc(uint8 state, PROC_FUN_PTR fun);
PROC_FUN_PTR GetProcedure(uint16 event);
virtual void NoFreeInstances() = 0;
virtual void Process(uint8 *msg);
void FreeFSM();
The class constructor first sets the number of timers, the number of states,
and the maximal number of branches per state. It then calls the function
Initialize(), provided by the user. This function typically uses a series of calls
to functions InitEventProc() and InitUnexpectedEventProc(). The former
defines the state transition function for the given state and message type
Implementation 211
whereas the latter defines the unexpected message handler for the given
state.
The function GetProcedure() is a control function that returns the address
of the state transition function for the given message type in the current
state. The function NoFreeInstances() is a recovery function that is called in
cases where no more free objects of this type are found. The function Process()
is the prototype of the state transition function. The function FreeFSM()
releases the FSM object by returning it to the pool of objects of this type.
The class KernelAPI provides the following groups of functions:
• Initialization functions
• Memory management functions
• Message management functions
• Timer management functions
The initialization functions provided by the class KernelAPI are its con-
structors (see Section 6.8) and the function setKernelObjects, whose prototype
is the following:
The parameters of this function are the pointers to the objects that comprise
the system mailboxes, buffers, and timers. These objects will be described
in the next section.
The memory management functions provided by the class KernelAPI are
the following:
The function GetBuffer() returns the pointer to the buffer of the sufficient
size (not less than specified by its parameter). The function RetBuffer()
releases the given buffer. The function IsBufferSmall() checks the size of the
given buffer. The function GetBufferLength() returns the size of the given
buffer.
The message management functions provided by the class KernelAPI are
the following:
The function Discard() releases the given message. The function SetMes-
sageFromData() copies the data about this FSM (type, group, and instance
identifications) to the corresponding fields of the new message header.
According to the FSM Library terminology, the current message is the one
that has been received and processed whereas the new message is the mes-
sage that is currently under construction (and will be subsequently sent).
The function SendMessage(uint8 mbxId) sends the new message to the given
mailbox. The function SendMessage(uint8 mbxId, unit8 *msg) sends the given
message to the given mailbox. The functions SendMessageLeft() and SendMes-
sageRight() send the new message to the left and right automata, respectively.
The function ReturnMsg() sends the current message to the given mailbox.
The timer management functions provided by the class KernelAPI are the
following:
The function StartTimer() starts the given timer by setting its duration and
the corresponding message buffer. The function StopTimer() stops the given
timer. The function IsTimerRunning() checks if the given timer is running.
The interface defined by the class MessageHandler comprises the following
two parts:
The message header handling part provides getting and setting functions
for the individual message header fields. The main message header fields
are the following:
Implementation 213
Depending on the message format type, the first and the second items
listed may be implicit or explicit. Some of the messages carry the message
parameter identification and length and some do not. However, all three
items must be known to the message handling functions.
Another important fact related to the message format is that particular
message formats can be disassembled to a series of primitive elements of
the following type:
• Byte (1 byte)
• Word (2 bytes)
• DWord (4 bytes)
• Sequence of bytes (n bytes)
the requested parameter of the size Byte, Word, and DWord, respectively. The
new message handling functions are the following:
The first four functions add the given sequence of octets, Byte, Word, and
DWord parameter to the new message, respectively. The function Remove-
Param() removes the parameter — whose identification is given — from the
message.
Each message handling function consists of two parts, a preparation part
and an operation part. The preparation part of the current message handling
functions includes preparing temporary data and message parsing. In case
of message syntax errors, message handling functions report an error by
returning the value false. The preparation part of the new message handling
functions includes allocation of the message buffer and initialization of the
message header fields MSG_CODE, MSG_INFO_CODING and
MSG_LENGTH (initially set to 0).
• Memory manager
• Message manager
• Time manager
The interfaces to these three resource managers are defined by the classes
TBuffers, TPostOffice, and CTimer, respectively. The memory manager comprises
the class TBuffers and a set of instances of the class TBufferQueue. The message
manager consists of the class TPostOffice and a set of instances of the class
TMailBox. The time manager is implemented by the class CTimer itself.
The class TBuffers creates the abstraction of a set of buffer pools. The size
of the buffers in the pool is the same, but these sizes are different between
the pools. For example, we can have three pools with three different sizes,
Implementation 215
kernel::KernelAPI
-Buffers
-PostOffice
-Timer
-TimerResolution
1 1 1
1 1 1
1
1
∗ ∗
kernel::TBufferQueue kernel::TMailBox
#BufferPtr #Buff
1
#BuffersInitiated #Count
#CsBuffer #CsMailBox
#FreeBufferCount #Head
#Head #Tail
#Tail
FIGURE 4.14
The internal Kernel static structure.
namely, small, medium, and large. The class TBufferQueue models one such
a pool.
The constructor of the class TBufferQueue initially allocates an array of
bytes (uint8), which is the actual memory space that accommodates the
memory pool:
// calculate memory size for all buffers and get memory for them
memSize = bufferLength + BUFF_HEADER_LENGTH;
memSize *= buffersNo;
BufferPtr = new uint8[memSize];
instances of the class TBufferQueue (in the field member Buffers), as well as the
array of the corresponding buffer sizes (in the field member BuffersLength).
The function GetBuffer() provided by the class KernelAPI first searches the
field BuffersLength to find the pool of buffers of the sufficient size. It then
gets the buffer from the head of the list of free buffers and returns the pointer
to it. The function RetBuffer() uses buffer code from its header to return the
buffer by adding it to the end of the corresponding list.
The class TPostOffice stores the array of pointers to the corresponding
mailboxes. A mailbox is implemented as an instance of the class TMailBox.
Actually, the class TMailBox is very similar to the class TBufferQueue. The
main difference between them is that the former provides atomic (uninter-
ruptible) access to the list of messages. This feature is needed because the
list of messages is a resource shared by two concurrent processes, namely
the event interpreter and the time interrupt routine.
The atomic mailbox access is ensured by two virtual functions, namely
MbxLock() and MbxUnlock(). The former function locks the mailbox and the
latter unlocks it. These functions ensure the FSM Library’s portability. They
can be implemented by the use of semaphores provided by the local oper-
ating systems. (The FSM Library supports OS Linux® and Windows® NT
at the moment.)
The class CTimer is the most target-platform dependent part of Kernel. It
consists of two parts, a platform-dependent part and a platform-independent
part. The platform-dependent part comprises the time-driven routine that is
periodically called by the local operating system and the routines that pro-
vide access to shared data. The platform-independent part consists of the
list of running timers and routines that maintain that list. The list of running
timers is implemented as a traditional delta list (the timer at the head of the
list contains the absolute time interval whereas all other timers contain the
time interval relative to the previous timer in the list).
To simplify timer maintenance, the function StopTimer() does not analyze
the current status of the given timer (already expired or still running) — it
simply marks the timer as expired. If the timer was still running, it will
remain in the list of running timers. When it expires, it is forwarded to the
given mailbox and from there it is discarded by the function member Get()
of the class TMailBox.
Implementation 217
We then write the main program, which typically follows these steps:
4.5 Examples
This section includes two representative examples of the FSM Library-
based implementations. The first example is the implementation of the
application for reading Internet electronic mail. The second example
shows the implementation of the SIP invite client transaction.
4.5.1 Example 1
This example demonstrates how an application for reading Internet elec-
tronic mail can be constructed. The application is actually an e-mail client
that comprises the following three objects (see the general collaboration
diagram in Figure 4.15):
FIGURE 4.15
The receive e-mail application collaboration diagram.
As shown in Figure 4.15, the objects user, pop3, and channel are the instances
of the classes UserAuto, ClAuto, and ChAuto, respectively. The object pop3 is
the central object. On its left side is the object user and on its right side is
the object channel. The interaction between these objects is illustrated with
three typical scenarios that are shown in Figure 4.16, Figure 4.17, and Figure
4.18. Figure 4.16 shows a successful session during which all pending e-mails
are received and saved as files on a mass storage device. The flow of events
from the point of view of the object pop3 is the following:
Implementation 219
User_Check_Mail
Cl_Connection_Request
Cl_Connection_Accept
User_Connected
User_Name_Password
MSG(USER name)
MSG(+OK)
MSG(PASS password)
MSG(+OK)
MSG(STAT)
MSG(+OK nn mm)
MSG(RETR nn)
MSG(mail)
Mail(mail)
MSG(mail)
Mail(mail)
MSG(mail)
Mail(mail)
MSG(DELE nn)
MSG(+OK)
User_Save_Mail
Repeat e-mail read
and delete procedures
at this point.
MSG(QUIT)
Cl_Disconnected
User_Disconnected
FIGURE 4.16
The successful receive e-mail session establishment scenario.
the acknowledgment MSG(+OK) from the right object, the left object
is informed accordingly with the message User_Save_Mail (normally,
the object user should save the current e-mail message as a file on
a mass storage device at this point).
User_Check_Mail
Cl_Connection_Request
Cl_Connection_Accept
User_Connected
User_Name_Password
MSG(USER name)
MSG(+OK)
MSG(PASS password)
MSG(-ERR)
MSG(QUIT)
Cl_Disconnected
User_Disconnected
FIGURE 4.17
The invalid e-mail password processing scenario.
User_Check_Mail
Cl_Connection_Request
TIMER1_EXPIRED
Cl_Connection_Reject
User_Connection_Fail
FIGURE 4.18
The unsuccessful receive e-mail session establishment scenario.
• Finally, the object pop3 starts the session closing procedure by send-
ing the message MSG(QUIT) to the right object. Then, upon recep-
tion of the message Cl_Disconnected from the right object, it sends
the message User_Disconnected to the left object.
Figure 4.17 shows the invalid password processing scenario. It is the same
as the previous scenario up to the point where the object pop3 sends the
message MSG(PASS password) to the right object. Because the password is
Implementation 221
invalid, the right object responds with the message MSG(ERR) and the object
pop3 immediately proceeds to the session closing procedure.
Figure 4.18 shows the unsuccessful session establishment scenario. It starts
in the same way as the scenario in Figure 4.16. Assume that the TCP con-
nection with the e-mail server cannot be established for some reason. There-
fore, TIMER1_ID that was started by the right object expires and the associate
message TIMER1_EXPIRED triggers the right object to send the message
Cl_Connection_Reject. The object pop3 in its turn sends the message
User_Connection_Fail to the left object.
To keep this example simple enough, we focus further on the design and
implementation of the key object in this application, the object pop3. The
complete dynamic behavior of this object is specified with the SDL diagram,
which is shown in Figure 4.19 and Figure 4.20. The corresponding FSM is
defined with nine states (Cl_Ready, Cl_Connecting, Cl_Authorizing,
Cl_User_Check, Cl_Pass_Check, Cl_Mail_Check, Cl_Receiving, Cl_Deleting, and
Cl_Disconnecting), six input messages (User_Check_Mail,
Cl_Connection_Reject, Cl_Connection_Accept, User_Name_Password, MSG, and l
Cl_Disconnected), and seven output messages (Cl_Connection_Request,
User_Connection_Fail, User_Connected, MSG, Mail, User_Save_Mail, and
User_Disconnected).
By convention, the names of all messages (except Mail) exchanged between
the object pop3 and the left object begin with the prefix User_. The names of
the control messages exchanged between the object pop3 and the right object
begin with the prefix Cl_. The names of the POP3-related messages
exchanged between the object pop3 and the right object are named MSG.
Two types of MSG messages are used — commands directed to the e-mail
server and responses received from it.
The MSG commands are the following:
Cl_Ready Cl_Connecting
User_Check-
Cl_Connecti- Cl_Connecti-
_Mail
on_Reject on_Accept
Cl_Connection
User_Conne- User_Conne-
_Request
ction_Fail cted
Cl_Connecting
Cl_Ready Cl_Authorizing
Cl_Authorizing Cl_User_Check
User_Name-
_Password MSG(+OK) MSG(-ERR)
MSG(USER
name) MSG(PASS
MSG(QUIT)
password)
Cl_User_Check
Cl_Pass_Check Cl_Disconnecting
Cl_Pass_Check
MSG(+OK) MSG(-ERR)
MSG(STAT) MSG(QUIT)
Cl_Mail_Check Cl_Disconnecting
FIGURE 4.19
The POP3 client SDL diagram, part I.
Implementation 223
Cl_Mail_Check
Cl_Receiving
MSG(+OK nn
mm) MSG(mail)
Yes nn > 0 No
Mail(mail)
MSG(RETR
MSG(QUIT)
nn)
size<255 Yes
Cl_Receiving Cl_Disconnecting
MSG(DELE
No nn)
Cl_Deleting
- Cl_Deleting
MSG(+OK)
User_Save- Cl_Disconnecting
_Mail
Cl_Disconne-
cted
nn = nn-1
User_Disco-
nnected
Yes nn > 0 No
MSG(RETR Cl_Ready
MSG(QUIT)
nn)
Cl_Receiving Cl_Disconnecting
FIGURE 4.20
The POP3 client SDL diagram, part II.
Figure 4.19 shows valid state transitions for the states Cl_Ready,
Cl_Connecting, Cl_Authorizing, Cl_User_Check, and Cl_Pass_Check. The eight
state transitions are shown in Figure 4.19, as follows:
Figure 4.20 shows valid state transitions for the states Cl_Mail_Check,
Cl_Receiving, Cl_Deleting, and Cl_Disconnecting. The seven state transitions
are shown in Figure 4.20, as follows:
#ifndef _CONST_H_
#define _CONST_H_
#include <fsm.h>
const uint8 CH_AUTOMATA_TYPE_ID = 0x00;
const uint8 CL_AUTOMATA_TYPE_ID = 0x01;
const uint8 USER_AUTOMATA_TYPE_ID = 0x02;
// channel messages
const uint16 MSG_Connection_Request = 0x0001;
const uint16 MSG_Sock_Connection_Reject = 0x0002;
Implementation 225
// user messages
const uint16 MSG_Set_All = 0x0010;
const uint16 MSG_User_Connected = 0x0011;
const uint16 MSG_User_Connection_Fail = 0x0012;
const uint16 MSG_Mail = 0x0013;
const uint16 MSG_User_Save_Mail = 0x0015;
const uint16 MSG_User_Disconnected = 0x0014;
#define TIMER1_ID 1
#define TIMER1_COUNT 10
#define TIMER1_EXPIRED 0x20
The file const.h starts with the definitions of automata types and their
private mailbox identifications. The identifications assigned to the classes
ChAuto, ClAuto, and UserAuto are CH_AUTOMATA_TYPE_ID,
CL_AUTOMATA_TYPE_ID, and USER_AUTOMATA_TYPE_ID, respec-
t i v e l y. T h e i d e n t i fi c a t i o n s o f t h e i r p r i v a t e m a i l b o x e s a r e
C H _ A U T O M ATA _ M B X _ I D , C L _ A U T O M ATA _ M B X _ I D , a n d
USER_AUTOMATA_MBX_ID, respectively. Next, we define the symbols that
correspond to the codes of the messages recognized by the classes ChAuto,
ClAuto, and UserAuto, respectively. By convention, these symbols are pro-
vided by prefixing the names of the messages from the SDL diagram (Figure
4.19 and Figure 4.20) with the prefix MSG_.
At the end of the file const.h, we define the domain name and the number
of the port, which are used to establish the TCP connection with the e-mail
server (symbols ADDRESS and PORT), channel timer-related constants
(symbols TIMER1_ID, TIMER1_COUNT, and TIMER1_EXPIRED), and the
identifications of the message parameters (symbols PARAM_DATA,
PARAM_Name, and PARAM_Pass).
Next, we write the header file ClAuto.h. Its content is the following:
#ifndef _Cl_AUTO_H_
#define _Cl_AUTO_H_
#include <NetFSM.h>
#include <fsmsystem.h>
#include “const.h”
class ClAuto : public FiniteStateMachine {
// for FSM
StandardMessage StandardMsgCoding;
MessageInterface *GetMessageInterface(uint32 id);
void SetDefaultHeader(uint8 infoCoding);
void SetDefaultFSMData();
void NoFreeInstances();
void Reset();
uint8 GetMbxId();
uint8 GetAutomata();
uint32 GetObject();
void ResetData();
// FSM States
enum ClStates {
FSM_Cl_Ready,
FSM_Cl_Connecting,
FSM_Cl_Authorizing,
FSM_Cl_User_Check,
FSM_Cl_Pass_Check,
FSM_Cl_Mail_Check,
FSM_Cl_Receiving,
FSM_Cl_Deleting,
FSM_Cl_Disconnecting
};
public:
ClAuto();
~ClAuto();
void Initialize();
void FSM_Cl_Ready_User_Check_Mail();
void FSM_Cl_Connecting_Cl_Connection_Reject();
void FSM_Cl_Connecting_Cl_Connection_Accept();
void FSM_Cl_Authorizing_User_Name_Password();
void FSM_Cl_User_Check_MSG();
void FSM_Cl_Pass_Check_MSG();
void FSM_Cl_Mail_Check_MSG();
void FSM_Cl_Receiving_MSG();
void FSM_Cl_Deleting_MSG();
void FSM_Cl_Disconnecting_Cl_Disconnected();
protected:
int m_MessageCount;
char m_UserName[20];
char m_Password[20];
};
#endif /* _Cl_AUTO_H */
After listing all necessary header files, we declare the class ClAuto, which
is derived from the base class FiniteStateMachine. The declaration of the class
Implementation 227
ClAuto starts with the declaration of field and function members that are
mandatory for any class that is derived from the class FiniteStateMachine (as
explained previously in this chapter). It continues with the declaration of
the FSM state names and state transition function prototypes.
By convention, FSM state names are the names from the SDL diagram
prefixed with the prefix FSM_ (e.g., the initial state Cl_Ready is named
FSM_Cl_Ready in the C++ code). The state transition function is named by
concatenating the state name and the input message name and by prefixing
this composite name with the prefix FSM_ (e.g., the state transition function
performed when the FSM in state Cl_Ready receives the message
User_Check_Mail is named FSM_Cl_Ready_User_Check_Mail). As already
mentioned, ClAuto FSM has nine states and fourteen state transitions.
The reader may be puzzled with the fact that there are fourteen valid FSM
state transitions and only ten state transition functions declared in the header
file ClAuto.h. This circumstance is because some of the state transitions are
triggered with the same message type but different message content — e.g.,
MSG(+OK) and MSG(ERR) — or they are guarded with the complementary
conditions — e.g., (nn > 0) and !(nn > 0). To clearly understand these matters,
remember that FiniteStateMachine derivatives react to various message types in
various FSM states. This is how we calculate the number of state transitions.
If we apply the principle stated above to the class ClAuto, we have the
situation where all the states react to a single message with the exception of
the state Cl_Connecting, which reacts to two valid messages,
Cl_Connection_Reject and Cl_Connection_Accept. Because of this, we have
(8 × 1) + (1 × 2) state transition functions, which resolves to ten state transition
functions, as mentioned above.
Finally, we write the class ClAuto definition file, named ClAuto.cpp. The
content of this file is the following:
#include <stdio.h>
#include “const.h”
#include “ClAuto.h”
#define StandardMessageCoding 0x00
ClAuto::ClAuto() : FiniteStateMachine(0, 9, 2) {}
ClAuto::~ClAuto() {}
uint8 ClAuto::GetAutomate() {
return CL_AUTOMATA_TYPE_ID;
}
uint8 ClAuto::GetMbxId() {
return CL_AUTOMATA_MBX_ID;
}
uint32 ClAuto::GetObject() {
return GetObjectId();
}
void ClAuto::SetDefaultFSMData() {
SetDefaultHeader(StandardMessageCoding);
}
void ClAuto::NoFreeInstances() {
printf(“[%d] ClAuto::NoFreeInstances()\n”, GetObjectId());
}
void ClAuto::Reset() {
printf(“[%d] ClAuto::Reset()\n”, GetObjectId());
}
void ClAuto::Initialize() {
SetState(FSM_Cl_Ready);
InitEventProc(FSM_Cl_Connecting, MSG_Cl_Connection_Reject,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Connecting_Cl_Connection_Reject));
InitEventProc(FSM_Cl_Connecting, MSG_Cl_Connection_Accept,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Connecting_Cl_Connection_Accept));
InitEventProc(FSM_Cl_Authorizing, MSG_User_Name_Password,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Authorizing_User_Name_Password));
InitEventProc(FSM_Cl_User_Check, MSG_MSG,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_User_Check_MSG));
InitEventProc(FSM_Cl_Pass_Check, MSG_MSG,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Pass_Check_MSG));
InitEventProc(FSM_Cl_Mail_Check, MSG_MSG,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Mail_Check_MSG));
InitEventProc(FSM_Cl_Receiving, MSG_MSG,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Receiving_MSG));
InitEventProc(FSM_Cl_Deleting, MSG_MSG,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Deleting_MSG));
Implementation 229
InitEventProc(FSM_Cl_Disconnecting, MSG_Cl_Disconnected,
(PROC_FUN_PTR)&ClAuto::FSM_Cl_Disconnecting_Cl_Disconnected));
}
void ClAuto::FSM_Cl_Ready_User_Check_Mail(){
PrepareNewMessage(0x00, MSG_Connection_Request);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Connecting);
}
void ClAuto::FSM_Cl_Connecting_Cl_Connection_Reject(){
PrepareNewMessage(0x00, MSG_User_Connection_Fail);
SetMsgToAutomata(USER_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
SendMessage(USER_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Ready);
}
void ClAuto::FSM_Cl_Connecting_Cl_Connection_Accept(){
PrepareNewMessage(0x00, MSG_User_Connected);
SetMsgToAutomata(USER_AUTOMATE_TYPA_ID);
SetMsgObjectNumberTo(0);
SendMessage(USER_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Authorizing);
}
void ClAuto::FSM_Cl_Authorizing_User_Name_Password(){
char* name = new char[20];
char* pass = new char[20];
uint8* buffer = GetParam(PARAM_Name);
memcpy(m_UserName,buffer+2,buffer[1]);
m_UserName[buffer[1]] = 0;// terminate string
buffer = GetParam(PARAM_Pass);
memcpy(m_Password,buffer+2,buffer[1]);
m_Password[buffer[1]] = 0;// terminate string
char l_Command[20] = “user”;
strcpy(l_Command+5,m_UserName);
strcpy(l_Command+5+strlen(m_UserName),“\r\n”);
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,strlen(l_Command),(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_User_Check);
}
void ClAuto::FSM_Cl_User_Check_MSG(){
char* data = new char[255];
uint8* buffer = GetParam(PARAM_DATA);
uint16 size = buffer[1];
memcpy(data,buffer + 2,size);
data[size]=0;
printf(“%s”,data);
if((data[0] == '+')) {
char l_Command[20] = “pass ”;
strcpy(l_Command+5,m_Password);
strcpy(l_Command+5+strlen(m_Password),“\r\n”);
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,strlen(l_Command),(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Pass_Check);
else {
char l_Command[20] = “quit\r\n”;
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,6,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Disconnecting);
}
}
void ClAuto::FSM_Cl_Pass_Check_MSG(){
char* data = new char[255];
uint8* buffer = GetParam(PARAM_DATA);
uint16 size = buffer[1];
memcpy(data,buffer + 2,size);
data[size]=0;
printf(“%s”,data);
if((data[0] == '+')) {
char l_Command[20] = “stat\r\n”;
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,6,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Mail_Check);
else {
char l_Command[20] = “quit\r\n”;
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,6,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Disconnecting);
}
}
void ClAuto::FSM_Cl_Mail_Check_MSG(){
char* data = new char[255];
uint8* buffer = GetParam(PARAM_DATA);
uint16 size = buffer[1];
Implementation 231
memcpy(data,buffer+2,size);
data[size]=0;
printf(“%s”,data);
int l_nDigit = 1;
while(buffer[l_nDigit+6] != ' ') l_nDigit++;
memcpy(data,buffer +6,l_nDigit);
data[l_nDigit]=0;
m_MessageCount = atoi(data);
if((m_MessageCount == 0) {
char l_Command[20] = “quit\r\n”;
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,6,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Disconnecting);
else {
char l_Command[20] = “retr ”;
strcpy(l_Command+5,data);
strcpy(l_Command+5+l_nDigit,“\r\n”);
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,5+l_nDigit+2,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Receiving);
}
}
void ClAuto::FSM_Cl_Receiving_MSG(){
char* data = new char[255];
uint8* buffer = GetParam(PARAM_DATA);
uint16 size = buffer[1];
memcpy(data,buffer + 2,size);
char temp[4];
memcpy(temp,data,3); temp[3] = 0;
if((strcmp(temp,“+OK“) != 0) {
PrepareNewMessage(0x00, MSG_Mail);
SetMsgToAutomata(USER_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,size,(uint8*)data);
SendMessage(USER_AUTOMATA_MBX_ID);
if((size < 255) {
char l_Command[20] = “dele ”;
itoa(m_MessageCount,data,10);
strcpy(l_Command+5,data);
strcpy(l_Command+5+strlen(data),“\r\n”);
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,5+strlen(data)+2,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Deleting);
}
}
}
void ClAuto::FSM_Cl_Deleting_MSG(){
PrepareNewMessage(0x00, MSG_User_Save_Mail);
SetMsgToAutomata(USER_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
SendMessage(USER_AUTOMATA_MBX_ID);
m_MessageCount——;
if(m_MessageCount > 0) {
char data[5];
char l_Command[20] = “retr ”;
itoa(m_MessageCount,data,10);
strcpy(l_Command+5,data);
strcpy(l_Command+5+strlen(data),“\r\n”);
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,5+strlen(data)+2,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Receiving);
else {
char l_Command[20] = “quit\r\n”;
PrepareNewMessage(0x00, MSG_Cl_MSG);
SetMsgToAutomata(CH_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
AddParam(PARAM_DATA,6,(uint8*)l_Command);
SendMessage(CH_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Disconnecting);
}
}
void ClAuto::FSM_Cl_Disconnecting_Cl_Disconnected(){
PrepareNewMessage(0x00, MSG_User_Disconnected);
SetMsgToAutomata(USER_AUTOMATA_TYPE_ID);
SetMsgObjectNumberTo(0);
SendMessage(USER_AUTOMATA_MBX_ID);
SetState(FSM_Cl_Ready);
}
The file ClAuto.cpp starts with the list of all necessary header files (stdio.h,
const.h, and ClAuto.h), followed by the definition of the symbolic constant
StandardMessageCoding and the set of mandatory function definitions — class
constructor, class destructor, and functions GetAutomata(), GetMbxId(), GetO-
bject(), GetMessageInterface(), SetDefaultHeader(), SetDefaultFSMData(), NoFree-
Instances(), Reset(), and Initialize().
The class constructor ClAuto() calls the constructor of the class FiniteState-
Machine with a list of parameters, which specifies that ClAuto FSM has no
timers, nine states, and the maximum of two state transitions per state (see
Implementation 233
the FSM Library API specification in Section 6.8, particularly, Section 6.8.11).
The class destructor performs no particular operation.
The mandatory functions provide the following functionalities:
Implementation 235
4.5.2 Example 2
The aim of this example is to implement the SIP INVITE client transaction
design, which is given in Section 3.10.5 (Chapter 3, Example 5). Briefly, in
that section we examined the general collaboration diagram of the SIP Soft-
phone (see Section 2.3.3, Figure 2.16) with the focus on the INVITE client
transaction. The result is the general collaboration diagram shown in Figure
3.69. We then made two particular collaboration diagrams and their seman-
tically equivalent sequence diagrams for the cases of successful and unsuc-
cessful SIP session establishment (Figure 3.70, Figure 3.71, Figure 3.72, and
Figure 3.73). Finally, we devised the complete dynamic behavior specification
in the form of the statechart diagram (Figure 3.74) and semantically equiv-
alent SDL diagram (Figure 3.75, Figure 3.76, Figure 3.77, and Figure 3.78).
We start the implementation of this design by defining the symbolic con-
stants, such as the FSM type names (e.g., the name of the INVITE client FSM
type is InviteClienteTE_FSM), mailbox names (e.g., the name of the INVITE
client mailbox is InviteClienteTE_FSM_MBX), names of the FSM
Library related message types, timer names (e.g., TIMER_A, TIMER_B,
TIMER_D), names of the SIP messages (e.g., INVITE, OPTIONS, CANCEL,
ACK, BYE, RESISTER), names of the response codes (e.g., _180_RINGING,
_200_OK, _302_MOVED_TEMPORARILY, _401_UNAUTHORIZED,
_403_FORBIDDEN, _404_NOT_FOUND), and names of situations (e.g
URI_IN_TO_UNRECOGNIZED and NOT_TO_CURRENT_USER). Tradition-
ally, we write definitions of all these constants into the file constants.h.
Next, we write the class that represents an SIP message, simply named
Message. The most important field member of this class is the last (also
referred to as the current) SIP message (its type is the C++ type string). Other
field members hold the relevant SIP session related information. The function
members support SIP message analysis and synthesis (parsing and creation).
Actually, the class Message that is used in this example is a simple wrapper
around the OpenSIP SIP message parser. (OpenSIP is freely available on the
Internet at http://sourceforge.net/projects/opensip/.)
We skip the content of the file constants.h and the source code of the class
Message intentionally to keep this example short enough and easily compre-
hendible, and we proceed with the introduction of the supplementary class
TALE. The declaration of the class TALE is the following:
#ifndef _TALE_FSM_
#define _TALE_FSM_
#include “../kernel/fsm.h”
#include “../message/message.h”
#include “../constants.h”
public:
TALE(uint16 numOfTimers, uint16 numOfState, uint16 maxNumOfPrPerSt);
~TALE();
};
The class TALE is a good example of how we can make our implementa-
tions more compact. As we can see from the previous example, sending a
single message requires a series of FSM Library function calls. For example,
forwarding the current message would require a series of calls to the function
CopyMessage(), SetMsgToAutomata(), SetMsgToGroup(), SetMsgObjectNum-
berTo(), and function SendMessage() — five function calls. In the case of simple
designs, we can tolerate repetition of this series of function calls, but in cases
of more complex design or platforms with limited resources, this repetition
may not be tolerated.
Consider the SIP invite client transaction FSM. It has thirteen state transi-
tions, and most of them require sending a message to either TPL (transport
layer) or TU (transaction user). We would need to repeat the same series of
function calls about ten times. Consider now the whole SIP Softphone, which
supports four types of transactions (invite and non invite, client and server
transactions). In such situations, replacing this series of function calls with
a single function call, which in its turn performs the original sequence of
function calls, makes sense.
Implementation 237
This replacement is exactly the reason why the class TALE has been intro-
duced in the first place. This class inherits all field and function members
from the class FiniteStateMachine from which it is derived. It also adds some
new field and function class members. All classes that implement SIP trans-
actions are derived from the class TALE. The most important field member
of the class TALE is the field MessageCopy, which holds the copy of the last
sent message. Actually, this field is the retransmission buffer (remember that
SIP invite client in the state Calling must retransmit the message INVITE in
case the timer A expires).
The two most important function members are the functions SendMessage-
ToTU() and SendMessageToTPL(). The former sends the current message to
TU and the latter to TPL. They are very similar; therefore, it is sufficient to
study just one of them. Here is the source code of the former function:
void TALE::SendMessageToTU() {
CopyMessage();
SetMsgToAutomata(UA_Disp_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
SendMessage(UA_Disp_FSM_MBX);
}
This is the most elegant way to forward a message in the FSM Library-
based implementations. The function CopyMessage() copies the current (last
received) message to the new (output) message. The symbolic constant
UA_Disp_FSM is the name of the UA (user agent) FSM type, and the constant
UA_Disp_FSM_MBX is the name of its mailbox. As we will shortly see, the
use of the functions SendMessageToTU() and SendMessageToTPL() signifi-
cantly compresses the source code. They make one-to-one mapping of SDL
diagrams to C++ code possible.
Next, we proceed to the implementation of the INVITE client transaction
FSM. We implement it by writing the class InviteClientTE. Note that in Figure
3.69 to Figure 3.73, we used the abbreviation InClientT for this name. The
declaration of the class InviteClientTE is the following:
#ifndef _InviteClientTE_FSM_
#define _InviteClientTE_FSM_
#include “TALE.h”
public:
enum States {
STATE_INITIAL,
STATE_CALLING,
STATE_PROCEEDING,
STATE_COMPLETED
};
// state Initial message handlers
void Evt_Init_INVITE();
// state Calling message handlers
void Evt_Calng_TIMER_A_EXP();
void Evt_Calng_RESPONSE_1XX();
void Evt_Calng_RESPONSE_2XX();
void Evt_Calng_TIMER_B_EXP();
void Evt_Calng_RESPONSE_3_6XX();
void Evt_Calng_TRANSPORT_ERR();
// state Proceeding message handlers
void Evt_Proc_RESPONSE_1XX();
void Evt_Proc_RESPONSE_2XX();
void Evt_Proc_RESPONSE_3_6XX();
// state Completed message handlers
void Evt_Comptd_TIMER_D_EXP();
void Evt_Comptd_RESPONSE_3_6XX();
void Evt_Comptd_TRANSPORT_ERR();
// unexpected messages message handler
void Event_UNEXPECTED();
// problem specific functions
void RetransmitInvite();
BOOL SendAckMessageToTPL();
// FiniteStateMachine abstract functions
StandardMessage StandardMsgCoding;
MessageInterface *GetMessageInterface(uint32 id);
void SetDefaultHeader(uint8 infoCoding);
void SetDefaultFSMData();
void NoFreeInstances();
void Reset();
uint8 GetMbxId();
uint8 GetAutomate();
uint32 GetObject();
void ResetData();
public:
The class InviteClientTE is derived from the class TALE. The meaning of
its field members is the following:
• The field SIPMsg is the SIP message parser (an instance of the class
Message).
• The field cseq_number holds the value of the SIP message header
field CSeq, which is used to identify and order transactions (see RFC
3261, Subsection 8.1.1.5).
• The field TimerADuration contains the current value of the timer A
(remember, the value of the timer A is doubled each time it expires).
Next, we enumerate the names of the FSM states. There are altogether four
FSM states, STATE_INITIAL, STATE_CALLING, STATE_PROCEEDING, and
STATE_COMPLETED. A short explanation is needed at this point. According
to the original specification (RFC 3261, Figure 5, page 128), the INVITE client
Implementation 239
transaction FSM also has four explicitly rendered states, namely, Calling,
Proceeding, Completed, and Terminated. The initial state is omitted in the orig-
inal specification. In our implementation, we create a pool of InviteClientTE
objects, which are dynamically allocated on demand by the TU. These objects
are never really terminated. Once they play their simple role, they are
returned to the pool of free InviteClientTE objects, and from there they are
dynamically assigned to play the same role again. Therefore, we renamed
the state Terminated to Initial. We also made this state the source of the initial
state transition (triggered with the INVITE message from TU), thus making
the FSM a never-terminating one.
We then list the state transition function prototypes for each state individ-
ually. The naming convention is the same as in the previous example: The
name of the state transition function is constructed by concatenating the state
name and the message name and by prefixing that name with a certain prefix.
The naming convention is applied more freely in this example by shortening
the state names. This practice is frequently done to keep the name lengths
acceptable (short enough but providing code readability at the same time).
Thirteen valid state transitions and their corresponding state transition func-
tions (message handlers) are used. The fourteenth message handler, named
Event_UNEXPECTED(), handles all unexpected messages in all states.
Finally, we list the function prototypes of the problem-specific functions
and mandatory FiniteStateMachine abstract functions. These functions —
except the function RetransmitInvite() — are intentionally skipped in the text
that follows to keep the presentation of this example short.
We finish the implementation by writing the class InviteClientTE definition
file, named InvClientTE.cpp. The content of this file is the following:
#include <stdio.h>
#include “InvClientTE.h”
#include “../Message/message.h”
#include “timer_values.h”
#define StandardMessageCoding 0x00
void InviteClientTE::Initialize() {
SetState(STATE_INITIAL);
// define timers
InitTimerBlock(TIMER_A,1,TIMER_A_EXPIRED);
InitTimerBlock(TIMER_B,1,TIMER_B_EXPIRED);
InitTimerBlock(TIMER_D,1,TIMER_D_EXPIRED);
// state STATE_INITIAL message handlers
InitEventProc(STATE_INITIAL, INVITE,
(PROC_FUN_PTR)&InviteClientTE::Evt_Init_INVITE);
// state STATE_CALLING message handlers
InitEventProc(STATE_CALLING, TIMER_A_EXPIRED,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_TIMER_A_EXP);
InitEventProc(STATE_CALLING, RESPONSE_1XX_T,
(PRO_FUN_PTR)&InviteClientTE::Evt_Calng_RESPONSE_1XX);
InitEventProc(STATE_CALLING, RESPONSE_2XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_RESPONSE_2XX);
InitEventProc(STATE_CALLING, TIMER_B_EXPIRED,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_TIMER_B_EXP);
InitEventProc(STATE_CALLING, RESPONSE_3XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_RESPONSE_3_6XX);
InitEventProc(STATE_CALLING, RESPONSE_4XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_RESPONSE_3_6XX);
InitEventProc(STATE_CALLING, RESPONSE_5XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_RESPONSE_3_6XX);
InitEventProc(STATE_CALLING, RESPONSE_6XX_T,
(PROC_FUN_PTR)&InviteClientTE::Ev_Calng_RESPONSE_3_6XX);
InitEventProc(STATE_CALLING, TRANSPORT_ERR,
(PROC_FUN_PTR)&InviteClientTE::Evt_Calng_TRANSPORT_ERR);
InitEventProc(STATE_PROCEEDING, RESPONSE_2XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Proc_RESPONSE_2XX);
InitEventProc(STATE_PROCEEDING, RESPONSE_3XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Proc_RESPONSE_3_6XX);
InitEventProc(STATE_PROCEEDING, RESPONSE_4XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Proc_RESPONSE_3_6XX);
InitEventProc(STATE_PROCEEDING, RESPONSE_5XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Proc_RESPONSE_3_6XX);
InitEventProc(STATE_PROCEEDING, RESPONSE_6XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Proc_RESPONSE_3_6XX);
InitEventProc(STATE_COMPLETED, RESPONSE_3XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Comptd_RESPONSE_3_6XX);
InitEventProc(STATE_COMPLETED, RESPONSE_4XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Comptd_RESPONSE_3_6XX);
InitEventProc(STATE_COMPLETED, RESPONSE_5XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Comptd_RESPONSE_3_6XX);
Implementation 241
InitEventProc(STATE_COMPLETED, RESPONSE_6XX_T,
(PROC_FUN_PTR)&InviteClientTE::Evt_Comptd_RESPONSE_3_6XX);
InitEventProc(STATE_COMPLETED, TRANSPORT_ERR,
(PROC_FUN_PTR)&InviteClientTE::Evt_Comptd_TRANSPORT_ERR);
InitUnexpectedEventProc(STATE_CALLING,
(PROC_FUN_PTR)&InviteClientTE::Event_UNEXPECTED);
InitUnexpectedEventProc(STATE_PROCEEDING,
(PROC_FUN_PTR)&InviteClientTE::Event_UNEXPECTED);
InitUnexpectedEventProc(STATE_COMPLETED,
(PROC_FUN_PTR)&InviteClientTE::Event_UNEXPECTED);
}
void InviteClientTE::Evt_Init_INVITE() {
SendMessageToTPL();
if (!IsTransportReliable()){
TimerADuration = GetT1();
setTimerCount(TIMER_A, TimerADuration);
StartTimer(TIMER_A);
}
setTimerCount(TIMER_B, 64*GetT1());
StartTimer(TIMER_B);
MakeLocalCopyOfMsg();
SetState(STATE_CALLING);
}
void InviteClientTE::Evt_Calng_TIMER_A_EXP(){
TimerADuration = 2 * TimerADuration;
setTimerCount(TIMER_A, TimerADuration);
RestartTimer(TIMER_A);
RetransmitInvite();
}
void InviteClientTE::Evt_Calng_RESPONSE_1XX(){
uint16 val;
StopTimer(TIMER_A);
StopTimer(TIMER_B);
SendMessageToTU();
GetParamWord(INDEX_TLI_PARAM, val);
SetIndexTLI(val);
SetState(STATE_PROCEEDING);
}
void InviteClientTE::Evt_Calng_RESPONSE_2XX(){
StopTimer(TIMER_A);
StopTimer(TIMER_B);
SendMessageToTU();
SetState(STATE_INITIAL);
void InviteClientTE::Evt_Calng_TIMER_B_EXP(){
StopTimer(TIMER_A);
SendErrorMessageToTU();
SetState(STATE_INITIAL);
}
void InviteClientTE::Evt_Calng_TRANSPORT_ERR(){
StopTimer(TIMER_A);
StopTimer(TIMER_B);
SendErrorMessageToTU();
SetState(STATE_INITIAL);
}
void InviteClientTE::Evt_Calng_RESPONSE_3_6XX(){
uint16 val;
StopTimer(TIMER_A);
StopTimer(TIMER_B);
SendMessageToTU();
GetParamWord(INDEX_TLI_PARAM, val);
SetIndexTLI(val);
SendAckMessageToTPL();
if (IsTransportReliable())
setTimerCount(TIMER_D, ZERO_TIMER_VAL_APPROX);
else
setTimerCount(TIMER_D, 64*GetT1());//64T1
StartTimer(TIMER_D);
SetState(STATE_COMPLETED);
}
void InviteClientTE::Evt_Proc_RESPONSE_1XX(){
SendMessageToTU();
}
void InviteClientTE::Evt_Proc_RESPONSE_2XX(){
SendMessageToTU();
SetState(STATE_INITIAL);
}
void InviteClientTE::Evt_Proc_RESPONSE_3_6XX(){
SendMessageToTU();
SendAckMessageToTPL();
if (IsTransportReliable())
setTimerCount(TIMER_D, ZERO_TIMER_VAL_APPROX);
else
setTimerCount(TIMER_D, 64*GetT1());//64T1
StartTimer(TIMER_D);
SetState(STATE_COMPLETED);
}
void InviteClientTE::Evt_Comptd_TIMER_D_EXP(){
SetState(STATE_INITIAL);
}
Implementation 243
void InviteClientTE::Evt_Comptd_RESPONSE_3_6XX(){
SendAckMessageToTPL();
}
void InviteClientTE::Evt_Comptd_TRANSPORT_ERR(){
StopTimer(TIMER_D);
SendErrorMessageToTU();
SetState(STATE_INITIAL);
}
void InviteClientTE::Event_UNEXPECTED() {
}
void InviteClientTE::RetransmitInvite(){
SendCopiedMessageToTPL();
}
The mandatory function Initialize() starts by setting the FSM initial state
STATE_INITIAL. It then initializes the timers A, B, and D by calling the FSM
Library function InitTimerBlock() (its parameters are the timer identification,
the timer interval duration, and the identification of the associated message;
see also Section 6.8.74). The function Initialize() finishes by setting the FSM
state transition functions. These functions process various message types in
different states, as follows:
As we can see from the source code above, the state transition functions
(message handlers) are short and easily readable because each program
statement is easily traceable back to the original statechart and SDL dia-
grams. For example, consider the first state transition function
Evt_Init_INVITE(). The original SDL specification of this state transition
starts with the reception of the message INVITE (Figure 3.75). This step is
provided by the class FSMSystem. The next step in the SDL diagram says:
“Invite_T to TPL.” This step is implemented with a single program state-
ment, namely, the function call to the function SendMessageToTPL().
The next step in the SDL diagram is the question, “Is transport reliable?”
We implement it also with a single function call to the function IsTrans-
portReliable(). We continue the SDL coding in this manner. If the transport
is reliable, the initial value of the timer A is provided by calling the
function GetT1() — a way to parameterize the software. Next, we set the
timer A duration by calling the function setTimerCount() — this is the
undocumented FSM Library function at the moment, to be included in
the next official release — and start the timer A by calling the function
StartTimer() (the parameter of this function is the timer identification; see
also Section 6.8.138).
At the end of this function, we set the duration of the timer B and start it,
make the local copy of the last sent message by calling the function Make-
LocalCopy() — remember that it is needed for the possible retransmission —
and set the new state by calling the function SetState() (its parameter is the
state identification; see also Section 6.8.137).
Next, the state transmission function, EvtCalng_TIMER_A_EXP(), per-
forms the reaction to the timer A expiration (see the corresponding SDL
specification in Figure 3.75) with only four program statements. The first one
doubles the timer A duration, the second sets this new duration, the third
restarts the timer A by calling the FSM Library function RestartTimer() (see
Section 6.8.87), and the fourth retransmits the message INVITE by calling
the function RetransmitInvite(). Also, all other state transition functions are
Implementation 245
made in this spirit of one-to-one mapping from the original SDL diagram.
The reader is advised to study them as an additional exercise.
References
Booch, G., Rumbaugh, J., and Jacobson, I., The Unified Modeling Language User Guide ,
Addison-Wesley, Reading, MA, 1998.
Gamma, E., Helm, R., Johnson, R., and Vlissides, J., Design Patterns: Elements of
Reusable Object-Oriented Software , Addison-Wesley, Reading, MA, 1995.
5
Test and Verification
• Unit testing
• Integration testing
• Conformance testing
• Load testing
• In-field testing
• Formal verification
• Statistical usage testing
The first four types of activities (unit testing, conformance testing, load
testing, and in-field testing) are stemming from the traditional software
engineering, whereas the last two (formal verification and statistical usage
testing) are originating from the Cleanroom engineering. Today, communi-
cation protocol engineers tend to complement software engineering with the
Cleanroom engineering testing approaches, therefore we cover all the above
listed activities in this chapter.
247
As its name suggests, the unit testing is used for testing individual software
units before their integration into the product. Typically, a software unit is
a single class written in a separate Java compilation unit or C++ module.
This class most commonly implements a simple communication protocol or
a part of a more complex communication protocol. In the case of the FSM
Library based paradigm, such a unit would be a C++ module that defines
the class derived from the class FiniteStateMachine.
Unit testing of communication protocols is relatively straightforward. Typ-
ically, we construct a set of test cases that check individual FSM state tran-
sitions, as well as more complex FSM transactions (series of FSM state
transitions). We will use JUnit and CppUinit testing frameworks for unit
testing of communication protocols in this book. Details of unit testing are
given in Section 5.1 (unit testing) and Section 5.5.1 (Example 1).
The next phase is integration testing. The philosophy of integration testing
starts from the fact that some of the units have successfully undergone unit
testing and that they are available for further testing, whereas the rest of
them are not. For the purpose of integration testing, we introduce replace-
ments for the units that are not available, which are referred to as the
imitators (or simulators).
There are two kinds of imitators, namely drivers and stubs. A driver is an
active imitator that generates input messages for the real objects (units)
under test. A stub is a passive imitator that accepts the output messages
generated by the objects under test. Stubs can also send replays that are
expected from the objects they are imitating. Of course, we can construct
more complex imitators that act as both drivers and stubs. In this book, we
will call the collaborations of real objects, drivers, and stubs simply integration
test collaborations.
Generally, communication protocols are well suited for integration testing
because families of communication protocols are hierarchically organized in
layers with well-defined interfaces. The communication between individual
protocols is based on messages, which are traditionally exchanged through
the mailboxes (for example, as in implementations based on the FSM
Library). Simulating the environment of a real object under test in such a
situation is easy. Drivers and stubs simply exchange messages with objects
under test. Actually, they act on behalf of the units that will communicate
with the units under test in the final product.
Normally, protocol stacks are implemented in the bottom-up fashion, start-
ing from the lowest layer of the protocol stack and building the next layer
on top of the previous one. Drivers and stubs in such an approach simulate
only a part of the environment, the higher layer of the protocol stack in
particular. The example of the simple integration test collaboration is given
in Section 5.5.2 (Example 2).
When all software units have undergone unit and integration testing, the
final product is integrated and ready for acceptance testing, which comprises
conformance testing (also referred to as compliance testing), load testing,
and in-field testing. Preliminary acceptance testing can be organized solely
The second precondition is that test suite execution should not involve
any human intervention. This is the essential precondition to make unit
testing completely automatic. If we want to eliminate human interventions,
we must secure two conditions. First, the input data required by a test case
must be defined as symbolic constants in its source code or in other external
files. Second, the results of the test case must be automatically checked by
a test case itself. The unit testing framework must provide adequate func-
tions for this purpose.
A typical function for checking test case results is the function assert(con-
dition), where condition is a Boolean expression that evaluates to either the
value true or false. Test case continues (pass) in the former case and breaks
(fail) in the latter case. If the test case execution successfully reaches the end
of the test case, it is considered successful (qualified with the verdict pass).
Otherwise, it is considered unsuccessful (qualified with the verdict fail). If
the test case execution breaks because of some error (most typically, an
exception such as “divide by zero”), it is qualified with the verdict error.
Another typical function for checking test case results is the function
assertEquals(p1,p2). This function call is semantically equivalent to the func-
tion call assert(p1==p2). This means that if the parameters p1 and p2 are equal
(of course, they must be comparable), the test case execution continues;
otherwise, it breaks. Typically, one of the parameters is a constant and
another is a program variable.
Although these two functions are semantically equivalent, the function
assertEquals() is advantageous when it comes to test case reporting. If the
function assert() breaks the test case execution, the unit testing framework
reports only that the condition evaluated to the value false, which is not a
very informative report. Alternately, if the function assertEquals() breaks, the
framework provides the report “expected C but was V,” where C is the value
of the constant (e.g., p1) and V is the real value of the variable (e.g., p2).
We can further improve the readability of the test case execution reports
by using the optional text string parameter of the function assertEquals().
Generally, the function call format for this function is assertEquals(text, con-
dition), where text is the text string that explains the meaning of this assertion
point in more detail. The string text is used as a prefix of the test report
shown above. For example, if the value of the variable ch should be ‘A’ but
it turns out to be ‘B’ instead, the function call assertEquals(“Check ch:,” ‘A’,
ch) would produce the report, “Check ch: expected ‘A’ but was ‘B’”.
Besides the functions assert() and assertEquals(), unit testing framework
typically provides two additional functions for writing test cases, setUp()
and tearDown(). The former sets up the test fixture whereas the latter destroys
it. A test fixture is a set of objects that act as samples for testing. Normally,
the test fixture comprises the instance of the unit under test (e.g., the instance
of the class that is derived from the class FiniteStateMachine) and also other
supplementary objects, which are required for effective unit testing.
Typically, the unit testing framework offers the base class for writing test
cases, which provides the functions assert(), assertEquals(), setUp(), and
tearDown(). The programmer normally derives his tester class from this base
class, fills in setUp() and tearDown() functions, and starts writing individual
test cases. Each function member of the tester class — whose name follows
the given naming convention — is a single test case.
Remember that concrete setUp() and tearDown() implementations are
shared by all test cases defined within a single tester class. Actually, these
two functions are implemented as null (empty) methods on test cases. The
execution of each test case starts with the call to the function setUp(), proceeds
with the call to the user-defined function that implements a single test case,
and ends with the call to the function tearDown(). Normally, we put the test
case initialization and cleanup code in the functions setUp() and tearDown(),
respectively.
The third unit testing postulate is that the unit under test must not be
touched at all. We are only allowed to write new classes that are derived
from the base class, which is provided by the unit testing framework. Chang-
ing the source code of the unit under test for the purpose of its testing is
strictly forbidden, even by adding a simple print statement to the standard
output file. Because of that, the only proper way to do the unit testing is to
drive the unit under test with various messages, capture its responses, and
check the correctness of the unit’s external behavior.
This kind of controlled execution of the implementation under test is
referred to as the test harness. The key request is that it must be fully
automatic. The programmer should provide the mechanisms that support
the test harness while he plays the role of the implementer (what we refer
to as the design for testability). Otherwise, providing a test harness can be a
very hard task. For example, consider a simple program that reads its input
from the keyboard and writes its output to the monitor by using the oper-
ating system services, which cannot be replaced. Because we are not allowed
to change the source code of the implementation under test, providing a test
harness in this case is hardly achievable.
The example of the unit testing framework is JUnit, an open-source testing
framework for unit testing Java programs that was originally developed by
Erich Gamma and Kent Beck. Based on this framework, the open-source
community came up with CppUnit, a semantically equivalent testing frame-
work for unit testing C++ modules. These frameworks are very simple but
powerful enough to enable industrial-strength unit testing of individual
software units. Because JUnit and CppUnit are semantically equivalent, we
will treat them as two implementations of the same framework.
The framework comprises the interface Test and two fundamental classes,
the classes TestSuite and TestCase (Figure 5.1). As shown in the figure, the
test suite (an instance of the class TestSuite) can contain an arbitrary number
of test cases (instances of the class TestCase), as well as an arbitrary number
of other hierarchically subordinated test suites. This arrangement allows
programmers (playing the role of unit testers) to organize test cases into a
hierarchy of test suites to their convenience.
Any concrete tester class (such as the class MyTester in Figure 5.1) must be
derived from the base class TestCase, which among others provides the four
fundamental functions described above, namely, setUp(), tearDown(), assert(),
∗ «interface»
Test
TestSuite TestCase
MyTester
FIGURE 5.1
The structure of the JUnit testing framework.
package automata4;
import java.util.*;
The field member lastOutput is used to store the last output generated by
the FSM. The function getLastOutput() returns this last output generated by
the FSM to its caller. It is used by the test case function to retrieve the last
FSM output to compare it with the expected output (also referred to as the
“golden output”). The function println() is simple enough — it just stores
the output of the FSM and prints it by calling the standard function Sys-
tem.out.println().
Although we do not need it in this example, we can generally use an
analogous approach for capturing the FSM inputs also. Instead of calling the
standard function System.in.read() directly, we can construct and call the
function member read() of the class MyIO. This function would in its own
turn read the input by calling the standard input functions and store that
input into the corresponding field member of the class MyIO (e.g., lastInput).
The last FSM input would be available through the function member get-
LastInput().
After providing test harness support, we continue with the definition of
the tester class, which is named Automata4Tester in this example. The source
code of this class is the following:
/*
* Automata4 tester
*
*/
package automata4;
import junit.framework.*;
// test case 1
public void test1() {
a4.processMsg('0');
assertEquals(MyIO.getLastOutput(),“Output 0”);
a4.processMsg('0');
assertTrue(MyIO.getLastOutput() == “Output 0”);
}
// test case 2
public void test2() {
for(int i=0;i<100;i++) {
a4.processMsg('0');
assertEquals(MyIO.getLastOutput(),“Output 0”);
}
}
// test case 3
public void test3() {
a4.processMsg('0');
assertEquals(MyIO.getLastOutput(),“Output 0”);
a4.processMsg('1');
assertEquals(MyIO.getLastOutput(),“Output 1”);
a4.processMsg('0');
assertEquals(MyIO.getLastOutput(),“Output 1”);
a4.processMsg('1');
assertEquals(MyIO.getLastOutput(),“Output 2”);
a4.processMsg('0');
assertEquals(MyIO.getLastOutput(),“Output 2”);
a4.processMsg('1');
assertEquals(MyIO.getLastOutput(),“Output 0”);
}
// test case 4
public void test4() {
a4.processMsg('1');
assertEquals(MyIO.getLastOutput(),“Output 1”);
a4.processMsg('1');
assertEquals(MyIO.getLastOutput(),“Output 2”);
a4.processMsg('1');
assertEquals(MyIO.getLastOutput(),“Output 0”);
}
// test case 5
public void test5() {
for(int i=0;i<1000;i++) {
test3();
test4();
}
}
The tester class Automata4Tester is derived from the class TestCase. Its field
member a4 is an instance of the implementation under test, namely, the class
Automata4. The constructor of the class Automata4 simply calls the constructor
of its super class (the class TestCase) and passes its input parameter (String name).
The function setUp() creates an instance of the implementation under test
by instantiating the class Automata4 and storing its instance into the field
member a4. The function tearDown() is empty in this example because the
Java garbage collector takes care of unused objects. The garbage collector
destroys the object that is stored in the field member a4 at the end of the test
case.
The function test1() is the first test case defined within the tester class
Automata4Tester. Basically, it tests the FSM state transition from the state S0
to the state S0, which is driven by the input value 0. It does the same
operation twice. Each time it supplies input 0 to the implementation under
test (stored in the field member a4) by calling its function processMsg() and
passing it the parameter, ‘0’.
Assuming that the implementation under test was in its initial state and
that it reacted correctly to the given input, its last output should be the text,
“Output 0”. The test case function test1() checks that assumption by calling
the function assertEquals(). The first real parameter of that function call is the
value of the last output, which is returned by the function member get-
LastOutput() of the class MyIO, whereas the second parameter is the expected
string, “Output 0”.
Second, the test case function test1() again supplies input 0 to the imple-
mentation under test (stored in the field member a4) by calling its function
processMsg() and passing it the parameter, ‘0’. Assuming that the implemen-
tation under test has reacted properly in the first place, it would be in the
initial state at the time the second call to the function processMsg() happens.
Driven with the input ‘0’, it should produce again the output string,“Output
0”. The test case function test1() checks this assumption again, only this time
it does so by calling the function assert(). The real parameter of this function
call is the condition MyIO.getLastOutput() == “Output 0”.
The function test2() is the second test case defined within the tester class
Automata4Tester. This test case is slightly more complex than the previous
one. The previous test case checks if the implementation under test reacts
correctly when it is driven twice with the same input value ‘0’ in the same
current state (S0). We did this on purpose — first, to demonstrate the usage
of both assert() and assertEquals(), and second, the implementation under test
may not always react correctly if it is driven with a certain input value in
the given state, at least not in theory.
This practice may seem paranoid but, in reality, various types of time- and
FSM evolution-dependent bugs are hidden at the beginning and become
evident only later during the FSM evolution. Returning to the problem at
hand, we ask ourselves: Will this FSM react correctly many times, for exam-
ple, 100 times? With JUnit at our disposal, we can easily construct a test case
that resolves such dilemmas.
This is exactly what the test case function test2() does. It does so by exe-
cuting the body of the for loop 100 times. Inside the body of the loop, it
drives the implementation under test with input value ‘0’ by calling its
function processMsg(). After each of these calls, it checks if the last output
was the string “Output 0” by calling the function assertEquals().
The function test3() is the third test case defined within the tester class
Automata4Tester. This is a typical FSM-related test case, characterized with
the complete coverage of the FSM state transition graph. The flow of the
state transitions checked by this test case is the following:
The function test4() is the fourth test case defined within the tester class
Automata4Tester. This is another typical FSM-related test case, characterized
by its progressive nature. The counter is always driven with the input “1”
so that its content is incremented every time. This test case does not provide
the full state transition graph coverage, but it is valid and we can think of
many partial graph coverage test cases. The flow of the state transitions
checked by this test case is the following:
The function test5() is the fifth, and the last, test case defined within the
tester class Automata4Tester. It is a fairly simple, yet rather intensive, test case
that is based on the combination of the previous two test cases. The test case
function test5() repeats the body of the for loop 1,000 times. Inside the body
of the loop, it just calls the functions test3() and test4() in succession.
The function suite() returns the test suite, which it creates by calling the
constructor of the class TestSuite. The real parameter of this function call is
the name of the implementation under test class file (Automata4Tester.class).
The constructor of the class TestSuite finds all the functions whose names
start with the word “test” defined within the class Automata4Tester and
automatically adds them to the test suite it creates.
The function main() runs the test suite defined by the previous function
suite(). It does that by calling the function run() of the class TestRunner, which
is an integral part of the JUnit testing framework. The real parameter of this
function call is the test suite that is created by the function suite(). This test
suite contains all test cases defined within the class Automata4Tester.
In the case of more complex implementations, we may decide to create
more tester classes rather than define all test cases within a single tester class,
such as the class Automata4Tester. In such a situation, we would need to create
a hierarchy of test suites and the overall tester class that would automatically
run all test cases in all test suites. The source code of such a tester class is
the following:
/*
* Tester
*
*/
package automata4;
import junit.framework.*;
/*
* TestSuite that runs all test suites
*
*/
The class AllTests comprises two function members, namely, the functions
suite() and main(). The former function creates and returns the test suite that
is in the root of the test suite hierarchy. This means that it contains all other
hierarchically subordinated test suites. The latter function executes the root
test suite, i.e., it executes all test suites that were added to it.
The function suite() creates the root test suite simply by calling the con-
structor of the class TestSuite. The real parameter of this function call is the
name of that test suite (the string “All Tests”). It then adds the test suite that
contains the test cases defined within the tester class Automata4Tester to the
root test suite. It does this by calling the function member addTests() of the
root test suite object suite. Generally, in the case when we have multiple
tester classes, we would repeat the call to the function addTests() for each
tester class.
The function main() runs the test suite defined by the previous function
suite(). It does this by calling the function member run() of the class TestRun-
ner. The real parameter of this function call is the test suite created by the
function member suite() of the class AllTests. This test suite contains a single
hierarchically subordinated test suite, which in turn contains all test cases
defined within the class Automata4Tester.
We start the automatic execution of all test cases defined within the class
Automata4Tester by running the file Automata4Tester.class. Similarly, we start
the automatic execution of all test cases defined within all tester classes (in
this simple example we have just one of them, the class Automata4Tester) by
running the file AllTests.class. In both cases, we should get the same result.
Each test case function will print its own outputs to the standard output file.
At the end, the test runner will print out the final report, which should look
like this:
Time: 1,783
OK (5 tests)
Press any key to continue...
The number 1783 corresponds to the number of seconds that were needed
to execute all test cases, whereas the number 5 in parenthesis corresponds
to the total number of test cases that were executed.
INVITE
404
ACK
FIGURE 5.2
An example of the conformance testing test case.
The present version of the specification considers the following three types
of sessions:
The way the SIP conformance test suite is structured is a good example of
typical conformance test suite structuring. All test cases are classified into
the following four main groups (which correspond to the main SIP function-
alities):
• Registration
• Call control
• Querying for capabilities
• Messaging
The test cases in the main groups are further classified according to the
role that should be checked. The roles for the main group registration are the
registrant and the registrar. The roles for the main group call control are
originating endpoint, terminating endpoint, proxy, and redirect server. The roles
for the main group querying for capabilities are originating endpoint, terminating
endpoint, and proxy. The roles for the main group messaging are registrant,
registrar, originating endpoint, terminating endpoint, proxy, and redirect server.
Some of the role subgroups are further divided into functional subgroups.
For example, the role subgroup originating endpoint of the main group call
control is divided into three functional subgroups, namely, call establishment,
call release, and session modification. Finally, functional subgroups of test cases
can be divided into three test groups: valid behavior (V), invalid behavior (I),
and inopportune behavior (O).
Notice that official conformance testing can be conducted only by the
authorized organizations (national certification centers, telecom operators,
and so on) that use special tools that themselves were certified for such a
usage. These tools are professional equipment, most frequently referred to
as testers, e.g., a SIP tester. A tester typically comprises the framework that
supports test suite administration, execution (most frequently based on inter-
pretation), and associated reporting. Such a framework is referred to as the
testing framework.
members. Original IETF SIP torture tests focus on areas that have caused
problems in the past or have particularly unfavorable characteristics if han-
dled improperly. Some of them test only the parser and others test both the
parser and the application above it. Some use valid and some use invalid
SIP messages to check the target functionality.
The SIP Forum tests are classified into the following eight test groups:
protocol tortures (26 tests), authentication (4 tests), registration (1 test), dialog
and transaction processing (19 tests), DNS (2 tests), NAT capabilities (2 tests),
services (2 tests), and warnings about obsolete features (5 tests). All tests are
defined in one spreadsheet (XLS file). The test attributes (spreadsheet col-
umns) are the following: number, title, tested device, expected behavior,
typical failures, notes, call flow, source (the corresponding section in RFC
3261), and comment.
For example, the test number 201 entitled “A Short Tortuous Request”
tests the SIP user agent server behavior. The expected behavior is, “Server
considers the request valid and generates a proper response”. The call flow
is illustrated with the sequence diagram shown in Figure 5.3.
c : UAC s : UAS
INVITE
180 (Ringing)
CANCEL
200 (OK)
ACK
FIGURE 5.3
An example of the SIP protocol torture test.
The axiomatic specification of the finite state machine is the model of the
FSM in the predicate calculus. This model is the set of well-formulated
formulas. The first well-formulated formula in the model is optional and it
defines the initial state of the FSM. Its general format is the following:
State(INITIAL).
State is a predicate and INITIAL is the name (label) of the FSM initial state.
The names State and INITIAL are noninterpretative user-defined names (like
names of the user-defined functions and constants in the higher-level pro-
gramming languages). For brevity, in this section we use the name S instead
of State and we label finite state machine states with numbers (0, 1, 2…)
rather than with symbolic names.
The fact that this first well-formulated formula is optional requires a short
comment. In most of the formal FSM descriptions, such as UML activity
diagrams and statecharts, the specification of the FSM initial state is man-
datory. Here, it is not. If we always want to examine the FSM evolution
beginning from the same state, we will define it as the FSM initial state in
the FSM axiomatic specification. Alternately, sometimes it is possible and
preferable to examine the FSM evolution beginning from different FSM
states. In that case, we do not define the FSM initial state in the FSM axiomatic
specification and we define it on the left-hand side of the concluding well-
formulated formula instead.
The rest of the well-formulated formulas in the FSM axiomatic specification
are obligatory. Each of the mandatory well-formulated formulas models a
single FSM state transition (also referred to as a FSM branch). The format of
the well-formulated formula that models time invariant FSM state transition
from the state X to the state Y triggered with the input T and generating the
output R is the following:
State, Input, and Output are predicates. X, Y, T, and R are constants that
label the source FSM state, the destination FSM state, the particular FSM
input, and the particular FSM output, respectively. Most frequently, we use
abbreviated names I and S instead of Input and Output, respectively. In the
case that the state transition generates more, say N, output signals (mes-
sages), the corresponding well-formulated formula has the following format:
I is the label of the particular FSM and J is the label of the particular state
transition modeled with this formula. If we include both Automata(I) and
Transition(J), the state transition is enabled. If we skip Automata(I), the FSM
(i.e., all its state transitions) are disabled. If we skip Transition(J), this indi-
vidual state transition is disabled. This concludes the presentation of the
axiomatic specification of a single FSM.
A theoretical test case for a single FSM is the theorem about the particular
FSM evolution path, which states that for a given series of inputs (I1, I2…In),
FSM performs a series of state transitions (S1, S2…Sn), which will produce a
series of particular output values (O1, O2…On). The corresponding well-
formulated formula has the following format:
{Automata(N)&Transition(M)&Input(I1)&…&Input(In)} =>
{Output(O1)&...&Output(On)&State(S1)&…&State(Sn)}
Most frequently, we only want to check that FSM produces the expected
series of outputs and that at the end it reaches the expected final state Sn.
The corresponding theorem has a very similar but simpler format:
{Automata(N)&Transition(M)&Input(I1)&…&Input(In)} =>
{Output(O1)&...&Output(On)&State(Sn)}
Counter by
modulo 2
I(0)/O(0)
S(0)
I(1)/O(1)
I(0)/O(1)
S(1)
I(1)/O(0)
I(1)/O(2)
I(0)/O(2)
S(2)
FIGURE 5.4
The counter by modulo two statechart.
S(0)
The first well-formulated formula defines the state S(0) as the FSM initial
state. Next, six well-formulated formulas define six FSM state transitions —
from the state S(0) to S(0), from S(0) to S(1), from S(1) to S(1), from S(1) to
S(2), from S(2) to S(2), and from S(2) to S(0), respectively. A(0) is the global
control predicate. T(0), T(1)…T(5) are the individual state transition control
predicates. The sample theorem is the following:
Predicates: S A T I O
Functions: 0 1 . 2 3 4 5 :
EQ:
ESAF:
ESAP:
0 <BC: 19 NC: 6 AC: 3 U: 0>
1 {T0 N1 R1 F0 C9 H0 h0 U11} *
.Proof Found!
In the formula above, Signal(P) is received and Signal(Q) is sent out of any
signaling path, channel, or network. In the case where the former signal is
transferred over path M and the latter signal is sent over the path N, the
formula would look like this:
In the sample theorem above, Signal(A) triggers the evolution of the system.
As the result of the evolution, the system generates three signals: Signal(B),
Signal(C), and Signal(D). At the end of the evolution, the FSMs reach their
final states, namely, State(X) and State(Y).
We now illustrate the concepts introduced above by the means of a simple
example. Consider a simple system with three FSMs (see their statechart
diagrams in Figure 5.5). The first FSM waits for the signal E(0) in its state
S(0). After receiving that signal, it sends the signal E(10) and goes to the state
S(1), where it waits for the signal E(1). Once it receives the signal E(1), it
sends the signal E(20) and goes to the state S(2). The second and the third
FSMs are very much alike. The former waits for the signal E(10) and after
E(1)/E(20)
S(2)
FIGURE 5.5
The statecharts of three communicating FSMs.
receiving that signal, it sends the signal E(11). The latter waits for E(20) and
sends E(21).
Next, we construct the theorem about the expected behavior of this simple
system. This theorem says that if we supply signals E(0) and E(1) to this
system, the first FSM will start evolving and will generate the signals E(10)
and E(20). These two signals will trigger the second and the third FSMs,
which will in their turn generate signals E(11) and E(21), respectively. Finally,
these FSMs will reach final states S(2), S(11), and S(21), respectively.
The axiomatic specification of this simple system and the theorem
explained above are specified in the following sequence of well-formulated
formulas:
; Theorem
conclusion
{E(0)&E(1)} => {S(2)&S(11)&S(21)&E(10)&E(20)&E(11)&E(21)}.
To automatically prove this theorem, we run Compile and THEO once again.
The final result looks like this:
Predicates: S E
Functions: 0 1 10 2 20 11 21 : .
EQ:
ESAF:
ESAP:
0 <BC: 14 NC: 3 AC: 3 U: 0>
1 {T0 N1 R1 F0 C1 H0 h0 U14} *
.Proof Found!
Because in first-order logic, I(0) <=> I(0)&I(0), we can rewrite the theorem
as follows:
We may interpret this theorem as follows: If we apply the same signal I(0)
many times (even up to infinity), we will always get the signal O(0) at the
FSM output and it will remain in the state S(0). Therefore, by proving indi-
vidual theoretical test cases, most frequently we are actually checking the
families of test cases. This concludes the presentation of the axiomatic spec-
ification and theoretical test cases related to FSMs.
Now let us see how we can use this in communication protocol engineer-
ing. We start with the formal verification of the specification. The concept is
rather simple, although it can prove to be difficult to realize in practice.
Ideally, two independent teams must be present (or at least a person who is
“changing hats”), namely, the design and testing teams. The former writes
the axiomatic specification of the family of communication protocols that is
modeled as a group of FSMs. The latter writes and proves the theoretical
test cases.
If a theoretical test case fails (the proof of the theorem cannot be found),
at least one error is generated in either axiomatic specification or in the
theorem. It may be the case that two or even more errors occur in both of
them. Most frequently, the errors are trivial oversights made by theorem
writers because they are not so familiar with the system at hand. If not, the
errors are typically caused by rather nontrivial oversights in the system
design.
Finding these errors is not a trivial task at all. Typically, we would try to
shorten the theorem or the axiomatic specification and see what happens.
Of course, with an automated theorem prover such as THEO at our disposal,
this is much easier than doing it by hand. Control predicates may help, also
— with them, we can sequence the events to our convenience. The need for
them is typically a clue that we have synchronization problems.
We can also use an automated theorem prover for automatic test case
generation. To do that, we assume that axiomatic specification of the system
is errorless. We start by selecting one of the possible input signals on the
left-hand side of the theorem. We then check various output signals at the
right-hand side of the theorem by trying to prove the theorem. If the proof
is found, our assumption was correct and we keep that signal at the right-
hand side. If not, we continue by checking other signals.
Of course, some input signals can just cause internal state transitions and
no signals at the output of the system. The right-hand side will remain empty
in that case. By continuing this process, we can generate theoretical test cases
of arbitrary length:
Similarly, we can make guesses about transient or final states of the system,
for example:
The real benefit of such automatically generated test cases is that they can
be translated into executable test cases and used for automatic testing of the
system implementation. Generating test cases in the previously described
fashion is not very efficient, and neither it is well coordinated. We can
generate test cases more cleverly by respecting the structure of the FSM
axiomatic specification rather then viewing it as a black box. Actually, the
FSM axiomatic specification introduced in this section is yet another means
of modeling the FSM state transition graph.
Generating test cases by traversing the FSM state transition graph is pos-
sible with the goal to achieve its complete coverage. Three possible types of
FSM state transition coverage exist, namely, node, branch (arc), and path
coverage. That the path coverage cannot be achieved if the graph is cyclic
is well known. Alternately, branch coverage subsumes node coverage and,
because of that, seems to be the best selection.
Sometimes we may have the opposite problem. The test suite (a set of test
cases) may already be available, such as the SIP conformance test suite
available from ETSI in TTCN-3 language (see Section 5.3). In such a situation,
we can use a tool to translate TTCN-3 test cases into theorems, and then we
can use the automated theorem prover to formally verify conformance of
the system axiomatic specification with the standard.
Yet another application of the automated theorem prover is the formal
verification of the system implementation. To do this, we assume that a
conformance test suite is already available and use the reverse engineering
tool to extract the axiomatic specification of the system from the implemen-
tation source code and, optionally, from log files if some are available. The
reverse engineering tool normally relies on conventions that govern the
structure of the source code and log files.
For example, the reverse engineering tool for the FSM Library-based imple-
mentations relies on the specification of the FSM Library API (see Section
6.8). This tool simply searches the source code for the specific library func-
tions and their real parameters to retrieve the well-formulated formulas that
constitute system axiomatic specification. More precisely, the tool extracts
;
; FE1FE5 definition
;
; Initial state definition:
S(FE1FE5_ON_HOOK).
{S(FE1FE5_ON_HOOK)&E(r3_DisconnectReqInd)} =>
{S(FE1FE5_ON_HOOK)&E(r3_DisconnectRespConf)}.
{S(FE1FE5_ON_HOOK)&E(r3_SetupReqInd)} =>
{S(FE1FE5_WAIT_OFF_HOOK)&E(r3_ReportReqInd)}.
{S(FE1FE5_ACTIV)&E(r3_SetupReqInd)} =>
{S(FE1FE5_ACTIV)&E(r3_DisconnectReqInd)}.
{S(FE1FE5_ACTIV)&E(r3_DisconnectReqInd)} =>
{S(FE1FE5_WAIT_ON_HOOK)&E(r3_DisconnectRespConf)}.
{S(FE1FE5_ACTIV)&E(User_ON_HOOK)} =>
{S(FE1FE5_ON_HOOK?)&E(r3_DisconnectReqInd)}.
{S(FE1FE5_WAIT_ON_HOOK)&E(User_ON_HOOK)} =>
{S(FE1FE5_ON_HOOK)}.
{S(FE1FE5_WAIT_ON_HOOK)&E(r3_DisconnectReqInd)} =>
{S(FE1FE5_WAIT_ON_HOOK)&E(r3_DisconnectRespConf)}.
{S(FE1FE5_WAIT_ON_HOOK)&E(r3_SetupReqInd)} =>
{S(FE1FE5_WAIT_ON_HOOK)&E(r3_DisconnectReqInd)}.
{S(FE1FE5_WAIT_OFF_HOOK)&E(User_OFF_HOOK)} =>
{S(FE1FE5_ACTIV)&E(r3_SetupRespConf)}.
{S(FE1FE5_WAIT_OFF_HOOK)&E(r3_DisconnectReqInd)} =>
{S(FE1FE5_ON_HOOK)&E(r3_DisconnectRespConf)}.
{S(FE1FE5_WAIT_OFF_HOOK)&E(r3_SetupReqInd)} =>
{S(FE1FE5_WAIT_OFF_HOOK)&E(r3_DisconnectReqInd)}.
conclusion
; {S(FE1FE5_ON_HOOK)&E(User_OFF_HOOK)} =>
; {S(FE1FE5_UNKNOWN_FE2)&E(r1_SetupReqInd)}.
; {S(FE1FE5_UNKNOWN_FE2)&E(User_ON_HOOK)} =>
; {S(FE1FE5_DISCONNECTING_FE2)}.
{S(FE1FE5_ON_HOOK)&E(User_OFF_HOOK)&E(User_ON_HOOK)} =>
{S(FE1FE5_DISCONNECTING_FE2)&E(r1_SetupReqInd)}.
Actually, this file contains three theorems (starting after the keyword con-
clusion). The first two are commented out (the semicolon character “;” at the
beginning of the line means that the line is a comment) leaving only the
third open as a subject to prove by the automated theorem prover. The first
commented theorem claims that if the FSM FE1FE5 is stimulated with the
input signal User_OFF_HOOK in its initial state FE1FE5_ON_HOOK, it will
generate the output signal r1_SetupReqInd and move to the state
FE1FE5_UNKNOWN_FE2. The second commented theorem claims that if
the FSM FE1FE5 is further stimulated with the signal User_ON_HOOK in
the state FE1FE5_UNKNOWN_FE2, it will just move to the state
FE1FE5_DISCONNECTING_FE2.
Finally, the third theorem — which is actually the subject of automated the-
orem proving — is a simple composition of the previous two theorems. It states
that if the FSM FE1FE5 is stimulated by the sequence of the input signals
User_OFF_HOOK and User_ON_HOOK in its initial state FE1FE5_ON_HOOK,
it will generate the output signal r1_SetupReqInd and finish in the state
FE1FE5_DISCONNECTING_FE2. To automatically prove this theorem, we run
Compile and THEO once again. The final result looks like this:
Predicates: S E
Functions: FE1FE5_ON_HOOK User_OFF_HOOK r1_SetupReqInd User_ON_HOOK
FE1FE5_DISCONNECTING_FE2 . r1_DisconnectRespConf FE1FE5_UNKNOWN_FE2
r1_DisconnectReqInd User_DIGIT r1_ProceedingReqInd
FE1FE5_WAIT_FOR_DIGITS r1_ADDL_AddrReqInd r3_DisconnectReqInd
FE1FE5_WAIT_ON_HOOK r1_SetupRespConf FE1FE5_ACTIV r1_ReportReqInd
r3_DisconnectRespConf r3_SetupReqInd FE1FE5_WAIT_OFF_HOOK
r3_ReportReqInd FE1FE5_ON_HOOK? r3_SetupRespConf :
EQ:
ESAF:
ESAP:
0 <BC: 56 NC: 4 AC: 4 U: 0>
1 {T1 N1 R1 F0 C49 H1 h0 U8} *
.Proof Found!
test the product under conditions that it is expected to face in its real exploi-
tation. The description of these conditions is given with the set of product’s
operational profiles. Two key ideas are behind the concept of statistical usage
testing.
The first addresses the focus of testing whereas the second addresses the
quality of the final product. We start with the genesis of the first of these
two ideas. That any nontrivial product requires a vast amount of test cases
for its verification should be obvious by now. The order of this amount can
very easily go up to hundreds of thousands of test cases or even more.
Because some of the product working modes (also referred as states) are
more frequently used than others, selecting the number of associated test
cases accordingly makes sense, especially if we want to limit the size of the
test suite.
This reasoning led to the concept of the operational profile. Remember
that the motivation for its introduction was to respect the usage frequencies
of individual operational states. Actually, because product state transitions
are triggered by the corresponding events (signals, messages), the state usage
frequencies are equal to the frequencies of these events. Furthermore, if we
want to make our considerations independent of the total number of usages
(tests), introducing the probabilities of events is convenient. (In this context,
we define the probability as the number of real occurrences of the event
divided by the total number of its possible occurrences.)
Mathematically, the operational profile is a Markov process. It can be
modeled as a special kind of graph whose vertices are product states and
whose arcs are state transitions triggered with the corresponding events of
the given probability. The operational profile is essentially a finite state
machine with given probabilities of its state transitions. Of course, the sum
of probabilities of all outgoing state transitions for a single state must be
equal to 1 (100%).
The second idea behind the concept of statistical usage testing is to use
the product reliability as the main measure of its quality. The genesis of this
idea is that traditional software engineering measures of product quality are
the number of remaining bugs and the test coverage of the implementation under
test that was achieved through its testing. However, achieving good results
with respect to these two measures is not sufficient for assuring the high
quality of the product.
For example, consider the following paradox. Imagine a software product
that has a single bug that causes a system crash every time the software is
started. Although the product has the excellent value of the metric number
of remaining bugs (only 1 bug remaining), it is completely unreliable and
therefore practically unusable. In real life, we are not interested in how good
the product is with respect to number of remaining bugs and test coverage.
Rather, we are primarily interested in its reliability.
Of course, we cannot measure the product reliability directly, but we can
estimate this from the number of test cases that it has successfully passed.
More precisely, in real engineering practice we have the opposite problem.
We want to calculate the number of test cases needed for the desired product
reliability and for the given level of risk we are ready to accept. We can do
this by solving the following equation:
B = RN
where
B is an upper bound on the probability that the model assertions are
erroneous
R is a lower bound on the estimate of product reliability
N is the number of random test cases that the product must successfully
pass
Front-End
TestSuite
∗
OpProfile OpProfileSpec
«framework» GTCG
∗ 1 1 ∗
GME ∗ 1
1 Statistics
1
1 1
∗
1 1
FIGURE 5.6
The working environment for generating statistical test suites.
The particular metamodel that specifies the language (and the paradigm)
for modeling operational profiles is represented with the metaclass Opera-
tionalProfile in Figure 5.6. Each concrete operational profile model (repre-
sented with the class OpProfile in Figure 5.6) is created by using the
operational profile modeling paradigm (the class OpProfile is derived from
the class OperationalProfile). Creating operational profile models by using this
paradigm is quite easy.
The modeling language for rendering operational profile models has a
single symbol, State. This symbol has a single attribute, which is the name
of the state. Normally, we just drag and drop the state symbol icon to the
working sheet, click on the name field, and type in its name. Each of the
state symbols we place on the working sheet represents a single working
state (mode) of the product that we want to test.
Rendering state transitions requires a little more work. To render a state
transition, we select a connecting tool (symbolized by the operator “+”), click
on the source state, and click on the destination state. When the state tran-
sition is in place, we enter the particular data for its attributes. A state
transition has the following three attributes:
• EventClass: specifies the class of events that trigger the state transi-
tion
• Output: specifies the expected output of the state transition
• Probability: specifies the probability of the state transition (in percent)
The event class definition above consists of two parts. The first one is on the
left-hand side of the substring “->” and is referred to as the event class. The
event class E(a,b,c…); is a string with an arbitrary number of parameters
(substrings), labeled here as a, b, c, and so on. The second part of the defi-
nition is on the right-hand side of the substring “->”. It provides definitions
of possible replacements (which are also strings) for each event class param-
eter. As indicated above, the parameter a may be replaced with the string A1
or A2 and so on.
A particular event (also referred to as the constant event) is an event class
without parameters. We may also think about it as the event class with a
single member. Particular events are generated from the event class by sub-
stituting each event class parameter with the randomly selected replacement
from the list of possible replacements. All replacements have equal selection
probabilities. Examples of particular events for the event class definition
given above are E(A1,B1,C1…), E(A1,B1,C2…), E(A1, B2, C1…), E(A2, B1, C1…),
and so on.
The event class format shown above is feasible as far as the number of the
possible values of event class parameters is relatively small. But when the
number of the possible values is large, writing them explicitly becomes
impractical, if not impossible. For example, consider the integer parameter
whose possible values are from the interval [0,10000). Writing all 10,000 of
its possible values would be really annoying. To make it easier for the user,
the working environment supports the following two intrinsic functions:
When we place and name all state symbols, interconnect them with state
transitions, and enter the data for attributes of all state transitions, the oper-
ational profile model is finished and we can store it in a file (or a database).
This is exactly the main purpose of the working environment front-end
(Figure 5.6). Of course, later we may modify the model by adding or deleting
states or state transitions, as well as by changing the data for attributes of
state transitions, and store it again. All these manipulations are supported
by the GME’s GUI.
The working environment back-end consists of two parts. The first is the
operational profile model interpreter (represented by the class ModelInter-
preter in Figure 5.6), which is registered to GME. The second part of the back-
end is a separate program written in Java, which is named Generic Test Case
• Part I defines the number of states (M) and the number of event
classes (N).
• Part II is a matrix of state transition probabilities. The matrix element
Pij defines the probability of the event class number j in the opera-
tional profile state number i.
• Part III is a matrix of event class definitions. The matrix element Eij
defines the event class number j in the operational profile state
number i. Most frequently, Eij is the same in all states (Ei1 = Ei2 =
…EiM).
• Part IV is a matrix of next states. The matrix element Tij defines the
next state number (index) for the event class number j in the oper-
ational profile state number i.
The file testcases.txt contains the series of test cases. Each test case starts
with its number followed by the column character ‘:’ (e.g., 0:, 1:, 2:). The next
line contains the test bed setup command TestBox.initialize(), which essen-
tially initializes the hardware connected to product inputs and outputs for
the purpose of automatic testing. The test bed setup command is followed
by the series of lines that contain particular events randomly selected from
the associated event classes (the number of these lines is determined by the
given test case length). The event class itself is selected randomly from the
distribution defined by the operational profile data (opspec.txt, Part II).
The file statistics.txt consists of two parts. The first part contains a series
of lines, one per operational profile state. Each of these lines indicates the
number of occurrences of the corresponding operational profile state (ci), the
discrepancy between the observed and expected frequency of state occur-
rence (di), and the significance level (SLi). The significance level is actually
the probability that the discrepancies as large as those observed would occur
with random variation. The second part of the statistical report shows the
mean value of the discrepancy and the mean value of the significance value.
The detailed explanation of the statistical measures mentioned above is
outside the scope of this book but can be found elsewhere (e.g., Woit, 1994).
Practically, it is enough to remember the following guides:
The statistical usage testing methodology governs the usage of tools that
create the working environment. The methodology subsumes the following
steps:
This methodology can be used for testing both parts of products and
complete products. We will illustrate such applications by the following two
examples. The implementation under test in the first example is the SIP invite
client transaction. We start with modeling its operational profile in accor-
dance with the methodology outlined above (Figure 5.7).
The operational profile shown in Figure 5.7 has five working states,
namely, Initial, Calling, Proceeding, Completed, and Terminated. At the same
time, it has nine event classes that are intentionally labeled with names that
resemble the original specification (see RFC 3261, Figure 5). The definitions
of the event classes (not shown in Figure 5.7) are the following:
• The event class labeled INVITE is defined as INVITE (this class has
a single member).
• The event class labeled 300–699 is defined as M3->M3:=rand-
Int<300,700>;
• The event class labeled TA is defined as TA (original RFC 3261 label:
Timer A fires).
• The event class labeled 1XX is defined as M1->M1:=rand-
Int<100,200>;
• The event class labeled TB or TransportERR is defined as E->E:=TB/
TransportERR;
Initial
INVITE, P = 100%
TA, P = 20%
Calling
1XX, P = 20%
TB or TransportERR, P = 20%
300–699, P = 34%
2XX, P = 20%
2XX, P = 33%
300–699, P = 33%
Completed
TransportERR, P = 33%
TD, P = 34%
Terminated
End, P = 100%
FIGURE 5.7
The SIP INVITE client transaction operational profile.
5 9
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
0.2 0.2 0.2 0.0 0.0 0.2 0.2 0.0 0.0
0.0 0.33 0.0 0.0 0.0 0.33 0.34 0.0 0.0
0.0 0.0 0.0 0.0 0.33 0.0 0.33 0.34 0.0
0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
null null null null null null null null End
E->E:=TB/TransportERR; M1->M1:=randInt<100,200>; TA null null
M2->M2:=randInt<200,300>; M3->M3:=randInt<300,700>; null null
null M1->M1:=randInt<100,200>; null null null
M2->M2:=randInt<200,300>; M3->M3:=randInt<300,700>; null null
null null null null TransportERR null M3->M3:=randInt<300,700>;
TD null
null null null INVITE null null null null null
0 0 0 0 0 0 0 0 0
0 2 1 0 0 0 3 0 0
0 2 0 0 0 0 3 0 0
0 0 0 0 0 0 3 0 0
0 0 0 1 0 0 0 0 0
Note: The specifications of event classes for the states 1, 2, and 3 (Calling,
Proceeding, and Completed) were too long to fit into a single line. Therefore,
definitions of event classes for each of these states spans across two lines
(the second starts at the next level of indentation).
Next, we activate GTCG with the script that specifies the starting state
identification 4 (Initial), the number of test cases that is equal to 1,000, and
the test case length that is equal to 4 (this means 4 steps, i.e., particular
events, per test case). Selection of this particular test case length requires a
short comment. This value is exactly the length of the shortest path across
all five states starting from the state Initial (path Initial-Calling-Proceeding-
Completed-Terminated, with five states and four state transitions). Of course,
other paths of length 4 are possible and will be generated.
As already mentioned, the GTCG creates two output files, testcases.txt and
statistics.txt. According to the methodology outlined above, we first check
the quality of the generated test suite by inspecting the file statistics.txt. Its
content is the following:
Calculating statistics
The average significance level SL is equal to 72% (0.72). Because this number
is greater than the required 20%, we conclude that the quality of the generated
test suite is sufficient and that we can use it for the statistical usage testing.
Next, we look more closely to a couple of test cases from the beginning of
the file testcases.txt to get a better feeling of the nature of statistical test cases.
The relevant comments are interleaved with the test cases:
0:
TestBox.initialize();
INVITE
443
TransportERR
End
Test case number 0: After the initial INVITE, GTCG randomly selects the
event class labeled 300–699 and the particular event 443 from that class. This
action causes the state transition to the state Completed (Figure 5.7). Next,
GTCG randomly selects the event TransportERR, thus causing the state tran-
sition to the state Terminated. End is the only possible event in that state.
1:
TestBox.initialize();
INVITE
TA
586
TD
Test case number 1: After the initial INVITE, GTCG randomly selects the event
class TA (Timer A fires). The current state remains the state Calling (Figure 5.7).
Next, GTCG randomly selects the event 586, thus causing the state transition
to the state Completed. Finally, GTCG randomly selects the event TD (Timer D
fires), which causes the state transition to the state Terminated.
2:
TestBox.initialize();
INVITE
190
267
End
Test case number 2: After the initial INVITE, GTCG randomly selects the
event class 1XX and the particular event 190. This causes the state transition
to the state Proceeding (Figure 5.7). Next, GTCG randomly selects the event
267, thus causing the state transition to the state Terminated. The next event
must be the event End.
3:
TestBox.initialize();
INVITE
494
TD
End
Test case number 3: After the initial INVITE, GTCG randomly selects the
event class 300–699 and the particular event 494. This causes the state tran-
sition to the state Completed (Figure 5.7). Next, GTCG randomly selects the
event TD, thus causing the state transition to the state Terminated. The next
event must be the event End.
In the short descriptions of the generated test cases given above, we used the
construct, “GTCG randomly selects the event class X and the particular event
Y,” for brevity. One should remember that the selection of the event class is
always in accordance with the given operational profile probability distribution
whereas the selection of the particular event from the given class is really
random.
The previous example shows how we can use statistical usage testing for
testing a part of the product. As already mentioned, we can employ statistical
usage testing for testing the whole products, too. The next example shows
such an application — statistical usage testing of the simple SIP softphone.
The operational profile of the SIP softphone is shown in Figure 5.8. It has
8 states and 13 event classes. The states are Connecting, Terminating, Discon-
necting, Connected, Calling, Initial, Proceeding, and Ringing (listed here in the
ascending order of their identification). The event classes are RELEASE, 200,
ACK, 180, ERR, END, ANSWER, 100, INVITE, SETUP, BYE, TH, and TB (also
listed in the ascending order of their identification).
All event classes have just one member and their definition is equal to the
label shown in Figure 5.8 with the exception of the event class that is labeled
ERR, which is defined as follows:
M3->M3:=randInt<300,381>/randInt<400,494>/randInt<500,514>/randInt<600,607>;
This definition is a good example of how we can specify a random value that
may be selected from more disjoint intervals of values. Next, we generate 1,000
test cases with five test steps each. The content of the file statistics.txt is the
following:
Calculating statistics
Because the average significance level is 62% (greater than 20%), we can
conclude that the test suite quality is acceptable. A couple of typical test
cases are taken from the file testcases.txt and shown here without comments
(the reader should study them for their own exercise):
Calling Ringing
180, P = 30%
Proceeding Connecting
Connected
200, P = 100%
Terminated
End, P = 100%
FIGURE 5.8
The SIP softphone operational profile.
15:
TestBox.initialize();
SETUP
100
180
200
BYE
16:
TestBox.initialize();
INVITE
ANSWER
ACK
BYE
END
17:
TestBox.initialize();
SETUP
100
200
BYE
END
18:
TestBox.initialize();
INVITE
ANSWER
ACK
RELEASE
200
5.5 Examples
This section includes two examples and two related problems. The first
example demonstrates unit testing of the FSM Library-based implementa-
tions. The second example illustrates integration testing of the FSM Library-
based products.
5.5.1 Example 1
This example demonstrates unit testing of the SIP invite client transaction
implementation, which is described in Section 4.5.2 (Example 2). The SIP
invite client transaction implementation is based on the requirements and
analysis made in Section 2.3.3 (Figure 2.16) and the design presented in
Section 3.10.5 (Example 5).
Because the implementation under test (SIP invite client transaction) is
implemented in C++, we use CppUnit implementation of the unit testing
framework, introduced in Section 5.1. In this simple example, we will con-
struct just one test case to keep it short enough. Also, we will skip some SIP
message-specific message handling, which is really not essential for this
example.
We start this example by constructing two classes: ExampleTestCase and
ExampleMessageFactory. The former is the tester class, which comprises one
sample test case, whereas the latter is the supplementary class, which pro-
vides the functions for message management. The content of the class Exam-
pleTestCase declaration file, named ExampleTestCase.h, is the following:
#ifndef CPP_UNIT_EXAMPLETESTCASE_H
#define CPP_UNIT_EXAMPLETESTCASE_H
// CppUnit helper macros
#include <cppunit/extensions/HelperMacros.h>
// Problem specific headers
#include “../kernel/fsmsystem.h”
#include “../kernel/logfile.h”
#include “../NewSIP/InvClientTE.h”
#include “ExampleMessageFactory.h”
/*
* A sample test case
*
*/
class ExampleTestCase : public CPPUNIT_NS::TestFixture {
CPPUNIT_TEST_SUITE(ExampleTestCase);
CPPUNIT_TEST(example);
CPPUNIT_TEST_SUITE_END();
protected:
FSMSystemWithTCP *pSys;
LogFile *lf;
InviteClientTE* pInviteCltTE[NUMBER_OF_TES];
ExampleMessageFactory* pEMF;
uint8 *msg;
uint16 msgcode;
public:
void setUp();
protected:
void example();
};
#endif
The declaration file above includes the CppUnit helper macros header file
(HelperMacros.h) and the problem-specific header files (fsmsystem.h, logfile.h,
InvClientTE.h, and ExampleMessageFactory.h). The class ExampleTestCase is
derived from the class that is defined by the macro instruction
CPPUNIT_NS::TestFixture. The definition of the test suite starts with the
macro instruction CPPUNIT_TEST_SUITE() and ends with the macro
instruction CPPUNIT_TEST_SUITE_END(). The parameter of the former
macro instruction is the name of the test suite (ExampleTestCase, in this
example).
Generally, we use the macro instruction CPPUNIT_TEST() to define indi-
vidual test cases inside the body of the test suite definition. The parameter
of this macro instruction is the name of the test case function that is defined
within the tester class and that we want to add to the test suite. In this
particular example, we add a single test case function, named example(), with
a single macro instruction, CPPUNIT_TEST(), whose real parameter is the
string “example”.
Next, we define the test case fixture. In this example, it comprises the
following:
At the end of this file we declare the function setUp() and the test case
function example(). The content of the class ExampleTestCase definition file,
named ExampleTestCase.cpp, is the following:
#include “ExampleTestCase.h”
#include “../kernel/fsmsystem.h”
#include “../kernel/logfile.h”
#include “../NewSIP/InvClientTE.h”
#include “ExampleMessageFactory.h”
CPPUNIT_TES_SUITE_REGISTRATION(ExampleTestCase);
void ExampleTestCase::setUp() {
pSys = new FSMSystemWithTCP(11,11);
pEMF = new ExampleMessageFactory();
for (int i = 0; i < NUMBER_OF_TES; i++) {
pInviteCltTE[i] = new InviteClientTE();
}
uint8 buffClassNo = 4;
uint32 buffsCount[4] = {50, 50, 50, 50};
uint32 buffsLength[4] = {1025, 1025, 1025, 1025};
pSys->InitKernel(buffClassNo, buffsCount, buffsLength, 1);
void ExampleTestCase::example() {
msg = pEMF->MakeInviteToTALMsg();
pInviteCltTE[0]->Process(msg);
msgcode = pEMF->GetMsgCodeFromMBX(TLI_Test_FSM_MBX);
CPPUNIT_ASSERT_EQUAL(msgcode,(uint16)INVITE);
msg = pEMF->Make1XXToTAL();
pInviteCltTE[0]->Process(msg);
msgcode = pEMF->GetMsgCodeFromMBX(UA_Disp_FSM_MBX);
CPPUNIT_ASSERT_EQUAL(msgcode,(uint16)RESPONSE_1XX);
msg = pEMF->Make2XXToTAL();
pInviteCltTE[0]->Process(msg);
msgcode = pEMF->GetMsgCodeFromMBX(UA_Disp_FSM_MBX);
CPPUNIT_ASSERT_EQUAL(msgcode,(uint16)RESPONSE_2XX);
}
At the beginning of this file, we register the test suite with the macro
instruction CPPUNIT_TEST_SUITE_REGISTRATION(). The real parameter
of this macro instruction is the name of the test suite. Next, we define the
function setup() and the test case function example().
The function setup() starts by creating an instance of the class FSMSystem-
WithTCP, an instance of the class ExampleMessageFactory, and the given num-
ber (NUMBER_OF_TES) of instances of the implementation under test (the
class InviteClientTE). After that, it defines the types of buffers to be used by
the FSM Library kernel, initializes the kernel by calling the function Init-
Kernel() (see Section 6.8.4), creates the log file by calling the function LogFile(),
and sets the log interface by calling the function SetLogInterface() (see Section
6.8.105). At the end, it adds the given number (NUMBER_OF_TES) of
instances of the implementation under test to the FSM system by calling its
function Add() (see Section 6.8.2 and Section 6.8.3).
The function example() performs the test case by checking state transitions
of the implementation under test in the following three steps:
• Check the state transition form the state STATE_IDLE (see Section
4.5.2) to the state STATE_CALLING, driven by the message INVITE
• Check the state transition from the state STATE_CALLING to the
state STATE_PROCEEDING, driven by the message 1XX
• Check the state transition from the state STATE_PROCEEDING to
the state STATE_INITIAL, driven by the message 2XX
#ifndef _ExampleMessageFactory_FSM_
#define _ExampleMessageFactory_FSM_
#include “../constants.h”
#include “../kernel/fsm.h”
#include “../message/message.h”
uint8* Make2XXToTAL();
#include “ExampleMessageFactory.h”
#include “../parser/smsgtypes.h”
#include “../parser/smsg.h”
#define SipMessageCoding 0x00
extern char* IPString(unsigned int addr, char* buf, int len);
ExampleMessageFactory::ExampleMessageFactory() : FiniteStateMachine(16, 2, 3) {}
ExampleMessageFactory::~ExampleMessageFactory() {}
void ExampleMessageFactory::Initialize() {}
uint8* ExampleMessageFactory::MakeInviteToTALMsg(){
char temp[10];
char szHostName[255];
hostent* HostData;
uint8* recmsg;
uint8* msg;
...
PrepareNewMessage(0x00,INVITE);
SetMsgToAutomate(InviteClientTE_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
AddParam(SIP_RAW_MESSAGE, SIPMsg.getLastMessage().length(),
(uint8*) SIPMsg.getLastMessage().c_str());
AddParamDWord(SIP_PARSED_MESSAGE, (unsigned long) mes);
SendMessage(InviteClientTE_FSM_MBX);
msg = GetMsg(InviteClientTE_FSM_MBX);
return msg;
}
uint8* ExampleMessageFactory::Make1XXToTAL(){
uint8* msg;
...
PrepareNewMessage(0x00,RESPONSE_1XX_T);
SetMsgToAutomate(TAL_Disp_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
AddParamDWord(SIP_PARSED_MESSAGE, (unsigned long) mes);
SendMessage(InviteClientTE_FSM_MBX);
msg = GetMsg(InviteClientTE_FSM_MBX);
return msg;
}
uint8* ExampleMessageFactory::Make2XXToTAL(){
uint8* msg;
SIPMsg.makeResponse(“200”,“OK”,responseBody,0);
PrepareNewMessage(0x00,RESPONSE_2XX_T);
SetMsgToAutomate(TAL_Disp_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
AddParamDWord(SIP_PARSED_MESSAGE, (unsigned long) mes);
SendMessage(InviteClientTE_FSM_MBX);
msg = GetMsg(InviteClientTE_FSM_MBX);
return msg;
}
...
result outpuller
controller
progress
runner
FIGURE 5.9
The collaboration of objects necessary for the automatic execution of the CppUnit test suite.
Finally, we write the main module, named Main.cpp. This module creates
the collaboration of objects necessary to automatically execute the test suite
and report the results of its execution (Figure 5.9). The function main() per-
forms the following steps:
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TestRunner.h>
As the result of the automatic test suite execution, we get the following report
on the monitor:
ExampleTestCase::example : OK
OK(1)
Press any key to continue...
Additionally, we will get the log file with the following content:
Each record of the log file indicates date and time, message source and
destination, message type, message length, message coding type, the content
of the message (in hexadecimal code), timer operations, and state transition
information (e.g., “0 -> 1” means a transition from the state S0 to the state
S1). By looking at this particular log file, we see that the implementation
under test behaves as expected. But normally we do not look at the log file
if all test cases pass. The real value of the log file is that it is of great help in
localizing bugs if a test case fails. Additionally, we could use the log file to
check the internal operation of the implementation under test automatically
by the tester class. We skipped that step to keep the example simple enough.
5.5.2 Example 2
This example illustrates one of the steps in the integration testing of the
SIP-based softphone. Imagine that the SIP invite client transaction and the
pUA : UA _Test
Test Driver.
5:
r
8: sp(1
rsp X
(2 XX
00 )
)
)
ITE
INV
q(
re
0:
7: rsp(200)
3: 4: rsp(1XX)
6: rsp )
rs (1 1: req(INVITE) TE
p( XX VI
20 ) IN
0) eq(
2:r
FIGURE 5.10
The example of the integration testing collaboration.
transaction layer dispatcher have undergone complete unit testing. The next
normal step would be to integrate them into the final product. Furthermore,
imagine that TU and TPL are not yet developed. The only thing we can do
is to replace TU and TPL with their imitator classes, named UA_Test and
TLI_Test (TLI stands for Transport Layer Interface), respectively (see the
collaboration diagram in Figure 5.10).
The aim of this simple example is to check one particular interaction,
illustrated with the collaboration diagram in Figure 5.10. To achieve that
goal, we construct the class UA_Test that acts as a simple test driver and the
class TLI_Test that acts as a simple test stub. Both classes are derived from
the class FiniteStateMachine. The former class has a single state and a single
state transition, whereas the latter has two states and two state transitions.
The class UA_Test declaration file, named UA_Test.h, has the following
content:
#ifndef _UA_Test_FSM_
#define _UA_Test_FSM_
#include “../constants.h”
#include “../kernel/fsm.h”
#include “../message/message.h”
int cseq_number;
Message SIPMsg;
void SendInviteToTAL();
public:
enum States { STATE_INITIAL };
void Evt_Init_TIMER_TINV_EXP();
void Event_UNEXPECTED();
// FiniteStateMachine abstract functions
StandardMessage StandardMsgCoding;
MessageInterface *GetMessageInterface(uint32 id);
void SetDefaultHeader(uint8 infoCoding);
void SetDefaultFSMData();
void NoFreeInstances();
void Reset();
uint8 GetMbxId();
uint8 GetAutomate();
uint32 GetObject();
void ResetData();
public:
UA_Test();
~UA_Test();
void Initialize();
};
#endif
#include “UA_Test.h”
#include “../parser/smsgtypes.h”
#include “../parser/smsg.h”
#define SipMessageCoding 0x00
extern char* IPString(unsigned int addr, char* buf, int len);
UA_Test::UA_Test() : FiniteStateMachine(16, 2, 3) {}
UA_Test::~UA_Test() {}
void UA_Test::Initialize() {
SetState(STATE_INITIAL);
InitTimerBlock(TIMER_TINV,1,TIMER_TINV_EXPIRED);
InitEventProc(STATE_INITIAL,TIMER_TINV_EXPIRED,
(PROC_FUN_PTR)&UA_Test::Evt_Init_TIME_TINV_EXP);
InitUnexpectedEventProc(STATE_INITIAL,
(PROC_FUN_PTR)&UA_Test::Event_UNEXPECTED);
StartTimer(TIMER_TINV);
}
void UA_Test::Evt_Ini_TIMER_TINV_EXP() {
SendInviteToTAL();
}
void UA_Test::SendInviteToTAL(){
char temp[10];
char szHostName[255];
hostent* HostData;
uint8* recmsg;
sip_t *mes;
...
PrepareNewMessage(0x00,INVITE);
SetMsgToAutomate(TAL_Disp_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
AddParam((SIP_RAW_MESSAGE, SIPMsg.getLastMessage().length(),
(uint8*) SIPMsg.getLastMessage().c_str());
AddParamDWord((SIP_PARSED_MESSAGE, (unsigned long) mes);
SendMessage(TAL_Disp_FSM_MBX);
}
...
The function Initialize() sets the FSM initial state, initializes the timer
TIMER_TINV to a 1-sec delay, sets the state transition functions, and starts
the timer TIMER_TINV. When the timer expires, the state transition function
Evt_Init_TIMER_TINV_EXP() is called. This function sends the INVITE mes-
sage to the transaction layer dispatcher (TAL_Disp) by calling the function
SendInviteToTAL(), which is very similar to the one given in the Example 1
(see Section 5.5.1). Further on, the INVITE message is routed toward the test
stub class TLI_Test.
The class TLI_Test declaration file, named TLI_Test.h, has the following
content (parts that are not essential are omitted):
#ifndef _TLI_Test_FSM_
#define _TLI_Test_FSM_
#include “../constants.h”
#include “../kernel/fsm.h”
#include “../message/message.h”
TLI_Test();
~TLI_Test();
void Initialize();
};
#endif
#include “TLI_Test.h”
#define SipMessageCoding 0x00
extern char* IPString(unsigned int addr, char* buf, int len);
TLI_Test::TLI_Test() : FiniteStateMachine(16, 2, 3) {}
TLI_Test::~TLI_Test() {}
void TLI_Test::Initialize() {
char szHostName[255];
hostent* HostData;
SetState(STATE_INITIAL);
InitTimerBlock(TIMER_T2XX,2,TIMER_T2XX_EXPIRED);
InitEventProc(STATE_INITIAL,INVITE,
(PROC_FUN_PTR)&TLI_Test::Evt_Init_INVITE_T);
InitEventProc(STATE_1XX_SENT,TIMER_T2XX_EXPIRED,
(PROC_FUN_PTR)&TLI_Test::Evt_1XXSent_TIME_T2XX_EXP);
InitUnexpectedEventProc(STATE_INITIAL,
(PROC_FUN_PTR)&TLI_Test::Event_UNEXPECTED);
// Problem specific part
...
}
void TLI_Test::Evt_Init_INVITE_T() {
Send1XXToTAL();
StartTimer(TIMER_T2XX);
SetState(STATE_1XX_SENT);
}
void TLI_Test::Evt_1XXSent_TIMER_T2XX_EXP() {
Send2XXToTAL();
}
void TLI_Test::Send1XXToTAL(){
uint8* recmsg;
recmsg = GetParam(SIP_RAW_MESSAGE);
...
SIPMsg.makeResponse(“100”,“Trying”,responseBody,0);
PrepareNewMessage(0x00,RESPONSE_1XX_T);
SetMsgToAutomate(TAL_Disp_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
AddParamDWord((SIP_PARSED_MESSAGE, (unsigned long) mes);
SendMessage(TAL_Disp_FSM_MBX);
}
void TLI_Test::Send2XXToTAL(){
SIPMsg.makeResponse(“200”,“OK”,responseBody,0);
PrepareNewMessage(0x00,RESPONSE_2XX_T);
SetMsgToAutomate(TAL_Disp_FSM);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(0);
AddParamDWord((SIP_PARSED_MESSAGE, (unsigned long) mes);
SendMessage(TAL_Disp_FSM_MBX);
}
...
The function Initialize() sets the initial state, initializes the timer
TIMER_T2XX to a 2-sec delay, sets the state transition functions, and finishes
with some problem-specific initializations. The state transition function
Evt_Init_INVITE_T(), triggered with the reception of the message INVITE,
sends the preliminary response 100 (Trying) by calling the function
Send1XXToTAL(), starts the timer TIMER_T2XX, and changes its state to
S TAT E _ 1 X X _ S E N T. The state transition function
Evt_1XXSent_TIMER_T2XX_EXP(), triggered with the expiration of the
timer TIMER_T2XX, sends the final response 200 (OK) by calling the function
Send2XXToTAL().
The content of the main module, named test_main.cpp, is the following
(parts that are not essential are omitted):
#include <conio.h>
#include “kernel/fsmsystem.h”
#include “kernel/logfile.h”
#include “NewSIP/TAL_Disp.h”
#include “Test/UA_Test.h”
#include “Test/TLI_Test.h”
#include “NewSIP/InvClientTE.h”
FSMSystemWithTCP *pSys;
LogFile *lf;
TAL_Disp* pTALDisp;
TLI_Test* pTLI;
UA_Test* pUA;
InviteClientTE* pInviteCltTE[NUMBER_OF_TES];
DWORD thread_id;
HANDLE thread_handle;
...
DWORD WINAPI SystemThread(void *data){
FSMSystem *sysAutomate = (FSMSystem *)data;
sysAutomate->Start();
return 0;
}
int init(){
pSys = new FSMSystemWithTCP(11,11);
pTALDisp = new TAL_Disp();
pTLI = new TLI_Test();
As the result of the execution of the main module, we get the log file with
nine records that correspond to the messages that are exchanged between
implementations under test (transaction layer dispatcher and SIP invite cli-
ent transaction) and test driver (UA_Test) and test stub (TLI_Test). This file
is very similar to the one given in Example 1 (see Section 5.5.1) but three
times longer, hence not included here.
Test automation of integration tests based on log files is possible for simple
collaborations like the one shown in this example, although it may be cum-
bersome. However, if we must deal with more complex collaborations that
evolve concurrently, this approach is hardly applicable. Using log files in
such situations would normally require human intervention for checking
the results of the integration tests. Generally, we should try to use the style
of unit testing based on automatic checking of results (see Section 5.1), even
for the integration of the parts of the system.
References
Berard, B., Bidoit, M., Finkel, A., Laroussinie, F., Petit, A., Petrucci, L., Schnoebelen,
Ph., and McKenzie, P., Systems and Software Verification: Model-Checking Tech-
niques and Tools, Springer-Verlag, Berlin, 2001.
Newborn, M., Automated Theorem Proving , Springer-Verlag, New York, 2001.
Popovic, M., Atlagic., B., and Kovacevic, V., “Case study: A maintenance practice
used with real-time telecommunication software,” Journal of Software Mainte-
nance and Evolution: Research and Practice , John Wiley & Sons, West Sussex, No.
13, pp. 97, 2001.
Popovic, M. and Velikic, I., “A generic model-based test case generator,” Proc. IEEE
International Conference and Workshop on Engineering of Computer Based Systems ,
Greenbelt, MD, April 4–7, 2005.
Woit, D.M., “Operational profile specification, test case generation, and reliability
estimation for modules,” Ph.D. thesis, Queens University Kingstone, Ontario,
Canada, February 1994.
6
FSM Library
6.1 Introduction
The FSM library described in this book is created to be used as a working
environment for the implementation of groups of communication protocols.
The programmer has two basic classes at his or her disposal, namely, FSM-
System and FiniteStateMachine. The class FSMSystem models a platform for
a group of communication processes (otherwise called finite state machines
or automata). An instance of this class interconnects individual communi-
cation processes by handling all of the resources needed for the operation
of individual finite state machines.
The class FiniteStateMachine models a generic communication process (i.e.,
communication protocol). Each individual communication protocol is rep-
resented by an instance of this class. The implementation of a particular
communication protocol is narrowed down to writing state-transition func-
tions in C++. The transition function comprises procedures that process the
message received in a given FSM state. This processing results in a transition
to a new FSM state and the optional generation of the corresponding out-
going messages. All state transition functions must be defined for all finite
state machines registered to a single FSM system (an instance of the class
FSMSystem). Additionally, all the FSM system run-time elements must be
initialized properly before it can be successfully started.
The relationship between the classes FSMSystem and FiniteStateMachine is
a symbiosis — one cannot operate without the other. The FSM system clearly
represents just an infrastructure, or an unused platform. In reality, an FSM
307
system is always used so that at least a couple of finite state machines are
registered to it, together representing a group of finite state machines.
Because of that and to achieve simplicity and brevity, we frequently use the
term “FSM system” as a synonym for the group of automata, assuming that
some individual automata are actually registered to it, and vice versa.
Although an instance of the class FiniteStateMachine cannot operate on its
own, we simply refer to it as a “finite state machine.”
• FiniteStateMachine
• FSMSystem
In the list above, the idiom “FSM system” represents an instance of the class
FSMSystem.
The first of the overloaded functions above is used to add the first finite
state machine of each type. The other instances of the same type are added
using the second function.
The initialization of the FSM system kernel is performed by calling the
following function:
[AUTOMATA]
1=AUTOMATA1_FSM
2=AUTOMATA2_FSM
SequenceNumber=AUTOMATA_TYPE
[MESSAGES]
0=0xe000,MSG_1,0
1=0xe002,MSG_2,0
SequenceNumber=MSG_CODE,TEXT_TITLE,0
#define NO_BUFFERS 3
#define NO_AUTOMATA_1 5
#define NO_AUTOMATA_2 9
...
// Create FSM system that has two automata types and uses
// two mailboxes (one mailbox per each automata type)
FSMSystem *fsmSystem = new FSMSystem(2,2);
fsmSystem->Add(&automata2[0],AUTOMATA2_FSM,NO_AUTOMATA_2,true);
// Initialize kernel
fsmSystem->InitKernel(buffClassNo,buffersCount,buffersLength,2);
// Create and set logging system (log file name, message definition file)
lf = new LogFile(“log.log”, “log.ini”);
LogAutomataNew::SetLogInterface(lf);
...
The example above starts with the definition of the number of buffer types.
In this example, three buffer types are defined (i.e., small, medium, and large
buffers) by setting the symbolic constant NO_BUFFERS value to 3. Next, we
define the number of instances of two automata types by setting the values
of symbolic constants NO_AUTOMATA_1 to 5 and NO_AUTOMATA_2 to 9.
This means that five instances of the first automata type and nine instances
of the second automata type will exist in the group of automata we are going
to create.
Next, the program paragraph defines the number of buffers, as well as
their size, for each buffer type. Fifty small buffers of size 128 bytes, thirty
medium buffers of size 256 bytes, and twenty large buffers of size 512 bytes
would be used. The number of buffer types is stored in the variable buff-
ClassNo. The number of buffers of each type and their lengths are stored in
the arrays buffersCount and buffersLength.
We then create the FSM system by calling the constructor of the class
FSMSystem. This constructor has two parameters: the number of automata
types and the number of mailboxes to be used by the system for its own
purposes. Next, we create two groups of automata of two different types. In
the program, these groups are represented as arrays of instances of classes,
namely, the classes Automata1 and Automata2. In this example, we assume
that these classes have already been defined by extending the base class
FiniteStateMachine.
After creating two groups of automata of different types, all the automata
are added to the already created FSM system. The first instance of each
automata type is added by calling the overloaded function Add with the first
type of signature, which specifies the instance address, the instance type, the
total number of instances of this type, and the indicator specifying if a list
of free automata of this type exists or not. The rest of the instances are added
by calling the overloaded function Add with the second type of signature,
specifying just the instance address and its type.
The first automata type in this example does not have a list of free auto-
mata, whereas the second type does have a list of free automata. This means
that the instance of the second automata type can be viewed as a pool of
resources of the same type. They may be dynamically allocated to be engaged
in a certain communication scenario. When a programmer decides to use
this opportunity, he must provide the function NoFreeInstance, which is called
In the example above, we start the FSM system by calling its function Start
from the thread function FsmSystemThreadFunction. We assume that thread
has already been created and that its identification is stored in the variable
fsmSystemThreadId.
// This function returns the message interface for the given interface ID.
// It is assumed that standardMsgCoding is defined as:
// StandardMessage standardMsgCoding;
MessageInterface *Automata::GetMessageInterface(uint32 id){
switch(id){
case 0x00:
return &standardMsgCoding;
// Other definitions
// case 0x01:
// case 0x02:
}
throw TErrorObject(__LINE__,__FILE__,0x01010400);
}
// This function returns the number (ID) which identifies the automata
// type defined by this class.
uint8 Automata::GetAutomate(){
return AUTOMATA_TYPE_ID;
InitTimerBlock(T200,T200_VALUE,T200_CODE);
}
In the example above, we would like to create the class Automata that
models one type of finite state machines (automata). The definition of the
class comprises the definitions of its function members. The function member
GetMessageInterface returns the object that embodies the coding of the mes-
sages to be used by the instances of the class Automata. In this example, it
is an instance of the class StandardMessage.
The member function SetDefaultHeader is used to automatically fill in the
message header defaults. Normally, these are the data about the automata
instance that has created the message to send to some other automata
instance. In this example, it uses the function SetMsgInfoCoding to specify
the type of the coding to be applied. It also uses the function SetMessage-
FromData to specify the type of the originating automata instance, the iden-
tification of the group to which the automata instance belongs, and the
identification of the originating automata instance.
The member function GetMbxId returns the identification of the mailbox
used by the automata instance of this type. In this example, it is the value
of the symbolic constant AUTOMATA_MBX_ID. The member function
GetAutomata returns the identification of the automata type. It is the value
of the symbolic constant AUTOMATA_TYPE_ID. The member function Set-
DefaultFSMData is used by the automata instance to set its specific data
before it commences its normal operation. In this example, attribute1 is set
to the value VALUE_1 and attribute2 is set to the value VALUE_2.
The member function NoFreeInstances can be used to specify the action to
be performed if no more free automata instances of this type are found, e.g.,
to make a small system restart, allocate some additional automata instances,
and so on. This mechanism is available to the programmer if the instances
of automata have been added (function Add) to the FSM system with the
parameter useFreeList set to the value true.
The member function Initialize is used to define automata state transition
functions and timers (referred to as timer blocks throughout the FSM library
documentation) to be used by the automata. The FSM library distinguishes
two types of events, expected and unexpected, and allows the programmer
to specify the corresponding event handlers, which are just specialized C++
functions. These handlers are defined by calling the registration functions,
namely, the function InitEventProc for the expected events and the function
InitUnexpectedEventProc for the unexpected events. The parameters of both
of these functions specify the state code, the event (message) code, and the
pointer to the event handler.
In this example, we have defined seven automata state transition functions
altogether, five of them triggered by the expected events and two triggered
by the unexpected events. The part of the automata shown in the example
has two states, IDLE and SEND. The expected events in the state IDLE are
MSG_SEND, MSG_RCV, and T200_CODE. The corresponding event han-
dlers are Idle_MsgSend, Idle_MsgReceive, and T200Expired, respectively. Two
legible events exist in the state SEND, MSG_NEW and MSG_END. The
corresponding handlers are Send_MsgNew and Send_MsgEnd. The unex-
pected event handler for the state IDLE is Idle_Unexpected whereas for the
state SEND it is Send_Unexpected. The corresponding state transition table is
shown in Table 6.1.
TABLE 6.1
Example of a State Transition Table
MSG_RCV MSG_SEND T200_CODE MSG_NEW MSG_END ?
Idle Idle_MsgReceive Idle_MsgSend T200Expired Idle_
Unexpected
Send Send_ Send_ Send_
MsgNew MsgEnd Unexpected
The timers are initialized by calling the function InitTimerBlock. The para-
meters of this function specify the unique timer identification, its duration
(as the number of basic timer resolution units), and the code of the message
sent when the timer expires. In the example above, these are the symbolic
constants T200, T200_VALUE, and T200_CODE.
To sum, automata states and attributes are defined in accordance with the
problem at hand. The state transition function, referred to as the event
handler, is called upon the reception of a given message in a given state, as
defined by the function Initialize. Each event handler is defined as a class
member function responsible for handling a given event.
The timers to be used by the automata are defined also by the function
Initialize. This is done by calling the function InitTimerBlock, which in turn
creates the internal kernel timer block (essentially a program object) and fills
in its identification, duration, and the corresponding timer message code.
The function InitTimerBlock is used to define (initialize) the timer. Its para-
meters specify the unique timer identification, its duration as a multiple of
the basic timer resolution unit, and the code of the message sent to the
automata mailbox when the timer expires. This is explained in the previous
section. Notice that each timer has the unique identification tmrId used as a
parameter of all the API functions to identify the timer.
Each API function represents a primitive timer operation. The function
StartTimer is used to start the timer, the function StopTimer stops the timer,
the function RestartTimer restarts the timer, and the function IsTimerRunning
is used to check if the timer is running or not.
The following example illustrates the usage of these primitives:
if(!IsTimerRunning(T200)){
StartTimer(T200);
}
else
StopTimer(T200);
...
Besides the memory allocation (malloc) and free primitives, two additional
primitives provide the information about the buffer already allocated to the
finite state machine. The function IsBufferSmall checks if the buffer size is
smaller than the value of its parameter. If yes, it returns true, otherwise, it
returns false. Another function, named GetBufferLength, returns the buffer
size in octets (bytes).
The following example illustrates the usage of the buffer management
primitives:
uint32 bufferLength;
uint8 *pointer = GetBuffer(100);
if((IsBufferSmall(pointer,129)){
RetBuffer(pointer);
pointer = GetBuffer(129);
}
if((pointer != NULL))
bufferLength = GetBufferLength(pointer);
...
In the example above, we first define two buffer types — small and large
— by calling the function InitKernel. Its fourth parameter (noMBX, the num-
ber of the mailboxes) is not relevant for this example. The rest of the program
illustrates the usage of FSM library’s buffer management functions. First,
the program asks for a buffer not smaller than 100 bytes, then it checks if
this buffer is smaller than 129 bytes. If yes, it returns the allocated buffer
and requests a new one not smaller than 129 bytes (in this example, it will
get one large buffer of size 256 bytes). At the end, the program checks if the
pointer is defined, which also means that it points to a certain buffer. If it is
defined, the program asks for its size by calling the function GetBufferLength.
typically assigned to the individual automata. The message sent from the
originating automata instance towards the destination automata instance is
placed temporarily in the mailbox assigned to the destination automata
instance. There it waits to be taken over and subsequently processed by the
destination automata instance (process).
As already mentioned, a mailbox is a message queue that can contain
messages for any automata type, thus it does not need to be assigned to
some particular automata type. In contrast to a typical paradigm, it can be
used as a general message queue shared by more destination automata.
Essentially, in such a paradigm the source automata instance can put the
message in any mailbox hosted by the FSM system and it will eventually be
delivered to its proper destination.
This message routing and delivery is performed automatically by the FSM
system and is hidden from the automata, which are just service users. The
FSM system has an abstraction of the mailbox from which it takes messages,
one at a time (mailbox abstraction provides buffering functionality by
employing the FIFO memory type). Upon the reception of each individual
message, the FSM system consults the message header to determine the
destination automata instance and passes the message to it. The destination
automata instance looks up the message code and, based on the current
automata state, calls the appropriate automata state transition function.
Message reception is completely transparent for the programmer writing
the program code for the finite state machine. The above mechanism is
absolutely hidden from him. The programmer must simply accept that the
message reception and its classification are done automatically by the sys-
tem. He just writes the message processing functions that are called auto-
matically by the system upon the reception of the corresponding message.
The API functions can be partitioned into two groups:
The functions in the first group are used to provide the information about
the originating automata instance. The source of this information is the
message header and the values of the message parameters. The functions in
the second group provide primitives needed to make and send a message:
The messages may be sent only from a finite state machine or a FSM
system. Note that during normal system operation, a FSM system does not
send any messages. In this context, a finite state machine is an instance of
the class FiniteStateMachine, or a class derived from it, and an FSM system
is an instance of the class FSMSystem.
Example 1:
// Get parameter of type PARAM_1 from the received message.
// The size of PARAM_1 is WORD.
WORD word;
GetParamWord(PARAM_1,word);
pointer = GetParam(TEXT);
if(pointer != NULL){
// StandardMessage format: bytes 1 and 2 contain parameter name,
// byte 3 contains parameter length in bytes,
// byte 4 and further contain the parameter itself.
memcpy(text,pointer+3,*((pointer+2)));
The example above shows how the programmer can get a parameter from
the current message. A current message is the last message received by the
automata instance, i.e., it is the last message taken from the mailbox and
assigned to the automata instance for processing. The parameter size is
WORD (2 bytes). First, the programmer declares the variable word in which
he wants to store the parameter value.
The message can contain many parameters, therefore the programmer
must specify the unique identifier of the parameter he wants to get. In this
example, the identifier is the value of the symbolic constant PARAM_1.
Finally, a copy of the desired parameter is provided by calling the API
function GetParam. The first parameter of this function is the parameter
identifier (PARAM_1) and the second is the variable (word) in which the
desired parameter is to be copied.
The second part of the example above demonstrates how the programmer
may handle textual parameters of arbitrary size. The StandardMessage format
prescribes that the first 2 bytes of such a parameter are reserved for the
parameter name, the next byte is used for the parameter length (in bytes), and
the rest of the bytes in the parameter represent its value. The example shows
how a copy of such a parameter can be provided and how a null terminated
string can be constructed by adding the NULL character at its end.
Example 2:
...
// PrepareNewMessage parameters: buffer size and message type.
PrepareNewMessage(0xAA,MSG_NAME);
The example above shows a common way to construct and send a message.
The first step is to call the function PrepareNewMessage. The parameters of
this function specify the expected buffer size (0xAA in this example) and the
message name, which also specifies the message type (MSG_NAME).
Next, we fill in the message header by calling the following functions:
Example 3:
// Send a message from the FSM system.
uint8 *msg = GetBuffer(messageInfoLength+MSG_HEADER_LENGTH);
SetMsgFromAutomata(AUTOMATA_TYPE_FROM_ID,msg);
SetMsgFromGroup(INVALID_08,msg);
SetMsgObjectNumberFrom(automataFromId,msg);
SetMsgToAutomata(AUTOMATA_TYPE_TO_ID,msg);
SetMsgToGroup(INVALID_08,msg);
SetMsgObjectNumberTo(automataToId,msg);
SetMsgInfoCoding(0,msg);// 0 = StandardMessage
SetMsgCode(MSG_FROM_SYSTEM_AUTOMATA,msg);
SetMsgInfoLength(infoBufferLength,msg);
SendMessage(AUTOMATA_TO_MBX_ID,msg);
...
The example above shows how a message can be created and sent within
the FSM system. This process is done through the following steps:
Example:
// In processor 1 (server)
//
// Initialize kernel.
fsmSystem1->InitKernel(buffClassNo,buffersCount,buffersLength,2);
// In processor 2 (client)
//
// Set server TCP/IP parameters (port, IP address).
// Establish the connection.
fsmSystem2.setPort(5000);
fsmSystem2.setIP("192.168.77.77");
fsmSystem2.establishConnection();
...
This example shows the code excerpts for the TCP/IP server and client
machines, named processor 1 and processor 2. At startup, the server initial-
izes the FSM library kernel by calling the function InitKernel (its parameters
are the number of buffer types, their count, length, and the number of the
mailboxes to be used). Next, it calls the function InitTCPServer to start the
T C P / I P s e r v e r. We a s s u m e d i n t h i s e x a m p l e t h a t t h e c l a s s
NetFSM_Automata1 is derived from the class NetFSM.
Alternately, the client sets the TCP port number (5000) by calling the
function setPort and the IP address of the TCP server (192.168.77.77) by
calling the function setIP, and establishes the connection with the server by
calling the function establishConnection.
These functions are used to convert the internal message format (abbrevi-
ated as FSM) into external, or network message format (abbreviated as Net),
and vice versa. Normally, automata executing in the same processor
exchange internal messages coded in internal message format. However, this
message format is not suitable for transmission over the network. Most
commonly, the message must be serialized, i.e., transformed from data object
and structure form into the external message in accordance with a given
external message format. This is a series of bits, sometimes grouped in octets
or words, that are transmitted over the communication line.
The functions listed above are virtual functions and therefore the program-
mer must define them while he writes a class that is derived from the class
NetFSM. The message format conversion functions naturally read a message
from some input buffer, convert it into a requested format, and write the
output to an output buffer.
The function convertFSMToNetMessage is not intended to be used directly
by the communicating automata but rather to be called internally by the
FSM library kernel to convert an internal message into the external one before
it can be sent over the network. Therefore, the input of this function is the
internal message and its output is the corresponding output message. The
parameters of this function specify the pointer to the internal message fsm-
MessageS, its length fsmMessageLength, the pointer to the output, the external
message protocolMessageS, and its length sendMsgLength. The programmer
must specify the mapping algorithm by writing this function.
Symmetrically, the function convertNetToFSM is intended to be used by the
FSM library kernel to convert an external message received over the network
into an internal message representation, which must be delivered to the local
mailbox and processed further by the corresponding local automata. The
input of this function is the external message and the output is the internal
message. The parameters of this function specify the pointer to the external
message protocolMessageR, its length receivedMessageLength, the pointer to the
output, internal message fsmMessageR, and its length fsmMessageRLength.
The function getProtocolInfoCoding returns the code of the type of external
information coding. An instance of the class NetFSM, referred to as net
automata, initiates the transmission of the message across the TCP/IP net-
work by calling the function sentToTCP. This function may throw an excep-
tion in the case of an error, e.g., when net automata wants to send a message
after the TCP connection has been closed.
Example:
// PrepareNewMessage parameters: buffer size and message type
PrepareNewMessage(0xAA,MESSAGE_NAME);
The utility functions provided for the load-store manipulation with various
data types are the following:
The utility functions are provided to avoid cast operators in C/C++ pro-
grams because some microcontrollers do not allow word or double-word
memory access to odd memory addresses.
TABLE 6.2
FSMSystem Constructor Summary
FSMSystem(uint8 numOfAutomata, uint8 numberOfMbx)
The constructor initializes the object that represents the FSM system along with the data
structures needed for its proper operation.
TABLE 6.3
FSMSystem Member Functions Summary
Type Member Function
Void Add (ptrFiniteStateMachine object, uint8
automataType, uint32 numOfObjects, bool
useFreeList=false)
This function adds the first instance of each automata type to the
FSM system.
Void Add(ptrFiniteStateMachine object, uint8
automataType)
This function adds all the automata instances of the given type to
the FSM system, except for the first instance.
Void InitKernel(uint8 buffClassNo, uint32 *buffersCount,
uint32 *buffersLength, uint8 numOfMbxs=0,
TimerResolutionEnum timerRes=Timer1s)
This function initializes the elements of the kernel responsible for
time, buffer, and message management.
Void Remove(uint8 automataType)
This function removes all the instances of the given automata type
from the FSM system.
ptrFiniteState Remove(uint8 automataType, uint32 object)
Machine This function removes the given instance of the given automata
type.
Virtual void Start()
This function starts the FSM system.
Void StopSystem()
This function stops the FSM system.
TABLE 6.4
FSMSystemWithTCP Constructor Summary
FSMSystemWithTCP(uint8 numOfAutomata, uint8 numberOfMbx)
The constructor initializes the object that represents the FSM system supporting
communication over TCP/IP network along with the data structures needed for its
proper operation.
TABLE 6.5
FSMSystemWithTCP Member Functions Summary
Type Member Function
int InitTCPServer(uint16 port, uint8 automataType, char
*ipAddress=0, unsigned char *parm=0, int length=0)
This function initializes the TCP server. Once initialized, the server waits for
a request to establish the TCP connection with a remote client.
TABLE 6.6
FiniteStateMachine Constructor Summary
FiniteStateMachine(uint16 numOfTimers=DEFAULT_TIMER_NO, uint16
numOfState=DEFAULT_STATE_NO, uint16
maxNumOfProceduresPerState=DEFAULT_PROCEDURE_NO_PE_STATE, bool
getMemory=true)
This constructor initializes the object that represents the instance of a given automata
type along with the data structures needed for its proper operation.
6.8.1 FSMSystem
Function prototype:
FSMSystem(
uint8 numOfAutomata,
uint8 numberOfMbx)
Parameters:
numOfAutomata: the number of various automata types to be added to
the FSM system
numberOfMbx: the number of mailboxes to be used by the FSM system
TABLE 6.7
FiniteStateMachine Member Functions Summary
Type Member Function
uint8* AddParam(uint16 paramCode, uint32 paramLength,
uint8 *param)
This function is used to add the given parameter of the
given length to the new message.
uint8* AddParamByte(uint16 paramCode, BYTE param)
This function is used to add the given parameter of length
1 byte to the new message.
uint8* AddParamDWord(uint16 paramCode, DWORD param)
This function is used to add the given parameter of length
4 bytes to the new message.
uint8* AddParamWord(uint16 paramCode, WORD param)
This function is used to add the given parameter of length
2 bytes to the new message.
virtual void CheckBufferSize(uint32 paramLength)
This function provides a new message buffer with the size
sufficient to accept the parameter of the given length.
virtual void ClearMessage()
This function returns the buffer allocated for the current
message to the kernel and assigns value NULL to the
internal pointer to the current message. The current
message is the last message received by the automata
instance.
virtual void CopyMessage()
This function makes a copy of the current message and
assigns that copy to the new message.
virtual void CopyMessage(uint8 *msg)
This function makes a copy of the given message and
assigns that copy to the new message.
virtual void CopyMessageInfo(uint8 infoCoding, uint16
lengthCorrection=0)
This function copies the part of the message containing the
useful information, referred to as a payload (message
without its header), from the current to the new message.
virtual void Discard(uint8* buff)
This function deletes the message placed in the given buffer
and returns the buffer to the kernel.
void DoNothing()
This function performs no operation. It is called when the
automata receives an unexpected message unless a new
function is provided to handle unexpected messages.
void Free FSM()
This function reports to the FSM system that the automata
instance has finished its current assignment and is free for
further assignments.
virtual uint8 GetAutomata()=0
This function returns the identification of the automata type
for this automata instance.
TABLE 6.8
NetFSM Constructor Summary
NetFSM(uint16 numOfTimers=DEFAULT_TIMER_NO, uint16
numOfState=DEFAULT_STATE_NO, uint16
maxNumOfProceduresPerState=DEFAULT_PROCEDURE_NO_PER_STATE, bool
getMemory=true)
The constructor initializes the object that represents an instance of the given automata
type along with the data structures needed for its proper operation.
mailbox to receive the messages from network interfaces (drivers) and the
second to receive the messages from TCP. Yet another arrangement would
be to assign a single mailbox to all the protocols. Finally, a set of mailboxes
can be used to prioritize the messages. For example, three mailboxes may
be used to distinguish high, middle, and low priority messages.
TABLE 6.9
NetFSM Member Functions Summary
Type Member Function
virtual void convertFSMToNetMessage()
This function converts the internal message format into the
external message format appropriate for the transmission over
the TCP/IP network.
virtual uint16 convertNetToFSMMessage()
This function converts the external message format into the
internal message format appropriate for the communication
within the FSM system.
void establishConnection()
This function establishes the TCP connection between two
geographically distributed FSM systems.
virtual uint8 getProtocolInfoCoding()
This function returns the identification of the type of the
external message coding.
void sendToTCP()
This function sends the new message to the remote FSM system
over the previously established TCP connection.
Function description: This function adds the first instance of each automata
type to the FSM system. At the same time, this function defines the unique
identification of this automata type and the number of instances of this
automata type that will be subsequently added to the FSM system. It also
declares a group of instances of this automata type as either a set of resources
to be used individually or as a pool of resources of the same type available
for dynamic allocation.
Function parameters:
object: the pointer to the first instance of this automata type to be added
to the FSM system
automataType: the unique identification of this type of automata
numberOfObjects: the total number of instances of this type to be added
to the FSM system.
useFreeList: the indicator selecting the mode of usage of individual in-
stances of this type.
Note: Typically, the FSM system is created at system startup and then
groups of various automata types are added to it. As a rule, the first instance
of the given automata type is added by this function. Its parameters specify,
in order from left to right, the pointer to the first object of this type, the
identification of this automata type, the total number of instances that will
be added to the FSM system, and the mode of individual instance allocation.
This last parameter has a default value false, which means that each automata
instance represents an individual resource. If this default is overridden by
the value true, the group of instances of this automata type represents a pool
of resources of the same type. The individual instances from this pool are
allocated dynamically and on-demand, based on the use of the internal
FSMSystem library kernel list of resources of the given type. (This is the
origin of the name of the last parameter of this function, useFreeList.) This
dynamic allocation is requested by sending a message to an unknown auto-
mata, which is identified by the instance identification set to the value –1
(see function SetMsgObjectNumberTo).
Function description: This function adds all the automata instances except
the first instance of the given type to the FSM system. It assumes that the
first instance of this automata type has been added previously to the FSM
system by calling the overloaded function Add with four parameters in its
signature.
Function parameters:
object: the pointer to the instance of this automata type to be added to
the FSM system
automataType: the unique identification of this automata type
6.8.4 InitKernel
Function prototype:
void InitKernel(
uint8 buffClassNo,
uint32 *buffersCount,
uint32 *buffersLength,
uint8 numOfMbxs=0,
Function parameters:
buffClassNo: the number of buffer types
buffersCount: the pointer to the array of the numbers of instances of the
corresponding buffer types
buffersLength: the pointer to the array of the sizes of the corresponding
buffer types
numOfMbxs: the number of the mailboxes
timerRes: the basic timer resolution
Finally, the programmer should specify the pointers to these two arrays
as the second and the third parameter of this function.
6.8.5 Remove(uint8)
Function prototype:
void Remove(unit8 automataType)
Function parameters:
automataType — the type of automata to be removed from the system
Note: First, the FSM system removes all instances of the given automata
type from the FSM system. Next, the kernel frees all the memory zones
occupied by the internal data structures used by the automata of this type.
Function description: This function removes the given instance of the given
automata type. The parameters of this function specify the identification of
the automata type and the identification of the automata instance.
Function parameters:
automataType: the identification of the automata type
object: the identification of the instance of the given automata type
6.8.7 Start
Function prototype:
virtual void Start()
Function description: This function starts the FSM system and is the main
function of the FSM system. In this function, the FSM system thread enters
a loop in which it reads the kernel mailboxes and distributes the messages
to the destination automata.
Note: The FSM system thread remains in the loop while the internal
attribute SystemWorking is set to the value true. A typical implementation of
the FSM system thread is shown in the example in Section 6.2.1.2.
6.8.8 StopSystem
Function prototype:
void StopSystem()
Function description: This function stops the FSM system. It sets the
internal attribute SystemWorking to the value false, thus causing the FSM
system thread to exit its loop and stop the FSM system.
Note: If the function Start has been called from the separate operating
system thread, the call to the function StopSystem will cause the termination
of that thread.
6.8.9 FSMSystemWithTCP
Function prototype:
FSMSystemWithTCP(
uint8 numOfAutomata,
uint8 numberOfMbx)
Function parameters:
numOfAutomata: the number of automata types that will be added to the
FSM system
numberOfMbx: the number of mailboxes that will be used by the auto-
mata added to the FSM system
6.8.10 InitTCPServer
Function prototype:
int InitTCPServer(
uint16 port,
unit8 automataType,
char *ipAddress = 0,
unsigned char *parm = 0,
int length = 0)
Function description: This function initializes the TCP server. Once initial-
ized, the server waits for a request to establish the TCP connection with a
remote client. The parameters of this function specify the number of the TCP
port on which the server awaits the connection request, the automata type
Function parameters:
Port: the number of the TCP port on which the server awaits a connec-
tion request
automataType: the automata type included in the FSM system that is
engaged in the communication. This automata type must be derived
from the class NetFSM. After the connection has been initially
estbalished, the server transfers it to the allocated instance of this
automata type.
ipAddress: the pointer to the server IP address
parm: the pointer to the area where the parameters received while es-
tablishing the connection should be passed and subsequently taken
by to the specified automata type
length: the parameter lengths specified by the previous pointer, in bytes
Function returns: If the TCP server awaiting a request from a remote client
is successfully started, this function returns the value 0. Otherwise, it returns
the value –1.
Note: This function should be called only once, just initially to start the
TCP server.
6.8.11 FiniteStateMachine
Function prototype:
FiniteStateMachine(
unit16 numOfTimers = DEFAULT_TIMER_NO,
uint16 numOfState = DEFAULT_STATE_NO,
uint16 maxNumOfProceduresPerState = DEFAULT_PROCEDURE_NO_PER_STATE,
bool getMemory = true)
type or not. The default value of this indicator is true, which means that this
constructor is responsible for memory allocation.
Function parameters:
numOfTimers: the number of the timers to be used by this automata type
numOfState: the number of the states that this automata type has
maxNumOfProceduresPerState: the maximal number of state transitions
per state
getMemory: the memory allocation indicator (by default, its value is true)
Note: This constructor may be called either with some or without any of
the parameters. If the parameter is not specified, the constructor will use its
default value. The indicator getMemory may be set to the value false when
the programmer wants to do manual memory allocation to optimize overall
memory consumption.
6.8.12 AddParam
Function prototype:
uint8 *AddParam(
uint16 paramCode,
uint32 paramLength,
uint8 *param)
Function parameters:
paramCode: the parameter type
paramLength: the parameter length, in bytes
param: the pointer to the parameter
Function returns: This function returns the pointer to the buffer that con-
tains the new message.
Note: This function enables the programmer to add a parameter of an
arbitrary size to the new message with the limitation that it must not exceed
the maximal parameter length specified for the given type of message coding
(e.g., for the type StandardMessage, the maximal parameter length is 256
bytes). The message parameters in StandardMessage are sorted in the ascend-
ing order of their corresponding type identifiers.
6.8.13 AddParamByte
Function prototype:
uint8 *AddParamByte(
uint16 paramCode,
BYTE param)
Function parameters:
paramCode: the parameter type
param: the parameter value
Function returns: This function returns the pointer to the buffer that con-
tains the new message.
Note: The total message length must not exceed the limit specified for the
given type of message coding. In any case, it must not exceed 8G bytes.
6.8.14 AddParamDWord
Function prototype:
uint8 *AddParamDWord(
uint16 paramCode,
DWORD param)
Function parameters:
paramCode: the parameter type
param: the parameter value
Function returns: This function returns the pointer to the buffer that con-
tains the new message.
Note: The total message length must not exceed the limit specified for the
given type of message coding. In any case, it must not exceed 232 bytes.
6.8.15 AddParamWord
Function prototype:
uint8 *AddParamDWord(
uint16 paramCode,
WORD param)
Function parameters:
paramCode: the parameter type
param: the parameter value
Function returns: This function returns the pointer to the buffer that con-
tains the new message.
Note: The total message length must not exceed the limit specified for the
given type of message coding. In any case, it must not exceed 8G bytes.
6.8.16 CheckBufferSize
Function prototype:
uint8 *CheckBufferSize(uint32 paramLength)
Function parameters:
paramLength: the parameter length
Function returns: This function returns the pointer to the new message.
Note: This function is obsolete. In the previous version of the FSM library,
this function ensured the new message buffer management was transparent
to the programmer. Typically, the programmer would call this function
before calling some of the AddParam functions to ensure that the new mes-
sage is stored in a buffer of a sufficient size. This means that the buffer is
large enough to accept a new parameter of the given size in addition to the
current content of the new message. Behind the scenes, this function checked
the current size of the new message. If it was not sufficient, the function
allocated a new, larger buffer, copied the current new message into it,
released the old buffer, and returned the pointer to the newly allocated buffer
containing the new message. In the current version of the FSM library, all
the AddParam functions call this function internally at their very beginning
and the programmer need no longer call it explicitly.
6.8.17 ClearMessage
Function prototype:
virtual void ClearMessage()
Function description: This function returns the buffer allocated for the
current message to the kernel and assigns the value NULL to the internal
pointer to the current message. The current message is the last message
received by the automata instance.
Note: If the FSMSystem library has been compiled for the debug mode,
this function will additionally verify that the return value of the function is
NULL.
6.8.18 CopyMessage( )
Function prototype:
virtual void CopyMessage()
6.8.19 CopyMessage(uint*)
Function prototype:
virtual void CopyMessage(uint8 *msg)
Function parameters:
msg: the pointer to the original message
Note: This function assumes that the new message does not exist, i.e., the
internal pointer to the new message should contain the value NULL before
this function is called. However, if the new message already exists, this
function will return its buffer and get a fresh buffer for the new message
before copying the given message into it.
6.8.20 CopyMessageInfo
Function prototype:
virtual void CopyMessageInfo(
uint8 infoCoding,
uint16 lengthCorrection = 0)
Function description: This function copies the part of the message con-
taining the useful information, referred to as a payload (message without
its header), from the current into the new message stored in a newly
allocated buffer. The parameters of this function specify the type of the
information coding that governs the formatting and length correction of
the message.
Function parameters:
infoCoding: the identification of the type of the information coding
lengthCorrection: the message length correction
Note: The message length correction depends on the type of applied infor-
mation coding. If the new message buffer does not exist, this function will
get a buffer, assign it to the new message, and make the required copy.
6.8.21 Discard
Function prototype:
virtual void Discard(uint8* buff)
Function description: This function deletes the message placed in the given
buffer and returns the buffer to the kernel. The parameter of this function
specifies the buffer to be cleared and released.
Function parameters:
buff: the pointer to the buffer
6.8.22 DoNothing
Function prototype:
void DoNothing()
6.8.23 FreeFSM
Function prototype:
void FreeFSM()
6.8.24 GetAutomata
Function prototype:
virtual uint8 GetAutomata() = 0
6.8.25 GetBitParamByteBasic
Function prototype:
unit8 GetBitParamByteBasic(
uint32 offset,
uint32 mask=MASK_32_BIT)
Function description: This function returns the value of the current mes-
sage parameter of length 1 byte masked with the given mask. The parameters
of this function specify the offset of the original parameter of the message
and the value of the mask.
Function parameters:
offset: the offset of the original parameter of the message
mask: the value of the mask
Function returns: This function returns the result of the bit-wise AND
operation between the value of the message parameter at the given message
offset and the given value of the parameter mask.
Note: Normally, depending on the value of the parameter mask, testing
the value of a single bit, or of a group of bits simultaneously, is possible in
the parameter of size 1 byte that is at a given distance from the beginning
of the message.
6.8.26 GetBitParamWordBasic
Function prototype:
unit8 GetBitParamWordBasic(
uint32 offset,
uint32 mask=MASK_32_BIT)
Function description: This function returns the value of the current mes-
sage parameter of length 2 bytes masked with the given mask. The para-
meters of this function specify the offset of the original parameter of the
message and the value of the mask.
Function parameters:
offset: the offset of the original parameter of the message
mask: the value of the mask
Function returns: This function returns the result of the bit-wise AND
operation between the value of the message parameter at the given message
offset and the given value of the parameter mask.
Note: Normally, depending on the value of the parameter mask, testing
the value of a single bit, or a group of bits simultaneously, is possible in the
parameter of size 2 bytes that is at a given distance from the beginning of
the message.
6.8.27 GetBitParamDWordBasic
Function prototype:
unit8 GetBitParamDWordBasic(
uint32 offset,
uint32 mask=MASK_32_BIT)
Function description: This function returns the value of the current mes-
sage parameter of length 4 bytes masked with the given mask. The para-
meters of this function specify the offset of the original parameter of the
message and the value of the mask.
Function parameters:
offset: the offset of the original parameter of the message
mask: the value of the mask
Function returns: This function returns the result of the bit-wise AND
operation between the value of the message parameter at the given message
offset and the given value of the parameter mask.
Note: Normally, depending on the value of the parameter mask, testing
the value of a single bit, or of a group of bits simultaneously, is possible in
the parameter of size 4 bytes that is at a given distance from the beginning
of the message.
6.8.28 GetBuffer
Function prototype:
virtual uint8 *GetBuffer(uint32 length)
Function description: This function returns a buffer whose size is not less
than the size given by the value of its parameter. The parameter of this
message specifies the minimal buffer length in bytes.
Function parameters:
length: the buffer length
Now let us go back to the buffer allocation algorithm. When this function
finds a buffer type of a sufficient size, it checks for a free buffer of that
type. If no such type is found, the system is badly designed and a new
buffer type must be added to the system. If such a buffer type exists but
no free buffers of that type are available, the function will look for the
next size buffer. If all the buffers of the sufficient size are already allocated,
the FSM system experiences the memory exhaustion problem. In academic
examples, the system is allowed to crash under these circumstances.
However, industrial-strength applications require implementation of
additional mechanisms, such as overload protection and intelligent auto-
matic restarts.
6.8.29 GetBufferLength
Function prototype:
uint32 GetBufferLength(uint8 *buff)
Function description: This function returns the size of the given buffer in
bytes. The parameter of this function specifies the pointer to the buffer.
Function parameters:
buff: the address of the buffer
Function returns: This function returns the specified buffer length in bytes.
6.8.30 GetCallId
Function prototype:
virtual inline uint32 GetCallId()
6.8.31 GetCount
Function prototype:
uint32 GetCount(uint8 mbx)
Function parameters:
mbx: the mailbox identification
6.8.32 GetGroup
Function prototype:
virtual uint8 GetGroup()
6.8.33 GetInitialState
Function prototype:
virtual uint8 GetInitialState()
6.8.34 GetLeftMbx
Function prototype:
virtual inline uint8 GetLeftMbx()
Function returns: This function returns the number that uniquely identifies
the default mailbox assigned to the left automata instance.
Note: Historically, the terms left and right automata instance originate from
SDL, where an automata instance typically communicates with its left and
right neighbors. These neighbors might have their own mailboxes, some-
times briefly called left and right mailboxes.
6.8.35 GetLeftAutomata
Function prototype:
virtual inline uint8 GetLeftAutomata()
6.8.36 GetLeftGroup
Function prototype:
virtual linline uint8 GetLeftGroup()
6.8.37 GetLeftObjectId
Function prototype:
virtual inline uint32 GetLeftObjectId()
6.8.38 GetMbxId
Function prototype:
virtual uint8 GetMbxId()
6.8.39 GetMessageInterface
Function prototype:
virtual MessageInterface *GetMessageInterface(uint32 id) = 0
Function description: This function returns the object that governs the
coding of messages used by this automata instance. The parameter of this
function specifies the identification of the information coding scheme. The
returned object is an instance of the class derived from the class Message-
Interface.
Function parameters:
id: the information coding scheme
Function returns: This function returns the pointer to the object responsible
for parsing and coding the messages used by this automata instance.
Note: This function is a virtual function, which means that it must be
defined when the programmer writes a class derived from the class Finite-
StateMachine. The identification with the value 0 is reserved for the informa-
tion coding used by the format of the class StandardMessage, which is a basic
type of a message supported by the FSMSystem library.
6.8.40 GetMsg( )
Function prototype:
uint8* GetMsg()
Function description: This function returns the first unread message from
the mailbox assigned to this automata instance.
Function returns: This function returns a pointer to the buffer that has been
removed from the head of the list, which is hidden by the abstraction of the
mailbox assigned to this automata instance. If no such buffer exists, i.e., if
the list is empty, the function returns the value NULL.
6.8.41 GetMsg(uint8)
Function prototype:
static uint8* GetMsg(uint8 mbx)
Function description: This function returns the first unread message from
the given mailbox. The parameter of this function specifies the identification
of the mailbox.
Function parameters:
mbx: the mailbox ID
Function returns: This function returns the pointer to the buffer that has
been removed from the head of the list, which is hidden by the abstraction
of the given mailbox. If no such buffer exists, i.e., if the list is empty, the
function returns the value NULL.
Note: Although this function is defined as a static function, a call to this
function is not allowed before the kernel initialization and the FSM system
startup. The call to this function made before that may cause unpredictable
behavior.
6.8.42 GetMsgCallId
Function prototype:
inline uint32 GetMsgCallId()
6.8.43 GetMsgCode
Function prototype:
inline uint16 GetMsgCode()
Function description: This function returns the message code from the
current message header.
Function returns: This function returns the value of the message code from
the header of the current (last received) message.
6.8.44 GetMsgFromAutomata
Function prototype:
inline uint8 GetMsgFromAutomata()
6.8.45 GetMsgFromGroup
Function prototype:
inline uint8 GetMsgFromGroup()
6.8.46 GetMsgInfoCoding
Function prototype:
inline uint8 GetMsgInfoCoding()
6.8.47 GetMsgInfoLength( )
Function prototype:
inline uint16 GetMsgInfoLength()
Function description: This function returns the payload length of the cur-
rent message in bytes.
Function returns: This function returns the value of the current message
payload size in bytes.
Note: The length of the message header is not included in the length
returned by this message. By definition, the total message length is the sum
of the length of the message header and the length of the message payload.
6.8.48 GetMsgInfoLength(uint8*)
Function prototype:
inline uint16 GetMsgInfoLength(uint8 *msg)
Function description: This function returns the payload length of the given
message in bytes. The parameter of this function specifies the pointer to the
message.
Function parameters:
msg: the pointer to the message
Function returns: This function returns the value of the size of the given
message payload in bytes.
Note: The length of the message header is not included in the length
returned by this message. By definition, the total message length is the sum
of the length of the message header and the length of the message payload.
6.8.49 GetMsgObjectNumberFrom
Function prototype:
inline uint32 GetMsgObjectNumberFrom()
6.8.50 GetMsgObjectNumberTo
Function prototype:
inline uint32 GetMsgObjectNumberTo()
6.8.51 GetMsgToAutomata
Function prototype:
inline uint8 GetMsgToAutomata()
6.8.52 GetMsgToGroup
Function prototype:
inline uint8 GetMsgToGroup()
6.8.53 GetNewMessage
Function prototype:
inline uint8 *GetNewMessage()
Function description: This function returns the address of the buffer that
contains the new message.
Function returns: This function returns the pointer to the already defined
new message or the message under construction.
Note: If the new message does not exist, this function returns the value
NULL. This function assumes that the programmer has already allocated a
buffer for the new message by previously calling the function PrepareNew-
Message or calling the function GetBuffer.
6.8.54 GetNewMsgInfoCoding
Function prototype:
inline uint8 GetNewMsgInfoCoding()
6.8.55 GetNewMsgInfoLength
Function prototype:
inline uint16 GetNewMsgInfoLength()
Function description: This function returns the payload length of the new
message in bytes.
Function returns: This function returns the value of the new message
payload size in bytes.
Note: The length of the message header is not included in the length
returned by this message. By definition, the total message length is the
sum of the length of the message header and the length of the message
payload.
6.8.56 GetNextParam
Function prototype:
uint8 *GetNextParam(uint16 paramCode)
Function description: This function returns the address of the next instance
of the given parameter type within the current message. The parameter of
this function specifies the type of the message parameter.
Function parameters:
paramCode: the identification of the type of the message parameter
Function returns: The function returns the pointer to the next instance of
the message parameter. If it does not exist, the function returns the value
NULL.
Note: This function cannot be used by the programmer to get the first
instance of the message parameter of a given type. It assumes that the first
instance has already been provided by calling the function GetParam. Typi-
cally, the function GetParam is called once to provide the first instance of the
parameter and then called iteratively to provide the next instances of the
parameter.
6.8.57 GetNextParamByte
Function prototype:
bool GetNextParamByte(
uint16 paramCode,
BYTE ¶m)
Function description: This function searches for the next instance of the
given type of the single-byte parameter in the current message. If the instance
is found, the function copies it into its parameter specified by the reference
and returns the value true; otherwise, it returns the value false. The para-
meters of this function specify the identification of the type of the message
parameter and the pointer to the memory area where this function should
store the next instance of the message parameter.
Function parameters:
paramCode: the identification of the type of the message parameter
param: the pointer to the memory area reserved by the programmer for
the next instance of the message parameter
Function returns: This function returns the value true if the next instance
of the message parameter is found. If the instance is not found, this function
returns the value false.
Note: The programmer cannot use this function to get the first instance of
the message parameter of the given type. This function assumes that the first
instance has already been provided by calling the function GetParamByte.
Typically, the function GetParamByte is called once to provide the first
instance of the parameter and then called iteratively to provide the next
instances of the parameter.
6.8.58 GetNextParamDWord
Function prototype:
bool GetNextParamDWord(
uint16 paramCode,
DWORD ¶m)
Function description: This function searches for the next instance of the
given type of parameter 4 bytes in the current message. If the instance is
found, the function copies it into its parameter specified by the reference
and returns the value true; otherwise, it returns the value false. The para-
meters of this function specify the identification of the type of the message
parameter and the pointer to the memory area where this function should
store the next instance of the message parameter.
Function parameters:
paramCode: the identification of the type of message parameter
param: the pointer to the memory area reserved by the programmer for
the next instance of the message parameter
Function returns: This function returns the value true if the next instance
of the message parameter is found. If the instance is not found, this function
returns the value false.
Note: The programmer cannot use this function to get the first instance of
the message parameter of the given type. This function assumes that the first
instance has already been provided by calling the function GetParamDWord.
Typically, the function GetParamDWord is called once to provide the first
instance of the parameter and then called iteratively to provide the next
instances of the parameter.
6.8.59 GetNextParamWord
Function prototype:
bool GetNextParamWord(
uint16 paramCode,
WORD ¶m)
Function description: This function searches for the next instance of the
given type of parameter 2 bytes in the current message. If the instance is
found, the function copies it into its parameter specified by the reference
and returns the value true; otherwise, it returns the value false. The para-
meters of this function specify the identification of the type of the message
parameter and the pointer to the memory area where this function should
store the next instance of the message parameter.
Function parameters:
paramCode: the identification of the type of message parameter
param: the pointer to the memory area reserved by the programmer for
the next instance of the message parameter
Function returns: This function returns the value true if the next instance
of the message parameter is found. If the instance is not found, this function
returns the value false.
Note: The programmer cannot use this function to get the first instance of
the message parameter of the given type. This function assumes that the first
instance has already been provided by the call to the function GetParamWord.
Typically, the function GetParamWord is called once to provide the first
instance of the parameter and then called iteratively to provide the next
instances of the parameter.
6.8.60 GetObjectId
Function prototype:
virtual uint32 GetObjectId()
6.8.61 GetParam
Function prototype:
uint8 *GetParam(uint16 paramCode)
Function description: This function returns the address of the first instance
of the given type of the message parameter within the current message. The
parameter of this function specifies the identification of the parameter type.
Function parameters:
paramCode: the identification of the parameter type
Function returns: This function returns the pointer to the first instance of
the message parameter within the current message. If no message parameters
of the given type are found, this function returns the value NULL.
Note: This function returns the pointer to the beginning of the message
parameter. The format of the message parameter is governed by the selected
type of the message information coding. For example, the parameter of the
message StandardMessage consists of three fields. These fields are the para-
meter type (stored in 2 bytes), the parameter length (stored in 1 byte), and
the information part of the parameter (stored in the number of bytes deter-
mined by the content of the previous field of the parameter).
6.8.62 GetParamByte
Function prototype:
bool GetParamByte(
uint16 paramCode,
BYTE ¶m)
Function description: This function searches for the first instance of the
given type of single-byte parameter in the current message. If the instance
is found, the function copies it into its parameter specified by the reference
and returns the value true; otherwise, it returns the value false. The para-
meters of this function specify the identification of the type of the message
parameter and the pointer to the memory area where this function should
store the first instance of the message parameter.
Function parameters:
paramCode: the identification of the type of message parameter
param: the pointer to the memory area reserved by the programmer for
the next instance of the message parameter
Function returns: This function returns the value true if the first instance
of the message parameter is found. If the instance is not found, this function
returns the value false.
Note: The programmer must use this function to get the first instance of
the message parameter of the given type. Typically, this function is called
once to provide the first instance of the parameter and then the function
GetNextParamByte is called iteratively to provide the next instances of the
parameter.
6.8.63 GetParamDWord
Function prototype:
bool GetParamDWord(
uint16 paramCode,
DWORD ¶m)
Function description: This function searches for the first instance of the
given type of parameter 4 bytes in the current message. If the instance is
found, the function copies it into its parameter specified by the reference
and returns the value true; otherwise, it returns the value false. The
parameters of this function specify the identification of the type of message
parameter and the pointer to the memory area where this function should
store the first instance of the message parameter.
Function parameters:
paramCode: the identification of the type of message parameter
param: the pointer to the memory area reserved by the programmer for
the next instance of the message parameter
Function returns: This function returns the value true if the first instance
of the message parameter is found. If the instance is not found, this function
returns the value false.
Note: The programmer must use this function to get the first instance of
the message parameter of the given type. Typically, this function is called
once to provide the first instance of the parameter and then the function
GetNextParamDWord is called iteratively to provide the next instances of the
parameter.
6.8.64 GetParamWord
Function prototype:
bool GetParamWord(
uint16 paramCode,
BYTE ¶m)
Function description: This function searches for the first instance of the
given type of parameter 2 bytes in the current message. If the instance is
found, the function copies it into its parameter specified by the reference
and returns the value true; otherwise, it returns the value false. The para-
meters of this function specify the identification of the type of message
parameter and the pointer to the memory area where this function should
store the first instance of the message parameter.
Function parameters:
paramCode: the identification of the type of message parameter
param: the pointer to the memory area reserved by the programmer for
the next instance of the message parameter
Function returns: This function returns the value true if the first instance
of the message parameter is found. If the instance is not found, this function
returns the value false.
Note: The programmer must use this function to get the first instance of
the message parameter of the given type. Typically, this function is called
once to provide the first instance of the parameter and then the function
GetNextParamWord is called iteratively to provide the next instances of the
parameter.
6.8.65 GetProcedure
Function prototype:
PROC_FUN_PTR GetProcedure(uint16 event)
Function description: This function returns the pointer to the event handler
for the given event identifier and the current state of automata. The para-
meter of this function specifies the identification of the event type.
Function parameters:
event: the identification of the event type (message code)
Function returns: This function returns the pointer to the event handler.
Essentially, the event handler is a C++ class function member that handles
the given event type in the current state.
Note: The FSM system internal data structures contain all the necessary
information about the automata states, the sets of recognizable events (mes-
sages) for all automata states, and the corresponding event handlers. This
information must be defined for each automata type after it has been added
to the FSM system by the function Add. The programmer specifies this
information in the parameters of the function Initialize. If the event handler
has not been specified by the function Initialize for the given event type in
the current automata state, this function returns the pointer to the function
DoNothing, which performs the default processing of the unexpected events
(messages).
6.8.66 GetRightMbx
Function prototype:
virtual inline uint8 GetRightMbx()
6.8.67 GetRightAutomata
Function prototype:
virtual inline uint8 GetRightAutomata()
Note: By definition, right automata are logically placed to the right of the
currently observed automata instance.
6.8.68 GetRightGroup
Function prototype:
virtual linline uint8 GetRightGroup()
6.8.69 GetRightObjectId
Function prototype:
virtual inline uint32 GetRightObjectId()
6.8.70 GetState
Function prototype:
virtual inline uint8 GetState()
6.8.71 IsBufferSmall
Function prototype:
virtual bool IsBuferSmall(
uint8 *buff,
uint32 length)
Function description: This function returns the value true if the size of the
given buffer is not greater than the given size specified as the value of its
second parameter; otherwise, it returns the value false. The parameters of
this function specify the buffer whose size is to be checked and the size to
be used as a measuring unit.
Function parameters:
buff: the pointer to the buffer whose size is to be checked
length: the value of the measuring unit
Function returns:
This function returns the value true if the size of the given buffer is less
than or equal to the given size. If the buffer size is greater than the given
size, the function returns the value false.
6.8.72 Initialize
Function prototype:
virtual void Initialize() = 0
6.8.73 InitEventProc
Function prototype:
void InitEventProc(
uint8 state,
uint16 event,
PROC_FUN_PTR fun)
Function description: This function defines the given state transition event
handler for the given automata state and the given event (message code).
The parameters of this function specify the identification of the state of this
automata type, the identification of the event type, and the pointer to the
event handler.
Function parameters:
state: the identification of the state of this automata type
event: the identification of the event type
fun: the pointer to the event handler
Note: This function may be used only within the definition of the function
Initialize. A sequence of calls to this function fills in the internal state table
for this automata type. This table is used by the FSM system and this
automata type during its normal operation to locate the event handler that
corresponds to the given pair (state, event).
6.8.74 InitTimerBlock
Function prototype:
void InitTimerBlock (
uint16 tmrId,
uint32 count,
uint16 signalId)
Function description: This function initializes the given timer by the given
duration and the timer expiration message code. The parameters of this
function specify the timer identification, the timer duration, and the iden-
tification of the message to be sent to this automata type when the specified
timer expires.
Function parameters:
tmrId: the timer identification
count: the timer duration (in timer ticks)
signalId: the identification of the message (signal) to be sent by the
specified timer
6.8.75 InitUnexpectedEventProc
Function prototype:
void InitUnexpectedEventProc(
uint8 state,
PROC_FUN_PTR fun)
Function description: This function defines the given state transition event
handler for unexpected events in the given automata state. The parameters
of the function specify the automata state and the unexpected event handler,
which is essentially a C++ function that handles unexpected events (mes-
sages).
Function parameters:
state: the value that uniquely identifies the automata state
fun: the pointer to the unexpected event handler
Note: If the unexpected event (message) handler does not exist because it
has not been defined by this function, the FSM system and this automata
type will use the function DoNothing to handle unexpected messages for all
the states in which the unexpected message is not defined.
6.8.76 IsTimerRunning
Function prototype:
bool IsTimerRunning(uint16 id)
Function description: This function returns the value true if a given timer
is active (running); otherwise, it returns the value false. The parameter of this
function specifies the timer identification.
Function parameters:
id: the timer identification
Function returns: This function returns the value true if the timer is run-
ning. If the timer is not active, this function returns the value false.
Note: The timer may not be active because it has not been started at all or
it has been started but has expired in the meantime.
6.8.77 NoFreeObjectProcedure
Function prototype:
void NoFreeObjectProcedure(uint8 *msg)
Function parameters:
msg: the pointer to the pending message
6.8.78 NoFreeInstances
Function prototype:
virtual void NoFreeInstances() = 0
Function description: This function defines the behavior of the FSM system
if a list of free automata is used and if it is empty at the moment when a
free instance is requested.
Note: This function is used if a group of automata of this type is used as
a pool of resources of the same type within the FSM system. This function
is called if the message related to this automata type appears and no available
automata instances (resources) of this type are available. The programmer
should write his own function to handle this situation in an application-
specific way. This situation is additionally handled at the level of this auto-
mata type by the function NoFreeObjectProcedure.
6.8.79 ParseMessage
Function prototype:
virtual bool ParseMessage(uint8 *msg)
Function parameters:
msg: the pointer to the message to be parsed
Function returns: This function returns the value true if the message syntax
is correct; otherwise, it returns the value false.
Note: This function is called internally for each received message. Nor-
mally, this function is called after the reception of the message to check its
syntax. If the message syntax is correct, further message processing functions
are called. Otherwise, the FSM system reports an error and discards the
syntactically incorrect message.
6.8.80 PrepareNewMessage(uint8*)
Function prototype:
virtual void PrepareNewMessage(uint8 *msg)
Function description: This function defines the given buffer as the new
message buffer by assigning the given pointer to the internal variable New-
Message. The buffer is used by this automata instance as a working area for
the construction of the new message. The parameter of this function specifies
the buffer.
Function parameters:
msg: the pointer to the buffer
Function description: This function creates the new message of the given
length with the given message code and the given type of information
coding. The parameters of this function specify the message length, the
message code, and the identification of the type of message information
coding.
Function parameters:
length: the message length
Note: Dealing with static messages of fixed and known sizes is easy. In
this case, the programmer normally knows the size of the message he must
create. The programmer creates the new message by calling this function
and specifying the size as the value of the function parameter length. How-
ever, dealing with dynamic messages is more complicated because the mes-
sage length might not be known in advance. In this case, the programmer
may specify the value 0 as the value of the parameter length. This function
in its turn will create the empty message that has its header but has no
payload. Further on, the programmer typically uses functions AddParamX
to dynamically add new parameters to the message. Whenever not enough
room exists for the new parameter in the existing new message buffer, the
function AddParamX transparently allocates a bigger buffer, moves the con-
tent of the new message into it, and releases the smaller buffer. Of course,
the price paid for this flexibility is the processing overhead for the transpar-
ent buffer management.
6.8.82 Process
Function prototype:
virtual void Process(uint8 *msg)
Function description: This function performs the preparations for the mes-
sage processing and selects the state transition event handler based on the
message code and current state of this automata instance. After completion
of the message processing, this function releases the buffer used by
the message. The parameter of this function specifies the message to be
processed.
Function parameters:
msg: the pointer to the message to be processed
Note: This function is called internally by this automata type. Because this
function is virtual, the programmer may define the message handling pro-
cedure in accordance with the application-specific requirements.
6.8.83 PurgeMailBox
Function prototype:
void PurgeMailBox()
Function description: This function purges all the messages from the mail-
box assigned to this automata type and releases all the buffers assigned to
the messages.
Note: Notice that the mailbox is assigned to an automata type rather than
to an individual instance of this type. This means that the mailbox may contain
the messages addressed to different instances of this type. This function does
not differentiate the messages. Instead, it simply purges all of them.
6.8.84 RemoveParam
Function prototype:
bool RemoveParam(uint16 paramCode)
Function parameters:
paramCode: the value that uniquely identifies the type of message pa-
rameter
Function returns: This function returns the value true if the given type of
the message parameter is successfully found and removed. If the new mes-
sage does not contain the given type, this function returns the value false.
Note: Removing the type of message parameter with identification 0 is not
recommended because it marks the end of the parameters in the message.
FSMSystem library debug version will report an error in that case and stop
the program execution.
6.8.85 Reset
Function prototype:
virtual void Reset()
6.8.86 ResetTimer
Function prototype:
void ResetTimer(uint16 id)
Function description: This function resets the internal timer block object
and returns the buffer allocated by the StartTimer primitive to the FSM library
kernel. The parameter of this function specifies the identification of the timer.
Function parameters:
id: the value that uniquely identifies the timer
6.8.87 RestartTimer
Function prototype:
void RestartTimer(uint16 tmrId)
Function parameters:
tmrId: the value that uniquely identifies the timer
6.8.88 RetBuffer
Function prototype:
virtual void RetBuffer(uint8 *buff)
Function description: This function returns the given buffer to the FSM
library kernel. Normally, each memory buffer is returned at the end of its
life cycle. The failure to do so leads to the memory leak problem. The
parameter of this function specifies the buffer to be released.
Function parameters:
buff: the pointer to the buffer to be released
Note: The programmer must pay special attention to releasing the buffers
when they are not needed anymore because the FSMSystem library does not
include the garbage collector. Memory outage causes the exception that will
stop the program execution.
6.8.89 ReturnMsg
Function prototype:
void ReturnMsg(uint8 mbxId)
react in this simple way. The parameter of this function specifies the iden-
tification of the mailbox.
Function parameters:
mbxId: the value that uniquely identifies the mailbox
6.8.90 SetBitParamByteBasic
Function prototype:
void SetBitParamByteBasic(
BYTE param,
uint32 offset,
uint32 mask = MASK_32_BIT)
Function parameters:
param: the value of the single-byte parameter
offset: the target parameter of the new message
mask: the value of the bit-mask
6.8.91 SetBitParamDWordBasic
Function prototype:
void SetBitParamDWordBasic(
DWORD param,
uint32 offset,
uint32 mask = MASK_32_BIT)
Function description: This function sets the given 4-byte parameter of the
new message to the result of the bit-wise inclusive OR operation applied to
the given parameter and its previous value masked (bit-wise AND opera-
tion) with the given bit-mask. The parameters of this function specify the
value of the 4-byte parameter, the offset of the target parameter of the new
message, and the value of the bit-mask.
Function parameters:
param: the value of the 4-byte parameter
offset: the target parameter of the new message
mask: the value of the bit-mask
6.8.92 SetBitParamWordBasic
Function prototype:
void SetBitParamWordBasic(
WORD param,
uint32 offset,
uint32 mask = MASK_32_BIT)
Function description: This function sets the given 2-byte parameter of the
new message to the result of the bit-wise inclusive OR operation applied to
the given parameter and its previous value masked (bit-wise AND opera-
tion) with the given bit-mask. The parameters of this function specify the
value of the 2-byte parameter, the offset of the target parameter of the new
message, and the value of the bit-mask.
Function parameters:
param: the value of the 2-byte parameter
offset: the target parameter of the new message
mask: the value of the bit-mask
6.8.93 SetCallId( )
Function prototype:
inline void SetCallId()
Function description: This function sets the default value of the attribute
CallId of this automata instance.
Note: This function automatically allocates the first available identification
and assigns it to the protected class attribute CallId, completely transparent
to the programmer.
6.8.94 SetCallId(uint32)
Function prototype:
inline void SetCallId(uint32 id)
Function description: This function sets the given value of the attribute
CallId of this automata instance. The parameter of this function specifies the
value to be assigned to the attribute CallId.
Function parameters:
id: the value to be assigned to the attribute CallId
6.8.95 SetCallIdFromMsg
Function prototype:
inline void SetCallIdFromMsg()
Function description: This function sets the attribute CallId of this automata
instance to the value of the parameter CallId of the current message. This
primitive is used to store the reference number specific to the communication
protocol.
6.8.96 SetDefaultFSMData
Function prototype:
virtual void SetDefaultFSMData() = 0
6.8.97 SetDefaultHeader
Function prototype:
virtual void SetDefaultHeader(uint8 infoCoding = 0)
Function description: This function sets the default header field values for
the given type of the message information coding. The parameter of this
function specifies the identification of the type of the message information
coding.
Function parameters:
infoCoding: the type of the message information coding
Note: The programmer must define this virtual function for a class derived
from the class FiniteStateMachine. They do so by writing a C++ function that
fills in the protocol-specific data in the new message header.
6.8.98 SetGroup
Function prototype:
inline void SetGroup(uint8 id)
Function parameters:
id: the value that uniquely identifies the group of automata
6.8.99 SetInitialState
Function prototype:
virtual void SetInitialState()
Function description: This function sets the current state of this automata
instance to its initial state.
Note: The programmer must obey the rule that the value of the iden-
tification of the initial automata state is 0.
6.8.100 SetKernelObjects
Function prototype:
static void SetKernelObjects(
TPostOffice *postOffice,
TBuffers *buffers,
CTimer *timer)
Function parameters:
postOffice: the pointer to the post office object
buffers: the pointer to the buffers object
timer: the pointer to the timers object
6.8.101 SetLeftMbx
Function prototype:
inline void SetLeftMbx(uint8 mbx)
Function parameters:
mbx: the value that uniquely identifies the mailbox
6.8.102 SetLeftAutomata
Function prototype:
inline void SetLeftAutomata(uint8 automata)
Function parameters:
automata: the value that uniquely identifies the automata type
6.8.103 SetLeftObject
Function prototype:
inline void SetLeftObject(uint8 group)
Function parameters:
group: the value that uniquely identifies the group of automata
6.8.104 SetLeftObjectId
Function prototype:
inline void SetLeftObjectId(uint32 id)
Function parameters:
id: the identification of the automata instance
6.8.105 SetLogInterface
Function prototype:
static void SetLogInterface(LogInterface *logingObject)
Function description: This function defines the object responsible for mes-
sage logging. The object is an instance of a class derived from the class
LogInterface. The parameter of this function specifies the message logging
object.
Function parameters:
logingObject: the pointer to the message logging object
Note: The programmer must not call this function before the initialization
of all the automata included in the FSM system has been finished. The
logging object may log data to the file on the local mass memory unit (e.g.,
flash memory) or to the network file server. The log file is essential for
debugging and test and verification purposes.
6.8.106 SendMessage(uint8)
Function prototype:
inline void SendMessage(uint8 mbxId)
Function description: This function sends the new message to the given
mailbox. The parameter of this function specifies the identification of the
mailbox.
Function parameters:
mbxId: the value that uniquely specifies the mailbox
Function description: This function sends the given message to the given
mailbox. The parameters of this function specify the identification of the
mailbox and the message to be sent to that mailbox.
Function parameters:
mbxId: the value that uniquely identifies the mailbox
msg: the pointer to the message
6.8.108 SetMessageFromData
Function prototype:
void SetMessageFromData()
Function description: This function sets the header fields of the new mes-
sage related to the originating automata instance to the values specific to
this automata instance. The data specifying the originating automata
instance are its type, its group, and its identification.
Note: This function is automatically called from the function SendMessage.
6.8.109 SetMsgCallId(uint32)
Function prototype:
inline void SetMsgCallId(uint32 id)
Function description: This function sets the call ID parameter of the new
message to the given value. The parameter of this function specifies the value
of the call ID.
Function parameters:
id: the value of the call ID
Note: The call ID parameter has been traditionally used to identify a single
telephone call. In general, it may be used to uniquely identify a communi-
cation process or a transaction that engages a group of automata that par-
ticipate in its processing.
Function description: This function sets the call ID parameter of the given
message to the given value. The parameters of this function specify the value
of the call ID and the target message.
Function parameters:
id: the value of the call ID
msg: the pointer to the buffer that contains the target message
Note: The value of the call ID parameter is the same for all the messages
involved in a transaction or a process, e.g., a single telephone call.
6.8.111 SetMsgCode(uint16)
Function prototype:
inline void SetMsgCode(uint16 code)
Function parameters:
code: the message code
Function parameters:
code: the message code
msg: the pointer to the buffer that contains the target message
6.8.113 SetMsgFromAutomata(uint8)
Function prototype:
inline void SetMsgFromAutomata(uint8 from)
Function description: This function sets the type of the originating automata
parameter of the new message to the given value. The parameter of this function
specifies the identification of the automata type that is the message source.
Function parameters:
from: the identification of the automata type
Function description: This function sets the type of the originating auto-
mata parameter of the given message to the given value. The parameters of
this function specify the type of the automata that is the message source and
the target message.
Function parameters:
from: the automata type that is the message source
msg: the pointer to the buffer that contains the target message
6.8.115 SetMsgFromGroup(uint8)
Function prototype:
inline void SetMsgFromGroup(uint8 from)
Function description: This function sets the type of the originating group
of automata parameter of the new message to the given value. The parameter
of this message specifies the identification of the group of automata that is
the message source.
Function parameters:
from: the identification of the group of automata that is the message
source
Function description: This function sets the type of the originating group
of automata parameter of the given message to the given value. The para-
meters of this function specify the identification of the group of automata
that is the message source and the target message.
Function parameters:
from: the identification of the group of automata that is the message
source
msg: the pointer to the buffer that contains the target message
6.8.117 SetMsgInfoCoding(uint8)
Function prototype:
inline void SetMsgInfoCoding(uint8 codingType)
Function parameters:
codingType: the value that uniquely specifies the information coding
scheme
Function parameters:
codingType: the identification of the information coding scheme
msg: the pointer to the target message
6.8.119 SetMsgInfoLength(uint16)
Function prototype:
inline void SetMsgInfoLength(uint16 length)
Function description: This function sets the message payload (useful infor-
mation) length parameter of the new message. The parameter of this function
specifies the value of the payload length.
Function parameters:
length: the payload length in octets (bytes)
Note: All the AddParamX functions — which are responsible for adding
parameters to the new message — call this function automatically to update
the length of the message payload.
Function parameters:
length: the payload length in octets (bytes)
msg: the pointer to the buffer that contains the target message
6.8.121 SetMsgObjectNumberFrom(uint32)
Function prototype:
inline void SetMsgObjectNumberFrom(uint32 from)
Function parameters:
from: the identification of the automata instance that is the message
source
Function parameters:
from: the identification of the automata instance that is the message
source
msg: the pointer to the buffer that contains the target message
6.8.123 SetMsgObjectNumberTo(uint32)
Function prototype:
inline void SetMsgObjectNumberTo(uint32 to)
Function parameters:
to: the automata instance that is the message destination
parameters of this function specify the automata instance that is the message
destination and the target message.
Function parameters:
to: the automata instance that is the message destination
msg: the pointer to the buffer that contains the target message
6.8.125 SetMsgToAutomata(uint8)
Function prototype:
inline void SetMsgToAutomata(uint8 to)
Function description: This function sets the destination automata type iden-
tification parameter of the new message to the given value. The parameter of
this function specifies the automata type that is the message destination.
Function parameters:
to: the automata type that is the message destination
Function parameters:
to: the identification of the automata type that is the message destination
msg: the pointer to the buffer that contains the target message
6.8.127 SetMsgToGroup(uint8)
Function prototype:
inline void SetMsgToGroup(uint8 to)
Function parameters:
to: the identification of the group of automata that is the message des-
tination
Function parameters:
to: the identification of the group of automata that is the message des-
tination
msg: the pointer to the buffer that contains the target message
6.8.129 SendMessageLeft
Function prototype:
void SendMessageLeft()
Function description: This function sends the new message to the mailbox
assigned to the automata instance that is logically to the left of this automata
instance.
Note: The programmer may use this function if he has already defined the
left automata instance for the currently observed automata instance. This
definition includes the definition of the mailbox assigned to the left automata
instance. If the left automata instance and its mailbox are defined, this func-
tion automatically fills in all the data related to both source (originating) and
destination automata instances within the new message and sends the new
message to the left mailbox.
6.8.130 SendMessageRight
Function prototype:
void SendMessageLeft()
Function description: This function sends the new message to the mailbox
assigned to the automata instance that is logically to the right of this auto-
mata instance.
Note: The programmer may use this function if he has already defined the
right automata instance for the currently observed automata instance. This
definition includes the definition of the mailbox assigned to the right auto-
mata instance. If the right automata instance and its mailbox are defined,
this function automatically fills in all the data related to both source (origi-
nating) and destination automata instances within the new message and
sends the new message to the right mailbox.
6.8.131 SetNewMessage
Function prototype:
inline void SetNewMessage(uint8 *msg)
Function description: This function sets the new message to the given
message by assigning the given message pointer to the internal pointer
to the new message. The parameter of this function specifies the target
message.
Function parameters:
msg: the pointer to the buffer that contains the target message
6.8.132 SetObjectId
Function prototype:
inline void SetObjectId(uint32 id)
Function parameters:
id: the value that uniquely identifies this automata instance
6.8.133 SetRightMbx
Function prototype:
inline void SetRightMbx(uint8 mbx)
Function parameters:
mbx: the identification of the right mailbox for this automata instance
6.8.134 SetRightAutomata
Function prototype:
inline void SetRightAutomata(uint8 automata)
Function parameters:
automata: the identification of the automata type
6.8.135 SetRightObject
Function prototype:
inline void SetRightObject(uint8 group)
Function parameters:
group: the identification of the group of automata
6.8.136 SetRightObjectId
Function prototype:
inline void SetRightObjectId(uint32 id)
Function parameters:
id: the identification of the automata instance
6.8.137 SetState
Function prototype:
inline void SetState(uint8 state)
Function parameters:
state: the value that uniquely identifies the particular state of automata
6.8.138 StartTimer
Function prototype:
void StartTimer(uint16 tmrId)
Function description: This function starts the given timer. The parameter
of this function specifies the identification of the timer.
Function parameters:
tmrId: the value that uniquely identifies the particular timer
6.8.139 StopTimer
Function prototype:
void StopTimer(uint16 tmrId)
Function description: This function stops the given timer. The parameter
of this function specifies the identification of the timer.
Function parameters:
tmrId: the value that uniquely identifies the particular timer
6.8.140 SysClearLogFlag
Function prototype:
static void SysClearLogFlag()
6.8.141 SysStartAll
Function prototype:
Static void SysStartAll()
6.8.142 NetFSM
Function prototype:
NetFSM(
uint16 numOfTimers = DEFAULT_TIMER_NO,
uint16 numOfState = DEFAULT_STATE_NO,
uint16 maxNumOfProceduresPerState = DEFAULT_PROCEDURE_NO_PER_STATE,
bool getMemory = true)
Function parameters:
numOfTimers: the number of timers to be used by this automata type
numOfState: the total number of states for this automata type
maxNumOfProceduresPerState: the maximal number of state transitions
per state
getMemory: the memory allocation indicator
6.8.143 convertFSMToNetMessage
Function prototype:
virtual void convertFSMToNetMessage() = 0
6.8.144 convertNetToFSMMessage
Function prototype:
virtual uint16 convertNetToFSMMessage() = 0
6.8.145 establishConnection
Function prototype:
void establishConnection()
6.8.146 getProtocolInfoCoding
Function prototype:
virtual uint8 getProtocolInfoCoding() = 0
6.8.147 sendToTCP
Function prototype:
void sendToTCP()
Function description: This function sends the new message to the remote
FSM system over the previously established TCP connection.
Note: The programmer must call the function establishConnection before he
can call this function.
System
«uses»
Show Simple Demo
Demonstrator
FIGURE 6.1
The simple use case diagram for the example with three automata instances.
instance_3 : Automata
)
(9
SG (6) )
2 : IDLE_START _M SG (3
SG _M SG
: M SG _M
11 : M SG
13 : MSG_STOP(1)
10 : MSG_MSG(8)
4 : IDLE_MSG(2)
7 : MSG_MSG(5)
8 :M
«self»
5
1 : StartDemo()
3
main : Thread instance_1 : Automata 6 : : IDL
9 M E
12 : M SG_ _MS
: M SG M G(
SG _M SG( 1)
_S SG 4)
TO (7)
P(2
)
instance_2 : Automata
FIGURE 6.2
The collaboration diagram for the example with three automata instances.
StartDemo();
IDLE_START
IDLE_MSG(1)
IDLE_MSG(2)
MSG_MSG(3)
MSG_MSG(4)
Condition A
MSG_MSG(5)
MSG_MSG(6)
MSG_MSG(7)
MSG_MSG(8)
MSG_MSG(9)
MSG_STOP(2)
MSG_STOP(1)
Condition B
Condition C
FIGURE 6.3
The sequence diagram for the example with three automata instances.
UNKNOWN
IDLE
IDLE_START
[else]
IDLE_MSG
Automata_IDLE_START Automata_MSG_MSG
Automata_IDLE_MSG Automata_MSG_STOP
MSG_MSG
[msgno<MAX_MSG_NUM]
MSG_STOP
MESSAGE
UNKNOWN
FIGURE 6.4
The statechart diagram for the example with three automata instances.
Automata_IDLE_START
INITIAL
/msgno = 1
PREPARING
SENDING
/SendMessage(MBX_AUTOMATA_ID);
FIGURE 6.5
The statechart diagram for the composite state Automata_IDLE_START.
Automata_IDLE_MSG
INITIAL
PREPARING
SENDING_IDLE
SENDING_MSG
/SendMessage(MBX_AUTOMATA_ID);
/SendMessage(MBX_AUTOMATA_ID);
FIGURE 6.6
The statechart diagram for the composite state Automata_IDLE_MSG.
Automata_MSG_MSG
INITIAL
PREPARING
SENDING_MSG
SENDING_STOP /SendMessage(MBX_AUTOMATA_ID);
/SendMessage(MBX_AUTOMATA_ID);
FIGURE 6.7
The statechart diagram for the composite state Automata_MSG_MSG.
File Automata.h:
#ifndef __AUTOMATA__
#define __AUTOMATA__
#include <stdio.h>
#include “stdlib.h”
#include “kernel\fsm.h”
#include “kernel\errorObject.h”
#include “Constants.h”
Automata_MSG_STOP
INITIAL
PREPARING
[msgno>0]/PrepareNewMessage(0×00,MSG_STOP);
AddParamDWord(COUNT, msgno);
[else]
SENDING_STOP
/SendMessage(MBX_AUTOMATA_ID);
FIGURE 6.8
The statechart diagram for the composite state Automata_MSG_STOP.
uint8 text[20];
uint32 msgNumber;
uint32 idToMsg;
public:
Automata();
~Automata(){};
Automata_IDLE_START
msgno = 1
PREPARE IDLE_MSG
FIGURE 6.9
The activity diagram for the operation Automata_IDLE_START.
void Initialize();
void StartDemo();
};
#endif
The file Automata.h contains the declaration of the class Automata derived
from the class FiniteStateMachine. This declaration has its private and public
parts. The private field members are the message interface object StandardMsg-
Coding, the text work area text, the message sequence number msgNumber, and
the identification of the message destination automata idToMsg.
The common private function members are the following functions:
Automata_IDLE_MSG
GET msgno
INCREMENT msgno
[else] [msgno<NUM_AUTOMATA]
FIGURE 6.10
The activity diagram for the operation Automata_IDLE_MSG.
Automata_MSG_MSG
GET msgno
INCREMENT msgno
[else] [msgno<MAX_MSG_NUM]
SET msgno TO
NUM_AUTOMATA - 1 PREPARE MSG_MSG
FIGURE 6.11
The activity diagram for the operation Automata_MSG_MSG.
The public function members are the class constructor, the class destructor,
the initialization function Initialize, and the startup function StartDemo.
File Automata.cpp:
#include “kernel/LogFile.h”
#include “Automata.h”
Automata::Automata() : FiniteStateMachine(
0, // uint16 numOfTimers = DEFAULT_TIMER_NO,
Automata_MSG_STOP
GET msgno
DECREMENT msgno
[else] [msgno>0]
FIGURE 6.12
The activity diagram for the operation Automata_MSG_STOP.
// This function returns the pointer to the object that governs the
// message information coding (the pointer to the message interface).
// This automata instance works only with the standard messages
// (ID 0x00). If the caller specifies another type of coding,
// this function throws the exception TErrorObject. The message
// interface is defined in Automata.h
MessageInterface *Automata::GetMessageInterface(uint32 id){
switch(id){
case 0x00:
return &StandardMsgCoding;
}
throw TErrorObject(__LINE__,__FILE__,0x01010400);
}
IDLE
Automata_IDLE_START
IDLE_START
SET msgno
TO 1
PREPARE
IDLE_MSG
SEND TO
NEXT
INSTANCE
MESSAGE
FIGURE 6.13
The SDL diagram for the transition Automata_IDLE_START.
IDLE
Automata_IDLE_MSG
IDLE_MSG
GET msgno
INCREMENT
msgno
msgo<
NO NUM_AU YES
TOMATA
PREPARE PREPARE
MSG_MSG IDLE_MSG
SEND TO SEND TO
NEXT NEXT
INSTANCE INSTANCE
MESSAGE
FIGURE 6.14
The SDL diagram for the transition Automata_IDLE_MSG.
void Automata::SetDefaultFSMData(){
msgNumber = 0;
idToMsg = INVALID_32;
}
MESSAGE
Automata_MSG_MSG
MSG_MSG
GET msgno
INCREMENT
msgno
msgno<
MAX_MS
G_NUM
SET msgno
TO PREPARE
NUM_AUTO MSG_MSG
MATA - 1
SEND TO
PREPARE
NEXT
MSG_STOP
INSTANCE
SEND TO MESSAGE
NEXT
INSTANCE
IDLE
FIGURE 6.15
The SDL diagram for the transition Automata_MSG_MSG.
MESSAGE
Automata_MSG_STOP
MSG_STOP
GET msgno
DECREME-
NT msgno
msgno>0
IDLE
PREPARE
MSG_STOP
SEND TO
NEXT
INSTANCE
IDLE
FIGURE 6.16
The SDL diagram for the transition Automata_MSG_STOP.
Automata FSMSystem
-msgno -PostOffice
Here and in other UML
-Automata_IDLE_START() -Buffers
diagrams we use the «uses» -Timer
-Automata_IDLE_MSG()
abbrevation msgno. -Automata_MSG_MSG() #Automates
The full name of this +Automata_MSG_STOP() #NumberOfMbx
field is msgNumber. +Initialize() #NumberOfAutomates
+StartDemo() -NumberOfObjects
-FreeKernelMemory : bool
-SystemWorking : bool
#GetBuffer()
FiniteStateMachine #GetMsg()
This is not the complete -NumOfStates #GetMsgToAutomate()
-NumOfTimers #GetMsgToGroup()
specification of the class #GetMsgInfoLength()
-MaxNumOfProcPerState
FiniteStateMachine. -States #GetMsgObjectNumberTo()
It is just a snippet that -ConnectionId #SendToMbx()
should give you an idea -GroupId +FSMSystem()
of its complexity. -CallId +~FSMSystem()
-LeftMbx +Add()
-LeftAutomate +Delete()
-LeftGroup +InitKernel()
-LeftObjectId +Start()
-RightMbx +StopSystem()
-RightAutomate
-RightGroup
-RightObjectId
-State This is not the complete
#GetLeftMbx() specification of the class
#GetLeftAutomate() FSMSystem.
#GetLeftGroup()
#GetLeftObjectId()
#SetLeftMbx()
#SetLeftAutomate()
#SetLeftObject()
#SetLeftObjectId()
#Initialize()
#InitEventProc()
#InitUnexpectedEventProc()
+FiniteStateMachine()
+~FiniteStateMachine()
+Process()
FIGURE 6.17
The class diagram for the example with three automata instances.
InitEventProc(MESSAGE,MSG_MSG,(PROC_FUN_PTR)
&Automata::Automata_MSG_MSG);
InitEventProc(MESSAGE,MSG_STOP,(PROC_FUN_PTR)
&Automata::Automata_MSG_STOP);
InitUnexpectedEventProc(IDLE,(PROC_FUN_PTR)
&Automata::Automata_UNEXPECTED_IDLE);
InitUnexpectedEventProc(MESSAGE,(PROC_FUN_PTR)
&Automata::Automata_UNEXPECTED_MSG);
}
instance_3 : Automata
virtual
instance_2 : Automata
This instance_1 and instance_2
peer to peer connection is virtual.
The real communication involves
their interaction with the fsmSystem.
FIGURE 6.18
The object diagram for the example with three automata instances.
«file» «executable»
Main.dsw Main.exe
«file» «file»
Automata.cpp Main.cpp «framework»
FSM Library
«file»
Constants.h
«file»
Automata.h
FIGURE 6.19
The component diagram for the example with three automata instances.
«executable» PC
Main.exe
FIGURE 6.20
The deployment diagram for the example with three automata instances.
SetMsgToAutomata(FSM_TYPE_AUTOMATA);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(idToMsg);
SendMessage(MBX_AUTOMATA_ID);
SetState(MESSAGE);
}
void Automata::Automata_IDLE_MSG(){
idToMsg = GetObjectId()+1;
// Round Robin – this instance receives the message from the previous one
uint32 idFromMsg = GetObjectId()-1;
if(idFromMsg == -1)
idFromMsg = 2;
SetMsgToAutomata(FSM_TYPE_AUTOMATA);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(idToMsg);
SendMessage(MBX_AUTOMATA_ID);
}
else {
// Prepare and send the message.
// Change automata state to MESSAGE.
PrepareNewMessage(0x00,MSG_MSG);
AddParamDWord(COUNT,msgNumber);
SetMsgToAutomata(FSM_TYPE_AUTOMATA);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(idToMsg);
SendMessage(MBX_AUTOMATA_ID);
}
SetState(MESSAGE);
}
void Automata::Automata_MSG_MSG(){
GetParamDWord(COUNT,msgNumber);
msgNumber++;
if(msgNumber < MAX_MSG_NUM){
// Forward the message to the next automata instance.
PrepareNewMessage(0x00,MSG_MSG);
AddParamDWord(COUNT,msgNumber);
SetMsgToAutomata(FSM_TYPE_AUTOMATA);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(idToMsg);
SendMessage(MBX_AUTOMAT_ID);
}
else {
printf(“Stop automata:%with message:%u\n”,GetObjectId(),msgNumber);
void Automata::Automata_MSG_STOP(){
printf(“Stop automata instance: %u\n”,GetObjectId());
GetParamDWord(COUNT,msgNumber);
msgNumber——;
if(msgNumber > 0){
// Prepare and send the message.
// Change automata state to IDLE.
PrepareNewMessage(0x00,MSG_STOP);
AddParamDWord(COUNT,msgNumber);
SetMsgToAutomata(FSM_TYPE_AUTOMATA);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(idToMsg);
SendMessage(MBX_AUTOMATA_ID);
}
SetState(IDLE);
}
void Automata::Automata_UNEXPECTED_IDLE(){
printf(“Unexpected message in the state IDLE \n”);
}
void Automata::Automata_UNEXPECTED_MSG(){
printf(“Unexpected message in the state MESSAGE \n”);
}
void Automata::StartDemo(){
uint8 *msg = GetBuffer(MSG_HEADER_LENGTH);
SetMsgFromAutomata(FSM_TYPE_AUTOMATA,msg);
SetMsgFromGroup(INVALID_08,msg);
SetMsgObjectNumberFrom(0,msg);
SetMsgToAutomata(FSM_TYPE_AUTOMATA,msg);
SetMsgToGroup(INVALID_08,msg);
SetMsgObjectNumberTo(0,msg);
SetMsgInfoCoding(0,msg); // 0 = StandardMessage
SetMsgCode(IDLE_START,msg);
SetMsgInfoLength(0,msg);
SendMessage(MBX_AUTOMATA_ID,msg);
}
The file Automata.cpp contains the definition of the class Automata. This def-
inition starts with the class constructor that first calls the base class constructor
specifying no timers, two states, and the maximum of three state transitions
per state for this automata type. After that, the constructor calls the function
SetDefaultFSMData, which sets the data specific for this automata type.
The function GetMessageInterface returns the pointer to the message interface
object for the given type of information coding. This class operates with only
standard messages (the corresponding ID is 0x00). If the caller of this function
specifies the identification of the standard message as its parameter, the func-
tion returns the pointer to the object StandardMsgCoding. If the caller specifies
some other message type, this function throws the exception TErrorObject.
The function SetDefaultHeader sets the message information coding by
calling the function SetMsgInfoCoding and the automata specific data by
calling the function SetMessageFromData. The function GetMbxId returns the
value MBX_AUTOMATA_ID as the identification of the mailbox assigned to
this automata type. The function GetAutomata returns the value
FSM_TYPE_AUTOMATA as the identification of this automata type. The
function SetDefaultFSMData sets the field msgNumber to the value 0 and the
field idToMsg to the value INVALID_32. The function NoFreeInstances is empty
in this simple example. In real-world projects, it would be used to trigger
some higher-level protection or recovery mechanism.
The function Initialize defines the event handlers by calling the function
InitEventProc and the unexpected events handlers by calling the function
InitUnexpectedEventProc. More precisely, this function defines the event han-
dlers for the messages IDLE_START and IDLE_MSG in the state IDLE and
for the messages MSG_MSG and MSG_STOP in the state MESSAGE. It also
defines the handlers for unexpected messages in both states.
The function Automata_IDLE_START handles the message IDLE_START in
the state IDLE. First, it sets the message sequence number msgNumber to the
value 1. It then determines the identification of the destination automata
instance by incrementing its own identification by modulo 3. (This means
that the destination of the messages created and sent by instance_0 is
instance_1, the destination for instance_1 is instance_2, and the destination for
instance_2 is instance_0.) Next, this function prepares and sends the message,
“THIS IS THE FIRST MESSAGE”. At the end, it performs the state transition
from IDLE to MESSAGE by calling the function SetState and specifying the
value MESSAGE as its parameter.
The function Automata_IDLE_MSG handles the message IDLE_MSG in the
state IDLE. First, it determines the identifications of the source and destina-
tion automata instances for the received message and prints them to the
monitor. It then increments the message sequence numbers and checks if
they are less than the number of communicating automata instances
NUM_AUTOMATA (value 3). If yes, the function prepares and sends the
message IDLE_MSG with the text, “THIS IS THE SECOND MESSAGE”. If
not, the function prepares and sends the message MSG_MSG without any
text. In both cases, it sets the current state of this automata instance to the
value MESSAGE.
The function Automata_MSG_MSG handles the message MSG_MSG in the
state MESSAGE. First, it gets the message sequence number from the
received message and increments that number. It then checks if the new
value of the message sequence number has reached the given limit. If not,
this function prepares and sends the message MSG_MSG to the next auto-
mata instance in the chain. If it has, this function prepares and sends the
message MSG_STOP to the next automata instance in the chain and sets the
current state of this automata instance to IDLE.
File Constants.h:
// FSM
#define FSM_TYPE_AUTOMATA 0
// MBX
#define MBX_AUTOMATA_ID 0
#define MAX_MSG_NUM 10
#define NUM_AUTOMATA 3
#define COUNT 1
#define PARAM_TEXT 2
enum AutomataStates{
IDLE = 0,
MESSAGE,
};
enum Messages{
IDLE_START = 0,
IDLE_MSG,
MSG_MSG,
MSG_STOP
};
The file Constants.h first defines general symbolic constants. The identification
of this automata type FSM_TYPE_AUTOMATA is assigned the value 0, the
i d e n t i fi c a t i o n o f t h e m a i l b o x re l a t e d t o t h i s a u t o m a t a t y p e
MBX_AUTOMATA_ID is assigned the value 0, the maximal message sequence
number MAX_MSG_NUM is assigned the value 10, the number of automata
instances of this type NUM_AUTOMATA is assigned the value 3, the iden-
tification of the message parameter that contains the messages sequence num-
ber COUNT is assigned the value 1, and the identification of the message
parameter that contains the text PARAM_TEXT is assigned the value 2.
Next, the identifications of the individual states of this automata type are
enumerated. The identification of the state IDLE is assigned the value 0 and
the identification of the state MESSAGES is assigned the value 1. Finally, the
identifications of various message types (message codes) are enumerated.
File Main.cpp:
#include “conio.h”
#include “Kernel/fsmsystem.h”
#include “Kernel/LogFile.h”
#include “Automata.h”
fsmSystem.InitKernel(buffClassNo,buffersCount,buffersLength,1);
The file Main.cpp starts with the instantiation of the class FSMSystem by
calling its constructor. The parameters used in this call specify that the
instance of the FSMSystem, named fsmSystem, will include a single automata
type and this automata type will use a single mailbox. Next, three instances
of the class Automata are made, namely, instance_1, instance_2, and instance_3.
Additionally, this file contains the definitions of the FSM system thread
function ThreadFunction and the function main.
The function ThreadFunction first prepares the data needed to define three
buffer types. The sizes and quantities of these buffers are five at 128 bytes,
three at 256 bytes, and two at 512 bytes. Next, three automata instances are
added to fsmSystem. Note that the fourth parameter of the first call to the
function Add is set to the value false, which means that these three instances
are to be used as three distinctive instances rather than as a pool of instances
of the same type. After that, this function initializes the kernel by calling the
function InitKernel, defines and sets the logging interface by calling the
function SetLogInterface, and starts fsmSystem by calling its function Start.
The function main starts the FSM system thread (which executes the func-
tion ThreadFunction) and suspends itself for 100 ms. After that, it just waits
for the character ‘Q’ or ‘q’ to be pressed and to subsequently terminate the
program.
3 : IDLE_MSG(1)
4 : MSG_MSG(2)
5 : MSG_MSG(3)
2 : IDLE_START 6 : MSG_MSG(4)
7 : MSG_MSG(5)
8 : MSG_MSG(6)
«self»
9 : MSG_MSG(7)
1 : StartDemo() 10 : MSG_MSG(8)
main : Thread instance_1 : NetAutomata instance_1 : NetAutomata
FIGURE 6.21
The collaboration diagram for the example with network-aware automata.
StartDemo();
IDLE_START
IDLE_MSG(1)
MSG_MSG(2)
MSG_MSG(3)
MSG_MSG(4)
MSG_MSG(5)
MSG_MSG(6)
MSG_MSG(7)
MSG_MSG(8)
MSG_MSG(9)
MSG_MSG(10)
MSG_STOP
FIGURE 6.22
The sequence diagram for the example with network-aware automata.
File NetAutomata.h:
#ifndef __NET_AUTOMATA__
#define __NET_AUTOMATA__
Automata_IDLE_START
INITIAL
/msgno = 1
PREPARING
SENDING
/sendToTCP();
FIGURE 6.23
The composite state Automata_IDLE_START.
#include <stdio.h>
#include “stdlib.h”
#include “kernel\NetFSM.h”
#include “kernel\errorObject.h”
#include “Constants.h”
uint8 text[20];
Automata_IDLE_MSG
INITIAL
PREPARING
SENDING_MSG
/sendToTCP();
FIGURE 6.24
The composite state Automata_IDLE_MSG.
uint32 msgNumber;
uint32 idToMsg;
public:
NetAutomata();
~NetAutomata(){};
void Initialize();
void StartDemo();
};
#endif
Automata_MSG_MSG
INITIAL
PREPARING
SENDING_MSG
SENDING_STOP
/sendToTCP();
/sendToTCP();
FIGURE 6.25
The composite state Automata_MSG_MSG.
Automata_MSG_STOP
INITIAL
/PrintStopMessage();
FIGURE 6.26
The composite state Automata_MSG_STOP.
«executable» «executable»
i1 : Example21 i1 : Example22
1 1 1 1
FIGURE 6.27
The deployment diagram for the example with network-aware automata.
The private function members specific to the class FinteStateMachine are the
following functions:
The public function members are the class constructor, the class destructor,
the initialization function Initialize, and the startup function StartDemo.
File NetAutomata.cpp:
#include “kernel/LogFile.h”
#include “NetAutomata.h”
NetAutomata::NetAutomata() : NetFSM(
0, // uint16 numOfTimers = DEFAULT_TIMER_NO,
2, // uint16 numOfState = DEFAULT_STATE_NO,
3) // uint16 maxNumOfProceduresPerState = DEFAULT_PROCEDURE_NO_PER_STATE
{
SetDefaultFSMData();
}
// This function returns the pointer to the object that governs the
// message information coding (the pointer to the message interface).
// This automata instance works only with the standard messages
// (ID 0x00). If the caller specifies another type of coding,
// this function throws the exception TErrorObject.
// The message interface is defined in NetAutomata.h
MessageInterface *NetAutomata::GetMessageInterface(uint32 id){
switch(id) {
case 0x00:
return &StandardMsgCoding;
}
throw TErrorObject(__LINE__,__FILE__,0x01010400);
}
InitEventProc(IDLE,IDLE_START,(PROC_FUN_PTR)
&NetAutomata::NetAutomata_IDLE_START);
InitEventProc(IDLE,IDLE_MSG,(PROC_FUN_PTR)
&NetAutomata::NetAutomata_IDLE_MSG);
InitEventProc(MESSAGE,MSG_MSG,(PROC_FUN_PTR)
&NetAutomata::NetAutomata_MSG_MSG);
InitEventProc(MESSAGE,MSG_STOP,(PROC_FUN_PTR)
&NetAutomata::NetAutomata_MSG_STOP);
InitUnexpectedEventProc(IDLE,(PROC_FUN_PTR)
&NetAutomata::NetAutomata_UNEXPECTED_IDLE);
InitUnexpectedEventProc(MESSAGE,(PROC_FUN_PTR)
&NetAutomata::NetAutomata_UNEXPECTED_MSG);
}
SetMsgToAutomata(FSM_TYPE_AUTOMATA);
SetMsgToGroup(INVALID_08);
SetMsgObjectNumberTo(idToMsg);
sendToTCP();
SetState(MESSAGE);
}
void NetAutomata::NetAutomata_IDLE_MSG(){
idToMsg = 0;
GetParamDWord(COUNT,msgNumber);
printf(“Text received: %s\n”,text);
void NetAutomata::NetAutomata_MSG_MSG(){
GetParamDWord(COUNT,msgNumber);
msgNumber++;
void NetAutomata::NetAutomata_MSG_STOP(){
printf(“Stop automata: %u\n”,GetObjectId());
SetState(IDLE);
}
void NetAutomata::NetAutomata_UNEXPECTED_IDLE(){
printf(“Unexpected message in the state IDLE \n”);
}
void NetAutomata::NetAutomata_UNEXPECTED_MSG(){
printf(“Unexpected message in the state MESSAGE \n”);
}
void NetAutomata::StartDemo(){
uint8 *msg = GetBuffer(MSG_HEADER_LENGTH);
SetMsgFromAutomata(FSM_TYPE_AUTOMATA,msg);
SetMsgFromGroup(INVALID_08,msg);
SetMsgObjectNumberFrom(0,msg);
SetMsgToAutomata(FSM_TYPE_AUTOMATA,msg);
SetMsgToGroup(INVALID_08,msg);
SetMsgObjectNumberTo(0,msg);
SetMsgInfoCoding(0,msg); // 0 = StandardMessage
SetMsgCode(IDLE_START,msg);
SetMsgInfoLength(0,msg);
SendMessage(MBX_AUTOMATA_ID,msg);
}
uint16 NetAutomata::convertNetToFSMMessage(){
// Manipulate only data because automata sends the new
// message to itself.
int length = receivedMessageLength-MSG_HEADER_LENGTH;
// Rotate bytes
uint16 msgCode = GetUint16((uint8*)(protocolMessageR+MSG_CODE));
switch((msgCode)){
case IDLE_START:
msgCode = IDLE_START;
break;
case IDLE_MSG:
msgCode = IDLE_MSG;
break;
case MSG_MSG:
msgCode = MSG_MSG;
break;
case MSG_STOP:
msgCode = MSG_STOP;
break;
default:
msgCode = 0xffff;
}
return msgCode;
}
void NetAutomata::convertFSMToNetMessage(){
// Here we send the whole message.
memcpy(protocolMessageS,fsmMessageS,fsmMessageSLength);
sendMsgLength = fsmMessageSLength;
}
uint8 NetAutomata::getProtocolInfoCoding(){
// Standard msg info coding
return 0;
}
transition from IDLE to MESSAGE by calling the function SetState and spec-
ifying the value MESSAGE as its parameter.
The function NetAutomata_IDLE_MSG handles the message IDLE_MSG in
the state IDLE. First, it prints the received message to the monitor. It then
prepares and sends the message with the code MSG_MSG to its peer by
calling the function SendToTCP and sets the current state of this automata
instance to the value MESSAGE.
The function NetAutomata_MSG_MSG handles the message MSG_MSG in
the state MESSAGE. First, it gets the message sequence number from the
received message and increments this value. It then checks if the new value
of the message sequence number has reached the given limit. If not, this
function prepares and sends the message MSG_MSG to its peer at the remote
FSM system by calling the function SendToTCP. If it has reached the limit,
this function prepares and sends the message MSG_STOP to its peer at the
remote FSM system and sets the current state of this automata instance to
IDLE.
The function NetAutomata_MSG_STOP handles the message MSG_STOP
in the state MESSAGE. It is fairly simple and just sets the current state of
this automata instance to IDLE. The unexpected event handlers in this exam-
ple just print the warning messages. In real-world applications, these func-
tions would trigger some higher-level recovery mechanisms. The function
StartDemo creates the first message in the system. It fills in its header as if
the automata instance with the identification 0 had sent that message to itself
and sends this message to the mailbox assigned to this automata type.
The function convertNetToFSMMessage just copies the payload of the exter-
nal message received from the remote FSM system to the current FSM system
internal message (the last received message), because in this simple example
the two communicating instances have the same IDs and no need exists for
any mappings between them. The pointer fsmMessageR points to the current
internal message, the pointer protocolMessageR points to the current external
message, and the variable fsmMessageRLength is equal to the payload size of
the current external message. At the end, this function determines the mes-
sage code and returns it as its return value.
The function convertFSMToNetMessage copies the whole new internal mes-
sage to the new external message and sets the value of its length. The pointer
fsmMessageS points to the new internal message, the pointer protocolMessageS
points to the new external message, and the variables fsmMessageSLength
and sendMsgLength contain their lengths.
The function getProtocolInfoCoding returns the code of the standard mes-
sage coding (code 0x00) used for coding external messages. Note that in this
simple example, both internal and external messages are actually standard
messages.
File Constants.h:
// FSM
#define FSM_TYPE_AUTOMATA 0
// MBX
#define MBX_AUTOMATA_ID 0
#define MAX_MSG_NUM 10
#define COUNT 1
#define PARAM_TEXT 2
#define IP_ADDRESS “192.168.0.57”
#define PORT_1 7000
#define PORT_2 8000
enum AutomataStates {
IDLE = 0,
MESSAGE,
};
enum Messages {
IDLE_START = 0,
IDLE_MSG,
MSG_MSG,
MSG_STOP
};
File Main.cpp:
#include “conio.h”
#include “Kernel/fsmsystem.h”
#include “Kernel/LogFile.h”
#include “NetAutomata.h”
// If the following line is not commented out we get the code for the
// server listening to the port number PORT_1.
// If the following line is commented out we get the code for the
// server listening to the port number PORT_2.
#define AUTOMATA1
#endif
// Start the FSM system.
printf(ìStart the FSM system...\n”);
try {
fsmSystem.Start();
}
catch(...) {
OutputDebugString(“Exception - stop the FSM system...\n”);
return 0;
}
OutputDebugString(“The end of the operation.\n”);
return 0;
}
The file Main.cpp starts with the list of the necessary include files and the
definition of the symbolic constant AUTOMATA1. This constant should be
defined for the local process and not for the remote process (this is done by
commenting out the source code line that defines the symbol AUTOMATA1).
Next, the instantiation of the class FSMSystemWithTCP is performed by a
call to its constructor. The parameters used in this call specify that the
instance of the FSMSystemWithTCP, named fsmSystem, will include a single
automata type and this automata type will use a single mailbox. After that,
a single instance of the class NetAutomata is made, instance_1. Additionally,
this file contains the definitions of the FSM system thread function Thread-
Function and the function main.
The function ThreadFunction first prepares the data needed to define three
buffer types. The sizes and quantities of these buffers are five at 128 bytes,
three at 256 bytes, and two at 512 bytes. Next, the three automata instances
are added to fsmSystem. Note that the fourth parameter of the first call to
the function Add is set to the value true, which means that the instances are
to be used as a pool of instances of the same type. After that, this function
initializes the kernel by calling the function InitKernel, defines and sets the
logging interface by calling the function SetLogInterface, starts the TCP server
by calling the function InitTCPServer, and starts the fsmSystem by calling its
function Start.
The function main starts the FSM system thread (which executes the func-
tion ThreadFunction) and suspends itself for 100 ms. After this, it waits for
the user command. If the user presses the character ‘E’ or ‘e’, it establishes
the TCP connection with the remote TCP server by calling the function
establishConnection. If the user presses the character ‘Q’ or ‘q’, it terminates
the program.