InterAgent Communications Reference Manual

Version 0.5.0, last updated 12 April 2000

F.G. McCabe fgm@fla.fujitsu.com">fgm@fla.fujitsu.com


GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.
675 Mass Ave, Cambridge, MA 02139, USA

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  1. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
  2. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
  3. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
    1. You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
    2. You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
    3. If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
    These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
  4. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
    1. Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
    2. Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
    3. Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
    The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
  5. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
  6. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
  7. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
  8. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
  9. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
  10. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
  11. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

    NO WARRANTY

  12. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
  13. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

one line to give the program's name and an idea of what it does.
Copyright (C) 19yy  name of author

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type `show w'.  This is free software, and you are welcome
to redistribute it under certain conditions; type `show c' 
for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright
interest in the program `Gnomovision'
(which makes passes at compilers) written 
by James Hacker.

signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.

Introduction

The InterAgent Communications Model (ICM) is a model of communication which is oriented towards the needs of inter-agent communication. This manual introduces the ICM, shows how it can be used and gives a complete listing of the Application Programmer's Interface (API) to the ICM.

Some of the key features and benefits of this model are:

  1. A store and forward architecture which allows truly asynchronous communication between agents: the receiver agent need not be on-line when a message is sent to it.
  2. Messages are encoded in an efficient structured way; permitting lists, tuples, strings, program code and circular structures to be represented.
  3. The self-parsing nature of the encoding permits open and extensible forms of communication without requiring the use of predefined message format information to be compiled into applications. It also permits additional message oriented services to be added to the ICM.
  4. The use of communications servers permits individual agent platforms to communicate with large numbers of different host computers while minimizing the use of system resources.
  5. The use of globally unique agent handles permits the ICM to route messages without requiring that every agent platform has the skill to route all forms of message.
  6. The use of agent handle aliasing permits agent migration in a well controlled straightforward way.
  7. The use of virtual hosts allows the use of intermittently connected devices such as Personal Digital Assistants and dial-up computers, in a way that is largely transparent to individual agent platforms and agent applications.
  8. The ICM API permits multilingual access to the agent platform via message communication.
  9. The ICM sits logically `below' an agent communication language: it specifies how messages may be constructed and transported between agent platforms; while allowing powerful KQML-style messages to be built in terms of the ICM's simpler components.

The InterAgent Communications model

This manual describes a communications model that is tailored to the requirements of inter-agent communication; particularly in the context of Internet-based agents. The ICM is based on a store-and-forward messaging architecture not unlike the e-mail model of interpersonal communication. Like e-mail, the ICM relies on globally unique identifiers which are ultimately based on an agent's geographic location; however these identifiers refer to active processes rather than people. Also like e-mail, the ICM permits truly asynchronous communication between agents: agents do not need to rendezvous with other in order to communicate. However, unlike e-mail, ICM messages are structured: they can contain strings, numbers, records, lists of numbers and strings, executable code and even circular and arbitrary graph structures. This allows a complex message to be sent without expensive and error prone parsing of a text message.

Apart from the basic messaging service, the ICM also supports mobile agents -- agents can be cloned and tracked as they move between host computers - and it also supports agents running on mobile computers. A mobile computer -- or more accurately an intermittently connected computer -- offers special opportunities for agents: a mobile agent may be `born' on a PDA; `hop' from the PDA on to the Internet where it may move from host to host as it performs its tasks and then at some later date `hop' back on to the PDA where the owner can recover the results of the agent's work. The ICM supports this model and also allows the agent to be in communication with other agents and agent resources wherever it is located.

An important consideration for any inter-agent model is performance and scalability. If large numbers of agents may be owned and operated by many people -- not all of whom necessarily belonging to the same organizations -- then it is important that any inter-agent communications systen is scalable to large numbers of agents scattered over the Internet as a whole. In addition, it is important that agents that can move are able to move between organizations as well as within a single organization. The ICM presented here is intended to meet these requirements.

The scope of the ICM is limited to the lower level aspects of inter-agent communication: the higher level format of those messages -- and the semantics attached to individual messages -- is not part of the scope of the ICM. Typically, messages will be structured using an Agent Communication Language -- possibly in terms of KQML or FIPA ACL. The ICM allows efficient support for such styles of messages but does not prescribe the format of ACL messages.

The ICM is built from three essential components: a low level message format that encodes the arbitrary data structures typically found in messages in an efficient way, a communications server that acts as the inter-agent mail service and an API that implements access to the ICM from various programming languages.

The ICM allows messages to be transmitted asynchronously -- the sender and receiver do not need to be executing concurrently for messages to be sent -- it allows agents to migrate between hosts and it allows communication with agents residing on intermittently connected computers -- such as Personal Digital Assistants.

Historical note

The ICM presented here is based on the April communications model. The main differences are that references to April have been removed and replaced with the more generic ICM label; and some of the details of the message format have been adjusted to allow more general program code to be transmitted in messages.

Basic communications model

Each host that can support inter-agent communications contains a special CS. The function of the CSs is to provide reliable inter-agent communication. Sending a message from one agent to another is normally a three stage process: from the agent to its local CS, then the message is forwarded to the recipient's local CS and finally the message is read by the recipient from its local cummunications server (CS). For the agents involved the intermediate communication steps are transparent: an agent sends a message to another agent in a single step, and receives messages from other agents also as a single action.

The CSs provide a number of benefits to agents involved in inter-agent communication:

Globally unique agent identifiers

The ICM uses a system of globally unique identifiers called handles. A handle is a combined structure that embeds within it a globally unique identifier for the agent, together with additional location information that serves to locate the actual address of the agent.

A handle may be written using an expression such as:

target:name@home/[locations]

where:

target
is a field that is used only within the agent itself; for example, to specify internal targets within the agent -- but it is not necessary for the purposes of the ICM itself.
name
is a name which is unique to the host -- no two agents may have the same name on a given host computer
home
is normally the name of the agent's home machine which is a unique Internet host name. The host name should be in the normal DNS-style of fully qualified host names.
locations
is a comma separated list of `possible' locations of the agent. Locations are of the form of one of:
DNS-hostname
a location may be a fully qualified DNS-style hostname; in which case the ICM uses DNS-lookup in order to determine the address of the CS machine that this name refers to. Note that, while convenient, under certain circumstances this may cause additional security risks.
IP address quartet
a location may be in the form of an IP quartet.(1) Such an address refers to the IP address of a computer on which a CS is executing.
protocol reference
a location may also be in the form:
protocol://address
This is a reference to an address associated with a different protocol. For example, an agent that communicates via email might use an location of the form:
email://myagent@general.com
The CS is able to route messages of any protocol provided that appropriate handlers have been `plugged in'.

The agent's name and home are always sufficient to uniquely identify the agent -- irrespective of the contents of the locations field. The purpose of the home field is to provide a means for a `naming authority' to issue names. It is not necessary for home to be an actual host name of a computer; however the various API functions that compute handles assume that it is a host name of a computer in order to compute default values for the locations field of the handle.

Semantics of the location field of a handle

There are several situations where a handle may have more than one location embeded within it: for example, where an agent is mobile and has moved to a different location, or where a host computer is using a CS that is not located on the computer itself -- it may be sharing the CS with several computers in a local area network.

From the point of view of the ICM CSs, the list of locations has a disjunctive semantics: in the case that an ICM CS receives a message for an agent that is not registered with it, it is sufficient for the CS to deliver the message to any of the locations identified in the list.

More specifically, the addresses in the list are taken in order: if a CS cannot contact a particular location -- it may be associated with a mobile computer, or it may be on the `wrong side' of a firewall -- then the CS will attempt the next in the list.

Where a CS is required to forward a message, the message is forwarded to the first CS in the list that is able to handle the message. Again, if there is no such CS, then the message is held until a suitable CS registers with this CS.(2)

If the list of locations is empty, then it is understood that the message is intended for an agent on the same host computer as the CS. The CS is then able to make a `final' determination of the message: it can either deliver the message, hold the message or reject the message.

Basic steps to communication

For an agent to send a message to another agent, the sender agent formats the message using the ICM's low level message format and then sends the message to the local CS. Depending on the particular platform architecture and implementation language for the agent, this can often be achieved in a single call to the ICM API:

icmFmtSendMsg(conn,toAgent,fromAgent,opts,"%(say%*s%)",
              listLength(itemList),itemList);

