Read Me
-------- SIMULATOR FOR FOR BACnet/WS 2 (Addendum 135-2012am-PPR1) -------------
This is a simulator for the BACnet Web Services (BACnet/WS) protocol proposed
in First Public Review of Addendum am to ANSI/ASHRAE Standard 135, Spring 2014.
(Not to be confused with the earlier "First *Advisory* Public Review")
This is mostly proof-of-concept code, but it tries to be hard-to-crash so it
can be used for unattended testing. The emphasis has been on simplicity, rapid
development, and ease of debugging, so it is definitely not optimized for a
production deployment. It is mostly single threaded and was written with an
emphasis on portability to other languages, so it does not leverage many of the
fancy features of modern Java (other than generics to avoid a lot of casting).
The primary purpose for writing it was to spur development of prototypes to
test the protocol. However, it has a BSD license, so you can do whatever you
want with it, even for commercial purposes.
Initially, this is just an implementation of the server side of the protocol.
Future enhancements may include the ability to be a data-driven client
simulator, and thus morph into a simple form of an automated testing tool.
The speed of development of this has left it with few comments; but hopefully
well-named classes, methods, and variables will help make things clear. Even
so, an IDE with a "Find Usages" feature will be very helpful!
--------------------------- MAKING AND RUNNING --------------------------------
Tester.java contains an example main() that starts and stops the server.
Project files for IntelliJ Idea 13 Community Edition (free) are included, but
you can also just use the command lines:
javac -sourcepath src -d out src/org/ampii/xd/tests/Tester.java
java -cp out org.ampii.xd.tests.Tester
The main configuration constants are in Application.java, e.g.,
public static String configFile = "resources/config/config.xml";
public static int serverPort = 8080;
public static int serverSSLPort = 4443;
public static Locale locale = Locale.US;
public static Level generalLogLevel = Level.INFO;
public static Level httpLogLevel = Level.FINE;
public static boolean autoactivateTLS = true;
For testing, Firefox has a add-on called "RESTClient" that is useful for
testing. Tip: Keeping multiple tabs open with RESTClient allows for easier
switching between messages (e.g. one for PUT/POST and one for GET) rather than
trying to switch back and forth using the drop-downs on one tab.
--------------------------------- DATA ----------------------------------------
This is entirely static data driven. It loads all of its test data from XML
files at startup and does not save anything. It also does not attempt to
connect to a real communications back end for "live" data. A good follow-on
project would be to hook the /.bacnet tree data to a real BACnet stack using
preread() and postwrite() handlers.
All the data in the server is loaded via XML files specified by the
Application.configFile constant (and changed by commandline arg to
Tester.java). Only the root and the "/.definitions" node are created
automatically, everything else (including TLS certificate/key) comes from the
xml files.
If you want to test factory default conditions, set the user/pass to ".",
remove the TLS cert/key info, and set Application.autoactivateTLS to false.
This will then wait for external writes of that data before attempting to
start TLS.
There is an example subscription
------------------------------ IMPLEMENTED -----------------------------------
Everything in the PPR1 addendum for servers is implemented except as noted
below in TO DO / NOT IMPLEMENTED YET. (this is a loooong list of features, not
included in this doc - go read the addendum)
This code makes no attempt to allow multiple server instances in one JVM (as
the use of static constants in the Application class indicates).
The XML implementation is the minimum needed for interoperability. It does not
support general namespaces or entities beyond the required ones (" etc).
However, it does support a fixed secondary namespace to allow proprietary
attributes that this application uses to tag certain data with behavior
(for setting rules for preread(), postwrite(), etc.)
Most of the code tries to be just a generic-server-of-data without any
application-specific "behavior". All the things that are *not* generic are in
the "application" package. This includes:
AccessHooks: this is where the ties to a back end can be done.
HTTPHooks: this traps any special URI paths that are not normal data.
XMLHooks: this handles the extra namespace that configures the access hooks.
Watcher: the thread that monitors the .subscriptions and .multi records.
------------------------ TO DO / NOT IMPLEMENTED YET --------------------------
It would be useful to make console logs available through the web interface for
remote testers.
historyPeriodic() is not implemented.
errorPrefix and errorText query parameters are not implemented.
Can't handle pure write-only data:
e.g., /.auth/dev-key-pend is readable while in factory defaults mode
and /auth/int/pass is readable, with "auth", when not in factory defaults
mode. These seem harmless though, so perhaps we will change the addendum
after PR (write-only data is a pain). In general: Client must present
sufficient scope(s) to be able to read data before writing is considered
(i.e. paths are read before writing). Is this a bug? Or is the idea that
you can have permission to write data that you can't also read just dumb
and the code is actually correct?
Client.java uses HttpURLConnection.
It would be helpful for language portability to remove dependency on this
library functionality and implement on raw sockets like the server side
(started but didn't finish code for this).
Some limits/restrictions are not enforced:
Metadata like 'maximum' and 'allowedChoices' are not checked. Only
basetype is checked for PUT. And XxxxPattern basetypes don't parse
their data; they just contain an unparsed string with no error checking:
e.g. "2013-fred-14" is OK.
Definitions are not checked:
For Choice: only one child is allowed but it is not checked against the
members of the 'choices' metadata.
For Enumeration: any string is accepted, no check of 'namedValues'.
For BitString: and bit names are accepted, no check of 'namedBits'.
For BitString: and bit names are accepted, no check of 'namedBits'.
Root certificates:
/.auth/root-cert-pend is not used to validate the /.auth/dev-cert-pend.
External authorization servers:
Tokens from external authorizations servers are not processed; only the
internal auth server is implemented.
Group audiences:
/.auth/group-uuids is not used for token verification.
remote() function:
Not implemented - need to figure out a method to determine if a URI refers
to this server. Relative paths are easy, but how do I know if a URI
hostname resolves to me or not?
/.bacnet data:
The data in the /.bacnet tree is "dead". It is not tied to any kind of
communications back end. There is also no local "behavior". e.g., priority
arrays don't function.
'priority' query parameter:
the priority query parameter is parsed but nothing is done with it yet
since the /.bacnet data has no "behavior".
".required" and ".optional":
the 'select' query works but the special values ".required" and
".optional" don't do anything.
Reading a range of an OctetString or String:
the 'skip' and 'max-results' query parameters do not affect the results of
a String or OctetString like Clause XX.16.3 says
Filter paths with "*" segments:
the 'select' query supports the syntax "*/*/foo" but filter doesn't yet
(not actually required by the draft, but should be!)
alt=media:
the method to access an OctetString as a "media" blob (e.g. returning
"Content-Type: application/pdf") is not supported.
next:
client-driven limiting with 'max-results' query works, but the server
currently doesn't do any response limiting on its own, so there's no way
to generate a 'next' link.
.self:
the "/.bacnet/xxx/.self" is not a magic alias for the server's device in
scope xxx
--------------- THINGS IMPLEMENTED THAT ARE NOT IN SPEC -----------------------
@basetype:
useful for plain text clients that otherwise would not have a way to know
the base type of the data