Browse Source

initial import

Alois Zoitl 16 years ago
parent
commit
c75a26ca5d

+ 154 - 0
.cproject

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?fileVersion 4.0.0?>
+
+<cproject>
+<storageModule moduleId="org.eclipse.cdt.core.settings">
+<cconfiguration id="cdt.managedbuild.toolchain.gnu.cygwin.base.1026482511">
+<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cygwin.base.1026482511" moduleId="org.eclipse.cdt.core.settings" name="Cygwin GCC">
+<externalSettings/>
+<extensions>
+<extension id="org.eclipse.cdt.core.Cygwin_PE" point="org.eclipse.cdt.core.BinaryParser"/>
+<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+</extensions>
+</storageModule>
+<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+<configuration artifactName="OpENer" buildProperties="" id="cdt.managedbuild.toolchain.gnu.cygwin.base.1026482511" name="Cygwin GCC" parent="org.eclipse.cdt.build.core.emptycfg">
+<folderInfo id="cdt.managedbuild.toolchain.gnu.cygwin.base.1026482511.288016310" name="/" resourcePath="">
+<toolChain id="cdt.managedbuild.toolchain.gnu.cygwin.base.337077287" name="cdt.managedbuild.toolchain.gnu.cygwin.base" superClass="cdt.managedbuild.toolchain.gnu.cygwin.base">
+<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.Cygwin_PE" id="cdt.managedbuild.target.gnu.platform.cygwin.base.1379908666" name="Debug Platform" osList="win32" superClass="cdt.managedbuild.target.gnu.platform.cygwin.base"/>
+<builder id="cdt.managedbuild.target.gnu.builder.cygwin.base.448784399" managedBuildOn="false" name="Gnu Make Builder.Cygwin GCC" superClass="cdt.managedbuild.target.gnu.builder.cygwin.base"/>
+<tool id="cdt.managedbuild.tool.gnu.assembler.cygwin.base.148994494" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.cygwin.base">
+<inputType id="cdt.managedbuild.tool.gnu.assembler.input.443881293" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+</tool>
+<tool id="cdt.managedbuild.tool.gnu.archiver.cygwin.base.1409668832" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.cygwin.base"/>
+<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.cygwin.base.1706444915" name="Cygwin C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.cygwin.base">
+<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.cygwin.1289037190" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input.cygwin"/>
+</tool>
+<tool id="cdt.managedbuild.tool.gnu.c.compiler.cygwin.base.1767451638" name="Cygwin C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.cygwin.base">
+<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.cygwin.1879919928" superClass="cdt.managedbuild.tool.gnu.c.compiler.input.cygwin"/>
+</tool>
+<tool id="cdt.managedbuild.tool.gnu.c.linker.cygwin.base.1530826068" name="Cygwin C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.cygwin.base"/>
+<tool id="cdt.managedbuild.tool.gnu.cpp.linker.cygwin.base.1901443871" name="Cygwin C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.cygwin.base">
+<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.23921161" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+<additionalInput kind="additionalinput" paths="$(LIBS)"/>
+</inputType>
+</tool>
+</toolChain>
+</folderInfo>
+</configuration>
+</storageModule>
+<storageModule moduleId="scannerConfiguration">
+<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"/>
+<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="makefileGenerator">
+<runAction arguments="-f ${project_name}_scd.mk" command="make" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC">
+<buildOutputProvider>
+<openAction enabled="true" filePath=""/>
+<parser enabled="true"/>
+</buildOutputProvider>
+<scannerInfoProvider id="specsFile">
+<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/>
+<parser enabled="true"/>
+</scannerInfoProvider>
+</profile>
+</storageModule>
+<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+<storageModule moduleId="org.eclipse.cdt.core.language.mapping"/>
+<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
+<buildTargets>
+<target name="all" path="bin/pc" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
+<buildCommand>make</buildCommand>
+<buildArguments/>
+<buildTarget>all</buildTarget>
+<stopOnError>true</stopOnError>
+<useDefaultCommand>true</useDefaultCommand>
+<runAllBuilders>true</runAllBuilders>
+</target>
+<target name="clean" path="bin/pc" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
+<buildCommand>make</buildCommand>
+<buildArguments/>
+<buildTarget>clean</buildTarget>
+<stopOnError>true</stopOnError>
+<useDefaultCommand>true</useDefaultCommand>
+<runAllBuilders>true</runAllBuilders>
+</target>
+</buildTargets>
+</storageModule>
+</cconfiguration>
+</storageModule>
+<storageModule moduleId="cdtBuildSystem" version="4.0.0">
+<project id="OpENer.null.735668320" name="OpENer"/>
+</storageModule>
+</cproject>

+ 78 - 0
.project

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>OpENer</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
+			<triggers>clean,full,incremental,</triggers>
+			<arguments>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
+					<value>clean</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>?name?</key>
+					<value></value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.append_environment</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.stopOnError</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.buildCommand</key>
+					<value>make</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.contents</key>
+					<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
+					<value>false</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.enableFullBuild</key>
+					<value>true</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.buildArguments</key>
+					<value></value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.fullBuildTarget</key>
+					<value>all</value>
+				</dictionary>
+				<dictionary>
+					<key>org.eclipse.cdt.make.core.autoBuildTarget</key>
+					<value>all</value>
+				</dictionary>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.cdt.core.ccnature</nature>
+		<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
+		<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
+		<nature>org.eclipse.cdt.core.cnature</nature>
+	</natures>
+</projectDescription>

+ 4 - 0
AUTHORS

@@ -0,0 +1,4 @@
+Kurt Schweiger
+Rene Smodic
+Alois Zoitl
+Jonathan Engdahl

+ 0 - 0
ChangeLog


+ 53 - 0
README

@@ -0,0 +1,53 @@
+==================
+OpENer Version 1.0
+==================
+
+Welcome to OpENer!
+
+OpENer is an EtherNet/IP(TM) stack for I/O adapter devices; supports multiple 
+I/O and explicit connections; includes objects and services to make EtherNet/IP-
+compliant products defined in THE ETHERNET/IP SPECIFICATION and published by 
+ODVA (www.odva.org).
+
+Requirements:
+=============
+OpENer has been developed to be highly portable. The default version targets PCs
+with a POSIX operating system and a BSD-socket network interface. To test this 
+version we recommend a Linux PC or Windows with Cygwin (www.cygwin.com) 
+installed. You will need to have the following installed:
+
+ gcc, make, binutils, etc. 
+ 
+for normal building. These should be installed on most Linux installations and
+are part of the development packages of cygwin.
+
+For the development itself we recommend the use of Eclipse with the CTD plugin 
+(www.eclipse.org).
+
+Compile for PCs:
+================
+  1. Directly in the shell:
+     1.1 Go into the bin/pc directory
+     1.2 Invoke make
+     1.3 For invoking opener type:
+          ./opener.exe ipaddress subnetmask gateway domainname hostaddress macaddress
+         e.g. ./opener.exe 192.168.0.2 255.255.255.0 192.168.0.1 test.com testdevice 00 15 C5 BF D0 87 
+  2. Within Eclipse 
+     2.1 Import the project
+     2.2 Go to the bin/pc folder in the make targets view
+     2.3 Choose all from the make targets
+     2.4 The resulting executable will be in the directory
+        ./bin/pc
+
+Documentation:
+==============
+The documentation of the functions of OpENer is part of the source code. You 
+will need the program Doxygen for generating the HTML documentation.
+
+Porting OpENer:
+===============
+For porting OpENer to new platforms please see the porting section in the 
+Doxygen documentation.  
+
+
+

+ 15 - 0
TODO

@@ -0,0 +1,15 @@
+This file serves as brainstorming buffer for ideas to improve and enhance OpENer:
+
+* New Features:
+  - Change of State and application triggered connections
+  - Implementation of common CIP-objects
+  - CIP-Sync
+  - CIP-Motion
+
+
+* Improvements and Optimizations
+  - Remove the need for the response buffer in the explicit message handling (zero copy stack)
+  - Rework I/O message handling:
+     - own buffers for each connection that are preconfigured and only runtime data needs to be changed.
+     - Use only one single outgoing UDP socket for I/O messages    
+          

+ 91 - 0
bin/pc/Makefile

@@ -0,0 +1,91 @@
+#*******************************************************************************
+# * Copyright (c) 2009, Rockwell Automation, Inc.
+# * All rights reserved. 
+# *
+# * Contributors:
+# *     <date>: <author>, <author email> - changes
+# ******************************************************************************
+SILENT=0		# do not print, terminal is not available
+TERSE=1			# error and connection up/down messages only
+VERBOSE=2		# running commentary
+VVERBOSE=3		# commentary plus message dumps
+
+
+CFLAGS= \
+	-g \
+	-I . \
+	-I ../../src \
+	-I ../../src/enet_encap \
+	-I ../../src/cip \
+	-I ../../src/ports/platform-pc \
+	-DEIP_DEBUG=$(VERBOSE) \
+	-W -Wall
+
+LDFLAGS=
+
+SOURCES= \
+	../../src/ports/platform-pc/main.c \
+	../../src/cip/cipconnectionmanager.c	\
+	../../src/cip/cipcommon.c \
+	../../src/cip/cipmessagerouter.c \
+	../../src/cip/cipassembly.c \
+	../../src/cip/ciptcpipinterface.c \
+	../../src/cip/cipidentity.c \
+	../../src/cip/cipethernetlink.c \
+	../../src/enet_encap/cpf.c \
+	../../src/enet_encap/endianconv.c \
+	../../src/enet_encap/encap.c \
+	../../src/ports/platform-pc/networkhandler.c
+
+
+OBJECTS= \
+	../../src/ports/platform-pc/main.o \
+	../../src/ports/platform-pc/networkhandler.o \
+	../../src/cip/cipconnectionmanager.o \
+	../../src/cip/cipcommon.o \
+	../../src/cip/cipmessagerouter.o \
+	../../src/cip/cipassembly.o \
+	../../src/cip/ciptcpipinterface.o \
+	../../src/cip/cipidentity.o \
+	../../src/cip/cipethernetlink.o \
+	../../src/enet_encap/cpf.o \
+	../../src/enet_encap/endianconv.o \
+	../../src/enet_encap/encap.o \
+	../../src/ports/platform-pc/stubs.o
+
+
+EXECUTABLE=opener
+
+
+all: $(SOURCES) $(EXECUTABLE)
+	
+$(EXECUTABLE): $(OBJECTS) Makefile
+	@echo "  [LD]	" $@
+	@$(CC) $(LDFLAGS) $(OBJECTS) -o $@
+
+# pull in dependency info for *existing* .o files
+-include ./.deps/*.d
+-include ../ethernet/.deps/*.d
+-include ../cip/.deps/*.d
+
+# compile and generate dependency info;
+# see http://www.cs.berkeley.edu/~smcpeak/autodepend/autodepend.html
+
+DEP="`dirname $*`/.deps/`basename $*.d`"
+
+%.o: %.c Makefile
+	@echo "  [CC]	" $*.c
+	@$(CC) -c $(CFLAGS) $*.c -o $*.o
+	@install -d `dirname $*`/.deps
+	@$(CC) -MM $(CFLAGS) $*.c > $(DEP)
+	@mv -f $(DEP) $(DEP).tmp
+	@sed -e 's|.*:|$*.o:|' < $(DEP).tmp > $(DEP)
+	@sed -e 's/.*://' -e 's/\\$$//' < $(DEP).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(DEP)
+	@rm -f $(DEP).tmp
+
+
+clean :
+	@echo "  [CLEAN]"
+	@-rm -f $(EXECUTABLE) $(OBJECTS)
+	@-find .. -name .deps | xargs rm -rf
+

+ 55 - 0
license.txt

@@ -0,0 +1,55 @@
+                    SOFTWARE DISTRIBUTION LICENSE FOR THE 
+                     ETHERNET/IP(TM) COMMUNICATION STACK 
+                         (ADAPTED BSD STYLE LICENSE)   
+
+Copyright (c) 2009, Rockwell Automation, Inc. ALL RIGHTS RESERVED.
+EtherNet/IP is a trademark of ODVA, Inc.
+
+Redistribution of the Communications Stack Software for EtherNet/IP and use in 
+source and binary forms, with or without modification, are permitted provided 
+that the following conditions are met:
+
+Redistributions of source code must retain the above copyright and trademark 
+notices, this list of conditions and the following disclaimer in the 
+documentation and/or other materials provided with the distribution.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or 
+other materials provided with the distribution.
+
+Neither the name of Rockwell Automation, ODVA, nor the names of its 
+contributors may be used to endorse or promote products derived from this 
+software without specific prior written permission from the respective owners.
+
+The Communications Stack Software for EtherNet/IP, or any portion thereof, with
+or without modifications, may be incorporated into products for sale.  However,
+the software does not, by itself, convey any right to make, have made, use, 
+import, offer to sell, sell, lease, market, or otherwise distribute or dispose 
+of any products that implement this software, which products might be covered 
+by valid patents or copyrights of ODVA, Inc., its members or other licensors 
+nor does this software result in any license to use the EtherNet/IP mark owned 
+by ODVA. To make, have made, use, import, offer to sell, sell, lease, market, 
+or otherwise distribute or dispose of any products that implement this software, 
+and to use the EtherNet/IP mark, one must obtain the necessary license from 
+ODVA through its Terms of Usage Agreement for the EtherNet/IP technology, 
+available through the ODVA web site at www.odva.org. This license requirement 
+applies equally (a) to devices that completely implement ODVA's Final 
+Specification for EtherNet/IP (“Network Devices”), (b) to components of such 
+Network Devices to the extent they implement portions of the Final 
+Specification for EtherNet/IP, and (c) to enabling technology products, such as
+any other EtherNet/IP or other network protocol stack designed for use in 
+Network Devices to the extent they implement portions of the Final 
+Specification for EtherNet/IP. Persons or entities who are not already licensed
+for the EtherNet/IP technology must contact ODVA for a Terms of Usage Agreement.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

+ 1356 - 0
opener.doxyfile

@@ -0,0 +1,1356 @@
+# Doxyfile 1.5.5
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file 
+# that follow. The default is UTF-8 which is also the encoding used for all 
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the 
+# iconv built into libc) for the transcoding. See 
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME = "OpENer - Open Source EtherNet/IP(TM)  I/O Target Stack"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER = 1
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc\api_doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, 
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), 
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, 
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, 
+# and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Java. For instance, namespaces will be presented as packages, qualified 
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran 
+# sources only. Doxygen will then generate output that is more tailored for 
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL 
+# sources. Doxygen will then generate output that is tailored for 
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want 
+# to include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum 
+# is documented as struct, union, or enum with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code in case the coding convention dictates that all compound 
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be 
+# extracted and appear in the documentation as a namespace called 
+# 'anonymous_namespace{file}', where file will be replaced with the base 
+# name of the file that contains the anonymous namespace. By default 
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS = YES
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the 
+# hierarchy of group names into alphabetical order. If set to NO (the default) 
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = YES
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST = YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from 
+# the version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT = src
+
+# This tag can be used to specify the character encoding of the source files 
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is 
+# also the default input encoding. Doxygen uses libiconv (or the iconv built 
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for 
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = 
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE = src\ports 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the 
+# output. The symbol name can be a fully qualified name, a word, or if the 
+# wildcard * is used, a substring. Examples: ANamespace, AClass, 
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files 
+# will be generated that can be used as input for Apple's Xcode 3 
+# integrated development environment, introduced with OSX 10.5 (Leopard). 
+# To create a documentation set, doxygen will generate a Makefile in the 
+# HTML output directory. Running make will produce the docset in that 
+# directory and running "make install" will install the docset in 
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find 
+# it at startup.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the 
+# feed. A documentation feed provides an umbrella under which multiple 
+# documentation sets from a single provider (such as a company or product suite) 
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that 
+# should uniquely identify the documentation set bundle. This should be a 
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen 
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see 
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the 
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where 
+# the mscgen tool resides. If left empty the tool is assumed to be found in the 
+# default search path.
+
+MSCGEN_PATH = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then 
+# doxygen will generate a call dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable call graphs 
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = YES
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then 
+# doxygen will generate a caller dependency graph for every global function 
+# or class method. Note that enabling this option will significantly increase 
+# the time of a run. So in most cases it will be better to enable caller 
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS = 
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the 
+# number of direct children of the root node in a graph is already larger than 
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is enabled by default, which results in a transparent 
+# background. Warning: Depending on the platform used, enabling this option 
+# may lead to badly anti-aliased labels on the edges of a graph (i.e. they 
+# become hard to read).
+
+DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO

+ 83 - 0
src/cip/cipassembly.c

@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <string.h>    /*needed for memcpy */
+#include "cipassembly.h"
+#include "cipcommon.h"
+#include "opener_api.h"
+
+EIP_STATUS CIP_Assembly_Init()
+  { /* create the CIP Assembly object with zero instances */
+    return createCIPClass(CIP_ASSEMBLY_CLASS_CODE, 0, // # class attributes
+        0xffffffff, // class getAttributeAll mask
+        0, // # class services
+        1, // # instance attributes
+        0xffffffff, // instance getAttributeAll mask
+        1, // # instance services
+        0, // # instances
+        "assembly", 1) ? EIP_OK : EIP_ERROR;
+  }
+
+S_CIP_Instance *createAssemblyObject(EIP_UINT8 pa_nInstanceID,
+    EIP_BYTE * pa_data, EIP_UINT16 pa_datalength)
+  {
+    S_CIP_Class *pstAssemblyClass;
+    S_CIP_Instance *pstAssemblyInstance;
+    S_CIP_Byte_Array *stAssemblyByteArray;
+
+    if ((pstAssemblyClass = getCIPClass(CIP_ASSEMBLY_CLASS_CODE)) == 0)
+      {
+        if ((pstAssemblyClass = createCIPClass(CIP_ASSEMBLY_CLASS_CODE, 0, // # class attributes
+            0xffffffff, // class getAttributeAll mask
+            0, // # class services
+            1, // # instance attributes
+            0xffffffff, // instance getAttributeAll mask
+            1, // # instance services
+            0, // # instances
+            "assembly", 1)) == 0)
+          {
+            return 0;
+          }
+      }
+
+    pstAssemblyInstance = addCIPInstance(pstAssemblyClass, pa_nInstanceID); // add instances (always succeeds (or asserts))
+
+    if ((stAssemblyByteArray = IApp_CipCalloc(1, sizeof(S_CIP_Byte_Array))) == 0)
+      {
+        return 0; //TODO remove assembly instance in case of error
+      }
+
+    stAssemblyByteArray->len = pa_datalength;
+    stAssemblyByteArray->Data = pa_data;
+    insertAttribute(pstAssemblyInstance, 3, CIP_BYTE_ARRAY, stAssemblyByteArray);
+
+    return pstAssemblyInstance;
+  }
+
+EIP_STATUS notifyAssemblyConnectedDataReceived(S_CIP_Instance * pa_pstInstance,
+    EIP_UINT8 * pa_pnData, EIP_UINT16 pa_nDataLength)
+  {
+    S_CIP_Byte_Array *p;
+    
+    /* empty path (path size = 0) need to be checked and taken care of in future */
+    /* copy received data to Attribute 3 */
+    p = pa_pstInstance->pstAttributes->pt2data;
+    if (p->len < pa_nDataLength)
+      {
+        if (EIP_DEBUG>EIP_VERBOSE)
+          printf("too much data arrived\n");
+        return EIP_ERROR; //TODO question should we notify the application that wrong data has been recieved???
+      }
+    else
+      {
+        memcpy(p->Data, pa_pnData, pa_nDataLength);
+        /* call the application that new data arrived */
+      }
+    
+    return IApp_AfterAssemblyDataReceived(pa_pstInstance);
+  }

+ 39 - 0
src/cip/cipassembly.h

@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPASSEMBLY_H_
+#define CIPASSEMBLY_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+#define CIP_ASSEMBLY_CLASS_CODE 0x04
+
+/* public functions */
+
+/*! Setup the Assembly object
+ * 
+ * Creates the Assembly Class with zero instances and sets up all services.
+ */
+EIP_STATUS CIP_Assembly_Init(void);
+
+/*! notify an Assembly object that data has been recieved for it.
+ * 
+ *  The data will be copied into the assembly objects attribute 3 and
+ *  the application will be informed with the IApp_after_assembly_data_received function.
+ *  
+ *  @param pa_pstInstance the assembly object instance for which the data was recieved
+ *  @param pa_pnData pointer to the data received
+ *  @param pa_nDatalength number of bytes recieved
+ *  @return 
+ *     - EIP_OK the received data was ok 
+ *     - EIP_ERROR the received data was wrong
+ */ 
+EIP_STATUS notifyAssemblyConnectedDataReceived(S_CIP_Instance *pa_pstInstance,
+    EIP_UINT8 *pa_pnData, EIP_UINT16 pa_nDatalength);
+
+#endif /*CIPASSEMBLY_H_*/

+ 622 - 0
src/cip/cipcommon.c