(assuming that the agent is written in `C') where itemList is a `C'-level list of char * pointers. The string "%(say%*s%)" is a format string; along the lines of the format string in other standard C library functions, which describes the structure of the message. The particular details of how a message is described necessarily depends on the programming language used to access the API. In higher level languages, such as Prolog, a message send can be expressed more simply:

:-icmSendMsg(toAgent,fromAgent,[],say([goal,foo]))

In April this is accomplished even more simply:

say([goal,foo]) >> toAgent

Once the icmSendMsg is executed (see section icmSendMsg), the sender agent can immediately continue with other activities: there is no need to wait to see if the receiver has received the message.

The local CS has to be able to handle incoming and outgoing messages from multiple sources. In particular, if a long message is being sent, the CS must be able to handle it and handle other short messages from other agents on the same host at the same time. This is an important attribute since it prevents a rogue agent from denying service to other agents simply by sending a very long message.

Once the CS has consumed a message it must determine the location of the recipient. In the case of an agent on the same physical host then the CS stores the message until it is called for by the receiving agent. If the message is intended for a different host, then the CS `contacts' the CS on the remote host and forwards the message to that CS. When the remote CS receives the message it stores the message until it is called for by the receiver.

When an agent is ready to handle a message it simply reads the message from its local CS. Within the ICM API this can be handled with a icmWaitMsg call -- see section icmWaitMsg:

icmWaitMsg(conn,&toAgent,&fromAgent,&opts,
           "%(say%*#%)",&listLen,itemList)

which waits until a message that matches the pattern `"%(say%*#%)"' has been received over the previously opened connection. The pattern denotes a message that has the `shape':

(say, [...])

If a message is received that matches the pattern, the variables toAgent, fromAgent and opts are updated with the recipient agent's handle (presumably the handle of the program that is executing the call to icmWaitMsg), the sender agent's handle and an opaque pointer to a list of options which give additional information about the message received (see section icmOption).

Alternatively, the next available message can be retrieved using icmGetMsg (see section icmGetMsg). Messages which have been received using icmGetMsg or sub-messages which are extracted from received messages can be further analysed using icmScanMsg (see section icmScanMsg). For example the above icmWaitMsg function call can be approximated by first of all `getting' a message with icmGetMsg:

icmGetMsg(&toAgent,&fromAgent,&opts,&msg,&seqNo)

followed by a call to icmScanMsg to analyse the returned message:(3)

if(icmScanMsg(msg,"%(say%*#%)",&listLen,itemList))...

The icmWaitMsg/icmScanMsg functions both match the message against a particular pattern and converts the contents of the message into a format suitable for further processing by the programming language. Further uses of the icmScanMsg function on the individual elements of the `itemList' can further break down the structure and extract relevant data.(4)

The ICM API allows the same message buffer to be scanned in alternative ways -- allowing a receiving agent to receive a message and then determine the content of the message.

Communications servers and message handling

A given ICM CS can receive messages which are not ultimately intended for it.(5) When a CS receives a message it has several options available to it:

It is the purpose of this section to detail the precise rules that a CS should follow when receiving messages.

Receiving messages for registered agents

When a CS receives a message for a registered agent then -- by virtue of the fact that the agent has registered with this CS -- the CS has independent confirmation of the address for that agent. The CS should hold the message until the agent reads it -- this can be handled by attempting to send the message to the agent and blocking until the agent actually reads the message.(6)

Note that even though the agent may be registered with this CS, the message will still require forwarding to another CS.

Receiving messages for non-registered agents

When a CS receives a message for an agent that is not registered with it, then the CS must attempt to forward the message -- typically to another CS. The selection of which CS to forward the message to is based on the locations field of the recipient's handle -- as identified within the message structure itself -- see section Message envelope.

A handle has associated with it a list of potential locations that may receive the message. The CS searches this list -- from left to right -- attempting to contact a CS at the identified locations.

The first `remote' CS that is reachable is used.(7) The message is forwarded to the remote CS in the same fashion that a message is delivered to an agent: by sending the message and blocking until the remote CS actively reads it.

Note that if the list of locations associated with the recipient agent handle is empty, then the CS may reject the message or hold the message for some period of time. If the CS rejects the message, it simply discards it and sends a rejection message to the sender of the message:(8)

(icmFailed, sender, recip, msg)

If the CS holds the message, at a later stage, the agent that is responsible for receiving the message can register with the CS -- giving the CS a means of delivering its responsibility.(9)

Message loops

Note that when a message is transferred from a CS to a new CS the `recipient' handle which is passed within the message should not be identical to the recipient handle as received by the CS. The location of the current CS should be removed from the list of locations associated with the recipient's handle. This prevents a circular loop from forming whereby messages are forwarded endlessly between CS whilst never reaching their intended destination.

Message ordering

While it would be desirable to ensure that messages arrived in order -- it is difficult to ensure this in a context where agents may be mobile, or where mobile computers are involved.

In the case of mobile agents, when an agent sends a message to a mobile agent that has moved, some message forwarding must take place. However, while a message is forwarded, the mobile agent may have `updated' its communications partner, and therefore a subsequent message could be sent direct to the mobile agent's new (and real) location. Due to the potentially lengthy process involved in redirecting messages, the direct message is liable to reach the mobile agent first. Furthermore, when a mobile agent de-registers from one CS on its way to another, the first CS may no longer have the responsibility or ability to forward messages to the mobile agent -- in which case it may reject such messages.

In order to `solve' this problem, agents would need to keep track of the number of messages they have ever sent to other agents; and they would have to keep track of the number of messages sent to them by other agents. By doing this, an agent could re-impose an order on incoming messages. However, this would greatly increase the burden on agents -- and the ICM system -- for all messages; and unfortunately, is prone to a number of other problems (such as recovering these tables when an agent is restarted after having failed).

In the case of mobile computers, the CSs on intermittently connected computers are not guaranteed to be on-line all the time. This also leads to message re-routing on a dynamic basic, with the consequent problems in ensuring message ordering.

For these reasons, it is not required that an ICM system ensure that messages arrive in the order they are sent. On the other hand, we do expect a `best effort' to make sure that messages are delivered in order.

For those situations where it is important that a conversation between two agents preserves the order of messages, this can readily be achieved by adding counters to the messages -- in the message body. These counters should be maintained by the application -- either directly or via some kind of message filter. Care needs to be exercised by the application constructors that these message counters can be preserved where necessary.

Intranets and Extranets

In some situations it may be desirable to allow messages between agents to `penetrate' firewalls. There are many ways to achieve this; for example, by so-called `HTTP tunelling' for example, where an ICM message send/receive is re-packaged as an HTTP `PUT'/`GET' combination. However, such tunelling techniques deprive the system administrators of an opportunity to actively control ICM message traffic.

ICM communications servers and firewalls

An alternative method is to use the ICM handle's facility to contain more than one location. The technique is based on placing an ICM CS on the `firewall' machine.

A key feature of a firewall machine is that it is able to simultaneously `see' hosts on both sides of the firewall -- on the external Internet and on the internal Intranet. On the other hand, hosts within the Internet are not able to see hosts inside the firewall, and -- on some configurations -- vice-versa: hosts on the Intranet cannot see hosts on the Internet at large.

Sending a message to an agent on an Intranet host

When an agent -- which is resident on an external host -- wishes to send a message to an agent on an Intranet-based host it must use a handle which mentions both the actual location of the internal host and the location of the firewall host:

msg >> agentB@dest.com/[dest.com,firewall.com]

The CS on the source host machine will not be able to directly contact the CS on the dest host machine; and therefore will not be able to deliver the message directly. However, since the location of the firewall machine is also in the handle, and since it is accessible from the outside, the source machine's communication server will be able to deliver the message to the CS on the firewall machine.

The CS on the firewall machine will receive the message and since the recipient is not registered with the firewall machine will attempt to deliver the message. This time however, the internal dest machine is accessible to it, and can therefore deliver the message to the dest machine.

Sending messages from an Intranet host to an external host

Conversely, if an agent which is within an Intranet wishes to send a message to an agent on the external Internet, is constructs a handle for the external agent which refers to the firewall machine location as well as the desired destination machine.

When the Intranet-based CS attempts to locate the external host it will not be able to -- assuming that external hosts are hidden from within the firewall. By default, the message is forwarded to the firewall machine which then forwards it to the external host.

Multiple firewalls

The primary benefit of this architecture -- compared to various tunelling techniques -- is that it allows system administrators to control the message traffic more transparently. For example, by attaching various filters to the CS, a system administrator can prohibit messages which contain program code fragments, or it may restrict external access to a sub-set of the Intranet's host computers.

In addition, this strategy scales well to situations where there are mulitple layers within the Intranet: with firewall machines acting as barriers between each layer of the Intranet.

In this situation, when an agent needs to contact agents outside its immediate Intranet region, it must use a handle which mentions the required intermediate firewall locations.

Note that in a typical situation -- of an internal agent having a conversation consiting of a sequence of message with an external agent -- typically only the originating agent that needs to know about the intervening firewalls. The external agent simply uses the replyto field of a message when making replies -- and this handle may be constructed to refer to all the required firewall machines.

Supporting mobile agents

Mobile agents are -- from the point of view of the ICM -- executing entities that can move across host computers. There may be many applications where mobile agents represent a natural technology for solving certain classes of problems. On the other hand, executing `foreign' programs represents a significant security risk that may outweigh many of the benefits. It is not our intention to justify the utility of mobile agents -- we identify here some of the issues involved -- from the point of view of communications systems -- and show how the ICM can support agent mobility.

In order to support mobile agents, a number of issues need to be resolved: the executable code of the agent -- together with its execution state -- must be transferred between one host and another; in addition is it necessary to transfer `permission' to execute and access resources on a remote host, and it must be possible to redirect communications so that any messages for the agent are correctly delivered to the agent's new location. It is the last issue which raises the most concerns in any inter-agent communications system.

It is not possible to handle the questions of transferring code and execution rights without consideration of the particular platforms involved. Generally, a single language platform makes it much simpler for agents to be fully mobile. The ICM does not itself address these issues, although the low-level message format is designed to allow executable programs to be transferred using inter-agent messages.

On the other hand, certain aspects of an agent's movements are properly handled by the ICM: in particular the reliable re-direction of messages to an agent's new location and tracking the current whereabouts of the agent. This reduces to managing an agent's identity -- in so far as an agent's identity can be linked with its ability to send messages and to receive messages.

Agent identity and agent mobility

Recall that the pair agent name and home serve to uniquely identify an agent. This is true even when the agent has moved. However, when an agent has moved, its location will probably not be the same as home: we still require an ability to send messages to the mobile agent wherever it is actually located. We can do this by combining dynamic registration of a mobile agent and extended use of the list of locations in an agent's handle.

Normally, for static agents, the `minimal' handle for the agent contains a single location -- the location of the agent's home computer. For mobile agents we generally require at least two locations: the current address of the agent and its home base location.

When a mobile agent sends a message to another agent, it will generate a return handle which contains its current address and its home base address. The replying agent can then use this handle to send a reply. If the mobile agent's handle mentions its current location first then the reply will normally go to that location. However, there is always the possibility that a mobile agent moves before a reply message can be sent -- which is why we also need the mobile agent's home base in the handle.

There are two sub-cases for mobile agents that we must consider, and at least one major policy issue: the mobile agent's home base may not itself be stable -- it may be only intermittently connected to the network -- and we must decide policy for redirecting messages to an agent that has moved.

Static home bases

A static home base is one which can be reasonably assumed to be on-line for the duration of the mobile agent's lifetime. In that situation, we can use the home base location as follows:

Suppose an agent (B) moves from its home base (HB) to a new base (CB), and while there sends messages to another agent (A), and before A has an opportunity to reply to B, B moves to a new location (CB'). The handle that A has of B looks like:

B@HB/[CB,HB]

whereas its `correct' handle is really:

B@HB/[CB',HB]

When A's communication server sends A's reply message to the communication server on CB, B will no longer be registered there.(10) According to the message forwarding rules identified in section Communications servers and message handling, the communication server on CB will therefore deliver the message to the communication server on HB.

By dynamically registering its real address with the HB's communication server, in the form of a special forwarding message, the agent can ensure that HB's communication server can forward the message to B's current location. The ICM supports this by allowing agents to register forwarding addresses with CSs.

Thus, for the most part, we expect that a mobile agent will receive replies directly at its current location; but in the case that the agent moves before it can receive the message, the message will be redirected with at most one additional `hop'.

An agent registers a forwarding address with another agent simply by providing a new `default' handle -- with a new list of locations -- for the agent.

Intermittent home bases

An intermittent home base is one which cannot be reasonably assumed to be on-line for the duration of the mobile agent's lifetime. For example, if an agent's home base is a PDA, then we can easily assume that the PDA may be disconnected from the network while the mobile agent is executing. In fact, dealing with such home bases is one primary motivation for allowing mobile agents: it allows user's to perform network-tasks while they themselves are off-line.

In order to allow a mobile agent to continue to reliably receive messages even while it moves, we can use a variation of the technique for static home bases: the mobile agent `designates' a proxy computer -- which is going to be permanently available on the network.

In order to use a proxy, the agent registers forwarding addresses with the proxy, and ensures that the proxy's location is included with the locations in the mobile agent's handle.

Forwarding addresses can be set up using special API functions -- which translate into messages sent to the CS itself which are commands to construct and modify an agent's forwarding address.

Address forwarding

In order for the ICM to be able to redirect messages to a mobile agent, it must be possible for an agent to explicitly inform a CS of its location. When an agent registers with a communication server -- see section icmRegisterAgent -- it also establishes the agent's initial location.

The icmRequestForwarding API function -- see section icmRequestForwarding -- can be used by an agent to request that messages be forwarded.

Moving an agent

The process by which an agent moves is slightly elaborate in order to protect the agent and the new host computer. We can describe this in terms of a sequence of messages between the agent, its home base (or proxy) CS and the execution server on a remote computer that will be responsible for executing the mobile agent:

  1. Agent `A@HL', which is currently executing on `H1', sends a package of code and remote execution request to remote host `H2'.
  2. After verifying the code and permissions, the remote host executes the code as agent `A@HL/[H2,HL]'.
  3. `A@HL/[H2,HL]' sends a rendezvous message to `A@HL/[H1,HL]'
  4. `A@HL/[H1,HL]' requests to its local CS that it set up the forwarding address: `A@HL->[H2,HL]'. At this point `A@HL/[H1,HL]' can no longer receive any messages. Note that for security purposes, only the agent itself may instruct a CS to set up an forwarding address for itself.
  5. At the same time as requesting the address forwarding, `A@HL/[H1,HL]' collects any outstanding messages from its local CS.
  6. A package of any outstanding messages and an `ok' message is sent by `A@HL/[H1,HL]' to `A@HL/[H2,HL]'.
  7. `A@HL/[H2,HL]' continues processing outstanding messages and the goals of its mission.
  8. The old incarnation of the agent -- `A@HL/[H1,HL]' -- has the option of terminating or `sleeping' until its new incarnation -- `A@HL/[H2,HL]' -- returns to its home base. Although the original agent could continue executing; due to the presence of the forwarding address on its local CS it can receive no messages from outside; furthermore, if additional access permissions were transferred to the new location then its own access to resources will similarly be constrained. However, there are advantages to an agent sleeping until the return of its wandering self -- for example, the final return of the agent need not contain any executable code -- just a package of result data.

In order for an agent to return, all that is necessary is for the returning agent to cancel the forwarding address and to send a final `home-coming' message to its original incarnation. If the original agent is still present then, when the forwarding is cancelled, it immediately becomes available to receive messages -- including the final homecoming message sent by the returning agent.

In the case that the original agent terminated itself, an agent coming home is very similar to an agent migrating to another host: the agent must send a package of code to its home base and negotiate with its home base for permission to execute code. The main difference is that instead of a new forwarding address being set up, the original forwarding is cancelled.

Issues to do with message forwarding and agent migration

There are a number of special considerations to do with agent migration:

Missed messages

When an agent is en-route between hosts it is potentially incommunicado since it is neither executing on its original host nor on its new host. Clearly, no en-route agent is in a position to send any messages; however the issue is what happens to messages sent to the agent while it is en-route? The ICM with its combination of a store-and-forward architecture and the protocol for agent migration outlined above means that messages are not lost. Any messages which are sent to an agent while it is en-route can be read by the agent once it has re-established itself at its new host.

Message ordering and mobile agents

When an agent moves rapidly -- while is it involved in coversations with another agent -- it may give rise to situations where messages arrive out of sequence.

Consider the case that an agent A, currently located on host C1, is in conversation with an agent B (which may be anywhere). Agent B `believes' that agent A has as its handle A/[C1], and so if it sends a message to A it will use that handle.

However, suppose that before B sends this message, agent A moves to host C2 -- and then it informs B of its new location after agent B has sent a message to agent A's old address. Agent B then sends a second message to agent A at its new address.

Since agent A is no longer at host C1, the first message that agent B sent to agent A will go via agent A's home location -- since the CS on C1 is no longer responsible for agent A's messages. The second message will go directly to agent A at its current location -- which may be a shorter route than for the first message. As a result, the second message may arrive at agent A before the first message.

Since this is potentially a distributed environment, where the host computers C1 and C2 may be a long distance apart, and may be owned by different people, it is not possible to guarantee message ordering for mobile agents. For this reason, where it is important that agents A and B preserve the ordering of messages between them, they must use a `higher-level' protocol on top of the normal message communications.

Such a message ordering protocol can be readily implemented by the two agents if they include in each message a sequence number which represents the number of messages sent to each other: every message from agent A to agent B incorporates a number which identifies how many messages A has already sent to B. Conversely, messages from agent B to agent A have a similar sequence number. Using this number the two agents can ensure that their message traffic is correctly synchronized no matter how often agent A moves.(11)

Double identity

An agent which has migrated one or more times must necessarily have a double -- or even triple or more -- identity: its original name and the name(s) it acquires on migration. This makes certain operations more difficult; in particular if another agent relies on the agent's handle as a way of ensuring integrity of communications then there may be a problem. Consider the simple scenario where an agent moves while another agent sends it a message:

  1. A@HL/[HL] sends message M to B@H2/[H2]
  2. B@H2/[H2] migrates to B@H2/[H3,H2]
  3. B@H2/[H3,H2] reads the re-directed message M from A@HL/[HL] and sends a reply R.
  4. A@HL/[HL] receives reply message R from B@H2/[H3] when it is expecting a reply from B@H2/[H2].

Since agent A has no direct control over agent B, and since they may be executing on different computers, agent A has to be able to verify that the reply came from a legitimate version of B and not an imposter. Unfortunately, it is not possible for agent A to be able to rely on the communications channel to ensure that the reply message really did come from a legitimate source. In order to ensure this, all messages between the migrating agent B and other agents should incorporate the original identity of the agent -- possibly in encrypted form.

Agent tracking

Agent tracking refers to finding out an agent's current location. In principle this can be done simply by requesting that the CS -- which has the agent's current location encoded as an alias -- resolve an agent handle into an actual aliased handle. However, there are certain fundamental problems due to the asynchronous nature of a distributed system. In particular, it is always possible for an agent to move after another agent has requested its location.

Lost agents

An agent may disappear from a host before cancelling all the aliases that it has established. This is particularly true for migrating agents since they rely on their host computers for executon time, and an agent may not be executing on its owners computer. This is a more general case of an agent disappearing from a CS without properly deregistering itself.

In the last resort, a CS can handle the latter case when the agent platform on which an agent is executing itself disconnects from the CS or terminates abnormally. In that situation, the CS is able to automatically deregister and remove all aliases pertaining to agents executing on the failed agent platform. However, that is a relatively crude mechanism and is not always sufficient.

An alternative method is to establish monitors on particular agent handles; and to allow remote monitoring of agent handles. A monitor on an agent handle is a promise -- on the part of the agent platform to another party -- that when a given agent terminates -- whether normally or abnormally -- then the agent platform itself will send a message indicating that the monitored agent has terminated.

Once a system of agent monitors is in place for agent platforms, it is straightforward to extend it to remote monitoring: the CS network can forward monitoring requests and termination signals.

The ICM can certainly use an agent monitoring system, but it relies on the `honesty' of individual agent platforms to fully benefit from it. In particular, a CS can establish a monitor on the `target' of an agent alias. When the aliased agent terminates, the CS may automatically remove any alaises established that point to the aliased agent; potentially allowing the original agent to resume.

Supporting mobile computers

An intermittently connected computer is one which is not permanently connected to the Internet. Some examples of such devices are Personal Digital Assistants (PDAs) and computers which only have dial-up access to the Internet. The ability of an agent to move between hosts is especially useful in the context of such devices: it allows an agent to perform a task independently of the agent's user.

The primary problem to be solved in supporting intermittent connections is the safe handling of messages to agents which are currently off-line. This problem is similar to the problem of handling messages for agents which are busy or inactive; however, in the case of a disconnected PDA, it is the PDA's CS which is off-line.

The strategy that we have outlined earlier of storing-and-forwarding messages on CSs helps but is not completely sufficient: if an agent is off-line then the receiving CS is able to buffer the message until the agent comes back. If the agent's CS itself is off-line then we must use an alternative place to store messages.

The solution is to introduce another, permanent, CS into the network; the role of this CS being to collect messages for host computers which are currently off-line until their return. In the ICM this is achieved by using a `virtual host' and attaching a CS to that virtual host. In effect, the CS on the PDA itself registers with the permanent CS; at the same time whenever an agent registers with the PDA's CS, teh agent is informed of the permanent CS also.

For example, if a PDA device has a host name of pda.someone.com then we might establish a virtual host with the name icmhost.someone.com. An agent registering with the CS on pda.someone.com is given two locations: the location of pda.someone.com and the location of the virtual host: icmhost.someone.com. Any messages that the agent on pda.someone.com sends to another agent will have both locations; and therefore any agent making a reply will have the use of an agent handle with at least those two locations.

If a CS on a remote host is required to forward a message to an agent on pda.someone.com -- when it is off-line -- then the message is re-directed to the CS on the virtual host -- icmhost.someone.com -- instead. This is achieved by the sender's CS: when this CS attempts to forward messages to the CS on pda.someone.com it is unable to; therefore the sender's CS attempts a different location in the handle -- the location of icmhost.someone.com.

Since the CS on pda.someone.com registered with icmhost.someone.com's CS, the latter is already primed to keep the message until pda.someone.com's CS re-attaches itself. At that point, messages can be forwarded to the CS on the PDA and ultimately the agents on it also. Conversely, any messages sent by agent onboard the PDA while it was off-line can now be sent as normal.

Note that this architecture permits inter-agent communication even when both agents are on separate PDAs: at each stage in the communication the message is buffered while waiting for the sending PDA to be connected, or waiting for the receiving PDA to be connected or both. Note also that this service of supporting PDAs and similar devices is completely transparent to both sending and receiving parties of a message: there is no need for a special form of message send.

Invoking the communications server

It is necessary to run a copy of the communications server on each host that will be running agent programs. However, it is only necessary to have a single communications server on a given host -- no matter how many agent applications will be running on that host.

On each host, the commserver is invoked using the command:

% icm

(where % represents Unix command prompt). This will check to see if there is already a commserver running in the local domain, and abort if there is one.

The icm is designed to be executed as a background process; when started, it detaches itself as a daemon and the icm command immediately terminates. Therefore it is safe to install it as part of the main Unix boot initialization process.(12)

Options when invoking icm

-P port
This option controls the port that the commserver will attempt to open and will use when communicating with applications using the ICM.
-n name
Override the default `name' of the CS. This is normally of the form:
icm@lipo.doc.ic.ac.uk
Using the -n option affects the agent part of this name -- in this case the name icm.
-l location
This extends the default domain location that the commserver is serving. Usually this information is extracted automatically by the commserver and is based on the full name of the host computer. Any number of `-l location' options can be used, and they are all appended together. This is useful where the host computer is not permanently connected to the network -- where the alternate locations are used as proxies for third parties to communicate with agents attached to this CS -- or where communications may require `going through' firewalls. Where the `-l' option is given, the communication server will automatically register itself with CSs at the indicated locations when it starts executing.
-L file
This overrides the default name for the `log file' that the commserver uses. The commserver is designed to execute as a `daemon' -- with no output to standard output. However, there are occasional log messages and these are written to a special log file -- by default this log file is `/var/log/icm.log'. Using the -L option overrides this. The special file name of - when used with this option, causes log messages to be sent to standard error -- in which case the icm will not detach itself as a daemon. The special log file name of + causes logging information to be sent to the standard system log. Note that the icm may generate a large number of log messages, and so it may not be advisable to use this option.

InterAgent Communications API

In this chapter we outline the complete API for the ICM. This API is described in terms of a C language binding; a Java, or C++ language binding should be immediately inferable from this description. This API focuses on the agent platform's interface to the ICM: these functions are intended to be invoked by the agent platform on behalf of individual agents that are supported by the platform. There is a related protocol for use between communications servers that is not part of the API but is part of the ICM standard.

Note that a compliant implementation of the ICM API is thread-safe. That means that all ICM API functions -- unless otherwise stated -- may be freely used by multiple threads in a single application.

General types

icmStatus

typedef enum{icmOk, icmFailed, icmEndFile, icmError} icmStatus;

Most of the functions in the ICM API return a status result indicating the success or otherwise of the call. Informally, icmOk means that the previous operation was a sucess, icmFailed means that the parameters to the API call were valid but the operation failed to complete and icmError means that an invalid operation was requested. icmEndFile is used when an end-fo-file or equivalent condition is detected.

icmHandle

icmHandle is an opaque type, and is used to identify agents to the ICM API.

typedef struct _icmHandle_ *icmHandle;

Agent handles can be explicitly created -- using the icmMakeHandle function -- see section icmMakeHandle -- and can also be created automatically as a result of receiving a message.

icmConn

icmConn is an opaque type returned by the ICM API function icmInitComms (see section icmInitComms) if a sucessful connection has been made. This connection value should be used by the agent platform whenever a message is to be received or sent using the ICM API.

typedef struct _icmConnection_ icmConn;

icmOpt

icmOpt is an enumerated type that is used to identify an option type. Its definition is:

typedef enum {icmLeaseTime,icmReplyto,icmReciptRequest,icmAuditTrail} icmOpt;

icmLeaseTime
This is used both for icmSendMsg and icmRegisterAgent. The value associated with this option is a Unix-style time value -- i.e., a number of seconds since Unix epoch or January 1st, 1970.
icmReplyto
This is used by icmSendMsg to override to the reciever of the message where any reply should be directed to; rather than the sender of the message.
icmReceiptRequest
This is used when the sender of the message requires that a receipt message is sent when the recipient agent has read the message. Note that it is difficult to be precise about when an agent actually reads a message; the ICM interprets the final delivery of the message contents to the TPC/IP connection opened by the agent's system as delivery of the message. Whether the agent reads the message that was `piped' in to it or not cannot be confirmed by the ICM.
icmAuditTrail
This is used by the sender of the message to request that a list of icm communications servers' handles be attached to the message. The receiver of the message may query this list for information about the routing of the message.

icmOption

icmOption is an opaque type that is used to encapsulate message send and other options.

typedef struct _icm_msg_option_ *icmOption;

icmMsg

icmMsg is an opaque type that refers to a message structure; as received by icmGetMsg or sent via icmSendMsg.

icmDataPo

icmDataPo values are used when `uninterpreted' values are extracted from or embedded in messages. Such a value can contain anything -- from a block of text to program code -- depending on the context.

typedef struct {
        long size;              /* length - in bytes - of data block */
        char *data;             /* pointer to the data block */
        } icmDataRec, *icmDataPo;

icmOpaquePo

icmOpaquePo values are used when `anonymous' or opaque values are extracted from or embedded in messages. Opaque values have a name and a value - the latter typically being a icmDataPo. These are represented in the structure:

typedef struct {
        char *name;              /* owner of the value */
        icmDataPo val;           /* pointer to the data block */
        } icmOpaqueRec, *icmOpaquePo;

icmIdleProc

This type refers to a procedure that can be invoked as part of the activities of icmEventLoop (see section icmEventLoop).

void (*icmIdleProc)(icmConn conn,void *client);

icmMsgProc

This type refers to a function value which is occasionally invoked on messages; particularly in the case that a watch has been set on a particular handle.

typedef icmStatus (*icmMsgProc)(icmConn conn,
                                icmHandle recip,
                                icmHandle sender,
				icmOption opt,
                                icmMsg msg, void *client);

When such a function is invoked, conn identifies the connection, recip, identifies the intended recipient of the message, replyto identifies the agent handle to send replies to and sender identifies the originator of the message.

Agent handle functions

These functions are used to allocate, analyse and release opaque agent handle objects.

icmMakeHandle

The icmMakeHandle function constructs an opaque agent identification handle for use by the ICM in other calls -- such as icmRegisterAgent.

icmHandle icmMakeHandle(char *target,char *name,char*home,
                        int count, char **locations);
target
The target field is an additional field that may be used to identify targets within an agent. If target is NULL, or the empty string, then a default, empty, decoration is assumed. target is not used directly by the ICM system but it permits multiple targets to be registered via a single entity within the ICM system.
name
The name field is the name of the agent -- which is expected to be unique to a given host -- it should always be a valid name.
home
The home field identifies the host to which the agent belongs, and locations is an count-sized array of string pointers that identify the possible locations of the handle. If host is NULL then the current or local host name is assumed.

The issuance of an icmHandle is no guarantee that the agent exists, or would be permitted to operate by the local CS.

The return value is an opaque identifier of the correct form for use in other ICM API calls. Until the handle is released -- using an icmReleaseHandle call -- the value returned by icmMakeHandle can be assumed to be stable.

icmSameAgentHandle

This function tests to see if two handles refer to the same agent. The target and location fields are not checked in this comparison.

icmStatus icmSameAgentHandle(icmHandle h1,icmHandle h2)

The return result from this function is:

icmOk
If the two handles do refer to the same agent. Recall that this means that the name and the home portions of the two handles are the same.
icmFailed
If the two handles do not refer to the same agent.
icmError
If either of the two handles are not valid.

icmAnalyseHandle

The icmAnalyseHandle function `unpacks' an opaque handle into its constituent components:

icmStatus icmAnalyseHandle(icmHande handle
                           char **target,
                           char **name,
                           char **home,
                           char ***locations, int *locCount)

The returned values -- name, home, locations and target -- reflect the basic components of an agent handle:

target
This argument -- if non-NULL -- is bound to a C string that identifies any target associated with the handle.
name
This argument -- if non-NULL -- is bound to a C string that identifies the name portion of the handle.
home
This is bound to a C string that identifies the home portion of the handle.
locations
This is bound to a char *** array that gives the possible locations associated with the handle. The number of locations is returned in locCount.
locCount
This gives the number of locations associated with the handle.

Note that the string pointers which are returned by icmAnalyseHandle address data which should not be modified by the calling application. To do so may cause unintended side-effects -- probably unpleasant ones.

icmAnalyseHandle returns icmOk if a valid handle was passed in; icmError otherwise.

icmKeepHandle

The ICM library tries to garbage collect handles that are created -- especially those created automatically during a message receive. The icmKeepHandle function informs the ICM library that a particular handle should be `kept' even if the library itself no longer has a need for it.

icmStatus icmKeepHandle(icmHandle handle);

When a handle has been marked in this way, it should be released using icmReleaseHandle -- see below -- when the programmer no longer needs it. The library keeps track of the number of times a handle is kept, and only actually releases the handle when it has been released a corresponding number of times.

icmReleaseHandle

The icmReleaseHandle function `releases' a handle that had been created -- either explicitly or automatically -- by the ICM API. In most situations, it should not be necessary to release agent handles as they are cheap to produce.

icmStatus icmReleaseHandle(icmHandle handle)

icmReleaseHandle returns icmOk if handle is a valid handle; otherwise it returns icmError. In either case, after a call to icmReleaseHandle, the handle is no longer a valid agent handle structure.

icmHandleName

The icmHandleName function `unpacks' an opaque handle and generates a string buffer based on the handle:

icmStatus icmHandleName(icmHande handle,char *buffer,int buffsize);

The handle's component parts are marshalled into a string buffer -- which is supplied as a parameter -- in a canonical form. The string buffer is formatted according to:

target:agent@home/[locations]

where target is the target (if any) associated with the handle, agent is the agent name, home is the agent's home base location and locations is a comma-separated list of the agent's possible locations.

If there is no target associated with this handle then the leading target: field is omitted. If the current location is not known, or is the same as the home base then the trailing /[location] field is also omitted.

icmHandleName returns icmOk if a valid handle was passed in; icmError otherwise.

icmParseHandle

The icmParseHandle function parses a string which represents a handle using the standard notation and returns a icmHandle corresponding to the string.

icmHandle icmParseHandle(char *buffer);

The string buffer should contain a representation of a handle in canonical form:

target:agent@home/[locations]

where target is the target (if any) associated with the handle, agent is the agent name, home is the agent's home base location and locations is a comma-separated list of the agent's possible locations.

If there is no target associated with this handle then the leading target: field may be omitted. Similarly, the list of locations may be empty, in which case the trailing /[location] field may also be also omitted.

icmParseHandle returns a non-null icmHandle if a valid handle description was passed in; NULL otherwise.

icmCommServerHandle

The icmCommServerHandle function is a convenience function which returns an icmHandle that refers to a communications server.

icmHandle icmCommServerHandle(char *host)

This function returns an icmHandle that can be used to communicate with the communications server located on the machine host. If host is NULL, then the local communications server is assumed.

The form of this handle is:

icmCommServer@host/[host]

where host is the name of the host machine -- as returned by icmMachineName -- see section icmMachineName.

Managing communications

These functions provide for connections to the local CS, registering agents with the local CS and managing agent aliases.

icmInitComms

The icmInitComms function initializes the agent platform's link to the local communications server.

icmStatus icmInitComms(int port,char *host,icmConn *conn)

this initializes the communications API, and attempts to establish an initial connection to the local CS. Normally, port will be zero and host will be NULL -- in which case the default communications port number -- 4549 -- and the communications server on the local host computer will be used.

If port is not zero then the CS that is listening to that port will be contacted. If host is not NULL, then the CS on host will be contacted instead of the local one.

The returned conn value is required for many of the ICM API functions: whenever the agent platform may send or receive a message. Typically, the agent platform will only require one connection to the CS; which will be held so long as the agent platform is functioning.

The return value of icmInitComms reflects the success of the connection attempt:

icmOk
The connection was successful, and messages may now be sent and/or received from the CS.
icmFailed
The required CS could not be contacted.
icmError
There was an error in one of the arguments to the call, or the connection to the CS was rejected.

icmInitComms also makes use of certain environment variables:

ICM_COMMSERVER
If the ICM_COMMSERVER environment variable is set, then this variable identifies the default host computer where the commserver is located. Normally, the current host computer is used to identify the location of the commserver.
ICM_COMMS_PORT
If the ICM_COMMS_PORT environment variable is set, then the default communications port associated with the communications server is read from this environment variable.

icmCloseComms

The icmCloseComms function closes the connection to the CS. Typically, this is done once by the agent platform just prior to the agent platform terminating.

icmStatus icmCloseComms(icmConn conn);

icmCloseComms has the effect of deregistering any agents registered with the CS using the conn connection -- unless those agents were detached first using icmDetachAgent (see section icmDetachAgent).

icmCloseComms returns icmOk unless there had been no prior successful connection to the CS, in which case it returns icmError.

icmDisconnect

The icmDisconnect function closes the connection to the CS in a way that can be re-started later. Any registered agents are not automatically deregistered as a result of a disconnect -- they are marked as being detached.

icmStatus icmDisconnect(icmConn conn);

Any messages which are due for any local agents will be held by the CS until a subsequent icmReconnect call. The communications channel is also closed as a result of the disconnect; however the conn is still valid and should not be discarded except through a call to icmCloseComms call -- see section icmCloseComms.

icmDisconnect returns icmOk unless there had been no prior successful connection to the CS, in which case it return icmError.

When the agent needs to re-attach itself to the communications server, it should invoke icmReconnect -- see section icmReconnect -- which will reestablish a connection to the communications server and automatically reattach the agents that were connected using this connection.

icmReconnect

The icmReconnect function reconnects an agent platform to the CS after it has been disconnected with an icmDisconnect call. Once the agent platform has successfully reconnected, it may download messages intended for agents that are onboard the platform.

icmStatus icmReconnect(icmConn conn);

icmReconnect returns

icmOk
If the agent platform had previously disconnected, and the CS accepts the reconnection.
icmFailed
If the CS is unwilling or unable to accept the reconnection of the agent platform.
icmError
If the agent platform had not previously disconnected properly.

icmDetachComms

This function can be used to request that the local CS should detach itself from the network. This may be important when, for example, the physical host computer is also detaching itself from the network.

After detachment, any messages for any agents registered with with local CS will either be forwarded to other locations -- depending on whether the handles of the recipients included alternative addresses -- or held over until the communications server reconnects.

icmStatus icmDetachComms(icmConn conn);

icmDetachComms returns

icmOk
The CS detached itself from the local network. Inter-agent communication may still continue within the local host; but no messages to or from other host computers will arrive at their destination -- they will be held until the CS is reattached to the network.
icmFailed
The CS was already detached.
icmError
The CS rejected the request to detach itself from the network.

Note that a CS may detach itself automatically from the network; for example, if it is able to detect that its host computer has been or is about to be physically removed from the network.(13)

icmReattachComms

This function requests the CS to reattach itself to the network. If successful, then any outgoing messages to other hosts are forwarded, and incoming messages from other hosts may be read.(14)

icmStatus icmReattachComms(icmConn conn);

icmReattachComms returns

icmOk
The CS reattached successfully to the local network.
icmFailed
The CS was already attached.
icmError
The CS rejected the request to reattach itself to the network.

Note that a CS may reattach itself automatically to the network; for example, if it is able to detect that its host computer has been previously disconnected and the network is now available again.

icmEventLoop

The icmEventLoop function can be a convenient way of accessing the message channel. It `takes over' the application -- at least the thread that invokes the icmEventLoop function -- and reads messages from the CS whenever it can.Whenever a message arrives, then a callback function is invoked to allow the application to process the message.

icmStatus icmEventLoop(icmConn conn,icmMsgProc proc,void *client);

The return value from icmEventLoop is only interesting in the case that something went wrong with the initial call:

icmOk
The event loop was entered and exited ok.
icmError
The connection conn was not valid.

The icmEventLoop function will not normally return; instead it processes incoming messages -- from the previously established connection conn. For each message that it reads, it invokes the proc function as follows:

proc(conn,recip,sender,opts,msg,client)

where conn is the connection identifier, recip is the handle of the recipient agent, sender is the handle of the sending agent, opts is the set of options associated with the message, msg is the message itself and client is the value passed in to the call to icmEventLoop.

In order to terminate the event loop, the proc function may invoke the icmCloseComms function. If it does, then some time after the proc call returns, the event loop itself will terminate.

icmSetIdleProc

The icmSetIdleProc function establishes an `idle' procedure within an event loop. It only has effect if icmEventLoop has been or will be called for a particular connection.

During the event loop, immediately prior to each attempt to read a message from the CS, the icmEventLoop function will call the idle procedure. This allows an application to perform other background activities concurrently with the event loop. However, there is no guarantee when the idle procedure will be called, or with what frequency. If true concurrency is required, then the programmer is advised to structure the application with multiple threads of activity -- with one thread devoted to handling messages, and other threads to other parts of the application.

icmStatus icmSetIdleProc(icmConn conn,icmIdleProc idle,void *client);

The return value from icmSetIdleProc is only interesting in the case that something went wrong with the initial call:

icmOk
The idle procedure for conn was set.
icmError
The connection conn was not valid.

The call to idle takes the form:

idle(conn,client)

icmTerminateEventLoop

The icmTerminateEventLoop function is used within the message handler or idle procedure of an event loop -- as established using icmEventLoop.

void icmTerminateEventLoop(icmConn conn)

Calling icmTerminateEventLoop within such a handler will have the effect of terminating the event loop some short time after the message handler or idle procedure itself returns.

Managing agents

icmRegisterAgent

The icmRegisterAgent function registers an agent with the CS.

icmStatus icmRegisterAgent(icmConn conn,icmHandle hdl,icmOption opts,
        icmHandle *real)

The icmRegisterAgent function returns:

icmOk
The agent was successfully registered with the CS. Note that the application should use the returned real handle rather than hdl which was given as an argument -- the list of locations embedded within real may be different to that in hdl. In particular, if the CS is on a remote machine, or if the CS has been started with additional locations associated with it (via -l address options) then the real handle that is returned will contain additional locations. More precisely, the list of locations returned is the union of any locations supplied in the hdl handle, the address of the communication server's machine and any addresses given at the icm command line -- also as -l loc options.
icmFailed
The CS rejected the attempt to register the agent; probably because there already was an agent registered with the same name.
icmError
Either the CS was unavailable, or the handle was not a valid agent handle.

icmDeregisterAgent

The icmDeregisterAgent function removes the entries corresponding to an agent from the CS. Agents are also automatically deregistered from the CS by a call to the icmCloseComms API function.

icmStatus icmDeregisterAgent(icmConn conn,icmHandle handle);

icmDeregisterAgent returns:

icmOk
The agent referred to in the handle was deregistered.
icmFailed
The CS had no record of the agent described in handle
icmError
Either the CS was unavailable, or the handle was not a valid agent handle.

icmDetachAgent

The icmDetachAgent function is used by an agent when it wishes to temporarily detach itself from its communications server. A detached agent is still registered with the communications server, and therefore the CS will hold messages intended for it.

icmStatus icmDetachAgent(icmConn conn,icmHandle handle,icmOption opts);

icmDetachAgent returns:

icmOk
The agent was successfully detached from the communications server.
icmFailed
The agent was unknown by the communications server.
icmError
Either the CS was unavailable, or the handle was not a valid agent handle.

icmAttachAgent

The icmAttachAgent function is used by an agent when it wishes to re-attach itself to its communications server. When the agent has reattached itself to its communications server, any outstanding messages for it will be delivered.

icmStatus icmAttachAgent(icmConn conn,icmHandle handle,icmOption opts);

icmAttachAgent returns:

icmOk
The agent was successfully attached to the communications server. The agent is able to read its outstanding messages.
icmFailed
The agent handle was unknown to the communications server.
icmError
Either the CS was unavailable, or the handle was not a valid agent handle.

icmMonitorAgent

The icmMonitorAgent establishes a watch on a particular agent -- for example, waiting for the watched agent to terminate.

icmStatus icmMonitorAgent(icmConn conn,icmHandle ourH,icmHandle agent)

where ourH is the handle of the `client' agent, and agent is the `target' agent that is to be monitored.

This function terminates once the monitor has been established -- or failed to be established. After that, monitoring messages will be sent to the requesting client reflecting the behaviour of the monitored agent:

(icmMonitorAgent, icmRegisterAgent, agent)
This message is sent to the monitoring client when the agent has registered with the communications server. Note that if agent was already registered, the requesting client will get this message immediately.
(icmMonitorAgent, icmDeregisterAgent, agent)
This message is sent to the monitoring client when the agent has deregistered with the communications server. This may safely be taken by the requesting agent as a signal that agent has terminated.
(icmMonitorAgent, icmDetachAgent, agent)
This message is sent to the monitoring client when the agent has detached from the communications server. The agent is still `extant' from the point of view of the communications server -- and therefore should be interpreted as a form of temporary suspension from the network.
(icmMonitorAgent, icmAttachAgent, agent)
This message is sent to the monitoring client when the agent has reattached to the communications server. This message will only be sent if the agent had previously detached from the communications server.

Note that the `granularity' of this information should be considered to be quite coarse: one agent may contain many targets, and therefore many semi-independent threads of activity within it.

icmUnMonitorAgent

The icmUnMonitorAgent function removes a watch that had been previously established for a handle.

icmStatus icmUnMonitorAgent(icmConn conn,icmHandle ourH,icmHandle agent)

icmUnMonitorAgent returns:

icmOk
The watch on agent has been removed as requested.
icmFailed
There was no existing watch on the agent.
icmError
The watch could not be removed for another reason.

icmRequestForwarding

The icmRequestForwarding function requests that address forwarding be established for a handle. This is used in supporting agent migration.

icmStatus icmRequestForwarding(icmConn conn, icmHandle h1)

The handle h1 should contain the agent's identity, and its new list of locations. If the request is successful, then the local CS will redirect all further messages intended for the agent to the locations referred to in h1.

icmRequestForwarding returns:

icmOk
The forwarding on h1 has been established as requested.
icmFailed
The CS rejected the request to establish the forwarding.
icmError
If h1 had not been registered with the CS.

icmCancelForwarding

The icmCancelForwarding function requests that address forwarding be removed for a handle. This is used in supporting agent migration.

If no address forwarding is in place for an agent then the communications server will hold messages for the agent until the agent registers and reads them.

icmStatus icmCancelForwarding(icmConn conn,icmHandle h1)

There must have been previously an address forwarding established for h1; otherwise the call will fail.

If the request is successful, then the local CS will allow messages intended for h1 to be read by h1.

icmCancelForwarding returns a status result as follows:

icmOk
The forwarding on h1 has been removed as requested.
icmFailed
The CS rejected the request to remove the forwarding.
icmError
h2 had not been registered with the CS.

icmPingAgent

The icmPingAgent function can be used to check the current availability of a local or a remote agent.

icmStatus icmPingAgent(icmConn conn,icmHandle handle,icmHandle *real)

where handle is the handle of an agent that has been registered with the communications server.

icmPingAgent returns:

icmOk
The identified agent is currently on-line. The `real' address of the agent is given in real.
icmFailed
The identified agent is not on-line. Note this may be for a variety of reasons.
icmError
The host computer associated with handle was unreachable.

Note: Due to the nature of the Internet, this function may take a considerable amount of time to execute. The programmer is recommended to arrange the application using multiple threads, or to perform the same function by means of message communication to the CS, to avoid unnecessary delays in the application.

icmListAgents

The icmListAgents function can be used to determine the agents that are currently registered with the CS.

icmStatus icmListAgents(icmConn conn,icmHandle who,icmHandle *list,
                        int max)

icmListAgents returns:

icmOk
The local CS was able to compute a list of registered agents. The currently registered agents are placed in the array list -- whose maximum size is max. The first entry in list which has a value of NULL terminates the list. Note that who is the handle of the agent making the request to list the agents.
icmFailed
The CS refused to provide the list.
icmError
The host computer associated with conn was unreachable.

Note: Due to the nature of the Internet, this function may take a considerable amount of time to execute. The programmer is recommended to arrange the application using multiple threads, or to perform the same function by means of message communication to the CS, to avoid unnecessary delays in the application.

Message handling

icmSendMsg

icmSendMsg is the main function used to send a message.

icmStatus icmSendMsg(icmConn conn,icmHandle recip,
                     icmHandle sender,icmOption opts,icmMsg msg);

conn is the connection on which to send the message, recip is the handle of the recipient of the message, sender should be the handle of the sending application, opts is an icmOption structure that encodes any message options -- such as setting the reply address or the lease time of the message (see section icmOption). msg is the message to send.

icmSendMsg returns:

icmOk
The message was delivered successfully to the local CS. Note that this is not a guarantee that the message can be delivered to the intended recipient.
icmFailed
The local CS was not able to accept the message. Please try again.
icmError
One or more of the parameters was invalid.

icmFmtSendMsg

The icmFmtSendMsg function combines an icmFormatMsg and an icmSendMsg into a single call.

icmStatus icmFmtSendMsg(icmConn conn,icmHandle recip,
                        icmHandle sender,icmOption opt, char *fmt,...);

where the fmt is a format control string as in icmFormatMsg -- see section icmFormatMsg -- and additional components for the message are passed in subsequent arguments as in icmFormatMsg.

icmFmtSendMsg returns:

icmOk
The message was successfully formatted and deliverd to the local CS. Note that this is not a guarantee that the message can be delivered to the intended recipient.
icmFailed
The local CS was not able to accept the message. Please try again.
icmError
One or more of the parameters was invalid.

icmGetMsg

The icmGetMsg function attempts to read the next message from the CS. The returned message may be for any of the agents that have been registered with the CS.

icmStatus icmGetMsg(icmConn conn,icmHandle *recip,
                    icmHandle *sender,icmOption *opts,
                    icmMsg *msg,long *seqNo);

icmGetMsg returns the next available message in the input queue; whose message number is greater than the value addressed by seqNo. If seqNo is NULL, then the first available message is retrieved(15); otherwise, to get the first available message, seqNo should be initialized with -LONG_MAX.

icmGetMsg returns:

icmOk
There was a message available, and the intended recipient of the message was recip, any message options are given in opts, and the sender agent was sender. The seqNo number is updated to reflect the actual sequence number of the message that was found -- this may be used later if the message needs to be replaced -- using icmReplaceMsg (see section icmReplaceMsg). The returned msg may be parsed -- using icmScanMsg to determine the nature of the message. The returned msg should be released -- once properly processed by the application -- using icmReleaseMsg. The icmOption structured returned in opts should be released using icmReleaseOptions (see section icmReleaseOptions) -- unless the message is replaced in the message queue using icmReplaceMsg.
icmFailed
There was no message available. This will only be returned if the conn connection is set to non-blocking. Otherwise, the function blocks until there is a message.
icmError
The host computer associated with conn was unreachable, or there was a fatal error in a message received.
icmEndFile
The CS closed the connection for some reason.

icmReplaceMsg

The icmReplaceMsg function replaces a message back in the incoming message queue. This is useful in situations where the client reads a message using icmGetMsg but then decides that the message cannot be processed at this time.

icmStatus icmReplaceMsg(icmConn conn,icmHandle recip,
                    icmHandle sender,icmOption opt,
                    icmMsg msg,long seqNo);

The message is replaced in the input queue at a point which is based on the value of seqNo: the message is placed immediately after any messages whose own sequence number is less than seqNo. Using a value of -LONG_MAX has the effect of placing the message at the front of the current message queue.

A message that has been replaced is available once again to be retrieved using icmGetMsg.

Note that the seqNo is not `retrievable' -- each message that is placed in the queue is assigned a potentially new sequence number. However, if the seqNo used is the same as that returned by a prior call to icmGetMsg, then the relative order of messages in the queue is preserved in the expected way.

icmReplaceMsg returns:

icmOk
The message was successfully replaced. Note that it is still the programmer's responsibility to release the original message using icmReleaseMsg if required.
icmError
Some internal condition was not satisfied and the message was not replaced.

icmWaitMsg

The icmWaitMsg function attempts to read the next message from the CS. The returned message may be for any of the agents that have been registered with the CS.

icmStatus icmWaitMsg(icmConn conn,icmHandle *recip,
                    icmHandle *sender,icmOption *opt, char *fmt,...)

icmWaitMsg returns the next available message in the input queue that matches the pattern denoted in fmt. The format string fmt is the same as for an icmScanMsg -- see section icmScanMsg -- function call; the arguments following fmt should be of the appropriate type for the format.

icmWaitMsg returns:

icmOk
There was a message available, and the intended recipient of the message was recip, the reply return address is reply, and the sender agent was sender. The returned arguments following fmt are updated to reflect extracted values from the message. Note that any messages which are received over this connection which do not match the pattern denoted in fmt are not `lost': a subsequent icmGetMsg or icmWaitMsg function call with a different pattern will `pick up' messages which are skipped over in this icmWaitMsg call.
icmFailed
There was no message available. This will only be returned if the conn connection is set to non-blocking. Otherwise, the function blocks until there is a message.
icmError
The host computer associated with conn was unreachable, or there was a fatal error in a message received.
icmEndFile
The CS closed the connection for some reason.

icmMsgAvail

The icmMsgAvail function tests to see if a message is available -- without attempting to read the message.

icmStatus icmMsgAvail(icmConn conn,long from);

The message input queue is tested to see if there is a message whose sequence number is greater than from; if there is, or if there is a message pending from the communications server, then icmMsgAvail return icmOk; otherwise it returns icmFailed.

icmReleaseMsg

The icmReleaseMsg function releases a message that had been created earlier -- either using an icmFormatMsg function or through icmGetMsg or icmWaitMsg function calls.

icmStatus icmReleaseMsg(icmMsg msg);

icmReleaseMsg returns:

icmOk
The previously allocated message was released.
icmError
The msg had not been allocated by the ICM API, or has already been previously released.

icmNewOpt

icmOption icmNewOpt(icmOption prev,icmOpt opt,...)

This function returns an opaque pointer to an options structure -- the first call to icmNewOpt should use NULL for prev. The option encodes an option for a subsequent message or agent registration. Which type of option is encoded depends on opt:

icmLeaseTime
The additional argument to icmNewOpt should be a number which indicates the lease time of the message -- in absolute seconds after Unix epoch. E.g.:
option = icmNewOpt(NULL,icmLeaseTime,when);
icmReplyto
The additional argument should be a handle of the agent that any reply to this message should be sent. If this option is present, then any errors generated by the CS will also be sent to this agent.
icmReceiptRequest
The additional argument should be a message structure built by icmFormatMsg (for example). This message will be sent by the recipient when the message has been received -- independently of any other messages the recipient may send. If the icmLeaseTime option is set, and if the message is discarded due to message lease expiry, then the message:
(icmLeaseExpired,msg)
is sent.
icmAuditTrail
The additional argument should be a NULL-terminated list of handles. These form the initial list of handles to be used in the audit trail.

icmReleaseOptions

The icmReleaseOptions function garbage collects the icmOption structure it is passed -- afterwards the structure is invalid.

icmStatus icmReleaseOptions(icmOption opts)

icmreleaseOptions returns icmOk if possible.

icmIsOption

The icmIsOption function queries an icmOption structure for a particular feature.

icmStatus icmIsOption(icmOption opts,icmOpt o,...)

Depending on o, icmIsOption returns a value via a pointer to the result variable that follows the o parameter:

icmLeaseTime
The additional argument to icmIsOption should be a pointer to a C time_t variable. If there is an icmLeaseTime option in the opts structure, then the lease time is returned and icmIsOption returns icmOk, otherwise icmIsOption returns icmFailed. The expiry time is expressed in absolute seconds after Unix epoch.
icmReplyto
The additional argument should be a pointer to an icmHandle variable. If an icmReplyto option had been specified in opts, then the reply handle is set in the variable and icmOk is returned. If no icmReply is given then icmFailed is returned.
icmReceiptRequest
The additional argument should be a pointer to an icmMsg variable. If an icmReceiptRequest option had been specified in opts, then the requested form of the receipt is set in the icmMsg variable and icmOk is returned. This is the message that should be sent to the originator of the message -- or if a icmReply is given then to the relevant agent. If no icmReceiptRequest is requested then icmFailed is returned.
icmAuditTrail
The additional argument should be an icmHandle* pointer. This is bound to a NULL terminated list of handles that represents the audit trail of the message.

The programmer should not modify any of the values returned from icmIsOption -- they will be released when icmReleaseOptions is invoked.

Message formatting and parsing

These functions are used to create and to parse ICM messages. The underlying data structure of an ICM message is a self-parsing stream of bytes. This data type is marshalled into the opaque type icmMsg; a value of this type may have additional information embedded within it.

icmFormatMsg

The icmFormatMsg function constructs a message based on a format string -- which is a normal C string -- together with additional data values. The format string controls the shape and structure of the message; and the contents of the message are extracted from normal data values native to the programming language.

icmMsg icmFormatMsg(icmStatus *return, char *fmt, ...);

The format string consists of a sequence of conversion items which reflect the particular nature of the object to be plugged in. Within the format string, white space is ignored: it can be used to make the format string more readable. While the format string may contain a complex structure, with many components, a given format string may specify only one overall structure.

A non-white space character -- which is not a conversion item, is assumed to be part of a symbol entry in the message.

Each conversion item is preceded by a % character, followed by one or more other characters that define the meaning. Many of the conversion items `consume' a real value. The pointer to this value is to be found in the arguments following the format string. Each following argument contains either a value or a pointer to a value; the exact interpretation will depend on the conversion character encountered.

%d
The corresponding value should be an integer; which is placed in the message.
%i
The corresponding value should be a long long integer (i.e., a 64 bit integer); which is placed in the message.
%f
The corresponding value should be a pointer to a floating point number; which is placed in the message.
%s
The corresponding value should be a C string; which is placed in the message as a symbol.
%S
The corresponding value should be an icmDataPo pointer; the contents of which is placed in the message as a block of uninterpreted bytes.
%h
The corresponding value should be an icmHandle; the contents of which is placed in the message as a handle object.
%x
The corresponding value should be an icmOpaquePo; the contents of which is placed as an opaque value into the message.
%C
The corresponding value should be an icmDataPo pointer; the contents of which is placed in the message as a code block.
%(
This marker starts a tuple/record in the message. It must be accompagnied by a corresponding %) marker. Each conversion specifier between the %( and %) markers correspond to different elements of the tuple.
%)
This marker ends a tuple/record in the message.
%[
This marker starts a list in the message. It must be accompagnied by a corresponding %] marker. Each conversion specifier between the %[ and %] markers correspond to different elements of the tuple.
%]
This marker ends a list in the message.
%,
This marker separates elements of a list or tuple/record in the message. This is used where a list or a tuple partly consists of symbols which are built into the format string.
%R
This marker marks an apply or record structure. It must be followed by 1 or more characters of regular text and a format element. The plain text characters that follow form the name of the constructor function, and the argument is gotten from the following convertion specifier element. For example, the format:
"...%Rfoo%(%d%d%)..."
denotes a constructor record of two elements -- both numbers -- whose identifier is the symbol foo.
%{
This encloses an encapsulated value. Encapsulated values have a type signature representing the type of the value attached. The type is computed by icmFormatMsg depending on the type of the embedded format specifier. icmFormatMsg can only determine the type of relatively simple values -- such as a numeric value, or a list of numeric values. It is not able to guess at the type of messages which are introduced dynamically -- using the %# format specifier for example. See section Encapsulated values for a more detailed description and how they are represented.
%}
This terminates an encapsulated value. There should be exactly one value enclosed in the %{%} markers.
%"
This marker is used to include a literal string/arbitrary byte sequence into the message. All the following characters in the format string up to second occurrence of %" are treated as though they are characters in the byte sequence.
%#
This marker is used to include the contents of a previously formatted message into the message. The corresponding value should be an icmMsg value.
%*
This marker introduces a list into the message. The %* conversion takes two parameters from icmFormatMsg: the number of elements of the list and the array of values themselves. The type of the elements of the array, and their corresponding interpretation within the message, are determined from the character that follows the %*, which may be any one of:
%*d
This generates a list of integers, and the array passed into icmFormatMsg should be an array of long numbers.
%*i
This generates a list of long (64bit) integers, and the array passed into icmFormatMsg should be an array of long long numbers.
%*f
This generates a list of floating point numbers, and the array passed into icmFormatMsg should be an array of pointers to double values.
%*s
This generates a list of symbols, and the array passed into icmFormatMsg should be an array of char * pointers.
%*S
This generates a list of strings/uninterpreted data blocks, and the array passed into icmFormatMsg should be an array of icmDataPo values.
%*h
This generates a list of handles, and the array passed into icmFormatMsg should be an array of icmHandle values.
%*C
This generates a list of code blocks, and the array passed into icmFormatMsg should be an array of icmDataPo values.
%*#
This generates a list of arbitrary messages, and the array passed into icmFormatMsg should be an array of icmMsg values.

icmFormatMsg returns:

icmOk
The format of the message was valid, and there were no problems with the conversion of any of the elements in the message.
icmFailed
There was insufficient resources to construct the message.
icmError
There was a problem with the format string, or with one of the values to be included in the message.

icmScanMsg

The icmScanMsg function parses a message based on a format string -- which is a normal C string. It can be used to both verify that a message matches a particular patten and to extract C values from the message. The format string controls the expected shape and structure of the message; and the contents of the message are extracted into normal data values native to the programming language.

icmStatus icmScanMsg(icmMsg msg, char *fmt, ...);

The format string consists of a sequence of conversion items which reflect the particular nature of the object to be plugged in. Within the format string, white space is ignored: it can be used to make the format string more readable. While the format string may contain a complex structure, with many components, a given format string may specify only one overall structure.

A non-white space character -- which is not a conversion item, is assumed to be part of a symbol entry in the message. If the actual message does not have the corresponding symbol then the icmScanMsg function will fail.

Each conversion item is preceded by a % character, followed by one or more other characters that define the meaning. Many of the conversion items `generate' a real value; the value generated is placed into the location pointed at by the appropriate argument following the conversion format. Successive arguments of icmScanMsg refer to successive conversion items in the fmt string.

%d
The corresponding message item should be an integer; which is placed in the location addressed by the corresponding argument -- which should be of type long *.
%i
The corresponding message item should be a long integer; which is placed in the location addressed by the corresponding argument -- which should be of type long long *.
%f
The corresponding message item should be a floating point number; which is placed in the location addressed by the corresponding argument -- which should be of type double *.
%s
The corresponding message item should be a symbol -- which is interpreted as a C string; a copy of which is placed in the char ** pointer which is passed into icmScanMsg. Note that it becomes the programmer's responsibility to release the memory occupied by the string data -- using the standard C function free. Note that if the message item is not a symbol -- with the result that icmScanMsg will fail -- then no memory is allocated.
%S
The corresponding message item should be a block of uninterpreted data -- and the corresponding argument to icmScanMsg should be an icmDataPo pointer. A copy of the message data -- together with its size -- is placed in the icmDataPo structure passed in to icmScanMsg. Note that it becomes the programmer's responsibility to release the memory occupied by the string data -- using the standard C function free. Note that if the message item is not a string -- with the result that icmScanMsg will fail -- then no memory is allocated.
%C
The corresponding message item should be a code block -- and the corresponding argument to icmScanMsg should be an icmDataPo pointer. A copy of the message data -- together with its size -- is placed in the icmDataPo structure passed in to icmScanMsg. Note that it becomes the programmer's responsibility to release the memory occupied by the code data -- using the standard C function free. Note that if the message item is not a code block -- with the result that icmScanMsg will fail -- then no memory is allocated.
%x
The corresponding message item should be an opaque value. Opaque values have an owner and a data value. These are returned in as values of the icmOpaquePo record -- which is passed in as a parameter. The values returned should be freed after use.
%h
The corresponding message should be a handle -- and the corresponding argument to icmScanMsg should be a pointer to an icmHandle variable. This is filled in with the handle recovered from the message.
%"
The corresponding message item should be a block of uninterpreted data -- as for an icmExpectData function call. In addition, the data in the message is checked against the embedded string within the format string -- the string of characters up to a second %" pair. If the embedded message is not a string, or if it has a different value to the explicit string data then icmScanMsg returns icmFailed.
%(
This marker starts a tuple/record in the message. It must be accompagnied by a corresponding %) marker. Each conversion specifier between the %( and %) markers correspond to different elements of the tuple. The elements of the tuple are loaded into succesive entries of the args array.
%)
This marker ends a tuple/record in the message.
%[
This marker starts a list in the message. It must be accompagnied by a corresponding %] marker. Each conversion specifier between the %[ and %] markers correspond to different elements of the list. The elements of the list are loaded into succesive args.
%]
This marker ends a list in the message.
%,
This marker separates elements of a list or tuple/list in the message. This is used where a list or a tuple partly consists of symbols which are built into the format string.
%R
This marker marks an apply or record structure. It must be followed by 1 or more characters of regular text and a format element. The plain text characters that follow form the name of the constructor function, and the argument is gotten from the following convertion specifier element. For example, the format:
"...%Rfoo%(%d%d%)..."
denotes a constructor record of two elements -- both numbers -- whose identifier is the symbol foo.
%{
This encloses an encapsulated value. Encapsulated values have a type signature representing the type of the value attached. The actual value should be an encapsulated value of the correct type.(16)
%}
This terminates an encapsulated value. There should be exactly one value enclosed in the %{%} markers.
%#
This marker is used to refer to any complete message or part thereof. The message is `packaged' as an icmMsg value which is stored in the location addressed by args[i] where this is the ith item encountered. This message must subsequently be released -- using an icmReleaseMsg call unless the icmScanMsg call as a whole fails.
%@
This marker access the index of the current message element. The index of the current element is stored in args[i], where this is the ith item encountered. The corresponding message element is skipped.
%*
This marker is used to scan a list within the message. There are two corresponding arguments used with the %* conversion format: the first is a pointer to a long integer and the second should be an array of suitable values -- one that is large enough to hold the values extracted from the list. The initial value of the `element counter' is the length of the array that is passed in; on successful parsing of the message, this count is updated with the actual number of elements loaded into the array. If the array is not long enough to hold all the elements of the list, then icmScanMsg will return icmError. The expected type of the list element is determined from the character following the %* specifier:
%*d
The list should be a list of numbers, and these are loaded into the array -- which should be an array of long values.
%*i
The list should be a list of 64 bit numbers, and these are loaded into the array -- which should be an array of long long values.
%*f
The list should be a list of floating point numbers, and these are loaded into the array -- which should be an array of double values.
%*s
The list should be a list of symbols, and copies of these are loaded into the array -- which should be an array of char * values. Note that it becomes the programmer's responsibility to release the memory occupied by these C strings after they are processed.
%*S
The list should be a list of string/data blocks, and copies of these are loaded into the array -- which should be an array of icmDataRec values. Note that it becomes the programmer's responsibility to release the memory occupied by these data strings after they are processed.
%*C
The list should be a list of code blocks, and copies of these are loaded into the array -- which should be an array of icmDataRec values. Note that it becomes the programmer's responsibility to release the memory occupied by these code blocks after they are processed.
%*h
The list should be a list of handles, and these are loaded into the array -- which should be an array of icmHandle values.
%*#
The list may be any kind of list, the elements are extracted as sub-messages and loaded into the icmMsg array. Note that it becomes the programmer's responsibility to release the messages generated by this conversion -- using the icmReleaseMsg function.
%**
One special case is the %** format specifier. This matches a list in the message, but merely counts the elements of the list. The length of the list within the message is stored in the long * count field passed in and there is no associated array argument.