@@ -0,0 +1,622 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "opener_user_conf.h"
+#include "opener_api.h"
+#include "cipcommon.h"
+#include "cipidentity.h"
+#include "ciptcpipinterface.h"
+#include "cipethernetlink.h"
+#include "cipconnectionmanager.h"
+#include "endianconv.h"
+#include "encap.h"
+#include "ciperror.h"
+#include "cipassembly.h"
+#include "cipmessagerouter.h"
+#include "cpf.h"
+
+/* global public variables */
+EIP_UINT8 g_acMessageDataReplyBuffer[OPENER_MESSAGE_DATA_REPLY_BUFFER];
+
+/* private functions*/
+/*! Produce the given attribute the message buffer.   
+ * 
+ *  @param pa_ptstAttribute pointer to CIP attribute structure.
+ *  @param pa_pnMsg pointer to memory where response should be written
+ *  @return length of attribute in bytes
+ *          -1 .. error
+ */
+int outputAttribute(S_CIP_attribute_struct *pa_ptstAttribute,
+    EIP_UINT8 *pa_pnMsg);
+
+void CIP_Init(void)
+  {
+    printf("CIP_Init: calling encapInit\n");
+    encapInit();
+    /* The message router is the first CIP object that has to be initialized first!!! */
+    assert(CIP_MessageRouter_Init() == EIP_OK);
+    assert(CIP_Identity_Init() == EIP_OK);
+    assert(CIP_TCPIP_Interface_Init() == EIP_OK);
+    assert(CIP_Ethernet_Link_Init() == EIP_OK);
+    assert(Connection_Manager_Init() == EIP_OK);
+    assert(CIP_Assembly_Init() == EIP_OK);
+    // the application has to be initiliazed at last
+    assert(IApp_Init() == EIP_OK);
+  }
+
+EIP_STATUS notifyClass(S_CIP_Class * pt2Class, // class receiving the message
+    S_CIP_MR_Request * pa_MRRequest, // request message
+    S_CIP_MR_Response * pa_MRResponse)
+  {
+    int i;
+    S_CIP_Instance *pstInstance;
+    S_CIP_service_struct *p;
+    unsigned instNr; // my instance number
+
+    // find the instance: if instNr==0, the class is addressed, else find the instance
+    instNr = pa_MRRequest->RequestPath.InstanceNr; // get the instance number
+    pstInstance = getCIPInstance(pt2Class, instNr); // look up the instance (note that if inst==0 this will be the class itself)
+    if (pstInstance) // if instance is found
+      {
+        if (EIP_DEBUG >= EIP_VVERBOSE)
+          {
+            printf("notify: found instance %d%s\n", instNr,
+                instNr==0 ? " (class object)" : "");
+          }
+        p = pstInstance->pstClass->pstServices; // get pointer to array of services
+        if (p) // if services are defined
+          {
+            for (i = 0; i < pstInstance->pstClass->nNr_of_Services; i++) // seach the services list
+              {
+                if (pa_MRRequest->Service == p->CIP_ServiceNr) // if match is found
+                  {
+                    pa_MRResponse->Data = &g_acMessageDataReplyBuffer[0]; // set reply buffer, using a fixed buffer (about 100 bytes)
+                    // call the service, and return what it returns
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                      printf("notify: calling %s service\n", p->name);
+                    assert(p->m_ptfuncService);
+                    return p->m_ptfuncService(pstInstance, pa_MRRequest,
+                        pa_MRResponse, &g_acMessageDataReplyBuffer[0]);
+                  }
+                else
+                  p++;
+              }
+          }
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notify: service 0x%x not supported\n", pa_MRRequest->Service);
+        pa_MRResponse->GeneralStatus = CIP_ERROR_SERVICE_NOT_SUPPORTED; // if no services or service not found, return an error reply
+      }
+    else
+      {
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notify: instance number %d unknown\n", instNr);
+        // if instance not found, return an error reply
+        pa_MRResponse->GeneralStatus = CIP_ERROR_PATH_DESTINATION_UNKNOWN; //according to the test tool this should be the correct error flag instead of CIP_ERROR_OBJECT_DOES_NOT_EXIST;
+      }
+
+    // handle error replies
+    pa_MRResponse->SizeofAdditionalStatus = 0; // fill in the rest of the reply with not much of anything
+    pa_MRResponse->DataLength = 0;
+    pa_MRResponse->ReplyService = (0x80 | pa_MRRequest->Service); // except the reply code is an echo of the command + the reply flag
+
+    return EIP_OK_SEND;
+  }
+
+S_CIP_Instance *addCIPInstances(S_CIP_Class * pa_pstCIPClass, // class being initialized
+    int pa_nNr_of_Instances) // number of instances to create
+  {
+    S_CIP_Instance *first, *p, **pp;
+    int i;
+    int inst=1; // the first instance is number 1
+
+    if (EIP_DEBUG>=EIP_VERBOSE)
+      printf("adding %d instances to class %s\n", pa_nNr_of_Instances,
+          pa_pstCIPClass->acName);
+
+    pp = &pa_pstCIPClass->pstInstances; // get address of pointer to head of chain
+    while (*pp) // as long as what pp points to is not zero
+      {
+        pp = &(*pp)->pstNext; //    follow the chain until pp points to pointer that contains a zero
+        inst++; //    keep track of what the first new instance number will be
+      }
+
+    first = p = IApp_CipCalloc(pa_nNr_of_Instances, sizeof(S_CIP_Instance)); // allocate a block of memory for all created instances
+    assert(p);
+    // fail if run out of memory
+
+    pa_pstCIPClass->nNr_of_Instances += pa_nNr_of_Instances; // add the number of instances just created to the total recorded by the class
+
+    for (i = 0; i < pa_nNr_of_Instances; i++) // initialize all the new instances
+      {
+        *pp = p; // link the previous pointer to this new node
+
+        p->nInstanceNr = inst; // assign the next sequential instance number
+        p->pstClass = pa_pstCIPClass; // point each instance to its class
+
+        if (pa_pstCIPClass->nNr_of_Attributes) // if the class calls for instance attributes
+          { // then allocate storage for the attribute array
+            p->pstAttributes = IApp_CipCalloc(
+                pa_pstCIPClass->nNr_of_Attributes,
+                sizeof(S_CIP_attribute_struct));
+          }
+
+        pp = &p->pstNext; // update pp to point to the next link of the current node
+        inst++; // update to the number of the next node
+        p++; // point to the next node in the calloc'ed array
+      }
+
+    return first;
+  }
+
+S_CIP_Instance *addCIPInstance(S_CIP_Class * pa_pstCIPClass, // class being initialized
+    EIP_UINT32 pa_nInstanceId) // instance id of the instance to create
+  {
+    S_CIP_Instance *pstInstance =
+        getCIPInstance(pa_pstCIPClass, pa_nInstanceId);
+
+    if (0 == pstInstance)
+      { //we have no instance with given id
+        pstInstance = addCIPInstances(pa_pstCIPClass, 1);
+        pstInstance->nInstanceNr = pa_nInstanceId;
+      }
+    return pstInstance;
+  }
+
+S_CIP_Class *createCIPClass(EIP_UINT32 pa_nClassID,
+    int pa_nNr_of_ClassAttributes, EIP_UINT32 pa_nClassGetAttrAllMask,
+    int pa_nNr_of_ClassServices, int pa_nNr_of_InstanceAttributes,
+    EIP_UINT32 pa_nInstGetAttrAllMask, int pa_nNr_of_InstanceServices,
+    int pa_nNr_of_Instances, char *pa_acName, EIP_UINT16 pa_nRevision)
+  {
+    S_CIP_Class *pt2Class; // pointer to the class struct
+    S_CIP_Class *pt2MetaClass; // pointer to the metaclass struct
+
+    printf("creating class '%s' with id: 0x%lx\n", pa_acName, pa_nClassID);
+
+    pt2Class = getCIPClass(pa_nClassID); // check if an class with the ClassID already exists
+    assert(pt2Class==0);
+    // should never try to redefine a class
+
+    // a metaClass is a class that holds the class attributes and services
+    // CIP can talk to an instance, therefore an instance has a pointer to its class
+    // CIP can talk to a class, therefore a class struct is a subclass of the instance struct,
+    // and contains a pointer to a metaclass
+    // CIP never explicitly addresses a metaclass
+
+    pt2Class = IApp_CipCalloc(1, sizeof(S_CIP_Class)); // create the class object
+    pt2MetaClass = IApp_CipCalloc(1, sizeof(S_CIP_Class)); // create the metaclass object
+
+    // initialize the class-specific fields of the Class struct
+    pt2Class->nClassID = pa_nClassID; // the class remembers the class ID
+    pt2Class->nRevision = pa_nRevision; // the class remembers the class ID
+    pt2Class->nNr_of_Instances = 0; // the number of instances initially zero (more created below)
+    pt2Class->pstInstances = 0; //
+    pt2Class->nNr_of_Attributes = pa_nNr_of_InstanceAttributes; // the class remembers the number of instances of that class
+    pt2Class->nGetAttrAllMask = pa_nInstGetAttrAllMask; // indicate which attributes are included in instance getAttributeAll
+    pt2Class->nNr_of_Services = pa_nNr_of_InstanceServices+2; // the class manages the behavior of the instances
+    pt2Class->pstServices = 0; //
+    pt2Class->acName = pa_acName;
+    // initialize the class-specific fields of the metaClass struct
+    pt2MetaClass->nClassID = 0xffffffff; // set metaclass ID (this should never be referenced)
+    pt2MetaClass->nNr_of_Instances = 1; // the class object is the only instance of the metaclass
+    pt2MetaClass->pstInstances = (S_CIP_Instance *)pt2Class; //
+    pt2MetaClass->nNr_of_Attributes = pa_nNr_of_ClassAttributes+5; // the metaclass remembers how many class attributes exist
+    pt2MetaClass->nGetAttrAllMask = pa_nClassGetAttrAllMask; // indicate which attributes are included in class getAttributeAll
+    pt2MetaClass->nNr_of_Services = pa_nNr_of_ClassServices+2; // the metaclass manages the behavior of the class itself
+    pt2Class->pstServices = 0; //
+    pt2MetaClass->acName = IApp_CipCalloc(1, strlen(pa_acName)+6); // fabricate the name "meta<classname>"
+    strcpy(pt2MetaClass->acName, "meta-");
+    strcat(pt2MetaClass->acName, pa_acName);
+
+    // initialize the instance-specific fields of the Class struct
+    pt2Class->nInstanceNr = 0; // the class object is instance zero of the class it describes (weird, but that's the spec)
+    pt2Class->pstAttributes = 0; // this will later point to the class attibutes
+    pt2Class->pstClass = pt2MetaClass; // the class's class is the metaclass (like SmallTalk)
+    pt2Class->pstNext = 0; // the next link will always be zero, sinc there is only one instance of any particular class object
+
+    pt2MetaClass->nInstanceNr = 0xffffffff; // the metaclass object does not really have a valid instance number
+    pt2MetaClass->pstAttributes = 0; // the metaclass has no attributes
+    pt2MetaClass->pstClass = 0; // the metaclass has no class
+    pt2MetaClass->pstNext = 0; // the next link will always be zero, since there is only one instance of any particular metaclass object
+
+
+    // further initialization of the class object
+
+    pt2Class->pstAttributes = IApp_CipCalloc(pa_nNr_of_ClassAttributes+5,
+        sizeof(S_CIP_attribute_struct));
+    // TODO -- check that we didn't run out of memory?
+
+    pt2MetaClass->pstServices = IApp_CipCalloc(pa_nNr_of_ClassServices+2,
+        sizeof(S_CIP_service_struct));
+
+    pt2Class->pstServices = IApp_CipCalloc(pa_nNr_of_InstanceServices+2,
+        sizeof(S_CIP_service_struct));
+
+    if (pa_nNr_of_Instances > 0)
+      {
+        addCIPInstances(pt2Class, pa_nNr_of_Instances); //TODO handle return value and clean up if necessary
+      }
+
+    if ((registerClass(pt2Class)) == EIP_ERROR)
+      { /* no memory to register class in Message Router */
+        return 0; //TODO handle return value and clean up if necessary
+      }
+
+    // create the standard class attributes
+    insertAttribute((S_CIP_Instance *)pt2Class, 1, CIP_UINT,
+        (void *)&pt2Class->nRevision); // revision
+    insertAttribute((S_CIP_Instance *)pt2Class, 2, CIP_UINT,
+        (void *)&pt2Class->nNr_of_Instances); // largest instance number
+    insertAttribute((S_CIP_Instance *)pt2Class, 3, CIP_UINT,
+        (void *)&pt2Class->nNr_of_Instances); // number of instances currently existing
+    insertAttribute((S_CIP_Instance *)pt2Class, 6, CIP_UINT,
+        (void *)&pt2MetaClass->nMaxAttribute); // max class attribute number
+    insertAttribute((S_CIP_Instance *)pt2Class, 7, CIP_UINT,
+        (void *)&pt2Class->nMaxAttribute); // max instance attribute number
+
+
+    // create the standard class services
+    insertService(pt2MetaClass, CIP_GET_ATTRIBUTE_ALL, &getAttributeAll,
+        "GetAttributeAll"); // bind instance services to the metaclass
+    insertService(pt2MetaClass, CIP_GET_ATTRIBUTE_SINGLE, &getAttributeSingle,
+        "GetAttributeSingle");
+
+    // create the standard instance services
+    insertService(pt2Class, CIP_GET_ATTRIBUTE_ALL, &getAttributeAll,
+        "GetAttributeAll"); // bind instance services to the class
+    insertService(pt2Class, CIP_GET_ATTRIBUTE_SINGLE, &getAttributeSingle,
+        "GetAttributeSingle");
+
+    return pt2Class;
+  }
+
+void insertAttribute(S_CIP_Instance * pa_pInstance, // pointer to instance
+    EIP_UINT8 pa_nAttributeNr, // attribute number
+    EIP_UINT8 pa_nCIP_Type, // attribute type
+    void *pa_pt2data) // attribute data
+  {
+    int i;
+    S_CIP_attribute_struct *p;
+
+    p = pa_pInstance->pstAttributes;
+    assert(p!=0);
+    // adding a attribute to a class that was not declared to have any attributes is not allowed
+    for (i = 0; i < pa_pInstance->pstClass->nNr_of_Attributes; i++)
+      {
+        if (p->pt2data == 0)
+          { /* found non set attribute */
+            p->CIP_AttributNr = pa_nAttributeNr;
+            p->CIP_Type = pa_nCIP_Type;
+            p->pt2data = pa_pt2data;
+
+            if (pa_nAttributeNr > pa_pInstance->pstClass->nMaxAttribute) // remember the max attribute number that was defined
+              {
+                pa_pInstance->pstClass->nMaxAttribute = pa_nAttributeNr;
+              }
+            return;
+          }
+        p++;
+      }
+    assert(0);
+    // trying to insert too mmany attributes
+  }
+
+void insertService(S_CIP_Class * pa_pClass, // pointer to the class
+    EIP_UINT8 pa_nServiceNr, // the service number
+    TCIPServiceFunc pa_ptfuncService, // pointer to a service function
+    char *name) // service name
+  {
+    int i;
+    S_CIP_service_struct *p;
+
+    p = pa_pClass->pstServices; // get a pointer to the service array
+    assert(p!=0);
+    // adding a service to a class that was not declared to have services is not allowed
+    for (i = 0; i < pa_pClass->nNr_of_Services; i++) // interate over all service slots attached to the class
+      {
+        if (p->CIP_ServiceNr == pa_nServiceNr || p->m_ptfuncService == 0) // found undefined service slot
+          {
+            p->CIP_ServiceNr = pa_nServiceNr; // fill in service number
+            p->m_ptfuncService = pa_ptfuncService; // fill in function address
+            p->name = name;
+            return;
+          }
+        p++;
+      }
+    assert(0);
+    // adding more services than were declared is a no-no
+  }
+
+S_CIP_attribute_struct *getAttribute(S_CIP_Instance * pa_pInstance,
+    EIP_UINT8 pa_nAttributeNr)
+  {
+    int i;
+    S_CIP_attribute_struct *p = pa_pInstance->pstAttributes; // init pointer to array of attributes
+    for (i = 0; i < pa_pInstance->pstClass->nNr_of_Attributes; i++)
+      {
+        if (pa_nAttributeNr == p->CIP_AttributNr)
+          return p;
+        else
+          p++;
+      }
+    if (EIP_DEBUG>=EIP_TERSE)
+      printf("attribute %d not defined\n", pa_nAttributeNr);
+    return 0;
+  }
+
+// TODO this needs to check for buffer overflow
+EIP_STATUS getAttributeSingle(S_CIP_Instance * pa_pInstance, // pointer to instance
+    S_CIP_MR_Request * pa_stMRRequest, // request message
+    S_CIP_MR_Response * pa_stMRResponse, // response message
+    EIP_UINT8 * pa_msg) // reply buffer
+  {
+    S_CIP_attribute_struct *p = getAttribute(pa_pInstance,
+        pa_stMRRequest->RequestPath.AttributNr);
+
+    if ((p != 0) && (p->pt2data != 0))
+      {
+        if (EIP_DEBUG>=EIP_VERBOSE)
+          printf("getAttribute %ld\n", pa_stMRRequest->RequestPath.AttributNr); // create a reply message containing the data
+        pa_stMRResponse->DataLength = outputAttribute(p, pa_msg);
+        pa_stMRResponse->ReplyService = (0x80 | pa_stMRRequest->Service);
+        pa_stMRResponse->GeneralStatus = CIP_ERROR_SUCCESS;
+        pa_stMRResponse->SizeofAdditionalStatus = 0;
+        return (EIP_STATUS)pa_stMRResponse->DataLength;
+      }
+
+    pa_stMRResponse->DataLength = 0;
+    pa_stMRResponse->ReplyService = (0x80 | pa_stMRRequest->Service);
+    pa_stMRResponse->GeneralStatus = CIP_ERROR_ATTRIBUTE_NOT_SUPPORTED;
+    pa_stMRResponse->SizeofAdditionalStatus = 0;
+    return EIP_OK;
+  }
+
+int outputAttribute(S_CIP_attribute_struct *pa_ptstAttribute,
+    EIP_UINT8 *pa_pnMsg)
+  {
+    int j;
+    int counter=0;
+
+    switch (pa_ptstAttribute->CIP_Type)
+      /* check the datatype of attribute */
+      {
+    case (CIP_BOOL):
+    case (CIP_SINT):
+    case (CIP_USINT):
+    case (CIP_BYTE):
+      *pa_pnMsg++ = *(EIP_UINT8 *) (pa_ptstAttribute->pt2data);
+      counter = 1;
+      break;
+
+    case (CIP_INT):
+    case (CIP_UINT):
+    case (CIP_WORD):
+      htols(*(EIP_UINT16 *) (pa_ptstAttribute->pt2data), &pa_pnMsg);
+      counter = 2;
+      break;
+
+    case (CIP_DINT):
+    case (CIP_UDINT):
+    case (CIP_DWORD):
+      htoll(*(EIP_UINT32 *) (pa_ptstAttribute->pt2data), &pa_pnMsg);
+      counter = 4;
+      break;
+
+    case (CIP_LINT):
+    case (CIP_ULINT):
+    case (CIP_LWORD):
+      break;
+
+    case (CIP_REAL):
+    case (CIP_LREAL):
+    case (CIP_STIME):
+    case (CIP_DATE):
+    case (CIP_TIME_OF_DAY):
+    case (CIP_DATE_AND_TIME):
+    case (CIP_STRING):
+      {
+        S_CIP_String *s = pa_ptstAttribute->pt2data;
+
+        htols(*(EIP_UINT16 *) &(s->Length), &pa_pnMsg);
+        for (j = 0; j < s->Length; j++)
+          {
+            *pa_pnMsg++ = s->String[j];
+          }
+        counter = s->Length + 2; /* we have a two byte length field */
+        if (counter & 0x01)
+          {
+            /* we have an odd byte count */
+            *pa_pnMsg++ = 0;
+            counter++;
+          }
+        break;
+      }
+    case (CIP_STRING2):
+    case (CIP_FTIME):
+    case (CIP_LTIME):
+    case (CIP_ITIME):
+    case (CIP_STRINGN):
+      break;
+
+    case (CIP_SHORT_STRING):
+      {
+        S_CIP_Short_String *ss = pa_ptstAttribute->pt2data;
+
+        *pa_pnMsg++ = ss->Length;
+        for (j = 0; j < ss->Length; j++)
+          {
+            *pa_pnMsg++ = ss->String[j];
+          }
+        counter = ss->Length + 1;
+        break;
+      }
+
+    case (CIP_TIME):
+      break;
+
+    case (CIP_EPATH):
+      {
+        EIP_UINT16 *p = pa_ptstAttribute->pt2data;
+        EIP_UINT16 len;
+        EIP_UINT16 data;
+
+        len = *p++;
+        htols(len, &pa_pnMsg);
+        counter = 2;
+        while (len--)
+          {
+            data = *p++;
+            htols(data, &pa_pnMsg);
+            counter += 2;
+          }
+      }
+      break;
+
+    case (CIP_ENGUNIT):
+      break;
+
+    case (CIP_USINT_USINT):
+      {
+        S_CIP_Revision *rv = pa_ptstAttribute->pt2data;
+
+        *pa_pnMsg++ = rv->MajorRevision;
+        *pa_pnMsg++ = rv->MinorRevision;
+        counter = 2;
+        break;
+      }
+
+    case (CIP_UDINT_UDINT_UDINT_UDINT_UDINT_STRING):
+      {
+        EIP_UINT32 *p = pa_ptstAttribute->pt2data;
+        S_CIP_String *s;
+
+        htoll(p[0], &pa_pnMsg);
+        htoll(p[1], &pa_pnMsg);
+        htoll(p[2], &pa_pnMsg);
+        htoll(p[3], &pa_pnMsg);
+        htoll(p[4], &pa_pnMsg);
+        counter = 20;
+        /* handle the string */
+        //TODO Think on how to use the string encoding mechanism
+        s = (S_CIP_String *)&p[5];
+        htols(s->Length, &pa_pnMsg); // length of string
+        counter++;
+        for (j = 0; j < s->Length; j++)
+          {
+            *pa_pnMsg++ = s->String[j];
+            counter++;
+          }
+
+        if (counter&1)
+          { /* odd bytes in string -> insert pad byte */
+            *pa_pnMsg++ = 0;
+            counter++;
+          }
+        break;
+      }
+
+    case (CIP_6USINT):
+      {
+        EIP_UINT8 *p = pa_ptstAttribute->pt2data;
+
+        for (j = 0; j < 6; j++)
+          {
+            *pa_pnMsg++ = p[j];
+          }
+        counter = 6;
+        break;
+      }
+
+    case (CIP_MEMBER_LIST):
+      break;
+
+    case (CIP_BYTE_ARRAY):
+      break;
+
+    case (INTERNAL_UINT16_6): // TODO for port class attribute 9, hopefully we can find a better way to do this
+      {
+        EIP_UINT16 *p = pa_ptstAttribute->pt2data;
+
+        htols(p[0], &pa_pnMsg);
+        htols(p[1], &pa_pnMsg);
+        htols(p[2], &pa_pnMsg);
+        htols(p[3], &pa_pnMsg);
+        htols(p[4], &pa_pnMsg);
+        htols(p[5], &pa_pnMsg);
+        counter = 12;
+        break;
+      }
+
+      }
+
+    return counter;
+  }
+
+EIP_STATUS getAttributeAll(S_CIP_Instance * pa_pstInstance, // instance that is getting this message
+    S_CIP_MR_Request * pa_stMRRequest, // pointer to the request
+    S_CIP_MR_Response * pa_stMRResponse, // pointer to the response
+    EIP_UINT8 * pa_msg) // pointer to the reply buffer
+  {
+    int i, j;
+    EIP_UINT8 *ptmp;
+    S_CIP_attribute_struct *p_attr;
+    S_CIP_service_struct *p_service;
+
+    ptmp = pa_msg; // pointer into the reply
+    p_attr = pa_pstInstance->pstAttributes; // pointer to list of attributes
+    p_service = pa_pstInstance->pstClass->pstServices; // pointer to list of services
+
+    if (pa_pstInstance->nInstanceNr==2)
+      {
+        if (EIP_DEBUG>=EIP_VERBOSE)
+          printf("GetAttributeAll: instance number 2\n");
+      }
+
+    for (i = 0; i < pa_pstInstance->pstClass->nNr_of_Services; i++) // hunt for the GET_ATTRIBUTE_SINGLE service
+      {
+        if (p_service->CIP_ServiceNr == CIP_GET_ATTRIBUTE_SINGLE) // found the service
+          {
+            if (0 == pa_pstInstance->pstClass->nNr_of_Attributes)
+              {
+                pa_stMRResponse->DataLength = 0; //there are no attributes to be sent back
+                pa_stMRResponse->ReplyService
+                    = (0x80 | pa_stMRRequest->Service);
+                pa_stMRResponse->GeneralStatus
+                    = CIP_ERROR_SERVICE_NOT_SUPPORTED;
+                pa_stMRResponse->SizeofAdditionalStatus = 0;
+              }
+            else
+              {
+                for (j = 0; j < pa_pstInstance->pstClass->nNr_of_Attributes; j++) // for each instance attribute of this class
+                  {
+                    int attrNum = p_attr->CIP_AttributNr;
+                    if (attrNum<32
+                        && (pa_pstInstance->pstClass->nGetAttrAllMask & 1
+                            <<attrNum)) // only return attributes that are flagged as being part of GetAttributeALl
+                      {
+                        pa_stMRRequest->RequestPath.AttributNr = attrNum;
+                        pa_msg += p_service->m_ptfuncService(pa_pstInstance,
+                            pa_stMRRequest, pa_stMRResponse, pa_msg);
+                        if (pa_stMRResponse->GeneralStatus != CIP_ERROR_SUCCESS)
+                          {
+                            return EIP_ERROR;
+                          }
+                      }
+                    p_attr++;
+                  }
+                pa_stMRResponse->DataLength = pa_msg - ptmp;
+              }
+            return (EIP_STATUS)pa_stMRResponse->DataLength;
+          }
+        p_service++;
+      }
+    return EIP_OK; // reurn 0 if cannot find GET_ATTRIBUTE_SINGLE service
+  }

+ 59 - 0
src/cip/cipcommon.h

@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPCOMMON_H_
+#define CIPCOMMON_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+/*! A buffer for holding the replay generated by explicit message requests 
+ *  or producing I/O connections. These will use this buffer in the following
+ *  ways:
+ *    1. Explicit messages will use this buffer to store the data generated by the request
+ *    2. I/O Connections will use this buffer for the produced data
+ */
+extern EIP_UINT8 g_acMessageDataReplyBuffer[];
+
+/** \brief  Check if requested service present in class/instance and call appropriate service.
+ *
+ * @param pt2Class class receiving the message
+ * @param pa_MRRequest request message
+ * @param pa_MRResponse reply message
+ * @return
+ *     - EIP_OK_SEND    ... success
+ *     - EIP_OK  ... no reply to send back
+ *     - EIP_ERROR ... error
+ */
+EIP_STATUS notifyClass(S_CIP_Class * pt2Class, S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse);
+
+/*   EIP_STATUS get_Attribut_Single(S_CIP_Instance *pa_pInstance, S_CIP_MR_Request *pa_stMRRequest, S_CIP_MR_Response *pa_stMRResponse, S_CIP_CPF_Data *pa_stCPFdata, INT8 *pa_msg)
+ *   check from classID which Object requests an attribute, search if object has the appropriate attribute implemented.
+ *      pa_stInstance pointer to instance.
+ *      pa_stMRRequest pointer to request.
+ *      pa_MRResponse pointer to response.
+ *      pa_msg pointer to memory where response should be written
+ *  return status  >0 .. success
+ *          -1 .. requested attribute not available
+ */
+EIP_STATUS getAttributeSingle(S_CIP_Instance *pa_pInstance,
+    S_CIP_MR_Request *pa_stMRRequest, S_CIP_MR_Response *pa_stMRResponse,
+    EIP_UINT8 *pa_msg);
+
+/*   EIP_STATUS getAttributeAll(S_CIP_Instance *pa_pstObjectInstance, S_CIP_MR_Request *pa_stMRRequest, S_CIP_MR_Response *pa_stMRResponse, S_CIP_CPF_Data *pa_stCPFdata, INT8 *pa_msg)
+ *   copy all attributs from Object into the global msg buffer.
+ *      pa_pstObjectInstance pointer to object instance with data.
+ *      pa_stMRRequest pointer to MR request.
+ *      pa_stMRResponse pointer for MR response.
+ *      pa_msg pointer to global message buffer for response.
+ *  return length of datastrem >0 .. succes, 0 .. no reply to send
+ */
+EIP_STATUS getAttributeAll(S_CIP_Instance *pa_pstObjectInstance,
+    S_CIP_MR_Request *pa_stMRRequest, S_CIP_MR_Response *pa_stMRResponse,
+    EIP_UINT8 *pa_msg);
+
+#endif /*CIPCOMMON_H_*/

+ 1674 - 0
src/cip/cipconnectionmanager.c

@@ -0,0 +1,1674 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "opener_user_conf.h"
+#include "cipconnectionmanager.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "ciperror.h"
+#include "endianconv.h"
+#include "opener_api.h"
+#include "cpf.h"
+#include "cipassembly.h"
+#include "encap.h"
+#include "cipidentity.h"
+
+//values needed from cipidentiy
+extern EIP_UINT16 VendorID;
+extern EIP_UINT16 DeviceType;
+extern EIP_UINT16 ProductCode;
+extern S_CIP_Revision Revison;
+
+//values need from tcpipinterface
+extern EIP_UINT32 g_nMultiCastAddress;
+
+/* Connection Manager Error codes */
+#define CIP_CON_MGR_SUCCESS 0x00
+#define CIP_CON_MGR_ERROR_CONNECTION_IN_USE 0x0100
+#define CIP_CON_MGR_ERROR_TRANSPORT_TRIGGER_NOT_SUPPORTED 0x0103
+#define CIP_CON_MGR_ERROR_CONNECTION_NOT_FOUND_AT_TARGET_APPLICATION 0x0107
+#define CIP_CON_MGR_ERROR_INVALID_CONNECTION_TYPE 0x0108
+#define CIP_CON_MGR_ERROR_INVALID_CONNECTION_SIZE 0x0109  
+#define CIP_CON_MGR_ERROR_NO_MORE_CONNECTIONS_AVAILABLE 0x0113
+#define CIP_CON_MGR_ERROR_VENDERID_OR_PRODUCTCODE_ERROR 0x0114
+#define CIP_CON_MGR_ERROR_VENDERID_OR_PRODUCT_TYPE_ERROR 0x0115
+#define CIP_CON_MGR_ERROR_REVISION_MISMATCH 0x0116
+#define CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT 0x0117
+#define CIP_CON_MGR_ERROR_INVALID_CONFIGURATION_FORMAT 0x0118
+#define CIP_CON_MGR_ERROR_PARAMETER_ERROR_IN_UNCONNECTED_SEND_SERVICE 0x0205
+#define CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH 0x0315
+
+#define FORWARD_OPEN_HEADER_LENGTH 36                                   //the lenght in bytes of the forward open command specific data till the start of the connection path (including con path size)
+#define EQLOGICALPATH(x,y) (((x)&0xfc)==(y))
+
+/*macros for comparing sequence numbers according to CIP spec vol 2 3-4.2*/
+#define SEQ_LEQ32(a, b) ((int)((a) - (b)) <= 0)
+#define SEQ_GEQ32(a, b) ((int)((a) - (b)) >= 0)
+
+/* similar macros for comparing 16 bit sequence numbers */
+#define SEQ_LEQ16(a, b) ((short)((a) - (b)) <= 0)
+#define SEQ_GEQ16(a, b) ((short)((a) - (b)) >= 0) 
+
+
+EIP_UINT32 g_nRunIdleState;
+//!< buffer for holding the run idle infromation.
+
+inline int GETPADDEDLOGICALPATH(unsigned char **x)
+  {
+    int tmp;
+
+    tmp = *(*x)++;
+    if ((tmp&3)==0)
+      {
+        tmp = *(*x)++;
+      }
+    else if ((tmp&3)==1)
+      {
+        (*x)++; // skip pad
+        tmp = *(*x)++;
+        tmp |= *(*x)++ <<8;
+      }
+    else
+      {
+        printf("illegal logical path segment\n");
+      }
+    return tmp;
+  }
+
+#define CIP_CONN_PATH_INVALID 1
+#define CIP_CONN_PATH_CONFIGURATION 2
+#define CIP_CONN_PATH_CONSUMPTION 3
+#define CIP_CONN_PATH_CONFIGURATION_AND_CONSUMPTION 4
+#define CIP_CONN_PATH_PRODUCTION 5
+#define CIP_CONN_PATH_CONFIGURATION_AND_PRODUCTION 6
+#define CIP_CONN_PATH_CONSUMTION_AND_PRODUCTION 7
+#define CIP_CONN_PATH_CONFIGURATION_AND_CONSUMPTION_AND_PRODUCTION 8
+
+#define CIP_CONN_TYPE_MASK 0x6000   //Bit 13&14 true
+/* global variables private */
+
+S_CIP_ConnectionObject g_stDummyConnectionObject; //!< buffer connection object needed for forward open
+S_CIP_ConnectionObject stConnectionObject[OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS];
+
+
+/* global variables*/
+extern S_CIP_ConnectionObject stConnectionObject[];
+
+/* private functions */
+EIP_STATUS ForwardOpen(S_CIP_Instance * pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg);
+EIP_STATUS ForwardClose(S_CIP_Instance * pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg);
+EIP_STATUS UnconnectedSend(S_CIP_Instance * pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg); /* for originating and routing devices */
+EIP_STATUS GetConnectionOwner(S_CIP_Instance * pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg);
+
+EIP_STATUS assembleFWDOpenResponse(S_CIP_ConnectionObject *pa_pstConnObj,
+    S_CIP_MR_Response * pa_MRResponse, EIP_UINT8 pa_nGeneralStatus,
+    EIP_UINT16 pa_nExtendedStatus, EIP_UINT8 * pa_msg);
+
+EIP_STATUS assembleFWDCloseResponse(EIP_UINT16 pa_ConnectionSerialNr,
+    EIP_UINT16 pa_OriginatorVendorID, EIP_UINT32 pa_OriginatorSerialNr,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT16 pa_nExtErrorCode, EIP_UINT8 * pa_msg);
+
+int getFreeConnectionObject(void);
+
+void
+    ConnectionObjectGeneralConfiguration(S_CIP_ConnectionObject *pa_pstConnObj);
+
+EIP_STATUS OpenMulticastConnection(int pa_direction,
+    S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_CPF_Data *pa_CPF_data);
+
+EIP_STATUS OpenConsumingPointToPointConnection(
+    S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_CPF_Data *pa_CPF_data);
+
+EIP_STATUS OpenProducingPointToPointConnection(
+    S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_CPF_Data *pa_CPF_data,
+    EIP_UINT16 *pa_pnExtendedError);
+
+/** \brief check if the data given in the connection object match with an already established connection
+ * 
+ * The comparision is done according to the definitions in the CIP spec Section 3-5.5.2:
+ * The following elements have to be equal: Vendor ID, Connection Serial Number, Originator Serial Number
+ * @param pa_pstConnObj connection object containing the comparision elements from the forward open request
+ * @return 
+ *    - -1 if no equal established connection exists
+ *    - the index number of the queal connection object       
+ */
+int checkForExistingConnection(S_CIP_ConnectionObject *pa_pstConnObj);
+
+/** \brief Compare the electronic key recieved with a forward open request with the device's data.
+ * 
+ * @param pa_nKeyFormat format identifier given in the forward open request
+ * @param pa_pstKeyData pointer to the electronic key data recieved in the forward open request
+ * @param pa_pnExtStatus the extended error code in case an error happend
+ * @return general status on the establishment
+ *    - EIP_OK ... on success
+ *    - On an error the general status code to be put into the response
+ */
+EIP_STATUS checkElectronicKeyData(EIP_UINT8 pa_nKeyFormat,
+    S_CIP_KeyData *pa_pstKeyData, EIP_UINT16 *pa_pnExtStatus);
+
+/** \brief Parse the connection path of a forward open request
+ * 
+ * This function will take the connection object and the recieved data stream and parse the connection path.
+ * @param pa_pstConnObj pointer to the connection object structure for which the connection should 
+ *                      be established
+ * @param pa_MRRequest pointer to the received request structre. The position of the data stream pointer has to be at the connection length entry
+ * @param pa_pnExtendedError the extended error code in case an error happend
+ * @return general status on the establishment
+ *    - EIP_OK ... on success
+ *    - On an error the general status code to be put into the response
+ */
+EIP_UINT8 parseConnectionPath(S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_MR_Request *pa_MRRequest, EIP_UINT16 *pa_pnExtendedError);
+
+/** \brief Setup all data in order to establish an IO connection
+ * 
+ * This function can be called after all data has been parsed from the foraward open request
+ * @param pa_pstConnObj pointer to the connection object structure for which the connection should be established
+ * @param pa_pnExtendedError the extended error code in case an error happend
+ * @return general status on the establishment
+ *    - EIP_OK ... on success
+ *    - On an error the general status code to be put into the response
+ */
+EIP_UINT8 establishIOConnction(S_CIP_ConnectionObject *pa_pstConnObj, EIP_UINT16 *pa_pnExtendedError);
+
+/** \brief Close the given connection
+ *
+ * This function will take the data form the connection and correctly closes the connection (e.g., open sockets)
+ * @param pa_pstConnObj pointer to the connection object structure to be closed 
+ */
+void closeConnection(S_CIP_ConnectionObject *pa_pstConnObj);
+
+/*!  Send the data from the produced CIP Object of the connection via the socket of the connection object
+ *   on UDP.
+ *      @param pa_pstConnection  pointer to the connection object
+ *      @return status  EIP_OK .. success
+ *                     EIP_ERROR .. error
+ */
+EIP_STATUS sendConnectedData(S_CIP_ConnectionObject *pa_pstConnection);
+
+
+EIP_STATUS Connection_Manager_Init()
+  {
+    S_CIP_Class *pstConnectionManager;
+
+    pstConnectionManager = createCIPClass( CIP_CONNECTION_MANAGER_CLASS_CODE, // class ID
+        0, // # of class attributes
+        0xffffffff, // class getAttributeAll mask
+        0, // # of class services
+        0, // # of instance attributes
+        0xffffffff, // instance getAttributeAll mask
+        4, // # of instance services
+        1, // # of instances
+        "connection manager", // class name
+        1); // revision
+    if (pstConnectionManager==0)
+      return EIP_ERROR;
+
+    insertService(pstConnectionManager, CIP_FORWARD_OPEN, &ForwardOpen,
+        "ForwardOpen");
+    insertService(pstConnectionManager, CIP_FORWARD_CLOSE, &ForwardClose,
+        "ForwardClose");
+    insertService(pstConnectionManager, CIP_GET_CONNECTION_OWNER,
+        &GetConnectionOwner, "GetConnectionOwner");
+    insertService(pstConnectionManager, CIP_UNCONNECTED_SEND, &UnconnectedSend,
+        "UnconnectedSend");
+
+    //    g_stCMObjectNode = allocateCMObjectNode(0);
+
+    return EIP_OK;
+  }
+
+EIP_STATUS handleReceivedConnectedData(EIP_UINT8 * pa_pnData, int pa_nDataLength)
+  {
+    S_CIP_ConnectionObject *pstConnectionObject;
+
+    if ((createCPFstructure(pa_pnData, pa_nDataLength, &g_stCPFDataItem)) == -1)
+      { /* error from createCPFstructure */
+        return EIP_ERROR;
+      }
+    else
+      {
+        /* check if connected address item or sequenced address item  received, otherwise it is no connected message and should not be here */
+        if ((g_stCPFDataItem.stAddr_Item.TypeID == CIP_ITEM_ID_CONNECTIONBASED)
+            || (g_stCPFDataItem.stAddr_Item.TypeID
+                == CIP_ITEM_ID_SEQUENCEDADDRESS))
+          { /* found connected address item or found sequenced address item -> for now the sequence number will be ignored */
+            if (g_stCPFDataItem.stDataI_Item.TypeID
+                == CIP_ITEM_ID_CONNECTIONTRANSPORTPACKET)
+              { /* connected data item received */
+                pstConnectionObject
+                    = getConnectedObject(g_stCPFDataItem.stAddr_Item.Data.ConnectionIdentifier);
+                if (pstConnectionObject == 0)
+                  return EIP_ERROR;
+
+                if (SEQ_GEQ32(g_stCPFDataItem.stAddr_Item.Data.SequenceNumber, pstConnectionObject->EIPSequenceCountConsuming))
+                  {
+                    /* reset the watchdogtimer */
+                    pstConnectionObject->InnacitvityWatchdogTimer = (pstConnectionObject->O_to_T_RPI / 1000) << 2;
+                    
+                    /* only inform assembly object if the sequence counter is greater or equal */
+                    pstConnectionObject->EIPSequenceCountConsuming
+                        = g_stCPFDataItem.stAddr_Item.Data.SequenceNumber;
+                    /* check class 1 sequence number*/
+                    if ((pstConnectionObject->TransportTypeTrigger & 0x0F) == 1)
+                      {
+                        EIP_UINT16 nSequenceBuf = ltohs(&(g_stCPFDataItem.stDataI_Item.Data));
+                        if (SEQ_LEQ16(nSequenceBuf, pstConnectionObject->SequenceCountConsuming))
+                          {
+                            return EIP_OK; /* no new data for the assembly */
+                          }
+                        pstConnectionObject->SequenceCountConsuming
+                            = nSequenceBuf;
+                        g_stCPFDataItem.stDataI_Item.Length -= 2;
+                      }
+
+                    if (OPENER_CONSUMED_DATA_HAS_RUN_IDLE_HEADER)
+                      {
+                        EIP_UINT32 nRunIdleBuf = ltohl(&(g_stCPFDataItem.stDataI_Item.Data));
+                        if (g_nRunIdleState != nRunIdleBuf)
+                          {
+                            IApp_RunIdleChanged(nRunIdleBuf);
+                          }
+                        g_nRunIdleState = nRunIdleBuf;
+                        g_stCPFDataItem.stDataI_Item.Length -= 4;
+                      }
+
+                    if (notifyAssemblyConnectedDataReceived(
+                        pstConnectionObject->p_stConsumingInstance,
+                        g_stCPFDataItem.stDataI_Item.Data,
+                        g_stCPFDataItem.stDataI_Item.Length) != 0)
+                      return EIP_ERROR;
+                  }
+
+              }
+          }
+      }
+    return EIP_OK;
+  }
+
+/*   EIP_STATUS ForwardOpen(S_CIP_Instance *pa_pstInstance, S_CIP_MR_Request *pa_MRRequest, S_CIP_MR_Response *pa_MRResponse, S_CIP_CPF_Data *pa_CPF_data, INT8 *pa_msg)
+ *   check if resources for new connection available, generate ForwardOpen Reply message.
+ *      pa_pstInstance	pointer to CIP object instance
+ *      pa_MRRequest		pointer to Message Router Request.
+ *      pa_MRResponse		pointer to Message Router Response.
+ *      pa_CPF_data		received CPF Data Item
+ *      pa_msg			pointer to memory where reply will be stored
+ *  return length of reply
+ * 		>0 .. success, 0 .. no reply to send back
+ *      	-1 .. error
+ */
+EIP_STATUS ForwardOpen(S_CIP_Instance *pa_pstInstance,
+    S_CIP_MR_Request *pa_MRRequest, S_CIP_MR_Response *pa_MRResponse,
+    EIP_UINT8 *pa_msg)
+  {
+    EIP_UINT16 nConnectionStatus = CIP_CON_MGR_SUCCESS;
+    int index, i;
+    EIP_UINT32 tmp;
+    EIP_UINT8 *pConnectionmsg;
+
+    (void)pa_pstInstance; /*suppress compiler warning */
+
+    /*first check if we have already a connection with the given params */
+    g_stDummyConnectionObject.Priority_Timetick = *pa_MRRequest->Data++;
+    g_stDummyConnectionObject.Timeoutticks = *pa_MRRequest->Data++;
+    // O_to_T Conn ID
+    g_stDummyConnectionObject.CIPConsumedConnectionID
+        = ltohl(&pa_MRRequest->Data);
+    // T_to_O Conn ID
+    g_stDummyConnectionObject.CIPProducedConnectionID
+        = ltohl(&pa_MRRequest->Data);
+    g_stDummyConnectionObject.ConnectionSerialNumber
+        = ltohs(&pa_MRRequest->Data);
+    g_stDummyConnectionObject.OriginatorVendorID = ltohs(&pa_MRRequest->Data);
+    g_stDummyConnectionObject.OriginatorSerialNumber
+        = ltohl(&pa_MRRequest->Data);
+
+    if ((-1 != checkForExistingConnection(&g_stDummyConnectionObject)))
+      {
+        // TODO this test is  incorrect, see CIP spec 3-5.5.2 re: duplicate forward open
+        // it should probably be testing the connection type fields
+        //TODO think on how a reconfigurationrequest could be handled correctly
+        if ((0 == g_stDummyConnectionObject.CIPConsumedConnectionID) && (0
+            == g_stDummyConnectionObject.CIPProducedConnectionID))
+          {
+            /*TODO implement reconfiguration of connection*/
+
+            if (EIP_DEBUG >= EIP_TERSE)
+              printf("this looks like a duplicate forward open -- I can't handle this yet, sending a CIP_CON_MGR_ERROR_CONNECTION_IN_USE response\n");
+          }
+        return assembleFWDOpenResponse(&g_stDummyConnectionObject,
+            pa_MRResponse, CIP_ERROR_CONNECTION_FAILURE, 
+            CIP_CON_MGR_ERROR_CONNECTION_IN_USE, pa_msg);
+      }
+    /* check if another connectionobject is available */
+    if ((index = getFreeConnectionObject()) == EIP_ERROR)
+      { /* no free connection Object available -> no resources */
+        printf("connection manager: no free connection object available\n");
+        pConnectionmsg = pa_MRResponse->Data;
+        pa_MRRequest->Data += 10; /* set datapointer to ConnectionSerialNumber in FWDOpen Request Data */
+        for (i = 0; i < 8; i++)
+          {
+            *pConnectionmsg = *pa_MRRequest->Data;
+            pa_MRRequest->Data++;
+            pConnectionmsg++;
+          }
+        *pConnectionmsg = 0;
+        pConnectionmsg++;
+        *pConnectionmsg = 0;
+        pConnectionmsg++;
+
+        g_stDummyConnectionObject.ConnectionSerialNumber = 0;
+        g_stDummyConnectionObject.OriginatorVendorID = 0;
+        g_stDummyConnectionObject.OriginatorSerialNumber = 0;
+
+        return assembleFWDOpenResponse(&g_stDummyConnectionObject,
+            pa_MRResponse, CIP_ERROR_CONNECTION_FAILURE, 
+            CIP_CON_MGR_ERROR_NO_MORE_CONNECTIONS_AVAILABLE, pa_msg);
+      }
+    /* found no error so far, create connection object */
+    /* set state to configuring */
+    stConnectionObject[index].State = CONN_STATE_CONFIGURING;
+    stConnectionObject[index].SequenceCountProducing = 0; /* set the sequence count to zero */
+
+    stConnectionObject[index].Priority_Timetick
+        = g_stDummyConnectionObject.Priority_Timetick;
+    stConnectionObject[index].Timeoutticks
+        = g_stDummyConnectionObject.Timeoutticks;
+    stConnectionObject[index].CIPConsumedConnectionID
+        = g_stDummyConnectionObject.CIPConsumedConnectionID;
+    stConnectionObject[index].CIPProducedConnectionID
+        = g_stDummyConnectionObject.CIPProducedConnectionID;
+    stConnectionObject[index].ConnectionSerialNumber
+        = g_stDummyConnectionObject.ConnectionSerialNumber;
+    stConnectionObject[index].OriginatorVendorID
+        = g_stDummyConnectionObject.OriginatorVendorID;
+    stConnectionObject[index].OriginatorSerialNumber
+        = g_stDummyConnectionObject.OriginatorSerialNumber;
+    stConnectionObject[index].ConnectionTimeoutMultiplier
+        = *pa_MRRequest->Data++;
+    pa_MRRequest->Data += 3; /* reserved */
+    /* the requested packet interval parameter needs to be a multiple of TIMERTICK from the header file */
+    printf("ForwardOpen: ConConnID %lu, ProdConnID %lu, ConnSerNo %u\n",
+        stConnectionObject[index].CIPConsumedConnectionID,
+        stConnectionObject[index].CIPProducedConnectionID,
+        stConnectionObject[index].ConnectionSerialNumber);
+
+    stConnectionObject[index].O_to_T_RPI = ltohl(&pa_MRRequest->Data);
+
+    tmp = stConnectionObject[index].T_to_O_RPI % (OPENER_TIMER_TICK * 1000);
+    if (tmp > 0)
+      {
+        stConnectionObject[index].T_to_O_RPI
+            = (EIP_UINT32) (stConnectionObject[index].T_to_O_RPI
+                / (OPENER_TIMER_TICK * 1000)) * (OPENER_TIMER_TICK * 1000)
+                + (OPENER_TIMER_TICK * 1000);
+      }
+
+    stConnectionObject[index].O_to_T_NetworkConnectionParameter
+        = ltohs(&pa_MRRequest->Data);
+    stConnectionObject[index].T_to_O_RPI = ltohl(&pa_MRRequest->Data);
+
+    stConnectionObject[index].T_to_O_NetworkConnectionParameter
+        = ltohs(&pa_MRRequest->Data);
+
+    /*check if Network connection paramters are ok */
+    if ((CIP_CONN_TYPE_MASK == (stConnectionObject[index].O_to_T_NetworkConnectionParameter & CIP_CONN_TYPE_MASK))
+        || (CIP_CONN_TYPE_MASK == (stConnectionObject[index].T_to_O_NetworkConnectionParameter & CIP_CONN_TYPE_MASK)))
+      {
+        stConnectionObject[index].State = CONN_STATE_NONEXISTENT;
+        return assembleFWDOpenResponse(&stConnectionObject[index],
+            pa_MRResponse, CIP_ERROR_CONNECTION_FAILURE, 
+            CIP_CON_MGR_ERROR_INVALID_CONNECTION_TYPE, pa_msg);
+      }
+
+    stConnectionObject[index].TransportTypeTrigger = *pa_MRRequest->Data++;
+    /*check if the trigger type value is ok */
+    if (0x40 & stConnectionObject[index].TransportTypeTrigger)
+      {
+        stConnectionObject[index].State = CONN_STATE_NONEXISTENT;
+        return assembleFWDOpenResponse(&stConnectionObject[index],
+            pa_MRResponse, CIP_ERROR_CONNECTION_FAILURE, 
+            CIP_CON_MGR_ERROR_TRANSPORT_TRIGGER_NOT_SUPPORTED, pa_msg);
+      }
+
+    tmp = parseConnectionPath(&stConnectionObject[index], pa_MRRequest,
+        &nConnectionStatus);
+    if (EIP_OK != tmp)
+      {
+        stConnectionObject[index].State = CONN_STATE_NONEXISTENT;
+        return assembleFWDOpenResponse(&stConnectionObject[index],
+            pa_MRResponse, tmp, nConnectionStatus, pa_msg);
+      }
+
+    //parsing is now finished all data is available and check now establish the connection
+    if (0x03 == (stConnectionObject[index].TransportTypeTrigger & 0x03))
+      {
+        //if we are here all values are checked and correct
+        //buffer the T_toO connection ID as we have to take the one given from the callee
+        tmp = stConnectionObject[index].CIPProducedConnectionID;
+        ConnectionObjectGeneralConfiguration(&stConnectionObject[index]);
+        stConnectionObject[index].WatchdogTimeoutAction = enWatchdogAutoDelete; /* the default for explicit connections */
+        stConnectionObject[index].CIPProducedConnectionID = tmp;
+        stConnectionObject[index].Instance_Type = CONN_TYPE_EXPLICIT;
+        stConnectionObject[index].sockfd[0]
+            = stConnectionObject[index].sockfd[1] = EIP_INVALID_SOCKET;
+      }
+    else
+      {
+        tmp = establishIOConnction(&stConnectionObject[index],
+            &nConnectionStatus);
+        if (EIP_OK != tmp)
+          {
+            if (EIP_DEBUG>EIP_VERBOSE)
+              printf("connection manager: connect failed\n");
+            stConnectionObject[index].State = CONN_STATE_NONEXISTENT;
+            return assembleFWDOpenResponse(&stConnectionObject[index],
+                pa_MRResponse, tmp, nConnectionStatus, pa_msg);
+          }
+      }
+
+    if (EIP_DEBUG>EIP_VERBOSE)
+      printf("connection manager: connect succeeded\n");
+    stConnectionObject[index].State = CONN_STATE_ESTABLISHED;
+
+    return assembleFWDOpenResponse(&(stConnectionObject[index]), pa_MRResponse, 
+    CIP_ERROR_SUCCESS, 0, pa_msg);
+  }
+
+/*   EIP_STATUS OpenPointToPointConnection(S_CIP_CPF_Data *pa_CPF_data, S_CIP_CM_Object *pa_pstCMObject, INT8 pa_direction, int pa_index)
+ *   open a Point2Point connection dependent on pa_direction.
+ *      pa_pstCMObject	pointer to registered Object in ConnectionManager.
+ *      pa_index	index of the connection object
+ *  return status
+ * 		 0 .. success
+ *      	-1 .. error
+ */
+
+EIP_STATUS OpenConsumingPointToPointConnection(
+    S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_CPF_Data *pa_CPF_data)
+  {
+    static EIP_UINT16 nUDPPort = 2222; //TODO think on improving the udp port assigment for point to point connections
+    int j;
+    struct sockaddr_in addr;
+    int newfd;
+
+    j = 0;
+    if (pa_CPF_data->AddrInfo[0].TypeID == 0)
+      { /* it is not used yet */
+        j = 0;
+      }
+    else if (pa_CPF_data->AddrInfo[1].TypeID == 0)
+      {
+        j = 1;
+      }
+
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = INADDR_ANY;
+    addr.sin_port = htons(nUDPPort++);
+
+    newfd = IApp_CreateUDPSocket(CONSUMING, &addr); // the address is only needed for bind used if consuming
+    if (newfd == -1)
+      {
+        printf("cannot create UDP socket in OpenPointToPointConnection\n");
+        return EIP_ERROR;
+      }
+    pa_pstConnObj->sockfd[CONSUMING] = newfd;
+
+    pa_CPF_data->AddrInfo[j].Length = 16;
+    pa_CPF_data->AddrInfo[j].TypeID = CIP_ITEM_ID_SOCKADDRINFO_O_TO_T;
+
+    pa_CPF_data->AddrInfo[j].nsin_port = addr.sin_port;
+    //TODO should we add our own address here?
+    pa_CPF_data->AddrInfo[j].nsin_addr = addr.sin_addr.s_addr;
+    memset(pa_CPF_data->AddrInfo[j].nasin_zero, 0, 8);
+    pa_CPF_data->AddrInfo[j].nsin_family = htons(AF_INET);
+
+    return EIP_OK;
+  }
+
+EIP_STATUS OpenProducingPointToPointConnection(
+    S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_CPF_Data *pa_CPF_data,
+    EIP_UINT16 *pa_pnExtendedError)
+  {
+    int j = -1;
+    int newfd;
+
+    if (CIP_ITEM_ID_SOCKADDRINFO_T_TO_O == pa_CPF_data->AddrInfo[0].TypeID)
+      {
+        j = 0;
+      }
+    else
+      {
+        if (CIP_ITEM_ID_SOCKADDRINFO_T_TO_O == pa_CPF_data->AddrInfo[1].TypeID)
+          {
+            j = 1;
+          }
+        else
+          {
+            /* the target should send us port and adress information  */
+            *pa_pnExtendedError
+                = CIP_CON_MGR_ERROR_PARAMETER_ERROR_IN_UNCONNECTED_SEND_SERVICE;
+            return CIP_ERROR_CONNECTION_FAILURE;
+          }
+      }
+
+    pa_pstConnObj->remote_addr.sin_family = AF_INET;
+    pa_pstConnObj->remote_addr.sin_addr.s_addr
+        = pa_CPF_data->AddrInfo[j].nsin_addr;
+    pa_pstConnObj->remote_addr.sin_port = pa_CPF_data->AddrInfo[j].nsin_port;
+
+    newfd = IApp_CreateUDPSocket(PRODUCING, &pa_pstConnObj->remote_addr); // the address is only needed for bind used if consuming
+    if (newfd == -1)
+      {
+        printf("cannot create UDP socket in OpenPointToPointConnection\n");
+        *pa_pnExtendedError = 0x0315; //miscelanouse
+        return CIP_ERROR_CONNECTION_FAILURE;
+      }
+    pa_pstConnObj->sockfd[PRODUCING] = newfd;
+
+    return EIP_OK;
+  }
+
+/*   INT8 OpenMulticastConnection(S_CIP_CPF_Data *pa_CPF_data, S_CIP_CM_Object *pa_pstCMObject, INT8 pa_direction, int pa_index)
+ *   open a Multicast connection dependent on pa_direction.
+ *      pa_CPF_data	received CPF Data Item.
+ *      pa_pstCMObject	pointer to registered Object in ConnectionManager.
+ *      pa_direction	flag to indicate if consuming or producing.
+ *      pa_index	index of the connection object
+ *  return status
+ * 		 0 .. success
+ *      	-1 .. error
+ */
+EIP_STATUS OpenMulticastConnection(int pa_direction,
+    S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_CPF_Data *pa_CPF_data)
+  {
+    int j;
+    struct sockaddr_in addr;
+    int newfd;
+
+    j = 0; // allocate an unused sockaddr struct to use
+    if (g_stCPFDataItem.AddrInfo[0].TypeID == 0)
+      { /* it is not used yet */
+        j = 0;
+      }
+    else if (g_stCPFDataItem.AddrInfo[1].TypeID == 0)
+      {
+        j = 1;
+      }
+
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = g_nMultiCastAddress;
+    addr.sin_port = htons(OPENER_UDP_MULTICAST_PORT);
+
+    newfd = IApp_CreateUDPSocket(pa_direction, &addr); // the address is only needed for bind used if consuming
+    if (newfd == -1)
+      {
+        printf("cannot create UDP socket in OpenMulticastConnection\n");
+        return EIP_ERROR;
+      }
+    pa_pstConnObj->sockfd[pa_direction] = newfd;
+
+    pa_CPF_data->AddrInfo[j].Length = 16;
+    if (pa_direction == CONSUMING)
+      {
+        pa_CPF_data->AddrInfo[j].TypeID = CIP_ITEM_ID_SOCKADDRINFO_O_TO_T;
+      }
+    else
+      {
+        pa_CPF_data->AddrInfo[j].TypeID = CIP_ITEM_ID_SOCKADDRINFO_T_TO_O;
+        pa_pstConnObj->remote_addr = addr;
+      }
+    pa_CPF_data->AddrInfo[j].nsin_port = addr.sin_port;
+    pa_CPF_data->AddrInfo[j].nsin_addr = addr.sin_addr.s_addr;
+    memset(pa_CPF_data->AddrInfo[j].nasin_zero, 0, 8);
+    pa_CPF_data->AddrInfo[j].nsin_family = htons(AF_INET);
+
+    return EIP_OK;
+  }
+
+/*   void ConnectionObjectGeneralConfiguration(int pa_index)
+ *   generate ConnectionID and set configurationparameter in connection object.
+ *      pa_index	index of the connection object
+ */
+void ConnectionObjectGeneralConfiguration(S_CIP_ConnectionObject *pa_pstConnObj)
+  {
+    //TODO improve the connection id algorithm, althoug this should not be a problem
+    static EIP_UINT32 connectionID = 18; /* start value to generate unique IDs */
+
+    /* copy information to ConnectionObject, generate IDs and start a UDP socket in listen mode */
+    pa_pstConnObj->CIPConsumedConnectionID = connectionID++;
+    pa_pstConnObj->CIPProducedConnectionID = connectionID++;
+
+    pa_pstConnObj->EIPSequenceCountProducing = 0;
+    pa_pstConnObj->SequenceCountProducing = 0;
+    pa_pstConnObj->EIPSequenceCountConsuming = 0;
+    pa_pstConnObj->SequenceCountConsuming = 0;
+
+    pa_pstConnObj->TransportClassTrigger = pa_pstConnObj->TransportTypeTrigger;
+
+    pa_pstConnObj->Instance_Type = CONN_TYPE_IO;
+    pa_pstConnObj->ExpectedPacketRate = 0; /* default value */
+
+    if ((pa_pstConnObj->TransportClassTrigger & 0x80) == 0x00)
+      { /* Client Type Connection requested */
+        pa_pstConnObj->ExpectedPacketRate
+            = (EIP_UINT16) ((pa_pstConnObj->T_to_O_RPI) / 1000);
+        /* As soon as we ara ready we should produce the connection. With the 0 here we will procude with the next timer tick
+         * which should be sufficient. */
+        pa_pstConnObj->TransmissionTriggerTimer = 0;
+      }
+    else
+      {
+        /* Server Type Connection requested */
+        pa_pstConnObj->ExpectedPacketRate
+            = (EIP_UINT16) ((pa_pstConnObj->O_to_T_RPI) / 1000);
+      }
+
+    /*setup the preconsuption timer: max(4* EpectetedPacketRate, 10s) */
+    pa_pstConnObj->InnacitvityWatchdogTimer = ((((pa_pstConnObj->T_to_O_RPI)
+        / 1000) << 2) > 10000) ? (((pa_pstConnObj->T_to_O_RPI) / 1000) << 2)
+        : 10000;
+
+    pa_pstConnObj->ConsumedConnectionSize
+        = pa_pstConnObj->O_to_T_NetworkConnectionParameter & 0x01FF;
+
+    pa_pstConnObj->ProducedConnectionSize
+        = pa_pstConnObj->T_to_O_NetworkConnectionParameter & 0x01FF;
+
+  }
+
+EIP_STATUS ForwardClose(S_CIP_Instance *pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg)
+  {
+    /* check connection serial number && Vendor ID && OriginatorSerialNr if connection is established */
+    int i;
+    EIP_UINT16 nConnectionStatus = CIP_CON_MGR_ERROR_CONNECTION_NOT_FOUND_AT_TARGET_APPLICATION;
+    EIP_UINT16 ConnectionSerialNr, OriginatorVendorID;
+    EIP_UINT32 OriginatorSerialNr;
+
+    /*supress compiler warning*/
+    (void)pa_pstInstance;
+
+    /* set AddressInfo Items to invalid TypeID to prevent assembleLinearMsg to read them */
+    g_stCPFDataItem.AddrInfo[0].TypeID = 0;
+    g_stCPFDataItem.AddrInfo[1].TypeID = 0;
+
+    pa_MRRequest->Data += 2; /* ignore Priority/Time_tick and Time-out_ticks */
+    ConnectionSerialNr = ltohs(&pa_MRRequest->Data);
+    OriginatorVendorID = ltohs(&pa_MRRequest->Data);
+    OriginatorSerialNr = ltohl(&pa_MRRequest->Data);
+
+    printf("ForwardClose: ConnSerNo %d\n", ConnectionSerialNr);
+
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS; i++)
+      {
+        if ((stConnectionObject[i].State == CONN_STATE_ESTABLISHED)
+            || (stConnectionObject[i].State == CONN_STATE_TIMEDOUT))
+          {
+            if ((stConnectionObject[i].ConnectionSerialNumber
+                == ConnectionSerialNr)
+                && (stConnectionObject[i].OriginatorVendorID
+                    == OriginatorVendorID)
+                && (stConnectionObject[i].OriginatorSerialNumber
+                    == OriginatorSerialNr))
+              { /* found the corresponding connectionobject -> close it */
+                closeConnection(&stConnectionObject[i]);
+                nConnectionStatus = CIP_CON_MGR_SUCCESS;
+              }
+          }
+      }
+
+    return assembleFWDCloseResponse(ConnectionSerialNr, OriginatorVendorID,
+        OriginatorSerialNr, pa_MRRequest, pa_MRResponse, nConnectionStatus,
+        pa_msg);
+  }
+
+EIP_STATUS UnconnectedSend(S_CIP_Instance * pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg)
+  {
+    EIP_UINT8 *p;
+    S_CIP_UnconnectedSend_Param_Struct data;
+    S_Data_Item stDataItem;
+
+    /*supress compiler warning*/
+    (void)pa_pstInstance;
+    (void)pa_MRResponse;
+    (void)pa_msg;
+
+    /* additional code from smr for communication with Devicelogix */
+    p = pa_MRRequest->Data;
+    //      printf("Calling unconnected send!\n");
+    data.Priority = *p++;
+    data.Timeout_Ticks = *p++;
+    data.Message_Request_Size = ltohs(&p);
+
+    if (data.Message_Request_Size != 0)
+      {
+        stDataItem.TypeID = g_stCPFDataItem.stDataI_Item.TypeID;
+        stDataItem.Length = data.Message_Request_Size;
+        stDataItem.Data = p;
+      }
+
+    p = pa_MRRequest->Data + 4 + data.Message_Request_Size;
+    /* check for padding */
+    if (data.Message_Request_Size % 2 != 0)
+      p++;
+
+    data.Route_Path.PathSize = *p++;
+    data.Reserved = *p++;
+    if (data.Route_Path.PathSize == 1)
+      {
+        data.Route_Path.Port = *p++;
+        data.Route_Path.Address = *p;
+      }
+    else
+      {
+        //TODO: other packet recieved
+        printf("Warning: Route path data of unconnected send currently not handled\n");
+      }
+    //TODO correctly handle the path, currently we just ignor it and forward to the message router which should be ok for non routing devices
+    return notifyMR(stDataItem.Data, data.Message_Request_Size);
+  }
+
+EIP_STATUS GetConnectionOwner(S_CIP_Instance * pa_pstInstance,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT8 * pa_msg)
+  {
+    /* suppress compiler warnings */
+    (void)pa_pstInstance;
+    (void)pa_MRRequest;
+    (void)pa_MRResponse;
+    (void)pa_msg;
+
+    return EIP_OK;
+  }
+
+EIP_STATUS manageConnections(void)
+  {
+    int i;
+    EIP_STATUS res;
+
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS; i++)
+      {
+        if (stConnectionObject[i].State == CONN_STATE_ESTABLISHED)
+          {
+            if ((0 != stConnectionObject[i].p_stConsumingInstance) || /* we have a consuming connection check innacitivitywatchdog timer */
+            (stConnectionObject[i].TransportClassTrigger & 0x80)) /* all sever connections have to maintain an innacitivitywatchdog timer */
+              {
+                stConnectionObject[i].InnacitvityWatchdogTimer
+                    -= OPENER_TIMER_TICK;
+                if (stConnectionObject[i].InnacitvityWatchdogTimer <= 0)
+                  {
+                    /* we have a timed out connection perform watchdog time out action*/
+                    printf(">>>>>>>>>>Connection timed out\n");
+                    switch (stConnectionObject[i].WatchdogTimeoutAction)
+                      {
+                    case enWatchdogTransitionToTimedOut:
+                      stConnectionObject[i].State = CONN_STATE_TIMEDOUT;
+                      break;
+                    case enWatchdogAutoDelete:
+                    default:
+                      closeConnection(&(stConnectionObject[i]));
+                      break;
+                      }
+                  }
+              }
+            /* only if the connection has not timed out check if data is to be send */
+            if (CONN_STATE_ESTABLISHED == stConnectionObject[i].State)
+              {
+                //                if ((stConnectionObject[i].State == CONN_STATE_ESTABLISHED) &&       /* check if we didn't time out*/
+                //                     (0 == (stConnectionObject[i].TransportClassTrigger & 0x80)))
+                //                  {
+                /* client connection */
+                if ((0 == (stConnectionObject[i].TransportClassTrigger & 0x70))
+                    && /* cyclic connection */
+                    (stConnectionObject[i].ExpectedPacketRate != 0))
+                  {
+                    stConnectionObject[i].TransmissionTriggerTimer
+                        -= OPENER_TIMER_TICK;
+                    if (stConnectionObject[i].TransmissionTriggerTimer <= 0)
+                      { /* need to send package */
+                        /* send() */
+                        res = sendConnectedData(&stConnectionObject[i]);
+                        if (res == EIP_ERROR)
+                          printf("sending of UDP data in manage Connection failed\n");
+
+                        /* reload the timer value */
+                        stConnectionObject[i].TransmissionTriggerTimer
+                            = stConnectionObject[i].ExpectedPacketRate;
+                      }
+                    //                      }
+
+                  }
+              }
+          }
+      }
+    return EIP_OK;
+  }
+
+/*   INT8 getFreeConnectionObject()
+ *   get a free connection object which is ready for connection establishment.
+ *  return index of connection object
+ * 			-1 .. error
+ */
+int getFreeConnectionObject(void)
+  {
+    int i;
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS; i++)
+      {
+        if (stConnectionObject[i].State == CONN_STATE_NONEXISTENT)
+          return i;
+      }
+    return -1;
+  }
+
+/*   INT8 registerObjectforConnections(S_CIP_Instance *pa_pstInstance, INT8 (*pt2function)(S_CIP_Instance *pa_pstInstance, INT8 *pa_data, UINT16 pa_dataLength))
+ *   register an object in the ConnectionManager for connections.
+ *      pa_pstInstance	pointer to object which is be registered
+ *      pt2function	pointer to callbackfunction of object to be registered
+ *  return status
+ * 			0 .. success, -1 .. error
+ */
+//EIP_STATUS registerObjectforConnections(S_CIP_Instance * pa_pstInstance,
+//    EIP_STATUS(*pt2function)( S_CIP_Instance * pa_pstInstance,
+//        EIP_UINT8 * pa_data,
+//        EIP_UINT16 pa_dataLength))
+//  {
+//    S_CIP_CM_Object *p, *p2;
+//    p = g_stCMObjectNode;
+//    while (1)
+//      {
+//        if (p->m_pstInstance == 0)
+//          {
+//            /* found free node for registration */
+//            p->m_ptfuncReceiveData = pt2function;
+//            p->m_pstInstance = pa_pstInstance;
+//            return EIP_OK;
+//          }
+//        else
+//          {
+//            if (p->next)
+//              {
+//                /* there is a next element available */
+//                p = p->next;
+//              }
+//            else
+//              { /* no more elements available -> create one */
+//                if ((p2 = allocateCMObjectNode(p)) == 0)
+//                  return EIP_ERROR;
+//                p2->m_ptfuncReceiveData = pt2function;
+//                p2->m_pstInstance = pa_pstInstance;
+//                return EIP_OK;
+//              }
+//          }
+//      }
+//  }
+
+/*   S_CIP_CM_Object* checkifObjectPresent(UINT32 pa_ClassID, UINT32 pa_InstanceNr)
+ *   check if object with classID and instanceNr is registered in Connection Manager.
+ *      pa_ClassID	requested ClassID
+ *      pa_InstanceNr	requested InstanceNr
+ *  return pointer to registerd Object
+ * 	0 .. object not registered
+ */
+//S_CIP_CM_Object *checkifObjectPresent(EIP_UINT32 pa_ClassID,
+//    EIP_UINT32 pa_InstanceNr)
+//  {
+//    S_CIP_CM_Object *p;
+//    p = g_stCMObjectNode;
+//
+//    while (1)
+//      {
+//        if (p->m_pstInstance)
+//          { /* there is at least one object present in list */
+//            if ((p->m_pstInstance->pstClass->nClassID == pa_ClassID)
+//                && (p->m_pstInstance->nInstanceNr == pa_InstanceNr))
+//              { /* found the right registered object */
+//                return p;
+//              }
+//            else
+//              {
+//                if (p->next)
+//                  p = p->next;
+//                else
+//                  return 0;
+//              }
+//          }
+//        else
+//          return 0;
+//      }
+//  }
+
+/*   INT8 assembleFWDOpenResponse(S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_MR_Response * pa_MRResponse, EIP_UINT8 pa_nGeneralStatus, EIP_UINT16 pa_nExtendedStatus,
+ void * deleteMeSomeday, EIP_UINT8 * pa_msg)
+ *   create FWDOpen response dependent on status.
+ *      pa_pstConnObj pointer to connection Object
+ *      pa_MRResponse	pointer to message router response
+ *      pa_nGeneralStatus the general status of the response
+ *      pa_nExtendedStatus extended status in the case of an error otherwise 0
+ *      pa_CPF_data	pointer to CPF Data Item
+ *      pa_msg		pointer to memory where reply has to be stored
+ *  return status
+ * 			0 .. no reply need to ne sent back
+ * 			1 .. need to send reply
+ * 		  -1 .. error
+ */
+EIP_STATUS assembleFWDOpenResponse(S_CIP_ConnectionObject *pa_pstConnObj,
+    S_CIP_MR_Response * pa_MRResponse, EIP_UINT8 pa_nGeneralStatus,
+    EIP_UINT16 pa_nExtendedStatus, EIP_UINT8 * pa_msg)
+  {
+    /* write reply information in CPF struct dependent of pa_status */
+    S_CIP_CPF_Data *pa_CPF_data = &g_stCPFDataItem;
+    pa_CPF_data->ItemCount = 2;
+    pa_MRResponse->Data = pa_msg;
+    pa_CPF_data->stDataI_Item.TypeID = CIP_ITEM_ID_UNCONNECTEDMESSAGE;
+    pa_CPF_data->stAddr_Item.TypeID = CIP_ITEM_ID_NULL;
+    pa_CPF_data->stAddr_Item.Length = 0;
+
+    pa_MRResponse->ReplyService = (0x80 | CIP_FORWARD_OPEN);
+    pa_MRResponse->GeneralStatus = pa_nGeneralStatus;
+
+    if (CIP_ERROR_SUCCESS == pa_nGeneralStatus)
+      {
+        if (EIP_DEBUG>EIP_VERBOSE)
+          printf("assembleFWDOpenResponse: sending success response\n");
+        pa_MRResponse->DataLength = 26; /* if there is no application specific data */
+        pa_MRResponse->SizeofAdditionalStatus = 0;
+
+        if (pa_CPF_data->AddrInfo[0].TypeID != 0)
+          {
+            pa_CPF_data->ItemCount = 3;
+            if (pa_CPF_data->AddrInfo[1].TypeID != 0)
+              {
+                pa_CPF_data->ItemCount = 4; /* there are two sockaddrinfo items to add */
+              }
+          }
+
+        htoll(pa_pstConnObj->CIPConsumedConnectionID, &pa_msg);
+        htoll(pa_pstConnObj->CIPProducedConnectionID, &pa_msg);
+      }
+    else
+      {
+        /* we have an connection creation error */
+        printf("assembleFWDOpenResponse: sending error response\n");
+        pa_MRResponse->DataLength = 10;
+        pa_MRResponse->SizeofAdditionalStatus = 1;
+        pa_MRResponse->AdditionalStatus[0] = pa_nExtendedStatus;
+      }
+
+    htols(pa_pstConnObj->ConnectionSerialNumber, &pa_msg);
+    htols(pa_pstConnObj->OriginatorVendorID, &pa_msg);
+    htoll(pa_pstConnObj->OriginatorSerialNumber, &pa_msg);
+
+    if (CIP_ERROR_SUCCESS == pa_nGeneralStatus)
+      {
+        /* set the actual packet rate to requested packet rate */
+        htoll(pa_pstConnObj->O_to_T_RPI, &pa_msg);
+        htoll(pa_pstConnObj->T_to_O_RPI, &pa_msg);
+      }
+
+    *pa_msg = 0; /* remaining path size -  for routing devices relevant */
+    pa_msg++;
+    *pa_msg = 0; /* reserved */
+    pa_msg++;
+
+    return EIP_OK_SEND; /* send reply */
+  }
+
+/*   INT8 assembleFWDCloseResponse(UINT16 pa_ConnectionSerialNr, UINT16 pa_OriginatorVendorID, UINT32 pa_OriginatorSerialNr, S_CIP_MR_Request *pa_MRRequest, S_CIP_MR_Response *pa_MRResponse, S_CIP_CPF_Data *pa_CPF_data, INT8 pa_status, INT8 *pa_msg)
+ *   create FWDClose response dependent on status.
+ *      pa_ConnectionSerialNr	requested ConnectionSerialNr
+ *      pa_OriginatorVendorID	requested OriginatorVendorID
+ *      pa_OriginatorSerialNr	requested OriginalSerialNr
+ *      pa_MRRequest		pointer to message router request
+ *      pa_MRResponse		pointer to message router response
+ *      pa_CPF_data		pointer to CPF Data Item
+ *      pa_status		status of FWDClose
+ *      pa_msg			pointer to memory where reply has to be stored
+ *  return status
+ * 			0 .. no reply need to ne sent back
+ * 			1 .. need to send reply
+ * 		       -1 .. error
+ */
+EIP_STATUS assembleFWDCloseResponse(EIP_UINT16 pa_ConnectionSerialNr,
+    EIP_UINT16 pa_OriginatorVendorID, EIP_UINT32 pa_OriginatorSerialNr,
+    S_CIP_MR_Request * pa_MRRequest, S_CIP_MR_Response * pa_MRResponse,
+    EIP_UINT16 pa_nExtErrorCode, EIP_UINT8 * pa_msg)
+  {
+    /* write reply information in CPF struct dependent of pa_status */
+    S_CIP_CPF_Data *pa_CPF_data = &g_stCPFDataItem;
+    pa_CPF_data->ItemCount = 2;
+    pa_MRResponse->Data = pa_msg;
+    pa_CPF_data->stDataI_Item.TypeID = CIP_ITEM_ID_UNCONNECTEDMESSAGE;
+    pa_CPF_data->stAddr_Item.TypeID = CIP_ITEM_ID_NULL;
+    pa_CPF_data->stAddr_Item.Length = 0;
+
+    htols(pa_ConnectionSerialNr, &pa_msg);
+    htols(pa_OriginatorVendorID, &pa_msg);
+    htoll(pa_OriginatorSerialNr, &pa_msg);
+
+    pa_MRResponse->ReplyService = (0x80 | pa_MRRequest->Service);
+    pa_MRResponse->DataLength = 10; /* if there is no application specific data */
+
+    if (CIP_CON_MGR_SUCCESS == pa_nExtErrorCode)
+      {
+        *pa_msg = 0; /* no application data */
+        pa_MRResponse->GeneralStatus = CIP_ERROR_SUCCESS;
+        pa_MRResponse->SizeofAdditionalStatus = 0;
+      }
+    else
+      {
+        *pa_msg = *pa_MRRequest->Data; /* remaining path size */
+        pa_MRResponse->GeneralStatus = CIP_ERROR_CONNECTION_FAILURE;
+        pa_MRResponse->AdditionalStatus[0] = pa_nExtErrorCode;
+        pa_MRResponse->SizeofAdditionalStatus = 1;
+      }
+
+    pa_msg++;
+    *pa_msg = 0; /* reserved */
+    pa_msg++;
+
+    return EIP_OK_SEND;
+  }
+
+S_CIP_ConnectionObject *getConnectedObject(EIP_UINT32 ConnectionID)
+  {
+    int i;
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS; i++)
+      {
+        if (stConnectionObject[i].State == CONN_STATE_ESTABLISHED)
+          {
+            if (stConnectionObject[i].CIPConsumedConnectionID == ConnectionID)
+              return &(stConnectionObject[i]);
+          }
+      }
+    return 0;
+  }
+
+int checkForExistingConnection(S_CIP_ConnectionObject *pa_pstConnObj)
+  {
+    int i;
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS; i++)
+      {
+        if (stConnectionObject[i].State == CONN_STATE_ESTABLISHED)
+          {
+            if ((pa_pstConnObj->ConnectionSerialNumber
+                == stConnectionObject[i].ConnectionSerialNumber)
+                && (pa_pstConnObj->OriginatorVendorID
+                    == stConnectionObject[i].OriginatorVendorID)
+                && (pa_pstConnObj->OriginatorSerialNumber
+                    == stConnectionObject[i].OriginatorSerialNumber))
+              {
+                return i;
+              }
+          }
+      }
+    return -1;
+  }
+
+EIP_STATUS checkElectronicKeyData(EIP_UINT8 pa_nKeyFormat,
+    S_CIP_KeyData *pa_pstKeyData, EIP_UINT16 *pa_pnExtStatus)
+  {
+    EIP_BYTE nCompatiblityMode = pa_pstKeyData->MajorRevision & 0x80;
+
+    pa_pstKeyData->MajorRevision &= 0x7F;
+    *pa_pnExtStatus = CIP_CON_MGR_SUCCESS;
+    if (4 != pa_nKeyFormat)
+      {
+        *pa_pnExtStatus = CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH;
+        return EIP_ERROR;
+      }
+
+    if (((pa_pstKeyData->VendorID != VendorID)&& (pa_pstKeyData->VendorID != 0))
+        || ((pa_pstKeyData->ProductCode != ProductCode)
+            && (pa_pstKeyData->ProductCode != 0)))
+      {
+        *pa_pnExtStatus = CIP_CON_MGR_ERROR_VENDERID_OR_PRODUCTCODE_ERROR;
+      }
+    else
+      {
+        if ((pa_pstKeyData->DeviceType != DeviceType)
+            && (pa_pstKeyData->DeviceType != 0))
+          {
+            *pa_pnExtStatus = CIP_CON_MGR_ERROR_VENDERID_OR_PRODUCT_TYPE_ERROR;
+          }
+        else
+          {
+            if (pa_pstKeyData->MajorRevision != 0)
+              { // 0 means accept any revision combination
+                if (pa_pstKeyData->MinorRevision == 0)
+                  {
+                    pa_pstKeyData->MinorRevision = Revison.MinorRevision;
+                  }
+
+                if (!((pa_pstKeyData->MajorRevision == Revison.MajorRevision)
+                    && (pa_pstKeyData->MinorRevision == Revison.MinorRevision)))
+                  {
+                    // we have no exact match
+                    if (!nCompatiblityMode)
+                      {
+                        *pa_pnExtStatus = CIP_CON_MGR_ERROR_REVISION_MISMATCH;
+                      }
+                    else
+                      {
+                        if ((pa_pstKeyData->MajorRevision
+                            != Revison.MajorRevision)
+                            || ((pa_pstKeyData->MajorRevision
+                                == Revison.MajorRevision)
+                                && (pa_pstKeyData->MinorRevision
+                                    > Revison.MinorRevision)))
+                          {
+                            //TODO check if we accept also greater minor revision depends on the product. Maybe should be configurable
+                            *pa_pnExtStatus
+                                = CIP_CON_MGR_ERROR_REVISION_MISMATCH;
+                          }
+                      }
+                  }
+
+              }
+          }
+      }
+    return (*pa_pnExtStatus == CIP_CON_MGR_SUCCESS) ? EIP_OK : EIP_ERROR;
+  }
+
+EIP_UINT8 parseConnectionPath(S_CIP_ConnectionObject *pa_pstConnObj, S_CIP_MR_Request *pa_MRRequest, EIP_UINT16 *pa_pnExtendedError)
+  {
+    int i;
+    EIP_UINT8 *pnMsg = pa_MRRequest->Data;
+    int nRemainingPathSize = pa_pstConnObj->ConnectionPathSize = *pnMsg++; /* length in words */
+
+    if ((FORWARD_OPEN_HEADER_LENGTH + nRemainingPathSize * 2 ) < pa_MRRequest->DataLength)
+      {
+        // the received packet is larger than the data in the path
+        *pa_pnExtendedError = 0;
+        return CIP_ERROR_TOO_MUCH_DATA;
+      }
+
+    if ((FORWARD_OPEN_HEADER_LENGTH + nRemainingPathSize * 2 )> pa_MRRequest->DataLength)
+      {
+        //there is not enough data in received packet
+        *pa_pnExtendedError = 0;
+        return CIP_ERROR_NOT_ENOUGH_DATA;
+      }
+
+    if(nRemainingPathSize> 0)
+      {
+        /* first electronic key */
+        if (*pnMsg == 0x34)
+          {
+            if(nRemainingPathSize < 5)
+              {
+                //there is not enough data for holding the electronic key segement
+                *pa_pnExtendedError = 0;
+                return CIP_ERROR_NOT_ENOUGH_DATA;
+              }
+
+            /* logical electronic key found */
+            pa_pstConnObj->ElectronicKey.SegmentType = 0x34;
+            pnMsg++;
+            pa_pstConnObj->ElectronicKey.KeyFormat
+            = *pnMsg++;
+            pa_pstConnObj->ElectronicKey.KeyData.VendorID
+            = ltohs(&pnMsg);
+            pa_pstConnObj->ElectronicKey.KeyData.DeviceType
+            = ltohs(&pnMsg);
+            pa_pstConnObj->ElectronicKey.KeyData.ProductCode
+            = ltohs(&pnMsg);
+            pa_pstConnObj->ElectronicKey.KeyData.MajorRevision
+            = *pnMsg++;
+            pa_pstConnObj->ElectronicKey.KeyData.MinorRevision
+            = *pnMsg++;
+            nRemainingPathSize -= 5; //length of the electronic key
+            if (EIP_DEBUG>=EIP_VERBOSE)
+            printf(
+                "key: ven ID %d, dev type %d, prod code %d, major %d, minor %d\n",
+                pa_pstConnObj->ElectronicKey.KeyData.VendorID,
+                pa_pstConnObj->ElectronicKey.KeyData.DeviceType,
+                pa_pstConnObj->ElectronicKey.KeyData.ProductCode,
+                pa_pstConnObj->ElectronicKey.KeyData.MajorRevision,
+                pa_pstConnObj->ElectronicKey.KeyData.MinorRevision);
+
+            if (EIP_OK != checkElectronicKeyData(
+                    pa_pstConnObj->ElectronicKey.KeyFormat,
+                    &(pa_pstConnObj->ElectronicKey.KeyData),
+                    pa_pnExtendedError))
+              {
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+        else
+          {
+            if (EIP_DEBUG>=EIP_VERBOSE)
+            printf("no key\n");
+          }
+
+        S_CIP_Class *pstClass = 0;
+
+        if (EQLOGICALPATH(*pnMsg,0x20))
+          { /* classID */
+            pa_pstConnObj->ConnectionPath.ClassID
+            = GETPADDEDLOGICALPATH(&pnMsg);
+            pstClass
+            = getCIPClass(pa_pstConnObj->ConnectionPath.ClassID);
+            if (0 == pstClass)
+              {
+                if (EIP_DEBUG>=EIP_TERSE)
+                printf("classid %lx not found\n",
+                    pa_pstConnObj->ConnectionPath.ClassID);
+                if(pa_pstConnObj->ConnectionPath.ClassID >= 0xC8) //reserved range of class ids
+
+                  {
+                    *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH;
+                  }
+                else
+                  {
+                    *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                  }
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+            if (EIP_DEBUG>=EIP_VERBOSE)
+            printf("classid %lx (%s)\n",
+                pa_pstConnObj->ConnectionPath.ClassID, pstClass->acName);
+          }
+        else
+          {
+            *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH;
+            return CIP_ERROR_CONNECTION_FAILURE;
+          }
+        nRemainingPathSize -= 1; // 1 16Bit word for the class part of the path
+
+        S_CIP_Instance *pstConfigInstance;
+
+        if (EQLOGICALPATH(*pnMsg,0x24))
+          { /* ignore inst ID */
+            int inst = GETPADDEDLOGICALPATH(&pnMsg);
+            if (EIP_DEBUG>=EIP_VERBOSE)
+            printf("Configuration instid %d\n", inst);
+            if (0 == (pstConfigInstance = getCIPInstance(pstClass, inst)))
+              {
+                //according to the test tool we should respond with this extended error code
+                *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH;
+                //*pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+        else
+          {
+            *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH;
+            return CIP_ERROR_CONNECTION_FAILURE;
+          }
+
+        nRemainingPathSize -= 1; // 1 16Bit word for the configuration instance part of the path
+
+        if(0x03 == (pa_pstConnObj->TransportTypeTrigger & 0x03))
+          { // we have Class 3 connection, connection endpoint has to be the message router instance 1
+            if((pa_pstConnObj->ConnectionPath.ClassID != CIP_MESSAGE_ROUTER_CLASS_CODE) || (pstConfigInstance->nInstanceNr != 1))
+              {
+                *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+            pa_pstConnObj->ConnectionPath.ConnectionPoint[0] = pstConfigInstance->nInstanceNr;
+          }
+        else
+          { // we have an IO connection
+            if(pa_pstConnObj->ConnectionPath.ClassID != CIP_ASSEMBLY_CLASS_CODE)
+              { // we currently only support IO connections to assembly instances
+                *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+
+            int O2TConnectionType, T2OConnectionType, imax;
+
+            O2TConnectionType = (pa_pstConnObj->O_to_T_NetworkConnectionParameter & 0x6000) >> 13;
+            T2OConnectionType = (pa_pstConnObj->T_to_O_NetworkConnectionParameter & 0x6000) >> 13;
+
+            pa_pstConnObj->ConnectionPath.ConnectionPoint[1] = 0; /* set not available path to Invalid */
+
+            if (O2TConnectionType == 0)
+              {
+                if(T2OConnectionType == 0)
+                  { //configuration only connection
+                    imax = 0;
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                    printf("assembly: type invalid\n");
+                  }
+                else
+                  { /* 1 path -> path is for production */
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                    printf("assembly: type produce\n");
+                    imax = 1;}
+              }
+            else
+              {
+                if(T2OConnectionType == 0)
+                  { /* 1 path -> path is for consumption */
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                    printf("assembly: type consume\n");
+                    imax = 1;
+                  }
+                else
+                  { /* 2 pathes -> 1st for production 2nd for consumption */
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                    printf("assembly: type bidirectional\n");
+                    imax = 2;
+                  }
+              }
+
+            for (i = 0; i < imax; i++) /* process up to 3 encoded paths */
+              {
+                if (EQLOGICALPATH(*pnMsg,0x24) || EQLOGICALPATH(*pnMsg,0x2C)) /* Connection Point interpreted as InstanceNr -> only in Assembly Objects */
+                  { /* InstanceNR */
+                    pa_pstConnObj->ConnectionPath.ConnectionPoint[i]
+                    = GETPADDEDLOGICALPATH(&pnMsg);
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                    printf("connection point %lu\n", pa_pstConnObj->ConnectionPath.ConnectionPoint[i]);
+                    if (0 == getCIPInstance(pstClass, pa_pstConnObj->ConnectionPath.ConnectionPoint[i]))
+                      {
+                        *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                        return CIP_ERROR_CONNECTION_FAILURE;
+                      }
+                    nRemainingPathSize -= 1; // 1 16Bit word for the connection point part of the path       
+                  }
+                else
+                  {
+                    *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_SEGMENT_TYPE_IN_PATH;
+                    return CIP_ERROR_CONNECTION_FAILURE;
+                  }
+              }
+
+            if(nRemainingPathSize> 0)
+              { // have something left in the path should be configuration data
+                if(0x80 == *pnMsg)
+                  { // we have a simple data segment
+                    //TODO do we have to handle ansi extended symbol data segemnts too?
+                    int nLen = pnMsg[1] * 2; //data segements store length 16-bit word wise
+                    /*put the data on the configuration assembly object with the current
+                     design this can be done rather efficiently */
+                    if(EIP_OK != notifyAssemblyConnectedDataReceived(pstConfigInstance, &(pnMsg[2]), nLen))
+                      {
+                        if (EIP_DEBUG>=EIP_VERBOSE)
+                        printf("Configuration data was invalid\n");
+                        *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONFIGURATION_FORMAT;
+                        return CIP_ERROR_CONNECTION_FAILURE;
+                      }
+                  }
+                else
+                  {
+                    if (EIP_DEBUG>=EIP_VERBOSE)
+                    printf("No data segement identifier found for the configuration data\n");
+                    *pa_pnExtendedError = pa_pstConnObj->ConnectionPathSize - nRemainingPathSize; //offset in 16Bit words where within the connection path the error happend
+                    return 0x04; //status code for invalid segment type
+                  }
+              }
+          }
+      }
+
+    //save back the current position in the stream allowing followers to parse anything thats still there
+    pa_MRRequest->Data = pnMsg;
+    return EIP_OK;
+  }
+
+EIP_UINT8 establishIOConnction(S_CIP_ConnectionObject *pa_pstConnObj, EIP_UINT16 *pa_pnExtendedError)
+  {
+    int O2TConnectionType, T2OConnectionType;
+    S_CIP_attribute_struct *pstAttribute;
+    O2TConnectionType = (pa_pstConnObj->O_to_T_NetworkConnectionParameter & 0x6000) >> 13;
+    T2OConnectionType = (pa_pstConnObj->T_to_O_NetworkConnectionParameter & 0x6000) >> 13;
+
+    ConnectionObjectGeneralConfiguration(pa_pstConnObj);
+
+    /* currently we allow I/O connections only to assembly objects */
+    S_CIP_Class *pstAssemblyClass = getCIPClass(CIP_ASSEMBLY_CLASS_CODE); /* we don't need to check for zero as this is handled in the connection path parsing */
+    S_CIP_Instance *pstInstance = 0;
+
+    if((O2TConnectionType == 0) && (T2OConnectionType == 0))
+      { // this indicates an re-configuration of the connection currently not supported and we should not come here as this is handled in the forwardopen function
+
+      }
+    else
+      {
+        int nProducingIndex = 0;
+        int nDataSize;
+        if((O2TConnectionType != 0) && (T2OConnectionType != 0))
+          { // we have a producing and consuming connection
+            nProducingIndex = 1;
+          }
+
+        pa_pstConnObj->p_stConsumingInstance = 0;
+        pa_pstConnObj->ConsumedConnectionPathLength = 0;
+        pa_pstConnObj->p_stProducingInstance = 0;
+        pa_pstConnObj->ProducedConnectionPathLength = 0;
+
+        if(O2TConnectionType != 0)
+          { //setup consumer side
+            if (0 != (pstInstance = getCIPInstance(pstAssemblyClass, pa_pstConnObj->ConnectionPath.ConnectionPoint[0])))
+              { /* consuming Connection Point is present */
+                pa_pstConnObj->p_stConsumingInstance = pstInstance;
+                //pa_pstConnObj->stLinkObject.Consumer.m_ptfuncReceiveData = pstCMObject->m_ptfuncReceiveData;
+
+                pa_pstConnObj->ConsumedConnectionPathLength = 6;
+                pa_pstConnObj->ConsumedConnectionPath.PathSize = 6;
+                pa_pstConnObj->ConsumedConnectionPath.ClassID = pa_pstConnObj->ConnectionPath.ClassID;
+                pa_pstConnObj->ConsumedConnectionPath.InstanceNr = pa_pstConnObj->ConnectionPath.ConnectionPoint[0];
+                pa_pstConnObj->ConsumedConnectionPath.AttributNr = 3;
+
+                pstAttribute = getAttribute(pstInstance, 3);
+                assert(pstAttribute != 0); /* an assembly object should always have an attribute 3 */
+                nDataSize = pa_pstConnObj->ConsumedConnectionSize;
+                if((pa_pstConnObj->TransportTypeTrigger & 0x0F) == 1)
+                  {
+                    /* class 1 connection */
+                    nDataSize -= 2; /* remove 16-bit sequence count length */
+                  }
+                if(OPENER_CONSUMED_DATA_HAS_RUN_IDLE_HEADER)
+                  {
+                    nDataSize -= 4; /* remove the 4 bytes needed for run/idle header */
+                  }
+                if(((S_CIP_Byte_Array * )pstAttribute->pt2data)->len != nDataSize)
+                  {
+                    //wrong connection size
+                    *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_SIZE;
+                    return CIP_ERROR_CONNECTION_FAILURE;
+                  }
+              }
+            else
+              {
+                *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+
+        if(T2OConnectionType != 0)
+          { //setup producer side
+            if (0 != (pstInstance = getCIPInstance(pstAssemblyClass, pa_pstConnObj->ConnectionPath.ConnectionPoint[nProducingIndex])))
+              {
+                pa_pstConnObj->p_stProducingInstance = pstInstance;
+
+                pa_pstConnObj->ProducedConnectionPathLength = 6;
+                pa_pstConnObj->ProducedConnectionPath.PathSize = 6;
+                pa_pstConnObj->ProducedConnectionPath.ClassID = pa_pstConnObj->ConnectionPath.ClassID;
+                pa_pstConnObj->ProducedConnectionPath.InstanceNr = pa_pstConnObj->ConnectionPath.ConnectionPoint[nProducingIndex];
+                pa_pstConnObj->ProducedConnectionPath.AttributNr = 3;
+
+                pstAttribute = getAttribute(pstInstance, 3);
+                assert(pstAttribute != 0); /* an assembly object should always have an attribute 3 */
+                nDataSize = pa_pstConnObj->ProducedConnectionSize;
+                if((pa_pstConnObj->TransportTypeTrigger & 0x0F) == 1)
+                  {
+                    /* class 1 connection */
+                    nDataSize -= 2; /* remove 16-bit sequence count length */
+                  }
+                if(((S_CIP_Byte_Array * )pstAttribute->pt2data)->len != nDataSize)
+                  {
+                    //wrong connection size
+                    *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_SIZE;
+                    return CIP_ERROR_CONNECTION_FAILURE;
+                  }
+
+              }
+            else
+              {
+                *pa_pnExtendedError = CIP_CON_MGR_ERROR_INVALID_CONNECTION_POINT;
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+
+        pa_pstConnObj->WatchdogTimeoutAction = enWatchdogTransitionToTimedOut; /* the default for I/O connections */
+
+        /*get pointer to the cpf data, currently we have just one global instance of the struct. This may change in the future*/
+        S_CIP_CPF_Data *pstCPF_data = &g_stCPFDataItem;
+
+        /* open a connection "point to point" or "multicast" based on the ConnectionParameter */
+        if (O2TConnectionType == 1) /* Multicast consuming */
+          {
+            if (OpenMulticastConnection(CONSUMING, pa_pstConnObj, pstCPF_data)
+                == EIP_ERROR)
+              {
+                printf("error in OpenMulticast Connection\n");
+                *pa_pnExtendedError = 0; //TODO find out the correct extended error code
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+        else if (O2TConnectionType == 2) /* Point to Point consuming */
+          {
+            if (OpenConsumingPointToPointConnection(pa_pstConnObj, pstCPF_data)
+                == EIP_ERROR)
+              {
+                printf("error in PointToPoint consuming connection\n");
+                *pa_pnExtendedError = 0; //TODO find out the correct extended error code
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+
+        if (T2OConnectionType == 1) /* Multicast producing */
+          {
+            if (OpenMulticastConnection(PRODUCING, pa_pstConnObj, pstCPF_data)
+                == EIP_ERROR)
+              {
+                printf("error in OpenMulticast Connection\n");
+                *pa_pnExtendedError = 0; //TODO find out the correct extended error code
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+        else if (T2OConnectionType == 2) /* Point to Point producing */
+          {
+
+            if (OpenProducingPointToPointConnection(pa_pstConnObj, pstCPF_data, pa_pnExtendedError)
+                != EIP_OK)
+              {
+                printf("error in PointToPoint producing connection\n");
+                return CIP_ERROR_CONNECTION_FAILURE;
+              }
+          }
+
+      }
+
+    return EIP_OK;
+  }
+
+void closeConnection(S_CIP_ConnectionObject *pa_pstConnObj)
+  {
+    pa_pstConnObj->State = CONN_STATE_NONEXISTENT;
+    if (0x03 != (pa_pstConnObj->TransportTypeTrigger & 0x03))
+      {
+        /* only close the udp connection for not class 3 connections */
+        IApp_CloseSocket(pa_pstConnObj->sockfd[CONSUMING]);
+        pa_pstConnObj->sockfd[CONSUMING] = EIP_INVALID_SOCKET;
+        IApp_CloseSocket(pa_pstConnObj->sockfd[PRODUCING]);
+        pa_pstConnObj->sockfd[CONSUMING] = EIP_INVALID_SOCKET;
+      }
+  }
+
+EIP_STATUS sendConnectedData(S_CIP_ConnectionObject *pa_pstConnection)
+  {
+    S_CIP_CPF_Data *pCPFDataItem;
+    S_CIP_Byte_Array *p;
+    EIP_UINT16 replylength;
+
+    /* TODO think of adding an own send buffer to each connection object in order to preset up the whole message on connection opening and just change the variable data items e.g., sequence number */
+    
+    pCPFDataItem = &g_stCPFDataItem; /* TODO think on adding a CPF data item to the S_CIP_ConnectionObject in order to remove the code here or even better allocate memory in the connection object for storing the message to send and just change the application data*/
+
+    pa_pstConnection->EIPSequenceCountProducing++;  
+    
+    /* assembleCPFData */
+    pCPFDataItem->ItemCount = 2;
+    if ((pa_pstConnection->TransportClassTrigger & 0x0F) != 0)
+      { /* use Sequenced Address Items if not Connection Class 0 */
+        pCPFDataItem->stAddr_Item.TypeID = CIP_ITEM_ID_SEQUENCEDADDRESS;
+        pCPFDataItem->stAddr_Item.Length = 8;
+        pCPFDataItem->stAddr_Item.Data.SequenceNumber
+            = pa_pstConnection->EIPSequenceCountProducing;
+      }
+    else
+      {
+        pCPFDataItem->stAddr_Item.TypeID = CIP_ITEM_ID_CONNECTIONBASED;
+        pCPFDataItem->stAddr_Item.Length = 4;
+
+      }
+    pCPFDataItem->stAddr_Item.Data.ConnectionIdentifier
+        = pa_pstConnection->CIPProducedConnectionID;
+
+    pCPFDataItem->stDataI_Item.TypeID = CIP_ITEM_ID_CONNECTIONTRANSPORTPACKET;
+
+    p = pa_pstConnection->p_stProducingInstance->pstAttributes->pt2data;
+    pCPFDataItem->stDataI_Item.Length = 0;
+    //pCPFDataItem->stDataI_Item.Data = (EIP_UINT8 *)p->Data;
+
+    /* notify the application that data will be sent immediately after the call */
+    if(IApp_BeforeAssemblyDataSend(pa_pstConnection->p_stProducingInstance))
+      {
+        /* the data has changed increase sequence counter */
+        pa_pstConnection->SequenceCountProducing++;
+      }
+
+    /* set AddressInfo Items to invalid Type */
+    pCPFDataItem->AddrInfo[0].TypeID = 0;
+    pCPFDataItem->AddrInfo[1].TypeID = 0;
+
+    replylength = assembleLinearMsg(0, pCPFDataItem, &g_acMessageDataReplyBuffer[0]);
+    
+    EIP_UINT8 *pnBuf = &g_acMessageDataReplyBuffer[replylength - 2];
+    pCPFDataItem->stDataI_Item.Length = p->len;
+    if((pa_pstConnection->TransportTypeTrigger & 0x0F) == 1)
+      {
+        pCPFDataItem->stDataI_Item.Length += 2;
+        htols(pCPFDataItem->stDataI_Item.Length, &pnBuf);
+        htols(pa_pstConnection->SequenceCountProducing, &pnBuf);
+      }
+    else
+      {
+        htols(pCPFDataItem->stDataI_Item.Length, &pnBuf);  
+      }
+    
+    int i;
+    
+    for (i = 0; i < p->len; i++)
+      {
+        *pnBuf = (EIP_UINT8) * (p->Data + i);
+        pnBuf++;
+      }
+    
+    replylength += pCPFDataItem->stDataI_Item.Length;
+    
+    return IApp_SendUDPData(&pa_pstConnection->remote_addr, pa_pstConnection->sockfd[PRODUCING], &g_acMessageDataReplyBuffer[0],
+        replylength);
+  }

+ 151 - 0
src/cip/cipconnectionmanager.h

@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPCONNECTIONMANAGER_H_
+#define CIPCONNECTIONMANAGER_H_
+
+#include "opener_user_conf.h"
+#include "typedefs.h"
+#include "ciptypes.h"
+
+#define CONSUMING 0           // these are used as array indexes, watch out if changing these values
+#define PRODUCING 1
+
+/* define states of a connection */
+typedef enum
+  {
+  CONN_STATE_NONEXISTENT = 0,
+  CONN_STATE_CONFIGURING = 1,
+  CONN_STATE_WAITINGFORCONNECTIONID = 2 /* only used in DeviceNet*/,
+  CONN_STATE_ESTABLISHED = 3,
+  CONN_STATE_TIMEDOUT = 4,
+  CONN_STATE_DEFERREDDELETE = 5 /* only used in DeviceNet */,
+  CONN_STATE_CLOSING
+  } CONN_STATE;
+
+/* instance_type attributes */
+typedef enum
+  {
+  CONN_TYPE_EXPLICIT = 0, CONN_TYPE_IO = 1
+  } CONN_TYPE;
+
+typedef enum
+  {
+    enWatchdogTransitionToTimedOut = 0,  /* default for I/O connections, invalid for explicit message connetions */
+    enWatchdogAutoDelete = 1,            /*default for explicit message connections */
+    enWatchdogAutoReset = 2,             /*invalid for explicit message connetions */
+    enWatchdogDeferredDelete = 3         /* only valid for DeviceNet, invalid for I/O connetions */
+  } EWatchdogTimeOutAction;
+  
+typedef struct
+  {
+    CONN_STATE state;
+    EIP_UINT16 ConnectionID;
+    //TODO think if this is needed anymore
+    //TCMReceiveDataFunc m_ptfuncReceiveData;
+  } S_Link_Consumer;
+
+typedef struct
+  {
+    CONN_STATE state;
+    EIP_UINT16 ConnectionID;
+  } S_Link_Producer;
+
+typedef struct
+  {
+    S_Link_Consumer Consumer;
+    S_Link_Producer Producer;
+  } S_Link_Object;
+
+  
+/* The data needed for handling connections. This data is strongly related to the connection object defined in the CIP-spec
+ * However the full functionality of the connection object is not implemented. Therefore this data cann not be accesed with
+ * CIP means.
+ */   
+typedef struct
+  {
+    CONN_STATE State;
+    CONN_TYPE Instance_Type;
+    EIP_BYTE TransportClassTrigger;
+    /* conditional
+     UINT16 DeviceNetProductedConnectionID;
+     UINT16 DeviceNetConsumedConnectionID;
+     */
+    EIP_BYTE DeviceNetInitialCommCharacteristics;
+    EIP_UINT16 ProducedConnectionSize;
+    EIP_UINT16 ConsumedConnectionSize;
+    EIP_UINT16 ExpectedPacketRate;
+    
+    /*conditional*/
+    EIP_UINT32 CIPProducedConnectionID;
+    EIP_UINT32 CIPConsumedConnectionID;
+    /**/
+    EWatchdogTimeOutAction WatchdogTimeoutAction;
+    EIP_UINT16 ProducedConnectionPathLength;
+    S_CIP_EPATH ProducedConnectionPath;
+    EIP_UINT16 ConsumedConnectionPathLength;
+    S_CIP_EPATH ConsumedConnectionPath;
+    /* conditional
+     UINT16 ProductionInhibitTime;
+     */
+    /* non CIP Attributes, only relevant for opened connections */
+    EIP_BYTE Priority_Timetick;
+    EIP_UINT8 Timeoutticks;
+    EIP_UINT16 ConnectionSerialNumber;
+    EIP_UINT16 OriginatorVendorID;
+    EIP_UINT32 OriginatorSerialNumber;
+    EIP_UINT16 ConnectionTimeoutMultiplier;
+    EIP_UINT32 O_to_T_RPI;
+    EIP_UINT16 O_to_T_NetworkConnectionParameter;
+    EIP_UINT32 T_to_O_RPI;
+    EIP_UINT16 T_to_O_NetworkConnectionParameter;
+    EIP_BYTE TransportTypeTrigger;
+    EIP_UINT8 ConnectionPathSize;
+    S_CIP_ElectronicKey ElectronicKey;
+    S_CIP_ConnectionPath ConnectionPath; /* padded EPATH*/
+    S_Link_Object stLinkObject;
+
+    S_CIP_Instance *p_stConsumingInstance;
+    //S_CIP_CM_Object *p_stConsumingCMObject;
+
+    S_CIP_Instance *p_stProducingInstance;
+    //S_CIP_CM_Object *p_stProducingCMObject;
+
+    EIP_UINT32 EIPSequenceCountProducing; /* the EIP level sequence Count for Class 0/1 Producing Connections may have a different value than SequenceCountProducing*/
+    EIP_UINT32 EIPSequenceCountConsuming; /* the EIP level sequence Count for Class 0/1 Producing Connections may have a different value than SequenceCountProducing*/
+
+    EIP_UINT16 SequenceCountProducing; /* sequence Count for Class 1 Producing Connections*/
+    EIP_UINT16 SequenceCountConsuming; /* sequence Count for Class 1 Producing Connections*/ 
+
+
+    EIP_INT32 TransmissionTriggerTimer;
+    EIP_INT32 InnacitvityWatchdogTimer;
+    struct sockaddr_in remote_addr; /* socket address for produce */
+    int sockfd[2]; /* socket handles, indexed by CONSUMING or PRODUCING */
+  } S_CIP_ConnectionObject;
+
+
+
+#define CIP_CONNECTION_MANAGER_CLASS_CODE 0x06
+
+/* public funtions */
+
+/*! Initialize the data of the connection manager object
+ */ 
+EIP_STATUS Connection_Manager_Init( void );
+
+/*!  Get a connected object dependent on requested ConnectionID.
+ *   The returned connection may not be in established state. The user has to check
+ *   this!   
+ *   @param ConnectionID  requested ConnectionID of opened connection
+ *   @return pointer to connected Object
+ *           0 .. connection not present in device
+ */
+S_CIP_ConnectionObject *getConnectedObject(EIP_UINT32 ConnectionID);
+
+#endif /*CIPCONNECTIONMANAGER_H_*/
+

+ 78 - 0
src/cip/ciperror.h

@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPERROR_H_
+#define CIPERROR_H_
+
+#define CIP_ERROR_SUCCESS 0x00 /*!< Service was successfully performed by the object specified. */
+#define CIP_ERROR_CONNECTION_FAILURE 0x01 /*!< A connection related service failed along the connection path. */
+#define CIP_ERROR_RESOURCE_UNAVAILABLE 0x02 /*!< Resources needed for the object to perform the requested service were unavailable */
+#define CIP_ERROR_INVALID_PARAMETER_VALUE 0x03 /*!< See Status Code 0x20, which is the preferred value to use for this condition. */
+#define CIP_ERROR_PATH_SEGMENT_ERROR 0x04 /*!< The path segment identifier or the segment syntax was not understood by the
+processing node. Path processing shall stop when a path segment error is encountered. */
+#define CIP_ERROR_PATH_DESTINATION_UNKNOWN 0x05 /*!< The path is referencing an object class, instance or structure element that is not
+known or is not contained in the processing node. Path processing shall stop when a path destination unknown error is encountered. */
+#define CIP_ERROR_PARTIAL_TRANSFER 0x06 /*!< Only part of the expected data was transferred. */
+#define CIP_ERROR_CONNECTION_LOST 0x07 /*!< The messaging connection was lost. */
+#define CIP_ERROR_SERVICE_NOT_SUPPORTED 0x08 /*!< The requested service was not implemented or was not defined for this Object
+Class/Instance. */
+#define CIP_ERROR_INVALID_ATTRIBUTE_VALUE 0x09 /*!< Invalid attribute data detected */
+#define CIP_ERROR_ATTRIBUTE_LIST_ERROR 0x0A /*!< An attribute in the Get_Attribute_List or Set_Attribute_List response has a
+non-zero status. */
+#define CIP_ERROR_ALREADY_IN_REQUESTED_MODE 0x0B /*!< The object is already in the mode/state being requested by the service */
+#define CIP_ERROR_OBJECT_STATE_CONFLICT 0x0C /*!< The object cannot perform the requested service in its current mode/state */
+#define CIP_ERROR_OBJECT_ALREADY_EXISTS 0x0D /*!< The requested instance of object to be created already exists.*/
+#define CIP_ERROR_ATTRIBUTE_NOT_SETTABLE 0x0E /*!< A request to modify a non-modifiable attribute was received. */
+#define CIP_ERROR_PRIVILEGE_VIOLATION 0x0F /*!< A permission/privilege check failed */
+#define CIP_ERROR_DEVICE_STATE_CONFLICT 0x10 /*!< The device’s current mode/state prohibits the execution of the requested service. */
+#define CIP_ERROR_REPLY_DATA_TOO_LARGE 0x11 /*!< The data to be transmitted in the response buffer is larger than the allocated response buffer */
+#define CIP_ERROR_FRAGMENTATION_OF_A_PRIMITIVE_VALUE 0x12 /*!< The service specified an operation that is going to fragment a primitive data
+value, i.e. half a REAL data type. */
+#define CIP_ERROR_NOT_ENOUGH_DATA 0x13 /*!< The service did not supply enough data to perform the specified operation. */
+#define CIP_ERROR_ATTRIBUTE_NOT_SUPPORTED 0x14 /*!< The attribute specified in the request is not supported */
+#define CIP_ERROR_TOO_MUCH_DATA 0x15 /*!< The service supplied more data than was expected */
+#define CIP_ERROR_OBJECT_DOES_NOT_EXIST 0x16 /*!< The object specified does not exist in the device. */
+#define CIP_ERROR_SERVICE_FRAGEMENTATION_SEQUENCE_NOT_IN_PROGRESS 0x17 /*!< The fragmentation sequence for this service is not currently active for this
+data. */
+#define CIP_ERROR_NO_STORED_ATTRIBUTE_DATA 0x18 /*!< The attribute data of this object was not saved prior to the requested service. */
+#define CIP_ERROR_STORE_OPERATION_FAILURE 0x19 /*!< The attribute data of this object was not saved due to a failure during the attempt. */
+#define CIP_ERROR_ROUTING_FAILURE_REQUEST_PACKET_TOO_LARGE 0x1A /*!< The service request packet was too large for transmission on a network in the
+path to the destination. The routing device was forced to abort the service. */
+#define CIP_ERROR_ROUTING_FAILURE_RESPONSE_PACKET_TOO_LARGE 0x1B /*!< The service response packet was too large for transmission on a network in the
+path from the destination. The routing device was forced to abort the service. */
+#define CIP_ERROR_MISSING_ATTIRBUTE_LIST_ENTRIY 0x1C /*!< The service did not supply an attribute in a list of attributes that was needed by
+the service to perform the requested behaviour. */
+#define CIP_ERROR_INVALID_ATTRIBUTE_VALUE_LIST 0x1D /*!< The service is returning the list of attributes supplied with status information
+for those attributes that were invalid. */
+#define CIP_ERROR_EMBEDDED_SERVICE_ERROR 0x1E /*!< An embedded service resulted in an error. */
+#define CIP_ERROR_VENDOR_SPECIFIC_ERROR 0x1F /*!< A vendor specific error has been encountered. The Additional Code Field of
+the Error Response defines the particular error encountered. Use of this
+General Error Code should only be performed when none of the Error Codes
+presented in this table or within an Object Class definition accurately reflect
+the error. */
+#define CIP_ERROR_INVALID_PARAMETER 0x20 /*!< A parameter associated with the request was invalid. This code is used when a
+parameter does not meet the requirements of this specification and/or the
+requirements defined in an Application Object Specification. */
+#define CIP_ERROR_WRITEONCE_VALUE_OR_MEDIUM_ALREADY_WRITTEN 0x21 /*!< An attempt was made to write to a write-once medium (e.g. WORM drive,
+PROM) that has already been written, or to modify a value that cannot be changed once established. */
+#define CIP_ERROR_INVALID_REPLY_RECEIVED 0x22 /*!< An invalid reply is received (e.g. reply service code does not match the request
+service code, or reply message is shorter than the minimum expected reply
+size). This status code can serve for other causes of invalid replies. */
+/*23-24 Reserved by CIP for future extensions*/
+#define CIP_ERROR_KEY_FAILURE_IN_PATH 0x25 /*!< The Key Segment that was included as the first segment in the path does not
+match the destination module. The object specific status shall indicate which part of the key check failed. */
+#define CIP_ERROR_PATH_SIZE_INVALID 0x26 /*!< The size of the path which was sent with the Service Request is either not large
+enough to allow the Request to be routed to an object or too much routing data was included. */
+#define CIP_ERROR_UNEXPECTED_ATTRIBUTE_IN_LIST 0x27 /*!< An attempt was made to set an attribute that is not able to be set at this time. */
+#define CIP_ERROR_INVALID_MEMBER_ID 0x28 /*!< The Member ID specified in the request does not exist in the specified Class/Instance/Attribute */
+#define CIP_ERROR_MEMBER_NOT_SETTABLE 0x29 /*!< A request to modify a non-modifiable member was received */
+#define CIP_ERROR_GROUP2_ONLY_SERVER_GENERAL_FAILURE 0x2A /*!< This error code may only be reported by DeviceNet group 2 only servers with
+4K or less code space and only in place of Service not supported, Attribute
+not supported and Attribute not settable. */
+/*2B - CF Reserved by CIP for future extensions
+ D0 - FF Reserved for Object Class and service errors*/
+#endif /*CIPERROR_H_*/

+ 60 - 0
src/cip/cipethernetlink.c

@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <string.h>
+
+#include "cipethernetlink.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "ciperror.h"
+#include "endianconv.h"
+#include "opener_api.h"
+
+/* global private variables */
+S_CIP_EthernetLinkObject stEthernetLink;
+
+
+void configureMACAddress(const EIP_UINT8 *pa_acMACAddress){
+  memcpy(&stEthernetLink.PhysicalAddress, pa_acMACAddress,
+      sizeof(stEthernetLink.PhysicalAddress));
+
+}
+
+EIP_STATUS CIP_Ethernet_Link_Init()
+  {
+    S_CIP_Class *pstEthernetLinkClass;
+    S_CIP_Instance *pstEthernetLinkInstance;
+
+    /* set attributes to initial values */
+    stEthernetLink.InterfaceSpeed = 100;
+    stEthernetLink.InterfaceFlags = 3; /* full duplex active link, in future it should be checked if link is active */
+
+    if ((pstEthernetLinkClass = createCIPClass(CIP_ETHERNETLINK_CLASS_CODE, 0, // # class attributes
+        0xffffffff, // class getAttributeAll mask
+        0, // # class services
+        3, // # instance attributes
+        0xffffffff, // instance getAttributeAll mask
+        0, // # instance services
+        1, // # instances
+        "Ethernet link", 1)) != 0)
+      {
+
+        pstEthernetLinkInstance = getCIPInstance(pstEthernetLinkClass, 1);
+        insertAttribute(pstEthernetLinkInstance, 1, CIP_UDINT,
+            &stEthernetLink.InterfaceSpeed); // bind attributes to the instance
+        insertAttribute(pstEthernetLinkInstance, 2, CIP_DWORD,
+            &stEthernetLink.InterfaceFlags);
+        insertAttribute(pstEthernetLinkInstance, 3, CIP_6USINT,
+            &stEthernetLink.PhysicalAddress);
+      }
+    else
+      {
+        return EIP_ERROR;
+      }
+
+    return EIP_OK;
+  }

+ 29 - 0
src/cip/cipethernetlink.h

@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPETHERNETLINK_H_
+#define CIPETHERNETLINK_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+#define CIP_ETHERNETLINK_CLASS_CODE 0xF6
+
+typedef struct
+  {
+    EIP_UINT32 InterfaceSpeed;
+    EIP_UINT32 InterfaceFlags;
+    EIP_UINT8 PhysicalAddress[6];
+  } S_CIP_EthernetLinkObject;
+
+/* public functions */
+/*!Initialize the Ethernet Link Objects data
+ */
+EIP_STATUS CIP_Ethernet_Link_Init(void);
+
+
+#endif /*CIPETHERNETLINK_H_*/

+ 108 - 0
src/cip/cipidentity.c

@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <string.h>
+#include "opener_user_conf.h"
+#include "cipidentity.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "ciperror.h"
+#include "endianconv.h"
+#include "opener_api.h"
+
+/* attributes in CIP Identity Object */
+
+EIP_UINT16 VendorID = OPENER_DEVICE_VENDOR_ID; /* #1 */
+EIP_UINT16 DeviceType = OPENER_DEVICE_TYPE; /* #2 */
+EIP_UINT16 ProductCode = OPENER_DEVICE_PRODUCT_CODE; /* #3 */
+S_CIP_Revision Revison =
+  { OPENER_DEVICE_MAJOR_REVISION, OPENER_DEVICE_MINOR_REVISION }; /* #4 */
+EIP_UINT16 ID_Status = 0; /* #5 */// TODO find out what this is and how it gets set
+EIP_UINT32 SerialNumber = OPENER_DEVICE_SERIAL; /* #6 */// TODO made this variable per device
+S_CIP_Short_String ProductName =
+  { sizeof(OPENER_DEVICE_NAME) - 1, OPENER_DEVICE_NAME }; /* #7 */
+
+static EIP_STATUS Reset(S_CIP_Instance *pa_pstInstance, // pointer to instance
+    S_CIP_MR_Request *pa_stMRRequest, // pointer to message router request
+    S_CIP_MR_Response *pa_stMRResponse, // pointer to message router response
+    EIP_UINT8 *pa_anMsg)
+  {
+    (void)pa_pstInstance;
+    (void)pa_anMsg;
+
+    pa_stMRResponse->ReplyService = (0x80 | pa_stMRRequest->Service);
+    pa_stMRResponse->SizeofAdditionalStatus = 0;
+    pa_stMRResponse->GeneralStatus = CIP_ERROR_SUCCESS;
+
+    if (pa_stMRRequest->DataLength == 1)
+      {
+        switch (pa_stMRRequest->Data[0])
+          {
+        case 0: //emulate device reset
+          if (EIP_ERROR == IApp_ResetDevice())
+            {
+              pa_stMRResponse->GeneralStatus = CIP_ERROR_INVALID_PARAMETER;
+            }
+          break;
+
+        case 1: //reset to device settings
+          if (EIP_ERROR == IApp_ResetDeviceToInitialConfiguration())
+            {
+              pa_stMRResponse->GeneralStatus = CIP_ERROR_INVALID_PARAMETER;
+            }
+          break;
+
+        default:
+          pa_stMRResponse->GeneralStatus = CIP_ERROR_INVALID_PARAMETER;
+          break;
+          }
+      }
+    else
+      {
+        //The same behavior as if the data value given would be 0
+        //emulate device reset
+        if (EIP_ERROR == IApp_ResetDevice())
+          {
+            pa_stMRResponse->GeneralStatus = CIP_ERROR_INVALID_PARAMETER;
+          }
+      }
+    pa_stMRResponse->DataLength = 0;
+    return EIP_OK;
+  }
+
+EIP_STATUS CIP_Identity_Init()
+  {
+    S_CIP_Class *pClass;
+    S_CIP_Instance *pInstance;
+
+    pClass = createCIPClass(CIP_IDENTITY_CLASS_CODE, 0, // # of non-default class attributes
+        MASK4(1, 2, 6, 7), // class getAttributeAll mask		CIP spec 5-2.3.2
+        0, // # of class services
+        7, // # of instance attributes
+        MASK7(1, 2, 3, 4, 5, 6, 7), // instance getAttributeAll mask	CIP spec 5-2.3.2
+        1, // # of instance services
+        1, // # of instances
+        "identity", // class name (for debug)
+        1); // class revision
+
+    if (pClass == 0)
+      return EIP_ERROR;
+
+    pInstance = getCIPInstance(pClass, 1);
+
+    insertAttribute(pInstance, 1, CIP_UINT, &VendorID);
+    insertAttribute(pInstance, 2, CIP_UINT, &DeviceType);
+    insertAttribute(pInstance, 3, CIP_UINT, &ProductCode);
+    insertAttribute(pInstance, 4, CIP_USINT_USINT, &Revison);
+    insertAttribute(pInstance, 5, CIP_WORD, &ID_Status);
+    insertAttribute(pInstance, 6, CIP_UDINT, &SerialNumber);
+    insertAttribute(pInstance, 7, CIP_SHORT_STRING, &ProductName);
+
+    insertService(pClass, CIP_RESET, &Reset, "Reset");
+
+    return EIP_OK;
+  }

+ 28 - 0
src/cip/cipidentity.h

@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPIDENTITY_H_
+#define CIPIDENTITY_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+#define CIP_IDENTITY_CLASS_CODE 0x01
+
+/* global public variables */
+
+/* public functions */
+EIP_STATUS CIP_Identity_Init(void);
+
+typedef struct
+  {
+    EIP_UINT8 MajorRevision;
+    EIP_UINT8 MinorRevision;
+  } S_CIP_Revision;
+
+
+#endif /*CIPIDENTITY_H_*/

+ 305 - 0
src/cip/cipmessagerouter.c

@@ -0,0 +1,305 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <assert.h>
+
+#include "opener_api.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "endianconv.h"
+#include "ciperror.h"
+
+S_CIP_MR_Request gMRRequest;
+S_CIP_MR_Response gMRResponse;
+
+/*! a class registry list node
+ * a linked list of this  object is the registry of classes known to the message router
+ * for small devices with very limited memory it could make sense to cange this list into an
+ * array with a given max size for removing the need for having to dynamically allocate 
+ * memory. The size of the array could be a paramter in the platform config file.
+ */
+typedef struct CIP_MR_Object
+  {
+    struct CIP_MR_Object *next; // link
+    S_CIP_Class *pt2Class; // object
+  } S_CIP_MR_Object;
+
+/* pointer to first registered object in MessageRouter*/
+S_CIP_MR_Object *g_pt2firstObject = 0;
+  
+/*! Register an Class to the message router
+ *  @param pa_pt2Class     pointer to a class object to be registered.
+ *  @return status      0 .. success
+ *                     -1 .. error no memory available to register more objects
+ */
+EIP_STATUS registerClass(S_CIP_Class * pa_pt2Class);     
+  
+/*!  Create MRRequest structure out of the recieved data.
+ * 
+ * Parses the UCMM header consisting of: service, IOI size, IOI, data into a request structure
+ * @param pa_pnData    pointer to the message data recieved
+ * @param pa_nLength   number of bytes in the message
+ * @param pa_pstMRReqdata   pointer to structure of MRRequest data item.
+ * @return status  0 .. success
+ *                 -1 .. error
+ */
+EIP_BYTE createMRRequeststructure(EIP_UINT8 * pa_pnData, EIP_INT16 pa_nLength, S_CIP_MR_Request * pa_pstMRReqdata);
+
+
+
+EIP_STATUS CIP_MessageRouter_Init()
+  {
+    // init the list of available objects 
+    g_pt2firstObject = 0;
+
+    S_CIP_Class *pstMessageRouter;
+
+    pstMessageRouter = createCIPClass(CIP_MESSAGE_ROUTER_CLASS_CODE, // class ID
+        0, // # of class attributes
+        0xffffffff, // class getAttributeAll mask
+        0, // # of class services
+        0, // # of instance attributes
+        0xffffffff, // instance getAttributeAll mask
+        0, // # of instance services
+        1, // # of instances
+        "message router", // class name
+        1); // revision
+    if (pstMessageRouter==0)
+      return EIP_ERROR;
+
+    /* reserved for future use -> set to zero */
+    gMRResponse.Reserved = 0;
+    return EIP_OK;
+  }
+
+/*! get the registered MessageRouter object corresponding to ClassID.
+ *  given a class ID, return a pointer to the registration node for that object
+ *  @param pa_nClassID      ClassCode to be searched for.
+ *  @return pointer to registered MR object
+ *      0 .. Class not registered
+ */
+S_CIP_MR_Object *getRegisteredObject(EIP_UINT32 pa_nClassID)
+  {
+    S_CIP_MR_Object *p = g_pt2firstObject; // get pointer to head of class registration list
+
+    while (p) // for each entry in list
+      {
+        assert(p->pt2Class!=0);
+        if (p->pt2Class->nClassID == pa_nClassID)
+          return p; // return registration node if it matches class ID
+        p = p->next;
+      }
+    return 0;
+  }
+
+S_CIP_Class *getCIPClass(EIP_UINT32 pa_nClassID)
+  {
+    S_CIP_MR_Object *p = getRegisteredObject(pa_nClassID);
+
+    if (p)
+      return p->pt2Class;
+    else
+      return 0;
+  }
+
+S_CIP_Instance *getCIPInstance(S_CIP_Class * pa_pstClass,
+    EIP_UINT16 pa_nInstanceNr)
+  {
+    S_CIP_Instance *p; // pointer to linked list of instances from the class object
+
+    if (pa_nInstanceNr==0)
+      return (S_CIP_Instance *)pa_pstClass; // if the instance number is zero, return the class object itself
+
+    for (p = pa_pstClass->pstInstances; p; p = p->pstNext) // follow the list
+      {
+        if (p->nInstanceNr == pa_nInstanceNr)
+          return p; // if the number matches, return the instance
+      }
+
+    return 0;
+  }
+
+EIP_STATUS registerClass(S_CIP_Class * pa_pt2Class)     
+  {
+    S_CIP_MR_Object **p = &g_pt2firstObject;
+
+    while (*p)
+      p = &(*p)->next; // follow the list until p points to an empty link (list end)
+
+    *p = IApp_CipCalloc(1, sizeof(S_CIP_MR_Object)); // create a new node at the end of the list
+    if (*p == 0)
+      return EIP_ERROR; // check for memory error
+
+    (*p)->pt2Class = pa_pt2Class; // fill in the new node
+
+    return EIP_OK;
+  }
+
+int notifyMR(EIP_UINT8 * pa_pnData, int pa_nDataLength)
+  {
+    EIP_BYTE nStatus;
+    if (EIP_DEBUG>=EIP_VERBOSE)
+      printf("notifyMR: routing unconnected message\n");
+    if (CIP_ERROR_SUCCESS != (nStatus = createMRRequeststructure(pa_pnData,
+            pa_nDataLength, &gMRRequest)))
+      { // error from create MR structure
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notifyMR: error from createMRRequeststructure\n");
+        gMRResponse.GeneralStatus = nStatus;
+        gMRResponse.SizeofAdditionalStatus = 0;
+        gMRResponse.Reserved = 0;
+        gMRResponse.DataLength = 0;
+        gMRResponse.ReplyService = (0x80 | gMRRequest.Service);
+        return 0;
+      }
+    else
+      {
+        // forward request to appropriate Object if it is registered
+        S_CIP_MR_Object *pt2regObject;
+
+        pt2regObject = getRegisteredObject(gMRRequest.RequestPath.ClassID);
+        if (pt2regObject == 0)
+          {
+            if (EIP_DEBUG>=EIP_TERSE)
+              printf(
+                  "notifyMR: sending CIP_ERROR_OBJECT_DOES_NOT_EXIST reply, class id 0x%x is not registered\n",
+                  (unsigned)gMRRequest.RequestPath.ClassID);
+            gMRResponse.GeneralStatus = CIP_ERROR_PATH_DESTINATION_UNKNOWN; //according to the test tool this should be the correct error flag instead of CIP_ERROR_OBJECT_DOES_NOT_EXIST;
+            gMRResponse.SizeofAdditionalStatus = 0;
+            gMRResponse.Reserved = 0;
+            gMRResponse.DataLength = 0;
+            gMRResponse.ReplyService = (0x80 | gMRRequest.Service);
+            return 0;
+          }
+        else
+          {
+            // call notify function from Object with ClassID (gMRRequest.RequestPath.ClassID)
+            // object will or will not make an reply into gMRResponse
+            EIP_STATUS status;
+
+            gMRResponse.Reserved = 0;
+            assert(pt2regObject->pt2Class);
+            if (EIP_DEBUG>=EIP_VERBOSE)
+              printf("notifyMR: calling notify function of class '%s'\n",
+                  pt2regObject->pt2Class->acName);
+            status = notifyClass(pt2regObject->pt2Class, &gMRRequest,
+                &gMRResponse);
+
+            if (EIP_DEBUG<EIP_VERBOSE)
+              {
+              }
+            else if (status == -1)
+              printf(
+                  "notifyMR: notify function of class '%s' returned an error\n",
+                  pt2regObject->pt2Class->acName);
+            else if (status == 0)
+              printf(
+                  "notifyMR: notify function of class '%s' returned no reply\n",
+                  pt2regObject->pt2Class->acName);
+            else
+              printf(
+                  "notifyMR: notify function of class '%s' returned a reply\n",
+                  pt2regObject->pt2Class->acName);
+            return status;
+          }
+      }
+  }
+
+EIP_BYTE createMRRequeststructure(EIP_UINT8 * pa_pnData, EIP_INT16 pa_nLength, S_CIP_MR_Request * pa_pstMRReqdata)
+  {
+    int i;
+
+    pa_pstMRReqdata->Service = *pa_pnData;
+    pa_pnData++;
+    pa_pstMRReqdata->RequestPath.PathSize = *pa_pnData;
+    pa_pnData++;
+    /* copy path to structure, in version 0.1 only 8 bit for Class,Instance and Attribut, need to be replaced with function */
+    pa_pstMRReqdata->RequestPath.ClassID = 0;
+    pa_pstMRReqdata->RequestPath.InstanceNr = 0;
+    pa_pstMRReqdata->RequestPath.AttributNr = 0;
+
+    for(i = 0; i < pa_pstMRReqdata->RequestPath.PathSize; i++)
+      {
+        if(0xE0 == ((*pa_pnData) & 0xE0))
+          {
+            //Invalid segment type
+            return CIP_ERROR_PATH_SEGMENT_ERROR;
+          }
+        switch (*pa_pnData)
+          {
+            case 0x20: /* classID */
+            pa_pstMRReqdata->RequestPath.ClassID = *(EIP_UINT8 *) (pa_pnData + 1);
+            pa_pnData += 2;
+            break;
+
+            case 0x21: /*classID 16Bit */
+            pa_pnData += 2;
+            pa_pstMRReqdata->RequestPath.ClassID = ltohs(&(pa_pnData));
+            i++;
+            break;
+
+            case 0x24: /* InstanceNr */
+            pa_pstMRReqdata->RequestPath.InstanceNr = *(EIP_UINT8 *) (pa_pnData + 1);
+            pa_pnData += 2;
+            break;
+
+            case 0x25: /* InstanceNr 16Bit */
+            pa_pnData += 2;
+            pa_pstMRReqdata->RequestPath.InstanceNr = ltohs(&(pa_pnData));
+            i++;
+            break;
+
+            case 0x30: /* AttributeNr */
+            pa_pstMRReqdata->RequestPath.AttributNr = *(EIP_UINT8 *) (pa_pnData + 1);
+            pa_pnData += 2;
+            break;
+
+            case 0x31: /* AttributeNr 16Bit */
+            pa_pnData += 2;
+            pa_pstMRReqdata->RequestPath.AttributNr = ltohs(&(pa_pnData));
+            i++;
+
+            default:
+//              if(0 == ((*pa_pnData) & 0xE0))
+//                {
+//                  // we recieved a port segment we will ignor this currently as we have no bridging devices
+//                  // the test is sending us port semgents normaly we should not get them
+//                  printf("Warning: port segment recieved will be ignored!\n");
+//                  if(0x10 == ((*pa_pnData) & 0x10))
+//                    {
+//                      // we have an extended port segemnt pa_pnData + 1 will have the size
+//                      buf = *(EIP_UINT8 *) (pa_pnData + 1);
+//                      i += buf / 2;
+//                      pa_pnData += buf;
+//                      if(buf & 0x01)
+//                      {
+//                        //we have an odd numer so there is an padd byte;
+//                        ++i;
+//                        ++pa_pnData;
+//                      }
+//                      
+//                    }
+//                  pa_pnData += 2;                 
+//                }
+//              else
+//                {
+                  printf("wrong path requested\n");
+                  return CIP_ERROR_PATH_SEGMENT_ERROR;
+//                }
+              break;
+          }
+      }
+
+    pa_pstMRReqdata->Data = pa_pnData;
+    pa_pstMRReqdata->DataLength = pa_nLength - ((pa_pstMRReqdata->RequestPath.PathSize)
+        * 2 + 2);
+    if (pa_pstMRReqdata->DataLength < 0)
+    return CIP_ERROR_PATH_SIZE_INVALID;
+    else
+    return CIP_ERROR_SUCCESS;
+  }

+ 51 - 0
src/cip/cipmessagerouter.h

@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPMESSAGEROUTER_H_
+#define CIPMESSAGEROUTER_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+#define CIP_MESSAGE_ROUTER_CLASS_CODE 0x02
+
+/*! Structure for storing the Response generated by an explict message.
+ * 
+ *  This buffer will be used for storing the result. The response message will be generated
+ *  by assembleLinearMsg. 
+ */
+extern S_CIP_MR_Response gMRResponse;
+
+/* public functions */
+
+/*!Initialize the data structures of the message router
+ */ 
+EIP_STATUS CIP_MessageRouter_Init(void);
+
+/*! Notify the MessageRouter that an explicit message (connected or unnconnected)
+ *  has been recieved. This function will be called from the encapsulation layer.
+ *  The CPF strcuture is allready parsed an cann be accessed via the global variable: 
+ *  g_stCPFDataItem.
+ *  @param pa_pnData pointer to the data buffer of the message directly at the beginning of the CIP part.
+ *  @param pa_nDataLength number of bytes in the data buffer
+ *  @return  EIP_ERROR on fault
+ *           EIP_OK on success           
+ */ 
+int notifyMR(EIP_UINT8 *pa_pnData, int pa_nDataLength); 
+
+/*! Register a class at the message router.
+ *  In order that the message router can deliver
+ *  explicit messages each class has to register.
+ *  Will be automaticall done when invoking create
+ *  createCIPClass.
+ *  @param pa_pt2Object cip class to be registered
+ *  @return EIP_OK on success
+ */ 
+EIP_STATUS registerClass(S_CIP_Class *pa_pt2Object);
+
+
+#endif /*CIPMESSAGEROUTER_H_*/

+ 162 - 0
src/cip/ciptcpipinterface.c

@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "opener_user_conf.h"
+#include "ciptcpipinterface.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "ciperror.h"
+#include "endianconv.h"
+#include "cipethernetlink.h"
+#include "opener_api.h"
+
+EIP_UINT32 TCP_Status = 0;                   /* #1 */// This is a TCPIP object attribute. For now it is always zero.
+EIP_UINT32 Configuration_Capability = 0x04;  /* #2 */// This is a default value meaning that it is a DHCP client see 5-3.2.2.2 EIP spec
+EIP_UINT32 Configuration_Control = 0;        /* #3 */// This is a TCPIP object attribute. For now it is always zero and is not used for anything.
+S_CIP_EPATH Physical_Link_Object = /* #4 */
+  { 2, // EIP_UINT8 PathSize
+      CIP_ETHERNETLINK_CLASS_CODE, // EIP_UINT32 ClassID
+      1, // EIP_UINT32 InstanceNr
+      0 // EIP_UINT32 AttributNr (not used as this is the EPATH the EthernetLink object)
+    }; // it was not initilized in the original code
+
+
+S_CIP_TCPIPNetworkInterfaceConfiguration Interface_Configuration = /* #5 */
+  { 0, /* default IP address */
+  0, /* NetworkMask */
+  0, /* Gateway */
+  0, /* NameServer */
+  0, /* NameServer2 */
+    { /* DomainName */
+    0, 0, 
+}
+};
+
+S_CIP_String Hostname = /* #6 */
+  { 0, 0 };
+
+//!Multicast address to be used for I/O connections
+EIP_UINT32 g_nMultiCastAddress;
+
+
+EIP_STATUS configureNetworkInterface(const char *pa_acIpAdress,
+    const char *pa_acSubNetMask, const char *pa_acGateway)
+  {
+    Interface_Configuration.IPAddress = inet_addr(pa_acIpAdress);
+    Interface_Configuration.NetworkMask = inet_addr(pa_acSubNetMask);
+    Interface_Configuration.Gateway = inet_addr(pa_acGateway);
+
+    // calculate the CIP multicast address. The multicast address is calculated, not input
+    unsigned nHostId = ntohl(Interface_Configuration.IPAddress)
+        & ~ntohl(Interface_Configuration.NetworkMask); // see CIP spec 3-5.3 for multicast address algorithm
+    nHostId -= 1;
+    nHostId &= 0x3ff;
+    g_nMultiCastAddress = htonl(ntohl(inet_addr("239.192.1.0")) | nHostId << 5);
+
+    return EIP_OK;
+  }
+
+void configureDomainName(const char *pa_acDomainName)
+  {
+    if (0 != Interface_Configuration.DomainName.String)
+      {
+        free(Interface_Configuration.DomainName.String);
+      }
+    Interface_Configuration.DomainName.Length = strlen(pa_acDomainName);
+    if (Interface_Configuration.DomainName.Length)
+      {
+        Interface_Configuration.DomainName.String = calloc(
+            Interface_Configuration.DomainName.Length + 1, sizeof(EIP_INT8));
+        strcpy(Interface_Configuration.DomainName.String, pa_acDomainName);
+      }
+    else
+      {
+        Interface_Configuration.DomainName.String = 0;
+      }
+  }
+
+void configureHostName(const char *pa_acHostName)
+  {
+    if (0 != Hostname.String)
+      {
+        free(Hostname.String);
+      }
+    Hostname.Length = strlen(pa_acHostName);
+    if (Hostname.Length)
+      {
+        Hostname.String = calloc(Hostname.Length + 1, sizeof(EIP_INT8));
+        strcpy(Hostname.String, pa_acHostName);
+      }
+    else
+      {
+        Hostname.String = 0;
+      }
+  }
+
+EIP_STATUS setAttributeSingleTCP(S_CIP_Instance *pa_pstObjectInstance, // pointer to instance
+    S_CIP_MR_Request *pa_pstMRRequest, // pointer to message router request 
+    S_CIP_MR_Response *pa_pstMRResponse, // pointer to message router response
+    EIP_UINT8 *pa_msg)
+  {
+    pa_pstObjectInstance = 0; /*surrpress compiler warning */
+    pa_msg = 0;  /*surrpress compiler warning */  
+    
+    if((1 <= pa_pstMRRequest->RequestPath.AttributNr) &&
+        (pa_pstMRRequest->RequestPath.AttributNr <= 6))
+      {
+          /* it is an attribute we currently support, however no attribute is setable */
+          //TODO if you like to have a device that can be configured via this CIP object add your code here */
+        pa_pstMRResponse->GeneralStatus = CIP_ERROR_ATTRIBUTE_NOT_SETTABLE;
+        
+      }
+    else
+      {
+        /* we don't have this attribute */
+        pa_pstMRResponse->GeneralStatus = CIP_ERROR_ATTRIBUTE_NOT_SUPPORTED;
+      }
+    
+    pa_pstMRResponse->SizeofAdditionalStatus = 0;
+    pa_pstMRResponse->DataLength = 0;
+    pa_pstMRResponse->ReplyService = (0x80 | pa_pstMRRequest->Service);
+    return EIP_OK_SEND;
+  }
+
+EIP_STATUS CIP_TCPIP_Interface_Init()
+  {
+    S_CIP_Class *p_stTCPIPClass;
+    S_CIP_Instance *pstInstance;
+
+    if ((p_stTCPIPClass = createCIPClass(CIP_TCPIPINTERFACE_CLASS_CODE, 0, // # class attributes
+        0xffffffff, // class getAttributeAll mask
+        0, // # class services
+        6, // # instance attributes
+        0xffffffff, // instance getAttributeAll mask
+        1, // # instance services
+        1, // # instances
+        "TCP/IP interface", 1)) == 0)
+      return EIP_ERROR;
+
+    pstInstance = getCIPInstance(p_stTCPIPClass, 1); // bind attributes to the instance #1 that was created above
+
+    insertAttribute(pstInstance, 1, CIP_DWORD, (void *)&TCP_Status);
+    insertAttribute(pstInstance, 2, CIP_DWORD,
+        (void *)&Configuration_Capability);
+    insertAttribute(pstInstance, 3, CIP_DWORD, (void *)&Configuration_Control);
+    insertAttribute(pstInstance, 4, CIP_EPATH, &Physical_Link_Object);
+    insertAttribute(pstInstance, 5, CIP_UDINT_UDINT_UDINT_UDINT_UDINT_STRING,
+        &Interface_Configuration);
+    insertAttribute(pstInstance, 6, CIP_STRING, (void *)&Hostname);
+
+    insertService(p_stTCPIPClass, CIP_SET_ATTRIBUTE_SINGLE, &setAttributeSingleTCP, "SetAttributeSingle");
+    
+    return EIP_OK;
+  }
+

+ 24 - 0
src/cip/ciptcpipinterface.h

@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPTCPIPINTERFACE_H_
+#define CIPTCPIPINTERFACE_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+#define CIP_TCPIPINTERFACE_CLASS_CODE 0xF5
+
+/* global public variables */
+
+/* public functions */
+/*!Initializing the data structures of the TCPIP interface object 
+ */ 
+EIP_STATUS CIP_TCPIP_Interface_Init(void);
+
+
+#endif /*CIPTCPIPINTERFACE_H_*/

+ 259 - 0
src/cip/ciptypes.h

@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIPTYPES_H_
+#define CIPTYPES_H_
+
+#include "typedefs.h"
+
+// TODO -- find some portable way of defining all these with enums rather than #defines so that the names rather than hex number are displayed in the debugger
+#ifdef __GNUC__
+typedef enum
+  {
+    SEG_PORT = 0x00,
+    SEG_EXTPORT = 0x10,
+    SEG_CLASS = 0x20,
+    SEG_INSTANCE = 0x24,
+    SEG_ATTRIBUTE = 0x30,
+    SEG_NETWORK = 0x40,
+    SEG_PACKED_SIZE = ENUM_INT8
+  } PACKED SEG_TYPE;
+#else
+#define SEG_PORT  0x00
+#define SEG_EXTPORT 0x10
+#define SEG_CLASS 0x20
+#define SEG_INSTANCE  0x24
+#define SEG_ATTRIBUTE 0x30
+#define SEG_NETWORK 0x40
+#endif
+
+/* definition of CIP basic data types */
+#define CIP_BOOL 			0xC1
+#define CIP_SINT 			0xC2
+#define CIP_INT 			0xC3
+#define CIP_DINT 			0xC4
+#define CIP_LINT 			0xC5
+#define CIP_USINT 			0xC6
+#define CIP_UINT 			0xC7
+#define CIP_UDINT 			0xC8
+#define CIP_ULINT 			0xC9
+#define CIP_REAL 			0xCA
+#define CIP_LREAL 			0xCB
+#define CIP_STIME 			0xCC
+#define CIP_DATE 			0xCD
+#define CIP_TIME_OF_DAY 		0xCE
+#define CIP_DATE_AND_TIME 		0xCF
+#define CIP_STRING 			0xD0
+#define CIP_BYTE 			0xD1
+#define CIP_WORD 			0xD2
+#define CIP_DWORD 			0xD3
+#define CIP_LWORD 			0xD4
+#define CIP_STRING2 			0xD5
+#define CIP_FTIME 			0xD6
+#define CIP_LTIME 			0xD7
+#define CIP_ITIME 			0xD8
+#define CIP_STRINGN 			0xD9
+#define CIP_SHORT_STRING 		0xDA
+#define CIP_TIME 			0xDB
+#define CIP_EPATH 			0xDC
+#define CIP_ENGUNIT 			0xDD
+
+/* definition of some CIP structs */
+/* need to be validated in IEC 1131-3 subclause 2.3.3 */
+#define CIP_USINT_USINT 		0xA0
+#define CIP_UDINT_UDINT_UDINT_UDINT_UDINT_STRING 0xA1
+#define CIP_6USINT			0xA2 /* for MAC Address*/
+#define CIP_MEMBER_LIST			0xA3
+#define CIP_BYTE_ARRAY			0xA4
+
+#define INTERNAL_UINT16_6		0xf0				// bogus hack, for port class attribute 9, TODO figure out the right way to handle it
+/* definition of CIP service codes */
+#define CIP_GET_ATTRIBUTE_SINGLE	0x0E
+#define CIP_SET_ATTRIBUTE_SINGLE	0x10
+#define CIP_RESET			            0x05
+#define CIP_CREATE                0x08  
+#define CIP_GET_ATTRIBUTE_ALL		  0x01
+#define CIP_FORWARD_OPEN		      0x54
+#define CIP_FORWARD_CLOSE		      0x4E
+#define CIP_UNCONNECTED_SEND		  0x52
+#define CIP_GET_CONNECTION_OWNER	0x5A
+
+/* typedefinition to general CIP structure */
+typedef struct
+  {
+    EIP_UINT8 AttributNr;
+    EIP_UINT8 CIP_Type;
+    void *ptostructure;
+  } S_CIP_general_struct;
+
+typedef struct
+  {
+    EIP_UINT16 len;
+    EIP_BYTE *Data;
+  } S_CIP_Byte_Array;
+
+typedef struct
+  {
+    EIP_UINT8 Length;
+    EIP_INT8 *String;
+  } S_CIP_Short_String;
+
+typedef struct
+  {
+    EIP_INT16 Length;
+    EIP_INT8 *String;
+  } S_CIP_String;
+
+typedef struct
+  {
+    EIP_UINT8 PathSize;
+    EIP_UINT32 ClassID; /* support up to 32 bit path*/
+    EIP_UINT32 InstanceNr;
+    EIP_UINT32 AttributNr;
+  } S_CIP_EPATH;
+
+typedef struct
+  {
+    EIP_UINT8 PathSize;
+    EIP_UINT32 ClassID;
+    EIP_UINT32 ConnectionPoint[3];
+    EIP_UINT8 DataSegment;
+    EIP_UINT8 *SegmentData;
+  } S_CIP_ConnectionPath;
+
+typedef struct
+  {
+    EIP_UINT16 VendorID;
+    EIP_UINT16 DeviceType;
+    EIP_UINT16 ProductCode;
+    EIP_BYTE MajorRevision;
+    EIP_UINT8 MinorRevision;
+  } S_CIP_KeyData;
+
+typedef struct
+  {
+    EIP_UINT8 SegmentType;
+    EIP_UINT8 KeyFormat;
+    S_CIP_KeyData KeyData;
+  } S_CIP_ElectronicKey;
+
+typedef struct
+  {
+    EIP_UINT8 Service;
+    S_CIP_EPATH RequestPath;
+    EIP_INT16 DataLength;
+    EIP_UINT8 *Data;
+  } S_CIP_MR_Request;
+
+#define MAX_SIZE_OF_ADD_STATUS 2 /* for now we support extended status codes up to 2 16bit values 
+									there is mostly only one 16bit value used */
+typedef struct
+  {
+    EIP_UINT8 ReplyService;
+    EIP_UINT8 Reserved;
+    EIP_UINT8 GeneralStatus;
+    EIP_UINT8 SizeofAdditionalStatus;
+    EIP_UINT16 AdditionalStatus[MAX_SIZE_OF_ADD_STATUS];
+    EIP_INT16 DataLength;
+    EIP_UINT8 *Data;
+  } S_CIP_MR_Response;
+
+typedef struct
+  {
+    EIP_UINT8 CIP_AttributNr;
+    EIP_UINT8 CIP_Type;
+    void *pt2data;
+  } S_CIP_attribute_struct;
+
+/* type definition of CIP service sructure */
+
+// instances are stored in a linked list
+typedef struct CIP_Instance
+  {
+    EIP_UINT32 nInstanceNr; // this instance's number (unique within the class)
+    S_CIP_attribute_struct *pstAttributes; // pointer to an array of attributes which is unique to this instance
+    struct CIP_Class *pstClass; // class the instance belongs to
+    struct CIP_Instance *pstNext; // next instance, all instances of a class live in a linked list
+  } S_CIP_Instance;
+
+typedef struct CIP_Class
+  { // Class is a subclass of Instance: the following group of fields must match CIP_Instance
+    EIP_UINT32 nInstanceNr; // this instance's number (unique within the class)
+    S_CIP_attribute_struct *pstAttributes; // pointer to an array of attributes which is unique to this instance
+    struct CIP_Class *pstClass; // class the instance belongs to
+    struct CIP_Instance *pstNext; // next instance, all instances of a class live in a linked list
+
+    // the rest of theswe are specific to the Class class only.
+    EIP_UINT32 nClassID; // class ID
+    EIP_UINT16 nRevision; // class revision
+    EIP_UINT16 nNr_of_Instances; // number of instances in the class (not including instance 0)
+    EIP_UINT16 nNr_of_Attributes; // number of attributes of each instance
+    EIP_UINT16 nMaxAttribute; // highest defined attribute number (attribute numbers are not necessarily consecutive)
+    EIP_UINT32 nGetAttrAllMask; // mask indicating which attributes are returned by getAttributeAll
+    EIP_UINT16 nNr_of_Services; // number of services supported
+    S_CIP_Instance *pstInstances; // pointer to the list of instances
+    struct CIP_service_struct *pstServices; // pointer to the array of services
+    char *acName; // class name
+  } S_CIP_Class;
+
+// a metaClass is a class that holds the class attributes and services
+
+
+typedef EIP_STATUS (*TCIPServiceFunc)(S_CIP_Instance *pa_pstInstance,
+    S_CIP_MR_Request *pa_MRRequest, S_CIP_MR_Response *pa_MRResponse, EIP_UINT8 *pa_msg);
+
+// service descriptor. These are stored in an array
+typedef struct CIP_service_struct
+  {
+    EIP_UINT8 CIP_ServiceNr; // service number
+    TCIPServiceFunc m_ptfuncService; // pointer to a function call
+    char *name; // name of the service
+  } S_CIP_service_struct;
+
+typedef struct
+  {
+    EIP_UINT32 IPAddress;
+    EIP_UINT32 NetworkMask;
+    EIP_UINT32 Gateway;
+    EIP_UINT32 NameServer;
+    EIP_UINT32 NameServer2;
+    S_CIP_String DomainName;
+  } S_CIP_TCPIPNetworkInterfaceConfiguration;
+
+typedef struct
+  {
+    EIP_UINT8 PathSize;
+    EIP_UINT32 Port; /* support up to 32 bit path*/
+    EIP_UINT32 Address;
+  // TODO: something else ?
+  } S_CIP_RPATH;
+
+typedef struct CIP_UnconnectedSend_Param_Struct
+  {
+    EIP_BYTE Priority;
+    EIP_UINT8 Timeout_Ticks;
+    EIP_UINT16 Message_Request_Size;
+    S_CIP_MR_Request Message_Request;
+    S_CIP_MR_Response *Message_Response;
+    EIP_UINT8 Reserved;
+    S_CIP_RPATH Route_Path;
+    void *CPFdata;
+  } S_CIP_UnconnectedSend_Param_Struct;
+
+// these are used for creating the getAttributeAll masks
+// TODO there might be a way simplifying this using __VARARGS__ in #define
+
+#define MASK1(a)               (1<<(a))
+#define MASK2(a,b)             (1<<(a) | 1<<(b))
+#define MASK3(a,b,c)           (1<<(a) | 1<<(b) | 1<<(c))
+#define MASK4(a,b,c,d)         (1<<(a) | 1<<(b) | 1<<(c) | 1<<(d))
+#define MASK5(a,b,c,d,e)       (1<<(a) | 1<<(b) | 1<<(c) | 1<<(d) | 1<<(e))
+#define MASK6(a,b,c,d,e,f)     (1<<(a) | 1<<(b) | 1<<(c) | 1<<(d) | 1<<(e) | 1<<(f))
+#define MASK7(a,b,c,d,e,f,g)   (1<<(a) | 1<<(b) | 1<<(c) | 1<<(d) | 1<<(e) | 1<<(f) | 1<<(g))
+#define MASK8(a,b,c,d,e,f,g,h) (1<<(a) | 1<<(b) | 1<<(c) | 1<<(d) | 1<<(e) | 1<<(f) | 1<<(g) | 1<<(h))
+
+#endif /*CIPTYPES_H_*/

+ 331 - 0
src/enet_encap/cpf.c

@@ -0,0 +1,331 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <assert.h>
+
+#include "opener_api.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "endianconv.h"
+#include "ciperror.h"
+#include "cpf.h"
+#include "cipconnectionmanager.h"
+
+/* CPF global data items */
+S_CIP_CPF_Data g_stCPFDataItem;
+
+int notifyCPF(EIP_UINT8 * pa_nData, // message data
+    EIP_INT16 pa_nData_length, // data length
+    EIP_UINT8 * pa_replybuf) // reply buffer
+  {
+    EIP_STATUS res;
+
+    if ((createCPFstructure(pa_nData, pa_nData_length, &g_stCPFDataItem))
+        == EIP_ERROR)
+      {
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notifyMR: error from createCPFstructure\n");
+        return EIP_ERROR; // error from createCPFstructure
+      }
+
+    if (g_stCPFDataItem.stAddr_Item.TypeID == CIP_ITEM_ID_NULL) // check if NullAddressItem received, otherwise it is no unconnected message and should not be here
+      { // found null address item
+        if (g_stCPFDataItem.stDataI_Item.TypeID == CIP_ITEM_ID_UNCONNECTEDMESSAGE)
+          { // unconnected data item received
+            res = notifyMR(g_stCPFDataItem.stDataI_Item.Data,
+                g_stCPFDataItem.stDataI_Item.Length);
+            if (res == EIP_ERROR)
+              return EIP_ERROR;
+
+            return assembleLinearMsg(&gMRResponse, &g_stCPFDataItem, pa_replybuf);
+          }
+        // wrong data item detected
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notifyMR: got something besides the expected CIP_ITEM_ID_UNCONNECTEDMESSAGE\n");
+        return EIP_ERROR;
+      }
+
+    if (EIP_DEBUG>=EIP_TERSE)
+      printf("notifyMR: got something besides the expected CIP_ITEM_ID_NULL\n");
+    return EIP_ERROR;
+  }
+
+int notifyConnectedCPF(EIP_UINT8 * pa_nData, // message data
+    EIP_INT16 pa_nData_length, // data length
+    EIP_UINT8 * pa_replybuf) // reply buffer
+  {
+    EIP_STATUS res;
+    S_CIP_ConnectionObject *pstConnectionObject;
+
+    if ((createCPFstructure(pa_nData, pa_nData_length, &g_stCPFDataItem))
+        == EIP_ERROR)
+      {
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notifyMR: error from createCPFstructure\n");
+        return EIP_ERROR; // error from createCPFstructure
+      }
+
+    if (g_stCPFDataItem.stAddr_Item.TypeID == CIP_ITEM_ID_CONNECTIONBASED) // check if ConnectedAddressItem received, otherwise it is no connected message and should not be here
+      { // ConnectedAddressItem item       
+        pstConnectionObject = getConnectedObject(g_stCPFDataItem.stAddr_Item.Data.ConnectionIdentifier);
+        if (pstConnectionObject == 0)
+          return EIP_ERROR;
+        
+        /* reset the watchdogtimer */
+        pstConnectionObject->InnacitvityWatchdogTimer = (pstConnectionObject->O_to_T_RPI / 1000) << 2; 
+      
+        //TODO check connection id  and sequence count        
+        if (g_stCPFDataItem.stDataI_Item.TypeID == CIP_ITEM_ID_CONNECTIONTRANSPORTPACKET)
+          { // connected data item received
+            EIP_UINT8 *pnBuf = g_stCPFDataItem.stDataI_Item.Data;
+            g_stCPFDataItem.stAddr_Item.Data.SequenceNumber = (EIP_UINT32)ltohs(&pnBuf);              
+            res = notifyMR(pnBuf, g_stCPFDataItem.stDataI_Item.Length - 2);
+            if (res == -1)
+              return EIP_ERROR;
+
+            return assembleLinearMsg(&gMRResponse, &g_stCPFDataItem, pa_replybuf);
+          }
+        // wrong data item detected
+        if (EIP_DEBUG>=EIP_TERSE)
+          printf("notifyMR: got something besides the expected CIP_ITEM_ID_UNCONNECTEDMESSAGE\n");
+        return EIP_ERROR;
+      }
+
+    if (EIP_DEBUG>=EIP_TERSE)
+      printf("notifyMR: got something besides the expected CIP_ITEM_ID_NULL\n");
+    return EIP_ERROR;
+  }
+
+/*   INT16 createCPFstructure(INT8 *pa_Data, INT16 pa_DataLength, S_CIP_CPF_Data *pa_CPF_data)
+ *   create CPF structure out of pa_data.
+ *      pa_Data		pointer to data which need to be structured.
+ *      pa_DataLength	length of data in pa_Data.
+ *      pa_CPF_data	pointer to structure of CPF data item.
+ *  return status
+ * 		0 .. success
+ * 	       -1 .. error
+ */
+EIP_STATUS createCPFstructure(EIP_UINT8 * pa_Data, int pa_DataLength,
+    S_CIP_CPF_Data * pa_CPF_data)
+  {
+    int len_count, i, j;
+    
+    pa_CPF_data->AddrInfo[0].TypeID = 0;
+    pa_CPF_data->AddrInfo[1].TypeID = 0;
+
+    len_count = 0;
+    pa_CPF_data->ItemCount = ltohs(&pa_Data);
+    len_count += 2;
+    if (pa_CPF_data->ItemCount >= 1)
+      {
+        pa_CPF_data->stAddr_Item.TypeID = ltohs(&pa_Data);
+        pa_CPF_data->stAddr_Item.Length = ltohs(&pa_Data);
+        len_count += 4;
+        if (pa_CPF_data->stAddr_Item.Length >= 4)
+          {
+            pa_CPF_data->stAddr_Item.Data.ConnectionIdentifier
+                = ltohl(&pa_Data);
+            len_count += 4;
+          }
+        if (pa_CPF_data->stAddr_Item.Length == 8)
+          {
+            pa_CPF_data->stAddr_Item.Data.SequenceNumber = ltohl(&pa_Data);
+            len_count += 4;
+          }
+      }
+    if (pa_CPF_data->ItemCount >= 2)
+      {
+        pa_CPF_data->stDataI_Item.TypeID = ltohs(&pa_Data);
+        pa_CPF_data->stDataI_Item.Length = ltohs(&pa_Data);
+        pa_CPF_data->stDataI_Item.Data = pa_Data;
+        pa_Data += pa_CPF_data->stDataI_Item.Length;
+        len_count += (4 + pa_CPF_data->stDataI_Item.Length);
+      }
+    for (j = 0; j < (pa_CPF_data->ItemCount - 2); j++) // TODO there needs to be a limit check here???
+      {
+        pa_CPF_data->AddrInfo[j].TypeID = ltohs(&pa_Data);
+        if ((pa_CPF_data->AddrInfo[j].TypeID == CIP_ITEM_ID_SOCKADDRINFO_O_TO_T)
+            || (pa_CPF_data->AddrInfo[j].TypeID
+                == CIP_ITEM_ID_SOCKADDRINFO_T_TO_O))
+          {
+            len_count += 16;
+            pa_CPF_data->AddrInfo[j].Length = ltohs(&pa_Data);
+            pa_CPF_data->AddrInfo[j].nsin_family = ltohs(&pa_Data);
+            pa_CPF_data->AddrInfo[j].nsin_port = ltohs(&pa_Data);
+            pa_CPF_data->AddrInfo[j].nsin_addr = ltohl(&pa_Data);
+            for (i = 0; i < 8; i++)
+              {
+                pa_CPF_data->AddrInfo[j].nasin_zero[i] = *pa_Data;
+                pa_Data++;
+              }
+            len_count += 16;
+          }
+        else
+          { /* no sockaddr item found */
+            pa_CPF_data->AddrInfo[j].TypeID = 0; /* mark as not set */
+            pa_Data -= 2;
+          }
+      }
+    /* set the addressInfoItems to not set if they werent received */
+    if (pa_CPF_data->ItemCount < 4)
+      {
+        pa_CPF_data->AddrInfo[1].TypeID = 0;
+        if (pa_CPF_data->ItemCount < 3)
+          {
+            pa_CPF_data->AddrInfo[0].TypeID = 0;
+          }
+      }
+    if (len_count == pa_DataLength)
+      { /* length of data is equal to length of Addr and length of Data */
+        return EIP_OK;
+      }
+    else
+      {
+        printf("something is wrong with the length in MR @ createCPFstructure\n");
+        if (pa_CPF_data->ItemCount > 2)
+          {
+            /* there is an optional packet in data stream which is not sockaddr item */
+            return EIP_OK;
+          }
+        else
+          { /* something with the length was wrong */
+            return EIP_ERROR;
+          }
+      }
+  }
+
+/*   INT8 assembleLinearMsg(S_CIP_MR_Response *pa_MRResponse, S_CIP_CPF_Data *pa_CPFDataItem, INT8 *pa_msg)
+ *   copy data from MRResponse struct and CPFDataItem into linear memory in pa_msg for transmission over in encapsulation.
+ *      pa_MRResponse	pointer to message router response which has to be alligned into linear memory.
+ *      pa_CPFDataItem	pointer to CPF stucture which has to be alligned into linear memory.
+ *      pa_msg		pointer to linear memory.
+ *  return length of reply in pa_msg in bytes
+ * 			-1 .. error
+ */
+int assembleLinearMsg(S_CIP_MR_Response * pa_MRResponse,
+    S_CIP_CPF_Data * pa_CPFDataItem, EIP_UINT8 * pa_msg)
+  {
+    int i, j, size;
+
+    size = 0;
+    if (pa_MRResponse)
+      {
+        /* add Interface Handle and Timeout = 0 -> only for SendRRData and SendUnitData necessary */
+        htoll(0, &pa_msg);
+        htols(0, &pa_msg);
+        size += 6;
+      }
+
+    htols(pa_CPFDataItem->ItemCount, &pa_msg); /* item count */
+    size += 2;
+    /* process Address Item */
+    if (pa_CPFDataItem->stAddr_Item.TypeID == CIP_ITEM_ID_NULL)
+      { /* null address item -> address length set to 0 */
+        htols(CIP_ITEM_ID_NULL, &pa_msg);
+        htols(0, &pa_msg);
+        size += 4;
+      }
+    if (pa_CPFDataItem->stAddr_Item.TypeID == CIP_ITEM_ID_CONNECTIONBASED)
+      { /* connected data item -> address length set to 4 and copy ConnectionIdentifier */
+        htols(CIP_ITEM_ID_CONNECTIONBASED, &pa_msg);
+        htols(4, &pa_msg);
+        htoll(pa_CPFDataItem->stAddr_Item.Data.ConnectionIdentifier, &pa_msg);
+        size += 8;
+      }
+    /* sequencenumber????? */
+    if (pa_CPFDataItem->stAddr_Item.TypeID == CIP_ITEM_ID_SEQUENCEDADDRESS)
+      { /* sequenced address item -> address length set to 8 and copy ConnectionIdentifier and SequenceNumber */
+        htols(CIP_ITEM_ID_SEQUENCEDADDRESS, &pa_msg);
+        htols(8, &pa_msg);
+        htoll(pa_CPFDataItem->stAddr_Item.Data.ConnectionIdentifier, &pa_msg);
+        htoll(pa_CPFDataItem->stAddr_Item.Data.SequenceNumber, &pa_msg);
+        size += 12;
+      }
+
+    /* process Data Item */
+    if ((pa_CPFDataItem->stDataI_Item.TypeID == CIP_ITEM_ID_UNCONNECTEDMESSAGE)
+        || (pa_CPFDataItem->stDataI_Item.TypeID
+            == CIP_ITEM_ID_CONNECTIONTRANSPORTPACKET))
+      {
+        if (pa_MRResponse)
+          {
+            htols(pa_CPFDataItem->stDataI_Item.TypeID, &pa_msg);
+            
+            if(pa_CPFDataItem->stDataI_Item.TypeID == CIP_ITEM_ID_CONNECTIONTRANSPORTPACKET)
+              {
+                htols((EIP_UINT16)(pa_MRResponse->DataLength + 4 + 2 + (2
+                                                * pa_MRResponse->SizeofAdditionalStatus)), &pa_msg);
+                
+                htols((EIP_UINT16)g_stCPFDataItem.stAddr_Item.Data.SequenceNumber, &pa_msg);
+                
+                size += (4 + pa_MRResponse->DataLength + 4 + 2 + (2
+                                * pa_MRResponse->SizeofAdditionalStatus));
+              }
+            else
+              {
+                htols((EIP_UINT16)(pa_MRResponse->DataLength + 4 + (2
+                                * pa_MRResponse->SizeofAdditionalStatus)), &pa_msg);
+                size += (4 + pa_MRResponse->DataLength + 4 + (2
+                                * pa_MRResponse->SizeofAdditionalStatus));
+              }
+              
+
+            /* write MR Response into linear memory */
+            *pa_msg = pa_MRResponse->ReplyService;
+            pa_msg++;
+            *pa_msg = pa_MRResponse->Reserved; /* reserved = 0 */
+            pa_msg++;
+            *pa_msg = pa_MRResponse->GeneralStatus;
+            pa_msg++;
+            *pa_msg = pa_MRResponse->SizeofAdditionalStatus;
+            pa_msg++;
+            for (i = 0; i < pa_MRResponse->SizeofAdditionalStatus; i++)
+              htols(pa_MRResponse->AdditionalStatus[i], &pa_msg);
+
+            for (i = 0; i < pa_MRResponse->DataLength; i++)
+              {
+                *pa_msg = (EIP_UINT8) * (pa_MRResponse->Data + i);
+                pa_msg++;
+              }            
+          }
+        else
+          { /* connected IO Message to send */
+            htols(pa_CPFDataItem->stDataI_Item.TypeID, &pa_msg);
+            htols(pa_CPFDataItem->stDataI_Item.Length, &pa_msg);
+            for (i = 0; i < pa_CPFDataItem->stDataI_Item.Length; i++)
+              {
+                *pa_msg = (EIP_UINT8) * (pa_CPFDataItem->stDataI_Item.Data + i);
+                pa_msg++;
+              }
+            size += (pa_CPFDataItem->stDataI_Item.Length + 4);
+          }
+      }
+    /* process SockAddr Info Items */
+    for (j = 0; j < 2; j++)
+      {
+        if ((pa_CPFDataItem->AddrInfo[j].TypeID
+            == CIP_ITEM_ID_SOCKADDRINFO_O_TO_T)
+            || (pa_CPFDataItem->AddrInfo[j].TypeID
+                == CIP_ITEM_ID_SOCKADDRINFO_T_TO_O))
+          {
+            htols(pa_CPFDataItem->AddrInfo[j].TypeID, &pa_msg);
+            htols(pa_CPFDataItem->AddrInfo[j].Length, &pa_msg);
+            htols(pa_CPFDataItem->AddrInfo[j].nsin_family, &pa_msg);
+            htols(pa_CPFDataItem->AddrInfo[j].nsin_port, &pa_msg);
+            htoll(pa_CPFDataItem->AddrInfo[j].nsin_addr, &pa_msg);
+            for (i = 0; i < 8; i++)
+              {
+                *pa_msg = 0; /* sin_zero */
+                pa_msg++;
+              }
+            size += 20;
+          }
+      }
+    return size;
+  }
+

+ 94 - 0
src/enet_encap/cpf.h

@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef _CPF_H
+#define _CPF_H
+
+#include "typedefs.h"
+#include "ciptypes.h"
+
+// CPF is Common Packet Format
+// CPF packet := <number of items> {<items>}
+// item := <TypeID> <Length> <data>
+// <number of items> := two bytes
+// <TypeID> := two bytes
+// <Length> := two bytes
+// <data> := <the number of bytes specified by Length>
+
+/* define Item ID numbers used for address and data items in CPF structures */
+#define CIP_ITEM_ID_NULL                                0x0000  /* Null Address Item */
+#define CIP_ITEM_ID_LISTIDENTITY_RESPONSE               0x000C
+#define CIP_ITEM_ID_CONNECTIONBASED                     0x00A1  /* Connected Address Item */
+#define CIP_ITEM_ID_CONNECTIONTRANSPORTPACKET           0x00B1  /* Connected Data Item */
+#define CIP_ITEM_ID_UNCONNECTEDMESSAGE                  0x00B2  /* Unconnected Data Item */
+#define CIP_ITEM_ID_LISTSERVICE_RESPONSE                0x0100
+#define CIP_ITEM_ID_SOCKADDRINFO_O_TO_T                 0x8000  /* Sockaddr info item originator to target (data) */
+#define CIP_ITEM_ID_SOCKADDRINFO_T_TO_O                 0x8001  /* Sockaddr info item target to originator (data) */
+#define CIP_ITEM_ID_SEQUENCEDADDRESS                    0x8002  /* Sequenced Address item */
+
+typedef struct
+  {
+    EIP_UINT32 ConnectionIdentifier;
+    EIP_UINT32 SequenceNumber;
+  } S_Address_Data;
+
+typedef struct
+  {
+    EIP_UINT16 TypeID;
+    EIP_UINT16 Length;
+    S_Address_Data Data;
+  } S_Address_Item;
+
+typedef struct
+  {
+    EIP_UINT16 TypeID;
+    EIP_UINT16 Length;
+    EIP_UINT8 *Data;
+  } S_Data_Item;
+
+typedef struct
+  {
+    EIP_UINT16 TypeID;
+    EIP_UINT16 Length;
+    EIP_INT16 nsin_family;
+    EIP_UINT16 nsin_port;
+    EIP_UINT32 nsin_addr;
+    EIP_UINT8 nasin_zero[8];
+  } S_SockAddrInfo_Item;
+
+// this one case of a CPF packet is supported:
+
+typedef struct
+  {
+    EIP_UINT16 ItemCount;
+    S_Address_Item stAddr_Item;
+    S_Data_Item stDataI_Item;
+    S_SockAddrInfo_Item AddrInfo[2];
+  } S_CIP_CPF_Data;
+
+int notifyCPF(EIP_UINT8 * pa_nData, // message data
+    EIP_INT16 pa_nData_length, // data length
+    EIP_UINT8 * pa_replybuf); // reply buffer
+
+int notifyConnectedCPF(EIP_UINT8 * pa_nData, // message data
+    EIP_INT16 pa_nData_length, // data length
+    EIP_UINT8 * pa_replybuf); // reply buffer
+
+
+EIP_STATUS createCPFstructure(EIP_UINT8 * pa_Data, int pa_DataLength,
+    S_CIP_CPF_Data * pa_CPF_data);
+
+int assembleLinearMsg(S_CIP_MR_Response * pa_MRResponse,
+    S_CIP_CPF_Data * pa_CPFDataItem, EIP_UINT8 * pa_msg);
+
+/*!Data storage for the any cpf data
+ * Currently we are single threaded and need only one cpf at the time.
+ * For future extensions towards multithreading maybe more cpf data items may be necessary
+ */
+extern S_CIP_CPF_Data g_stCPFDataItem;
+
+#endif

+ 545 - 0
src/enet_encap/encap.c

@@ -0,0 +1,545 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <string.h>
+#include "opener_api.h"
+#include "cpf.h"
+#include "encap.h"
+#include "endianconv.h"
+#include "cipcommon.h"
+#include "cipmessagerouter.h"
+#include "cipconnectionmanager.h"
+#include "cipidentity.h"
+
+#define INVALID_SESSION -1
+
+#define SENDER_CONTEXT_SIZE 8                   /*size of sender context in encapsulation header*/
+
+
+/* definition of known encapsulation commands */
+#define COMMAND_NOP                     0x0000
+#define COMMAND_LISTSERVICES            0x0004
+#define COMMAND_LISTIDENTITY            0x0063
+#define COMMAND_LISTINTERFACES          0x0064
+#define COMMAND_REGISTERSESSION         0x0065
+#define COMMAND_UNREGISTERSESSION       0x0066
+#define COMMAND_SENDRRDATA              0x006F
+#define COMMAND_SENDUNITDATA            0x0070
+
+/* definition of status codes in encapsulation protocol */
+#define OPENER_ENCAP_STATUS_SUCCESS                     0x0000
+#define OPENER_ENCAP_STATUS_INVALID_COMMAND             0x0001
+#define OPENER_ENCAP_STATUS_INSUFFICIENT_MEM            0x0002
+#define OPENER_ENCAP_STATUS_INCORRECT_DATA              0x0003
+#define OPENER_ENCAP_STATUS_INVALID_SESSION_HANDLE      0x0064
+#define OPENER_ENCAP_STATUS_INVALID_LENGTH              0x0065
+#define OPENER_ENCAP_STATUS_UNSUPPORTED_PROTOCOL        0x0069  
+
+/* definition of capability flags */
+#define SUPPORT_CIP_TCP                 0x0020
+#define SUPPORT_CIP_UDP_CLASS_0_OR_1    0x0100
+
+
+//identiy data from cipidentity.c
+extern EIP_UINT16 VendorID;
+extern EIP_UINT16 DeviceType;
+extern EIP_UINT16 ProductCode;
+extern S_CIP_Revision Revison;
+extern EIP_UINT16 ID_Status;
+extern EIP_UINT32 SerialNumber;
+extern S_CIP_Short_String ProductName;
+
+//ip address data taken from TCPIPInterfaceObject
+extern S_CIP_TCPIPNetworkInterfaceConfiguration Interface_Configuration;
+
+/*** defines ***/
+
+#define ITEM_ID_LISTIDENTITY 0x000C
+
+#define SUPPORTED_PROTOCOL_VERSION 1
+
+#define SUPPORTED_OPTIONS_MASK  0x00  //Mask of wich options are supported as of the current CIP specs no other option value as 0 should be supported.
+
+#define ENCAPSULATION_HEADER_SESSION_HANDLE_POS 4   //the position of the session handle within the encapsulation header
+//struct S_Encapsulation_Data S_ReceiveData, S_SendData;
+
+struct S_Encapsulation_Data g_sEncapData; //buffer for the encapsulation data used for sending and recieving
+
+/*struct S_Identity S_Identity_Object;
+ const static UINT8 productname[] = "test device";
+ */
+
+struct S_Encapsulation_Interface_Information g_stInterfaceInformation;
+
+int anRegisteredSessions[OPENER_NUMBER_OF_SUPPORTED_SESSIONS];
+
+/*** private functions ***/
+int nop(void);
+int ListServices(struct S_Encapsulation_Data *pa_stReceiveData);
+int ListInterfaces(struct S_Encapsulation_Data *pa_stReceiveData);
+int ListIdentity(struct S_Encapsulation_Data *pa_stReceiveData);
+int RegisterSession(int pa_nSockfd,
+    struct S_Encapsulation_Data *pa_stReceiveData);
+int UnregisterSession(struct S_Encapsulation_Data *pa_stReceiveData);
+int SendUnitData(struct S_Encapsulation_Data *pa_stReceiveData);
+int SendRRData(struct S_Encapsulation_Data *pa_stReceiveData);
+//int UnsupportedCommandReceived(struct S_Encapsulation_Data *pa_stReceiveData, EIP_UINT16 pa_Status);
+
+int getFreeSessionIndex(void);
+EIP_INT16 createEncapsulationStructure(EIP_UINT8 * buf, int length,
+    struct S_Encapsulation_Data *pa_S_ReceiveData);
+EIP_STATUS checkRegisteredSessions(
+    struct S_Encapsulation_Data *pa_S_ReceiveData);
+int encapsulate_data(struct S_Encapsulation_Data *pa_S_SendData);
+
+
+/*   void encapInit(void)
+ *   initialize sessionlist and interfaceinformation.
+ */
+
+void encapInit(void)
+  {
+    int i;
+
+    /* initialize Sessions to invalid == free session */
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_SESSIONS; i++)
+      {
+        anRegisteredSessions[i] = EIP_INVALID_SOCKET;
+      }
+    //   S_SendData.pEncapsulation_Data
+    //       = &eip_reply_buf[ENCAPSULATION_HEADER_LENGTH];
+
+    //TODO make th interface inforamtion configureable
+    /* initialize interface information */
+    g_stInterfaceInformation.TypeCode = CIP_ITEM_ID_LISTSERVICE_RESPONSE;
+    g_stInterfaceInformation.Length
+        = sizeof(g_stInterfaceInformation);
+    g_stInterfaceInformation.EncapsulationProtocolVersion = 1;
+    g_stInterfaceInformation.CapabilityFlags = SUPPORT_CIP_TCP
+    | SUPPORT_CIP_UDP_CLASS_0_OR_1;
+    strcpy((char *) g_stInterfaceInformation.NameofService, "communications");
+  }
+
+/*   int handleReceivedExplictData(int pa_socket, EIP_UINT8* pa_buf, int pa_length. int *pa_nRemainingBytes)
+ *   Read received bytes, copy to struct S_Encapsulation_data and handles the command.
+ *      pa_socket	socket handle from which data is received.
+ *      pa_buf		buffer to be read.
+ *      pa_length	length of the data in pa_buf.
+ *  return length of reply
+ */
+int handleReceivedExplictData(int pa_socket, // socket from which data was recevied
+    EIP_UINT8 * pa_buf, // input buffer
+    int pa_length, // length of input
+    int *pa_nRemainingBytes) // return how many bytes of the input are left over after we're done here
+  {
+    int nRetVal = 0;
+    // eat the encapsulation header
+    // the structure contains a pointer to the encapsulated data
+    // returns how many bytes are left after the encapsulated data
+    *pa_nRemainingBytes = createEncapsulationStructure(pa_buf, pa_length,
+        &g_sEncapData);
+
+    if (SUPPORTED_OPTIONS_MASK == g_sEncapData.nOptions) //TODO generate aproriate error response
+      {        
+        if (*pa_nRemainingBytes >= 0) // check if the message is corrupt: header size + claimed payload size > than what we actually received
+          {
+            /* full package or more received */
+            g_sEncapData.nStatus = OPENER_ENCAP_STATUS_SUCCESS;
+            // each of these can return one of:
+            //   reply size >0
+            //   0 (no reply shall be sent)
+            //   EIP_ERROR (i.e. -1) an error occurred, no reply shall be sent
+            switch (g_sEncapData.nCommand_code)
+              {
+            case (COMMAND_NOP):
+              nRetVal = nop();
+              break;
+
+            case (COMMAND_LISTSERVICES):
+              nRetVal = ListServices(&g_sEncapData);
+              break;
+
+            case (COMMAND_LISTIDENTITY):
+              nRetVal = ListIdentity(&g_sEncapData);
+              break;
+
+            case (COMMAND_LISTINTERFACES):
+              nRetVal = ListInterfaces(&g_sEncapData);
+              break;
+
+            case (COMMAND_REGISTERSESSION):
+              nRetVal = RegisterSession(pa_socket, &g_sEncapData);
+              break;
+
+            case (COMMAND_UNREGISTERSESSION):
+              nRetVal = UnregisterSession(&g_sEncapData);
+              break;
+
+            case (COMMAND_SENDRRDATA):
+              nRetVal = SendRRData(&g_sEncapData);
+              break;
+
+            case (COMMAND_SENDUNITDATA):
+              nRetVal = SendUnitData(&g_sEncapData); // not currently supported, SendUnitData is a stub
+              break;
+            default:
+              g_sEncapData.nStatus = OPENER_ENCAP_STATUS_INVALID_COMMAND;
+              g_sEncapData.nData_length = 0;
+              nRetVal = encapsulate_data(&g_sEncapData);
+              break;
+              }
+          }
+      }
+
+    return nRetVal;
+  }
+
+/*   INT8 encapsulate_data(struct S_Encapsulation_Data *pa_stSendData)
+ *   add encapsulation header and sender_context to data.
+ *      pa_stSendData pointer to structure with header and datapointer.
+ *  return size of reply
+ */
+int encapsulate_data(struct S_Encapsulation_Data *pa_stSendData)
+  {
+    pa_stSendData->pEncapsulation_Data = &g_acCommBuf[2];
+    //htols(pa_stSendData->nCommand_code, &pa_stSendData->pEncapsulation_Data);
+    htols(pa_stSendData->nData_length, &pa_stSendData->pEncapsulation_Data);
+    //the g_acCommBuf should already contain the correct session handle
+    //htoll(pa_stSendData->nSession_handle, &pa_stSendData->pEncapsulation_Data); 
+    pa_stSendData->pEncapsulation_Data += 4;
+    htoll(pa_stSendData->nStatus, &pa_stSendData->pEncapsulation_Data);
+    //the g_acCommBuf should already contain the correct sender context
+    //memcpy(pa_stSendData->pEncapsulation_Data, pa_stSendData->anSender_context, SENDER_CONTEXT_SIZE);
+    pa_stSendData->pEncapsulation_Data += SENDER_CONTEXT_SIZE + 2; // the plus 2 is for the options value
+    //the g_acCommBuf should already contain the correct  options value
+    //htols((EIP_UINT16)pa_stSendData->nOptions, &pa_stSendData->pEncapsulation_Data);
+
+    return ENCAPSULATION_HEADER_LENGTH + pa_stSendData->nData_length;
+  }
+
+/*   INT8 nop()
+ *   do nothing, only implemented for CIP conformity.
+ */
+int nop(void)
+  {
+    return 0;
+  }
+
+/*   INT8 ListServices(struct S_Encapsulation_Data *pa_S_ReceiveData)
+ *   generate reply with "Communications Services" + compatibility Flags.
+ *      pa_S_ReceiveData pointer to structur with received data
+ */
+int ListServices(struct S_Encapsulation_Data *pa_stReceiveData)
+  {
+    //      printf("ListServices called\n");
+    pa_stReceiveData->nData_length = g_stInterfaceInformation.Length + 2;
+
+    EIP_UINT8 *pacCommBuf = pa_stReceiveData->pEncapsulation_Data;
+    /* copy Interface data to nmsg for sending */
+    htols(1, &pacCommBuf);
+    htols(g_stInterfaceInformation.TypeCode, &pacCommBuf);
+    htols((EIP_UINT16)(g_stInterfaceInformation.Length - 4), &pacCommBuf);
+    htols(g_stInterfaceInformation.EncapsulationProtocolVersion, &pacCommBuf);
+    htols(g_stInterfaceInformation.CapabilityFlags, &pacCommBuf);
+    memcpy(pacCommBuf, g_stInterfaceInformation.NameofService, sizeof(g_stInterfaceInformation.NameofService));
+
+    return encapsulate_data(pa_stReceiveData); /* encapsulate the data from structure and send to originator a reply */
+  }
+
+int ListInterfaces(struct S_Encapsulation_Data *pa_stReceiveData)
+  {
+    //  printf("ListInterfaces called\n");
+
+    pa_stReceiveData->nData_length = 2;
+    EIP_UINT8 *pacCommBuf = pa_stReceiveData->pEncapsulation_Data;
+    htols(0x0000, &pacCommBuf); /* copy Interface data to nmsg for sending */
+
+    return encapsulate_data(pa_stReceiveData); /* encapsulate the data from structure and send to originator a reply */
+  }
+
+/*   INT8 ListIdentity(struct S_Encapsulation_Data *pa_S_ReceiveData)
+ *   send Get_Attribute_All to Identity Object and send data + sender_context back.
+ *      pa_S_ReceiveData pointer to sructure with received data
+ *  return status
+ * 			0 .. success
+ */
+int ListIdentity(struct S_Encapsulation_Data * pa_stReceiveData)
+  {
+    /* List Identity reply according to EIP/CIP Specification */
+    EIP_UINT8 *pacCommBuf = pa_stReceiveData->pEncapsulation_Data;
+
+    htols(1, &pacCommBuf); /* one item */
+    htols(ITEM_ID_LISTIDENTITY, &pacCommBuf);
+
+    EIP_BYTE *acIdLenBuf = pacCommBuf;
+    pacCommBuf += 2; //at this place the real length will be inserted below
+
+    htols(SUPPORTED_PROTOCOL_VERSION, &pacCommBuf);
+    htols(htons(AF_INET), &pacCommBuf); 
+    htols(htons(OPENER_ETHERNET_PORT), &pacCommBuf);
+    htoll(Interface_Configuration.IPAddress, &pacCommBuf);
+    memset(pacCommBuf, 0, 8);
+    pacCommBuf += 8;
+
+    htols(VendorID, &pacCommBuf);
+    htols(DeviceType, &pacCommBuf);
+    htols(ProductCode, &pacCommBuf);
+    *(pacCommBuf)++ = Revison.MajorRevision;
+    *(pacCommBuf)++ = Revison.MinorRevision;
+    htols(ID_Status, &pacCommBuf);
+    htoll(SerialNumber, &pacCommBuf);
+    *pacCommBuf++ = (unsigned char) ProductName.Length;
+    memcpy(pacCommBuf, ProductName.String, ProductName.Length);
+    pacCommBuf += ProductName.Length;
+    *pacCommBuf++ = 0x03;
+
+    pa_stReceiveData->nData_length = pacCommBuf
+        - &g_acCommBuf[ENCAPSULATION_HEADER_LENGTH];
+    htols(pacCommBuf - acIdLenBuf - 2, &acIdLenBuf); // the -2 is for not counting the lenght field
+
+    return encapsulate_data(pa_stReceiveData);
+  }
+
+/*   INT8 RegisterSession(struct S_Encapsulation_Data *pa_S_ReceiveData)
+ *   Check supported protocol, generate session handle, send replay back to originator.
+ *      pa_nSockfd       socket this request is asociated to. Needed for double register check 
+ *      pa_S_ReceiveData pointer to received data with request/response.
+ *  return status
+ * 			0 .. success
+ */
+int RegisterSession(int pa_nSockfd,
+    struct S_Encapsulation_Data * pa_stReceiveData)
+  {
+    int i;
+    int nSessionIndex = 0;
+    EIP_UINT8 *pacBuf;
+    EIP_UINT16 nProtocolVersion = ltohs(&pa_stReceiveData->pEncapsulation_Data);
+    EIP_UINT16 nOptionFlag = ltohs(&pa_stReceiveData->pEncapsulation_Data);
+    
+    /* check if requested protocol version is supported and the register session option flag is zero*/
+    if ((0 < nProtocolVersion) && (nProtocolVersion
+        <= SUPPORTED_PROTOCOL_VERSION) && (0 == nOptionFlag))
+      { //Option field should be zero
+        /* check if the socket has already a session open */
+        for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_SESSIONS; ++i)
+          {
+            if (anRegisteredSessions[i] == pa_nSockfd)
+              {
+                // the socket has already registered a session this is not allowed
+                pa_stReceiveData->nSession_handle = i + 1; //return the already assigned session back, the cip spec is not clear about this needs to be tested
+                pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_UNSUPPORTED_PROTOCOL;
+                nSessionIndex = INVALID_SESSION;
+                pacBuf
+                    = &g_acCommBuf[ENCAPSULATION_HEADER_SESSION_HANDLE_POS];
+                htoll(pa_stReceiveData->nSession_handle, &pacBuf); //encapsulate_data will not update the session handle so we have to do it here by hand
+                break;
+              }
+          }
+
+        if (INVALID_SESSION != nSessionIndex)
+          {
+            nSessionIndex = getFreeSessionIndex();
+            if (INVALID_SESSION == nSessionIndex) /* no more sessions available */
+              {
+                pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_INSUFFICIENT_MEM;
+              }
+            else
+              { /* successful session registered */
+                anRegisteredSessions[nSessionIndex] = pa_nSockfd; /* store associated socket */
+                pa_stReceiveData->nSession_handle = nSessionIndex + 1;
+                pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_SUCCESS;
+                pacBuf = &g_acCommBuf[ENCAPSULATION_HEADER_SESSION_HANDLE_POS];
+                htoll(pa_stReceiveData->nSession_handle, &pacBuf); //encapsulate_data will not update the session handle so we have to do it here by hand
+              }
+          }
+      }
+    else
+      { /* protocol not supported */
+        pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_UNSUPPORTED_PROTOCOL;
+      }
+
+    pa_stReceiveData->nData_length = 4;
+
+    return encapsulate_data(pa_stReceiveData);
+  }
+
+/*   INT8 UnregisterSession(struct S_Encapsulation_Data *pa_S_ReceiveData)
+ *   close all corresponding TCP connections and delete session handle.
+ *      pa_S_ReceiveData pointer to unregister session request with corresponding socket handle.
+ *  return status
+ * 			0.. success
+ */
+int UnregisterSession(struct S_Encapsulation_Data * pa_stReceiveData)
+  {
+    int i;
+    
+    if((0 < pa_stReceiveData->nSession_handle) && 
+       (pa_stReceiveData->nSession_handle <= OPENER_NUMBER_OF_SUPPORTED_SESSIONS))
+      {
+        i = pa_stReceiveData->nSession_handle - 1;   
+        if(EIP_INVALID_SOCKET != anRegisteredSessions[i])
+          {
+            IApp_CloseSocket(anRegisteredSessions[i]);
+            anRegisteredSessions[i] = EIP_INVALID_SOCKET;
+            return EIP_OK;
+          }
+      }
+    
+    /* no such session registered */
+    pa_stReceiveData->nData_length = 0;
+    pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_INVALID_SESSION_HANDLE;
+    return encapsulate_data(pa_stReceiveData);
+  }
+
+/*   INT8 SendUnitData(struct S_Encapsulation_Data *pa_S_ReceiveData)
+ *   Call Connection Manager.
+ *      pa_S_ReceiveData pointer to structur with data and headerinformation.
+ *  return status 	0 .. success.
+ * 					-1 .. error
+ */
+int SendUnitData(struct S_Encapsulation_Data * pa_stReceiveData)
+  {
+    EIP_INT16 nSendSize;
+    /* Commandspecific data UDINT .. Interface Handle, UINT .. Timeout, CPF packets */
+    /* don't use the data yet */
+    ltohl(&pa_stReceiveData->pEncapsulation_Data); // skip over null interface handle
+    ltohs(&pa_stReceiveData->pEncapsulation_Data); // skip over unnused timeout value
+    pa_stReceiveData->nData_length -= 6; // the rest is in CPF format
+
+    if (checkRegisteredSessions(pa_stReceiveData) == EIP_ERROR) // see if the EIP session is registered
+      { /* received a package with non registerd session handle */
+        pa_stReceiveData->nData_length = 0;
+        pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_INVALID_SESSION_HANDLE;
+        return encapsulate_data(pa_stReceiveData);
+      }
+
+    nSendSize = notifyConnectedCPF(pa_stReceiveData->pEncapsulation_Data,
+        pa_stReceiveData->nData_length,
+        &g_acCommBuf[ENCAPSULATION_HEADER_LENGTH]);
+
+    if (nSendSize > 0)
+      { /* need to send reply */
+        pa_stReceiveData->nData_length = nSendSize;
+        return encapsulate_data(pa_stReceiveData);
+      }
+
+    return nSendSize;
+  }
+
+/*   INT8 SendRRData(struct S_Encapsulation_Data *pa_stReceiveData)
+ *   Call UCMM or Message Router if UCMM not implemented.
+ *      pa_stReceiveData pointer to structur with data and headerinformation.
+ *  return status 	0 .. success.
+ * 					-1 .. error
+ */
+int SendRRData(struct S_Encapsulation_Data * pa_stReceiveData)
+  {
+    EIP_INT16 nSendSize;
+    /* Commandspecific data UDINT .. Interface Handle, UINT .. Timeout, CPF packets */
+    /* don't use the data yet */
+    ltohl(&pa_stReceiveData->pEncapsulation_Data); // skip over null interface handle
+    ltohs(&pa_stReceiveData->pEncapsulation_Data); // skip over unnused timeout value
+    pa_stReceiveData->nData_length -= 6; // the rest is in CPF format
+
+    if (checkRegisteredSessions(pa_stReceiveData) == EIP_ERROR) // see if the EIP session is registered
+      { /* received a package with non registerd session handle */
+        pa_stReceiveData->nData_length = 0;
+        pa_stReceiveData->nStatus = OPENER_ENCAP_STATUS_INVALID_SESSION_HANDLE;
+        return encapsulate_data(pa_stReceiveData);
+      }
+
+    nSendSize = notifyCPF(pa_stReceiveData->pEncapsulation_Data,
+        pa_stReceiveData->nData_length,
+        &g_acCommBuf[ENCAPSULATION_HEADER_LENGTH]);
+
+    if (nSendSize > 0)
+      { /* need to send reply */
+        pa_stReceiveData->nData_length = nSendSize;
+        return encapsulate_data(pa_stReceiveData);
+      }
+
+    return nSendSize;
+  }
+
+/*   INT8 getFreeSessionIndex()
+ *   search for available sessions an return index.
+ *  return index of free session in anRegisteredSessions.
+ * 			-1 .. no free session available
+ */
+int getFreeSessionIndex(void)
+  {
+    int i;
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_SESSIONS; i++)
+      {
+        if (EIP_INVALID_SOCKET == anRegisteredSessions[i])
+          {
+            return i;
+          }
+      }
+    return -1;
+  }
+
+/*   INT16 createEncapsulationStructure(INT16 pa_sockfd, INT8 *pa_buf, UINT16 pa_length, struct S_Encapsulation_Data *pa_S_Data)
+ *   copy data from pa_buf in little endian to host in structure.
+ *      pa_length	length of the data in pa_buf.
+ *      pa_S_Data	structure to which data shall be copied
+ *  return difference between bytes in pa_buf an data_length
+ *  		0 .. full package received
+ * 			>0 .. more than one packet received
+ * 			<0 .. only fragment of data portion received
+ */
+EIP_INT16 createEncapsulationStructure( EIP_UINT8 * pa_buf, // receive buffer
+    int pa_length, // size of stuff in  buffer (might be more than one message)
+    struct S_Encapsulation_Data * pa_stData) // the struct to be created
+
+  {
+    pa_stData->nCommand_code = ltohs(&pa_buf);
+    pa_stData->nData_length = ltohs(&pa_buf);
+    pa_stData->nSession_handle = ltohl(&pa_buf);
+    pa_stData->nStatus = ltohl(&pa_buf);
+    //memcpy(pa_stData->anSender_context, pa_buf, SENDER_CONTEXT_SIZE);
+    pa_buf += SENDER_CONTEXT_SIZE;
+    pa_stData->nOptions = ltohl(&pa_buf);
+    pa_stData->pEncapsulation_Data = pa_buf;
+    return (pa_length - ENCAPSULATION_HEADER_LENGTH - pa_stData->nData_length);
+  }
+
+/*   INT8 checkRegisteredSessions(struct S_Encapsulation_Data *pa_S_ReceiveData)
+ *   check if received package belongs to registered session.
+ *      pa_stReceiveData received data.
+ *  return 0 .. Session registered
+ *  		-1 .. invalid session -> return unsupported command received
+ */
+EIP_STATUS checkRegisteredSessions(
+    struct S_Encapsulation_Data * pa_stReceiveData)
+  {
+    
+    if((0 < pa_stReceiveData->nSession_handle) && 
+       (pa_stReceiveData->nSession_handle <= OPENER_NUMBER_OF_SUPPORTED_SESSIONS))
+      {
+        if(EIP_INVALID_SOCKET != anRegisteredSessions[pa_stReceiveData->nSession_handle - 1])
+          {
+            return EIP_OK;
+          }
+      }
+    
+    return EIP_ERROR;
+  }
+
+void closeSession(int pa_nSocket)
+  {
+    int i;
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_SESSIONS; ++i)
+      {
+        if (anRegisteredSessions[i] == pa_nSocket)
+          {
+            IApp_CloseSocket(pa_nSocket);
+            anRegisteredSessions[i] = EIP_INVALID_SOCKET;
+            break;
+          }
+      }
+  }

+ 48 - 0
src/enet_encap/encap.h

@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef ENCAP_H_
+#define ENCAP_H_
+
+#include "typedefs.h"
+
+/*** defines ***/
+
+#define ENCAPSULATION_HEADER_LENGTH 24
+#define OPENER_ETHERNET_PORT 0xAF12
+
+
+/*** structs ***/
+struct S_Encapsulation_Data
+  {
+    EIP_UINT16 nCommand_code;
+    EIP_UINT16 nData_length;
+    EIP_UINT32 nSession_handle;
+    EIP_UINT32 nStatus;
+    // The sender context is not needed any more with the new minumum data copy design
+    // EIP_UINT8 anSender_context[SENDER_CONTEXT_SIZE];  
+    EIP_UINT32 nOptions;
+    EIP_UINT8 *pEncapsulation_Data;
+  };
+
+struct S_Encapsulation_Interface_Information
+  {
+    EIP_UINT16 TypeCode;
+    EIP_UINT16 Length;
+    EIP_UINT16 EncapsulationProtocolVersion;
+    EIP_UINT16 CapabilityFlags;
+    EIP_INT8 NameofService[16];
+  };
+
+/*** global variables (public) ***/
+extern EIP_UINT8 g_acCommBuf[];   //!<Buffer to be used for communication data (in/out), has to be provided by the platform specific code.
+
+/*** public functions ***/
+void encapInit(void);
+void closeSession(int pa_nSocket); //!<The remote host closed the connection. Clean up and close the session
+
+#endif /*ENCAP_H_*/

+ 63 - 0
src/enet_encap/endianconv.c

@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include "endianconv.h"
+
+// THESE ROUTINES MODIFY THE BUFFER POINTER
+
+// little-endian-to-host unsigned 16 bit
+
+EIP_UINT16 ltohs(EIP_UINT8 ** pa_buf)
+  {
+    unsigned char *p = (unsigned char *)*pa_buf;
+    EIP_UINT16 data = p[0] | p[1]<<8;
+    *pa_buf += 2;
+    return data;
+  }
+
+/*   UINT32 ltohl(INT8 **pa_buf)
+ *   reeds UINT32 from *pa_buf and converts little endian to host.
+ *      *pa_buf pointer where data should be reed. 
+ *  return value
+ */
+EIP_UINT32 ltohl(EIP_UINT8 ** pa_buf)
+  {
+    unsigned char *p = (unsigned char *)*pa_buf;
+    EIP_UINT32 data = p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
+    *pa_buf += 4;
+    return data;
+  }
+
+/*   void htols(UINT16 data, UINT8 **pa_buf)
+ *   converts UINT16 data from host to little endian an writes it to pa_buf.
+ *      data value to be written
+ *      *pa_buf pointer where data should be written.
+ */
+void htols(EIP_UINT16 data, EIP_UINT8 ** pa_buf)
+  {
+    unsigned char *p = (unsigned char *)*pa_buf;
+
+    p[0] = (unsigned char)data;
+    p[1] = (unsigned char)(data >> 8);
+    *pa_buf += 2;
+  }
+
+/*   void htoll(UINT32 data, INT8 **pa_buf)
+ *   converts UINT32 data from host to little endian an writes it to pa_buf.
+ *      data value to be written
+ *      *pa_buf pointer where data should be written.
+ */
+void htoll(EIP_UINT32 data, EIP_UINT8 ** pa_buf)
+  {
+    unsigned char *p = (unsigned char *)*pa_buf;
+
+    p[0] = (unsigned char)data;
+    p[1] = (unsigned char)(data >> 8);
+    p[2] = (unsigned char)(data >> 16);
+    p[3] = (unsigned char)(data >> 24);
+    *pa_buf += 4;
+  }

+ 18 - 0
src/enet_encap/endianconv.h

@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef ENDIANCONV_H_
+#define ENDIANCONV_H_
+
+#include "typedefs.h"
+
+EIP_UINT16 ltohs(EIP_UINT8 **pa_buf);
+EIP_UINT32 ltohl(EIP_UINT8 **pa_buf);
+void htols(EIP_UINT16 data, EIP_UINT8 **pa_buf);
+void htoll(EIP_UINT32 data, EIP_UINT8 **pa_buf);
+
+#endif /*ENDIANCONV_H_*/

+ 325 - 0
src/opener_api.h

@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef CIP_API_H_
+#define CIP_API_H_
+
+#include "typedefs.h"
+#include "ciptypes.h"
+#include "ciperror.h"
+#include <opener_user_conf.h>
+
+/*! \mainpage OpENer - Open Source EtherNet/IP(TM) I/O Target Stack Documentation
+ *
+ * EtherNet/IP stack for adapter devices (connection target); supports multiple
+ * I/O and explicit connections; includes features and objects required by the
+ * CIP specification to enable devices to comply with ODVA's conformance/
+ * interoperability tests.
+ * 
+ * \section intro_sec Introduction
+ *
+ * This is the introduction.
+ *
+ * \section install_sec Installation
+ * How to compile, install and run OpENer on a specific platform.
+ * 
+ * \section porting_sec Porting OpENer
+ * 
+ * \section extetnding_sec Extending OpENer
+ * 
+ */  
+
+
+/**  \defgroup CIP_API OpENer User interface
+ * \brief This is the public interface of the OpENer. It provides all function needed to implement an EtherNet/IP enabled slave-device.
+ */
+
+/*! \ingroup CIP_API \brief configure the data of the network interface of the device
+ * 
+ *  This function setup the data of the network interface needed by OpENer.
+ *  The multicast address is automatically calculated fromt he given data.
+ * 
+ *  @param pa_acIpAdress    the current ip address of the device
+ *  @param pa_acSubNetMask  the subnetmask to be used
+ *  @param pa_acGateway     the gateway address 
+ *  @return EIP_OK if the configuring worked otherwise EIP_ERROR
+ */
+EIP_STATUS configureNetworkInterface(const char *pa_acIpAdress,
+    const char *pa_acSubNetMask, const char *pa_acGateway);
+
+/*! \ingroup CIP_API \brief configure the MAC address of the device
+ * 
+ *  @param pa_acMACAddress  the hardware MAC address of the network interface
+ */
+void configureMACAddress(const EIP_UINT8 *pa_acMACAddress);
+
+/*! \ingroup CIP_API \brief configure the domain name of the device
+ *  @param pa_acDomainName the domain name to be used
+ */
+void configureDomainName(const char *pa_acDomainName);
+
+/*! \ingroup CIP_API \brief configure the host name of the device
+ *  @param pa_acHostName the host name to be used
+ */
+void configureHostName(const char *pa_acHostName);
+
+/** \ingroup CIP_API \brief Initialize and setup the CIP-stack
+ * 
+ */
+void CIP_Init(void);
+
+/** \ingroup CIP_API Get a pointer to a cip object with given class code
+ * 
+ * @param pa_nClassID class ID of the object to retrieve 
+ * @return pointer to CIP Object
+ *          0 if object is not present in the stack
+ */
+S_CIP_Class *getCIPClass(EIP_UINT32 pa_nClassID);
+
+/** \ingroup CIP_API Get a pointer to an instance
+ * 
+ * @param pa_pstObject pointer to the object the instance belongs to
+ * @param pa_nInstanceNr number of the instance to retrieve
+ * @return pointer to CIP Instance
+ *          0 if instance is not in the object
+ */
+S_CIP_Instance *getCIPInstance(S_CIP_Class *pa_pstObject,
+    EIP_UINT16 pa_nInstanceNr);
+
+/** \ingroup CIP_API Get a pointer to an instance's attribute
+ * 
+ * As instances and objects are selsimilar this function can also be used
+ * to retrieve the attribute of an object. 
+ * @param pa_pInstance  pointer to the instance the attribute belongs to
+ * @param pa_nAttributeNr number of the attribute to retrieve
+ * @return poitner to attribute
+ *          0 if instance is not in the object
+ */
+S_CIP_attribute_struct *getAttribute(S_CIP_Instance * pa_pInstance,
+    EIP_UINT8 pa_nAttributeNr);
+
+/*! \ingroup CIP_API Allocate memory for new CIP Class and attributes;
+ *  and register the new CIP class at the stack to be able
+ *  for recieving ecplicit messages
+ * 
+ *  @param pa_nClassID class ID of the new class
+ *  @param pa_nNr_of_ClassAttributes number of class attributes
+ *  @param pa_nClassGetAttrAllMask mask of which attributes are included in the class getAttributeAll
+ *  @param pa_nNr_of_ClassServices number of class services
+ *  @param pa_nNr_of_InstanceAttributes number of attributes of each instance
+ *  @param pa_nInstGetAttrAllMask  mask of which attributes are included in the instance getAttributeAll
+ *  @param pa_nNr_of_InstanceServices number of instance services
+ *  @param pa_nNr_of_Instances number of initial instances to creat
+ *  @param pa_acName  class name (for debugging class structure)
+ *  @param pa_nRevision class revision
+ *  @return pointer to new class object
+ *      0 on error
+ */
+S_CIP_Class *createCIPClass(EIP_UINT32 pa_nClassID, int pa_nNr_of_ClassAttributes, EIP_UINT32 pa_nClassGetAttrAllMask, int pa_nNr_of_ClassServices,
+    int pa_nNr_of_InstanceAttributes, EIP_UINT32 pa_nInstGetAttrAllMask, int pa_nNr_of_InstanceServices, 
+    int pa_nNr_of_Instances, char *pa_acName, EIP_UINT16 pa_nRevision);
+
+/** \ingroup CIP_API \brief Add a number of cip instances to a given cip class
+ *
+ * the required number of instances are created in a block, but are attached to the class as a linked list.
+ * the instances are numbered sequentially -- i.e. the first node in the chain is instance 1, the second is 2, etc.
+ * you can add new instances at any time (you do not have to create all the instances of a class at the same time)
+ * deleting instances once they have been created is not supported
+ * out-of-order instance numbers are not supported
+ * running out of memory while creating new instances causes an assert
+ *
+ * @param pa_pstCIPObject CIP object the instances should be added
+ * @param pa_nNr_of_Instances number of instances to be generated.
+ * @return return pointer to the first of the new instances
+ * 	        0 on error
+ */
+S_CIP_Instance *addCIPInstances(S_CIP_Class *pa_pstCIPObject,
+    int pa_nNr_of_Instances);
+
+/** \ingroup CIP_API \brief Create one instance of a given class with a certain instance number
+ *
+ * This function can be used for creating out of order instance numbers
+ * @param pa_pstCIPClass the class the instance should be created for
+ * @param pa_nInstanceId the instance id of the created instance
+ * @return pointer to the created instance, if an instance with the given id 
+ *         already exists the exisiting is returned an no new instance is created
+ * 
+ */
+S_CIP_Instance *addCIPInstance(S_CIP_Class * pa_pstCIPClass,
+    EIP_UINT32 pa_nInstanceId);
+
+/*! \ingroup CIP_API Insert an attribute in an instance of a CIP class
+ *  @param pa_pInstance pointer to CIP class. (may be also instance 0)
+ *  @param pa_nAttributeNr number of attribute to be inserted.
+ *  @param pa_nCIP_Type type of attribute to be inserted.
+ *  @param pa_pt2data pointer to data of attribute.
+ */
+void insertAttribute(S_CIP_Instance *pa_pInstance, EIP_UINT8 pa_nAttributeNr,
+    EIP_UINT8 pa_nCIP_Type, void* pa_pt2data);
+
+/** \ingroup CIP_API \brief Insert a service in an instance of a CIP object
+ *  note that services are stored in an array pointer to by the class object
+ *  the service array is not expandable if you insert a service that has 
+ *  already been defined, the previous service will be replaced
+ * 
+ * @param pa_pClass pointer to CIP object. (may be also instance 0)
+ * @param pa_nServiceNr servicecode of service to be inserted.
+ * @param pa_ptfuncService pointer to function which represents the service.
+ * @param name name of the service
+ */
+void insertService(S_CIP_Class *pa_pClass, EIP_UINT8 pa_nServiceNr,
+    TCIPServiceFunc pa_ptfuncService, char *name);
+
+/** \ingroup CIP_API \brief Create an instance of an assembly object
+ * 
+ * @param pa_nInstanceID  instance number of the assembly object to create 
+ * @param pa_data         pointer to the data the assembly object should contain
+ * @param pa_datalength   lenght of the assembly object's data
+ * @return pointer to the instance of the created assembly object. NULL on error
+ *
+ * Assembly Objects for Configuration Data:
+ *
+ * The CIP stack treats configuration assembly objects the same way as any other assembly object. 
+ * In order to support a configuration assembly object it has to be created with this function.
+ * The notification on recieved configuration data is handled with the IApp_after_receive function.
+ */
+S_CIP_Instance *createAssemblyObject(EIP_UINT8 pa_nInstanceID,
+    EIP_BYTE *pa_data, EIP_UINT16 pa_datalength);
+
+
+/** \ingroup CIP_API 
+ * Notify the encapsulation layer that an explicit message has been recieved via TCP or UDP.
+ * 
+ * @param pa_socket socket handle from which data is received.
+ * @param pa_buf buffer to be read.
+ * @param pa_length length of the data in pa_buf.
+ * @param pa_nRemainingBytes return how many bytes of the input are left over after we're done here
+ * @return length of reply that need to be sent back
+ */
+int handleReceivedExplictData(int pa_socket, EIP_UINT8* pa_buf, int pa_length,
+    int *pa_nRemainingBytes);
+
+/*! \ingroup CIP_API
+ *  Notfiy the connection manager that data for a connection has been recieved.
+ *  This function should be invoked by the network layer.
+ *  @param pa_pnData pointer to the buffer of data that has been recieved 
+ *  @param pa_nDataLength number of bytes in the data buffer
+ *  @return EIP_OK on success
+ */ 
+EIP_STATUS handleReceivedConnectedData(EIP_UINT8 *pa_pnData, int pa_nDataLength);
+
+/*! \ingroup CIP_API
+ * Check if any of the connection timers (TransmissionTrigger or WarchdogTimeout) has timed out.
+ * If yes the function performs the necessary action. This function should be called periodicaly once every
+ * OPENER_TIMER_TICK ms. 
+ * 
+ * @return EIP_OK on success
+ */
+EIP_STATUS manageConnections(void); 
+
+/**  \defgroup CIP_CALLBACK_API Callback function demanded by the CIP Stack
+ * \ingroup CIP_API
+ * 
+ * \brief These functions have to implemented in order to give the CIP stack a method to inform the application on certain state changes
+ */
+
+/** \ingroup CIP_CALLBACK_API \brief Callback for the application initilisation
+ *
+ * This function will be called by the CIP stack after it has finished its initialisation. In this function the user can setup all CIP objects she likes to have.
+ * TODO think if this is still the right way to do this. Maybe just after the CIP_INIT would be fine to. 
+ *
+ *  return status -1 .. error.
+ */
+EIP_STATUS IApp_Init(void);
+
+/** \ingroup CIP_CALLBACK_API \brief Call back function to inform application on recieved data for an assembly object.
+ * 
+ * This function has to be implemented by the user of the CIP-stack.
+ * @param pa_pstInstance pointer to the assembly object data was received for
+ * @return Information if the data could be processed
+ *     - EIP_OK the received data was ok 
+ *     - EIP_ERROR the received data was wrong (especially needed for configuration data assembly
+ *                 objects) 
+ * 
+ * Assembly Objects for Configuration Data:
+ * The CIP-stack uses this function to inform on recieved configuration data. The length of the data
+ * is already checked within the stack. Therefore the user only has to check if the data is valid.
+ */
+EIP_STATUS IApp_AfterAssemblyDataReceived(S_CIP_Instance *pa_pstInstance);
+
+/** \ingroup CIP_CALLBACK_API \brief Inform the application that the data of an assembly
+ * object will be sent.
+ *
+ * Within this function the user can update the data of the assembly object before it
+ * gets sent. The application can inform the application if data has changed.
+ * @param pa_pstInstance instance of assembly object that should send data.
+ * @return data has changed:
+ *          - true assembly data has changed
+ *          - false assembly data has not changed
+ */
+EIP_BOOL8 IApp_BeforeAssemblyDataSend(S_CIP_Instance *pa_pstInstance);
+
+/** \ingroup CIP_CALLBACK_API \brief Emulate as close a possible a power cycle of the device
+ *  
+ * @return if the service is supported the function will not return.
+ *     EIP_ERROR if this service is not supported
+ */
+EIP_STATUS IApp_ResetDevice(void);
+
+/**\ingroup CIP_CALLBACK_API \brief Reset the device to the initial configuration and emulate as close as possible a power cycle of the device
+ * 
+ * return if the service is supported the function will not return.
+ *     EIP_ERROR if this service is not supported
+ */
+EIP_STATUS IApp_ResetDeviceToInitialConfiguration(void);
+
+/**\ingroup CIP_CALLBACK_API \brief Allocate memory for the cip stack
+ * 
+ * emulate the common c-libary function calloc
+ * In OpENer allocation only happens on application startup and on class/instance creation
+ * and configuration not on during operation (processing messages)
+ * @param pa_nNumberOfElements number of elements to allocate
+ * @param pa_nSizeOfElement size in bytes of one element
+ * return pointer to the allocated memory, 0 on error
+ */
+void *IApp_CipCalloc(unsigned pa_nNumberOfElements, unsigned pa_nSizeOfElement);
+
+/**\ingroup CIP_CALLBACK_API \brief Inform the application that the Run/Idle State has been changed
+ *  by the originator. 
+ * 
+ * 
+ * @param pa_nRunIdleValue  the current value of the run/idle flag according to CIP spec Vol 1 3-6.5
+ */
+void IApp_RunIdleChanged(EIP_UINT32 pa_nRunIdleValue);
+
+/**\ingroup CIP_CALLBACK_API \brief create a producing or consuming UDP socket
+ * 
+ * @param pa_nDirection PRODCUER or CONSUMER
+ * @param pa_pstAddr pointer to the address holding structure
+ * @return socket identfier on success
+ *         -1 on error 
+ */
+int IApp_CreateUDPSocket(int pa_nDirection, struct sockaddr_in *pa_pstAddr); // direction: PRODUCER or CONSUMER
+
+/**\ingroup CIP_CALLBACK_API \brief create a producing or consuming UDP socket
+ * 
+ * @param pa_pstAddr pointer to the sendto address
+ * @param pa_nSockFd socket descriptor to send on
+ * @param pa_acData pointer to the data to send
+ * @param pa_nDataLength length of the data to send
+ * @return  EIP_SUCCESS on success
+ */
+EIP_STATUS IApp_SendUDPData(struct sockaddr_in *pa_pstAddr, int pa_nSockFd,
+    EIP_UINT8 *pa_acData, EIP_UINT16 pa_nDataLength);
+
+/**\ingroup CIP_CALLBACK_API \brief Close the given socket and clean up the stack 
+ * 
+ * @param pa_nSockFd socket descriptor to close
+ */
+void IApp_CloseSocket(int pa_nSockFd);
+
+#endif /*CIP_API_H_*/

+ 119 - 0
src/ports/platform-pc/main.c

@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+
+#include "networkhandler.h"
+#include "opener_api.h"
+#include "cipcommon.h" 
+
+/* global variables for demo application (3 assembly data fields) */
+EIP_UINT8 g_assemblydata[32]; /* Input */
+EIP_UINT8 g_assemblydata2[32]; /* Output */
+EIP_UINT8 g_assemblydata3[10]; /* Config */
+
+extern int newfd;
+
+// cast an int as a struct_inaddr (check the "inet_ntoa" man page -- it wants a struct_inaddr passed by value, not an int)
+#define INAD(ipaddr) (*(struct in_addr *)&(ipaddr))
+
+int main(int argc, char *arg[])
+  {
+    EIP_UINT8 acMyMACAddress[6];
+
+    if (argc != 12)
+      { //! use default values
+        //configureNetworkInterface("192.168.1.1", "255.255.252.0", "192.168.1.1");
+        configureNetworkInterface("128.131.86.182", "255.255.255.128", "128.131.86.129");
+        configureDomainName("azrael.acin.tuwien.ac.at");
+
+        acMyMACAddress[0] = 0x00;
+        acMyMACAddress[1] = 0x15;
+        acMyMACAddress[2] = 0xC5;
+        acMyMACAddress[3] = 0xBF;
+        acMyMACAddress[4] = 0xD0;
+        acMyMACAddress[5] = 0x87;
+        configureMACAddress(acMyMACAddress);
+        configureHostName("OpenEIP");
+      }
+    else
+      {
+        // fetch internet address info from the platform
+        configureNetworkInterface(arg[1], arg[2], arg[3]);
+        configureDomainName(arg[4]);
+        configureHostName(arg[5]);
+
+        acMyMACAddress[0] = (EIP_UINT8)strtoul(arg[6], NULL, 16);
+        acMyMACAddress[1] = (EIP_UINT8)strtoul(arg[7], NULL, 16);
+        acMyMACAddress[2] = (EIP_UINT8)strtoul(arg[8], NULL, 16);
+        acMyMACAddress[3] = (EIP_UINT8)strtoul(arg[9], NULL, 16);
+        acMyMACAddress[4] = (EIP_UINT8)strtoul(arg[10], NULL, 16);
+        acMyMACAddress[5] = (EIP_UINT8)strtoul(arg[11], NULL, 16);
+        configureMACAddress(acMyMACAddress);
+      }
+
+    /* Setup the CIP Layer */
+    CIP_Init();
+
+    Start_NetworkHandler(); /* here is the select loop implemented */
+    return -1;
+  }
+
+EIP_STATUS IApp_Init(void)
+  {
+    /* create 3 assembly object instances*/
+    /*INPUT*/
+    createAssemblyObject(1, &g_assemblydata[0], sizeof(g_assemblydata));
+
+    /*OUTPUT*/
+    createAssemblyObject(2, &g_assemblydata2[0], sizeof(g_assemblydata2));
+
+    /*CONFIG*/
+    createAssemblyObject(3, &g_assemblydata3[0], sizeof(g_assemblydata3));
+
+    return EIP_OK;
+  }
+
+EIP_STATUS IApp_AfterAssemblyDataReceived(S_CIP_Instance *pa_pstInstance)
+  {
+    //handle the data received e.g., update outputs of the device
+
+    return EIP_OK;
+  }
+
+bool IApp_BeforeAssemblyDataSend(S_CIP_Instance *pa_pstInstance)
+  {
+    //update data to be sent e.g., read inputs of the device
+    return true;
+  }
+
+EIP_STATUS IApp_ResetDevice(void)
+  {
+    // add reset code here
+    return EIP_OK;
+  }
+
+EIP_STATUS IApp_ResetDeviceToInitialConfiguration(void)
+  {
+    //rest the parameters and than perform device reset
+    return EIP_OK;
+  }
+
+void *IApp_CipCalloc(unsigned pa_nNumberOfElements, unsigned pa_nSizeOfElement)
+  {
+    return calloc(pa_nNumberOfElements, pa_nSizeOfElement);
+  }
+
+void IApp_RunIdleChanged(EIP_UINT32 pa_nRunIdleValue)
+  {
+    (void)pa_nRunIdleValue;
+  }
+

+ 460 - 0
src/ports/platform-pc/networkhandler.c

@@ -0,0 +1,460 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <opener_api.h>
+#include "networkhandler.h"
+#include <encap.h>
+#include <cipconnectionmanager.h>
+#include <endianconv.h> 
+
+
+EIP_UINT8 g_acCommBuf[OPENER_ETHERNET_BUFFER_SIZE];
+
+extern void dump(unsigned char *p, int size);
+
+#define MAX_NO_OF_TCP_SOCKETS 10
+
+typedef long MILLISECONDS;
+
+fd_set master;
+fd_set read_fds;
+/* temporary file discriptor for select() */
+
+int fdmax;
+int listener;
+int newfd;
+
+static struct timeval tv;
+static MILLISECONDS actualtime, lasttime;
+
+
+EIP_STATUS handleDataOnTCPSocket(int pa_nSocket);
+
+
+static inline MILLISECONDS getmilliseconds(void)
+  {
+    struct timeval tv;
+
+    gettimeofday(&tv, 0);
+    return (MILLISECONDS)tv.tv_sec * 1000 + (MILLISECONDS)tv.tv_usec / 1000;
+  }
+
+/*! Check if the socket indentifier is associate with an active connection
+ * 
+ *  @param pa_nFD  socket handle
+ *  @return 1 if the given socket handler is of an active conneciton otherwise 0
+ */
+int isConnectedFd(int pa_nFD)
+  {
+    int i;
+    extern S_CIP_ConnectionObject stConnectionObject[];
+
+    S_CIP_ConnectionObject *con = &stConnectionObject[0]; // this is done this way because the debugger would not display the connection table
+
+    for (i = 0; i < OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS; i++)
+      {
+        if ((stConnectionObject[i].sockfd[0] == pa_nFD)
+            || (stConnectionObject[i].sockfd[1] == pa_nFD))
+          {
+            return 1;
+          }
+      }
+    return 0;
+  }
+
+/* INT8 Start_NetworkHandler()
+ * 	start a TCP listening socket, accept connections, receive data in select loop, call manageConnections periodicaly.
+ * 	return status
+ * 			-1 .. error
+ */
+
+EIP_STATUS Start_NetworkHandler()
+  {
+    struct sockaddr_in remote_addr, from;
+    struct sockaddr_in my_addr;
+    socklen_t addrlen;
+    socklen_t fromlen = sizeof(from);
+    int nUDPListener;
+
+    int nReceived_size;
+    int res;
+    MILLISECONDS elapsedtime = 0;
+    int fd;
+    EIP_UINT8 *rxp;
+    int nRemainingBytes;
+    int replylen;
+
+    /* clear the master an temp sets */
+    FD_ZERO(&master);
+    FD_ZERO(&read_fds);
+
+    /* create a new TCP socket */
+    if ((listener = socket(PF_INET,SOCK_STREAM, 0)) == -1)
+      {
+        printf("error allocating socket stream listener, %d\n", errno);
+        return EIP_ERROR;
+      }
+
+    /* create a new UDP socket */
+    if ((nUDPListener = socket(PF_INET,SOCK_DGRAM, 0)) == -1)
+      {
+        printf("error allocating udp listener socket, %d\n", errno);
+        return EIP_ERROR;
+      }
+
+    my_addr.sin_family = AF_INET;
+    my_addr.sin_port = htons(OPENER_ETHERNET_PORT);
+    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    memset(&my_addr.sin_zero, 0, sizeof(my_addr.sin_zero));
+
+    /* bind the new socket to port 0xAF12 (CIP) */
+    if ((bind(listener, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)))
+        == -1)
+      {
+        perror("error with bind");
+        return EIP_ERROR;
+      }
+
+    if ((bind(nUDPListener, (struct sockaddr *) &my_addr,
+        sizeof(struct sockaddr))) == -1)
+      {
+        perror("error with udp bind");
+        return EIP_ERROR;
+      }
+
+    /* switch socket in listen mode */
+    if ((listen(listener, MAX_NO_OF_TCP_SOCKETS)) == -1)
+      {
+        perror("networkhandler: error with listen");
+        return EIP_ERROR;
+      }
+
+    /* add the listener socket to the master set */
+    FD_SET(listener, &master);
+    FD_SET(nUDPListener, &master);
+
+    /* keep track of the biggest file discriptor */
+    fdmax = (listener > nUDPListener) ? listener : nUDPListener;
+
+    lasttime = getmilliseconds(); /* init timekeeping */
+    elapsedtime = 0;
+
+    while (1)
+      {
+        read_fds = master;
+
+        tv.tv_sec = 0;
+        tv.tv_usec = (elapsedtime < OPENER_TIMER_TICK ? OPENER_TIMER_TICK - elapsedtime : 0)
+            * 1000; /* 10 ms */
+
+        res = select(fdmax + 1, &read_fds, 0, 0, &tv);
+        if (res == -1)
+          {
+            perror("networkhandler: error with select");
+            return EIP_ERROR;
+          }
+
+        if (res > 0)
+          for (fd = 0; fd <= fdmax; fd++)
+            {
+              if (FD_ISSET(fd, &read_fds))
+                {
+                  if (!FD_ISSET(fd, &master))
+                    {
+                      printf("socket fd %d closed with pending message\n", fd);
+                      continue;
+                    }
+
+                  // see if this is a connection request to the TCP listener
+                  if (fd == listener) // handle new TCP connection
+                    {
+                      if (EIP_DEBUG >= EIP_VERBOSE)
+                        printf("networkhandler: new TCP connection\n");
+                      addrlen = sizeof(remote_addr);
+                      newfd = accept(listener, (struct sockaddr *)&remote_addr,
+                          &addrlen); // remote_addr does not seem to be used
+                      if (newfd == -1)
+                        {
+                          perror("networkhandler: error on accept");
+                          continue;
+                        }
+
+                      FD_SET(newfd, &master);
+                      // add newfd to master set
+                      if (newfd > fdmax)
+                        fdmax = newfd;
+                      if (EIP_DEBUG >= EIP_VERBOSE)
+                        printf(
+                            "networkhandler: opened new TCP connection on fd %d\n",
+                            newfd);
+                      continue;
+                    }
+
+                  // see if this is an unsolicited inbound UDP message
+                  if (fd == nUDPListener)
+                    {
+                      if (EIP_DEBUG >= EIP_VERBOSE)
+                        printf(
+                            "networkhandler: unsolicited UDP message on fd %d\n",
+                            fd);
+
+                      /*Handle udp broadcast messages */
+                      nReceived_size = recvfrom(fd, g_acCommBuf,
+                          OPENER_ETHERNET_BUFFER_SIZE, 0, (struct sockaddr *)&from,
+                          &fromlen);
+
+                      if (nReceived_size <= 0)
+                        { /* got error */
+                          perror("networkhandler: error on recvfrom udp broadcast port");
+                          continue;
+                        }
+
+                      if (EIP_DEBUG >= EIP_VVERBOSE)
+                        {
+                          printf("Data received on udp:\n");
+                          dump(g_acCommBuf, nReceived_size);
+                        }
+
+                      rxp = &g_acCommBuf[0];
+                      do
+                        {
+                          replylen = handleReceivedExplictData(fd, rxp, nReceived_size,
+                              &nRemainingBytes);
+
+                          rxp += nReceived_size - nRemainingBytes;
+                          nReceived_size = nRemainingBytes;
+
+                          if (replylen > 0)
+                            {
+                              if (EIP_DEBUG >= EIP_VVERBOSE)
+                                {
+                                  printf("reply sent:\n");
+                                  dump(g_acCommBuf, replylen);
+                                }
+
+                              // if the active fd matches a registered UDP callback, handle a UDP packet
+                              res = sendto(fd, (char *)g_acCommBuf, replylen,
+                                  0, (struct sockaddr *)&from, sizeof(from));
+                              if (res != replylen)
+                                {
+                                  printf("networkhandler: UDP response was not fully sent\n");
+                                }
+                            }
+                        } while (nRemainingBytes > 0);
+                      continue;
+                    }
+
+                  // see if the message is from a registered UDP socket
+                  if (isConnectedFd(fd))
+                    {
+                      fromlen = sizeof(from);
+                      nReceived_size = recvfrom(fd, g_acCommBuf, 
+                          OPENER_ETHERNET_BUFFER_SIZE, 0, (struct sockaddr *)&from, &fromlen);
+                      if (nReceived_size == 0)
+                        {
+                          printf("connection closed by client\n");
+                          FD_CLR(fd, &master);
+                          /* remove from master set */
+                          close(fd); /* close socket */
+                          continue;
+                        }
+                      if (nReceived_size <= 0)
+                        {
+                          perror("networkhandler: error on recv");
+                          FD_CLR(fd, &master);
+                          /* remove from master set */
+                          close(fd); /* close socket */
+                          continue;
+                        }
+
+                      handleReceivedConnectedData(g_acCommBuf, nReceived_size);
+                      continue;
+                    }
+
+                  // if not registered UDP, handle as a TCP receive
+                  if (EIP_ERROR == handleDataOnTCPSocket(fd)) /* if error */
+                    {
+                      FD_CLR(fd, &master);
+                      /* remove connection from master set */
+                      closeSession(fd); /* clean up session and close the socket */
+                    }
+                }
+            }
+
+        actualtime = getmilliseconds();
+        elapsedtime += actualtime - lasttime;
+        lasttime = actualtime;
+
+        /* check if we had been not able to update the connection manager for several OPENER_TIMER_TICK.
+         * This should compensate the jitter of the windows timer
+         */
+        while(elapsedtime >= OPENER_TIMER_TICK)
+          {
+            /* call manage_connections() in connection manager every OPENER_TIMER_TICK ms */
+            manageConnections();              
+            elapsedtime -= OPENER_TIMER_TICK; 
+          }  
+      }
+  }
+
+EIP_STATUS IApp_SendUDPData(struct sockaddr_in *pa_pstAddr, int pa_nSockFd,
+    EIP_UINT8 *pa_acData, EIP_UINT16 pa_nDataLength)
+  {
+    int sentlength;
+
+    sentlength = sendto(pa_nSockFd, (char *)pa_acData, pa_nDataLength, 0,
+        (struct sockaddr *)pa_pstAddr, sizeof(*pa_pstAddr));
+
+    if (sentlength < 0)
+      {
+        perror("networkhandler: error with sendto in sendUDPData");
+        return EIP_ERROR;
+      }
+    else if (sentlength != pa_nDataLength)
+      {
+        printf("not all data was sent in sendUDPData, sent %d of %d\n",
+            sentlength, pa_nDataLength);
+        return EIP_ERROR;
+      }
+    else
+      return EIP_OK;
+  }
+
+EIP_STATUS handleDataOnTCPSocket(int pa_nSocket)
+  {
+    EIP_UINT8 *rxp;
+    int nDataSize;
+    int nDataSent;
+    int nRemainingBytes = 0;
+
+    // We will handle just one EIP packet here the rest is done by the select method which will inform us if more data is available in the socket
+    // because of the current implementation of the main loop this may not be the fastest way and a loop here with a non blocking socket would better fit
+
+    //Check how many data is here -- read the first four bytes from the connection
+    nDataSize = recv(pa_nSocket, g_acCommBuf, 4, 0); //TODO we may have to set the socket to a non blocking socket
+
+    if (nDataSize == 0)
+      {
+        perror("networkhandler: connection closed by client");
+        return EIP_ERROR;
+      }
+    if (nDataSize < 0)
+      {
+        perror("networkhandler: error on recv");
+        return EIP_ERROR;
+      }
+
+    rxp = &g_acCommBuf[2]; // at this place EIP stores the data length
+    nDataSize = ltohs(&rxp) + ENCAPSULATION_HEADER_LENGTH - 4; // -4 is for the 4 bytes we have already read
+    // (NOTE this advances the buffer pointer)
+    if (OPENER_ETHERNET_BUFFER_SIZE < nDataSize)
+      { //TODO can this be handled in a better way?
+        printf("too large packet recieved will be ignored\n"); // this may corrupt the connection ???
+        return EIP_OK;
+      }
+
+    nDataSize = recv(pa_nSocket, &g_acCommBuf[4], nDataSize, 0);
+
+    if (nDataSize == 0) /* got error or connection closed by client */
+      {
+        perror("networkhandler: connection closed by client");
+        return EIP_ERROR;
+      }
+    if (nDataSize < 0)
+      {
+        perror("networkhandler: error on recv");
+        return EIP_ERROR;
+      }
+
+    nDataSize += 4;
+    //TODO handle partial packets
+    if (EIP_DEBUG> EIP_VVERBOSE)
+      {
+        printf("Data received on tcp:\n");
+        dump(g_acCommBuf, nDataSize);
+      }
+
+    nDataSize = handleReceivedExplictData(pa_nSocket, g_acCommBuf, nDataSize,
+        &nRemainingBytes);
+    if (nRemainingBytes != 0)
+      {
+        if (EIP_DEBUG >= EIP_TERSE)
+          printf("Warning: received packet was to long: %d Bytes left!\n",
+              nRemainingBytes);
+      }
+
+    if (nDataSize > 0)
+      {
+        if (EIP_DEBUG >= EIP_VVERBOSE)
+          {
+            printf("reply sent:\n");
+            dump(g_acCommBuf, nDataSize);
+          }
+
+        nDataSent = send(pa_nSocket, (char *)g_acCommBuf, nDataSize, 0);
+        if (nDataSent != nDataSize)
+          {
+            if (EIP_DEBUG >= EIP_TERSE)
+              {
+                printf("TCP response was not fully sent\n");
+              }
+          }
+      }
+
+    return EIP_OK;
+  }
+
+// create a new UDP socket for the connection manager
+// returns the fd if successful, else -1
+int IApp_CreateUDPSocket(int pa_nDirection, struct sockaddr_in *pa_pstAddr)
+  {
+    /* create a new UDP socket */
+    if ((newfd = socket(PF_INET,SOCK_DGRAM, 0)) == -1)
+      {
+        printf("networkhandler: cannot create UDP socket\n");
+        return EIP_ERROR;
+      }
+    if (EIP_DEBUG >= EIP_VERBOSE)
+      printf("networkhandler: UDP socket %d\n", newfd);
+
+    /* check if it is sending or receiving */
+    if (pa_nDirection == CONSUMING)
+      { /* bind is only for consuming necessary */
+        if ((bind(newfd, (struct sockaddr *)pa_pstAddr, sizeof(struct sockaddr)))
+            == -1)
+          {
+            printf("error on bind udp\n");
+            return EIP_ERROR;
+          }
+        if (EIP_DEBUG >= EIP_VERBOSE)
+          printf("networkhandler: bind UDP socket %d\n", newfd);
+      }
+
+    // add new fd to the master list
+    FD_SET(newfd, &master);
+    if (newfd > fdmax)
+      {
+        fdmax = newfd;
+      }
+    return newfd;
+  }
+
+void IApp_CloseSocket(int pa_nSockFd)
+  {
+    if(EIP_INVALID_SOCKET != pa_nSockFd)
+      {
+        FD_CLR(pa_nSockFd, &master);
+        close(pa_nSockFd);
+      }  
+  }

+ 20 - 0
src/ports/platform-pc/networkhandler.h

@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef NETWORKHANDLER_H_
+#define NETWORKHANDLER_H_
+
+#include "typedefs.h"
+
+
+/*! Start a TCP/UDP listening socket, accept connections, receive data in select loop, call manageConnections periodicaly.
+ *  @return status
+ *          EIP_ERROR .. error
+ */
+EIP_STATUS Start_NetworkHandler(void);
+
+#endif /*NETWORKHANDLER_H_*/

+ 74 - 0
src/ports/platform-pc/opener_user_conf.h

@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef OPENER_USER_CONF_H_
+#define OPENER_USER_CONF_H_
+
+/*! Identiy configuration of the device */
+#define OPENER_DEVICE_VENDOR_ID           1
+#define OPENER_DEVICE_TYPE               12
+#define OPENER_DEVICE_PRODUCT_CODE      200
+#define OPENER_DEVICE_MAJOR_REVISION      1
+#define OPENER_DEVICE_MINOR_REVISION      2
+#define OPENER_DEVICE_SERIAL       12345678      //TODO -- this must made settable per device instance
+#define OPENER_DEVICE_NAME      "ENetIP EC"
+
+
+/*! The port address to be used for udp multi cast connections
+ */ 
+#define OPENER_UDP_MULTICAST_PORT   2222
+
+
+/*! Define the number of supported connections (Class 0, 1, and 3 together)
+ */  
+#define OPENER_NUMBER_OF_SUPPORTED_CONNECTIONS 10
+
+
+/*! The number of bytes used for the ethernet message buffer.
+ *  This buffer size will be used for any recieved message.
+ *  The same buffer is used for the replied eplicit message.
+ */ 
+#define OPENER_ETHERNET_BUFFER_SIZE 512
+
+
+/*! The number of bytes used for the buffer that will be used for generating any
+ *  reply data of messages. There are two uses in OpENer:
+ *    1. Explicit messages will use this buffer to store the data generated by the request
+ *    2. I/O Connections will use this buffer for the produced data
+ */ 
+#define OPENER_MESSAGE_DATA_REPLY_BUFFER 100
+
+/*! Number of sessions that can be handled at the same time
+ */ 
+#define OPENER_NUMBER_OF_SUPPORTED_SESSIONS 4
+
+/*! The time in ms of the timmer used in this implementations
+ */ 
+#define OPENER_TIMER_TICK 10 
+
+
+/*! Define if RUN IDLE data is sent with consumed data
+ */ 
+#define OPENER_CONSUMED_DATA_HAS_RUN_IDLE_HEADER 1
+
+
+/* Platform specific network include files
+ * Opener needs defintions for the follwing data-types 
+ * and functions:
+ *    - struct sockaddr_in
+ *    - AF_INET
+ *    - INADDR_ANY
+ *    - htons
+ *    - ntohl
+ *    - inet_addr
+ */
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+
+#endif /*OPENER_USER_CONF_H_*/

+ 46 - 0
src/ports/platform-pc/stubs.c

@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+
+static char dumpbuf[256];
+
+void dump(unsigned char *p, int size)
+  {
+    int i;
+    char *b;
+
+    while (size > 0)
+      {
+        b = dumpbuf;
+        // b += sprintf(b,"%08x: ",(int)p);
+        for (i = 0; i < 16; i++)
+          {
+            if (i < size)
+              b += sprintf(b, "%02x ", p[i]);
+            else
+              b += sprintf(b, "   ");
+          }
+        b += sprintf(b, " |");
+        for (i = 0; i < 16; i++)
+          {
+            if (i < size)
+              {
+                if (' ' <= p[i] && p[i] < 0x7f)
+                  b += sprintf(b, "%c", p[i]);
+                else
+                  b += sprintf(b, ".");
+              }
+          }
+        p += 16;
+        size -= 16;
+        printf("%s\n", dumpbuf);
+      }
+  }

+ 94 - 0
src/typedefs.h

@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2009, Rockwell Automation, Inc.
+ * All rights reserved. 
+ *
+ * Contributors:
+ *     <date>: <author>, <author email> - changes
+ ******************************************************************************/
+#ifndef TYPEDEFS_H_
+#define TYPEDEFS_H_
+
+/*
+
+ Do not use interface types for internal variables, such as "int i;", which is commonly used for loop counters or counting things.
+
+ Do not over-constrain data types. Prefer the use of the native "int" and "unsigned" types.
+
+ Use char for native character strings.
+
+ Do not use "char" for data buffers - use "unsigned char" instead. Using char for data buffers can occasionally blow up in your face rather nastily.
+
+
+ */
+
+#define EIP_BYTE 	unsigned char
+#define EIP_INT8 	char
+#define EIP_INT16	short
+#define EIP_INT32	long
+#define EIP_UINT8	unsigned char
+#define EIP_UINT16	unsigned short
+#define EIP_UINT32	unsigned long
+#define EIP_FLOAT	float
+#define EIP_DFLOAT	double
+#define EIP_BOOL8	bool
+
+/*! Constant idenfying if a socket desciptor is invalid
+ */ 
+#define EIP_INVALID_SOCKET      -1
+
+/*
+
+ The following are generally true regarding return status:
+ -1 ... an error occurred
+ 0 ... success
+
+ Occasionally there is a variation on this:
+ -1 ... an error occurred
+ 0 ..  success and there is no reply to send
+ 1 ... success and there is a reply to send
+
+ For both of these cases EIP_STATUS is the return type.
+
+ Other return type are:
+ -- return pointer to thing, 0 if error (return type is "pointer to thing")
+ -- return count of something, -1 if error, (return type is int)
+
+ */
+
+typedef enum
+  {
+    EIP_OK = 0,
+    EIP_OK_SEND = 1,
+    EIP_ERROR = -1,
+  } EIP_STATUS;
+
+typedef enum // define C++ -like "bool"
+  {
+    false=0,
+    true=1
+  }bool;
+
+// by default an enum is 32 bits
+// __attribute((packed)) allows the compiler to use a shorter data type
+// the following forces an enum to a specified minimum length, it also documents the size of the enum
+//
+// example:
+//
+// typedef enum { A, B, C, FOO_PACKED_SIZE=ENUM_UINT16} PACKED FOO;		// this forces the field to be 16 bits long, even though the defined values could be contained in 8 bits
+//										// the definition FOO_PACKED_SIZE is a dummy, but it forces the minimum size
+
+// TODO -- find some portable way of dealing with packed structs and typed enums
+#ifdef __GNUC__
+#define PACKED __attribute__((packed))
+
+#define ENUM_INT8 0x7f
+#define ENUM_INT16  0x7fff
+#define ENUM_INT32  0x7fffffff
+
+#endif
+
+#define EIP_SILENT	0	// print nothing
+#define EIP_TERSE	1	// print signifcant events and error messages
+#define EIP_VERBOSE	2	// print a running commentary on many events
+#define EIP_VVERBOSE	3	// print a running commentary plus dumps of messages
+#endif /*TYPEDEFS_H_*/