icmScanMsg returns:

icmOk
The format of the message was valid, and the message matched the format specified, and there were no problems with the conversion of elements of the message into the corresponding C values.
icmFailed
Either the message did not match the format string, or there was a problem with one or more of the conversions. In this situation, any structures generated during the course of any partial match are automatically released by the icmScanMsg function.
icmError
There was a problem with the format string, or with the conversion of one of the values included in the message.

icmScanXMsg

The icmScanXMsg function is similar to the icmScanMsg function (see section icmScanMsg), except that the initial index into the message is specified.

icmStatus icmScanXMsg(icmMsg msg, long start, long *exit,char *fmt, ...);

The initial point of entry is given by start, and the index of the immediately following message component is returned in exit.

Note that a call to icmScanMsg is equivalent to a call to icmScanXMsg of the form:

icmScanXMsg(msg,0,NULL,format,...)

The interpretation of the other arguments and return code is the same as for icmScanMsg.

Low-level message parsing

The low-level message parsing functions form a collection which allows an application to parse a message in piece-meal fashion. This is most suitable in situations where the agent platform is a higher-level language than C or Java -- e.g., Prolog, LISP or April -- and the native programming language already has adequate methods for representing complex structures. In this situation, the low-level message parsing functions can be used to parse the message and build a language specific structure from the message.

icmElTag

This type enumerates the possible type of individual message elements:

typedef enum {icmInt=0x10, icmFlt=0x20, icmNegFlt=0x30, icmSym=0x40,
         icmHandle=0x50, icmData=0x60, icmCode=0x70, icmNil=0x80,
         icmList=0x81, icmApply=0x82, icmTuple=0x90, icmTag=0xa0, icmRef=0xb0,
         icmShort=0xc0} icmElTag;

Note that the ordinal values associated with these tags reflects the underlying encoding of ICM messages.

icmMsgElType

The icmMsgElType function returns the type of the `next' component of a message.

icmStatus icmMsgElType(icmMsg msg, int index,icmElTag *type);

This function examines the message component -- defined by the pair: msg/index and returns in type the type of the message element. To examine the beginning of the message use an index of 0; otherwise valid values of index must come from the returned values of other low-level message parsing functions.

icmMsgElType returns:

icmOk
The format of the message was valid, and the element type at msg[index] is type.
icmFailed
Unable to determine a correct message element type.
icmEndFile
If index was too large for the message
icmError
The msg[index] combination is not valid.

Once a message element's type and size have been identified, its value can be extracted using one of the following element decoding functions.

icmMsgElSize

The icmMsgElSize function returns the size of the current component of a message. Depending on the type of structure accessed, the size returned is the either number of bytes needed to hold the value or the `length' of the list or tuple.

icmStatus icmMsgElSize(icmMsg msg, int index,long *size);

This function examines the message component -- defined by the pair: msg/index and returns in size the size of the message element.

icmMsgElSize returns:

icmOk
If the format of the message was valid, and the size of the element at msg[index] could be determined.
icmFailed
If unable to determine a correct message element type.
icmEndFile
If index was too large for the message
icmError
If the msg[index] combination is not valid.

Where the element is a list, icmElSize returns 0 for an empty list and 1 for a non-empty list. Where the element is a tuple, icmElSize returns the arity of the tuple.

Once a message element's type and size have been identified, its value can be extracted using one of the following element decoding functions.

icmExpectInt

The icmExpectInt function verifies that there is an integer value at a particular point in a message.

icmStatus icmExpectInt(icmMsg msg, int x, int *newx, long *result);

This function will succeed if there is an integer value encoded in the message at msg[x]; the value of the integer is returned in result and the index of the following message element is returned in newx.

icmExpectInt returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is an integer value.
icmFailed
If the message element at msg[x] was not an integer value.
icmEndFile
The requested index was too large, or the message did not contain a complete integer value at that point.
icmError
The msg[x] combination is not valid.

icmExpectFlt

The icmExpectFlt function verifies that there is a floating point value at a particular point in a message, and returns the floating point number.

icmStatus icmExpectFlt(icmMsg msg, int x, int *newx, double *result);

This function will succeed if there is a floating point value encoded in the message at msg[x]; the value of the float is returned in result and the index of the following message element is returned in newx.

icmExpectFlt returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a floating point value.
icmFailed
If the message element at msg[x] was not a floating point value.
icmEndFile
If the index was too great or the message did not contain a complete floating point number at that point.
icmError
If the msg[x] combination is not valid.

icmExpectSym

The icmExpectSym function verifies that there is a symbol value at a particular point in a message, and returns the symbol as a C string pointer.

icmStatus icmExpectSym(icmMsg msg,int x,int *newx,char *buffer,long len);

This function will succeed if there is a symbol value encoded in the message at msg[x]; the symbol is returned in result -- as a C string -- and the index of the following message element is returned in newx.

Note that the C buffer is supplied to the icmExpectSym function, and that len should be large enough to hold the expected C string pointer.

icmExpectSym returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a symbol value.
icmFailed
If the message element at msg[x] was not a symbol value.
icmEndFile
If the supplied index was too large, or the message did not contain a complete symbol.
icmError
If the msg[x] combination is not valid, or the supplied buffer was not long enough.

icmExpectHandle

The icmExpectHandle function verifies that there is a handle value at a particular point in a message, and returns the handle as an icmHandle pointer.

icmStatus icmExpectHandle(icmMsg msg,int x,int *newx,icmHandle *result);

This function will succeed if there is a handle encoded in the message at msg[x]; the handle is returned in result -- as an icmHandle pointer -- and the index of the following message element is returned in newx.

icmExpectHandle returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a valid handle.
icmFailed
If the message element at msg[x] was not a valid handle.
icmEndFile
If the supplied index was too large, or the message did not contain a complete handle.
icmError
If the msg[x] combination is not valid.

icmExpectData

The icmExpectData function verifies that there is a data block value at a particular point in a message, and returns the data as a combination of a C void pointer and the length of the data block.

icmStatus icmExpectData(icmMsg msg,int x,int *newx,
                        void *buffer,long *len,long size);

This function will succeed if there is an uninterpreted string/data block value encoded in the message at msg[x]; then the data is copied into buffer -- which has a maximum length of size bytes. The index of the following message element is returned in newx.

The found size of the data block is returned in len.

icmExpectData returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a data block value.
icmFailed
If the message element at msg[x] was not a data block value.
icmError
The msg[x] combination is not valid, or the supplied buffer was not large enough to hold the data.

icmExpectOpaque

The icmExpectOpaque function verifies that there is an opaque value at a particular point in a message. Opaque values have an owner and a value; these are conventionally a symbol for the owner, and a data block for the value. However, neither are decoded by the icmExpectOpaque function -- the application should follow the call by a call to icmExpectSym and icmExpectData respectively.

icmStatus icmExpectOpaque(icmMsg msg,int x,int *newx);

This function will succeed if there is an opaque value encoded in the message at msg[x]. The index of the owner element is returned in newx.

icmExpectOpaque returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is an opaque value.
icmFailed
If the message element at msg[x] was not an opaque value.
icmError
If the msg[x] combination is not valid, or the supplied buffer was not long enough.

icmExpectCode

The icmExpectCode function verifies that there is a code block value at a particular point in a message.

icmStatus icmExpectCode(icmMsg msg,int x,int *newx,
                        void *buffer,long *len,long size);

The code data is copied into buffer -- which has a maximum length of size bytes. The index of the following message element is returned in newx. The found size of the code block is returned in len.

Note that the language of the code block is not defined by the ICM; in order to further distinguish the program code, there should be additional internal evidence within the code block.

icmExpectCode returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a code block value.
icmFailed
If the message element at msg[x] was not a code block value.
icmError
If the msg[x] combination is not valid, or the supplied buffer was not long enough

icmExpectNil

The icmExpectNil function verifies that there is an empty list at a particular point in a message.

icmStatus icmExpectNil(icmMsg msg,int x,int *newx);

This function will succeed if there is an empty list encoded in the message at msg[x]; the index of the following message element is returned in newx.

icmExpectNil returns:

@item icmOk The format of the message was valid, and the element type at msg[x] is an empty list.
icmFailed
If the message element at msg[x] was not an empty list.
icmError
If the msg[x] combination is not valid.

icmExpectList

The icmExpectList function verifies that there is a non-empty list at a particular point in a message.

icmStatus icmExpectList(icmMsg msg, int x, int *newx);

This function will succeed if there is a non-empty list encoded in the message at msg[x]; the index of the message element corresponding to the head of the list is returned in newx.

icmExpectList returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a non-empty list.
icmFailed
If the message element at msg[x] was not a non-empty list.
icmError
If the msg[x] combination is not valid.

icmExpectApply

The icmExpectApply function verifies that there is an apply structure at a particular point in a message. Apply structures consist of a head and a tail: conceptually, the head refers to the function and the tail the arguments.

icmStatus icmExpectApply(icmMsg msg, int x, int *newx);

This function will succeed if there is an apply structure encoded in the message at msg[x]; the index of the message element corresponding to the head of the apply is returned in newx.

icmExpectApply returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is an apply structure.
icmFailed
If the message element at msg[x] was not an apply structure.
icmError
If the msg[x] combination is not valid.

icmExpectTuple

The icmExpectTuple function verifies that there is a tuple at a particular point in a message.

icmStatus icmExpectTuple(icmMsg msg, int x, int *newx,int *arity);

This function will succeed if there is a tuple encoded in the message at msg[x]; the index of the message element corresponding to the first element of the tuple is returned in newx and the arity of the tuple is returned in arity.

icmExpectTuple returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a tuple.
icmFailed
If the message element at msg[x] was not a tuple.
icmError
If the msg[x] combination is not valid.

icmExpectTag

The icmExpectTag function verifies that there is a tagged structure at a particular point in a message.

icmStatus icmExpectTag(icmMsg msg,long index,long *newx,long *tag)

This function will succeed if there is a tagged structure encoded in the message at msg[x]; the message index of the structure that is tagged is returned in newx and the tag number is returned in tag.

icmExpectTag returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a tagged structure.
icmFailed
If the message element at msg[x] was not a tagged structure.
icmError
If the msg[x] combination is not valid.

Note that tagged structures are typically used to represent circular or general graph structures.

icmExpectRef

The icmExpectRef function verifies that there is a reference to a tagged structure at a particular point in a message.

icmStatus icmExpectRef(icmMsg msg,long index,long *newx,long *tag)

This function will succeed if there is a reference to a tagged structure encoded in the message at msg[x]; the tag number of the referenced structure is returned in tag and the message index of the following message element is returned in newx .

icmExpectRef returns:

icmOk
If the format of the message was valid, and the element type at msg[x] is a reference to a tagged structure.
icmFailed
If the message element at msg[x] was not a reference to a tagged structure.
icmError
If the msg[x] combination is not valid.

icmSkipElement

The icmSkipElement function `skips over' a complete component of a message.

icmStatus icmSkipElement(icmMsg msg, int x, int *newx);

This function will succeed if there is a complete -- well formed -- message sub-structure at msg[x] and returns the message index of the following message element is returned in newx . If the message element was a list -- or an apply pair or a tuple -- then all the elements of the list or tuple are skipped over as well.

icmSkipElement returns:

icmOk
The format of the message was valid.
icmFailed
The message element at msg[x] was not a well formed structure.
icmError
The msg[x] combination is not valid.

Low-level message generation

The low-level message generation functions form a collection which allows an application to construct a message in piece-meal fashion. Note that it is the responsibility of the programmer to ensure that a well-formed message is constructed using these primitives.

icmNewMsg

The icmNewMsg function constructs a new `empty' message that can be extended with other message generation functions.

icmStatus icmNewMsg(icmMsg *msg);

icmNewMsg returns:

icmOk
A new message message was created.
icmError
Could not create an empty message.

icmPlaceInt

The icmPlaceInt function extends a message structure by placing an integer value at the current end of the message.

icmStatus icmPlaceInt(icmMsg msg,long i);

icmPlaceInt returns:

icmOk
The integer was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceFlt

The icmPlaceFlt function extends a message structure by placing a floating point value at the current end of the message.

icmStatus icmPlaceFlt(icmMsg msg,double f);

icmPlaceFlt returns:

icmOk
The floating point value was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceSym

The icmPlaceSym function extends a message structure by placing a symbol -- encoded as a C string -- at the current end of the message.

icmStatus icmPlaceSym(icmMsg msg,char *sym);

icmPlaceSym returns:

icmOk
The symbol was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceHandle

The icmPlaceHandle function extends a message structure by placing a handle -- encoded as described in section Handle encoding -- at the current end of the message.

icmStatus icmPlaceHandle(icmMsg msg,icmHandle hdl);

icmPlaceHandle returns:

icmOk
The handle hdl was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceData

The icmPlaceData function extends a message structure by placing a data block -- encoded as a void * pointer and the length in bytes of the data block -- at the current end of the message.

icmStatus icmPlaceData(icmMsg msg,void *data,long size);

icmPlaceData returns:

icmOk
The data block was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceCode

The icmPlaceCode function extends a message structure by placing a code block -- encoded as a void * pointer and the length in bytes of the code block -- at the current end of the message.

icmStatus icmPlaceCode(icmMsg msg,void *code, long size);

icmPlaceCode returns:

icmOk
The code block was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceNil

The icmPlaceNil function extends a message structure by placing an empty list at the current end of the message.

icmStatus icmPlaceNil(icmMsg msg);

icmPlaceNil returns:

icmOk
The nil value was successfully appended to the existing message.
icmError
Could not extend the message.

icmPlaceList

The icmPlaceList function extends a message structure by placing a non-empty list marker at the current end of the message.

icmStatus icmPlaceList(icmMsg msg);

icmPlaceList returns:

icmOk
The non-empty list marker value was successfully appended to the existing message.
icmError
Could not extend the message.

Note that immediately following the non-empty list marker the head of the list, followed by the tail of the list should be generated.

icmPlaceApply

The icmPlaceApply function extends a message structure by placing an apply marker at the current end of the message.

icmStatus icmPlaceApply(icmMsg msg);

icmPlaceApply returns:

icmOk
The apply marker value was successfully appended to the existing message.
icmError
Could not extend the message.

Note that immediately following the apply marker the head of the apply, followed by the tail of the apply should be generated.

icmPlaceTuple

The icmPlaceTuple function extends a message structure by placing a tuple marker at the current end of the message.

icmStatus icmPlaceTuple(icmMsg msg,long arity);

icmPlaceTuple returns:

icmOk
The tuple marker value was successfully appended to the existing message.
icmError
Could not extend the message.

Note that immediately following the tuple marker all the elements of the tuple should be generated in sequence.

icmPlaceTag

The icmPlaceTag function extends a message structure by placing a tag marker at the current end of the message.

icmStatus icmPlaceTag(icmMsg msg,long tag);

icmPlaceTag returns:

icmOk
The tag was successfully appended to the existing message.
icmError
Could not extend the message.

Note that immediately following the tag marker the tagged structure itself should be generated.

icmPlaceRef

The icmPlaceRef function extends a message structure by placing a tag reference at the current end of the message.

icmStatus icmPlaceRef(icmMsg msg,long tag);

icmPlaceRef returns:

icmOk
The reference was successfully appended to the existing message.
icmError
Could not extend the message.

Note that each reference placed should have a corresponding tag defined at some point within the msg.

icmPlaceMsg

The icmPlaceMsg function extends a message structure by placing a complete sub-message at the current end of the message.

icmStatus icmPlaceMsg(icmMsg msg,icmMsg sub);

icmPlaceMsg returns:

icmOk
The sub message was successfully appended to the existing message.
icmError
Could not extend the message.

ICM Utility API

The ICM library has built into it a number utility functions which, though not essential to users of the ICM API, may be none-the-less useful in constructing applications invloving the ICM API.

Hash Utility functions

The hash facilities included in this section are not `critical' to the ICM, however, they are used by the ICM and may prove useful to the programmer for other purposes.

icmHashPo

The icmHashPo is an opaque type that refers to a hash table created and manipulated by the ICM hash functions.

typedef void *icmHashPo;

icmHashFun

typedef long (*icmHashFun)(void *);

The icmHashFun function type is a function prototype that refers to the specific `hashing' function used by the ICM hash table functions.

Its input is a void * -- i.e., a value whose type depends on the particular use of the hash functions. Its output is a hash value that will be used by the hash table search functions.

icmCompFun

typedef int (*icmCompFun)(void *A,void *B);

The icmCompFun function prototype refers to a function that is used to compare two values for equality. icmCompFun should return 0 when A and B are equal, and a non-zero value when they are different.

icmDestFun

typedef icmStatus (*icmDestFun)(void *N,void *R);

The icmDestFun function prototype refers to a function that will be called when an entry in the hash table is deleted. This represents an opportunity to release space allocated when a hash table entry is created. The N argument will be the `name' of the hash table entry, and R is the `value' of the hash table entry.

The destroy function should return icmOk if the element's structures could be released satisfactorily. If the called function returns icmFailed or icmError then the element is NOT removed from the table.

icmProcFun

typedef icmProc (*icmProcFun)(void *N,void *R,void *C);

The icmProcFun function prototype refers to a function that will be called when the whole of the hash table has to be processed. The N argument will be the `name' of the hash table entry, the R is the `value' of the hash table entry, and C is `client' data passed into the icmProcTable function.

The value returned by this function will govern the returned value from icmProcTable function.

icmNewHash

The icmNewHash function constructs a new hash table.

icmHashPo icmNewHash(long size,icmHashFun hash,
                     icmCompFun cmp,icmDestFun dest);

The table returned by icmNewHash is sufficient to accomodate at least size entries (its actual size may be different). The function used to generate keys is given by hash, cmp can be used by the hash functions to compare values and dest is used whenever an entry is deleted from the table.

The hash and cmp functions should not be NULL when a new table is created. However, if dest is NULL, then no function will be invoked when elements are deleted from the table.

Normally, icmNewHash returns the address of a new table; if it returns NULL then it was not possible to construct the table.

icmDelHash

The icmDelhash deletes a hash table previosuly generated by a call to icmNewHash. Before releasing the space for the table itself, icmDelHash first of all removes all elements from the table -- this may result in the dest function being cvalled for the elements being deleted.

icmStatus icmDelHash(icmHashPo hp);

icmDelHash returns:

icmOk
If all the entries were successfully deleted from the table, and the table was destroyed.
icmFailed
If the table could not be destroyed because at least one of the elements `refused' to be destroyed when the dest function was invoked. If icmFailed is returned, the table still exists; however, it is undecided how many of the table's elements are still in the table.
icmError
If there was an error in the process of destroying the table.

icmInstall

This function installs a new entry in the table.

icmStatus icmInstall(void *n,void *r,icmHashPo tbl);

This installs a new entry with `name' n and `value' r. Note that icmInstall does not create any additional structures to support n or r: it is the programmer's responsibility to ensure that n and r are valid for the expected lifetime of the entry.

icmInstall returns:

icmOk
If the entry was successfully created in tbl
icmError
If the table could not be updated for some reason.

icmSearch

This function searches for an entry in the table.

void * icmSearch(void *n,icmHashPo tbl);

This searches the table for an entry whose name is n -- as defined by the comparison function installed in the table when it was created. The return value is the value associated with that name, or NULL if the search failed.

icmUninstall

This function attempts to remove an entry from the table.

icmStatus icmUninstall(void *n,icmHashPo tbl);

This attempts to remove the entry associated with `name' n from the table tbl. If there is a dest function associated with the table then this is invoked prior to the entry being deleted from the table.

icmUninstall returns:

icmOk
If the dest function returns with icmOk then the entry is deleted and icmUninstall also returns with icmOk. If there is no entry associated with n then icmUninstall also returns with icmOk.
icmFailed
If the dest function returns with icmFailed then the entry is NOT deleted, and icmUninstall also returns with icmFailed.
icmError
If the dest function returns with icmError then the entry is deleted, and icmUninstall also returns with icmError.

icmProcessTable

The icmProcessTable function applies a user-supplied function to every element currently in the hash table.

icmStatus icmProcessTable(icmProcFun fun,icmHashPo tbl,void *client);

For every valid entry currently in tbl the fun function is invoked; each call to fun takes the form:

fun(N,R,client)

where N is the name component of the entry, R is the value component and client is as given in the call to icmProcessTable.

icmProcessTable returns:

icmOk
If either the table is empty, or if every call to the fun function returned icmOk
icmFailed
If at least one entry in the table resulted in an icmFailed return from fun. Note that all entries are processed, even when the fun function returns icmFailed on one or more entries.
icmError
If a call to the fun function resulted in an icmError return code. In this case, further processing of the table is prevented: the first entry that results in an icmError also terminates processing of the table.

Note that due to the nature of hash tables, it is not possible to guarantee a particular order when processing the elements of the table: neither the order in which entries are added to the table, nor any possible relative ordering based on the keys, will affect the order of processing.

Host name management

These functions are made available from the ICM implementation to make it simpler to access hostnames in a standard way.

icmGetHostname

The icmGetHostname accesses the `real' host name of a mahine. It is able to resolve relative hosts names -- such as prospero -- and determine if the host is known and its canonical, fully qualified name such as: prospero@net.fla.fujitsu.com.

char *icmGetHostname(char *host);

icmGetHostname returns NULL if the host is unknown. It is able to determine a hostname from a name given as an IP quartet as well as a DNS hostname format.

Note that the ICM library caches host names -- for a period of up to one day. This can lead to a performance improvement compared to the standard Unix library functions.

icmMachineName

The icmMachineName returns the `real' hostname of the current machine; if it is possible to determine.

Data structure pooling

The ICM library includes a `data structure pooling' mechanism. The intention of this mechanism is to supplement the existing memory management functions of malloc and free. The ICM data structure pooling mechanism has the equivalent of malloc and free; however when released back into a pool, a data structure is not freed, instead it is kept in a linked list of equivalently sized objects. When a new object is requested from the pool, this list can be consulted directly without requiring a potentially expensive malloc.

icmPoolBase

This opaque pointer type is used to denote a pool of empty data structures.

typedef struct _poolbase_ *icmPoolBase;

icmInitPool

The icmInitPool function must be used to create a pool for a particular type. The form of this function is:

icmPoolBase icmInitPool(size_t elsize, int initial);

where elsize is the size of each element of the data type -- usually computed using the sizeof operator -- and initial is advise to the pool manager to create at least initial elements in the pool.

icmAllocPool

The icmAllocPool function is used to allocate an empty data structure from the pool of available objects. If the pool is empty, then icmAllocPool automatically uses malloc to create additional empty objects.

void *icmAllocPool(icmPoolBase base)

Note that the size of the element is determined by the initialization of the base. Note also, that the return value is a void *. This value should be type caste into the appropriate type before use.

Important:

A value that has been allocated using icmAllocPool should not be freed using free -- use icmFreePool (see section icmFreePool) instead.

icmFreePool

The icmFreePool function releases a previously allocated object back into the pool.

void icmFreePool(icmPoolBase base, void *p)

Important:

Only values that have been allocated using icmAllocPool should be freed using icmFreePool.

Command-line options processing

The ICM offers a command-line processing option package that can be used to simplify the processing of command-line options.

icmOptionCallback

typedef icmStatus (*icmOptionCallback)(char opt,char *optarg,void *client);

This type defines a type of function that is called for each potential option on the command line.

icmGetOptions

The icmGetOptions function can be used to process the command-line options is a regular and standard way.

int icmGetOptions(int argc,char **argv,char *fmt,
                  icmOptionCallback opt,void *client);

The first two arguments are the standard arguments representing the C command-line arguments to a program. The fmt argument specifies which command-line options are valid and opt/client are used to process the options individually.

The fmt string consists of a series of letters, each may be suffixed by a : character if the option is associated with a value. For each command-line option encountered by icmGetOptions, the callback function opt is called; each call is of the form:

opt(opt,arg,client)

where opt is a character representing the particular option, if the option was identified as having a data value associated with it (such as a file name) then the appropriate value from the command line arguments is passed in arg, and client is the client data passed into icmGetOptions.

The proc call should return icmOk if the actual option and data are valid, icmFailed if there were of valid form but otherwise rejected and icmError if there was an error in processing the option.

icmGetOptions returns the index of the first non-option entry in the command-line argument vector argv.

icmGetOptions `understands' certain standard options to relate to the ICM itself. These options are:

-L <file>
This redirects any ICM-specific log messages to the file file. If - is speficied for the log file name, then log messages are directed to the standard output stream.

Communications server protocol

Many of the API functions are actually commands to the communications server. These commands are delivered to the CS using the normal message passing mechanism: the difference is that the message is intended for the CS itself rather than another agent.

Here, we detail the standard messages that any CS must be able to understand, and the expected format of the reply.

The CS specific API functions are implemented in terms of this protocol. Normally, it is sufficient for an application to simply use the API; however, in some situations it may be desireable for an application to directly use the protocol.

Communications server handle

Each CS has a standard well-known name. This name takes the form:

icmCommServer@home/[home]

The local CS always has the name:

icmCommServer@hostname/[hostname]

where hostname is the full canonical name of the local host machine. It is possible to acquire this name using the API function icmMachineName -- see section icmMachineName.(17)

On occasion, it is necessary to generate a handle to a remote CS, the API function icmCommServerHandle -- see section icmCommServerHandle -- can be used for this purpose.

Message envelope

Each message that is communicated to/from the CS is packaged into a standard message envelope. The message envelope is a standard wrapper for the message being transmitted; it contains information such as the sender of the message, the recipient of the message as well as the message itself.

The top-level of the message envelope takes the form of a 4-tuple:

(recipient,sender,options,message)

recipient
The recipient is the handle which identifies the recipient of the message. This may be modified by intermediate CSs as the message is routed to the final destination -- for example when a communication server forwards a message, it removes its own address from the list of locations in the recipient handle.
sender
The sender is the handle of the originating agent.
message
The message is the actual message being sent.
options
This is a list of additional optional information that can guide and inform the icm network on the appropriate way of treating the message.

Message timeout option

The message timeout option allows the sender of a message to control the valid lifetime of a message. The format of this option is:

(icmLeaseTime, when)

where when is a Unix epoch time value(18) which identifies when the message loses its validity.

If a CS receives a message which has timed out then it is free to discard the message. However, it is not required to discard timed-out messages; and generally a CS should add a `fuzz' factor to any timeout: the reason being that few computers have totally accurate system clocks.

Reply to indicator

The replyto indicator is used when the sender of a message would prefer that any reply should be directed to a third party. The format of this option is:

(icmReplyTo, agent)

This information should be taken at an advisory level only: any agent is free to reply to messages or not -- depending on its `mood'. However, a well behaved agent would typically heed this request.

Message packetization

In addition to a message being wrapped in a standard envelope, it may also be `packetized'.(19) A packetized message consists of a sequence of messages; individual packets of a packetized message have the same form as a regular non-packetized message, except that they have several additional pairs in the options list:

(icmMsgNumber, msgno)
(icmPacketNumber, pno)
(icmPacketCount, count)

where:

msg
msgno is a message number which, together with the sender, can be used to identify which packets belong to which messages.
count
count is the total number of packets in the message.
pno
pno is the number of this packet.

The body portion of each packet is encoded as a data block using the block encoding (see section String or data block encoding).

Once a complete message has been processed, the blocks of the various messages may be concatenated together to form a complete message -- in the normal icm envelope format, including the usual sender , recipient, msg and option fields.

The primary benefit of message packetization is that mixed size messages which emanate from a single source but are destined for different ultimate destinations can be intermingled -- allowing a small message to be sent even while sending a large message at the same time. Of course, if mixed size messages are sent from a single source to a single destination they must still be read in the original order they were sent.

Registering and deregistering agents

Register an agent

In order to register an agent with a CS, it is necessary to send it an icmRegisterAgent message. Note that an application can send messages as soon as it has established a connection with icmInitComms -- see section icmInitComms.

The form of the icmRegisterAgent message is:

(icmRegisterAgent, agent, options)

where agent is a handle that identifies the agent to be registered and options is a list of optional modifications to the request for registering the agent.

The allowed options are:

(icmLeaseTime, when)
This establishes a `lease' on the registration at least until when -- which is an UNIX-style epoch number. If a lease period is established, then the CS will not forcibly deregister the agent until the indicated time even if the agent is disconnects from the CS -- by closing the underlying TCP/IP connection for example. This may allow such a disconnected agent to re-establish itself with the communication server without losing any messages.

The CS responds to an icmRegisterAgent request with its own message:

(icmRegisterAgent, icmOk, handle)
Note that the returned handle may be different to the one sent -- in particular the list of locations associated with the handle may be different.(20) The application should use the returned handle rather than the one sent in the original request.
(icmRegisterAgent, icmFailed, handle)
The CS rejects the request to register the agent -- probably due to the fact that there already is an agent with the same name registered with the CS.

In the situation where an agent only registers with the local communication server, the API function icmRegisterAgent -- see section icmRegisterAgent -- can be used. However, where an agent registers with more than one CS, or where the API functions are not appropriate, an agent can register directly using this message protocol.

De-register an agent

In order to de-register an agent that had previously been registered with a CS, the icmDeregisterAgent message can be used. The form of the message is:

(icmDeregisterAgent, agent)

This message should be sent to the CS that the agent has registered with. In the situation where an agent only registers with the local communication server, the API function icmDeregisterAgent -- see section icmDeregisterAgent -- can be used.

The CS replies with its own message:

(icmDeregisterAgent, icmOk, handle)
The agent was successfully deregistered with the communications server. Note that de-registering an agent does not imply the cancellation of any message forwarding that the agent has requested.
(icmDeregisterAgent, icmFailed, handle)
The agent was unknown by the CS.

Detach an agent

The icmDetachAgent protocol is used by an agent when it wishes to temporarily detach itself from its CS. A detached agent is still registered with the CS, and therefore the CS will hold messages intended for it.

The form of the message is:

(icmDetachAgent, agent)

This message should be sent to the CS that the agent has registered with.

The CS replies with its own message:

(icmDetachAgent, icmOk, handle)
The agent was successfully detached from the communications server.
(icmDetachAgent, icmFailed, handle)
The agent was unknown by the CS.

Attach an agent

The icmAttachAgent protocol is used by an agent when it wishes to re-attach itself to its CS -- after it had previously detached itself. When the agent has reattached itself to its CS, any outstanding messages for it will be delivered.

The form of the message is:

(icmAttachAgent, agent)

This message should be sent to the CS that the agent has registered with.

The CS replies with its own message:

(icmAttachAgent, icmOk, handle)
The agent was successfully (re-)attached to the communications server.
(icmAttachAgent, icmFailed, handle)
The agent was unknown by the CS.

Disconnect all agents

The icmDisconnect message detaches all the agents connected via a given connection.

The form of the message is:

icmDisconnect

This message should be sent to the CS that the agent has registered with.

The CS replies with its own message:

(icmDisconnect, icmOk, tag)
Where tag is an integer key which should be used when the agent reconnects with the CS.

Reconnect all agents

The icmReconnect message reattaches all the agents connected via a given connection, after a connection has been reestablished.

The form of the message is:

(icmReconnect, tag)

where tag was the integer value returned by the communications server at the time of the last disconnect.

The CS replies with its own message:

(icmReconnect, icmOk)

Monitoring agent status

The agent monitoring functions allow applications to be informed when an agent terminates, detaches or re-attaches.

Monitor an agent

The icmMonitorAgent requests that the CS monitors the status of another agent. The `icmMonitorAgent' message should be sent to the communication server where the agent may be registered.(21)

(icmMonitorAgent, agent)

There are two `phases' to this interaction with the communications server: in the initial phase, when the CS responds with a success code:

(icmMonitorAgent, icmOk, agent)
The identified agent will be monitored on behalf of the requesting agent.
(icmMonitorAgent, icmFailed, agent)
The agent was unknown by the CS.

The second phase occurs when some event occurs in the monitored agent. Essentially, CSs recognise and react to four events:

(icmMonitorAgent, icmRegisterAgent, agent)
This message is sent to the monitoring agent when the agent has registered with the CS. Note that if agent was already registered, the requesting agent will get this message immediately.
(icmMonitorAgent, icmDeregisterAgent, agent)
This message is sent to the monitoring agent when the agent has deregistered with the CS. This may safely be taken by the requesting agent as a signal that agent has terminated.
(icmMonitorAgent, icmAttachAgent, agent)
This message is sent to the monitoring agent when the agent has reattached to the CS. This message will only be sent if the agent had detached from the CS.
(icmMonitorAgent, icmDetachAgent, agent)
This message is sent to the monitoring agent when the agent has detached from the CS. The agent is still `extant' from the point of view of the CS -- and therefore should be interpreted as a form of temporary suspension from the network.

Note that the `granularity' of this information should be considered to be quite coarse: one agent may contain many targets, and therefore many semi-independent threads of activity within it.(22)

Stop monitoring an agent

The icmUnMonitorAgent requests that the CS removes any monitors the sender has for the named agent. The icmUnMonitorAgent message should be sent to the communication server where the agent may be registered.

(icmUnMonitorAgent,agent)

The CS returns with:

(icmUnMonitorAgent, icmOk, agent)
The identified agent will no longer be monitored on behalf of the requesting agent.
(icmUnMonitorAgent, icmFailed, agent)
The agent was unknown by the CS.

Forwarding messages to agents

When an agent requests message forwarding, it is the responsibility of the CS to implement it. This message protocol explains the precise commands that must be understood by the CS.

Request message forwarding

The icmRequestForwarding message requests that a communications server forwards messages. The form of this request is:

(icmRequestForwarding, handle)

where handle is a handle that identifies the new location(s) for the agent. Note that handle should identify the same agent as the agent requesting the address forwarding -- as determined by the sender of the message to the CS.

Address forwarding `works' by modifying the identified agent's location. The list of locations referred to in handle gives the agent's new location.

The CS responds to this message with one of:

(icmRequestForwarding, icmOk, handle)
The CS agrees to forward messages to the location(s) referred to in handle.
(icmRequestForwarding, icmFailed, agent)
The agent was unknown by the CS, or was not registered with the CS.

Note that in the case that the forwarding address does not include the `original' location of the agent, it implies that the agent has detached itself from the CS.

Cancelling message forwarding

The icmCancelForwarding message cancels any message forwarding for the agent. The `known' location of the agent resumes to that established when the agent initially registered with the communications server.

(icmCancelForwarding, handle)

Note that the request to cancel forwarding must come from the agent that requested the message forwarding: i.e., that the agent@home portion of handle is the same as that of the sender of the icmCancelForwarding request.

The CS replies with:

(icmCancelForwarding, icmOk, agent)
Any message forwarding for agent has been cancelled.
(icmCancelForwarding, icmFailed, agent)
The agent was unknown by the CS, or the requesting agent was not the same as agent.

An agent's location

The following mesage protocols can be used to determine the current location and live-ness of agents.

Locating an agent

The icmLookupLocation message protocol can be used to determine the current location of an agent.

(icmLookupLocation, agent)

The CS responds with:

(icmLookupLocation, icmOk, handle)
The returned handle embeds within it the location(s) of the agent as known by the CS. Note that this information cannot be guaranteed to be accurate in the case that the looked up agent is mobile: it may have moved.
(icmLookupLocation, icmFailed, agent)
The CS had no knowledge of the agent.

Check if an agent is on-line

The icmPingAgent message can be used to tell if an agent is currently registered and attached.

(icmPingAgent, agent)

The CS responds with:

(icmPingAgent, icmOk, handle)
The agent referred to in handle is both registered and attached. The current location of the agent is embedded within handle. Note that this information cannot be guaranteed to be accurate for any period of time: the agent may deregister or detach immediately after the CS replies with this message. The only guarantee is that the agent was registered at the time that the communications server replied.
(icmPingAgent, icmFailed, agent)
The CS had no knowledge of the agent, or the agent was detached.

Listing the registered agents

The icmListAgents message requests that the CS furnish a list of the currently registered and attached agents.

icmListAgents

The CS reponds with:

(icmListAgents, [h1, ..., h2])
The returned list of handles h1, ..., h2 embeds within them the location(s) of the agents as known by the communications server. Each of h1, ..., h2 are both registered with the CS and currently attached to it.
(icmListAgents, icmFailed)
A CS is not required to furnish the list of registered agents. If, for security reasons, it does not wish to provide this information then it should respond with the icmFailed message.

Supporting additional protocols

The ICM is able to support messages intended for non-ICM based message transport systems. A handle that refers to a non-ICM transport delivery system would normally have the form:

tgt:name@home/[ICM-address,...,proto://address,...]

In order for a CS to be able to deliver messages to non-ICM message transport systems there has to be a `plug-in' for that message system. A plug-in for a message system takes the form of a regular agent; e.g., if an email plug-in is to be registered, then the email `agent' registers itself with the CS in the normal manner.

When the CS attempts to use an external address to deliver a message it looks for an agent that has registered with it using the proto name -- for example, to deliver to an email://... address, the CS looks for an agent called email that is registered locally with it. If such an agent is found, then the message is forwarded to that agent (which is then responsible for actually using the sendmail protocol to deliver the message.

Delivering messages to a protocol agent

When the CS delivers a message to a protocol agent, it is in `full-form' including the message envelope:

(icmFwdMsg,location,
  (recipient,sender,options,message))

where location is the location that the proto agent should use (extracted from the location field of the recipient handle of the original message). This envelope is the envelope of the message to the intended recipient of the message; the complete message to the protocol agent itself looks like:

(proto,icmCommServer@local,[],
    (icmFwdMsg,location,
       (recipient,sender,options,message)))

i.e., the message to the proto agent comes from the CS, but the content of the message is a message envelope to be delivered to the external transport mechanism.

Note that recipient will not include in its list of locations the location referred to by this protocol. For example, if the original recipient handle was:

foo@bar/[email://foo.bar.com,123.456.789.012]

then the recipient handle passed in the envelope to the email protocol handler will be:

foo@bar/[123.456.789.012]

There may be several addresses in a list of locations, and there may be alternate addresses for the same protocol also. However, the protocol agent should be careful to only attempt the delivery of the message to the address identified in the icmFwdMsg message.

It is the responsibility of the protocol agent to reformat the contents of the message in an appropriate manner for the message transport protocol.

If the protocol agent cannot deliver the message, then it should reply to the CS with an icmFwdFailed message:

(icmFwdFailed,reason,badLoc,envelope)

where reason was the reason that the message could not be delivered, badLoc was the `bad location' that the protocol agent couldnt (or wouldnt) access, and envelope is the envelope and original message that was supposed to have been delivered.

The CS responds to this message by attempting to use the next available location from the recip handle to deliver the message.(23)

Using the ICM API

In this example, we construct a `minimal' ICM compatible application, written in C -- which initializes contact with the CS, registers a name -- echo -- and then waits for messages. It responds to all messages by replying with the same message embedded in the structure

(echo, message)

to the agent which sent the message. The full text of this program is listed in section Listing of echo server.

The program is divided into three phases -- an initialization phase, a main loop and a termination phase. In the initialization phase we establish contact with the CS -- after parsing command line options -- and register the name echo with the CS.

In the main phase we wait for messages and respond to them. In this program, we do not use a `busy' wait since that would waste a large amount of CPU resources. The ICM API has a standard function call -- icmEventLoop -- which can be used to implement a simple `main' program that will call one of our own functions every time a message is received. Alternatively we can make use of the lower level UNIX select function to pause the program until there is some data on the ICM's communications socket.

The termination phase is mainly concerned with tidying up; in particular with deregistering the name echo and closing down the API. This phase is actually implemented as a series of hook functions that will be called when the user presses `control-C' at the keyboard. (Like many servers, this one has no natural termination condition.)

Initialization and connection to the commserver

In order to send messages to and receive messages from the commserver we must first of all connect to the CS. The ICM API offers convenience functions to make this process easier.

Standard header files

In order to access ICM's API properly, the standard ICM header file needs to be included:

#include "icm.h"

This header file ensures that all of the ICM's functions and data structures are properly declared. Apart from this file, others may be needed for other access to Unix system services.

Note that in order for the C compiler to be able to find this header file it may be necessary to set a comand-line option to the C compile:

gcc -I icmDir/include ...

Note that, typically, icmDir is set to opt/icm; but this depends on the installation of the ICM.

Selecting standard options

The ICM API has a standard method for accessing command-line options -- as a convenience for the C programmer. The icmGetOptions function (see section icmGetOptions) offers a high-level method to process our echo program's command-line options.

We will process the command line options to allow the user of echo to override the port and host computer to find the CS on.

To use icmGetOptions, we provide a C string which specifies the legal command line options, and a callback procedure -- pickOpt --which is invoked for each commmand line option found by icmGetOptions.

This fragment is part of the pickOpt callback procedure in echo:

...  
case 'n':               /* override agent name for the echo server */
  name = optarg;
  return;
...

When we invoke icmGetOptions we specify what the legal options are, and display an error message if something went wrong at this stage:

if(icmGetOptions(argc,argv,"n:P:",pickOpt,&errflag)==-1){
  if(icmLogFile!=NULL)
    icmLogMsg("Usage: %s [-n name] [-P port]",argv[0]);
  else
    fprintf(stderr,"Usage: %s [-n name] [-P port]",argv[0]);
  exit(1);
}

if(icmLogFile == NULL)
  icmInitLogfile(LOGFILE); /* Default log file */

Setting up the log file

The API supports a file logging function -- reflecting the fact that many servers are not intended to be connected to a console but may still need to record special events in a log file. We will use this to log the messages that the echo server receives.

By default, logging messages generated using icmLogMsg are thrown away, but we want to be able to keep them. So we determine the correct log file from the command line options, or if the user does not specify one we use a default log file.

Notice how we used icmLogMsg if we managed to specify at least the log file. A true daemon would generally have no opportunity to display messages on the console.

The icmInitLogfile function is an ICM API function that creates an output file. By default the API does not have a log file, and all calls to icmLogMsg are discarded. But we generally want to record these messages, so icmInitLogfile makes sure that there is somewhere for the logging messages to go to.

Initializing the ICM API

Before we can send any messages, or receive any messages from the commserver, we must initialise the API using the function icmInitComms which establishes contact with the CS and sets up the raw communications ports.

The initialization sequence in our application with the code fragment is:

icmConn conn;
...

ourHandle = icmParseHandle(name);

if(icmInitComms(portNo,NULL,&conn)==icmOk){

  signal(SIGINT, sig_int);      /* set up signal handling */
  signal(SIGBUS, sig_fatal);
  signal(SIGSEGV, sig_fatal);
  signal(SIGFPE, sig_fatal);

  /* register ourselves */
  if(icmRegisterAgent(conn,ourHandle,NULL,&ourHandle)==icmOk){
    icmLogMsg("Echo server %h started",ourHandle);
    icmEventLoop(conn,HandleQuery,NULL);
  }
  else
    icmLogMsg("Failed to register %h",ourHandle);
}

The call to icmParseHandle constructs a handle from the name that we decided to call our echo server.

Once we have initialized the communications, we register this handle with the CS. Notice that ourHandle is potentially overwritten by the call to icmRegisterAgent -- see section icmRegisterAgent. This is to allow the CS to update us with any proxy services that it is aware of. The returned handle will be essentially the same as the one we supplied, except that it may have additional addresses in the locations field of the handle. These additional addresses refer to proxy services that the CS has registered with; this in turn allows the echo server to reside on a mobile computer, or an intermittently connected computer, without losing messages from other network devices.

The main loop

After registering ourHandle, the echo server enters the standard event loop -- using icmEventLoop (see section icmEventLoop). Once inside this function, the only way that the loop may be terminated is either through an error in the connection to the CS, or if the icmTerminateEventLoop function is called (see section icmTerminateEventLoop).

icmEventLoop expects three arguments -- the connection, a callback function and a `client' parameter that we can pass to the callback function. Since we are just going to echo any messages we get we don't really need to pass in a client value. Our main loop then becomes:

icmEventLoop(conn,HandleQuery,NULL);

The main work goes on in the HandleQuery function. This is called every time the event loop receives a message for the echo application. Since work in the echo program is simply to echo back each message received we can implement this easily:

static icmStatus HandleQuery(icmConn conn,icmMsg msg,
        icmHandle to,icmOption opt,
        icmHandle sender,void *cl)
{
  icmLogMsg("Message `%M' from %h to %h",msg,from,to);

  icmFmtSendMsg(conn,sender,ourHandle,NULL,"%(echo%#%)",msg);

  return icmOk;         /* signal that the message was consumed */
}

To be a little more sophisticated, our message handler can be extended to recognize a termination command, which it responds to by invoking icmTerminateEventLoop (see section icmTerminateEventLoop):

static icmStatus HandleQuery(icmConn conn,icmMsg msg,
        icmHandle to,icmHandle reply,
        icmHandle sender,void *cl)
{
  icmLogMsg("Message `%M' from %h to %h",msg,from,to);

  if(icmScanMsg(msg,"quit")==icmOk){
    icmTerminateEventLoop(conn);
    icmFmtSendMsg(conn,reply,ourHandle,NULL,"%(ok%,quit%)");
  }
  else
    icmFmtSendMsg(conn,reply,ourHandle,NULL,"%(echo%#%)",msg);

  return icmOk;         /* signal that the message was consumed */
}

This slightly more realistic example displays the typical structure of a message handler -- parsing the message for different types of message and generating different responses depending on the particular message.(24)

An alternative main loop

For those applications which are not purely designed as servers for ICM messages, a more complex main loop is necessary. The precise form of this code depends on the operating system and other environmental aspects of the application. Here we assume a Unix environment.

We can simulate the effect of the icmEventLoop using a non-terminating while statement, in each iteration of the loop we test to see if there are any messages. If there are messages then we perform the appropriate actions.

The main function for determining if there is a message for us is icmGetMsg. This returns icmOk if there is a message, and icmFailed otherwise.

In order to make sure that our application does not consume too much CPU resources simply waiting for messages, we use a Unix system call to pause the application until there is data available on ICM's communications port. This is done using the select system call; which takes as argument an array of sockets to `listen to' for data.

In our echo server application, we are only listening to one socket -- ICM's socket; but in a real application we might well be listening to several different sources of data, including but not limited to ICM's message stream.

At the heart of the event loop is the call to icmGetMsg and its response:

while(icmGetMsg(conn,&to,&sender,&opts,&msg,&seqNo)==icmOk){
  HandleQuery(msg,to,sender,opts,NULL);
  icmReleaseMsg(msg);
  icmReleaseOptions(opts);
}

The icmGetMsg function assigns the sender to the handle of the sender of the message, msg is set to the message itself and opts is set to an icmOption structure (see section icmOption) which contains information about any message options selected. The seqNo variable must first be initialized with the sequence number of the first acceptable message, typically this is set to -LONG_MAX:

seqNo = -LONG_MAX;
while(icmGetMsg(conn,&to,&sender,&opts,&msg,&seqNo)==icmOk){
  HandleQuery(msg,to,sender,opts,NULL);
  icmReleaseMsg(msg);
  icmReleaseOptions(opts);
  seqNo = -LONG_MAX;
}

The call to icmReleaseOptions removes any message options structure built as part of the icmGetMsg call.

Selectively reading messages

There are many situations where an agent is not in a position to process just any message, but is looking for a response to a message it sent out earlier. The agent needs to be able to process particular messages, leaving other messages to be processed later.

The ICM API library provides for this with the icmReplaceMsg call (see section icmReplaceMsg). The general technique when an agent is looking for particular messages is to enter a loop which reads any messages that are available but replaces those messages it cannot immediately handle back in the message queue.

For example, the icmRegisterAgent API function is a case in point: this function requires that a message be sent to the CS, and then wait for the reply. However, due to the nature of communication, other messages may be received in between sending the original request and the CS's reply.(25)

The body of the icmRegisterAgent function shows a typical template for this kind of message manipulation:

seqNo = -LONG_MAX;

...

while((ret=icmGetMsg(conn,&to,&sndr,&opts,&msg,&seqNo))==icmOk){
  char res[MAXSTRING];
  icmHandle hh;

  if(icmSameAgentHandle(sndr,conn->server)==icmOk && 
     icmScanMsg(msg,"%(icmRegisterAgent%s%h%)",&res,&hh)==icmOk &&
         icmSameAgentHandle(hh,agent)==icmOk){
    ...                     /* process the reply from the icm */
  }
  else                          /* message doesnt interest us now */
    icmReplaceMsg(conn,to,sndr,opts,msg,seqNo);
}

The technique relies on the specific semantics of the seqNo variable as it relates to icmGetMsg and icmReplaceMsg. This variable represents a message sequence number; icmGetMsg interprets its initial value as the number of the last message that should be ignored before returning a message. For example, if there are 5 messages in the message queue, with sequence numbers 5, 10, 11, 12 and 13 respectively, and seqNo has value 10 on entry to icmGetMsg, then messages 5 and 10 will not be returned by the function. Instead, message number 11 will be returned, and seqNo will be updated to 11.

icmReplaceMsg replaces a message back in the message queue with a specific sequence number; normally the same sequence number that was returned by the prior call to icmGetMsg.

Before deciding whether to accept the message, we apply a test to the message. In this case, the test involves a combination of a scan of the message -- using icmScanMsg -- and a test on the handle of the sender -- using icmSameAgentHandle. This kind of test is typical of message processing; and may involve quite complex processing before deciding whether or not to accept the message.

Terminating cleanly

In order to terminate an agent properly which has initialized contact with the ICM system it is necessary to perform two main functions -- to deregister all agents which have been registered and to terminate the communications.

Note that this is not necessary in normal circumstances for applications using the icmEventLoop as this is done when icmTerminateEventLoop is called (see section icmTerminateEventLoop). However for abnormal termination -- when the user presses control-C for example -- we should still follow these guidelines.

The first is done using the icmDeregisterAgent function (see section icmDeregisterAgent) and the latter is done using the API function icmCloseComms (see section icmCloseComms).

In common with many realistic server applications, our server does not have a natural termination condition. Our server will only be terminated on an error condition or when the user terminates it by pressing control-C on the keyboard.

In order to handle this we establish 'signal handlers' for the main termination situations which are on a `Control-C' and when a major run-time error such as floating point violation or memory violation occurs.

In our example, it is within these functions that we call the API functions to terminate the April communications sessions; although other applications may have more graceful methods of termination.

The functions that perform these exits are:

/*
 * cleanup after CTRL-C and other major errors
 */
void sig_int()
{
  icmLogMsg("Control-C exit");
  icmDeregisterAgent(conn,ourHandle);
  icmCloseComms(conn);
  exit(0);
}

void sig_fatal()
{
  icmLogMsg("bus error or segmentation fault");
  icmDeregisterAgent(conn,ourHandle);
  icmCloseComms(conn);
  exit(0);
}

In order to inform Unix that we want these functions to be called, we add the following lines to the initialization sequence:

signal(SIGINT, sig_int);        /* set up termination routines */
signal(SIGBUS, sig_fatal);
signal(SIGSEGV, sig_fatal);
signal(SIGFPE, sig_fatal);

Note that icmDeregisterAgent and icmCloseComms should only be invoked if contact with the CS has been successfully established.

Detaching versus deregistering

There are occasions when an agent needs to temporarily remove itself from a CS without losing any future messages to it. For this purpose, the icmDetachAgent function (see section icmDetachAgent) can be used.

This prevents further messages to an agent from being sent through the connection, but the messages are not lost: they are held on the CS until the agent re-attaches itself to the CS.

Linking in the correct libraries

In order to access the ICM API, it is necessary to link in to the application code the library file(26) -- libicm.

The sample Makefile listed below show how our server application might be built.

#
# Make sample program to use April's API
#
# define an ANSI C compiler here
CC = gcc

# Where the installation directory of the ICM is
ICM = /opt/icm

CFLAGS = -g -I$(ICM)/include
LIBS = -rpath $(ICM)/lib -L$(ICM)/lib -licm

# Main make target for the sample server
apecho: apecho.o
        $(CC) -o $@ $(EOBJS) $(LDOPTIONS) $(LOCAL_LIBRARIES) $(LIBS)

Testing the echo server

The simplest way of invoking our application is simply to run it on a Unix command line:

% apecho

However, our complete application is configurable, so that we can change the name that we use when registering with the CS. So, we might call our echo server as follows:

% apecho -n MyEcho -L-

which would result in the name MyEcho@host being registered with the local CS and logging messages to be displayed on stderr.

We can test our application using the simple April `pinger' program listed below. This simply sends a message built from the command line to the apecho server, and waits for the reply:

program{
  main(who,msg){
    "echo: "++msg^0++" to "++who^0++"\n">>stdout;

    msg >> who;
    receive{
      X ->> "response was "++string%%X++"\n" >> stdout
    }
  }
} execute main;

The complete echo application is listed in section Listing of echo server.

Message format and structure encoding

Messages in the ICM are sent between agents and CSs in a structured representation. The underlying message format is `self-parsing' and is able to represent symbols, numbers, arbitrary strings, program code, lists and tuples of objects. Each of these types of structures have analogues in most programming languages: for example, the symbol type can be represented in Prolog as an atom, in April as a symbol, and in C or Java as a string. The message format is even able to represent circular or graph structured values.

The self-parsing nature of the message format is important as it allows a great deal of flexibility: it is not necessary for the receiving agent of a message to be able to predict accurately the complete structure of a message prior to receiving it. Instead, the receiving agent can consume the message, and then test it -- using the icmScanMsg API function -- see section icmScanMsg -- to see if the message matches a given pattern.

The representation of values is network neutral, and quite efficient. For certain common cases, the low-level representation is optimized to minimize the length of the encoded message structure.

In this section we show how each type of object is represented in the encoded message.

Representation of values in encoded messages

The low-level structure of a message consists of a sequence of bytes -- in network order. Each object is preceded by a tag byte, followed by zero-or-more data value bytes. Some structures are nested -- such as lists and tuples -- in which case the elements of the nested structure follow the lead tag information.

Integer encoding

An integer is represented as the byte 0x1k , where k is in the range 0x1...0xf, followed by k bytes defining the integer's value. The bytes making the value of the integer are in network -- that is most significant byte first -- order. The value is represented in standard 2's complement form.

For example, the integers 3 and 100,000 are represented by the sequences:

0x11 0x03

and

0x13 0x01 0x86 0xa0

respectively. Note that the value 0 is represented using the sequence 0x10 0x00.

If the tag code is 0x10 then the length of the integer is itself expressed as an integer following the tag code, and the integer data itself follows the length. This allows arbitrary sized integers.

The following C program shows how an integer can be converted into the appropriate stream of bytes:

unsigned char *icmEncodeInt(int tag,long val,unsigned char *buff)
{
  int len=0;
  unsigned char bytes[16];
  unsigned long v = val;

  if(v==0)              /* special case for 0 */
    bytes[len++]=0;
  else
    while(v>0){
      bytes[len++]=v&0xff;
      v >>= 8;
    }

 *buff++=tag|len;       /* 0x1n ... */

  while(len-->0)
    *buff++=bytes[len];
  return buff;
}

Note this program does not allow for encoding large numbers.

Floating point encoding

A floating point number is represented using the tag byte 0x2n, or 0x3n depending on whether the floating point number is positive or negative, followed by the exponent of the number -- in unbaissed form -- encoded in the same format as an integer and then n bytes of mantissae.

The following C program shows how a floating point number can be converted into the appropriate stream of bytes:

/* Convert a double into a string buffer */
unsigned char *icmEncodeFlt(double f, unsigned char *buff)
{
  unsigned char bytes[16], *bf = bytes;
 int i,exp,len,sign=0x20; /* positive float */

  if(f<0.0){
   sign = 0x30;         /* negative float */
    f = -f;
  }

  f = frexp(f, &exp);

  for(len=0;f!=0.0;len++){
    double ip;
    f = modf(f*256,&ip);

    *bf++ = (int)ip;
  }

 *buff++=sign|len;      /* the lead character of the number */
 buff = icmEncodeInt(exp,0x10,buff); /* the exponent */

  for(i=0;i<len;i++)
   *buff++=bytes[i];
  return buff;
}

Symbol encoding

A symbol is represented by a leading 0x4k byte, where k is a number in the range 0x0...0xf followed by a length number N -- itself encoded in k bytes -- followed by N characters of the symbol's name.

For example, the symbol apple is represented by the sequence:

0x41 0x05 0x61 0x70 0x70 0x6c 0x6e

Handle encoding

A handle is represented using a lead 0x50 byte, followed by four values -- the decorations on the handle, the name of the agent, its home location and a list of `current' or actual locations. These values are either of the form of a symbol or the nil tag -- (0x80) -- in the case that they are to be omitted from the handle.

For example, the handle:

foo:bar@home.com/[123.456.789.012, 127.0.0.1]

is represented in the encoding as:

0x50 0x41 0x03 0x66 0x6f 0x6f 0x41 0x03 0x62 0x61 0x72 0x41 0x08 0x68
0x6d 0x6e 0x2e 0x63 0x68 0x6d 0x81 0x0f 0x31 0x32 0x33 0x2e 0x34 0x35
0x36 0x2e 0x37 0x38 0x39 0x2e 0x30 0x31 0x32 0x81 0x31 0x32 0x37 0x2e
0x30 0x2e 0x30 0x2e 0x31 0x80

String or data block encoding

A string or uninterpreted data block contains bytes that are not interpreted as having an internal structure. Their type is analogous to a string in Java or April.

A string is represented with a leading 0x6k byte, where k is a number in the range 0x0...0xf followed by a length number N -- itself encoded in k bytes -- followed by N bytes of data.

For example, the string: "apple" might be encoded as an uninterpreted byte sequence:

0x61 0x05 0x61 0x70 0x70 0x6c 0x6e

Program code encoding

A program code is necessarily quite platform specific. In our representation of program codes, we assume that there is an internal method for verifying that a piece of program code is of the correct structure. From the point of view of the ICM message encoding, program codes are encoded in a similar manner to uninterpreted strings or uninterpreted data blocks.

A program code is represented with a leading 0x7k byte, where k is a number in the range 0x0...0xf followed by a length number N -- itself encoded in k bytes -- followed by N bytes of data.

List encoding

A list is a sequence of values, intended to be held either in a Prolog, LISP or April style list, or as an array of values in languages such as C, or Java.

Lists come in two `sizes': the empty list and the non-empty list. An empty list is represented by the 0x80 byte -- with no data following it -- and a non-empty list consists of the 0x81 byte, followed by the encoding of the head of the list, followed by the encoding of the tail of the list.

For example, the list of integer:

[1,2,3]

is encoded using the sequence:

0x81 0x11 0x01 0x81 0x11 0x02 0x81 0x11 0x03 0x80

Tuple/record encoding

A tuple is similar to a list except that the elements of the tuple will often be of different type. Tuples are analogous to struct records in C, object instance in Java, and -- in a particular form -- functors in Prolog.

A tuple is encoded using the lead byte 0x9k -- where k is a number in the range 0x0...0xf -- followed by the length N of the tuple -- itself encoded using k bytes -- followed by the N elements of the tuple.

For example, the April tuple

(fred, 23, [])

would be represented using the sequence:

0x91 0x03 0x41 0x04 0x66 0x72 0x65 0x64 0x11 0x17 0x80

Circular structure and graph structure encoding

Some structures must be represented using some kind of circular or graphical structure combination of lists and tuples. The ICM message encoding permits this through the use of tags and references. A tag is a marker that identifies a particular structure -- giving it a kind of label. A reference is a reference to a structure that is marked elsewhere in the message: the value denoted by a reference is the value marked by the corresponding tag.

With this, we can embed circular and arbitrary graphical structures in messages and reproduce their structure when the message is decoded.

Clearly, the exact manner in which circular and graphical structures are decoded depends on specific language platforms and data structure choices. In the case of Prolog, it is not legal to have a circular structure, but certain graph structures -- arising from the use of variable/variable bindings -- can be represented and decoded. In the case of C, while it is possible to represent circular structures, the lack of a standard C data type makes general decoding of circular messages difficult without reference to the application.

A tag is represented as the byte 0x9n -- where n is a number in the range 0x0...0xf -- followed by n bytes which encode a tag number N; followed by the encoding of the tagged structure itself.

A reference is represented using the byte 0xBn -- where n is a number in the range 0x0...0xf -- followed by n bytes which encode a tag number N. The `value' of the encoded structure is determined by the previously decoded tagged object for the same N.

For example, the following circular structure:

L0: (foo, 23, R0)

where R0 is a reference to the complete tuple, can be encoded using:

0xA1 0x00 0x91 0x03 0x41 0x03 0x66 0x6f 0x6f 0x11 0x17 0xB1 0x00

Shorthand references

A shorthand reference allows significant message compression by avoiding repetition of elements of the message. The basic form of a shorthand reference consists of a definition followed by a structure which may (or may not) embed a reference to the definiens.

A shorthard definition is encoded using the lead byte 0xCn -- where n is a number in the range 0x0...0xf -- followed by a number N which is the key, followed by an encoded term -- which is the definiens -- followed by a second encoded term -- which is the `value' of the entire sequence.

For example, the structure

(foo, (bar, foo), foo)

would normally be represented using the sequence:

0x91 0x03 0x41 0x03 0x66 0x6f 0x6f 0x91 0x02 0x41
0x03 0x62 0x61 0x72 0x41 0x03 0x66 0x6f 0x6f 0x41 0x03 0x66 0x6f 0x6f 

There are three references to the symbol foo. Normally, each reference would result in the symbol's name being represented in full; however, using a shorthand reference we can instead represent the structure using:

0xC1 0x00 0x41 0x03 0x66 0x6f 0x6f 0x91 0x03 0xB1 0x00 0x91 0x02 0x41
0x03 0x62 0x61 0x72 0xB1 0x00 0xB1 0x00

Note that references to the shorthand object are made using the same encoded sequence as references to values in a circular structure.

Encapsulated values

A type signed value or encapsulated value has a type signature associated with it as well as the value. Encapsulated values allow a greater security than un-adorned values since it is possible to be much more specific about the type of a value. For example, an empty list can -- in principle -- be consistent with any kind of list; however embedding the empty list in an encapsulated structure would allow programs to send an empty list of numbers (say) and have that be represented differently to a list of handles (say).

An encapsulated value is encoded using the code 0xd0, followed by a string (itself represented using the uninterpreted data format) representing the type signature of the value, and this is followed by the value itself.

For example, an empty list of number can be encoded as:

0xd0 0x61 0x02 0x4c 0x4e 0x80

where 0xd0 introduces the encapsulated value, 0x61 0x02 introduces a string which will contain a the type signature as a two byte string, 0x4c 0x4e ('LN') signifies a number list and 0c80 is the empty list itself.

The format of type signatures is presented below.

Opaque values

An opaque value is a value which is wrapped by the application. From the point of view of the ICM, an opaque value has no internal structure; however, it does have an `owner'. The owner can be used by the intending applications (and other applications) to determine if the value `belongs to them'.

The message format of an opaque value does not mandate the form of either the owner or the data; however, the convention we recommend (and enforced in parts of the ICM API), is that the owner is in the form of a symbol and the data is in the form of an uninterpreted data block.

An opaque value is encoded using the code 0xE0, followed by the encoding of the owner and then the encoding of the data itself.

One application of opaque values is to embed ICM messages inside an ICM message, or to embed a message encoded in another encoding scheme.

Type signature

A type signature is a string that describes a type. The type signature string follows a prefix format: a lead character denotes the top-level type (such as list or tuple) and this is optionally followed by type signatures of the elements of the list or tuple.

N
The 'N' type signature denotes a number value. Note that no distinction is made between integers or floating point values.
s
The 's' type signature denotes a symbol value. Note that this is different to the `literal signature' described below.
S
The 'S' type signature denotes a string or uninterpreted data value.
h
The 'h' type signature denotes a handle value
l
The 'l' type signature denotes a logical value. Legal values of this type are the symbols false and true.
L
The 'L' type signature denotes a list value. The type of the elements of the list follows the L character.
T
The 'T' type signature denotes a tuple value. It is followed by a byte giving the number of elements of the tuple and then the types of the elements of the tuple in order.
|
The '|' type signature denotes an alternative type. It is followed by two type signatures representing the either/or branches. Note that it is not a good idea to use `predefined' types (such as N or Lh in the branches of a alternative type. Instead the arms of the alternatives should either be literal types or labelled types.
#
The '#' type signature denotes a particular symbol. It is followed by the symbol itself enclosed in delimiter characters (which may be any character not itself included in the symbol. For exmaple, the literal value false is represented with the type signature:
#'false'
Literal types are mostly used in conjunction with alternate types.
R
The 'R' type signature denotes a labelled type. It is followed by the label -- which is itself enclosed in delimiter characters as with the literal type -- and the labelled type itself. For example, to denote a time record where the labelled type is a triple of numbers, we use:
R'time'T\3NNN
?
The '?' type signature denotes a fielded type. I.e., an elements of a record. The ? is followed by the delimited field name and the type itself.
$
The '$' introduces a type variable. Where the type variable is `free' in the type signature it means that the type is left unspecified and may be any legal type. This is only valid for values such as the empty list which are the same for all possible lists. The '$' character is followed by a byte giving an index which represents the variable. Tyep variables with the same index in a type signature represent the same variable.
.
The '.' character denotes a bound or fix-point type. The form of a fixpoint type is:
X = type-expression
where type-expression may refer to X. The meaning of such a type is the smallest type for which the equation holds -- otherwis referred to as recursive types. For example, a type for the tree type:
tree ::= empty | node(tree,tree)
would be represented as a type signature:
.\0|'empty'R'node'$\0$\0
The byte following the '.' character is an index. If a type variable of the same index appears in the following type expression then the type variable refers to the `entire' type.
The ':' character denotes a universally quantified type. A universally quantified type refers to all possible instantiations of the bound variable. The bound variable is represented by an index -- whcih is represented in the byte following the ':' character. For example, to represent the type:
tree(%A) ::= empty | node(tree(%A),%A,tree(%A))
we can use the type signature:
:\0.\1|#'empty'R'node'$\0$\1$\0
note that the fixpoint variable and universally bound variable must be distinguished within the main type expression since they are both `in scope'. In practice, universally quantified type expressions do not apply to literal data values; however they may apply in the case of functions or procedures.
F
The 'F' type signature denotes a function. It is followed by two types: the type of the argument vector of the function and the type of the returned value. The ICM system allows functions (and procedures) to be sent in messages. However, the code format used to represent code fragments is beyond the scope of the ICM itself.
P
The 'P' type signature denotes a procedure. It is followed by the type of the argument vector of the procedure.
A
The 'A' type signature denotes an encapsulated (or any) value.

Listing of echo server

This program is the complete listing of a simple but functional server that uses the ICM's api.

/*
 * This is a simple `echo' server implemented as an example to demonstrate 
 * The ICM API
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "icm.h"
#include "icmFile.h"

char *name = "echo";
int portNo = 0;
#define LOGFILE "echo.log"
icmHandle ourHandle = NULL;
icmConn conn=NULL;
long delayTime = 0;			/* delay before sending replies */

#ifdef ALLTRACE
#ifndef COMMTRACE
#define COMMTRACE
#endif
#endif

#ifdef COMMTRACE
extern icmTruth traceMsg;
#endif

void sig_int(int sig);
void sig_fatal(int sig);

static icmStatus HandleQuery(icmConn con,icmHandle recip,icmHandle snder,
			     icmOption opts,icmMsg msg,void *cl);

icmStatus pickOpt(char opt,char *optarg,void *cl)
{
  switch(opt){
  case 'P':
    portNo = atoi(optarg);
    return icmOk;
  case 'n':
    name = optarg;
    return icmOk;

  case 'd':
    delayTime = atoi(optarg);   /* allows a delay between msg and response */
    return icmOk;

  case 'D':{			/* Debug options */
    char *d = optarg;
    while(*d!='\0')
      switch(*d++){

      case 'm':			/* Tracing TCP-level messages? */
#ifdef COMMTRACE
	traceMsg = icmYes;
	continue;
#else
	fprintf(stderr,"Message tracing not enabled\n");
	return icmError;
#endif
      default:
	fprintf(stderr,"Unknown debug option [%c]\n",*(d-1));
	return icmError;
      }
    return icmOk;
  }
  default:
    return icmError;
  }
}

int main(int argc, char **argv)
{
  if(icmGetOptions(argc,argv,"n:d:P:L:D:",pickOpt,NULL)==-1){
    if(icmLogFile!=NULL)
      icmLogMsg("Usage: %s [-n name] [-n delay] [-P port] [-L log]",argv[0]);
    else
      fprintf(stderr,"Usage: %s [-n name] [-n delay] [-P port] [-L log]",argv[0]);
    exit(1);
  }

  if(icmLogFile == NULL)
    icmInitLogfile(LOGFILE);    /* Default log file */

  ourHandle = icmParseHandle(name);

  if(icmInitComms(portNo,NULL,&conn)==icmOk){

    signal(SIGINT, sig_int);	/* set up termination routines */
    signal(SIGBUS, sig_fatal);
    signal(SIGSEGV, sig_fatal);
    signal(SIGFPE, sig_fatal);

    /* register ourselves */
    if(icmRegisterAgent(conn,ourHandle,NULL,&ourHandle)==icmOk){
      icmLogMsg("Echo server %h started",ourHandle);
      icmEventLoop(conn,HandleQuery,NULL);
    }
    else
      icmLogMsg("Failed to register %h",ourHandle);
  }
  else
    icmLogMsg("Failed to initialize ICM API");
  return 0;
}
	
static icmStatus HandleQuery(icmConn con,icmHandle recip,
			     icmHandle snder,icmOption opts,icmMsg msg,void *cl)
{
  icmHandle replyto = snder;

  icmIsOption(opts,icmReplyto,&replyto);

  icmLogMsg("Message `%M' from %h",msg,snder);

  if(icmScanMsg(msg,"quit")==icmOk){
    icmTerminateEventLoop(conn);
    icmFmtSendMsg(conn,replyto,ourHandle,NULL,"%(ok%,quit%)");
  }
  else{
    sleep(delayTime);
    icmFmtSendMsg(conn,replyto,ourHandle,NULL,"%(echo%#%)",msg);
  }
  return icmOk;
}

/*
 * cleanup after CTRL-C and other major errors
 */
void sig_int(int sig)
{
  icmLogMsg("Control-C exit");
  icmDeregisterAgent(conn,ourHandle);
  icmCloseComms(conn);
  exit(0);
}

void sig_fatal(int sig)
{
  icmLogMsg("bus error or segmentation fault");
  icmDeregisterAgent(conn,ourHandle);
  icmCloseComms(conn);
  exit(0);
}

Concept index

Jump to: a - b - c - d - e - f - g - h - i - l - m - o - p - r - s - t - w

a

  • access canonical host name, access canonical host name
  • Accessing standard host names
  • Address forwarding
  • Agent handle functions
  • Agent handles
  • Agent identity and agent mobility
  • Agent names, home bases and locations
  • Agent tracking
  • Aliasing
  • allocate from a pool
  • API function to (re-)attach an agent
  • API function to detach an agent
  • API functions for managing agents
  • API functions for message handling
  • API header files
  • b

  • Basic communications model
  • Basic steps to communication
  • Build a new hash table
  • c

  • Circular structure and graph structure encoding
  • Command-line options processing
  • Communications server handle
  • Communications server protocol
  • Computers that move
  • d

  • Data structure pooling
  • Delete a hash table
  • Delivering messages to a protocol agent
  • Detaching versus deregistering
  • Double identity
  • e

  • echo main loop, echo main loop
  • Example uses of the ICM API
  • f

  • Floating point encoding
  • Format of encapsulated values
  • Format of opaque values
  • g

  • General types
  • Globally unique agent identifiers
  • h

  • Handle encoding
  • Hash table processing
  • hold on to a handle
  • i

  • ICM communications and Firewalls
  • ICM communications libraries
  • ICM communications servers and firewalls
  • icmPoolBase -- opaque pool base pointer type
  • Initialization and connection to the commserver
  • initialize a data structure pool
  • Initializing the ICM API
  • install new entry in table
  • Integer encoding
  • Intranets and Extranets
  • Issues to do with message forwarding and agent migration
  • l

  • Linking in API libraries
  • List encoding
  • Listing of the echo server
  • Lost agents
  • Low-level message generation
  • Low-level message parsing
  • m

  • Managing communications
  • Message envelope
  • Message format and structure encoding
  • Message formatting and parsing
  • message handling
  • message ordering
  • Message ordering and mobile agents
  • Message packetization
  • Message timeout option
  • Messages understood by the communications server
  • Missed messages
  • Mobile agents with a intermittently connected home base
  • Mobile agents with static home base
  • Moving an agent
  • Multiple firewalls
  • o

  • Options for invoking the icm
  • Other useful ICM API functions
  • p

  • Process command line options
  • Process whole hash table
  • Program code encoding
  • Protocol for forwarding messages to agents
  • Protocol for monitoring agent status
  • Protocol for registering and deregistering agents
  • Protocol to attach an agent
  • Protocol to cancel message forwarding
  • Protocol to check if an agent is on-line
  • Protocol to de-register an agent
  • Protocol to detach an agent
  • Protocol to disconnect all agents
  • Protocol to list the registered agents
  • Protocol to locate an agent
  • Protocol to monitor the status of an agent, Protocol to monitor the status of an agent
  • Protocol to reconnect all agents
  • Protocol to register an agent
  • Protocol to request message forwarding
  • Protocols to determine agents' locations
  • r

  • release an object back into the pool
  • Release option structure
  • remove entry in table
  • Reply to indicator
  • Representation of values in encoded messages
  • Rules for receiving messages for non-registered agents
  • Rules for receiving messages for registered agents
  • s

  • search for entry in table
  • select system call
  • Selecting standard options
  • Selectively reading messages
  • Semantics of the location field of a handle
  • Sending a message to an agent on an Intranet host
  • Sending messages from an Intranet host to an external host
  • Setting up the log file
  • Shorthand references
  • String or data block encoding
  • Supporting additional protocols
  • Supporting mobile agents
  • Supporting mobile computers
  • Symbol encoding
  • t

  • Terminating echo cleanly
  • Test option structure
  • Testing the echo server
  • The InterAgent Communications model
  • Tuple/record encoding
  • Type signature
  • Type signatures
  • w

  • When an external protocol agent fail to deliver a message
  • Standard function index

    Jump to: i

    i

  • icmAllocPool API function
  • icmAnalyseHandle API feature
  • icmAttachAgent API function
  • icmAttachAgent commserver message type
  • icmCancelForwarding API feature
  • icmCloseComms API feature
  • icmCloseComms API function
  • icmCommServerHandle API function
  • icmCompFun ICM API type
  • icmConn API feature
  • icmDataPo API feature
  • icmDelHash API function
  • icmDeregisterAgent API feature
  • icmDeregisterAgent API function
  • icmDeregisterAgent commserver message type
  • icmDestFun ICM API type
  • icmDetachAgent API function
  • icmDetachAgent commserver message type
  • icmDetachComms API feature
  • icmDisconnect API feature
  • icmDisconnect CS message
  • icmElTag API type
  • icmEventLoop API feature
  • icmExpectApply API feature
  • icmExpectCode API feature
  • icmExpectData API feature
  • icmExpectFlt API feature
  • icmExpectHandle API feature
  • icmExpectInt API feature
  • icmExpectList API feature
  • icmExpectNil API feature
  • icmExpectOpaque API feature
  • icmExpectRef API feature
  • icmExpectSym API feature
  • icmExpectTag API feature
  • icmExpectTuple API feature
  • icmFmtSendMsg API function
  • icmFormatMsg API feature
  • icmFreePool release an object back into the pool
  • icmGetHostname API function
  • icmGetMsg API feature
  • icmGetMsg API function, icmGetMsg API function
  • icmGetOptions API function
  • icmHandle API feature
  • icmHandleName API function
  • icmHashFun ICM API type
  • icmHashPo ICM API type
  • icmIdleProc API feature
  • icmInitComms API feature
  • icmInitPool API function
  • icmInstall API function
  • icmIsOption API function
  • icmKeepHandle
  • icmLeaseTime message protocol attribute
  • icmListAgents API feature
  • icmLookupLocation communication server message
  • icmMachineName API function
  • icmMakeHandle API feature
  • icmMonitorAgent API feature
  • icmMonitorAgent communication server message
  • icmMsg API feature
  • icmMsgAval API feature
  • icmMsgElType API function, icmMsgElType API function
  • icmMsgProc API feature
  • icmNewHash ICM API function
  • icmNewMsg API feature
  • icmNewOpt API feature
  • icmOpaquePo API feature
  • icmOption API type, icmOption API type
  • icmOptionCallback API type
  • icmParseHandle API function
  • icmPingAgent API feature
  • icmPingAgent CS message
  • icmPlaceApply API feature
  • icmPlaceCode API feature
  • icmPlaceData API feature
  • icmPlaceFlt API feature
  • icmPlaceHandle API feature
  • icmPlaceInt API feature
  • icmPlaceList API feature
  • icmPlaceMsg API feature
  • icmPlaceNil API feature
  • icmPlaceRef API feature
  • icmPlaceSym API feature
  • icmPlaceTag API feature
  • icmPlaceTuple API feature
  • icmPoolBase opaque pool base pointer type
  • icmProcessTable API function
  • icmProcFun ICM API type
  • icmReattachComms API feature
  • icmReconnect API feature
  • icmReconnect CS message
  • icmRegisterAgent API feature
  • icmRegisterAgent API function
  • icmRegisterAgent commserver message type
  • icmReleaseHandle API feature
  • icmReleaseMsg API feature
  • icmReleaseOptions API function
  • icmReplaceMsg API feature
  • icmRequestForwarding API feature
  • icmSameAgentHandle API feature
  • icmScanMsg API feature
  • icmScanXMsg API function
  • icmSearch API function
  • icmSendMsg API feature
  • icmSetIdleProc API feature
  • icmSkipElement API feature
  • icmStatus API type
  • icmTerminateEventLoop API function
  • icmUninstall API function
  • icmUnMonitorAgent API feature
  • icmUnMonitorAgent communication server message
  • icmWaitMsg API feature

  • This document was generated on 10 October 2002 using texi2html 1.56k.